bolt7

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

commit 20dea434be34eee354da2b7acd7365ba9de68b30
parent 7aa54cabff49e52ca9f4ea94f4e430eb18fd3cc7
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 19 Apr 2026 12:45:58 +0800

lib: use shared types from ppad-bolt1

Import ChainHash, ShortChannelId, ChannelId, Signature, and Point
from BOLT1.Prim instead of defining them locally. Add scidFromBytes
and scidToBytes helpers for wire-format conversion of the now
Word64-backed ShortChannelId. Update all accessor references
(getX -> unX pattern) in Codec.hs and Validate.hs.

Diffstat:
Mflake.lock | 8++++----
Mlib/Lightning/Protocol/BOLT7/Codec.hs | 48++++++++++++++++++++++++------------------------
Mlib/Lightning/Protocol/BOLT7/Types.hs | 195+++++++++++++++++++++++--------------------------------------------------------
Mlib/Lightning/Protocol/BOLT7/Validate.hs | 2+-
4 files changed, 86 insertions(+), 167 deletions(-)

diff --git a/flake.lock b/flake.lock @@ -120,11 +120,11 @@ ] }, "locked": { - "lastModified": 1776481687, - "narHash": "sha256-1oT11gu84v1wEmOrigp6FTP1DIG7JkD1OVdgOCCPQFs=", + "lastModified": 1776570879, + "narHash": "sha256-XsgGBvYWL+sD7pDZoPPi4l39DE7GH7maNnhm8iUeB/E=", "ref": "master", - "rev": "0a19559d878ad6701d9d10fd08a32b736bcee662", - "revCount": 26, + "rev": "20ea43188d781368e5e64c7c646285a6b0aaeb94", + "revCount": 27, "type": "git", "url": "git://git.ppad.tech/bolt1.git" }, diff --git a/lib/Lightning/Protocol/BOLT7/Codec.hs b/lib/Lightning/Protocol/BOLT7/Codec.hs @@ -165,7 +165,7 @@ decodeChainHash = decodeFixed chainHashLen DecodeInvalidChainHash chainHash decodeShortChannelId :: ByteString -> Either DecodeError (ShortChannelId, ByteString) decodeShortChannelId = - decodeFixed shortChannelIdLen DecodeInvalidShortChannelId shortChannelId + decodeFixed shortChannelIdLen DecodeInvalidShortChannelId scidFromBytes {-# INLINE decodeShortChannelId #-} -- | Decode ChannelId (32 bytes). @@ -249,17 +249,17 @@ decodeAddresses bs = do -- | Encode channel_announcement message. encodeChannelAnnouncement :: ChannelAnnouncement -> ByteString encodeChannelAnnouncement msg = mconcat - [ getSignature (channelAnnNodeSig1 msg) - , getSignature (channelAnnNodeSig2 msg) - , getSignature (channelAnnBitcoinSig1 msg) - , getSignature (channelAnnBitcoinSig2 msg) + [ unSignature (channelAnnNodeSig1 msg) + , unSignature (channelAnnNodeSig2 msg) + , unSignature (channelAnnBitcoinSig1 msg) + , unSignature (channelAnnBitcoinSig2 msg) , encodeLenPrefixed (getFeatureBits (channelAnnFeatures msg)) - , getChainHash (channelAnnChainHash msg) - , getShortChannelId (channelAnnShortChanId msg) + , unChainHash (channelAnnChainHash msg) + , scidToBytes (channelAnnShortChanId msg) , getNodeId (channelAnnNodeId1 msg) , getNodeId (channelAnnNodeId2 msg) - , getPoint (channelAnnBitcoinKey1 msg) - , getPoint (channelAnnBitcoinKey2 msg) + , unPoint (channelAnnBitcoinKey1 msg) + , unPoint (channelAnnBitcoinKey2 msg) ] -- | Decode channel_announcement message. @@ -302,7 +302,7 @@ encodeNodeAnnouncement msg = do if BS.length features > 65535 then Left EncodeLengthOverflow else Right $ mconcat - [ getSignature (nodeAnnSignature msg) + [ unSignature (nodeAnnSignature msg) , encodeLenPrefixed features , Prim.encodeU32 (nodeAnnTimestamp msg) , getNodeId (nodeAnnNodeId msg) @@ -365,9 +365,9 @@ decodeNodeAnnouncement bs = do -- | Encode channel_update message. encodeChannelUpdate :: ChannelUpdate -> ByteString encodeChannelUpdate msg = mconcat - [ getSignature (chanUpdateSignature msg) - , getChainHash (chanUpdateChainHash msg) - , getShortChannelId (chanUpdateShortChanId msg) + [ unSignature (chanUpdateSignature msg) + , unChainHash (chanUpdateChainHash msg) + , scidToBytes (chanUpdateShortChanId msg) , Prim.encodeU32 (chanUpdateTimestamp msg) , BS.singleton (encodeMessageFlags (chanUpdateMsgFlags msg)) , BS.singleton (encodeChannelFlags (chanUpdateChanFlags msg)) @@ -422,10 +422,10 @@ decodeChannelUpdate bs = do -- | Encode announcement_signatures message. encodeAnnouncementSignatures :: AnnouncementSignatures -> ByteString encodeAnnouncementSignatures msg = mconcat - [ getChannelId (annSigChannelId msg) - , getShortChannelId (annSigShortChanId msg) - , getSignature (annSigNodeSig msg) - , getSignature (annSigBitcoinSig msg) + [ unChannelId (annSigChannelId msg) + , scidToBytes (annSigShortChanId msg) + , unSignature (annSigNodeSig msg) + , unSignature (annSigBitcoinSig msg) ] -- | Decode announcement_signatures message. @@ -455,7 +455,7 @@ encodeQueryShortChannelIds msg = do if BS.length scidData > 65535 then Left EncodeLengthOverflow else Right $ mconcat - [ getChainHash (queryScidsChainHash msg) + [ unChainHash (queryScidsChainHash msg) , encodeLenPrefixed scidData , TLV.encodeTlvStream (queryScidsTlvs msg) ] @@ -480,7 +480,7 @@ decodeQueryShortChannelIds bs = do -- | Encode reply_short_channel_ids_end message. encodeReplyShortChannelIdsEnd :: ReplyShortChannelIdsEnd -> ByteString encodeReplyShortChannelIdsEnd msg = mconcat - [ getChainHash (replyScidsChainHash msg) + [ unChainHash (replyScidsChainHash msg) , BS.singleton (replyScidsFullInfo msg) ] @@ -500,7 +500,7 @@ decodeReplyShortChannelIdsEnd bs = do -- | Encode query_channel_range message. encodeQueryChannelRange :: QueryChannelRange -> ByteString encodeQueryChannelRange msg = mconcat - [ getChainHash (queryRangeChainHash msg) + [ unChainHash (queryRangeChainHash msg) , Prim.encodeU32 (queryRangeFirstBlock msg) , Prim.encodeU32 (queryRangeNumBlocks msg) , TLV.encodeTlvStream (queryRangeTlvs msg) @@ -531,7 +531,7 @@ encodeReplyChannelRange msg = do if BS.length rangeData > 65535 then Left EncodeLengthOverflow else Right $ mconcat - [ getChainHash (replyRangeChainHash msg) + [ unChainHash (replyRangeChainHash msg) , Prim.encodeU32 (replyRangeFirstBlock msg) , Prim.encodeU32 (replyRangeNumBlocks msg) , BS.singleton (replyRangeSyncComplete msg) @@ -564,7 +564,7 @@ decodeReplyChannelRange bs = do -- | Encode gossip_timestamp_filter message. encodeGossipTimestampFilter :: GossipTimestampFilter -> ByteString encodeGossipTimestampFilter msg = mconcat - [ getChainHash (gossipFilterChainHash msg) + [ unChainHash (gossipFilterChainHash msg) , Prim.encodeU32 (gossipFilterFirstTimestamp msg) , Prim.encodeU32 (gossipFilterTimestampRange msg) ] @@ -595,7 +595,7 @@ decodeGossipTimestampFilter bs = do -- ascending order if that's required by the protocol context. encodeShortChannelIdList :: [ShortChannelId] -> ByteString encodeShortChannelIdList scids = BS.cons 0 $ - mconcat (map getShortChannelId scids) + mconcat (map scidToBytes scids) {-# INLINE encodeShortChannelIdList #-} -- | Decode a list of short channel IDs from encoded_short_ids data. @@ -618,7 +618,7 @@ decodeShortChannelIdList bs | BS.length d < shortChannelIdLen = Left DecodeInsufficientBytes | otherwise = do let (scidBytes, rest) = BS.splitAt shortChannelIdLen d - case shortChannelId scidBytes of + case scidFromBytes scidBytes of Nothing -> Left DecodeInvalidShortChannelId Just scid -> do scids <- decodeUncompressedScids rest diff --git a/lib/Lightning/Protocol/BOLT7/Types.hs b/lib/Lightning/Protocol/BOLT7/Types.hs @@ -12,30 +12,31 @@ -- Core types for BOLT #7 routing gossip. module Lightning.Protocol.BOLT7.Types ( - -- * Identifiers - ChainHash + -- * Identifiers (re-exported from BOLT1) + ChainHash(..) , chainHash - , getChainHash + , unChainHash , mainnetChainHash - , ShortChannelId + , ShortChannelId(..) , shortChannelId - , mkShortChannelId - , getShortChannelId , scidBlockHeight , scidTxIndex , scidOutputIndex + , scidWord64 + , scidFromBytes + , scidToBytes , formatScid - , ChannelId + , ChannelId(..) , channelId - , getChannelId + , unChannelId - -- * Cryptographic types - , Signature + -- * Cryptographic types (re-exported from BOLT1) + , Signature(..) , signature - , getSignature - , Point + , unSignature + , Point(..) , point - , getPoint + , unPoint , NodeId , nodeId , getNodeId @@ -99,6 +100,14 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.Word (Word8, Word16, Word32, Word64) import GHC.Generics (Generic) +import Lightning.Protocol.BOLT1.Prim + ( ChainHash(..), unChainHash, chainHash + , ShortChannelId(..), shortChannelId + , scidBlockHeight, scidTxIndex, scidOutputIndex, scidWord64 + , ChannelId(..), unChannelId, channelId + , Signature(..), unSignature, signature + , Point(..), unPoint, point + ) -- Constants ------------------------------------------------------------------- @@ -159,23 +168,7 @@ torV3AddrLen = 35 -- Identifiers ----------------------------------------------------------------- --- | Chain hash identifying the blockchain (32 bytes). -newtype ChainHash = ChainHash { getChainHash :: ByteString } - deriving (Eq, Show, Generic) - -instance NFData ChainHash - --- | Smart constructor for ChainHash. Returns Nothing if not 32 bytes. -chainHash :: ByteString -> Maybe ChainHash -chainHash !bs - | BS.length bs == chainHashLen = Just (ChainHash bs) - | otherwise = Nothing -{-# INLINE chainHash #-} - -- | Bitcoin mainnet chain hash (genesis block hash, little-endian). --- --- This is the double-SHA256 of the mainnet genesis block header, reversed --- to little-endian byte order as used in the protocol. mainnetChainHash :: ChainHash mainnetChainHash = ChainHash $ BS.pack [ 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72 @@ -184,122 +177,48 @@ mainnetChainHash = ChainHash $ BS.pack , 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00 ] --- | Short channel ID (8 bytes): block height (3) + tx index (3) + output (2). -newtype ShortChannelId = ShortChannelId { getShortChannelId :: ByteString } - deriving (Eq, Show, Generic) - -instance NFData ShortChannelId - --- | Smart constructor for ShortChannelId. Returns Nothing if not 8 bytes. -shortChannelId :: ByteString -> Maybe ShortChannelId -shortChannelId !bs - | BS.length bs == shortChannelIdLen = Just (ShortChannelId bs) - | otherwise = Nothing -{-# INLINE shortChannelId #-} - --- | Construct ShortChannelId from components. --- --- Block height and tx index are truncated to 24 bits. --- --- >>> mkShortChannelId 539268 845 1 --- ShortChannelId {getShortChannelId = "\NUL\131\132\NUL\ETX-\NUL\SOH"} -mkShortChannelId - :: Word32 -- ^ Block height (24 bits) - -> Word32 -- ^ Transaction index (24 bits) - -> Word16 -- ^ Output index - -> ShortChannelId -mkShortChannelId !block !txIdx !outIdx = ShortChannelId $ BS.pack - [ fromIntegral ((block `shiftR` 16) .&. 0xff) :: Word8 - , fromIntegral ((block `shiftR` 8) .&. 0xff) - , fromIntegral (block .&. 0xff) - , fromIntegral ((txIdx `shiftR` 16) .&. 0xff) - , fromIntegral ((txIdx `shiftR` 8) .&. 0xff) - , fromIntegral (txIdx .&. 0xff) - , fromIntegral ((outIdx `shiftR` 8) .&. 0xff) - , fromIntegral (outIdx .&. 0xff) - ] -{-# INLINE mkShortChannelId #-} - --- | Extract block height from short channel ID (first 3 bytes, big-endian). -scidBlockHeight :: ShortChannelId -> Word32 -scidBlockHeight (ShortChannelId bs) = - let b0 = fromIntegral (BS.index bs 0) - b1 = fromIntegral (BS.index bs 1) - b2 = fromIntegral (BS.index bs 2) - in (b0 `shiftL` 16) .|. (b1 `shiftL` 8) .|. b2 -{-# INLINE scidBlockHeight #-} - --- | Extract transaction index from short channel ID (bytes 3-5, big-endian). -scidTxIndex :: ShortChannelId -> Word32 -scidTxIndex (ShortChannelId bs) = - let b3 = fromIntegral (BS.index bs 3) - b4 = fromIntegral (BS.index bs 4) - b5 = fromIntegral (BS.index bs 5) - in (b3 `shiftL` 16) .|. (b4 `shiftL` 8) .|. b5 -{-# INLINE scidTxIndex #-} - --- | Extract output index from short channel ID (last 2 bytes, big-endian). -scidOutputIndex :: ShortChannelId -> Word16 -scidOutputIndex (ShortChannelId bs) = - let b6 = fromIntegral (BS.index bs 6) - b7 = fromIntegral (BS.index bs 7) - in (b6 `shiftL` 8) .|. b7 -{-# INLINE scidOutputIndex #-} +-- | Parse ShortChannelId from 8 big-endian bytes. +scidFromBytes :: ByteString -> Maybe ShortChannelId +scidFromBytes !bs + | BS.length bs /= shortChannelIdLen = Nothing + | otherwise = + let !w = (fromIntegral (BS.index bs 0) `shiftL` 56) + .|. (fromIntegral (BS.index bs 1) `shiftL` 48) + .|. (fromIntegral (BS.index bs 2) `shiftL` 40) + .|. (fromIntegral (BS.index bs 3) `shiftL` 32) + .|. (fromIntegral (BS.index bs 4) `shiftL` 24) + .|. (fromIntegral (BS.index bs 5) `shiftL` 16) + .|. (fromIntegral (BS.index bs 6) `shiftL` 8) + .|. fromIntegral (BS.index bs 7) :: Word64 + in Just (ShortChannelId w) +{-# INLINE scidFromBytes #-} + +-- | Encode ShortChannelId as 8 big-endian bytes. +scidToBytes :: ShortChannelId -> ByteString +scidToBytes !sci = + let !w = scidWord64 sci + in BS.pack + [ fromIntegral (w `shiftR` 56) + , fromIntegral (w `shiftR` 48) + , fromIntegral (w `shiftR` 40) + , fromIntegral (w `shiftR` 32) + , fromIntegral (w `shiftR` 24) + , fromIntegral (w `shiftR` 16) + , fromIntegral (w `shiftR` 8) + , fromIntegral w + ] +{-# INLINE scidToBytes #-} -- | Format short channel ID as human-readable string. --- --- Uses the standard "block x tx x output" notation. --- --- >>> formatScid (mkShortChannelId 539268 845 1) --- "539268x845x1" formatScid :: ShortChannelId -> String -formatScid scid = - show (scidBlockHeight scid) ++ "x" ++ - show (scidTxIndex scid) ++ "x" ++ - show (scidOutputIndex scid) +formatScid sci = + show (scidBlockHeight sci) ++ "x" ++ + show (scidTxIndex sci) ++ "x" ++ + show (scidOutputIndex sci) {-# INLINE formatScid #-} --- | Channel ID (32 bytes). -newtype ChannelId = ChannelId { getChannelId :: ByteString } - deriving (Eq, Show, Generic) - -instance NFData ChannelId - --- | Smart constructor for ChannelId. Returns Nothing if not 32 bytes. -channelId :: ByteString -> Maybe ChannelId -channelId !bs - | BS.length bs == channelIdLen = Just (ChannelId bs) - | otherwise = Nothing -{-# INLINE channelId #-} - -- Cryptographic types --------------------------------------------------------- --- | Signature (64 bytes). -newtype Signature = Signature { getSignature :: ByteString } - deriving (Eq, Show, Generic) - -instance NFData Signature - --- | Smart constructor for Signature. Returns Nothing if not 64 bytes. -signature :: ByteString -> Maybe Signature -signature !bs - | BS.length bs == signatureLen = Just (Signature bs) - | otherwise = Nothing -{-# INLINE signature #-} - --- | Compressed public key (33 bytes). -newtype Point = Point { getPoint :: ByteString } - deriving (Eq, Show, Generic) - -instance NFData Point - --- | Smart constructor for Point. Returns Nothing if not 33 bytes. -point :: ByteString -> Maybe Point -point !bs - | BS.length bs == pointLen = Just (Point bs) - | otherwise = Nothing -{-# INLINE point #-} - -- | Node ID (33 bytes, same as compressed public key). -- -- Has Ord instance for lexicographic comparison (required by spec for diff --git a/lib/Lightning/Protocol/BOLT7/Validate.hs b/lib/Lightning/Protocol/BOLT7/Validate.hs @@ -121,7 +121,7 @@ validateReplyChannelRange msg = checkAscending [] = Right () checkAscending [_] = Right () checkAscending (a:b:rest) - | getShortChannelId a < getShortChannelId b = checkAscending (b:rest) + | a < b = checkAscending (b:rest) | otherwise = Left ValidateScidNotAscending -- Internal helpers -----------------------------------------------------------