bolt7

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

Types.hs (13779B)


      1 {-# OPTIONS_HADDOCK prune #-}
      2 
      3 {-# LANGUAGE BangPatterns #-}
      4 {-# LANGUAGE DeriveGeneric #-}
      5 
      6 -- |
      7 -- Module: Lightning.Protocol.BOLT7.Types
      8 -- Copyright: (c) 2025 Jared Tobin
      9 -- License: MIT
     10 -- Maintainer: Jared Tobin <jared@ppad.tech>
     11 --
     12 -- Core types for BOLT #7 routing gossip.
     13 
     14 module Lightning.Protocol.BOLT7.Types (
     15   -- * Identifiers
     16     ChainHash
     17   , chainHash
     18   , getChainHash
     19   , mainnetChainHash
     20   , ShortChannelId
     21   , shortChannelId
     22   , mkShortChannelId
     23   , getShortChannelId
     24   , scidBlockHeight
     25   , scidTxIndex
     26   , scidOutputIndex
     27   , formatScid
     28   , ChannelId
     29   , channelId
     30   , getChannelId
     31 
     32   -- * Cryptographic types
     33   , Signature
     34   , signature
     35   , getSignature
     36   , Point
     37   , point
     38   , getPoint
     39   , NodeId
     40   , nodeId
     41   , getNodeId
     42 
     43   -- * Node metadata
     44   , RgbColor
     45   , rgbColor
     46   , getRgbColor
     47   , Alias
     48   , alias
     49   , getAlias
     50   , Timestamp
     51   , FeatureBits
     52   , featureBits
     53   , getFeatureBits
     54 
     55   -- * Address types
     56   , Address(..)
     57   , IPv4Addr
     58   , ipv4Addr
     59   , getIPv4Addr
     60   , IPv6Addr
     61   , ipv6Addr
     62   , getIPv6Addr
     63   , TorV3Addr
     64   , torV3Addr
     65   , getTorV3Addr
     66 
     67   -- * Channel update flags
     68   , MessageFlags(..)
     69   , encodeMessageFlags
     70   , decodeMessageFlags
     71   , ChannelFlags(..)
     72   , encodeChannelFlags
     73   , decodeChannelFlags
     74 
     75   -- * Routing parameters
     76   , CltvExpiryDelta(..)
     77   , FeeBaseMsat(..)
     78   , FeeProportionalMillionths(..)
     79   , HtlcMinimumMsat(..)
     80   , HtlcMaximumMsat(..)
     81 
     82   -- * Constants
     83   , chainHashLen
     84   , shortChannelIdLen
     85   , channelIdLen
     86   , signatureLen
     87   , pointLen
     88   , nodeIdLen
     89   , rgbColorLen
     90   , aliasLen
     91   , ipv4AddrLen
     92   , ipv6AddrLen
     93   , torV3AddrLen
     94   ) where
     95 
     96 import Control.DeepSeq (NFData)
     97 import Data.Bits (shiftL, shiftR, (.&.), (.|.))
     98 import Data.ByteString (ByteString)
     99 import qualified Data.ByteString as BS
    100 import Data.Word (Word8, Word16, Word32, Word64)
    101 import GHC.Generics (Generic)
    102 
    103 -- Constants -------------------------------------------------------------------
    104 
    105 -- | Length of a chain hash (32 bytes).
    106 chainHashLen :: Int
    107 chainHashLen = 32
    108 {-# INLINE chainHashLen #-}
    109 
    110 -- | Length of a short channel ID (8 bytes).
    111 shortChannelIdLen :: Int
    112 shortChannelIdLen = 8
    113 {-# INLINE shortChannelIdLen #-}
    114 
    115 -- | Length of a channel ID (32 bytes).
    116 channelIdLen :: Int
    117 channelIdLen = 32
    118 {-# INLINE channelIdLen #-}
    119 
    120 -- | Length of a signature (64 bytes).
    121 signatureLen :: Int
    122 signatureLen = 64
    123 {-# INLINE signatureLen #-}
    124 
    125 -- | Length of a compressed public key (33 bytes).
    126 pointLen :: Int
    127 pointLen = 33
    128 {-# INLINE pointLen #-}
    129 
    130 -- | Length of a node ID (33 bytes, same as compressed public key).
    131 nodeIdLen :: Int
    132 nodeIdLen = 33
    133 {-# INLINE nodeIdLen #-}
    134 
    135 -- | Length of RGB color (3 bytes).
    136 rgbColorLen :: Int
    137 rgbColorLen = 3
    138 {-# INLINE rgbColorLen #-}
    139 
    140 -- | Length of node alias (32 bytes).
    141 aliasLen :: Int
    142 aliasLen = 32
    143 {-# INLINE aliasLen #-}
    144 
    145 -- | Length of IPv4 address (4 bytes).
    146 ipv4AddrLen :: Int
    147 ipv4AddrLen = 4
    148 {-# INLINE ipv4AddrLen #-}
    149 
    150 -- | Length of IPv6 address (16 bytes).
    151 ipv6AddrLen :: Int
    152 ipv6AddrLen = 16
    153 {-# INLINE ipv6AddrLen #-}
    154 
    155 -- | Length of Tor v3 address (35 bytes).
    156 torV3AddrLen :: Int
    157 torV3AddrLen = 35
    158 {-# INLINE torV3AddrLen #-}
    159 
    160 -- Identifiers -----------------------------------------------------------------
    161 
    162 -- | Chain hash identifying the blockchain (32 bytes).
    163 newtype ChainHash = ChainHash { getChainHash :: ByteString }
    164   deriving (Eq, Show, Generic)
    165 
    166 instance NFData ChainHash
    167 
    168 -- | Smart constructor for ChainHash. Returns Nothing if not 32 bytes.
    169 chainHash :: ByteString -> Maybe ChainHash
    170 chainHash !bs
    171   | BS.length bs == chainHashLen = Just (ChainHash bs)
    172   | otherwise = Nothing
    173 {-# INLINE chainHash #-}
    174 
    175 -- | Bitcoin mainnet chain hash (genesis block hash, little-endian).
    176 --
    177 -- This is the double-SHA256 of the mainnet genesis block header, reversed
    178 -- to little-endian byte order as used in the protocol.
    179 mainnetChainHash :: ChainHash
    180 mainnetChainHash = ChainHash $ BS.pack
    181   [ 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72
    182   , 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f
    183   , 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c
    184   , 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00
    185   ]
    186 
    187 -- | Short channel ID (8 bytes): block height (3) + tx index (3) + output (2).
    188 newtype ShortChannelId = ShortChannelId { getShortChannelId :: ByteString }
    189   deriving (Eq, Show, Generic)
    190 
    191 instance NFData ShortChannelId
    192 
    193 -- | Smart constructor for ShortChannelId. Returns Nothing if not 8 bytes.
    194 shortChannelId :: ByteString -> Maybe ShortChannelId
    195 shortChannelId !bs
    196   | BS.length bs == shortChannelIdLen = Just (ShortChannelId bs)
    197   | otherwise = Nothing
    198 {-# INLINE shortChannelId #-}
    199 
    200 -- | Construct ShortChannelId from components.
    201 --
    202 -- Block height and tx index are truncated to 24 bits.
    203 --
    204 -- >>> mkShortChannelId 539268 845 1
    205 -- ShortChannelId {getShortChannelId = "\NUL\131\132\NUL\ETX-\NUL\SOH"}
    206 mkShortChannelId
    207   :: Word32  -- ^ Block height (24 bits)
    208   -> Word32  -- ^ Transaction index (24 bits)
    209   -> Word16  -- ^ Output index
    210   -> ShortChannelId
    211 mkShortChannelId !block !txIdx !outIdx = ShortChannelId $ BS.pack
    212   [ fromIntegral ((block `shiftR` 16) .&. 0xff) :: Word8
    213   , fromIntegral ((block `shiftR` 8) .&. 0xff)
    214   , fromIntegral (block .&. 0xff)
    215   , fromIntegral ((txIdx `shiftR` 16) .&. 0xff)
    216   , fromIntegral ((txIdx `shiftR` 8) .&. 0xff)
    217   , fromIntegral (txIdx .&. 0xff)
    218   , fromIntegral ((outIdx `shiftR` 8) .&. 0xff)
    219   , fromIntegral (outIdx .&. 0xff)
    220   ]
    221 {-# INLINE mkShortChannelId #-}
    222 
    223 -- | Extract block height from short channel ID (first 3 bytes, big-endian).
    224 scidBlockHeight :: ShortChannelId -> Word32
    225 scidBlockHeight (ShortChannelId bs) =
    226   let b0 = fromIntegral (BS.index bs 0)
    227       b1 = fromIntegral (BS.index bs 1)
    228       b2 = fromIntegral (BS.index bs 2)
    229   in  (b0 `shiftL` 16) .|. (b1 `shiftL` 8) .|. b2
    230 {-# INLINE scidBlockHeight #-}
    231 
    232 -- | Extract transaction index from short channel ID (bytes 3-5, big-endian).
    233 scidTxIndex :: ShortChannelId -> Word32
    234 scidTxIndex (ShortChannelId bs) =
    235   let b3 = fromIntegral (BS.index bs 3)
    236       b4 = fromIntegral (BS.index bs 4)
    237       b5 = fromIntegral (BS.index bs 5)
    238   in  (b3 `shiftL` 16) .|. (b4 `shiftL` 8) .|. b5
    239 {-# INLINE scidTxIndex #-}
    240 
    241 -- | Extract output index from short channel ID (last 2 bytes, big-endian).
    242 scidOutputIndex :: ShortChannelId -> Word16
    243 scidOutputIndex (ShortChannelId bs) =
    244   let b6 = fromIntegral (BS.index bs 6)
    245       b7 = fromIntegral (BS.index bs 7)
    246   in  (b6 `shiftL` 8) .|. b7
    247 {-# INLINE scidOutputIndex #-}
    248 
    249 -- | Format short channel ID as human-readable string.
    250 --
    251 -- Uses the standard "block x tx x output" notation.
    252 --
    253 -- >>> formatScid (mkShortChannelId 539268 845 1)
    254 -- "539268x845x1"
    255 formatScid :: ShortChannelId -> String
    256 formatScid scid =
    257   show (scidBlockHeight scid) ++ "x" ++
    258   show (scidTxIndex scid) ++ "x" ++
    259   show (scidOutputIndex scid)
    260 {-# INLINE formatScid #-}
    261 
    262 -- | Channel ID (32 bytes).
    263 newtype ChannelId = ChannelId { getChannelId :: ByteString }
    264   deriving (Eq, Show, Generic)
    265 
    266 instance NFData ChannelId
    267 
    268 -- | Smart constructor for ChannelId. Returns Nothing if not 32 bytes.
    269 channelId :: ByteString -> Maybe ChannelId
    270 channelId !bs
    271   | BS.length bs == channelIdLen = Just (ChannelId bs)
    272   | otherwise = Nothing
    273 {-# INLINE channelId #-}
    274 
    275 -- Cryptographic types ---------------------------------------------------------
    276 
    277 -- | Signature (64 bytes).
    278 newtype Signature = Signature { getSignature :: ByteString }
    279   deriving (Eq, Show, Generic)
    280 
    281 instance NFData Signature
    282 
    283 -- | Smart constructor for Signature. Returns Nothing if not 64 bytes.
    284 signature :: ByteString -> Maybe Signature
    285 signature !bs
    286   | BS.length bs == signatureLen = Just (Signature bs)
    287   | otherwise = Nothing
    288 {-# INLINE signature #-}
    289 
    290 -- | Compressed public key (33 bytes).
    291 newtype Point = Point { getPoint :: ByteString }
    292   deriving (Eq, Show, Generic)
    293 
    294 instance NFData Point
    295 
    296 -- | Smart constructor for Point. Returns Nothing if not 33 bytes.
    297 point :: ByteString -> Maybe Point
    298 point !bs
    299   | BS.length bs == pointLen = Just (Point bs)
    300   | otherwise = Nothing
    301 {-# INLINE point #-}
    302 
    303 -- | Node ID (33 bytes, same as compressed public key).
    304 --
    305 -- Has Ord instance for lexicographic comparison (required by spec for
    306 -- channel announcements where node_id_1 < node_id_2).
    307 newtype NodeId = NodeId { getNodeId :: ByteString }
    308   deriving (Eq, Ord, Show, Generic)
    309 
    310 instance NFData NodeId
    311 
    312 -- | Smart constructor for NodeId. Returns Nothing if not 33 bytes.
    313 nodeId :: ByteString -> Maybe NodeId
    314 nodeId !bs
    315   | BS.length bs == nodeIdLen = Just (NodeId bs)
    316   | otherwise = Nothing
    317 {-# INLINE nodeId #-}
    318 
    319 -- Node metadata ---------------------------------------------------------------
    320 
    321 -- | RGB color (3 bytes).
    322 newtype RgbColor = RgbColor { getRgbColor :: ByteString }
    323   deriving (Eq, Show, Generic)
    324 
    325 instance NFData RgbColor
    326 
    327 -- | Smart constructor for RgbColor. Returns Nothing if not 3 bytes.
    328 rgbColor :: ByteString -> Maybe RgbColor
    329 rgbColor !bs
    330   | BS.length bs == rgbColorLen = Just (RgbColor bs)
    331   | otherwise = Nothing
    332 {-# INLINE rgbColor #-}
    333 
    334 -- | Node alias (32 bytes, UTF-8 padded with zero bytes).
    335 newtype Alias = Alias { getAlias :: ByteString }
    336   deriving (Eq, Show, Generic)
    337 
    338 instance NFData Alias
    339 
    340 -- | Smart constructor for Alias. Returns Nothing if not 32 bytes.
    341 alias :: ByteString -> Maybe Alias
    342 alias !bs
    343   | BS.length bs == aliasLen = Just (Alias bs)
    344   | otherwise = Nothing
    345 {-# INLINE alias #-}
    346 
    347 -- | Timestamp (Unix epoch seconds).
    348 type Timestamp = Word32
    349 
    350 -- | Feature bits (variable length).
    351 newtype FeatureBits = FeatureBits { getFeatureBits :: ByteString }
    352   deriving (Eq, Show, Generic)
    353 
    354 instance NFData FeatureBits
    355 
    356 -- | Smart constructor for FeatureBits (any length).
    357 featureBits :: ByteString -> FeatureBits
    358 featureBits = FeatureBits
    359 {-# INLINE featureBits #-}
    360 
    361 -- Address types ---------------------------------------------------------------
    362 
    363 -- | IPv4 address (4 bytes).
    364 newtype IPv4Addr = IPv4Addr { getIPv4Addr :: ByteString }
    365   deriving (Eq, Show, Generic)
    366 
    367 instance NFData IPv4Addr
    368 
    369 -- | Smart constructor for IPv4Addr. Returns Nothing if not 4 bytes.
    370 ipv4Addr :: ByteString -> Maybe IPv4Addr
    371 ipv4Addr !bs
    372   | BS.length bs == ipv4AddrLen = Just (IPv4Addr bs)
    373   | otherwise = Nothing
    374 {-# INLINE ipv4Addr #-}
    375 
    376 -- | IPv6 address (16 bytes).
    377 newtype IPv6Addr = IPv6Addr { getIPv6Addr :: ByteString }
    378   deriving (Eq, Show, Generic)
    379 
    380 instance NFData IPv6Addr
    381 
    382 -- | Smart constructor for IPv6Addr. Returns Nothing if not 16 bytes.
    383 ipv6Addr :: ByteString -> Maybe IPv6Addr
    384 ipv6Addr !bs
    385   | BS.length bs == ipv6AddrLen = Just (IPv6Addr bs)
    386   | otherwise = Nothing
    387 {-# INLINE ipv6Addr #-}
    388 
    389 -- | Tor v3 onion address (35 bytes: 32 pubkey + 2 checksum + 1 version).
    390 newtype TorV3Addr = TorV3Addr { getTorV3Addr :: ByteString }
    391   deriving (Eq, Show, Generic)
    392 
    393 instance NFData TorV3Addr
    394 
    395 -- | Smart constructor for TorV3Addr. Returns Nothing if not 35 bytes.
    396 torV3Addr :: ByteString -> Maybe TorV3Addr
    397 torV3Addr !bs
    398   | BS.length bs == torV3AddrLen = Just (TorV3Addr bs)
    399   | otherwise = Nothing
    400 {-# INLINE torV3Addr #-}
    401 
    402 -- | Network address with port.
    403 data Address
    404   = AddrIPv4 !IPv4Addr !Word16    -- ^ IPv4 address + port
    405   | AddrIPv6 !IPv6Addr !Word16    -- ^ IPv6 address + port
    406   | AddrTorV3 !TorV3Addr !Word16  -- ^ Tor v3 address + port
    407   | AddrDNS !ByteString !Word16   -- ^ DNS hostname + port
    408   deriving (Eq, Show, Generic)
    409 
    410 instance NFData Address
    411 
    412 -- Channel update flags --------------------------------------------------------
    413 
    414 -- | Message flags for channel_update.
    415 --
    416 -- Bit 0: htlc_maximum_msat field is present.
    417 data MessageFlags = MessageFlags
    418   { mfHtlcMaxPresent :: !Bool  -- ^ htlc_maximum_msat is present
    419   }
    420   deriving (Eq, Show, Generic)
    421 
    422 instance NFData MessageFlags
    423 
    424 -- | Encode MessageFlags to Word8.
    425 encodeMessageFlags :: MessageFlags -> Word8
    426 encodeMessageFlags mf = if mfHtlcMaxPresent mf then 0x01 else 0x00
    427 {-# INLINE encodeMessageFlags #-}
    428 
    429 -- | Decode Word8 to MessageFlags.
    430 decodeMessageFlags :: Word8 -> MessageFlags
    431 decodeMessageFlags w = MessageFlags { mfHtlcMaxPresent = w .&. 0x01 /= 0 }
    432 {-# INLINE decodeMessageFlags #-}
    433 
    434 -- | Channel flags for channel_update.
    435 --
    436 -- Bit 0: direction (0 = node_id_1 is origin, 1 = node_id_2 is origin).
    437 -- Bit 1: disabled (1 = channel disabled).
    438 data ChannelFlags = ChannelFlags
    439   { cfDirection :: !Bool  -- ^ True = node_id_2 is origin
    440   , cfDisabled  :: !Bool  -- ^ True = channel is disabled
    441   }
    442   deriving (Eq, Show, Generic)
    443 
    444 instance NFData ChannelFlags
    445 
    446 -- | Encode ChannelFlags to Word8.
    447 encodeChannelFlags :: ChannelFlags -> Word8
    448 encodeChannelFlags cf =
    449   (if cfDirection cf then 0x01 else 0x00) .|.
    450   (if cfDisabled cf then 0x02 else 0x00)
    451 {-# INLINE encodeChannelFlags #-}
    452 
    453 -- | Decode Word8 to ChannelFlags.
    454 decodeChannelFlags :: Word8 -> ChannelFlags
    455 decodeChannelFlags w = ChannelFlags
    456   { cfDirection = w .&. 0x01 /= 0
    457   , cfDisabled  = w .&. 0x02 /= 0
    458   }
    459 {-# INLINE decodeChannelFlags #-}
    460 
    461 -- Routing parameters ----------------------------------------------------------
    462 
    463 -- | CLTV expiry delta.
    464 newtype CltvExpiryDelta = CltvExpiryDelta { getCltvExpiryDelta :: Word16 }
    465   deriving (Eq, Ord, Show, Generic)
    466 
    467 instance NFData CltvExpiryDelta
    468 
    469 -- | Base fee in millisatoshis.
    470 newtype FeeBaseMsat = FeeBaseMsat { getFeeBaseMsat :: Word32 }
    471   deriving (Eq, Ord, Show, Generic)
    472 
    473 instance NFData FeeBaseMsat
    474 
    475 -- | Proportional fee in millionths.
    476 newtype FeeProportionalMillionths = FeeProportionalMillionths
    477   { getFeeProportionalMillionths :: Word32 }
    478   deriving (Eq, Ord, Show, Generic)
    479 
    480 instance NFData FeeProportionalMillionths
    481 
    482 -- | Minimum HTLC value in millisatoshis.
    483 newtype HtlcMinimumMsat = HtlcMinimumMsat { getHtlcMinimumMsat :: Word64 }
    484   deriving (Eq, Ord, Show, Generic)
    485 
    486 instance NFData HtlcMinimumMsat
    487 
    488 -- | Maximum HTLC value in millisatoshis.
    489 newtype HtlcMaximumMsat = HtlcMaximumMsat { getHtlcMaximumMsat :: Word64 }
    490   deriving (Eq, Ord, Show, Generic)
    491 
    492 instance NFData HtlcMaximumMsat