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 (5089B)


      1 {-# OPTIONS_HADDOCK hide #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 
      4 -- |
      5 -- Module: Lightning.Protocol.BOLT7.Hash
      6 -- Copyright: (c) 2025 Jared Tobin
      7 -- License: MIT
      8 -- Maintainer: Jared Tobin <jared@ppad.tech>
      9 --
     10 -- Signature digests, signature verification and checksums for BOLT #7
     11 -- messages.
     12 --
     13 -- Signatures cover the encoded message after its signature fields,
     14 -- extension included. Since decoding keeps every byte, the digest of a
     15 -- decoded message is the digest of the bytes received.
     16 
     17 module Lightning.Protocol.BOLT7.Hash (
     18     channel_announcement_hash
     19   , node_announcement_hash
     20   , channel_update_hash
     21   , verify_channel_announcement
     22   , verify_node_announcement
     23   , verify_channel_update
     24   , channel_update_checksum
     25   ) where
     26 
     27 import qualified Crypto.Curve.Secp256k1 as Secp256k1
     28 import qualified Crypto.Hash.SHA256 as SHA256
     29 import qualified Data.ByteString as BS
     30 import Data.Word (Word32)
     31 import Lightning.Protocol.BOLT1 (Point, Signature)
     32 import qualified Lightning.Protocol.BOLT1 as BOLT1
     33 import Lightning.Protocol.BOLT7.CRC32C (crc32c)
     34 import Lightning.Protocol.BOLT7.Codec
     35 import Lightning.Protocol.BOLT7.Messages
     36 
     37 -- the signed bytes of each message
     38 announcement_signed :: ChannelAnnouncement -> Either EncodeError BS.ByteString
     39 announcement_signed = fmap (BS.drop 256) . encode_channel_announcement
     40 {-# INLINE announcement_signed #-}
     41 
     42 node_signed :: NodeAnnouncement -> Either EncodeError BS.ByteString
     43 node_signed = fmap (BS.drop 64) . encode_node_announcement
     44 {-# INLINE node_signed #-}
     45 
     46 update_signed :: ChannelUpdate -> BS.ByteString
     47 update_signed = BS.drop 64 . encode_channel_update
     48 {-# INLINE update_signed #-}
     49 
     50 double_sha256 :: BS.ByteString -> BS.ByteString
     51 double_sha256 = SHA256.hash . SHA256.hash
     52 {-# INLINE double_sha256 #-}
     53 
     54 -- | The digest the four signatures of a t'ChannelAnnouncement' sign: the
     55 --   double SHA-256 of its encoding from offset 256 (after the
     56 --   signatures) to the end. Fails as 'encode_channel_announcement' does.
     57 channel_announcement_hash
     58   :: ChannelAnnouncement -> Either EncodeError BS.ByteString
     59 channel_announcement_hash = fmap double_sha256 . announcement_signed
     60 
     61 -- | The digest the signature of a t'NodeAnnouncement' signs: the double
     62 --   SHA-256 of its encoding after the signature. Fails as
     63 --   'encode_node_announcement' does.
     64 node_announcement_hash
     65   :: NodeAnnouncement -> Either EncodeError BS.ByteString
     66 node_announcement_hash = fmap double_sha256 . node_signed
     67 
     68 -- | The digest the signature of a t'ChannelUpdate' signs: the double
     69 --   SHA-256 of its encoding after the signature.
     70 channel_update_hash :: ChannelUpdate -> BS.ByteString
     71 channel_update_hash = double_sha256 . update_signed
     72 
     73 -- | Verify the four signatures of a t'ChannelAnnouncement': both node
     74 --   signatures and both bitcoin signatures, each over
     75 --   'channel_announcement_hash' under the corresponding key.
     76 --
     77 --   As with libsecp256k1, signatures must be in low-s form. A key that
     78 --   isn't a point on the curve fails verification.
     79 verify_channel_announcement :: ChannelAnnouncement -> Bool
     80 verify_channel_announcement m = case announcement_signed m of
     81   Left _ -> False
     82   Right signed ->
     83     let !d = SHA256.hash signed
     84     in  verify d (ca_node_id_1 m) (ca_node_signature_1 m)
     85           && verify d (ca_node_id_2 m) (ca_node_signature_2 m)
     86           && verify d (ca_bitcoin_key_1 m) (ca_bitcoin_signature_1 m)
     87           && verify d (ca_bitcoin_key_2 m) (ca_bitcoin_signature_2 m)
     88 
     89 -- | Verify the signature of a t'NodeAnnouncement' under its node id.
     90 --
     91 --   As with libsecp256k1, the signature must be in low-s form.
     92 verify_node_announcement :: NodeAnnouncement -> Bool
     93 verify_node_announcement m = case node_signed m of
     94   Left _       -> False
     95   Right signed -> verify (SHA256.hash signed) (na_node_id m) (na_signature m)
     96 
     97 -- | Verify the signature of a t'ChannelUpdate' under the given node id.
     98 --
     99 --   The key is that of the channel's t'ChannelAnnouncement' selected by
    100 --   the update's direction: 'ca_node_id_1' for
    101 --   'Lightning.Protocol.BOLT7.NodeOne', 'ca_node_id_2' for
    102 --   'Lightning.Protocol.BOLT7.NodeTwo'. As with libsecp256k1, the
    103 --   signature must be in low-s form.
    104 verify_channel_update :: Point -> ChannelUpdate -> Bool
    105 verify_channel_update k m =
    106   verify (SHA256.hash (update_signed m)) k (cu_signature m)
    107 
    108 -- verify a signature over the double SHA-256 of some bytes, given their
    109 -- single SHA-256 (secp256k1's verify_ecdsa hashes once more)
    110 verify :: BS.ByteString -> Point -> Signature -> Bool
    111 verify d k s =
    112   case ( Secp256k1.parse_point (BOLT1.un_point k)
    113        , Secp256k1.parse_sig (BOLT1.un_signature s) ) of
    114     (Just p, Just sig) -> Secp256k1.verify_ecdsa' Secp256k1.precompute d p sig
    115     _                  -> False
    116 
    117 -- | The checksum of a t'ChannelUpdate', as used in the @checksums_tlv@ of
    118 --   @reply_channel_range@: the CRC-32C (RFC 3720) of the update without
    119 --   its signature and timestamp. Extension records are included.
    120 channel_update_checksum :: ChannelUpdate -> Word32
    121 channel_update_checksum m =
    122   let !bs = encode_channel_update m
    123   in  crc32c (BS.take 40 (BS.drop 64 bs) <> BS.drop 108 bs)