Types.hs (16365B)
1 {-# OPTIONS_HADDOCK hide #-} 2 {-# LANGUAGE DeriveGeneric #-} 3 4 -- | 5 -- Module: Lightning.Protocol.BOLT4.Types 6 -- Copyright: (c) 2025 Jared Tobin 7 -- License: MIT 8 -- Maintainer: Jared Tobin <jared@ppad.tech> 9 -- 10 -- Data types for BOLT #4. 11 12 module Lightning.Protocol.BOLT4.Types ( 13 -- * Onion packets 14 OnionPacket(..) 15 , HopPayloads(..) 16 , hop_payloads 17 , un_hop_payloads 18 , Hmac32(..) 19 , hmac32 20 , un_hmac32 21 22 -- * Hop payloads 23 , HopPayload(..) 24 , empty_hop_payload 25 , PaymentData(..) 26 , PaymentSecret(..) 27 , payment_secret 28 , un_payment_secret 29 30 -- * Route blinding 31 , BlindedPath(..) 32 , BlindedHop(..) 33 , BlindedHopData(..) 34 , empty_blinded_hop_data 35 , PaymentRelay(..) 36 , PaymentConstraints(..) 37 , BlindedInfo(..) 38 39 -- * Failure messages 40 , FailureMessage(..) 41 , Failure(..) 42 , OnionHash(..) 43 , onion_hash 44 , un_onion_hash 45 , failure_code 46 , is_badonion 47 , is_perm 48 , is_node 49 , is_update 50 51 -- * Errors 52 , DecodeError(..) 53 , EncodeError(..) 54 , ProcessError(..) 55 ) where 56 57 import Control.DeepSeq (NFData(..)) 58 import Data.Bits ((.&.)) 59 import qualified Data.ByteString as BS 60 import Data.Word (Word8, Word16, Word32, Word64) 61 import GHC.Generics (Generic) 62 import qualified Lightning.Protocol.BOLT1 as BOLT1 63 import qualified Lightning.Protocol.BOLT4.Prim as P 64 import qualified Lightning.Protocol.BOLT9 as BOLT9 65 66 -- onion packets -------------------------------------------------------------- 67 68 -- | An onion packet (@onion_packet@): 1366 bytes on the wire. 69 data OnionPacket = OnionPacket 70 { onion_version :: {-# UNPACK #-} !Word8 71 -- ^ the version byte; 0 in this version of the protocol 72 , onion_public_key :: !BOLT1.Point 73 -- ^ the ephemeral public key 74 , onion_hop_payloads :: !HopPayloads 75 -- ^ the obfuscated hop payloads 76 , onion_hmac :: !Hmac32 77 -- ^ the HMAC authenticating the packet 78 } deriving (Eq, Show, Generic) 79 80 instance NFData OnionPacket 81 82 -- | The 1300-byte @hop_payloads@ field of an onion packet. 83 newtype HopPayloads = HopPayloads BS.ByteString 84 deriving (Eq, Show, Generic) 85 86 instance NFData HopPayloads 87 88 -- | Construct t'HopPayloads' from exactly 1300 bytes. 89 -- 90 -- >>> let Just hp = hop_payloads (BS.replicate 1300 0) 91 -- >>> BS.length (un_hop_payloads hp) 92 -- 1300 93 -- >>> hop_payloads (BS.replicate 1299 0) 94 -- Nothing 95 hop_payloads :: BS.ByteString -> Maybe HopPayloads 96 hop_payloads bs 97 | BS.length bs == 1300 = Just (HopPayloads bs) 98 | otherwise = Nothing 99 {-# INLINE hop_payloads #-} 100 101 -- | The bytes of t'HopPayloads'. 102 un_hop_payloads :: HopPayloads -> BS.ByteString 103 un_hop_payloads (HopPayloads bs) = bs 104 {-# INLINE un_hop_payloads #-} 105 106 -- | A 32-byte HMAC. 107 newtype Hmac32 = Hmac32 BS.ByteString 108 deriving (Eq, Show, Generic) 109 110 instance NFData Hmac32 111 112 -- | Construct an t'Hmac32' from exactly 32 bytes. 113 -- 114 -- >>> fmap (BS.length . un_hmac32) (hmac32 (BS.replicate 32 0)) 115 -- Just 32 116 -- >>> hmac32 "too short" 117 -- Nothing 118 hmac32 :: BS.ByteString -> Maybe Hmac32 119 hmac32 bs 120 | BS.length bs == 32 = Just (Hmac32 bs) 121 | otherwise = Nothing 122 {-# INLINE hmac32 #-} 123 124 -- | The bytes of an t'Hmac32'. 125 un_hmac32 :: Hmac32 -> BS.ByteString 126 un_hmac32 (Hmac32 bs) = bs 127 {-# INLINE un_hmac32 #-} 128 129 -- hop payloads --------------------------------------------------------------- 130 131 -- | A hop's @payload@ TLV stream. 132 -- 133 -- Each known record has a typed field. 'hp_extra' holds the remaining 134 -- records: decoding fills it with the unknown odd records (unknown even 135 -- records are rejected), and encoding accepts any record in it whose 136 -- type isn't one of the known ones. 137 data HopPayload = HopPayload 138 { hp_amt_to_forward :: !(Maybe BOLT1.MilliSatoshi) 139 -- ^ type 2: @amt_to_forward@ 140 , hp_outgoing_cltv_value :: !(Maybe Word32) 141 -- ^ type 4: @outgoing_cltv_value@ 142 , hp_short_channel_id :: !(Maybe BOLT1.ShortChannelId) 143 -- ^ type 6: @short_channel_id@ 144 , hp_payment_data :: !(Maybe PaymentData) 145 -- ^ type 8: @payment_data@ 146 , hp_encrypted_data :: !(Maybe BS.ByteString) 147 -- ^ type 10: @encrypted_recipient_data@ 148 , hp_current_path_key :: !(Maybe BOLT1.Point) 149 -- ^ type 12: @current_path_key@ 150 , hp_payment_metadata :: !(Maybe BS.ByteString) 151 -- ^ type 16: @payment_metadata@ 152 , hp_total_amount_msat :: !(Maybe BOLT1.MilliSatoshi) 153 -- ^ type 18: @total_amount_msat@ 154 , hp_extra :: !BOLT1.TlvStream 155 -- ^ other records 156 } deriving (Eq, Show, Generic) 157 158 instance NFData HopPayload 159 160 -- | The empty t'HopPayload', to be updated with record syntax. 161 -- 162 -- >>> let Just amt = BOLT1.milli_satoshi 1000 163 -- >>> let hp = empty_hop_payload { hp_amt_to_forward = Just amt } 164 -- >>> encode_hop_payload hp 165 -- Right "\STX\STX\ETX\232" 166 empty_hop_payload :: HopPayload 167 empty_hop_payload = HopPayload 168 Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing 169 BOLT1.empty_tlv_stream 170 171 -- | The @payment_data@ record of a final hop's payload. 172 data PaymentData = PaymentData 173 { pd_payment_secret :: !PaymentSecret 174 , pd_total_msat :: !BOLT1.MilliSatoshi 175 } deriving (Eq, Show, Generic) 176 177 instance NFData PaymentData 178 179 -- | A 32-byte payment secret. 180 -- 181 -- Its 'Show' instance is redacted and its 'Eq' instance runs in 182 -- constant time. 183 newtype PaymentSecret = PaymentSecret BS.ByteString 184 185 instance Eq PaymentSecret where 186 PaymentSecret a == PaymentSecret b = P.ct_eq a b 187 188 instance Show PaymentSecret where 189 showsPrec d _ = showParen (d > 10) $ 190 showString "PaymentSecret <redacted>" 191 192 instance NFData PaymentSecret where 193 rnf (PaymentSecret bs) = rnf bs 194 195 -- | Construct a t'PaymentSecret' from exactly 32 bytes. 196 -- 197 -- >>> payment_secret (BS.replicate 32 0x01) 198 -- Just (PaymentSecret <redacted>) 199 -- >>> payment_secret (BS.replicate 16 0x01) 200 -- Nothing 201 payment_secret :: BS.ByteString -> Maybe PaymentSecret 202 payment_secret bs 203 | BS.length bs == 32 = Just (PaymentSecret bs) 204 | otherwise = Nothing 205 {-# INLINE payment_secret #-} 206 207 -- | The bytes of a t'PaymentSecret'. 208 un_payment_secret :: PaymentSecret -> BS.ByteString 209 un_payment_secret (PaymentSecret bs) = bs 210 {-# INLINE un_payment_secret #-} 211 212 -- route blinding ------------------------------------------------------------- 213 214 -- | A blinded route (@blinded_path@), as created by a recipient. 215 data BlindedPath = BlindedPath 216 { bp_first_node_id :: !BOLT1.Point 217 -- ^ the (unblinded) introduction node 218 , bp_first_path_key :: !BOLT1.Point 219 -- ^ the path key for the introduction node 220 , bp_hops :: ![BlindedHop] 221 -- ^ the blinded hops, starting with the introduction node 222 } deriving (Eq, Show, Generic) 223 224 instance NFData BlindedPath 225 226 -- | A hop of a blinded route (@blinded_path_hop@). 227 data BlindedHop = BlindedHop 228 { bh_blinded_node_id :: !BOLT1.Point 229 -- ^ the hop's blinded node id 230 , bh_encrypted_data :: !BS.ByteString 231 -- ^ the hop's @encrypted_recipient_data@ 232 } deriving (Eq, Show, Generic) 233 234 instance NFData BlindedHop 235 236 -- | The @encrypted_data_tlv@ stream carried, encrypted, in a hop's 237 -- @encrypted_recipient_data@. 238 -- 239 -- 'bhd_extra' plays the same role as 'hp_extra' in t'HopPayload'. 240 data BlindedHopData = BlindedHopData 241 { bhd_padding :: !(Maybe BS.ByteString) 242 -- ^ type 1: @padding@ 243 , bhd_short_channel_id :: !(Maybe BOLT1.ShortChannelId) 244 -- ^ type 2: @short_channel_id@ 245 , bhd_next_node_id :: !(Maybe BOLT1.Point) 246 -- ^ type 4: @next_node_id@ 247 , bhd_path_id :: !(Maybe BS.ByteString) 248 -- ^ type 6: @path_id@ 249 , bhd_next_path_key_override :: !(Maybe BOLT1.Point) 250 -- ^ type 8: @next_path_key_override@ 251 , bhd_payment_relay :: !(Maybe PaymentRelay) 252 -- ^ type 10: @payment_relay@ 253 , bhd_payment_constraints :: !(Maybe PaymentConstraints) 254 -- ^ type 12: @payment_constraints@ 255 , bhd_allowed_features :: !(Maybe BOLT9.FeatureVector) 256 -- ^ type 14: @allowed_features@ 257 , bhd_extra :: !BOLT1.TlvStream 258 -- ^ other records 259 } deriving (Eq, Show, Generic) 260 261 instance NFData BlindedHopData 262 263 -- | The empty t'BlindedHopData', to be updated with record syntax. 264 -- 265 -- >>> let bhd = empty_blinded_hop_data { bhd_path_id = Just "path id" } 266 -- >>> encode_blinded_hop_data bhd 267 -- Right "\ACK\apath id" 268 empty_blinded_hop_data :: BlindedHopData 269 empty_blinded_hop_data = BlindedHopData 270 Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing 271 BOLT1.empty_tlv_stream 272 273 -- | The @payment_relay@ record of t'BlindedHopData'. 274 data PaymentRelay = PaymentRelay 275 { pr_cltv_expiry_delta :: {-# UNPACK #-} !Word16 276 , pr_fee_proportional_millionths :: {-# UNPACK #-} !Word32 277 , pr_fee_base_msat :: {-# UNPACK #-} !Word32 278 } deriving (Eq, Show, Generic) 279 280 instance NFData PaymentRelay 281 282 -- | The @payment_constraints@ record of t'BlindedHopData'. 283 data PaymentConstraints = PaymentConstraints 284 { pc_max_cltv_expiry :: {-# UNPACK #-} !Word32 285 , pc_htlc_minimum_msat :: !BOLT1.MilliSatoshi 286 } deriving (Eq, Show, Generic) 287 288 instance NFData PaymentConstraints 289 290 -- | What a node in a blinded route learns from its 291 -- @encrypted_recipient_data@. 292 data BlindedInfo = BlindedInfo 293 { bi_data :: !BlindedHopData 294 -- ^ the decrypted @encrypted_data_tlv@ 295 , bi_next_path_key :: !BOLT1.Point 296 -- ^ the @path_key@ to send to the next node 297 } deriving (Eq, Show, Generic) 298 299 instance NFData BlindedInfo 300 301 -- failure messages ----------------------------------------------------------- 302 303 -- | A failure message (@failuremsg@): a failure, followed by an optional 304 -- TLV stream. 305 data FailureMessage = FailureMessage 306 { fm_failure :: !Failure 307 , fm_tlvs :: !BOLT1.TlvStream 308 } deriving (Eq, Show, Generic) 309 310 instance NFData FailureMessage 311 312 -- | A failure: a failure code and its data. 313 -- 314 -- A @channel_update@ field holds the raw bytes of the update; it is 315 -- empty when the update is omitted. 316 data Failure 317 = TemporaryNodeFailure 318 -- ^ NODE|2 319 | PermanentNodeFailure 320 -- ^ PERM|NODE|2 321 | RequiredNodeFeatureMissing 322 -- ^ PERM|NODE|3 323 | InvalidOnionVersion !OnionHash 324 -- ^ BADONION|PERM|4: @sha256_of_onion@ 325 | InvalidOnionHmac !OnionHash 326 -- ^ BADONION|PERM|5: @sha256_of_onion@ 327 | InvalidOnionKey !OnionHash 328 -- ^ BADONION|PERM|6: @sha256_of_onion@ 329 | TemporaryChannelFailure !BS.ByteString 330 -- ^ UPDATE|7: @channel_update@ 331 | PermanentChannelFailure 332 -- ^ PERM|8 333 | RequiredChannelFeatureMissing 334 -- ^ PERM|9 335 | UnknownNextPeer 336 -- ^ PERM|10 337 | AmountBelowMinimum !BOLT1.MilliSatoshi !BS.ByteString 338 -- ^ UPDATE|11: @htlc_msat@, @channel_update@ 339 | FeeInsufficient !BOLT1.MilliSatoshi !BS.ByteString 340 -- ^ UPDATE|12: @htlc_msat@, @channel_update@ 341 | IncorrectCltvExpiry {-# UNPACK #-} !Word32 !BS.ByteString 342 -- ^ UPDATE|13: @cltv_expiry@, @channel_update@ 343 | ExpiryTooSoon !BS.ByteString 344 -- ^ UPDATE|14: @channel_update@ 345 | IncorrectOrUnknownPaymentDetails !BOLT1.MilliSatoshi 346 {-# UNPACK #-} !Word32 347 -- ^ PERM|15: @htlc_msat@, @height@ 348 | FinalIncorrectCltvExpiry {-# UNPACK #-} !Word32 349 -- ^ 18: @cltv_expiry@ 350 | FinalIncorrectHtlcAmount !BOLT1.MilliSatoshi 351 -- ^ 19: @incoming_htlc_amt@ 352 | ChannelDisabled {-# UNPACK #-} !Word16 !BS.ByteString 353 -- ^ UPDATE|20: @disabled_flags@, @channel_update@ 354 | ExpiryTooFar 355 -- ^ 21 356 | InvalidOnionPayload !(Maybe (Word64, Word16)) 357 -- ^ PERM|22: the optional @type@ and @offset@ 358 | MppTimeout 359 -- ^ 23 360 | InvalidOnionBlinding !OnionHash 361 -- ^ BADONION|PERM|24: @sha256_of_onion@ 362 | UnknownFailure {-# UNPACK #-} !Word16 !BS.ByteString 363 -- ^ a failure code not defined above, and the bytes following it 364 deriving (Eq, Show, Generic) 365 366 instance NFData Failure 367 368 -- | A 32-byte @sha256_of_onion@. 369 newtype OnionHash = OnionHash BS.ByteString 370 deriving (Eq, Show, Generic) 371 372 instance NFData OnionHash 373 374 -- | Construct an t'OnionHash' from exactly 32 bytes. 375 -- 376 -- >>> fmap (BS.length . un_onion_hash) (onion_hash (BS.replicate 32 0)) 377 -- Just 32 378 onion_hash :: BS.ByteString -> Maybe OnionHash 379 onion_hash bs 380 | BS.length bs == 32 = Just (OnionHash bs) 381 | otherwise = Nothing 382 {-# INLINE onion_hash #-} 383 384 -- | The bytes of an t'OnionHash'. 385 un_onion_hash :: OnionHash -> BS.ByteString 386 un_onion_hash (OnionHash bs) = bs 387 {-# INLINE un_onion_hash #-} 388 389 -- | The @failure_code@ of a 'Failure'. 390 -- 391 -- >>> failure_code TemporaryNodeFailure 392 -- 8194 393 -- >>> failure_code (UnknownFailure 0x1234 mempty) 394 -- 4660 395 failure_code :: Failure -> Word16 396 failure_code f = case f of 397 TemporaryNodeFailure -> 0x2002 398 PermanentNodeFailure -> 0x6002 399 RequiredNodeFeatureMissing -> 0x6003 400 InvalidOnionVersion _ -> 0xc004 401 InvalidOnionHmac _ -> 0xc005 402 InvalidOnionKey _ -> 0xc006 403 TemporaryChannelFailure _ -> 0x1007 404 PermanentChannelFailure -> 0x4008 405 RequiredChannelFeatureMissing -> 0x4009 406 UnknownNextPeer -> 0x400a 407 AmountBelowMinimum _ _ -> 0x100b 408 FeeInsufficient _ _ -> 0x100c 409 IncorrectCltvExpiry _ _ -> 0x100d 410 ExpiryTooSoon _ -> 0x100e 411 IncorrectOrUnknownPaymentDetails _ _ -> 0x400f 412 FinalIncorrectCltvExpiry _ -> 0x0012 413 FinalIncorrectHtlcAmount _ -> 0x0013 414 ChannelDisabled _ _ -> 0x1014 415 ExpiryTooFar -> 0x0015 416 InvalidOnionPayload _ -> 0x4016 417 MppTimeout -> 0x0017 418 InvalidOnionBlinding _ -> 0xc018 419 UnknownFailure c _ -> c 420 421 -- | Is the BADONION flag (0x8000) set: was the onion unparsable? 422 -- 423 -- >>> is_badonion TemporaryNodeFailure 424 -- False 425 is_badonion :: Failure -> Bool 426 is_badonion f = failure_code f .&. 0x8000 /= 0 427 {-# INLINE is_badonion #-} 428 429 -- | Is the PERM flag (0x4000) set: is the failure permanent? 430 -- 431 -- >>> is_perm PermanentNodeFailure 432 -- True 433 is_perm :: Failure -> Bool 434 is_perm f = failure_code f .&. 0x4000 /= 0 435 {-# INLINE is_perm #-} 436 437 -- | Is the NODE flag (0x2000) set: is it a node (not channel) failure? 438 -- 439 -- >>> is_node TemporaryNodeFailure 440 -- True 441 is_node :: Failure -> Bool 442 is_node f = failure_code f .&. 0x2000 /= 0 443 {-# INLINE is_node #-} 444 445 -- | Is the UPDATE flag (0x1000) set: was a channel forwarding parameter 446 -- violated? 447 -- 448 -- >>> is_update (ExpiryTooSoon mempty) 449 -- True 450 is_update :: Failure -> Bool 451 is_update f = failure_code f .&. 0x1000 /= 0 452 {-# INLINE is_update #-} 453 454 -- errors --------------------------------------------------------------------- 455 456 -- | Why a decoder failed. 457 data DecodeError 458 = InvalidLength 459 -- ^ the input has the wrong length 460 | InvalidPoint 461 -- ^ a point lacks a compressed-encoding prefix 462 | InvalidTlvStream !BOLT1.TlvError 463 -- ^ the TLV stream is malformed, or has an unknown even type 464 | InvalidTlvValue {-# UNPACK #-} !Word64 465 -- ^ the value of the record of this type is malformed 466 | InvalidFailureData {-# UNPACK #-} !Word16 467 -- ^ the data of a failure with this code is malformed 468 deriving (Eq, Show, Generic) 469 470 instance NFData DecodeError 471 472 -- | Why an encoder failed. 473 data EncodeError 474 = ConflictingTlv 475 -- ^ an extra TLV record has the type of a known record 476 | FieldTooLong 477 -- ^ a length-prefixed field exceeds its maximum length 478 deriving (Eq, Show, Generic) 479 480 instance NFData EncodeError 481 482 -- | Why an onion packet was rejected. 483 data ProcessError 484 = InvalidVersion {-# UNPACK #-} !Word8 485 -- ^ the version byte is not 0 486 | InvalidPublicKey 487 -- ^ the ephemeral public key is not a valid point 488 | HmacMismatch 489 -- ^ the packet's HMAC is wrong 490 | InvalidPayloadLength 491 -- ^ the payload length is malformed, below 2, or past the end of 492 -- @hop_payloads@ 493 | InvalidPayload !DecodeError 494 -- ^ the payload is not a valid @payload@ TLV stream 495 | InvalidPathKey 496 -- ^ the path key is not a valid point 497 | UnexpectedPathKey 498 -- ^ a path key was given with no @encrypted_recipient_data@, or 499 -- both a @path_key@ and a @current_path_key@ were given 500 | MissingPathKey 501 -- ^ @encrypted_recipient_data@ was given with no path key 502 | InvalidRecipientData 503 -- ^ the @encrypted_recipient_data@ doesn't decrypt to a valid 504 -- @encrypted_data_tlv@ stream 505 deriving (Eq, Show, Generic) 506 507 instance NFData ProcessError