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 #-}