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).