bolt7

Routing gossip protocol, per BOLT #7 (docs.ppad.tech/bolt7).
git clone git://git.ppad.tech/bolt7.git
Log | Files | Refs | README | LICENSE

Hash.hs (3753B)


      1 {-# OPTIONS_HADDOCK prune #-}
      2 
      3 {-# LANGUAGE BangPatterns #-}
      4 
      5 -- |
      6 -- Module: Lightning.Protocol.BOLT7.Hash
      7 -- Copyright: (c) 2025 Jared Tobin
      8 -- License: MIT
      9 -- Maintainer: Jared Tobin <jared@ppad.tech>
     10 --
     11 -- Signature hash computation for BOLT #7 messages.
     12 --
     13 -- These functions compute the double-SHA256 hash that is signed in each
     14 -- message type. The hash covers the message content excluding the
     15 -- signature field(s).
     16 
     17 module Lightning.Protocol.BOLT7.Hash (
     18   -- * Signature hashes
     19     channelAnnouncementHash
     20   , nodeAnnouncementHash
     21   , channelUpdateHash
     22 
     23   -- * Checksums
     24   , channelUpdateChecksum
     25   ) where
     26 
     27 import Data.ByteString (ByteString)
     28 import qualified Data.ByteString as BS
     29 import Data.Word (Word32)
     30 import qualified Crypto.Hash.SHA256 as SHA256
     31 import Lightning.Protocol.BOLT7.CRC32C (crc32c)
     32 import Lightning.Protocol.BOLT7.Types (signatureLen, chainHashLen)
     33 
     34 -- | Double SHA-256 hash (used for Lightning message signing).
     35 doubleSha256 :: ByteString -> ByteString
     36 doubleSha256 = SHA256.hash . SHA256.hash
     37 {-# INLINE doubleSha256 #-}
     38 
     39 -- | Compute signature hash for channel_announcement.
     40 --
     41 -- The hash covers the message starting at byte offset 256, which is after
     42 -- the four 64-byte signatures (node_sig_1, node_sig_2, bitcoin_sig_1,
     43 -- bitcoin_sig_2).
     44 --
     45 -- Returns the double-SHA256 hash (32 bytes).
     46 channelAnnouncementHash :: ByteString -> ByteString
     47 channelAnnouncementHash !msg =
     48   let offset = 4 * signatureLen
     49       payload = BS.drop offset msg
     50   in  doubleSha256 payload
     51 {-# INLINE channelAnnouncementHash #-}
     52 
     53 -- | Compute signature hash for node_announcement.
     54 --
     55 -- The hash covers the message starting after the signature field (64 bytes).
     56 --
     57 -- Returns the double-SHA256 hash (32 bytes).
     58 nodeAnnouncementHash :: ByteString -> ByteString
     59 nodeAnnouncementHash !msg =
     60   let payload = BS.drop signatureLen msg
     61   in  doubleSha256 payload
     62 {-# INLINE nodeAnnouncementHash #-}
     63 
     64 -- | Compute signature hash for channel_update.
     65 --
     66 -- The hash covers the message starting after the signature field (64 bytes).
     67 --
     68 -- Returns the double-SHA256 hash (32 bytes).
     69 channelUpdateHash :: ByteString -> ByteString
     70 channelUpdateHash !msg =
     71   let payload = BS.drop signatureLen msg
     72   in  doubleSha256 payload
     73 {-# INLINE channelUpdateHash #-}
     74 
     75 -- | Compute checksum for channel_update.
     76 --
     77 -- This is the CRC-32C of the channel_update message excluding the
     78 -- signature field (bytes 0-63) and timestamp field (bytes 96-99).
     79 --
     80 -- The checksum is used in the checksums_tlv of reply_channel_range.
     81 --
     82 -- Message layout after signature:
     83 --   - chain_hash: 32 bytes (offset 64-95)
     84 --   - short_channel_id: 8 bytes (offset 96-103)
     85 --   - timestamp: 4 bytes (offset 104-107) -- EXCLUDED
     86 --   - message_flags: 1 byte (offset 108)
     87 --   - channel_flags: 1 byte (offset 109)
     88 --   - cltv_expiry_delta: 2 bytes (offset 110-111)
     89 --   - htlc_minimum_msat: 8 bytes (offset 112-119)
     90 --   - fee_base_msat: 4 bytes (offset 120-123)
     91 --   - fee_proportional_millionths: 4 bytes (offset 124-127)
     92 --   - htlc_maximum_msat: 8 bytes (offset 128-135, if present)
     93 channelUpdateChecksum :: ByteString -> Word32
     94 channelUpdateChecksum !msg =
     95   let -- Offset 64: chain_hash (32 bytes)
     96       chainHash = BS.take chainHashLen (BS.drop signatureLen msg)
     97       -- Offset 96: short_channel_id (8 bytes)
     98       scid = BS.take 8 (BS.drop (signatureLen + chainHashLen) msg)
     99       -- Skip timestamp (4 bytes at offset 104)
    100       -- Offset 108 to end: rest of message
    101       restOffset = signatureLen + chainHashLen + 8 + 4  -- 64 + 32 + 8 + 4 = 108
    102       rest = BS.drop restOffset msg
    103       -- Concatenate: chain_hash + scid + (message without sig and timestamp)
    104       checksumData = BS.concat [chainHash, scid, rest]
    105   in  crc32c checksumData
    106 {-# INLINE channelUpdateChecksum #-}