bolt4

Onion routing protocol, per BOLT #4 (docs.ppad.tech/bolt4).
git clone git://git.ppad.tech/bolt4.git
Log | Files | Refs | README | LICENSE

IMPL2.md (6915B)


      1 # IMPL2: Types and Codec
      2 
      3 **Module**: `Lightning.Protocol.BOLT4.Types`, `Lightning.Protocol.BOLT4.Codec`
      4 
      5 **Dependencies**: base, bytestring
      6 
      7 **Can run in parallel with**: IMPL1 (Primitives)
      8 
      9 ## Overview
     10 
     11 Define core data types and serialization (BigSize, TLV, packets).
     12 
     13 ## Types Module
     14 
     15 ### Packet Types
     16 
     17 ```haskell
     18 -- | Complete onion packet (1366 bytes).
     19 data OnionPacket = OnionPacket
     20   { opVersion      :: {-# UNPACK #-} !Word8
     21   , opEphemeralKey :: !BS.ByteString  -- 33 bytes, compressed pubkey
     22   , opHopPayloads  :: !BS.ByteString  -- 1300 bytes
     23   , opHmac         :: !BS.ByteString  -- 32 bytes
     24   } deriving (Eq, Show)
     25 
     26 -- | Parsed hop payload after decryption.
     27 data HopPayload = HopPayload
     28   { hpAmtToForward   :: !(Maybe Word64)       -- TLV type 2
     29   , hpOutgoingCltv   :: !(Maybe Word32)       -- TLV type 4
     30   , hpShortChannelId :: !(Maybe ShortChannelId) -- TLV type 6
     31   , hpPaymentData    :: !(Maybe PaymentData)  -- TLV type 8
     32   , hpEncryptedData  :: !(Maybe BS.ByteString) -- TLV type 10
     33   , hpCurrentPathKey :: !(Maybe BS.ByteString) -- TLV type 12
     34   , hpUnknownTlvs    :: ![TlvRecord]          -- unknown types
     35   } deriving (Eq, Show)
     36 
     37 -- | Short channel ID (8 bytes): block height, tx index, output index.
     38 data ShortChannelId = ShortChannelId
     39   { sciBlockHeight :: {-# UNPACK #-} !Word32  -- 3 bytes in encoding
     40   , sciTxIndex     :: {-# UNPACK #-} !Word32  -- 3 bytes in encoding
     41   , sciOutputIndex :: {-# UNPACK #-} !Word16  -- 2 bytes in encoding
     42   } deriving (Eq, Show)
     43 
     44 -- | Payment data for final hop (TLV type 8).
     45 data PaymentData = PaymentData
     46   { pdPaymentSecret :: !BS.ByteString  -- 32 bytes
     47   , pdTotalMsat     :: {-# UNPACK #-} !Word64
     48   } deriving (Eq, Show)
     49 
     50 -- | Generic TLV record for unknown/extension types.
     51 data TlvRecord = TlvRecord
     52   { tlvType  :: {-# UNPACK #-} !Word64
     53   , tlvValue :: !BS.ByteString
     54   } deriving (Eq, Show)
     55 ```
     56 
     57 ### Error Types
     58 
     59 ```haskell
     60 -- | Failure message from intermediate or final node.
     61 data FailureMessage = FailureMessage
     62   { fmCode :: {-# UNPACK #-} !FailureCode
     63   , fmData :: !BS.ByteString
     64   , fmTlvs :: ![TlvRecord]
     65   } deriving (Eq, Show)
     66 
     67 -- | 2-byte failure code with flag bits.
     68 newtype FailureCode = FailureCode Word16
     69   deriving (Eq, Show)
     70 
     71 -- Flag bits
     72 pattern BADONION :: Word16
     73 pattern BADONION = 0x8000
     74 
     75 pattern PERM :: Word16
     76 pattern PERM = 0x4000
     77 
     78 pattern NODE :: Word16
     79 pattern NODE = 0x2000
     80 
     81 pattern UPDATE :: Word16
     82 pattern UPDATE = 0x1000
     83 
     84 -- Common failure codes (not exhaustive)
     85 pattern InvalidRealm :: FailureCode
     86 pattern InvalidRealm = FailureCode (PERM .|. 1)
     87 
     88 pattern TemporaryNodeFailure :: FailureCode
     89 pattern TemporaryNodeFailure = FailureCode (NODE .|. 2)
     90 
     91 pattern PermanentNodeFailure :: FailureCode
     92 pattern PermanentNodeFailure = FailureCode (PERM .|. NODE .|. 2)
     93 
     94 pattern InvalidOnionHmac :: FailureCode
     95 pattern InvalidOnionHmac = FailureCode (BADONION .|. PERM .|. 5)
     96 
     97 pattern InvalidOnionKey :: FailureCode
     98 pattern InvalidOnionKey = FailureCode (BADONION .|. PERM .|. 6)
     99 
    100 pattern TemporaryChannelFailure :: FailureCode
    101 pattern TemporaryChannelFailure = FailureCode (UPDATE .|. 7)
    102 
    103 pattern IncorrectOrUnknownPaymentDetails :: FailureCode
    104 pattern IncorrectOrUnknownPaymentDetails = FailureCode (PERM .|. 15)
    105 ```
    106 
    107 ### Processing Results
    108 
    109 ```haskell
    110 -- | Result of processing an onion packet.
    111 data ProcessResult
    112   = Forward !ForwardInfo   -- ^ Forward to next hop
    113   | Receive !ReceiveInfo   -- ^ Final destination reached
    114   deriving (Eq, Show)
    115 
    116 data ForwardInfo = ForwardInfo
    117   { fiNextPacket    :: !OnionPacket
    118   , fiPayload       :: !HopPayload
    119   , fiSharedSecret  :: !BS.ByteString  -- for error attribution
    120   } deriving (Eq, Show)
    121 
    122 data ReceiveInfo = ReceiveInfo
    123   { riPayload      :: !HopPayload
    124   , riSharedSecret :: !BS.ByteString
    125   } deriving (Eq, Show)
    126 ```
    127 
    128 ### Constants
    129 
    130 ```haskell
    131 onionPacketSize :: Int
    132 onionPacketSize = 1366
    133 
    134 hopPayloadsSize :: Int
    135 hopPayloadsSize = 1300
    136 
    137 hmacSize :: Int
    138 hmacSize = 32
    139 
    140 pubkeySize :: Int
    141 pubkeySize = 33
    142 
    143 versionByte :: Word8
    144 versionByte = 0x00
    145 
    146 maxPayloadSize :: Int
    147 maxPayloadSize = 1300 - 32 - 1  -- minus HMAC and min length byte
    148 ```
    149 
    150 ## Codec Module
    151 
    152 ### BigSize Encoding
    153 
    154 Variable-length integer encoding per BOLT1:
    155 
    156 ```haskell
    157 -- | Encode integer as BigSize.
    158 -- 0-0xFC: 1 byte
    159 -- 0xFD-0xFFFF: 0xFD ++ 2 bytes BE
    160 -- 0x10000-0xFFFFFFFF: 0xFE ++ 4 bytes BE
    161 -- larger: 0xFF ++ 8 bytes BE
    162 encodeBigSize :: Word64 -> BS.ByteString
    163 
    164 -- | Decode BigSize, returning (value, remaining bytes).
    165 decodeBigSize :: BS.ByteString -> Maybe (Word64, BS.ByteString)
    166 
    167 -- | Get encoded size of a BigSize value without encoding.
    168 bigSizeLen :: Word64 -> Int
    169 ```
    170 
    171 ### TLV Encoding
    172 
    173 ```haskell
    174 -- | Encode a TLV record.
    175 encodeTlv :: TlvRecord -> BS.ByteString
    176 
    177 -- | Decode a single TLV record.
    178 decodeTlv :: BS.ByteString -> Maybe (TlvRecord, BS.ByteString)
    179 
    180 -- | Decode a TLV stream (sequence of records).
    181 decodeTlvStream :: BS.ByteString -> Maybe [TlvRecord]
    182 
    183 -- | Encode a TLV stream from records.
    184 -- Records must be sorted by type, no duplicates.
    185 encodeTlvStream :: [TlvRecord] -> BS.ByteString
    186 ```
    187 
    188 ### Packet Serialization
    189 
    190 ```haskell
    191 -- | Serialize OnionPacket to 1366 bytes.
    192 encodeOnionPacket :: OnionPacket -> BS.ByteString
    193 
    194 -- | Parse OnionPacket from 1366 bytes.
    195 decodeOnionPacket :: BS.ByteString -> Maybe OnionPacket
    196 
    197 -- | Encode HopPayload to bytes (without length prefix).
    198 encodeHopPayload :: HopPayload -> BS.ByteString
    199 
    200 -- | Decode HopPayload from bytes.
    201 decodeHopPayload :: BS.ByteString -> Maybe HopPayload
    202 ```
    203 
    204 ### ShortChannelId
    205 
    206 ```haskell
    207 -- | Encode ShortChannelId to 8 bytes.
    208 -- Format: 3 bytes block || 3 bytes tx || 2 bytes output (all BE)
    209 encodeShortChannelId :: ShortChannelId -> BS.ByteString
    210 
    211 -- | Decode ShortChannelId from 8 bytes.
    212 decodeShortChannelId :: BS.ByteString -> Maybe ShortChannelId
    213 ```
    214 
    215 ### Failure Message
    216 
    217 ```haskell
    218 -- | Encode failure message.
    219 encodeFailureMessage :: FailureMessage -> BS.ByteString
    220 
    221 -- | Decode failure message.
    222 decodeFailureMessage :: BS.ByteString -> Maybe FailureMessage
    223 ```
    224 
    225 ## Implementation Notes
    226 
    227 1. BigSize is the same as BOLT1's variable-length integer encoding.
    228    Consider importing from ppad-bolt1 if compatible.
    229 
    230 2. TLV types must be encoded in strictly increasing order. The decoder
    231    should reject streams with out-of-order or duplicate types.
    232 
    233 3. ShortChannelId packs 3+3+2 bytes into 8 bytes total. Use bit shifting.
    234 
    235 4. HopPayload decoding: parse TLV stream, then extract known types
    236    into structured fields. Unknown types go into `hpUnknownTlvs`.
    237 
    238 5. All decoders return Maybe to handle malformed input gracefully.
    239 
    240 6. Use Builder for efficient encoding, strict ByteString for results.
    241 
    242 ## Test Cases
    243 
    244 1. BigSize round-trip for boundary values: 0, 0xFC, 0xFD, 0xFFFF,
    245    0x10000, 0xFFFFFFFF, 0x100000000.
    246 
    247 2. TLV stream with multiple records, verify ordering enforcement.
    248 
    249 3. ShortChannelId encode/decode with known values.
    250 
    251 4. OnionPacket round-trip (construct, serialize, deserialize, compare).