packages feed

ppad-bolt4 0.0.1 → 0.1.0

raw patch · 15 files changed

+4431/−2859 lines, 15 filesdep +ppad-bolt1dep +ppad-bolt9dep ~bytestringdep ~deepseqdep ~ppad-aeadPVP ok

version bump matches the API change (PVP)

Dependencies added: ppad-bolt1, ppad-bolt9

Dependency ranges changed: bytestring, deepseq, ppad-aead, ppad-chacha, ppad-fixed, ppad-secp256k1, ppad-sha256

API changes (from Hackage documentation)

- Lightning.Protocol.BOLT4.Blinding: BlindedHop :: !ByteString -> !ByteString -> BlindedHop
- Lightning.Protocol.BOLT4.Blinding: BlindedHopData :: !Maybe ByteString -> !Maybe ShortChannelId -> !Maybe ByteString -> !Maybe ByteString -> !Maybe ByteString -> !Maybe PaymentRelay -> !Maybe PaymentConstraints -> !Maybe ByteString -> BlindedHopData
- Lightning.Protocol.BOLT4.Blinding: BlindedPath :: !Projective -> !Projective -> ![BlindedHop] -> BlindedPath
- Lightning.Protocol.BOLT4.Blinding: DecryptionFailed :: BlindingError
- Lightning.Protocol.BOLT4.Blinding: EmptyPath :: BlindingError
- Lightning.Protocol.BOLT4.Blinding: InvalidNodeKey :: Int -> BlindingError
- Lightning.Protocol.BOLT4.Blinding: InvalidPathKey :: BlindingError
- Lightning.Protocol.BOLT4.Blinding: InvalidSeed :: BlindingError
- Lightning.Protocol.BOLT4.Blinding: PaymentConstraints :: {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !Word64 -> PaymentConstraints
- Lightning.Protocol.BOLT4.Blinding: PaymentRelay :: {-# UNPACK #-} !Word16 -> {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !Word32 -> PaymentRelay
- Lightning.Protocol.BOLT4.Blinding: [bhBlindedNodeId] :: BlindedHop -> !ByteString
- Lightning.Protocol.BOLT4.Blinding: [bhEncryptedData] :: BlindedHop -> !ByteString
- Lightning.Protocol.BOLT4.Blinding: [bhdAllowedFeatures] :: BlindedHopData -> !Maybe ByteString
- Lightning.Protocol.BOLT4.Blinding: [bhdNextNodeId] :: BlindedHopData -> !Maybe ByteString
- Lightning.Protocol.BOLT4.Blinding: [bhdNextPathKeyOverride] :: BlindedHopData -> !Maybe ByteString
- Lightning.Protocol.BOLT4.Blinding: [bhdPadding] :: BlindedHopData -> !Maybe ByteString
- Lightning.Protocol.BOLT4.Blinding: [bhdPathId] :: BlindedHopData -> !Maybe ByteString
- Lightning.Protocol.BOLT4.Blinding: [bhdPaymentConstraints] :: BlindedHopData -> !Maybe PaymentConstraints
- Lightning.Protocol.BOLT4.Blinding: [bhdPaymentRelay] :: BlindedHopData -> !Maybe PaymentRelay
- Lightning.Protocol.BOLT4.Blinding: [bhdShortChannelId] :: BlindedHopData -> !Maybe ShortChannelId
- Lightning.Protocol.BOLT4.Blinding: [bpBlindedHops] :: BlindedPath -> ![BlindedHop]
- Lightning.Protocol.BOLT4.Blinding: [bpBlindingKey] :: BlindedPath -> !Projective
- Lightning.Protocol.BOLT4.Blinding: [bpIntroductionNode] :: BlindedPath -> !Projective
- Lightning.Protocol.BOLT4.Blinding: [pcHtlcMinimumMsat] :: PaymentConstraints -> {-# UNPACK #-} !Word64
- Lightning.Protocol.BOLT4.Blinding: [pcMaxCltvExpiry] :: PaymentConstraints -> {-# UNPACK #-} !Word32
- Lightning.Protocol.BOLT4.Blinding: [prCltvExpiryDelta] :: PaymentRelay -> {-# UNPACK #-} !Word16
- Lightning.Protocol.BOLT4.Blinding: [prFeeBaseMsat] :: PaymentRelay -> {-# UNPACK #-} !Word32
- Lightning.Protocol.BOLT4.Blinding: [prFeeProportional] :: PaymentRelay -> {-# UNPACK #-} !Word32
- Lightning.Protocol.BOLT4.Blinding: createBlindedPath :: ByteString -> [(Projective, BlindedHopData)] -> Either BlindingError BlindedPath
- Lightning.Protocol.BOLT4.Blinding: data BlindedHop
- Lightning.Protocol.BOLT4.Blinding: data BlindedHopData
- Lightning.Protocol.BOLT4.Blinding: data BlindedPath
- Lightning.Protocol.BOLT4.Blinding: data BlindingError
- Lightning.Protocol.BOLT4.Blinding: data PaymentConstraints
- Lightning.Protocol.BOLT4.Blinding: data PaymentRelay
- Lightning.Protocol.BOLT4.Blinding: decodeBlindedHopData :: ByteString -> Maybe BlindedHopData
- Lightning.Protocol.BOLT4.Blinding: decryptHopData :: DerivedKey -> ByteString -> Maybe BlindedHopData
- Lightning.Protocol.BOLT4.Blinding: deriveBlindedNodeId :: SharedSecret -> Projective -> Maybe ByteString
- Lightning.Protocol.BOLT4.Blinding: deriveBlindingRho :: SharedSecret -> DerivedKey
- Lightning.Protocol.BOLT4.Blinding: encodeBlindedHopData :: BlindedHopData -> ByteString
- Lightning.Protocol.BOLT4.Blinding: encryptHopData :: DerivedKey -> BlindedHopData -> ByteString
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Blinding.BlindedHop
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Blinding.BlindedHopData
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Blinding.BlindedPath
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Blinding.BlindingError
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Blinding.PaymentConstraints
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Blinding.PaymentRelay
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Show.Show Lightning.Protocol.BOLT4.Blinding.BlindedHop
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Show.Show Lightning.Protocol.BOLT4.Blinding.BlindedHopData
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Show.Show Lightning.Protocol.BOLT4.Blinding.BlindedPath
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Show.Show Lightning.Protocol.BOLT4.Blinding.BlindingError
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Show.Show Lightning.Protocol.BOLT4.Blinding.PaymentConstraints
- Lightning.Protocol.BOLT4.Blinding: instance GHC.Show.Show Lightning.Protocol.BOLT4.Blinding.PaymentRelay
- Lightning.Protocol.BOLT4.Blinding: nextEphemeral :: ByteString -> Projective -> SharedSecret -> Maybe (ByteString, Projective)
- Lightning.Protocol.BOLT4.Blinding: processBlindedHop :: ByteString -> Projective -> ByteString -> Either BlindingError (BlindedHopData, Projective)
- Lightning.Protocol.BOLT4.Codec: bigSizeLen :: Word64 -> Int
- Lightning.Protocol.BOLT4.Codec: decodeBigSize :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT4.Codec: decodeFailureMessage :: ByteString -> Maybe FailureMessage
- Lightning.Protocol.BOLT4.Codec: decodeHopPayload :: ByteString -> Maybe HopPayload
- Lightning.Protocol.BOLT4.Codec: decodeOnionPacket :: ByteString -> Maybe OnionPacket
- Lightning.Protocol.BOLT4.Codec: decodeShortChannelId :: ByteString -> Maybe ShortChannelId
- Lightning.Protocol.BOLT4.Codec: decodeTlv :: ByteString -> Maybe (TlvRecord, ByteString)
- Lightning.Protocol.BOLT4.Codec: decodeTlvStream :: ByteString -> Maybe [TlvRecord]
- Lightning.Protocol.BOLT4.Codec: decodeWord32TU :: ByteString -> Maybe Word32
- Lightning.Protocol.BOLT4.Codec: decodeWord64TU :: ByteString -> Maybe Word64
- Lightning.Protocol.BOLT4.Codec: encodeBigSize :: Word64 -> ByteString
- Lightning.Protocol.BOLT4.Codec: encodeFailureMessage :: FailureMessage -> ByteString
- Lightning.Protocol.BOLT4.Codec: encodeHopPayload :: HopPayload -> ByteString
- Lightning.Protocol.BOLT4.Codec: encodeOnionPacket :: OnionPacket -> ByteString
- Lightning.Protocol.BOLT4.Codec: encodeShortChannelId :: ShortChannelId -> ByteString
- Lightning.Protocol.BOLT4.Codec: encodeTlv :: TlvRecord -> ByteString
- Lightning.Protocol.BOLT4.Codec: encodeTlvStream :: [TlvRecord] -> ByteString
- Lightning.Protocol.BOLT4.Codec: encodeWord32TU :: Word32 -> ByteString
- Lightning.Protocol.BOLT4.Codec: encodeWord64TU :: Word64 -> ByteString
- Lightning.Protocol.BOLT4.Codec: toStrict :: Builder -> ByteString
- Lightning.Protocol.BOLT4.Codec: word16BE :: ByteString -> Word16
- Lightning.Protocol.BOLT4.Codec: word32BE :: ByteString -> Word32
- Lightning.Protocol.BOLT4.Construct: EmptyRoute :: Error
- Lightning.Protocol.BOLT4.Construct: Hop :: !Projective -> !HopPayload -> Hop
- Lightning.Protocol.BOLT4.Construct: InvalidHopPubKey :: !Int -> Error
- Lightning.Protocol.BOLT4.Construct: InvalidSessionKey :: Error
- Lightning.Protocol.BOLT4.Construct: PayloadTooLarge :: !Int -> Error
- Lightning.Protocol.BOLT4.Construct: TooManyHops :: Error
- Lightning.Protocol.BOLT4.Construct: [hopPayload] :: Hop -> !HopPayload
- Lightning.Protocol.BOLT4.Construct: [hopPubKey] :: Hop -> !Projective
- Lightning.Protocol.BOLT4.Construct: construct :: ByteString -> [Hop] -> ByteString -> Either Error (OnionPacket, [SharedSecret])
- Lightning.Protocol.BOLT4.Construct: data Error
- Lightning.Protocol.BOLT4.Construct: data Hop
- Lightning.Protocol.BOLT4.Construct: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Construct.Error
- Lightning.Protocol.BOLT4.Construct: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Construct.Hop
- Lightning.Protocol.BOLT4.Construct: instance GHC.Show.Show Lightning.Protocol.BOLT4.Construct.Error
- Lightning.Protocol.BOLT4.Construct: instance GHC.Show.Show Lightning.Protocol.BOLT4.Construct.Hop
- Lightning.Protocol.BOLT4.Error: Attributed :: {-# UNPACK #-} !Int -> !FailureMessage -> AttributionResult
- Lightning.Protocol.BOLT4.Error: ErrorPacket :: ByteString -> ErrorPacket
- Lightning.Protocol.BOLT4.Error: UnknownOrigin :: !ByteString -> AttributionResult
- Lightning.Protocol.BOLT4.Error: constructError :: SharedSecret -> FailureMessage -> ErrorPacket
- Lightning.Protocol.BOLT4.Error: data AttributionResult
- Lightning.Protocol.BOLT4.Error: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Error.AttributionResult
- Lightning.Protocol.BOLT4.Error: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Error.ErrorPacket
- Lightning.Protocol.BOLT4.Error: instance GHC.Show.Show Lightning.Protocol.BOLT4.Error.AttributionResult
- Lightning.Protocol.BOLT4.Error: instance GHC.Show.Show Lightning.Protocol.BOLT4.Error.ErrorPacket
- Lightning.Protocol.BOLT4.Error: minErrorPacketSize :: Int
- Lightning.Protocol.BOLT4.Error: newtype ErrorPacket
- Lightning.Protocol.BOLT4.Error: unwrapError :: [SharedSecret] -> ErrorPacket -> AttributionResult
- Lightning.Protocol.BOLT4.Error: wrapError :: SharedSecret -> ErrorPacket -> ErrorPacket
- Lightning.Protocol.BOLT4.Prim: BlindingFactor :: ByteString -> BlindingFactor
- Lightning.Protocol.BOLT4.Prim: DerivedKey :: ByteString -> DerivedKey
- Lightning.Protocol.BOLT4.Prim: SharedSecret :: ByteString -> SharedSecret
- Lightning.Protocol.BOLT4.Prim: blindPubKey :: Projective -> BlindingFactor -> Maybe Projective
- Lightning.Protocol.BOLT4.Prim: blindSecKey :: ByteString -> BlindingFactor -> Maybe ByteString
- Lightning.Protocol.BOLT4.Prim: computeBlindingFactor :: Projective -> SharedSecret -> BlindingFactor
- Lightning.Protocol.BOLT4.Prim: computeHmac :: DerivedKey -> ByteString -> ByteString -> ByteString
- Lightning.Protocol.BOLT4.Prim: computeSharedSecret :: ByteString -> Projective -> Maybe SharedSecret
- Lightning.Protocol.BOLT4.Prim: deriveAmmag :: SharedSecret -> DerivedKey
- Lightning.Protocol.BOLT4.Prim: deriveMu :: SharedSecret -> DerivedKey
- Lightning.Protocol.BOLT4.Prim: derivePad :: SharedSecret -> DerivedKey
- Lightning.Protocol.BOLT4.Prim: deriveRho :: SharedSecret -> DerivedKey
- Lightning.Protocol.BOLT4.Prim: deriveUm :: SharedSecret -> DerivedKey
- Lightning.Protocol.BOLT4.Prim: generateStream :: DerivedKey -> Int -> ByteString
- Lightning.Protocol.BOLT4.Prim: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Prim.BlindingFactor
- Lightning.Protocol.BOLT4.Prim: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Prim.DerivedKey
- Lightning.Protocol.BOLT4.Prim: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Prim.SharedSecret
- Lightning.Protocol.BOLT4.Prim: instance GHC.Show.Show Lightning.Protocol.BOLT4.Prim.BlindingFactor
- Lightning.Protocol.BOLT4.Prim: instance GHC.Show.Show Lightning.Protocol.BOLT4.Prim.DerivedKey
- Lightning.Protocol.BOLT4.Prim: instance GHC.Show.Show Lightning.Protocol.BOLT4.Prim.SharedSecret
- Lightning.Protocol.BOLT4.Prim: newtype BlindingFactor
- Lightning.Protocol.BOLT4.Prim: newtype DerivedKey
- Lightning.Protocol.BOLT4.Prim: newtype SharedSecret
- Lightning.Protocol.BOLT4.Prim: verifyHmac :: ByteString -> ByteString -> Bool
- Lightning.Protocol.BOLT4.Process: HmacMismatch :: RejectReason
- Lightning.Protocol.BOLT4.Process: InvalidEphemeralKey :: RejectReason
- Lightning.Protocol.BOLT4.Process: InvalidPayload :: !String -> RejectReason
- Lightning.Protocol.BOLT4.Process: InvalidVersion :: !Word8 -> RejectReason
- Lightning.Protocol.BOLT4.Process: data RejectReason
- Lightning.Protocol.BOLT4.Process: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Process.RejectReason
- Lightning.Protocol.BOLT4.Process: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Process.RejectReason
- Lightning.Protocol.BOLT4.Process: instance GHC.Show.Show Lightning.Protocol.BOLT4.Process.RejectReason
- Lightning.Protocol.BOLT4.Process: process :: ByteString -> OnionPacket -> ByteString -> Either RejectReason ProcessResult
- Lightning.Protocol.BOLT4.Types: FailureCode :: Word16 -> FailureCode
- Lightning.Protocol.BOLT4.Types: FailureMessage :: {-# UNPACK #-} !FailureCode -> !ByteString -> ![TlvRecord] -> FailureMessage
- Lightning.Protocol.BOLT4.Types: Forward :: !ForwardInfo -> ProcessResult
- Lightning.Protocol.BOLT4.Types: ForwardInfo :: !OnionPacket -> !HopPayload -> !ByteString -> ForwardInfo
- Lightning.Protocol.BOLT4.Types: HopPayload :: !Maybe Word64 -> !Maybe Word32 -> !Maybe ShortChannelId -> !Maybe PaymentData -> !Maybe ByteString -> !Maybe ByteString -> ![TlvRecord] -> HopPayload
- Lightning.Protocol.BOLT4.Types: OnionPacket :: {-# UNPACK #-} !Word8 -> !ByteString -> !ByteString -> !ByteString -> OnionPacket
- Lightning.Protocol.BOLT4.Types: PaymentData :: !ByteString -> {-# UNPACK #-} !Word64 -> PaymentData
- Lightning.Protocol.BOLT4.Types: Receive :: !ReceiveInfo -> ProcessResult
- Lightning.Protocol.BOLT4.Types: ReceiveInfo :: !HopPayload -> !ByteString -> ReceiveInfo
- Lightning.Protocol.BOLT4.Types: ShortChannelId :: {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !Word16 -> ShortChannelId
- Lightning.Protocol.BOLT4.Types: TlvRecord :: {-# UNPACK #-} !Word64 -> !ByteString -> TlvRecord
- Lightning.Protocol.BOLT4.Types: [fiNextPacket] :: ForwardInfo -> !OnionPacket
- Lightning.Protocol.BOLT4.Types: [fiPayload] :: ForwardInfo -> !HopPayload
- Lightning.Protocol.BOLT4.Types: [fiSharedSecret] :: ForwardInfo -> !ByteString
- Lightning.Protocol.BOLT4.Types: [fmCode] :: FailureMessage -> {-# UNPACK #-} !FailureCode
- Lightning.Protocol.BOLT4.Types: [fmData] :: FailureMessage -> !ByteString
- Lightning.Protocol.BOLT4.Types: [fmTlvs] :: FailureMessage -> ![TlvRecord]
- Lightning.Protocol.BOLT4.Types: [hpAmtToForward] :: HopPayload -> !Maybe Word64
- Lightning.Protocol.BOLT4.Types: [hpCurrentPathKey] :: HopPayload -> !Maybe ByteString
- Lightning.Protocol.BOLT4.Types: [hpEncryptedData] :: HopPayload -> !Maybe ByteString
- Lightning.Protocol.BOLT4.Types: [hpOutgoingCltv] :: HopPayload -> !Maybe Word32
- Lightning.Protocol.BOLT4.Types: [hpPaymentData] :: HopPayload -> !Maybe PaymentData
- Lightning.Protocol.BOLT4.Types: [hpShortChannelId] :: HopPayload -> !Maybe ShortChannelId
- Lightning.Protocol.BOLT4.Types: [hpUnknownTlvs] :: HopPayload -> ![TlvRecord]
- Lightning.Protocol.BOLT4.Types: [opEphemeralKey] :: OnionPacket -> !ByteString
- Lightning.Protocol.BOLT4.Types: [opHmac] :: OnionPacket -> !ByteString
- Lightning.Protocol.BOLT4.Types: [opHopPayloads] :: OnionPacket -> !ByteString
- Lightning.Protocol.BOLT4.Types: [opVersion] :: OnionPacket -> {-# UNPACK #-} !Word8
- Lightning.Protocol.BOLT4.Types: [pdPaymentSecret] :: PaymentData -> !ByteString
- Lightning.Protocol.BOLT4.Types: [pdTotalMsat] :: PaymentData -> {-# UNPACK #-} !Word64
- Lightning.Protocol.BOLT4.Types: [riPayload] :: ReceiveInfo -> !HopPayload
- Lightning.Protocol.BOLT4.Types: [riSharedSecret] :: ReceiveInfo -> !ByteString
- Lightning.Protocol.BOLT4.Types: [sciBlockHeight] :: ShortChannelId -> {-# UNPACK #-} !Word32
- Lightning.Protocol.BOLT4.Types: [sciOutputIndex] :: ShortChannelId -> {-# UNPACK #-} !Word16
- Lightning.Protocol.BOLT4.Types: [sciTxIndex] :: ShortChannelId -> {-# UNPACK #-} !Word32
- Lightning.Protocol.BOLT4.Types: [tlvType] :: TlvRecord -> {-# UNPACK #-} !Word64
- Lightning.Protocol.BOLT4.Types: [tlvValue] :: TlvRecord -> !ByteString
- Lightning.Protocol.BOLT4.Types: data FailureMessage
- Lightning.Protocol.BOLT4.Types: data ForwardInfo
- Lightning.Protocol.BOLT4.Types: data HopPayload
- Lightning.Protocol.BOLT4.Types: data OnionPacket
- Lightning.Protocol.BOLT4.Types: data PaymentData
- Lightning.Protocol.BOLT4.Types: data ProcessResult
- Lightning.Protocol.BOLT4.Types: data ReceiveInfo
- Lightning.Protocol.BOLT4.Types: data ShortChannelId
- Lightning.Protocol.BOLT4.Types: data TlvRecord
- Lightning.Protocol.BOLT4.Types: hmacSize :: Int
- Lightning.Protocol.BOLT4.Types: hopPayloadsSize :: Int
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.FailureCode
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.FailureMessage
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.ForwardInfo
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.HopPayload
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.OnionPacket
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.PaymentData
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.ProcessResult
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.ReceiveInfo
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.ShortChannelId
- Lightning.Protocol.BOLT4.Types: instance GHC.Classes.Eq Lightning.Protocol.BOLT4.Types.TlvRecord
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.FailureMessage
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.ForwardInfo
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.HopPayload
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.OnionPacket
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.PaymentData
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.ProcessResult
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.ReceiveInfo
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.ShortChannelId
- Lightning.Protocol.BOLT4.Types: instance GHC.Generics.Generic Lightning.Protocol.BOLT4.Types.TlvRecord
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.FailureCode
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.FailureMessage
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.ForwardInfo
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.HopPayload
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.OnionPacket
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.PaymentData
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.ProcessResult
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.ReceiveInfo
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.ShortChannelId
- Lightning.Protocol.BOLT4.Types: instance GHC.Show.Show Lightning.Protocol.BOLT4.Types.TlvRecord
- Lightning.Protocol.BOLT4.Types: maxPayloadSize :: Int
- Lightning.Protocol.BOLT4.Types: newtype FailureCode
- Lightning.Protocol.BOLT4.Types: onionPacketSize :: Int
- Lightning.Protocol.BOLT4.Types: pattern AmountBelowMinimum :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern BADONION :: Word16
- Lightning.Protocol.BOLT4.Types: pattern ChannelDisabled :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern ExpiryTooFar :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern ExpiryTooSoon :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern FeeInsufficient :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern FinalIncorrectCltvExpiry :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern FinalIncorrectHtlcAmount :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern IncorrectCltvExpiry :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern IncorrectOrUnknownPaymentDetails :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern InvalidOnionHmac :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern InvalidOnionKey :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern InvalidOnionPayload :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern InvalidOnionVersion :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern InvalidRealm :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern MppTimeout :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern NODE :: Word16
- Lightning.Protocol.BOLT4.Types: pattern PERM :: Word16
- Lightning.Protocol.BOLT4.Types: pattern PermanentChannelFailure :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern PermanentNodeFailure :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern RequiredNodeFeatureMissing :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern TemporaryChannelFailure :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern TemporaryNodeFailure :: FailureCode
- Lightning.Protocol.BOLT4.Types: pattern UPDATE :: Word16
- Lightning.Protocol.BOLT4.Types: pubkeySize :: Int
- Lightning.Protocol.BOLT4.Types: versionByte :: Word8
+ Lightning.Protocol.BOLT4: AmountBelowMinimum :: !MilliSatoshi -> !ByteString -> Failure
+ Lightning.Protocol.BOLT4: Attributed :: {-# UNPACK #-} !Int -> !FailureMessage -> Attribution
+ Lightning.Protocol.BOLT4: BlindedHop :: !Point -> !ByteString -> BlindedHop
+ Lightning.Protocol.BOLT4: BlindedHopData :: !Maybe ByteString -> !Maybe ShortChannelId -> !Maybe Point -> !Maybe ByteString -> !Maybe Point -> !Maybe PaymentRelay -> !Maybe PaymentConstraints -> !Maybe FeatureVector -> !TlvStream -> BlindedHopData
+ Lightning.Protocol.BOLT4: BlindedInfo :: !BlindedHopData -> !Point -> BlindedInfo
+ Lightning.Protocol.BOLT4: BlindedPath :: !Point -> !Point -> ![BlindedHop] -> BlindedPath
+ Lightning.Protocol.BOLT4: ChannelDisabled :: {-# UNPACK #-} !Word16 -> !ByteString -> Failure
+ Lightning.Protocol.BOLT4: ConflictingTlv :: EncodeError
+ Lightning.Protocol.BOLT4: EmptyPath :: BlindingError
+ Lightning.Protocol.BOLT4: EmptyRoute :: ConstructError
+ Lightning.Protocol.BOLT4: ErrorPacket :: ByteString -> ErrorPacket
+ Lightning.Protocol.BOLT4: ExpiryTooFar :: Failure
+ Lightning.Protocol.BOLT4: ExpiryTooSoon :: !ByteString -> Failure
+ Lightning.Protocol.BOLT4: FailureMessage :: !Failure -> !TlvStream -> FailureMessage
+ Lightning.Protocol.BOLT4: FeeInsufficient :: !MilliSatoshi -> !ByteString -> Failure
+ Lightning.Protocol.BOLT4: FieldTooLong :: EncodeError
+ Lightning.Protocol.BOLT4: FinalIncorrectCltvExpiry :: {-# UNPACK #-} !Word32 -> Failure
+ Lightning.Protocol.BOLT4: FinalIncorrectHtlcAmount :: !MilliSatoshi -> Failure
+ Lightning.Protocol.BOLT4: Forward :: !ForwardInfo -> ProcessResult
+ Lightning.Protocol.BOLT4: ForwardInfo :: !HopPayload -> !Maybe BlindedInfo -> !OnionPacket -> !SharedSecret -> ForwardInfo
+ Lightning.Protocol.BOLT4: HmacMismatch :: ProcessError
+ Lightning.Protocol.BOLT4: Hop :: !Point -> !HopPayload -> Hop
+ Lightning.Protocol.BOLT4: HopPayload :: !Maybe MilliSatoshi -> !Maybe Word32 -> !Maybe ShortChannelId -> !Maybe PaymentData -> !Maybe ByteString -> !Maybe Point -> !Maybe ByteString -> !Maybe MilliSatoshi -> !TlvStream -> HopPayload
+ Lightning.Protocol.BOLT4: IncorrectCltvExpiry :: {-# UNPACK #-} !Word32 -> !ByteString -> Failure
+ Lightning.Protocol.BOLT4: IncorrectOrUnknownPaymentDetails :: !MilliSatoshi -> {-# UNPACK #-} !Word32 -> Failure
+ Lightning.Protocol.BOLT4: InvalidFailureData :: {-# UNPACK #-} !Word16 -> DecodeError
+ Lightning.Protocol.BOLT4: InvalidHopData :: {-# UNPACK #-} !Int -> !EncodeError -> BlindingError
+ Lightning.Protocol.BOLT4: InvalidHopPayload :: {-# UNPACK #-} !Int -> ConstructError
+ Lightning.Protocol.BOLT4: InvalidHopPubKey :: {-# UNPACK #-} !Int -> ConstructError
+ Lightning.Protocol.BOLT4: InvalidLength :: DecodeError
+ Lightning.Protocol.BOLT4: InvalidNodeId :: {-# UNPACK #-} !Int -> BlindingError
+ Lightning.Protocol.BOLT4: InvalidOnionBlinding :: !OnionHash -> Failure
+ Lightning.Protocol.BOLT4: InvalidOnionHmac :: !OnionHash -> Failure
+ Lightning.Protocol.BOLT4: InvalidOnionKey :: !OnionHash -> Failure
+ Lightning.Protocol.BOLT4: InvalidOnionPayload :: !Maybe (Word64, Word16) -> Failure
+ Lightning.Protocol.BOLT4: InvalidOnionVersion :: !OnionHash -> Failure
+ Lightning.Protocol.BOLT4: InvalidPathKey :: ProcessError
+ Lightning.Protocol.BOLT4: InvalidPayload :: !DecodeError -> ProcessError
+ Lightning.Protocol.BOLT4: InvalidPayloadLength :: ProcessError
+ Lightning.Protocol.BOLT4: InvalidPoint :: DecodeError
+ Lightning.Protocol.BOLT4: InvalidPublicKey :: ProcessError
+ Lightning.Protocol.BOLT4: InvalidRecipientData :: ProcessError
+ Lightning.Protocol.BOLT4: InvalidTlvStream :: !TlvError -> DecodeError
+ Lightning.Protocol.BOLT4: InvalidTlvValue :: {-# UNPACK #-} !Word64 -> DecodeError
+ Lightning.Protocol.BOLT4: InvalidVersion :: {-# UNPACK #-} !Word8 -> ProcessError
+ Lightning.Protocol.BOLT4: MalformedFailure :: {-# UNPACK #-} !Int -> Attribution
+ Lightning.Protocol.BOLT4: MissingPathKey :: ProcessError
+ Lightning.Protocol.BOLT4: MppTimeout :: Failure
+ Lightning.Protocol.BOLT4: OnionPacket :: {-# UNPACK #-} !Word8 -> !Point -> !HopPayloads -> !Hmac32 -> OnionPacket
+ Lightning.Protocol.BOLT4: PayloadsTooLarge :: {-# UNPACK #-} !Int -> ConstructError
+ Lightning.Protocol.BOLT4: PaymentConstraints :: {-# UNPACK #-} !Word32 -> !MilliSatoshi -> PaymentConstraints
+ Lightning.Protocol.BOLT4: PaymentData :: !PaymentSecret -> !MilliSatoshi -> PaymentData
+ Lightning.Protocol.BOLT4: PaymentRelay :: {-# UNPACK #-} !Word16 -> {-# UNPACK #-} !Word32 -> {-# UNPACK #-} !Word32 -> PaymentRelay
+ Lightning.Protocol.BOLT4: PermanentChannelFailure :: Failure
+ Lightning.Protocol.BOLT4: PermanentNodeFailure :: Failure
+ Lightning.Protocol.BOLT4: Receive :: !ReceiveInfo -> ProcessResult
+ Lightning.Protocol.BOLT4: ReceiveInfo :: !HopPayload -> !Maybe BlindedInfo -> !SharedSecret -> ReceiveInfo
+ Lightning.Protocol.BOLT4: RequiredChannelFeatureMissing :: Failure
+ Lightning.Protocol.BOLT4: RequiredNodeFeatureMissing :: Failure
+ Lightning.Protocol.BOLT4: TemporaryChannelFailure :: !ByteString -> Failure
+ Lightning.Protocol.BOLT4: TemporaryNodeFailure :: Failure
+ Lightning.Protocol.BOLT4: TooManyHops :: ConstructError
+ Lightning.Protocol.BOLT4: UnexpectedPathKey :: ProcessError
+ Lightning.Protocol.BOLT4: UnknownFailure :: {-# UNPACK #-} !Word16 -> !ByteString -> Failure
+ Lightning.Protocol.BOLT4: UnknownNextPeer :: Failure
+ Lightning.Protocol.BOLT4: UnknownOrigin :: Attribution
+ Lightning.Protocol.BOLT4: [bh_blinded_node_id] :: BlindedHop -> !Point
+ Lightning.Protocol.BOLT4: [bh_encrypted_data] :: BlindedHop -> !ByteString
+ Lightning.Protocol.BOLT4: [bhd_allowed_features] :: BlindedHopData -> !Maybe FeatureVector
+ Lightning.Protocol.BOLT4: [bhd_extra] :: BlindedHopData -> !TlvStream
+ Lightning.Protocol.BOLT4: [bhd_next_node_id] :: BlindedHopData -> !Maybe Point
+ Lightning.Protocol.BOLT4: [bhd_next_path_key_override] :: BlindedHopData -> !Maybe Point
+ Lightning.Protocol.BOLT4: [bhd_padding] :: BlindedHopData -> !Maybe ByteString
+ Lightning.Protocol.BOLT4: [bhd_path_id] :: BlindedHopData -> !Maybe ByteString
+ Lightning.Protocol.BOLT4: [bhd_payment_constraints] :: BlindedHopData -> !Maybe PaymentConstraints
+ Lightning.Protocol.BOLT4: [bhd_payment_relay] :: BlindedHopData -> !Maybe PaymentRelay
+ Lightning.Protocol.BOLT4: [bhd_short_channel_id] :: BlindedHopData -> !Maybe ShortChannelId
+ Lightning.Protocol.BOLT4: [bi_data] :: BlindedInfo -> !BlindedHopData
+ Lightning.Protocol.BOLT4: [bi_next_path_key] :: BlindedInfo -> !Point
+ Lightning.Protocol.BOLT4: [bp_first_node_id] :: BlindedPath -> !Point
+ Lightning.Protocol.BOLT4: [bp_first_path_key] :: BlindedPath -> !Point
+ Lightning.Protocol.BOLT4: [bp_hops] :: BlindedPath -> ![BlindedHop]
+ Lightning.Protocol.BOLT4: [fm_failure] :: FailureMessage -> !Failure
+ Lightning.Protocol.BOLT4: [fm_tlvs] :: FailureMessage -> !TlvStream
+ Lightning.Protocol.BOLT4: [fwd_blinded] :: ForwardInfo -> !Maybe BlindedInfo
+ Lightning.Protocol.BOLT4: [fwd_next_packet] :: ForwardInfo -> !OnionPacket
+ Lightning.Protocol.BOLT4: [fwd_payload] :: ForwardInfo -> !HopPayload
+ Lightning.Protocol.BOLT4: [fwd_shared_secret] :: ForwardInfo -> !SharedSecret
+ Lightning.Protocol.BOLT4: [hop_payload] :: Hop -> !HopPayload
+ Lightning.Protocol.BOLT4: [hop_pubkey] :: Hop -> !Point
+ Lightning.Protocol.BOLT4: [hp_amt_to_forward] :: HopPayload -> !Maybe MilliSatoshi
+ Lightning.Protocol.BOLT4: [hp_current_path_key] :: HopPayload -> !Maybe Point
+ Lightning.Protocol.BOLT4: [hp_encrypted_data] :: HopPayload -> !Maybe ByteString
+ Lightning.Protocol.BOLT4: [hp_extra] :: HopPayload -> !TlvStream
+ Lightning.Protocol.BOLT4: [hp_outgoing_cltv_value] :: HopPayload -> !Maybe Word32
+ Lightning.Protocol.BOLT4: [hp_payment_data] :: HopPayload -> !Maybe PaymentData
+ Lightning.Protocol.BOLT4: [hp_payment_metadata] :: HopPayload -> !Maybe ByteString
+ Lightning.Protocol.BOLT4: [hp_short_channel_id] :: HopPayload -> !Maybe ShortChannelId
+ Lightning.Protocol.BOLT4: [hp_total_amount_msat] :: HopPayload -> !Maybe MilliSatoshi
+ Lightning.Protocol.BOLT4: [onion_hmac] :: OnionPacket -> !Hmac32
+ Lightning.Protocol.BOLT4: [onion_hop_payloads] :: OnionPacket -> !HopPayloads
+ Lightning.Protocol.BOLT4: [onion_public_key] :: OnionPacket -> !Point
+ Lightning.Protocol.BOLT4: [onion_version] :: OnionPacket -> {-# UNPACK #-} !Word8
+ Lightning.Protocol.BOLT4: [pc_htlc_minimum_msat] :: PaymentConstraints -> !MilliSatoshi
+ Lightning.Protocol.BOLT4: [pc_max_cltv_expiry] :: PaymentConstraints -> {-# UNPACK #-} !Word32
+ Lightning.Protocol.BOLT4: [pd_payment_secret] :: PaymentData -> !PaymentSecret
+ Lightning.Protocol.BOLT4: [pd_total_msat] :: PaymentData -> !MilliSatoshi
+ Lightning.Protocol.BOLT4: [pr_cltv_expiry_delta] :: PaymentRelay -> {-# UNPACK #-} !Word16
+ Lightning.Protocol.BOLT4: [pr_fee_base_msat] :: PaymentRelay -> {-# UNPACK #-} !Word32
+ Lightning.Protocol.BOLT4: [pr_fee_proportional_millionths] :: PaymentRelay -> {-# UNPACK #-} !Word32
+ Lightning.Protocol.BOLT4: [rcv_blinded] :: ReceiveInfo -> !Maybe BlindedInfo
+ Lightning.Protocol.BOLT4: [rcv_payload] :: ReceiveInfo -> !HopPayload
+ Lightning.Protocol.BOLT4: [rcv_shared_secret] :: ReceiveInfo -> !SharedSecret
+ Lightning.Protocol.BOLT4: construct :: SecretKey -> [Hop] -> ByteString -> Either ConstructError (OnionPacket, [SharedSecret])
+ Lightning.Protocol.BOLT4: construct_error :: SharedSecret -> FailureMessage -> Either EncodeError ErrorPacket
+ Lightning.Protocol.BOLT4: create_blinded_path :: SecretKey -> [(Point, BlindedHopData)] -> Either BlindingError BlindedPath
+ Lightning.Protocol.BOLT4: data Attribution
+ Lightning.Protocol.BOLT4: data BlindedHop
+ Lightning.Protocol.BOLT4: data BlindedHopData
+ Lightning.Protocol.BOLT4: data BlindedInfo
+ Lightning.Protocol.BOLT4: data BlindedPath
+ Lightning.Protocol.BOLT4: data BlindingError
+ Lightning.Protocol.BOLT4: data ConstructError
+ Lightning.Protocol.BOLT4: data DecodeError
+ Lightning.Protocol.BOLT4: data EncodeError
+ Lightning.Protocol.BOLT4: data Failure
+ Lightning.Protocol.BOLT4: data FailureMessage
+ Lightning.Protocol.BOLT4: data ForwardInfo
+ Lightning.Protocol.BOLT4: data Hmac32
+ Lightning.Protocol.BOLT4: data Hop
+ Lightning.Protocol.BOLT4: data HopPayload
+ Lightning.Protocol.BOLT4: data HopPayloads
+ Lightning.Protocol.BOLT4: data OnionHash
+ Lightning.Protocol.BOLT4: data OnionPacket
+ Lightning.Protocol.BOLT4: data PaymentConstraints
+ Lightning.Protocol.BOLT4: data PaymentData
+ Lightning.Protocol.BOLT4: data PaymentRelay
+ Lightning.Protocol.BOLT4: data PaymentSecret
+ Lightning.Protocol.BOLT4: data ProcessError
+ Lightning.Protocol.BOLT4: data ProcessResult
+ Lightning.Protocol.BOLT4: data ReceiveInfo
+ Lightning.Protocol.BOLT4: data SecretKey
+ Lightning.Protocol.BOLT4: data SharedSecret
+ Lightning.Protocol.BOLT4: decode_blinded_hop_data :: ByteString -> Either DecodeError BlindedHopData
+ Lightning.Protocol.BOLT4: decode_failure_message :: ByteString -> Either DecodeError FailureMessage
+ Lightning.Protocol.BOLT4: decode_hop_payload :: ByteString -> Either DecodeError HopPayload
+ Lightning.Protocol.BOLT4: decode_onion_packet :: ByteString -> Either DecodeError OnionPacket
+ Lightning.Protocol.BOLT4: decrypt_recipient_data :: SecretKey -> Point -> ByteString -> Either ProcessError BlindedInfo
+ Lightning.Protocol.BOLT4: empty_blinded_hop_data :: BlindedHopData
+ Lightning.Protocol.BOLT4: empty_hop_payload :: HopPayload
+ Lightning.Protocol.BOLT4: encode_blinded_hop_data :: BlindedHopData -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT4: encode_failure_message :: FailureMessage -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT4: encode_hop_payload :: HopPayload -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT4: encode_onion_packet :: OnionPacket -> ByteString
+ Lightning.Protocol.BOLT4: failure_code :: Failure -> Word16
+ Lightning.Protocol.BOLT4: hmac32 :: ByteString -> Maybe Hmac32
+ Lightning.Protocol.BOLT4: hop_payloads :: ByteString -> Maybe HopPayloads
+ Lightning.Protocol.BOLT4: is_badonion :: Failure -> Bool
+ Lightning.Protocol.BOLT4: is_node :: Failure -> Bool
+ Lightning.Protocol.BOLT4: is_perm :: Failure -> Bool
+ Lightning.Protocol.BOLT4: is_update :: Failure -> Bool
+ Lightning.Protocol.BOLT4: newtype ErrorPacket
+ Lightning.Protocol.BOLT4: onion_hash :: ByteString -> Maybe OnionHash
+ Lightning.Protocol.BOLT4: payment_secret :: ByteString -> Maybe PaymentSecret
+ Lightning.Protocol.BOLT4: process :: SecretKey -> OnionPacket -> ByteString -> Maybe Point -> Either ProcessError ProcessResult
+ Lightning.Protocol.BOLT4: public_key :: SecretKey -> Point
+ Lightning.Protocol.BOLT4: secret_key :: ByteString -> Maybe SecretKey
+ Lightning.Protocol.BOLT4: shared_secret :: ByteString -> Maybe SharedSecret
+ Lightning.Protocol.BOLT4: un_hmac32 :: Hmac32 -> ByteString
+ Lightning.Protocol.BOLT4: un_hop_payloads :: HopPayloads -> ByteString
+ Lightning.Protocol.BOLT4: un_onion_hash :: OnionHash -> ByteString
+ Lightning.Protocol.BOLT4: un_payment_secret :: PaymentSecret -> ByteString
+ Lightning.Protocol.BOLT4: un_shared_secret :: SharedSecret -> ByteString
+ Lightning.Protocol.BOLT4: unwrap_error :: [SharedSecret] -> ErrorPacket -> Attribution
+ Lightning.Protocol.BOLT4: wrap_error :: SharedSecret -> ErrorPacket -> ErrorPacket

Files

CHANGELOG view
@@ -1,4 +1,52 @@ # Changelog -- 0.0.1 (UNRELEASED)+- 0.1.0 (2026-10-10)+  * A substantial rewrite, with breaking changes throughout:++    * The API is exported from Lightning.Protocol.BOLT4 alone, and uses+      snake_case names (e.g. construct, process, construct_error,+      create_blinded_path). The Prim, Codec and Internal modules are+      no longer exposed.++    * Fundamental types come from ppad-bolt1: points are Points,+      amounts are MilliSatoshis, short channel ids and TLV streams are+      bolt1's. allowed_features is a ppad-bolt9 FeatureVector. The+      private BigSize, TLV and truncated-integer codecs are gone.++    * Secret keys (SecretKey) must be exactly 32 bytes encoding a valid+      scalar. Secret keys, shared secrets and payment secrets are+      abstract, with redacted Show instances; shared and payment+      secrets compare in constant time.++    * process takes the path_key received with an onion, derives the+      blinded private key, decrypts encrypted_recipient_data (using+      path_key or current_path_key) and returns its contents and the+      next path key.++    * Hop payloads and encrypted_data_tlv streams reject unknown even+      types and keep unknown odd ones, so re-encoding is exact.+      payment_metadata and total_amount_msat are typed fields.++    * Failures are a typed sum of every failure code in the spec, with+      an escape hatch for unknown codes; invalid_realm is gone, and+      required_channel_feature_missing, unknown_next_peer and+      invalid_onion_blinding are new.++    * unwrap_error reports the hop whose HMAC matches even when its+      failure message is malformed. wrap_error is total, and+      construct_error fails only on an unencodable failure message.++  * Fixes onion construction for routes of three or more hops, the+    framing and padding of failure messages, and decoding of payload+    lengths past the end of hop_payloads, overlong TLV lengths and+    unknown even TLV types.++  * The onion HMAC check is constant time.++  * Tests now cover the onion, error, route blinding and blinded+    payment test vectors of BOLT #4, through the public API.++  * Adds NFData instances, and criterion and weigh benchmarks.++- 0.0.1 (2026-04-18)   * Initial release.
+ bench/Fixtures.hs view
@@ -0,0 +1,150 @@+{-# LANGUAGE OverloadedStrings #-}++module Fixtures (+    Fixtures(..)+  , fixtures+  ) where++import Control.DeepSeq (NFData(..))+import qualified Data.ByteString as BS+import qualified Lightning.Protocol.BOLT1 as BOLT1+import Lightning.Protocol.BOLT4++data Fixtures = Fixtures {+    fx_session       :: !SecretKey+  , fx_ad            :: !BS.ByteString+  , fx_route_1       :: ![Hop]+  , fx_route_5       :: ![Hop]+  , fx_route_20      :: ![Hop]+  , fx_first_node    :: !SecretKey+  , fx_onion_5       :: !OnionPacket+  , fx_onion_1       :: !OnionPacket+  , fx_secrets_5     :: ![SharedSecret]+  , fx_secret        :: !SharedSecret+  , fx_blinded_node  :: !SecretKey+  , fx_blinded_onion :: !OnionPacket+  , fx_path_key      :: !BOLT1.Point+  , fx_path_seed     :: !SecretKey+  , fx_path_nodes    :: ![(BOLT1.Point, BlindedHopData)]+  , fx_encrypted     :: !BS.ByteString+  , fx_failure       :: !FailureMessage+  , fx_error         :: !ErrorPacket+  , fx_returned      :: !ErrorPacket+  , fx_onion_bytes   :: !BS.ByteString+  , fx_payload       :: !HopPayload+  , fx_payload_bytes :: !BS.ByteString+  }++instance NFData Fixtures where+  rnf (Fixtures a b c d e f g h i j k l m n o p q r s t u v) =+    rnf a `seq` rnf b `seq` rnf c `seq` rnf d `seq` rnf e `seq` rnf f+      `seq` rnf g `seq` rnf h `seq` rnf i `seq` rnf j `seq` rnf k+      `seq` rnf l `seq` rnf m `seq` rnf n `seq` rnf o `seq` rnf p+      `seq` rnf q `seq` rnf r `seq` rnf s `seq` rnf t `seq` rnf u+      `seq` rnf v++demand :: String -> Maybe a -> IO a+demand msg = maybe (ioError (userError msg)) pure++right :: Show e => String -> Either e a -> IO a+right msg = either (ioError . userError . (msg ++) . (": " ++) . show) pure++fixtures :: IO Fixtures+fixtures = do+  let key b = demand "secret_key" (secret_key (BS.replicate 32 b))+      ad = BS.replicate 32 0x42+  session <- key 0x41+  nodes <- mapM key [1 .. 20]+  amt <- demand "milli_satoshi" (BOLT1.milli_satoshi 100000)+  scid <- demand "short_channel_id" (BOLT1.short_channel_id 800000 1 0)+  ps <- demand "payment_secret" (payment_secret (BS.replicate 32 0xaa))+  let fwd = empty_hop_payload+        { hp_amt_to_forward = Just amt+        , hp_outgoing_cltv_value = Just 800000+        , hp_short_channel_id = Just scid+        }+      final = empty_hop_payload+        { hp_amt_to_forward = Just amt+        , hp_outgoing_cltv_value = Just 800000+        , hp_payment_data = Just (PaymentData ps amt)+        }+      route n = [ Hop (public_key k) fwd | k <- take (n - 1) nodes ]+             ++ [ Hop (public_key k) final+                | k <- take 1 (drop (n - 1) nodes) ]+  first_node <- demand "node" (safe_head nodes)+  (onion_5, secrets_5) <- right "construct" (construct session (route 5) ad)+  (onion_1, _) <- right "construct" (construct session (route 1) ad)++  -- a blinded route through nodes 1 to 3, entered at node 0+  seed <- key 0x55+  let relay = empty_blinded_hop_data+        { bhd_short_channel_id = Just scid+        , bhd_payment_relay = Just (PaymentRelay 40 100 1000)+        }+      path_nodes = [ (public_key k, relay) | k <- take 2 (drop 1 nodes) ]+                ++ [ (public_key k, empty_blinded_hop_data+                       { bhd_path_id = Just (BS.replicate 32 1) })+                   | k <- take 1 (drop 3 nodes) ]+  path <- right "create_blinded_path" (create_blinded_path seed path_nodes)+  hops <- case bp_hops path of+    [h1, h2, h3] -> pure (h1, h2, h3)+    _ -> ioError (userError "expected 3 blinded hops")+  let (h1, h2, h3) = hops+      bhop h pl = Hop (bh_blinded_node_id h)+        pl { hp_encrypted_data = Just (bh_encrypted_data h) }+      entry = Hop (public_key first_node) fwd+      broute = [ entry+               , Hop (bp_first_node_id path) empty_hop_payload+                   { hp_encrypted_data = Just (bh_encrypted_data h1)+                   , hp_current_path_key = Just (bp_first_path_key path) }+               , bhop h2 empty_hop_payload+               , bhop h3 empty_hop_payload+                   { hp_amt_to_forward = Just amt+                   , hp_outgoing_cltv_value = Just 800000+                   , hp_total_amount_msat = Just amt } ]+  (bonion, _) <- right "construct" (construct session broute ad)+  -- the onion received, with a path_key, by node 2+  node1 <- demand "node" (safe_head (drop 1 nodes))+  node2 <- demand "node" (safe_head (drop 2 nodes))+  f0 <- forwarded (process first_node bonion ad Nothing)+  f1 <- forwarded (process node1 (fwd_next_packet f0) ad Nothing)+  b1 <- demand "blinded" (fwd_blinded f1)++  -- node 4 fails a 5-hop payment+  ss4 <- demand "secret" (safe_head (drop 4 secrets_5))+  let failure = FailureMessage TemporaryNodeFailure BOLT1.empty_tlv_stream+  err <- right "construct_error" (construct_error ss4 failure)+  let returned = foldr wrap_error err (take 4 secrets_5)++  payload_bytes <- right "encode_hop_payload" (encode_hop_payload final)+  pure Fixtures {+      fx_session       = session+    , fx_ad            = ad+    , fx_route_1       = route 1+    , fx_route_5       = route 5+    , fx_route_20      = route 20+    , fx_first_node    = first_node+    , fx_onion_5       = onion_5+    , fx_onion_1       = onion_1+    , fx_secrets_5     = secrets_5+    , fx_secret        = ss4+    , fx_blinded_node  = node2+    , fx_blinded_onion = fwd_next_packet f1+    , fx_path_key      = bi_next_path_key b1+    , fx_path_seed     = seed+    , fx_path_nodes    = path_nodes+    , fx_encrypted     = bh_encrypted_data h2+    , fx_failure       = failure+    , fx_error         = err+    , fx_returned      = returned+    , fx_onion_bytes   = encode_onion_packet onion_5+    , fx_payload       = final+    , fx_payload_bytes = payload_bytes+    }+  where+    safe_head xs = case xs of+      x : _ -> Just x+      []    -> Nothing+    forwarded r = case r of+      Right (Forward f) -> pure f+      _ -> ioError (userError ("expected Forward: " ++ show r))
bench/Main.hs view
@@ -1,10 +1,55 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}- module Main where  import Criterion.Main+import Fixtures+import Lightning.Protocol.BOLT4  main :: IO () main = defaultMain [+    env fixtures $ \fx -> bgroup "ppad-bolt4" [+      bgroup "construct" [+        bench "1 hop" $+          nf (construct (fx_session fx) (fx_route_1 fx)) (fx_ad fx)+      , bench "5 hops" $+          nf (construct (fx_session fx) (fx_route_5 fx)) (fx_ad fx)+      , bench "20 hops" $+          nf (construct (fx_session fx) (fx_route_20 fx)) (fx_ad fx)+      ]+    , bgroup "process" [+        bench "forward" $+          nf (process (fx_first_node fx) (fx_onion_5 fx) (fx_ad fx))+            Nothing+      , bench "receive" $+          nf (process (fx_first_node fx) (fx_onion_1 fx) (fx_ad fx))+            Nothing+      , bench "forward (blinded)" $+          nf (process (fx_blinded_node fx) (fx_blinded_onion fx) (fx_ad fx))+            (Just (fx_path_key fx))+      ]+    , bgroup "returning errors" [+        bench "construct_error" $+          nf (construct_error (fx_secret fx)) (fx_failure fx)+      , bench "wrap_error" $+          nf (wrap_error (fx_secret fx)) (fx_error fx)+      , bench "unwrap_error (hop 4 of 5)" $+          nf (unwrap_error (fx_secrets_5 fx)) (fx_returned fx)+      ]+    , bgroup "route blinding" [+        bench "create_blinded_path (3 hops)" $+          nf (create_blinded_path (fx_path_seed fx)) (fx_path_nodes fx)+      , bench "decrypt_recipient_data" $+          nf (decrypt_recipient_data (fx_blinded_node fx) (fx_path_key fx))+            (fx_encrypted fx)+      ]+    , bgroup "codecs" [+        bench "encode_onion_packet" $+          nf encode_onion_packet (fx_onion_5 fx)+      , bench "decode_onion_packet" $+          nf decode_onion_packet (fx_onion_bytes fx)+      , bench "encode_hop_payload" $+          nf encode_hop_payload (fx_payload fx)+      , bench "decode_hop_payload" $+          nf decode_hop_payload (fx_payload_bytes fx)+      ]+    ]   ]
bench/Weight.hs view
@@ -1,9 +1,38 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}- module Main where +import Fixtures+import Lightning.Protocol.BOLT4 import Weigh  main :: IO ()-main = mainWith (pure ())+main = do+  fx <- fixtures+  mainWith $ do+    wgroup "construct" $ do+      func "1 hop" (construct (fx_session fx) (fx_route_1 fx)) (fx_ad fx)+      func "5 hops" (construct (fx_session fx) (fx_route_5 fx)) (fx_ad fx)+      func "20 hops" (construct (fx_session fx) (fx_route_20 fx)) (fx_ad fx)+    wgroup "process" $ do+      func "forward"+        (process (fx_first_node fx) (fx_onion_5 fx) (fx_ad fx)) Nothing+      func "receive"+        (process (fx_first_node fx) (fx_onion_1 fx) (fx_ad fx)) Nothing+      func "forward (blinded)"+        (process (fx_blinded_node fx) (fx_blinded_onion fx) (fx_ad fx))+        (Just (fx_path_key fx))+    wgroup "returning errors" $ do+      func "construct_error" (construct_error (fx_secret fx)) (fx_failure fx)+      func "wrap_error" (wrap_error (fx_secret fx)) (fx_error fx)+      func "unwrap_error (hop 4 of 5)"+        (unwrap_error (fx_secrets_5 fx)) (fx_returned fx)+    wgroup "route blinding" $ do+      func "create_blinded_path (3 hops)"+        (create_blinded_path (fx_path_seed fx)) (fx_path_nodes fx)+      func "decrypt_recipient_data"+        (decrypt_recipient_data (fx_blinded_node fx) (fx_path_key fx))+        (fx_encrypted fx)+    wgroup "codecs" $ do+      func "encode_onion_packet" encode_onion_packet (fx_onion_5 fx)+      func "decode_onion_packet" decode_onion_packet (fx_onion_bytes fx)+      func "encode_hop_payload" encode_hop_payload (fx_payload fx)+      func "decode_hop_payload" decode_hop_payload (fx_payload_bytes fx)
lib/Lightning/Protocol/BOLT4.hs view
@@ -6,19 +6,147 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- BOLT4 onion routing for the Lightning Network.+-- Onion routing for the Lightning Network, per BOLT #4+-- (<https://github.com/lightning/bolts/blob/master/04-onion-routing.md>):+-- onion construction and processing, returning errors, and route+-- blinding. ----- This module re-exports the public interface from submodules.+-- The examples below assume:+--+-- >>> :set -XOverloadedStrings+-- >>> import qualified Data.ByteString as BS+-- >>> import qualified Lightning.Protocol.BOLT1 as BOLT1+-- >>> import Lightning.Protocol.BOLT4+--+-- Construct a two-hop onion:+--+-- >>> let Just session = secret_key (BS.replicate 32 0x41)+-- >>> let Just alice = secret_key (BS.replicate 32 0x42)+-- >>> let Just bob = secret_key (BS.replicate 32 0x43)+-- >>> let Just scid = BOLT1.short_channel_id 800000 1 0+-- >>> let Just amt = BOLT1.milli_satoshi 100000+-- >>> :{+-- let alice_payload = empty_hop_payload {+--         hp_amt_to_forward      = Just amt+--       , hp_outgoing_cltv_value = Just 800040+--       , hp_short_channel_id    = Just scid+--       }+--     bob_payload = empty_hop_payload {+--         hp_amt_to_forward      = Just amt+--       , hp_outgoing_cltv_value = Just 800000+--       }+--     route = [ Hop (public_key alice) alice_payload+--             , Hop (public_key bob) bob_payload ]+--     payment_hash = BS.replicate 32 0xff+-- :}+-- >>> let Right (onion, secrets) = construct session route payment_hash+--+-- Process it at each hop:+--+-- >>> let Right (Forward fwd) = process alice onion payment_hash Nothing+-- >>> hp_short_channel_id (fwd_payload fwd) == Just scid+-- True+-- >>> let next = fwd_next_packet fwd+-- >>> let Right (Receive rcv) = process bob next payment_hash Nothing+-- >>> hp_outgoing_cltv_value (rcv_payload rcv)+-- Just 800000+--+-- Fail at the final hop, wrap the error at the first, and attribute it+-- at the origin:+--+-- >>> let tlvs = BOLT1.empty_tlv_stream+-- >>> let failure = FailureMessage TemporaryNodeFailure tlvs+-- >>> let Right err = construct_error (rcv_shared_secret rcv) failure+-- >>> let returned = wrap_error (fwd_shared_secret fwd) err+-- >>> unwrap_error secrets returned == Attributed 1 failure+-- True  module Lightning.Protocol.BOLT4 (-    -- * Re-exports-    module Lightning.Protocol.BOLT4.Blinding-  , module Lightning.Protocol.BOLT4.Codec-  , module Lightning.Protocol.BOLT4.Prim-  , module Lightning.Protocol.BOLT4.Types+  -- * Keys+    SecretKey+  , secret_key+  , public_key+  , SharedSecret+  , shared_secret+  , un_shared_secret++  -- * Onion packets+  , OnionPacket(..)+  , HopPayloads+  , hop_payloads+  , un_hop_payloads+  , Hmac32+  , hmac32+  , un_hmac32+  , encode_onion_packet+  , decode_onion_packet++  -- * Hop payloads+  , HopPayload(..)+  , empty_hop_payload+  , PaymentData(..)+  , PaymentSecret+  , payment_secret+  , un_payment_secret+  , encode_hop_payload+  , decode_hop_payload++  -- * Constructing onions+  , Hop(..)+  , construct+  , ConstructError(..)++  -- * Processing onions+  , process+  , ProcessResult(..)+  , ForwardInfo(..)+  , ReceiveInfo(..)+  , BlindedInfo(..)+  , ProcessError(..)++  -- * Route blinding+  , BlindedPath(..)+  , BlindedHop(..)+  , create_blinded_path+  , BlindingError(..)+  , decrypt_recipient_data+  , BlindedHopData(..)+  , empty_blinded_hop_data+  , PaymentRelay(..)+  , PaymentConstraints(..)+  , encode_blinded_hop_data+  , decode_blinded_hop_data++  -- * Failure messages+  , FailureMessage(..)+  , Failure(..)+  , OnionHash+  , onion_hash+  , un_onion_hash+  , failure_code+  , is_badonion+  , is_perm+  , is_node+  , is_update+  , encode_failure_message+  , decode_failure_message++  -- * Returning errors+  , ErrorPacket(..)+  , construct_error+  , wrap_error+  , unwrap_error+  , Attribution(..)++  -- * Encoding errors+  , DecodeError(..)+  , EncodeError(..)   ) where  import Lightning.Protocol.BOLT4.Blinding import Lightning.Protocol.BOLT4.Codec+import Lightning.Protocol.BOLT4.Construct+import Lightning.Protocol.BOLT4.Error import Lightning.Protocol.BOLT4.Prim+import Lightning.Protocol.BOLT4.Process import Lightning.Protocol.BOLT4.Types
lib/Lightning/Protocol/BOLT4/Blinding.hs view
@@ -1,6 +1,5 @@-{-# OPTIONS_HADDOCK prune #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE DeriveGeneric #-}  -- | -- Module: Lightning.Protocol.BOLT4.Blinding@@ -8,384 +7,137 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Route blinding for BOLT4 onion routing.+-- Route blinding.  module Lightning.Protocol.BOLT4.Blinding (-    -- * Types-    BlindedPath(..)-  , BlindedHop(..)-  , BlindedHopData(..)-  , PaymentRelay(..)-  , PaymentConstraints(..)-  , BlindingError(..)--    -- * Path creation-  , createBlindedPath--    -- * Hop processing-  , processBlindedHop--    -- * Key derivation (exported for testing)-  , deriveBlindingRho-  , deriveBlindedNodeId-  , nextEphemeral--    -- * TLV encoding (exported for testing)-  , encodeBlindedHopData-  , decodeBlindedHopData--    -- * Encryption (exported for testing)-  , encryptHopData-  , decryptHopData+    BlindingError(..)+  , create_blinded_path+  , decrypt_recipient_data+  , unblind   ) where +import Control.DeepSeq (NFData) import qualified Crypto.AEAD.ChaCha20Poly1305 as AEAD import qualified Crypto.Curve.Secp256k1 as Secp256k1-import qualified Crypto.Hash.SHA256 as SHA256 import qualified Data.ByteString as BS-import qualified Data.ByteString.Builder as B-import Data.Word (Word16, Word32, Word64)-import qualified Numeric.Montgomery.Secp256k1.Scalar as S+import GHC.Generics (Generic)+import qualified Lightning.Protocol.BOLT1 as BOLT1 import Lightning.Protocol.BOLT4.Codec-  ( encodeShortChannelId, decodeShortChannelId-  , encodeTlvStream, decodeTlvStream-  , toStrict, word16BE, word32BE-  , encodeWord64TU, decodeWord64TU-  , encodeWord32TU, decodeWord32TU-  )-import Lightning.Protocol.BOLT4.Prim (SharedSecret(..), DerivedKey(..))-import Lightning.Protocol.BOLT4.Types (ShortChannelId(..), TlvRecord(..))---- Types ------------------------------------------------------------------------- | A blinded route provided by recipient.-data BlindedPath = BlindedPath-  { bpIntroductionNode :: !Secp256k1.Projective  -- ^ First node (unblinded)-  , bpBlindingKey      :: !Secp256k1.Projective  -- ^ E_0, initial ephemeral-  , bpBlindedHops      :: ![BlindedHop]-  } deriving (Eq, Show)---- | A single hop in a blinded path.-data BlindedHop = BlindedHop-  { bhBlindedNodeId :: !BS.ByteString  -- ^ 33 bytes, blinded pubkey-  , bhEncryptedData :: !BS.ByteString  -- ^ Encrypted routing data-  } deriving (Eq, Show)---- | Data encrypted for each blinded hop (before encryption).-data BlindedHopData = BlindedHopData-  { bhdPadding             :: !(Maybe BS.ByteString)  -- ^ TLV 1-  , bhdShortChannelId      :: !(Maybe ShortChannelId) -- ^ TLV 2-  , bhdNextNodeId          :: !(Maybe BS.ByteString)  -- ^ TLV 4, 33-byte pubkey-  , bhdPathId              :: !(Maybe BS.ByteString)  -- ^ TLV 6-  , bhdNextPathKeyOverride :: !(Maybe BS.ByteString)  -- ^ TLV 8-  , bhdPaymentRelay        :: !(Maybe PaymentRelay)   -- ^ TLV 10-  , bhdPaymentConstraints  :: !(Maybe PaymentConstraints) -- ^ TLV 12-  , bhdAllowedFeatures     :: !(Maybe BS.ByteString)  -- ^ TLV 14-  } deriving (Eq, Show)---- | Payment relay parameters (TLV 10).-data PaymentRelay = PaymentRelay-  { prCltvExpiryDelta  :: {-# UNPACK #-} !Word16-  , prFeeProportional  :: {-# UNPACK #-} !Word32  -- ^ Fee in millionths-  , prFeeBaseMsat      :: {-# UNPACK #-} !Word32-  } deriving (Eq, Show)---- | Payment constraints (TLV 12).-data PaymentConstraints = PaymentConstraints-  { pcMaxCltvExpiry   :: {-# UNPACK #-} !Word32-  , pcHtlcMinimumMsat :: {-# UNPACK #-} !Word64-  } deriving (Eq, Show)+import Lightning.Protocol.BOLT4.Prim+import Lightning.Protocol.BOLT4.Types --- | Errors during blinding operations.+-- | Why a blinded route could not be created. data BlindingError-  = InvalidSeed-  | EmptyPath-  | InvalidNodeKey Int-  | DecryptionFailed-  | InvalidPathKey-  deriving (Eq, Show)---- Key derivation ---------------------------------------------------------------- | Derive rho key for encrypting hop data.------ @rho = HMAC-SHA256(key="rho", data=shared_secret)@-deriveBlindingRho :: SharedSecret -> DerivedKey-deriveBlindingRho (SharedSecret !ss) =-  let SHA256.MAC !result = SHA256.hmac "rho" ss-  in  DerivedKey result-{-# INLINE deriveBlindingRho #-}---- | Derive blinded node ID from shared secret and node pubkey.------ @B_i = HMAC256("blinded_node_id", ss_i) * N_i@-deriveBlindedNodeId-  :: SharedSecret-  -> Secp256k1.Projective-  -> Maybe BS.ByteString-deriveBlindedNodeId (SharedSecret !ss) !nodePub = do-  let SHA256.MAC !hmacResult = SHA256.hmac "blinded_node_id" ss-  sk <- Secp256k1.roll32 hmacResult-  blindedPub <- Secp256k1.mul nodePub sk-  pure $! Secp256k1.serialize_point blindedPub-{-# INLINE deriveBlindedNodeId #-}---- | Compute next ephemeral key pair.------ @e_{i+1} = SHA256(E_i || ss_i) * e_i@--- @E_{i+1} = SHA256(E_i || ss_i) * E_i@-nextEphemeral-  :: BS.ByteString        -- ^ e_i (32-byte secret key)-  -> Secp256k1.Projective -- ^ E_i-  -> SharedSecret         -- ^ ss_i-  -> Maybe (BS.ByteString, Secp256k1.Projective)  -- ^ (e_{i+1}, E_{i+1})-nextEphemeral !secKey !pubKey (SharedSecret !ss) = do-  let !pubBytes = Secp256k1.serialize_point pubKey-      !blindingFactor = SHA256.hash (pubBytes <> ss)-  bfInt <- Secp256k1.roll32 blindingFactor-  -- Compute e_{i+1} = e_i * blindingFactor (mod q)-  let !newSecKey = mulSecKey secKey blindingFactor-  -- Compute E_{i+1} = E_i * blindingFactor-  newPubKey <- Secp256k1.mul pubKey bfInt-  pure (newSecKey, newPubKey)-{-# INLINE nextEphemeral #-}---- | Compute blinding factor for next path key (public key only).-nextPathKey-  :: Secp256k1.Projective -- ^ E_i-  -> SharedSecret         -- ^ ss_i-  -> Maybe Secp256k1.Projective  -- ^ E_{i+1}-nextPathKey !pubKey (SharedSecret !ss) = do-  let !pubBytes = Secp256k1.serialize_point pubKey-      !blindingFactor = SHA256.hash (pubBytes <> ss)-  bfInt <- Secp256k1.roll32 blindingFactor-  Secp256k1.mul pubKey bfInt-{-# INLINE nextPathKey #-}+  = EmptyPath+    -- ^ the route has no hops+  | InvalidNodeId {-# UNPACK #-} !Int+    -- ^ the node id of the hop with this index is not a valid point+  | InvalidHopData {-# UNPACK #-} !Int !EncodeError+    -- ^ the data of the hop with this index can't be encoded+  deriving (Eq, Show, Generic) --- Encryption/Decryption -----------------------------------------------------+instance NFData BlindingError --- | Encrypt hop data with ChaCha20-Poly1305.+-- | Create a blinded route to oneself, given a seed for the route's+--   ephemeral keys (which must be fresh and random for every route) and+--   each node's id and data, from the introduction node to oneself. ----- Uses rho key and 12-byte zero nonce, empty AAD.-encryptHopData :: DerivedKey -> BlindedHopData -> BS.ByteString-encryptHopData (DerivedKey !rho) !hopData =-  let !plaintext = encodeBlindedHopData hopData-      !nonce = BS.replicate 12 0-  in  case AEAD.encrypt BS.empty rho nonce plaintext of-        Left e -> error $ "encryptHopData: unexpected AEAD error: " ++ show e-        Right (!ciphertext, !mac) -> ciphertext <> mac-{-# INLINE encryptHopData #-}---- | Decrypt hop data with ChaCha20-Poly1305.-decryptHopData :: DerivedKey -> BS.ByteString -> Maybe BlindedHopData-decryptHopData (DerivedKey !rho) !encData-  | BS.length encData < 16 = Nothing-  | otherwise = do-      let !ciphertext = BS.take (BS.length encData - 16) encData-          !mac = BS.drop (BS.length encData - 16) encData-          !nonce = BS.replicate 12 0-      case AEAD.decrypt BS.empty rho nonce (ciphertext, mac) of-        Left _ -> Nothing-        Right !plaintext -> decodeBlindedHopData plaintext-{-# INLINE decryptHopData #-}---- TLV Encoding/Decoding --------------------------------------------------------- | Encode BlindedHopData to TLV stream.-encodeBlindedHopData :: BlindedHopData -> BS.ByteString-encodeBlindedHopData !bhd = encodeTlvStream (buildTlvs bhd)-  where-    buildTlvs :: BlindedHopData -> [TlvRecord]-    buildTlvs (BlindedHopData pad sci nid pid pko pr pc af) =-      let pad'  = maybe [] (\p -> [TlvRecord 1 p]) pad-          sci'  = maybe [] (\s -> [TlvRecord 2 (encodeShortChannelId s)]) sci-          nid'  = maybe [] (\n -> [TlvRecord 4 n]) nid-          pid'  = maybe [] (\p -> [TlvRecord 6 p]) pid-          pko'  = maybe [] (\k -> [TlvRecord 8 k]) pko-          pr'   = maybe [] (\r -> [TlvRecord 10 (encodePaymentRelay r)]) pr-          pc'   = maybe [] (\c -> [TlvRecord 12 (encodePaymentConstraints c)]) pc-          af'   = maybe [] (\f -> [TlvRecord 14 f]) af-      in  pad' ++ sci' ++ nid' ++ pid' ++ pko' ++ pr' ++ pc' ++ af'-{-# INLINE encodeBlindedHopData #-}---- | Decode TLV stream to BlindedHopData.-decodeBlindedHopData :: BS.ByteString -> Maybe BlindedHopData-decodeBlindedHopData !bs = do-  tlvs <- decodeTlvStream bs-  parseBlindedHopData tlvs--parseBlindedHopData :: [TlvRecord] -> Maybe BlindedHopData-parseBlindedHopData = go emptyHopData+--   >>> let Just seed = secret_key (BS.replicate 32 0x01)+--   >>> let Just me = secret_key (BS.replicate 32 0x45)+--   >>> let dat = empty_blinded_hop_data { bhd_path_id = Just "deadbeef" }+--   >>> let Right path = create_blinded_path seed [(public_key me, dat)]+--   >>> bp_first_node_id path == public_key me+--   True+--   >>> length (bp_hops path)+--   1+create_blinded_path+  :: SecretKey+  -> [(BOLT1.Point, BlindedHopData)]+  -> Either BlindingError BlindedPath+create_blinded_path seed nodes = case nodes of+  [] -> Left EmptyPath+  (intro, _) : _ -> do+    hops <- go (sk_bytes seed) (sk_pub seed) (zip [0 ..] nodes)+    pure (BlindedPath intro (public_key seed) hops)   where-    emptyHopData :: BlindedHopData-    emptyHopData = BlindedHopData-      Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing--    go :: BlindedHopData -> [TlvRecord] -> Maybe BlindedHopData-    go !bhd [] = Just bhd-    go !bhd (TlvRecord typ val : rest) = case typ of-      1  -> go bhd { bhdPadding = Just val } rest-      2  -> do-        sci <- decodeShortChannelId val-        go bhd { bhdShortChannelId = Just sci } rest-      4  -> go bhd { bhdNextNodeId = Just val } rest-      6  -> go bhd { bhdPathId = Just val } rest-      8  -> go bhd { bhdNextPathKeyOverride = Just val } rest-      10 -> do-        pr <- decodePaymentRelay val-        go bhd { bhdPaymentRelay = Just pr } rest-      12 -> do-        pc <- decodePaymentConstraints val-        go bhd { bhdPaymentConstraints = Just pc } rest-      14 -> go bhd { bhdAllowedFeatures = Just val } rest-      _  -> go bhd rest  -- Skip unknown TLVs---- PaymentRelay encoding/decoding --------------------------------------------+    go _ _ [] = Right []+    go e epub ((i, (nid, dat)) : rest) = do+      let note = maybe (Left (InvalidNodeId i)) Right+      node <- note (from_point nid)+      ss <- note (ecdh e node)+      bid <- note (blind_pub node (blinded_node_tweak ss) >>= to_point)+      plain <- either (Left . InvalidHopData i) Right+                 (encode_blinded_hop_data dat)+      enc <- maybe (Left (InvalidHopData i FieldTooLong)) Right+               (encrypt (derive_rho ss) plain)+      let hop = BlindedHop bid enc+      case rest of+        [] -> Right [hop]+        _  -> do+          let bf = blinding_factor epub ss+          e' <- note (blind_scalar e bf)+          epub' <- note (blind_pub epub bf)+          (hop :) <$> go e' epub' rest --- | Encode PaymentRelay.+-- | Decrypt the @encrypted_recipient_data@ received by a node in a+--   blinded route, given the node's private key and the path key it+--   received. ----- Format: 2-byte cltv_delta BE, 4-byte fee_prop BE, tu32 fee_base-encodePaymentRelay :: PaymentRelay -> BS.ByteString-encodePaymentRelay (PaymentRelay !cltv !feeProp !feeBase) = toStrict $-  B.word16BE cltv <>-  B.word32BE feeProp <>-  B.byteString (encodeWord32TU feeBase)-{-# INLINE encodePaymentRelay #-}---- | Decode PaymentRelay.-decodePaymentRelay :: BS.ByteString -> Maybe PaymentRelay-decodePaymentRelay !bs-  | BS.length bs < 6 = Nothing-  | otherwise = do-      let !cltv = word16BE (BS.take 2 bs)-          !feeProp = word32BE (BS.take 4 (BS.drop 2 bs))-          !feeBaseBytes = BS.drop 6 bs-      feeBase <- decodeWord32TU feeBaseBytes-      Just (PaymentRelay cltv feeProp feeBase)-{-# INLINE decodePaymentRelay #-}---- PaymentConstraints encoding/decoding ------------------------------------------ | Encode PaymentConstraints.+--   Returns the decrypted data and the path key for the next node (the+--   data's @next_path_key_override@, if present).+--   'Lightning.Protocol.BOLT4.process' does this for payment onions. ----- Format: 4-byte max_cltv BE, tu64 htlc_min-encodePaymentConstraints :: PaymentConstraints -> BS.ByteString-encodePaymentConstraints (PaymentConstraints !maxCltv !htlcMin) = toStrict $-  B.word32BE maxCltv <>-  B.byteString (encodeWord64TU htlcMin)-{-# INLINE encodePaymentConstraints #-}---- | Decode PaymentConstraints.-decodePaymentConstraints :: BS.ByteString -> Maybe PaymentConstraints-decodePaymentConstraints !bs-  | BS.length bs < 4 = Nothing-  | otherwise = do-      let !maxCltv = word32BE (BS.take 4 bs)-          !htlcMinBytes = BS.drop 4 bs-      htlcMin <- decodeWord64TU htlcMinBytes-      Just (PaymentConstraints maxCltv htlcMin)-{-# INLINE decodePaymentConstraints #-}---- Shared secret computation ----------------------------------------------------- | Compute shared secret from ECDH.-computeSharedSecret-  :: BS.ByteString         -- ^ 32-byte secret key-  -> Secp256k1.Projective  -- ^ Public key-  -> Maybe SharedSecret-computeSharedSecret !secBs !pub = do-  sec <- Secp256k1.roll32 secBs-  ecdhPoint <- Secp256k1.mul pub sec-  let !compressed = Secp256k1.serialize_point ecdhPoint-      !ss = SHA256.hash compressed-  pure $! SharedSecret ss-{-# INLINE computeSharedSecret #-}---- Path creation ----------------------------------------------------------------- | Create a blinded path from a seed and list of nodes with their data.-createBlindedPath-  :: BS.ByteString  -- ^ 32-byte random seed for ephemeral key-  -> [(Secp256k1.Projective, BlindedHopData)]  -- ^ Nodes with their data-  -> Either BlindingError BlindedPath-createBlindedPath !seed !nodes-  | BS.length seed /= 32 = Left InvalidSeed-  | otherwise = case nodes of-      [] -> Left EmptyPath-      ((introNode, _) : _) -> do-        -- (e_0, E_0) = keypair from seed-        e0 <- maybe (Left InvalidSeed) Right (Secp256k1.roll32 seed)-        e0Pub <- maybe (Left InvalidSeed) Right-                   (Secp256k1.mul Secp256k1._CURVE_G e0)-        -- Process all hops-        hops <- processHops seed e0Pub nodes 0-        Right (BlindedPath introNode e0Pub hops)--processHops-  :: BS.ByteString  -- ^ Current e_i-  -> Secp256k1.Projective  -- ^ Current E_i-  -> [(Secp256k1.Projective, BlindedHopData)]-  -> Int  -- ^ Index for error reporting-  -> Either BlindingError [BlindedHop]-processHops _ _ [] _ = Right []-processHops !eKey !ePub ((nodePub, hopData) : rest) !idx = do-  -- ss_i = SHA256(ECDH(e_i, N_i))-  ss <- maybe (Left (InvalidNodeKey idx)) Right-          (computeSharedSecret eKey nodePub)-  -- rho_i = deriveBlindingRho(ss_i)-  let !rho = deriveBlindingRho ss-  -- B_i = deriveBlindedNodeId(ss_i, N_i)-  blindedId <- maybe (Left (InvalidNodeKey idx)) Right-                 (deriveBlindedNodeId ss nodePub)-  -- encrypted_i = encryptHopData(rho_i, data_i)-  let !encData = encryptHopData rho hopData-      !hop = BlindedHop blindedId encData-  -- (e_{i+1}, E_{i+1}) = nextEphemeral(e_i, E_i, ss_i)-  (nextE, nextEPub) <- maybe (Left (InvalidNodeKey idx)) Right-                         (nextEphemeral eKey ePub ss)-  -- Process remaining hops-  restHops <- processHops nextE nextEPub rest (idx + 1)-  Right (hop : restHops)---- Hop processing ------------------------------------------------------------+--   >>> let Just seed = secret_key (BS.replicate 32 0x01)+--   >>> let Just me = secret_key (BS.replicate 32 0x45)+--   >>> let dat = empty_blinded_hop_data { bhd_path_id = Just "deadbeef" }+--   >>> let Right path = create_blinded_path seed [(public_key me, dat)]+--   >>> let [hop] = bp_hops path+--   >>> let pk = bp_first_path_key path+--   >>> let Right info = decrypt_recipient_data me pk (bh_encrypted_data hop)+--   >>> bhd_path_id (bi_data info)+--   Just "deadbeef"+decrypt_recipient_data+  :: SecretKey+  -> BOLT1.Point+  -> BS.ByteString+  -> Either ProcessError BlindedInfo+decrypt_recipient_data sk pk enc = do+  e <- maybe (Left InvalidPathKey) Right (from_point pk)+  ss <- maybe (Left InvalidPathKey) Right (ecdh (sk_bytes sk) e)+  unblind ss e enc --- | Process a blinded hop, returning decrypted data and next path key.-processBlindedHop-  :: BS.ByteString        -- ^ Node's 32-byte private key-  -> Secp256k1.Projective -- ^ E_i, current path key (blinding point)-  -> BS.ByteString        -- ^ encrypted_data from onion payload-  -> Either BlindingError (BlindedHopData, Secp256k1.Projective)-processBlindedHop !nodeSecKey !pathKey !encData = do-  -- ss = SHA256(ECDH(node_seckey, path_key))-  ss <- maybe (Left InvalidPathKey) Right-          (computeSharedSecret nodeSecKey pathKey)-  -- rho = deriveBlindingRho(ss)-  let !rho = deriveBlindingRho ss-  -- hop_data = decryptHopData(rho, encrypted_data)-  hopData <- maybe (Left DecryptionFailed) Right-               (decryptHopData rho encData)-  -- Compute next path key-  nextKey <- case bhdNextPathKeyOverride hopData of-    Just override -> do-      -- Parse override as compressed point-      maybe (Left InvalidPathKey) Right (Secp256k1.parse_point override)-    Nothing -> do-      -- E_next = SHA256(path_key || ss) * path_key-      maybe (Left InvalidPathKey) Right (nextPathKey pathKey ss)-  Right (hopData, nextKey)+-- Decrypt encrypted_recipient_data, given the shared secret with the path+-- key, and the path key.+unblind+  :: SharedSecret+  -> Secp256k1.Projective+  -> BS.ByteString+  -> Either ProcessError BlindedInfo+unblind ss e enc = do+  plain <- maybe (Left InvalidRecipientData) Right+             (decrypt (derive_rho ss) enc)+  dat <- either (const (Left InvalidRecipientData)) Right+           (decode_blinded_hop_data plain)+  next <- case bhd_next_path_key_override dat of+    Just o  -> Right o+    Nothing -> maybe (Left InvalidPathKey) Right+                 (blind_pub e (blinding_factor e ss) >>= to_point)+  pure (BlindedInfo dat next) --- Scalar multiplication -----------------------------------------------------+-- ChaCha20-Poly1305 with an all-zero nonce and no associated data; the+-- tag follows the ciphertext. Fails only on counter overflow.+encrypt :: DerivedKey -> BS.ByteString -> Maybe BS.ByteString+encrypt k plain =+  case AEAD.encrypt BS.empty (un_derived_key k) (BS.replicate 12 0) plain of+    Right (c, t) -> Just (c <> t)+    Left _       -> Nothing --- | Multiply two 32-byte scalars mod curve order q.------ Uses Montgomery multiplication from ppad-fixed for efficiency.-mulSecKey :: BS.ByteString -> BS.ByteString -> BS.ByteString-mulSecKey !a !b =-  let !aW = Secp256k1.unsafe_roll32 a-      !bW = Secp256k1.unsafe_roll32 b-      !aM = S.to aW-      !bM = S.to bW-      !resultM = S.mul aM bM-      !resultW = S.retr resultM-  in  Secp256k1.unroll32 resultW-{-# INLINE mulSecKey #-}+decrypt :: DerivedKey -> BS.ByteString -> Maybe BS.ByteString+decrypt k enc+  | BS.length enc < 16 = Nothing+  | otherwise =+      let (c, t) = BS.splitAt (BS.length enc - 16) enc+      in  case AEAD.decrypt BS.empty (un_derived_key k) (BS.replicate 12 0)+                 (c, t) of+            Right p -> Just p+            Left _  -> Nothing
lib/Lightning/Protocol/BOLT4/Codec.hs view
@@ -1,6 +1,4 @@-{-# OPTIONS_HADDOCK prune #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_HADDOCK hide #-}  -- | -- Module: Lightning.Protocol.BOLT4.Codec@@ -8,380 +6,346 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Serialization and deserialization for BOLT4 types.+-- Encodings of the BOLT #4 types.  module Lightning.Protocol.BOLT4.Codec (-    -- * BigSize encoding-    encodeBigSize-  , decodeBigSize-  , bigSizeLen--    -- * TLV encoding-  , encodeTlv-  , decodeTlv-  , decodeTlvStream-  , encodeTlvStream--    -- * Packet serialization-  , encodeOnionPacket-  , decodeOnionPacket-  , encodeHopPayload-  , decodeHopPayload--    -- * ShortChannelId-  , encodeShortChannelId-  , decodeShortChannelId--    -- * Failure messages-  , encodeFailureMessage-  , decodeFailureMessage--    -- * Internal helpers (for Blinding)-  , toStrict-  , word16BE-  , word32BE-  , encodeWord64TU-  , decodeWord64TU-  , encodeWord32TU-  , decodeWord32TU+    encode_onion_packet+  , decode_onion_packet+  , encode_hop_payload+  , decode_hop_payload+  , encode_blinded_hop_data+  , decode_blinded_hop_data+  , encode_failure_message+  , decode_failure_message   ) where -import Data.Bits (shiftL, shiftR, (.&.))+import Data.Bifunctor (first) import qualified Data.ByteString as BS-import qualified Data.ByteString.Builder as B-import qualified Data.ByteString.Lazy as BL-import Data.Word (Word16, Word32, Word64)+import Data.Maybe (catMaybes)+import Data.Word (Word16, Word64)+import qualified Lightning.Protocol.BOLT1 as BOLT1 import Lightning.Protocol.BOLT4.Types+import qualified Lightning.Protocol.BOLT9 as BOLT9 --- BigSize encoding ---------------------------------------------------------+-- onion packets -------------------------------------------------------------- --- | Encode integer as BigSize.+-- | Encode an t'OnionPacket' (1366 bytes). ----- * 0-0xFC: 1 byte--- * 0xFD-0xFFFF: 0xFD ++ 2 bytes BE--- * 0x10000-0xFFFFFFFF: 0xFE ++ 4 bytes BE--- * larger: 0xFF ++ 8 bytes BE-encodeBigSize :: Word64 -> BS.ByteString-encodeBigSize !n-  | n < 0xFD = BS.singleton (fromIntegral n)-  | n <= 0xFFFF = toStrict $-      B.word8 0xFD <> B.word16BE (fromIntegral n)-  | n <= 0xFFFFFFFF = toStrict $-      B.word8 0xFE <> B.word32BE (fromIntegral n)-  | otherwise = toStrict $-      B.word8 0xFF <> B.word64BE n-{-# INLINE encodeBigSize #-}+--   >>> let Just pk = BOLT1.point (BS.cons 0x02 (BS.replicate 32 0x01))+--   >>> let Just hp = hop_payloads (BS.replicate 1300 0)+--   >>> let Just mac = hmac32 (BS.replicate 32 0)+--   >>> BS.length (encode_onion_packet (OnionPacket 0 pk hp mac))+--   1366+encode_onion_packet :: OnionPacket -> BS.ByteString+encode_onion_packet (OnionPacket v k hp mac) = BS.concat+  [BS.singleton v, BOLT1.un_point k, un_hop_payloads hp, un_hmac32 mac] --- | Decode BigSize, returning (value, remaining bytes).-decodeBigSize :: BS.ByteString -> Maybe (Word64, BS.ByteString)-decodeBigSize !bs = case BS.uncons bs of-  Nothing -> Nothing-  Just (b, rest)-    | b < 0xFD -> Just (fromIntegral b, rest)-    | b == 0xFD -> do-        (hi, r1) <- BS.uncons rest-        (lo, r2) <- BS.uncons r1-        let !val = fromIntegral hi `shiftL` 8 + fromIntegral lo-        -- Canonical: must be >= 0xFD-        if val < 0xFD then Nothing else Just (val, r2)-    | b == 0xFE -> do-        if BS.length rest < 4 then Nothing else do-          let !bytes = BS.take 4 rest-              !r = BS.drop 4 rest-              !val = word32BE bytes-          -- Canonical: must be > 0xFFFF-          if val <= 0xFFFF then Nothing else Just (fromIntegral val, r)-    | otherwise -> do  -- b == 0xFF-        if BS.length rest < 8 then Nothing else do-          let !bytes = BS.take 8 rest-              !r = BS.drop 8 rest-              !val = word64BE bytes-          -- Canonical: must be > 0xFFFFFFFF-          if val <= 0xFFFFFFFF then Nothing else Just (val, r)-{-# INLINE decodeBigSize #-}+-- | Decode an t'OnionPacket' from exactly 1366 bytes. The public key must+--   carry a compressed-encoding prefix; the version byte is not checked+--   (see 'Lightning.Protocol.BOLT4.process').+--+--   >>> decode_onion_packet (BS.replicate 1365 0)+--   Left InvalidLength+decode_onion_packet :: BS.ByteString -> Either DecodeError OnionPacket+decode_onion_packet bs = case BS.uncons bs of+  Just (v, rest) | BS.length bs == 1366 -> do+    let (k, r0)   = BS.splitAt 33 rest+        (hp, mac) = BS.splitAt 1300 r0+    pk <- maybe (Left InvalidPoint) Right (BOLT1.point k)+    pure (OnionPacket v pk (HopPayloads hp) (Hmac32 mac))+  _ -> Left InvalidLength --- | Get encoded size of a BigSize value without encoding.-bigSizeLen :: Word64 -> Int-bigSizeLen !n-  | n < 0xFD       = 1-  | n <= 0xFFFF    = 3-  | n <= 0xFFFFFFFF = 5-  | otherwise      = 9-{-# INLINE bigSizeLen #-}+-- TLV helpers ---------------------------------------------------------------- --- TLV encoding -------------------------------------------------------------+-- encode typed records with extra ones, none of which may have a known+-- type+encode_tlvs+  :: [Word64]+  -> [BOLT1.TlvRecord]+  -> BOLT1.TlvStream+  -> Either EncodeError BS.ByteString+encode_tlvs known typed extra+  | any ((`elem` known) . BOLT1.tlv_type) ex = Left ConflictingTlv+  | otherwise = case BOLT1.tlv_stream (typed ++ ex) of+      Nothing -> Left ConflictingTlv+      Just s  -> Right (BOLT1.encode_tlv_stream s)+  where+    ex = BOLT1.un_tlv_stream extra --- | Encode a TLV record.-encodeTlv :: TlvRecord -> BS.ByteString-encodeTlv (TlvRecord !typ !val) = toStrict $-  B.byteString (encodeBigSize typ) <>-  B.byteString (encodeBigSize (fromIntegral (BS.length val))) <>-  B.byteString val-{-# INLINE encodeTlv #-}+record :: Word64 -> (a -> BS.ByteString) -> Maybe a -> Maybe BOLT1.TlvRecord+record t f = fmap (BOLT1.TlvRecord t . f) --- | Decode a single TLV record.-decodeTlv :: BS.ByteString -> Maybe (TlvRecord, BS.ByteString)-decodeTlv !bs = do-  (typ, r1) <- decodeBigSize bs-  (len, r2) <- decodeBigSize r1-  let !len' = fromIntegral len-  if BS.length r2 < len'-    then Nothing-    else do-      let !val = BS.take len' r2-          !rest = BS.drop len' r2-      Just (TlvRecord typ val, rest)-{-# INLINE decodeTlv #-}+-- decode a TLV stream with the given known types+decode_tlvs :: [Word64] -> BS.ByteString -> Either DecodeError BOLT1.TlvStream+decode_tlvs known =+  first InvalidTlvStream . BOLT1.decode_tlv_stream (`elem` known) --- | Decode a TLV stream (sequence of records).--- Validates strictly increasing type order.-decodeTlvStream :: BS.ByteString -> Maybe [TlvRecord]-decodeTlvStream = go Nothing-  where-    go :: Maybe Word64 -> BS.ByteString -> Maybe [TlvRecord]-    go _ !bs | BS.null bs = Just []-    go !mPrev !bs = do-      (rec@(TlvRecord typ _), rest) <- decodeTlv bs-      -- Check strictly increasing order-      case mPrev of-        Just prev | typ <= prev -> Nothing-        _ -> do-          recs <- go (Just typ) rest-          Just (rec : recs)+-- parse the value of a record, if present+field+  :: Word64+  -> (BS.ByteString -> Maybe a)+  -> BOLT1.TlvStream+  -> Either DecodeError (Maybe a)+field t p s = case BOLT1.lookup_tlv t s of+  Nothing -> Right Nothing+  Just v  -> maybe (Left (InvalidTlvValue t)) (Right . Just) (p v) --- | Encode a TLV stream from records.--- Records must be sorted by type, no duplicates.-encodeTlvStream :: [TlvRecord] -> BS.ByteString-encodeTlvStream !recs = toStrict $ foldMap (B.byteString . encodeTlv) recs-{-# INLINE encodeTlvStream #-}+-- the unknown records of a stream+extra_tlvs :: [Word64] -> BOLT1.TlvStream -> BOLT1.TlvStream+extra_tlvs known = BOLT1.filter_tlv_stream (`notElem` known) --- Packet serialization -----------------------------------------------------+-- a decoder that must consume its whole input+whole+  :: (BS.ByteString -> Maybe (a, BS.ByteString))+  -> BS.ByteString+  -> Maybe a+whole d bs = case d bs of+  Just (a, r) | BS.null r -> Just a+  _ -> Nothing --- | Serialize OnionPacket to 1366 bytes.-encodeOnionPacket :: OnionPacket -> BS.ByteString-encodeOnionPacket (OnionPacket !ver !eph !payloads !mac) = toStrict $-  B.word8 ver <>-  B.byteString eph <>-  B.byteString payloads <>-  B.byteString mac-{-# INLINE encodeOnionPacket #-}+encode_tu_msat :: BOLT1.MilliSatoshi -> BS.ByteString+encode_tu_msat = BOLT1.encode_tu64 . BOLT1.un_milli_satoshi --- | Parse OnionPacket from 1366 bytes.-decodeOnionPacket :: BS.ByteString -> Maybe OnionPacket-decodeOnionPacket !bs-  | BS.length bs /= onionPacketSize = Nothing-  | otherwise =-      let !ver = BS.index bs 0-          !eph = BS.take pubkeySize (BS.drop 1 bs)-          !payloads = BS.take hopPayloadsSize (BS.drop (1 + pubkeySize) bs)-          !mac = BS.drop (1 + pubkeySize + hopPayloadsSize) bs-      in  Just (OnionPacket ver eph payloads mac)-{-# INLINE decodeOnionPacket #-}+decode_tu_msat :: BS.ByteString -> Maybe BOLT1.MilliSatoshi+decode_tu_msat v = BOLT1.decode_tu64 v >>= BOLT1.milli_satoshi --- | Encode HopPayload to bytes (without length prefix).-encodeHopPayload :: HopPayload -> BS.ByteString-encodeHopPayload !hp = encodeTlvStream (buildTlvs hp)-  where-    buildTlvs :: HopPayload -> [TlvRecord]-    buildTlvs (HopPayload amt cltv sci pd ed cpk unk) =-      let amt' = maybe [] (\a -> [TlvRecord 2 (encodeWord64TU a)]) amt-          cltv' = maybe [] (\c -> [TlvRecord 4 (encodeWord32TU c)]) cltv-          sci' = maybe [] (\s -> [TlvRecord 6 (encodeShortChannelId s)]) sci-          pd' = maybe [] (\p -> [TlvRecord 8 (encodePaymentData p)]) pd-          ed' = maybe [] (\e -> [TlvRecord 10 e]) ed-          cpk' = maybe [] (\k -> [TlvRecord 12 k]) cpk-      in  amt' ++ cltv' ++ sci' ++ pd' ++ ed' ++ cpk' ++ unk+-- hop payloads --------------------------------------------------------------- --- | Decode HopPayload from bytes.-decodeHopPayload :: BS.ByteString -> Maybe HopPayload-decodeHopPayload !bs = do-  tlvs <- decodeTlvStream bs-  parseHopPayload tlvs+hop_payload_types :: [Word64]+hop_payload_types = [2, 4, 6, 8, 10, 12, 16, 18] -parseHopPayload :: [TlvRecord] -> Maybe HopPayload-parseHopPayload = go emptyHop+-- | Encode a t'HopPayload' as a @payload@ TLV stream (without its length+--   prefix). Fails if 'hp_extra' has a record of a known type.+--+--   >>> let hp = empty_hop_payload { hp_outgoing_cltv_value = Just 144 }+--   >>> encode_hop_payload hp+--   Right "\EOT\SOH\144"+encode_hop_payload :: HopPayload -> Either EncodeError BS.ByteString+encode_hop_payload hp = encode_tlvs hop_payload_types typed (hp_extra hp)   where-    emptyHop :: HopPayload-    emptyHop = HopPayload Nothing Nothing Nothing Nothing Nothing Nothing []+    typed = catMaybes+      [ record 2 encode_tu_msat (hp_amt_to_forward hp)+      , record 4 BOLT1.encode_tu32 (hp_outgoing_cltv_value hp)+      , record 6 BOLT1.encode_short_channel_id (hp_short_channel_id hp)+      , record 8 encode_payment_data (hp_payment_data hp)+      , record 10 id (hp_encrypted_data hp)+      , record 12 BOLT1.un_point (hp_current_path_key hp)+      , record 16 id (hp_payment_metadata hp)+      , record 18 encode_tu_msat (hp_total_amount_msat hp)+      ] -    go :: HopPayload -> [TlvRecord] -> Maybe HopPayload-    go !hp [] = Just hp { hpUnknownTlvs = reverse (hpUnknownTlvs hp) }-    go !hp (TlvRecord typ val : rest) = case typ of-      2  -> do-        amt <- decodeWord64TU val-        go hp { hpAmtToForward = Just amt } rest-      4  -> do-        cltv <- decodeWord32TU val-        go hp { hpOutgoingCltv = Just cltv } rest-      6  -> do-        sci <- decodeShortChannelId val-        go hp { hpShortChannelId = Just sci } rest-      8  -> do-        pd <- decodePaymentData val-        go hp { hpPaymentData = Just pd } rest-      10 -> go hp { hpEncryptedData = Just val } rest-      12 -> go hp { hpCurrentPathKey = Just val } rest-      _  -> go hp { hpUnknownTlvs = TlvRecord typ val : hpUnknownTlvs hp } rest+-- | Decode a @payload@ TLV stream (without its length prefix). Fails on+--   a malformed stream, an unknown even type, or a malformed known+--   record.+--+--   >>> fmap hp_outgoing_cltv_value (decode_hop_payload "\EOT\SOH\144")+--   Right (Just 144)+--   >>> decode_hop_payload "\DC4\NUL"+--   Left (InvalidTlvStream (TlvUnknownEvenType 20))+decode_hop_payload :: BS.ByteString -> Either DecodeError HopPayload+decode_hop_payload bs = do+  s <- decode_tlvs hop_payload_types bs+  HopPayload+    <$> field 2 decode_tu_msat s+    <*> field 4 BOLT1.decode_tu32 s+    <*> field 6 (whole BOLT1.decode_short_channel_id) s+    <*> field 8 decode_payment_data s+    <*> field 10 Just s+    <*> field 12 BOLT1.point s+    <*> field 16 Just s+    <*> field 18 decode_tu_msat s+    <*> pure (extra_tlvs hop_payload_types s) --- ShortChannelId -----------------------------------------------------------+encode_payment_data :: PaymentData -> BS.ByteString+encode_payment_data (PaymentData s t) = un_payment_secret s <> encode_tu_msat t --- | Encode ShortChannelId to 8 bytes.--- Format: 3 bytes block || 3 bytes tx || 2 bytes output (all BE)-encodeShortChannelId :: ShortChannelId -> BS.ByteString-encodeShortChannelId (ShortChannelId !blk !tx !out) = toStrict $-  -- Block height: 3 bytes-  B.word8 (fromIntegral (blk `shiftR` 16) .&. 0xFF) <>-  B.word8 (fromIntegral (blk `shiftR` 8) .&. 0xFF) <>-  B.word8 (fromIntegral blk .&. 0xFF) <>-  -- Tx index: 3 bytes-  B.word8 (fromIntegral (tx `shiftR` 16) .&. 0xFF) <>-  B.word8 (fromIntegral (tx `shiftR` 8) .&. 0xFF) <>-  B.word8 (fromIntegral tx .&. 0xFF) <>-  -- Output index: 2 bytes-  B.word16BE out-{-# INLINE encodeShortChannelId #-}+decode_payment_data :: BS.ByteString -> Maybe PaymentData+decode_payment_data v = do+  let (s, t) = BS.splitAt 32 v+  PaymentData <$> payment_secret s <*> decode_tu_msat t --- | Decode ShortChannelId from 8 bytes.-decodeShortChannelId :: BS.ByteString -> Maybe ShortChannelId-decodeShortChannelId !bs-  | BS.length bs /= 8 = Nothing-  | otherwise =-      let !b0 = fromIntegral (BS.index bs 0) :: Word32-          !b1 = fromIntegral (BS.index bs 1) :: Word32-          !b2 = fromIntegral (BS.index bs 2) :: Word32-          !blk = (b0 `shiftL` 16) + (b1 `shiftL` 8) + b2-          !t0 = fromIntegral (BS.index bs 3) :: Word32-          !t1 = fromIntegral (BS.index bs 4) :: Word32-          !t2 = fromIntegral (BS.index bs 5) :: Word32-          !tx = (t0 `shiftL` 16) + (t1 `shiftL` 8) + t2-          !o0 = fromIntegral (BS.index bs 6) :: Word16-          !o1 = fromIntegral (BS.index bs 7) :: Word16-          !out = (o0 `shiftL` 8) + o1-      in  Just (ShortChannelId blk tx out)-{-# INLINE decodeShortChannelId #-}+-- blinded hop data ----------------------------------------------------------- --- Failure messages ---------------------------------------------------------+blinded_hop_data_types :: [Word64]+blinded_hop_data_types = [1, 2, 4, 6, 8, 10, 12, 14] --- | Encode failure message.-encodeFailureMessage :: FailureMessage -> BS.ByteString-encodeFailureMessage (FailureMessage (FailureCode !code) !dat !tlvs) =-  toStrict $-    B.word16BE code <>-    B.word16BE (fromIntegral (BS.length dat)) <>-    B.byteString dat <>-    B.byteString (encodeTlvStream tlvs)-{-# INLINE encodeFailureMessage #-}+-- | Encode t'BlindedHopData' as an @encrypted_data_tlv@ stream. Fails if+--   'bhd_extra' has a record of a known type.+encode_blinded_hop_data :: BlindedHopData -> Either EncodeError BS.ByteString+encode_blinded_hop_data d =+    encode_tlvs blinded_hop_data_types typed (bhd_extra d)+  where+    typed = catMaybes+      [ record 1 id (bhd_padding d)+      , record 2 BOLT1.encode_short_channel_id (bhd_short_channel_id d)+      , record 4 BOLT1.un_point (bhd_next_node_id d)+      , record 6 id (bhd_path_id d)+      , record 8 BOLT1.un_point (bhd_next_path_key_override d)+      , record 10 encode_payment_relay (bhd_payment_relay d)+      , record 12 encode_payment_constraints (bhd_payment_constraints d)+      , record 14 BOLT9.render (bhd_allowed_features d)+      ] --- | Decode failure message.-decodeFailureMessage :: BS.ByteString -> Maybe FailureMessage-decodeFailureMessage !bs = do-  if BS.length bs < 4 then Nothing else do-    let !code = word16BE (BS.take 2 bs)-        !dlen = fromIntegral (word16BE (BS.take 2 (BS.drop 2 bs)))-    if BS.length bs < 4 + dlen then Nothing else do-      let !dat = BS.take dlen (BS.drop 4 bs)-          !tlvBytes = BS.drop (4 + dlen) bs-      tlvs <- if BS.null tlvBytes-                then Just []-                else decodeTlvStream tlvBytes-      Just (FailureMessage (FailureCode code) dat tlvs)+-- | Decode an @encrypted_data_tlv@ stream. Fails on a malformed stream,+--   an unknown even type, or a malformed known record.+--+--   >>> fmap bhd_path_id (decode_blinded_hop_data "\ACK\apath id")+--   Right (Just "path id")+decode_blinded_hop_data :: BS.ByteString -> Either DecodeError BlindedHopData+decode_blinded_hop_data bs = do+  s <- decode_tlvs blinded_hop_data_types bs+  BlindedHopData+    <$> field 1 Just s+    <*> field 2 (whole BOLT1.decode_short_channel_id) s+    <*> field 4 BOLT1.point s+    <*> field 6 Just s+    <*> field 8 BOLT1.point s+    <*> field 10 decode_payment_relay s+    <*> field 12 decode_payment_constraints s+    <*> field 14 (Just . BOLT9.parse) s+    <*> pure (extra_tlvs blinded_hop_data_types s) --- Helper functions ---------------------------------------------------------+encode_payment_relay :: PaymentRelay -> BS.ByteString+encode_payment_relay (PaymentRelay c p b) =+  BOLT1.encode_u16 c <> BOLT1.encode_u32 p <> BOLT1.encode_tu32 b --- | Convert Builder to strict ByteString.-toStrict :: B.Builder -> BS.ByteString-toStrict = BL.toStrict . B.toLazyByteString-{-# INLINE toStrict #-}+decode_payment_relay :: BS.ByteString -> Maybe PaymentRelay+decode_payment_relay v = do+  (c, r0) <- BOLT1.decode_u16 v+  (p, r1) <- BOLT1.decode_u32 r0+  PaymentRelay c p <$> BOLT1.decode_tu32 r1 --- | Decode big-endian Word16.-word16BE :: BS.ByteString -> Word16-word16BE !bs =-  let !b0 = fromIntegral (BS.index bs 0) :: Word16-      !b1 = fromIntegral (BS.index bs 1) :: Word16-  in  (b0 `shiftL` 8) + b1-{-# INLINE word16BE #-}+encode_payment_constraints :: PaymentConstraints -> BS.ByteString+encode_payment_constraints (PaymentConstraints c m) =+  BOLT1.encode_u32 c <> encode_tu_msat m --- | Decode big-endian Word32.-word32BE :: BS.ByteString -> Word32-word32BE !bs =-  let !b0 = fromIntegral (BS.index bs 0) :: Word32-      !b1 = fromIntegral (BS.index bs 1) :: Word32-      !b2 = fromIntegral (BS.index bs 2) :: Word32-      !b3 = fromIntegral (BS.index bs 3) :: Word32-  in  (b0 `shiftL` 24) + (b1 `shiftL` 16) + (b2 `shiftL` 8) + b3-{-# INLINE word32BE #-}+decode_payment_constraints :: BS.ByteString -> Maybe PaymentConstraints+decode_payment_constraints v = do+  (c, r) <- BOLT1.decode_u32 v+  PaymentConstraints c <$> decode_tu_msat r --- | Decode big-endian Word64.-word64BE :: BS.ByteString -> Word64-word64BE !bs =-  let !b0 = fromIntegral (BS.index bs 0) :: Word64-      !b1 = fromIntegral (BS.index bs 1) :: Word64-      !b2 = fromIntegral (BS.index bs 2) :: Word64-      !b3 = fromIntegral (BS.index bs 3) :: Word64-      !b4 = fromIntegral (BS.index bs 4) :: Word64-      !b5 = fromIntegral (BS.index bs 5) :: Word64-      !b6 = fromIntegral (BS.index bs 6) :: Word64-      !b7 = fromIntegral (BS.index bs 7) :: Word64-  in  (b0 `shiftL` 56) + (b1 `shiftL` 48) + (b2 `shiftL` 40) +-      (b3 `shiftL` 32) + (b4 `shiftL` 24) + (b5 `shiftL` 16) +-      (b6 `shiftL` 8) + b7-{-# INLINE word64BE #-}+-- failure messages ----------------------------------------------------------- --- | Encode Word64 as truncated unsigned (minimal bytes).-encodeWord64TU :: Word64 -> BS.ByteString-encodeWord64TU !n-  | n == 0 = BS.empty-  | otherwise = BS.dropWhile (== 0) (toStrict (B.word64BE n))-{-# INLINE encodeWord64TU #-}+-- | Encode a t'FailureMessage': the failure code, its data and the TLV+--   stream. Fails if a @channel_update@ exceeds 65535 bytes.+--+--   >>> let tlvs = BOLT1.empty_tlv_stream+--   >>> encode_failure_message (FailureMessage TemporaryNodeFailure tlvs)+--   Right " \STX"+encode_failure_message :: FailureMessage -> Either EncodeError BS.ByteString+encode_failure_message (FailureMessage f tlvs) = do+  body <- encode_failure f+  pure (BOLT1.encode_u16 (failure_code f) <> body+          <> BOLT1.encode_tlv_stream tlvs) --- | Decode truncated unsigned to Word64.-decodeWord64TU :: BS.ByteString -> Maybe Word64-decodeWord64TU !bs-  | BS.null bs = Just 0-  | BS.length bs > 8 = Nothing-  | not (BS.null bs) && BS.index bs 0 == 0 = Nothing  -- Non-canonical-  | otherwise = Just (go 0 bs)+encode_failure :: Failure -> Either EncodeError BS.ByteString+encode_failure f = case f of+  TemporaryNodeFailure                 -> Right mempty+  PermanentNodeFailure                 -> Right mempty+  RequiredNodeFeatureMissing           -> Right mempty+  InvalidOnionVersion h                -> Right (un_onion_hash h)+  InvalidOnionHmac h                   -> Right (un_onion_hash h)+  InvalidOnionKey h                    -> Right (un_onion_hash h)+  TemporaryChannelFailure u            -> update u+  PermanentChannelFailure              -> Right mempty+  RequiredChannelFeatureMissing        -> Right mempty+  UnknownNextPeer                      -> Right mempty+  AmountBelowMinimum m u               -> (msat m <>) <$> update u+  FeeInsufficient m u                  -> (msat m <>) <$> update u+  IncorrectCltvExpiry c u              -> (BOLT1.encode_u32 c <>) <$> update u+  ExpiryTooSoon u                      -> update u+  IncorrectOrUnknownPaymentDetails m h -> Right (msat m <> BOLT1.encode_u32 h)+  FinalIncorrectCltvExpiry c           -> Right (BOLT1.encode_u32 c)+  FinalIncorrectHtlcAmount m           -> Right (msat m)+  ChannelDisabled d u                  -> (BOLT1.encode_u16 d <>) <$> update u+  ExpiryTooFar                         -> Right mempty+  InvalidOnionPayload Nothing          -> Right mempty+  InvalidOnionPayload (Just (t, o))    ->+    Right (BOLT1.encode_bigsize t <> BOLT1.encode_u16 o)+  MppTimeout                           -> Right mempty+  InvalidOnionBlinding h               -> Right (un_onion_hash h)+  UnknownFailure _ d                   -> Right d   where-    go :: Word64 -> BS.ByteString -> Word64-    go !acc !b = case BS.uncons b of-      Nothing -> acc-      Just (x, rest) -> go ((acc `shiftL` 8) + fromIntegral x) rest-{-# INLINE decodeWord64TU #-}+    msat = BOLT1.encode_milli_satoshi+    update = maybe (Left FieldTooLong) Right . BOLT1.encode_u16_prefixed --- | Encode Word32 as truncated unsigned.-encodeWord32TU :: Word32 -> BS.ByteString-encodeWord32TU !n-  | n == 0 = BS.empty-  | otherwise = BS.dropWhile (== 0) (toStrict (B.word32BE n))-{-# INLINE encodeWord32TU #-}+-- | Decode a failure message.+--+--   Per BOLT #4, bytes following a failure's data are ignored unless+--   they form a valid TLV stream (which, as no failure TLV types are+--   defined, may hold only odd types). The data of an unknown failure+--   code is every byte following it.+--+--   >>> fmap fm_failure (decode_failure_message " \STX")+--   Right TemporaryNodeFailure+--   >>> decode_failure_message "\DLE\r"+--   Left (InvalidFailureData 4109)+decode_failure_message :: BS.ByteString -> Either DecodeError FailureMessage+decode_failure_message bs = case BOLT1.decode_u16 bs of+  Nothing -> Left InvalidLength+  Just (c, rest) -> case decode_failure c rest of+    Nothing -> Left (InvalidFailureData c)+    Just (f, extra) ->+      let tlvs = either (const BOLT1.empty_tlv_stream) id+                   (BOLT1.decode_tlv_stream (const False) extra)+      in  Right (FailureMessage f tlvs) --- | Decode truncated unsigned to Word32.-decodeWord32TU :: BS.ByteString -> Maybe Word32-decodeWord32TU !bs-  | BS.null bs = Just 0-  | BS.length bs > 4 = Nothing-  | not (BS.null bs) && BS.index bs 0 == 0 = Nothing  -- Non-canonical-  | otherwise = Just (go 0 bs)+decode_failure :: Word16 -> BS.ByteString -> Maybe (Failure, BS.ByteString)+decode_failure c bs = case c of+  0x2002 -> plain TemporaryNodeFailure+  0x6002 -> plain PermanentNodeFailure+  0x6003 -> plain RequiredNodeFeatureMissing+  0xc004 -> hashed InvalidOnionVersion+  0xc005 -> hashed InvalidOnionHmac+  0xc006 -> hashed InvalidOnionKey+  0x1007 -> do+    (u, r) <- update bs+    pure (TemporaryChannelFailure u, r)+  0x4008 -> plain PermanentChannelFailure+  0x4009 -> plain RequiredChannelFeatureMissing+  0x400a -> plain UnknownNextPeer+  0x100b -> do+    (m, r0) <- BOLT1.decode_milli_satoshi bs+    (u, r1) <- update r0+    pure (AmountBelowMinimum m u, r1)+  0x100c -> do+    (m, r0) <- BOLT1.decode_milli_satoshi bs+    (u, r1) <- update r0+    pure (FeeInsufficient m u, r1)+  0x100d -> do+    (e, r0) <- BOLT1.decode_u32 bs+    (u, r1) <- update r0+    pure (IncorrectCltvExpiry e u, r1)+  0x100e -> do+    (u, r) <- update bs+    pure (ExpiryTooSoon u, r)+  0x400f -> do+    (m, r0) <- BOLT1.decode_milli_satoshi bs+    (h, r1) <- BOLT1.decode_u32 r0+    pure (IncorrectOrUnknownPaymentDetails m h, r1)+  0x0012 -> do+    (e, r) <- BOLT1.decode_u32 bs+    pure (FinalIncorrectCltvExpiry e, r)+  0x0013 -> do+    (m, r) <- BOLT1.decode_milli_satoshi bs+    pure (FinalIncorrectHtlcAmount m, r)+  0x1014 -> do+    (d, r0) <- BOLT1.decode_u16 bs+    (u, r1) <- update r0+    pure (ChannelDisabled d u, r1)+  0x0015 -> plain ExpiryTooFar+  0x4016 -> Just $ case BOLT1.decode_bigsize bs of+    Just (t, r0) | Just (o, r1) <- BOLT1.decode_u16 r0 ->+      (InvalidOnionPayload (Just (t, o)), r1)+    _ -> (InvalidOnionPayload Nothing, bs)+  0x0017 -> plain MppTimeout+  0xc018 -> hashed InvalidOnionBlinding+  _      -> Just (UnknownFailure c bs, BS.empty)   where-    go :: Word32 -> BS.ByteString -> Word32-    go !acc !b = case BS.uncons b of-      Nothing -> acc-      Just (x, rest) -> go ((acc `shiftL` 8) + fromIntegral x) rest-{-# INLINE decodeWord32TU #-}---- | Encode PaymentData.-encodePaymentData :: PaymentData -> BS.ByteString-encodePaymentData (PaymentData !secret !total) =-  secret <> encodeWord64TU total-{-# INLINE encodePaymentData #-}---- | Decode PaymentData.-decodePaymentData :: BS.ByteString -> Maybe PaymentData-decodePaymentData !bs-  | BS.length bs < 32 = Nothing-  | otherwise = do-      let !secret = BS.take 32 bs-          !rest = BS.drop 32 bs-      total <- decodeWord64TU rest-      Just (PaymentData secret total)-{-# INLINE decodePaymentData #-}+    plain f = Just (f, bs)+    hashed g+      | BS.length bs < 32 = Nothing+      | otherwise =+          let (h, r) = BS.splitAt 32 bs+          in  Just (g (OnionHash h), r)+    update = BOLT1.decode_u16_prefixed
lib/Lightning/Protocol/BOLT4/Construct.hs view
@@ -1,6 +1,6 @@-{-# OPTIONS_HADDOCK prune #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}  -- | -- Module: Lightning.Protocol.BOLT4.Construct@@ -8,205 +8,165 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Onion packet construction for BOLT4.+-- Onion packet construction.  module Lightning.Protocol.BOLT4.Construct (-    -- * Types     Hop(..)-  , Error(..)--    -- * Packet construction+  , ConstructError(..)   , construct   ) where -import Data.Bits (xor)+import Control.DeepSeq (NFData) import qualified Crypto.Curve.Secp256k1 as Secp256k1 import qualified Data.ByteString as BS-import Lightning.Protocol.BOLT4.Codec+import qualified Data.List as L+import GHC.Generics (Generic)+import qualified Lightning.Protocol.BOLT1 as BOLT1+import Lightning.Protocol.BOLT4.Codec (encode_hop_payload) import Lightning.Protocol.BOLT4.Prim import Lightning.Protocol.BOLT4.Types --- | Route information for a single hop.+-- | A hop of a route: the public key the hop's onion layer is encrypted+--   to (its node id, or its blinded node id in a blinded route), and its+--   payload. data Hop = Hop-  { hopPubKey  :: !Secp256k1.Projective  -- ^ node's public key-  , hopPayload :: !HopPayload            -- ^ routing data for this hop-  } deriving (Eq, Show)+  { hop_pubkey  :: !BOLT1.Point+  , hop_payload :: !HopPayload+  } deriving (Eq, Show, Generic) --- | Errors during packet construction.-data Error-  = InvalidSessionKey-  | EmptyRoute+instance NFData Hop++-- | Why an onion packet could not be constructed.+data ConstructError+  = EmptyRoute+    -- ^ the route has no hops   | TooManyHops-  | PayloadTooLarge !Int-  | InvalidHopPubKey !Int-  deriving (Eq, Show)+    -- ^ the route has more than 20 hops+  | InvalidHopPubKey {-# UNPACK #-} !Int+    -- ^ the public key of the hop with this index is not a valid point+  | InvalidHopPayload {-# UNPACK #-} !Int+    -- ^ the payload of the hop with this index can't be encoded, or+    --   encodes to fewer than 2 bytes+  | PayloadsTooLarge {-# UNPACK #-} !Int+    -- ^ the hops' payloads, with their length prefixes and HMACs, take+    --   this many bytes, more than the 1300 available+  deriving (Eq, Show, Generic) --- | Maximum number of hops in a route.-maxHops :: Int-maxHops = 20-{-# INLINE maxHops #-}+instance NFData ConstructError --- | Construct an onion packet for a payment route.+-- | Construct an onion packet for a route, given a session key (which+--   must be fresh and random for every onion), the route's hops from+--   the first to the final one, and the associated data (the payment+--   hash, for a payment). ----- Takes a session key (32 bytes random), list of hops, and associated--- data (typically payment_hash).+--   Returns the packet and the shared secret of each hop, in route+--   order; keep the latter to attribute returned errors with+--   'Lightning.Protocol.BOLT4.unwrap_error'. ----- Returns the onion packet and list of shared secrets (for error--- attribution).+--   >>> let Just session = secret_key (BS.replicate 32 0x41)+--   >>> let Just node = secret_key (BS.replicate 32 0x42)+--   >>> let Just amt = BOLT1.milli_satoshi 1000+--   >>> :{+--   let payload = empty_hop_payload {+--           hp_amt_to_forward      = Just amt+--         , hp_outgoing_cltv_value = Just 800000+--         }+--       route = [Hop (public_key node) payload]+--   :}+--   >>> let Right (pkt, sss) = construct session route (BS.replicate 32 0)+--   >>> BS.length (encode_onion_packet pkt)+--   1366+--   >>> length sss+--   1 construct-  :: BS.ByteString       -- ^ 32-byte session key (random)-  -> [Hop]               -- ^ route (first hop to final destination)-  -> BS.ByteString       -- ^ associated data-  -> Either Error (OnionPacket, [SharedSecret])-construct !sessionKey !hops !assocData-  | BS.length sessionKey /= 32 = Left InvalidSessionKey-  | null hops = Left EmptyRoute-  | length hops > maxHops = Left TooManyHops+  :: SecretKey+  -> [Hop]+  -> BS.ByteString+  -> Either ConstructError (OnionPacket, [SharedSecret])+construct sk hops ad+  | null hops        = Left EmptyRoute+  | length hops > 20 = Left TooManyHops   | otherwise = do-      -- Initialize ephemeral keypair from session key-      ephSec <- maybe (Left InvalidSessionKey) Right-                  (Secp256k1.roll32 sessionKey)-      ephPub <- maybe (Left InvalidSessionKey) Right-                  (Secp256k1.derive_pub ephSec)--      -- Compute shared secrets and blinding factors for all hops-      let hopPubKeys = map hopPubKey hops-      (secrets, _) <- computeAllSecrets sessionKey ephPub hopPubKeys--      -- Validate payload sizes-      let payloadBytes = map (encodeHopPayload . hopPayload) hops-          payloadSizes = map payloadShiftSize payloadBytes-          totalSize = sum payloadSizes-      if totalSize > hopPayloadsSize-        then Left (PayloadTooLarge totalSize)+      pubs <- traverse parse_pub ihops+      pls <- traverse encode_pl ihops+      let sizes = map shift_size pls+          total = sum sizes+      if total > 1300+        then Left (PayloadsTooLarge total)         else do-          -- Generate filler using secrets for all but final hop-          let numHops = length hops-              secretsExceptFinal = take (numHops - 1) secrets-              sizesExceptFinal = take (numHops - 1) payloadSizes-              filler = generateFiller secretsExceptFinal sizesExceptFinal--          -- Initialize hop_payloads with deterministic padding-          let DerivedKey padKey = derivePad (SharedSecret sessionKey)-              initialPayloads = generateStream (DerivedKey padKey)-                                  hopPayloadsSize--          -- Wrap payloads in reverse order (final hop first)-          let (finalPayloads, finalHmac) = wrapAllHops-                secrets payloadBytes filler assocData initialPayloads+          sss <- shared_secrets (sk_bytes sk) (sk_pub sk) (zip [0 ..] pubs)+          let streams = map (\ss -> keystream (derive_rho ss) 2600) sss+              n = length hops+              fill = filler (take (n - 1) streams) (take (n - 1) sizes)+              start = keystream (derive_pad sk) 1300+              (body, mac) = wrap ad fill start+                              (reverse (zip3 sss streams pls))+              pkt = OnionPacket 0 (public_key sk) (HopPayloads body)+                      (Hmac32 mac)+          pure (pkt, sss)+  where+    ihops = zip [0 ..] hops -          -- Build the final packet-          let ephPubBytes = Secp256k1.serialize_point ephPub-              packet = OnionPacket-                { opVersion = versionByte-                , opEphemeralKey = ephPubBytes-                , opHopPayloads = finalPayloads-                , opHmac = finalHmac-                }+    parse_pub (i, h) =+      maybe (Left (InvalidHopPubKey i)) Right (from_point (hop_pubkey h)) -          Right (packet, secrets)+    encode_pl (i, h) = case encode_hop_payload (hop_payload h) of+      Right p | BS.length p >= 2 -> Right p+      _ -> Left (InvalidHopPayload i) --- | Compute the total shift size for a payload.-payloadShiftSize :: BS.ByteString -> Int-payloadShiftSize !payload =-  let !len = BS.length payload-      !bsLen = bigSizeLen (fromIntegral len)-  in  bsLen + len + hmacSize-{-# INLINE payloadShiftSize #-}+-- bytes a payload takes in hop_payloads: length prefix, payload and HMAC+shift_size :: BS.ByteString -> Int+shift_size p =+  let !l = BS.length p+  in  BS.length (BOLT1.encode_bigsize (fromIntegral l)) + l + 32 --- | Compute shared secrets for all hops.-computeAllSecrets+-- the shared secret of each hop, blinding the ephemeral key pair after+-- each one+shared_secrets   :: BS.ByteString   -> Secp256k1.Projective-  -> [Secp256k1.Projective]-  -> Either Error ([SharedSecret], Secp256k1.Projective)-computeAllSecrets !initSec !initPub = go initSec initPub 0 []+  -> [(Int, Secp256k1.Projective)]+  -> Either ConstructError [SharedSecret]+shared_secrets _ _ [] = Right []+shared_secrets e epub ((i, pub) : rest) = do+  ss <- note (ecdh e pub)+  case rest of+    [] -> Right [ss]+    _  -> do+      let bf = blinding_factor epub ss+      e' <- note (blind_scalar e bf)+      epub' <- note (blind_pub epub bf)+      (ss :) <$> shared_secrets e' epub' rest   where-    go !_ephSec !ephPub !_ !acc [] = Right (reverse acc, ephPub)-    go !ephSec !ephPub !idx !acc (hopPub:rest) = do-      ss <- maybe (Left (InvalidHopPubKey idx)) Right-              (computeSharedSecret ephSec hopPub)-      let !bf = computeBlindingFactor ephPub ss-      newEphSec <- maybe (Left (InvalidHopPubKey idx)) Right-                     (blindSecKey ephSec bf)-      newEphPub <- maybe (Left (InvalidHopPubKey idx)) Right-                     (blindPubKey ephPub bf)-      go newEphSec newEphPub (idx + 1) (ss : acc) rest+    note = maybe (Left (InvalidHopPubKey i)) Right --- | Generate filler bytes.-generateFiller :: [SharedSecret] -> [Int] -> BS.ByteString-generateFiller !secrets !sizes = go BS.empty secrets sizes+-- the filler, from the 2600-byte rho streams and shift sizes of all+-- hops but the final one+filler :: [BS.ByteString] -> [Int] -> BS.ByteString+filler streams sizes = L.foldl' step BS.empty (zip streams sizes)   where-    go !filler [] [] = filler-    go !filler (ss:sss) (sz:szs) =-      let !extended = filler <> BS.replicate sz 0-          !rhoKey = deriveRho ss-          !stream = generateStream rhoKey (2 * hopPayloadsSize)-          !streamOffset = hopPayloadsSize-          !streamPart = BS.take (BS.length extended)-                          (BS.drop streamOffset stream)-          !newFiller = xorBytes extended streamPart-      in  go newFiller sss szs-    go !filler _ _ = filler-{-# INLINE generateFiller #-}+    step f (s, n) =+      let !ext = f <> BS.replicate n 0+          !off = 1300 - BS.length f+      in  xor_bytes ext (BS.take (BS.length ext) (BS.drop off s)) --- | Wrap all hops in reverse order.-wrapAllHops-  :: [SharedSecret]-  -> [BS.ByteString]-  -> BS.ByteString+-- wrap the hops' payloads, given (shared secret, rho stream, payload)+-- from the final hop to the first, returning hop_payloads and the HMAC+wrap+  :: BS.ByteString   -> BS.ByteString   -> BS.ByteString+  -> [(SharedSecret, BS.ByteString, BS.ByteString)]   -> (BS.ByteString, BS.ByteString)-wrapAllHops !secrets !payloads !filler !assocData !initPayloads =-  let !paired = reverse (zip secrets payloads)-      !numHops = length paired-      !initHmac = BS.replicate hmacSize 0-  in  go numHops initPayloads initHmac paired+wrap ad fill = go True (BS.replicate 32 0)   where-    go !_ !hopPayloads !hmac [] = (hopPayloads, hmac)-    go !remaining !hopPayloads !hmac ((ss, payload):rest) =-      let !isLastHop = remaining == length (reverse (zip secrets payloads))-          (!newPayloads, !newHmac) = wrapHop ss payload hmac hopPayloads-                                       assocData filler isLastHop-      in  go (remaining - 1) newPayloads newHmac rest---- | Wrap a single hop's payload.-wrapHop-  :: SharedSecret-  -> BS.ByteString-  -> BS.ByteString-  -> BS.ByteString-  -> BS.ByteString-  -> BS.ByteString-  -> Bool-  -> (BS.ByteString, BS.ByteString)-wrapHop !ss !payload !hmac !hopPayloads !assocData !filler !isFinalHop =-  let !payloadLen = BS.length payload-      !lenBytes = encodeBigSize (fromIntegral payloadLen)-      !shiftSize = BS.length lenBytes + payloadLen + hmacSize-      !shifted = BS.take (hopPayloadsSize - shiftSize) hopPayloads-      !prepended = lenBytes <> payload <> hmac <> shifted-      !rhoKey = deriveRho ss-      !stream = generateStream rhoKey hopPayloadsSize-      !obfuscated = xorBytes prepended stream-      !withFiller = if isFinalHop && not (BS.null filler)-                      then applyFiller obfuscated filler-                      else obfuscated-      !muKey = deriveMu ss-      !newHmac = computeHmac muKey withFiller assocData-  in  (withFiller, newHmac)-{-# INLINE wrapHop #-}---- | Apply filler to the tail of hop_payloads.-applyFiller :: BS.ByteString -> BS.ByteString -> BS.ByteString-applyFiller !hopPayloads !filler =-  let !fillerLen = BS.length filler-      !prefix = BS.take (hopPayloadsSize - fillerLen) hopPayloads-  in  prefix <> filler-{-# INLINE applyFiller #-}---- | XOR two ByteStrings.-xorBytes :: BS.ByteString -> BS.ByteString -> BS.ByteString-xorBytes !a !b = BS.pack $ BS.zipWith xor a b-{-# INLINE xorBytes #-}+    go _ !mac !buf [] = (buf, mac)+    go final !mac !buf ((ss, stream, pl) : rest) =+      let !len = BOLT1.encode_bigsize (fromIntegral (BS.length pl))+          !n = BS.length len + BS.length pl + 32+          !shifted = BS.concat [len, pl, mac, BS.take (1300 - n) buf]+          !obf = xor_bytes shifted stream+          !buf' | final     = BS.take (1300 - BS.length fill) obf <> fill+                | otherwise = obf+          !mac' = hmac (derive_mu ss) (buf' <> ad)+      in  go False mac' buf' rest
lib/Lightning/Protocol/BOLT4/Error.hs view
@@ -1,6 +1,5 @@-{-# OPTIONS_HADDOCK prune #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE DeriveGeneric #-}  -- | -- Module: Lightning.Protocol.BOLT4.Error@@ -8,216 +7,115 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Error packet construction and unwrapping for BOLT4 onion routing.------ Failing nodes construct error packets that are wrapped at each--- intermediate hop on the return path. The origin node unwraps--- layers to attribute the error to a specific hop.+-- Returning errors: construction, wrapping and attribution of return+-- packets.  module Lightning.Protocol.BOLT4.Error (-    -- * Types     ErrorPacket(..)-  , AttributionResult(..)-  , minErrorPacketSize--    -- * Error construction (failing node)-  , constructError--    -- * Error forwarding (intermediate node)-  , wrapError--    -- * Error unwrapping (origin node)-  , unwrapError+  , Attribution(..)+  , construct_error+  , wrap_error+  , unwrap_error   ) where -import Data.Bits (xor)+import Control.DeepSeq (NFData) import qualified Data.ByteString as BS-import qualified Data.ByteString.Builder as B-import qualified Data.ByteString.Lazy as BL-import qualified Crypto.Hash.SHA256 as SHA256-import Data.Word (Word8, Word16)-import Lightning.Protocol.BOLT4.Codec (encodeFailureMessage, decodeFailureMessage)+import GHC.Generics (Generic)+import qualified Lightning.Protocol.BOLT1 as BOLT1+import Lightning.Protocol.BOLT4.Codec import Lightning.Protocol.BOLT4.Prim-import Lightning.Protocol.BOLT4.Types (FailureMessage)+import Lightning.Protocol.BOLT4.Types --- | Wrapped error packet ready for return to origin.+-- | An obfuscated return packet (the @reason@ of @update_fail_htlc@). newtype ErrorPacket = ErrorPacket BS.ByteString-  deriving (Eq, Show)---- | Result of error attribution.-data AttributionResult-  = Attributed {-# UNPACK #-} !Int !FailureMessage-    -- ^ (hop index, failure)-  | UnknownOrigin !BS.ByteString-    -- ^ Could not attribute to any hop-  deriving (Eq, Show)---- | Minimum error packet size (256 bytes per spec).-minErrorPacketSize :: Int-minErrorPacketSize = 256-{-# INLINE minErrorPacketSize #-}---- Error construction ---------------------------------------------------------+  deriving (Eq, Show, Generic) --- | Construct an error packet at a failing node.------ Takes the shared secret (from processing) and failure message,--- and wraps it for return to origin.-constructError-  :: SharedSecret      -- ^ from packet processing-  -> FailureMessage    -- ^ failure details-  -> ErrorPacket-constructError !ss !failure =-  let !um = deriveUm ss-      !ammag = deriveAmmag ss-      !inner = buildErrorMessage um failure-      !obfuscated = obfuscateError ammag inner-  in  ErrorPacket obfuscated-{-# INLINE constructError #-}+instance NFData ErrorPacket --- | Wrap an existing error packet for forwarding back.------ Each intermediate node wraps the error with its own layer.-wrapError-  :: SharedSecret      -- ^ this node's shared secret-  -> ErrorPacket       -- ^ error from downstream-  -> ErrorPacket-wrapError !ss (ErrorPacket !packet) =-  let !ammag = deriveAmmag ss-      !wrapped = obfuscateError ammag packet-  in  ErrorPacket wrapped-{-# INLINE wrapError #-}+-- | The origin of a return packet, as found by 'unwrap_error'.+data Attribution+  = Attributed {-# UNPACK #-} !Int !FailureMessage+    -- ^ the hop with this index (from 0, in route order) returned the+    --   failure+  | MalformedFailure {-# UNPACK #-} !Int+    -- ^ the hop with this index returned the packet, but its failure+    --   message can't be decoded+  | UnknownOrigin+    -- ^ no hop's HMAC matches+  deriving (Eq, Show, Generic) --- Error unwrapping -----------------------------------------------------------+instance NFData Attribution --- | Attempt to attribute an error to a specific hop.------ Takes the shared secrets from original packet construction--- (in order from first hop to final) and the error packet.+-- | Construct a return packet at the failing node, given the shared+--   secret from processing the onion and the failure. ----- Tries each hop's keys until HMAC verifies, revealing origin.-unwrapError-  :: [SharedSecret]    -- ^ secrets from construction, in route order-  -> ErrorPacket       -- ^ received error-  -> AttributionResult-unwrapError secrets (ErrorPacket !initialPacket) = go 0 initialPacket secrets-  where-    go :: Int -> BS.ByteString -> [SharedSecret] -> AttributionResult-    go !_ !packet [] = UnknownOrigin packet-    go !idx !packet (ss:rest) =-      let !ammag = deriveAmmag ss-          !um = deriveUm ss-          !deobfuscated = deobfuscateError ammag packet-      in  if verifyErrorHmac um deobfuscated-            then case parseErrorMessage (BS.drop 32 deobfuscated) of-                   Just msg -> Attributed idx msg-                   Nothing  -> UnknownOrigin deobfuscated-            else go (idx + 1) deobfuscated rest---- Internal functions ------------------------------------------------------------- | Build the inner error message structure.+--   The failure message is padded so that its length plus that of the+--   padding is at least 256 bytes. Fails if the failure message can't+--   be encoded, or exceeds 65535 bytes. ----- Format: HMAC (32) || len (2) || message || pad_len (2) || padding--- Total must be >= 256 bytes.-buildErrorMessage-  :: DerivedKey        -- ^ um key-  -> FailureMessage    -- ^ failure to encode-  -> BS.ByteString     -- ^ complete message with HMAC-buildErrorMessage (DerivedKey !umKey) !failure =-  let !encoded = encodeFailureMessage failure-      !msgLen = BS.length encoded-      -- Total payload: len(2) + msg + pad_len(2) + padding = 256 - 32 = 224-      -- padding = 224 - 2 - msgLen - 2 = 220 - msgLen-      !padLen = max 0 (minErrorPacketSize - 32 - 2 - msgLen - 2)-      !padding = BS.replicate padLen 0-      -- Build: len || message || pad_len || padding-      !payload = toStrict $-        B.word16BE (fromIntegral msgLen) <>-        B.byteString encoded <>-        B.word16BE (fromIntegral padLen) <>-        B.byteString padding-      -- HMAC over the payload-      SHA256.MAC !hmac = SHA256.hmac umKey payload-  in  hmac <> payload-{-# INLINE buildErrorMessage #-}+--   >>> let Just ss = shared_secret (BS.replicate 32 0x01)+--   >>> let fm = FailureMessage TemporaryNodeFailure BOLT1.empty_tlv_stream+--   >>> let Right (ErrorPacket pkt) = construct_error ss fm+--   >>> BS.length pkt+--   292+construct_error+  :: SharedSecret+  -> FailureMessage+  -> Either EncodeError ErrorPacket+construct_error ss fm = do+  msg <- encode_failure_message fm+  let len = BS.length msg+  if len > 65535+    then Left FieldTooLong+    else do+      let pad  = max 0 (256 - len)+          body = BS.concat+            [ BOLT1.encode_u16 (fromIntegral len), msg+            , BOLT1.encode_u16 (fromIntegral pad), BS.replicate pad 0 ]+          mac  = hmac (derive_um ss) body+      pure (wrap_error ss (ErrorPacket (mac <> body))) --- | Obfuscate error packet with ammag stream.+-- | Wrap a return packet from downstream at an intermediate node, given+--   the shared secret from processing the onion. ----- XORs the entire packet with pseudo-random stream.-obfuscateError-  :: DerivedKey        -- ^ ammag key-  -> BS.ByteString     -- ^ error packet-  -> BS.ByteString     -- ^ obfuscated packet-obfuscateError !ammag !packet =-  let !stream = generateStream ammag (BS.length packet)-  in  xorBytes packet stream-{-# INLINE obfuscateError #-}+--   >>> let Just ss = shared_secret (BS.replicate 32 0x01)+--   >>> wrap_error ss (wrap_error ss (ErrorPacket "packet"))+--   ErrorPacket "packet"+wrap_error :: SharedSecret -> ErrorPacket -> ErrorPacket+wrap_error ss (ErrorPacket p) =+  ErrorPacket (xor_bytes p (keystream (derive_ammag ss) (BS.length p))) --- | Remove one layer of obfuscation from error packet.+-- | Find the origin of a return packet at the origin node, given the+--   shared secrets from 'Lightning.Protocol.BOLT4.construct', in route+--   order. ----- XOR is its own inverse, so same as obfuscation.-deobfuscateError-  :: DerivedKey        -- ^ ammag key-  -> BS.ByteString     -- ^ obfuscated packet-  -> BS.ByteString     -- ^ deobfuscated packet-deobfuscateError = obfuscateError-{-# INLINE deobfuscateError #-}---- | Verify error HMAC after deobfuscation.-verifyErrorHmac-  :: DerivedKey        -- ^ um key-  -> BS.ByteString     -- ^ deobfuscated packet (HMAC || rest)-  -> Bool-verifyErrorHmac (DerivedKey !umKey) !packet-  | BS.length packet < 32 = False-  | otherwise =-      let !receivedHmac = BS.take 32 packet-          !payload = BS.drop 32 packet-          SHA256.MAC !computedHmac = SHA256.hmac umKey payload-      in  constantTimeEq receivedHmac computedHmac-{-# INLINE verifyErrorHmac #-}---- | Parse error message from deobfuscated packet (after HMAC).-parseErrorMessage-  :: BS.ByteString     -- ^ packet after HMAC (len || msg || pad_len || pad)-  -> Maybe FailureMessage-parseErrorMessage !bs-  | BS.length bs < 4 = Nothing-  | otherwise =-      let !msgLen = fromIntegral (word16BE (BS.take 2 bs))-      in  if BS.length bs < 2 + msgLen-            then Nothing-            else decodeFailureMessage (BS.take msgLen (BS.drop 2 bs))-{-# INLINE parseErrorMessage #-}---- Helper functions --------------------------------------------------------------- | XOR two ByteStrings of equal length.-xorBytes :: BS.ByteString -> BS.ByteString -> BS.ByteString-xorBytes !a !b = BS.pack $ BS.zipWith xor a b-{-# INLINE xorBytes #-}---- | Constant-time equality comparison.-constantTimeEq :: BS.ByteString -> BS.ByteString -> Bool-constantTimeEq !a !b-  | BS.length a /= BS.length b = False-  | otherwise = go 0 (BS.zip a b)+--   >>> let Just ss0 = shared_secret (BS.replicate 32 0x01)+--   >>> let Just ss1 = shared_secret (BS.replicate 32 0x02)+--   >>> let fm = FailureMessage TemporaryNodeFailure BOLT1.empty_tlv_stream+--   >>> let Right pkt = construct_error ss1 fm+--   >>> unwrap_error [ss0, ss1] (wrap_error ss0 pkt) == Attributed 1 fm+--   True+--   >>> unwrap_error [ss0] (wrap_error ss0 pkt)+--   UnknownOrigin+unwrap_error :: [SharedSecret] -> ErrorPacket -> Attribution+unwrap_error = go 0   where-    go :: Word8 -> [(Word8, Word8)] -> Bool-    go !acc [] = acc == 0-    go !acc ((x, y):rest) = go (acc `xor` (x `xor` y)) rest-{-# INLINE constantTimeEq #-}---- | Decode big-endian Word16.-word16BE :: BS.ByteString -> Word16-word16BE !bs =-  let !b0 = fromIntegral (BS.index bs 0) :: Word16-      !b1 = fromIntegral (BS.index bs 1) :: Word16-  in  (b0 * 256) + b1-{-# INLINE word16BE #-}+    go _ [] _ = UnknownOrigin+    go i (ss : rest) p =+      let ErrorPacket q = wrap_error ss p+          (mac, body) = BS.splitAt 32 q+      in  if   ct_eq mac (hmac (derive_um ss) body)+          then either (const (MalformedFailure i)) (Attributed i)+                 (failure_message body)+          else go (i + 1) rest (ErrorPacket q) --- | Convert Builder to strict ByteString.-toStrict :: B.Builder -> BS.ByteString-toStrict = BL.toStrict . B.toLazyByteString-{-# INLINE toStrict #-}+-- The failure message of a return packet (after its HMAC).+failure_message :: BS.ByteString -> Either DecodeError FailureMessage+failure_message body = case BOLT1.decode_u16 body of+  Just (len, r0)+    | BS.length r0 >= fromIntegral len+    , let (msg, r1) = BS.splitAt (fromIntegral len) r0+    , Just (pad, r2) <- BOLT1.decode_u16 r1+    , BS.length r2 >= fromIntegral pad+    -> decode_failure_message msg+  _ -> Left InvalidLength
lib/Lightning/Protocol/BOLT4/Prim.hs view
@@ -1,4 +1,4 @@-{-# OPTIONS_HADDOCK prune #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} @@ -8,219 +8,251 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Low-level cryptographic primitives for BOLT4 onion routing.+-- Keys, secrets and the cryptographic primitives of BOLT #4.  module Lightning.Protocol.BOLT4.Prim (-    -- * Types-    SharedSecret(..)-  , DerivedKey(..)-  , BlindingFactor(..)--    -- * Key derivation-  , deriveRho-  , deriveMu-  , deriveUm-  , derivePad-  , deriveAmmag+  -- * Secret keys+    SecretKey+  , secret_key+  , public_key+  , sk_bytes+  , sk_pub -    -- * Shared secret computation-  , computeSharedSecret+  -- * Shared secrets+  , SharedSecret+  , shared_secret+  , un_shared_secret+  , ecdh -    -- * Blinding factor computation-  , computeBlindingFactor+  -- * Derived keys+  , DerivedKey+  , un_derived_key+  , derive_rho+  , derive_mu+  , derive_um+  , derive_ammag+  , derive_pad -    -- * Key blinding-  , blindPubKey-  , blindSecKey+  -- * Blinding+  , blinding_factor+  , blinded_node_tweak+  , blind_pub+  , blind_scalar -    -- * Stream generation-  , generateStream+  -- * Points+  , to_point+  , from_point -    -- * HMAC operations-  , computeHmac-  , verifyHmac+  -- * Streams and MACs+  , keystream+  , hmac+  , ct_eq+  , xor_bytes   ) where +import Control.DeepSeq (NFData(..)) import qualified Crypto.Cipher.ChaCha20 as ChaCha import qualified Crypto.Curve.Secp256k1 as Secp256k1 import qualified Crypto.Hash.SHA256 as SHA256 import Data.Bits (xor) import qualified Data.ByteString as BS-import qualified Data.List as L-import Data.Word (Word8, Word32)+import qualified Lightning.Protocol.BOLT1 as BOLT1 import qualified Numeric.Montgomery.Secp256k1.Scalar as S --- | 32-byte shared secret derived from ECDH.-newtype SharedSecret = SharedSecret BS.ByteString-  deriving (Eq, Show)+-- secret keys ---------------------------------------------------------------- --- | 32-byte derived key (rho, mu, um, pad, ammag).-newtype DerivedKey = DerivedKey BS.ByteString-  deriving (Eq, Show)+-- | A secp256k1 secret key: a session key, a node's private key, or the+--   seed of a blinded path.+--+--   This is secret material: its 'Show' instance is redacted and it+--   has no 'Eq' instance.+data SecretKey = SecretKey+  !BS.ByteString         -- the key+  !Secp256k1.Projective  -- its public key+  !BOLT1.Point           -- its serialized public key --- | 32-byte blinding factor for ephemeral key updates.-newtype BlindingFactor = BlindingFactor BS.ByteString-  deriving (Eq, Show)+instance Show SecretKey where+  showsPrec d _ = showParen (d > 10) $+    showString "SecretKey <redacted>" --- Key derivation ------------------------------------------------------------+instance NFData SecretKey where+  rnf (SecretKey k p q) = rnf k `seq` p `seq` rnf q --- | Derive rho key for obfuscation stream generation.+-- | Construct a t'SecretKey' from exactly 32 big-endian bytes encoding an+--   integer in [1, n - 1], where n is the secp256k1 group order. ----- @rho = HMAC-SHA256(key="rho", data=shared_secret)@-deriveRho :: SharedSecret -> DerivedKey-deriveRho = deriveKey "rho"-{-# INLINE deriveRho #-}+--   >>> secret_key (BS.replicate 32 0x41)+--   Just (SecretKey <redacted>)+--   >>> secret_key (BS.replicate 32 0x00)+--   Nothing+--   >>> secret_key (BS.replicate 31 0x41)+--   Nothing+secret_key :: BS.ByteString -> Maybe SecretKey+secret_key bs+  | BS.length bs /= 32 = Nothing+  | otherwise = do+      pub <- Secp256k1.derive_pub (Secp256k1.unsafe_roll32 bs)+      pt <- to_point pub+      pure $! SecretKey bs pub pt --- | Derive mu key for HMAC computation.+-- | The public key of a t'SecretKey'. ----- @mu = HMAC-SHA256(key="mu", data=shared_secret)@-deriveMu :: SharedSecret -> DerivedKey-deriveMu = deriveKey "mu"-{-# INLINE deriveMu #-}+--   >>> let Just sk = secret_key (BS.replicate 32 0x41)+--   >>> BS.length (BOLT1.un_point (public_key sk))+--   33+public_key :: SecretKey -> BOLT1.Point+public_key (SecretKey _ _ p) = p+{-# INLINE public_key #-} --- | Derive um key for return error HMAC.------ @um = HMAC-SHA256(key="um", data=shared_secret)@-deriveUm :: SharedSecret -> DerivedKey-deriveUm = deriveKey "um"-{-# INLINE deriveUm #-}+sk_bytes :: SecretKey -> BS.ByteString+sk_bytes (SecretKey k _ _) = k+{-# INLINE sk_bytes #-} --- | Derive pad key for filler generation.------ @pad = HMAC-SHA256(key="pad", data=shared_secret)@-derivePad :: SharedSecret -> DerivedKey-derivePad = deriveKey "pad"-{-# INLINE derivePad #-}+sk_pub :: SecretKey -> Secp256k1.Projective+sk_pub (SecretKey _ p _) = p+{-# INLINE sk_pub #-} --- | Derive ammag key for error obfuscation.+-- shared secrets -------------------------------------------------------------++-- | A 32-byte shared secret, established between the origin of an onion+--   and one of its hops. ----- @ammag = HMAC-SHA256(key="ammag", data=shared_secret)@-deriveAmmag :: SharedSecret -> DerivedKey-deriveAmmag = deriveKey "ammag"-{-# INLINE deriveAmmag #-}+--   This is secret material: its 'Show' instance is redacted and its+--   'Eq' instance runs in constant time.+newtype SharedSecret = SharedSecret BS.ByteString --- Internal helper for key derivation.-deriveKey :: BS.ByteString -> SharedSecret -> DerivedKey-deriveKey !keyType (SharedSecret !ss) =-  let SHA256.MAC !result = SHA256.hmac keyType ss-  in  DerivedKey result-{-# INLINE deriveKey #-}+instance Eq SharedSecret where+  SharedSecret a == SharedSecret b = ct_eq a b --- Shared secret computation -------------------------------------------------+instance Show SharedSecret where+  showsPrec d _ = showParen (d > 10) $+    showString "SharedSecret <redacted>" --- | Compute shared secret from ECDH.+instance NFData SharedSecret where+  rnf (SharedSecret bs) = rnf bs++-- | Construct a t'SharedSecret' from exactly 32 bytes (e.g. to restore one+--   that was stored with 'un_shared_secret'). ----- Takes a 32-byte secret key and a public key.--- Returns SHA256 of the compressed ECDH point (33 bytes).-computeSharedSecret-  :: BS.ByteString         -- ^ 32-byte secret key-  -> Secp256k1.Projective  -- ^ public key-  -> Maybe SharedSecret-computeSharedSecret !secBs !pub = do-  sec <- Secp256k1.roll32 secBs-  ecdhPoint <- Secp256k1.mul pub sec-  let !compressed = Secp256k1.serialize_point ecdhPoint-      !ss = SHA256.hash compressed-  pure $! SharedSecret ss-{-# INLINE computeSharedSecret #-}+--   >>> let Just ss = shared_secret (BS.replicate 32 0x01)+--   >>> BS.length (un_shared_secret ss)+--   32+--   >>> shared_secret "too short"+--   Nothing+shared_secret :: BS.ByteString -> Maybe SharedSecret+shared_secret bs+  | BS.length bs == 32 = Just (SharedSecret bs)+  | otherwise          = Nothing+{-# INLINE shared_secret #-} --- Blinding factor -----------------------------------------------------------+-- | The bytes of a t'SharedSecret'.+un_shared_secret :: SharedSecret -> BS.ByteString+un_shared_secret (SharedSecret bs) = bs+{-# INLINE un_shared_secret #-} --- | Compute blinding factor for ephemeral key updates.------ @blinding_factor = SHA256(ephemeral_pubkey || shared_secret)@-computeBlindingFactor-  :: Secp256k1.Projective  -- ^ ephemeral public key-  -> SharedSecret          -- ^ shared secret-  -> BlindingFactor-computeBlindingFactor !pub (SharedSecret !ss) =-  let !pubBytes = Secp256k1.serialize_point pub-      !combined = pubBytes <> ss-      !hashed = SHA256.hash combined-  in  BlindingFactor hashed-{-# INLINE computeBlindingFactor #-}+-- | ECDH per BOLT #4: SHA256 of the compressed product of a point and a+--   32-byte scalar. Fails if the scalar is not in [1, n - 1].+ecdh :: BS.ByteString -> Secp256k1.Projective -> Maybe SharedSecret+ecdh k pub = do+  pt <- Secp256k1.mul pub (Secp256k1.unsafe_roll32 k)+  pure $! SharedSecret (SHA256.hash (Secp256k1.serialize_point pt)) --- Key blinding --------------------------------------------------------------+-- derived keys --------------------------------------------------------------- --- | Blind a public key by multiplying with blinding factor.------ @new_pubkey = pubkey * blinding_factor@-blindPubKey-  :: Secp256k1.Projective-  -> BlindingFactor-  -> Maybe Secp256k1.Projective-blindPubKey !pub (BlindingFactor !bf) = do-  sk <- Secp256k1.roll32 bf-  Secp256k1.mul pub sk-{-# INLINE blindPubKey #-}+-- A 32-byte key derived from a shared secret.+newtype DerivedKey = DerivedKey BS.ByteString --- | Blind a secret key by multiplying with blinding factor (mod curve order).------ @new_seckey = seckey * blinding_factor (mod q)@------ Uses Montgomery multiplication from ppad-fixed for efficiency.--- Takes a 32-byte secret key and returns a 32-byte blinded secret key.-blindSecKey-  :: BS.ByteString     -- ^ 32-byte secret key-  -> BlindingFactor    -- ^ blinding factor-  -> Maybe BS.ByteString  -- ^ 32-byte blinded secret key-blindSecKey !secBs (BlindingFactor !bf)-  | BS.length secBs /= 32 = Nothing-  | BS.length bf /= 32 = Nothing-  | otherwise =-      let !secW = Secp256k1.unsafe_roll32 secBs-          !bfW = Secp256k1.unsafe_roll32 bf-          !secM = S.to secW-          !bfM = S.to bfW-          !resultM = S.mul secM bfM-          !resultW = S.retr resultM-      in  Just $! Secp256k1.unroll32 resultW-{-# INLINE blindSecKey #-}+un_derived_key :: DerivedKey -> BS.ByteString+un_derived_key (DerivedKey k) = k+{-# INLINE un_derived_key #-} --- Stream generation ---------------------------------------------------------+derive :: BS.ByteString -> BS.ByteString -> DerivedKey+derive label ss =+  let SHA256.MAC k = SHA256.hmac label ss+  in  DerivedKey k+{-# INLINE derive #-} --- | Generate pseudo-random byte stream using ChaCha20.------ Uses derived key as ChaCha20 key, 96-bit zero nonce, counter=0.--- Encrypts zeros to produce keystream.-generateStream-  :: DerivedKey     -- ^ rho or ammag key-  -> Int            -- ^ desired length-  -> BS.ByteString-generateStream (DerivedKey !key) !len =-  let !nonce = BS.replicate 12 0-      !zeros = BS.replicate len 0-  in  either (const (BS.replicate len 0)) id-        (ChaCha.cipher key (0 :: Word32) nonce zeros)-{-# INLINE generateStream #-}+derive_rho :: SharedSecret -> DerivedKey+derive_rho (SharedSecret ss) = derive "rho" ss --- HMAC operations -----------------------------------------------------------+derive_mu :: SharedSecret -> DerivedKey+derive_mu (SharedSecret ss) = derive "mu" ss --- | Compute HMAC-SHA256 for packet integrity.-computeHmac-  :: DerivedKey      -- ^ mu key-  -> BS.ByteString   -- ^ hop_payloads-  -> BS.ByteString   -- ^ associated_data-  -> BS.ByteString   -- ^ 32-byte HMAC-computeHmac (DerivedKey !key) !payloads !assocData =-  let SHA256.MAC !result = SHA256.hmac key (payloads <> assocData)-  in  result-{-# INLINE computeHmac #-}+derive_um :: SharedSecret -> DerivedKey+derive_um (SharedSecret ss) = derive "um" ss --- | Constant-time HMAC comparison.-verifyHmac-  :: BS.ByteString  -- ^ expected-  -> BS.ByteString  -- ^ computed-  -> Bool-verifyHmac !expected !computed-  | BS.length expected /= BS.length computed = False-  | otherwise = constantTimeEq expected computed-{-# INLINE verifyHmac #-}+derive_ammag :: SharedSecret -> DerivedKey+derive_ammag (SharedSecret ss) = derive "ammag" ss --- Constant-time equality comparison.-constantTimeEq :: BS.ByteString -> BS.ByteString -> Bool-constantTimeEq !a !b =-  let !diff = L.foldl' (\acc (x, y) -> acc `xor` (x `xor` y)) (0 :: Word8)-                       (BS.zip a b)-  in  diff == 0-{-# INLINE constantTimeEq #-}+-- The pad key is derived from the session key itself.+derive_pad :: SecretKey -> DerivedKey+derive_pad (SecretKey k _ _) = derive "pad" k++-- blinding -------------------------------------------------------------------++-- SHA256(E || ss), the factor by which ephemeral keys are blinded.+blinding_factor :: Secp256k1.Projective -> SharedSecret -> BS.ByteString+blinding_factor e (SharedSecret ss) =+  SHA256.hash (Secp256k1.serialize_point e <> ss)++-- HMAC256("blinded_node_id", ss), the factor by which node ids are+-- blinded in a blinded route.+blinded_node_tweak :: SharedSecret -> BS.ByteString+blinded_node_tweak (SharedSecret ss) =+  let SHA256.MAC t = SHA256.hmac "blinded_node_id" ss+  in  t++-- Multiply a point by a 32-byte factor.+blind_pub+  :: Secp256k1.Projective -> BS.ByteString -> Maybe Secp256k1.Projective+blind_pub p t = Secp256k1.mul p (Secp256k1.unsafe_roll32 t)++-- Multiply a 32-byte scalar by a 32-byte factor, mod n. Fails if the+-- product is zero.+blind_scalar :: BS.ByteString -> BS.ByteString -> Maybe BS.ByteString+blind_scalar k t =+  let !r = S.retr (S.mul (S.to (Secp256k1.unsafe_roll32 k))+                         (S.to (Secp256k1.unsafe_roll32 t)))+  in  if   Secp256k1.ge r+      then Just $! Secp256k1.unroll32 r+      else Nothing++-- points ---------------------------------------------------------------------++to_point :: Secp256k1.Projective -> Maybe BOLT1.Point+to_point = BOLT1.point . Secp256k1.serialize_point+{-# INLINE to_point #-}++from_point :: BOLT1.Point -> Maybe Secp256k1.Projective+from_point = Secp256k1.parse_point . BOLT1.un_point+{-# INLINE from_point #-}++-- streams and MACs -----------------------------------------------------------++-- The ChaCha20 keystream of the given length under a derived key, with+-- an all-zero nonce. The cipher fails only for a key or nonce of the+-- wrong length, or past 256 GiB of output, none of which can occur+-- here; the empty result in that case truncates any output XORed with+-- it, rather than leaving it unencrypted.+keystream :: DerivedKey -> Int -> BS.ByteString+keystream (DerivedKey k) n =+  case ChaCha.cipher k 0 (BS.replicate 12 0) (BS.replicate n 0) of+    Right s -> s+    Left _  -> BS.empty++-- HMAC-SHA256 under a derived key.+hmac :: DerivedKey -> BS.ByteString -> BS.ByteString+hmac (DerivedKey k) m =+  let SHA256.MAC h = SHA256.hmac k m+  in  h+{-# INLINE hmac #-}++-- Constant-time equality (variable-time only in the lengths).+ct_eq :: BS.ByteString -> BS.ByteString -> Bool+ct_eq a b = SHA256.MAC a == SHA256.MAC b+{-# INLINE ct_eq #-}++-- XOR, truncated to the shorter input.+xor_bytes :: BS.ByteString -> BS.ByteString -> BS.ByteString+xor_bytes = BS.packZipWith xor+{-# INLINE xor_bytes #-}
lib/Lightning/Protocol/BOLT4/Process.hs view
@@ -1,7 +1,5 @@-{-# OPTIONS_HADDOCK prune #-}-{-# LANGUAGE BangPatterns #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE OverloadedStrings #-}  -- | -- Module: Lightning.Protocol.BOLT4.Process@@ -9,212 +7,159 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Onion packet processing for BOLT4.+-- Onion packet processing.  module Lightning.Protocol.BOLT4.Process (-    -- * Processing-    process--    -- * Rejection reasons-  , RejectReason(..)+    ProcessResult(..)+  , ForwardInfo(..)+  , ReceiveInfo(..)+  , process   ) where -import Data.Bits (xor)+import Control.DeepSeq (NFData) import qualified Crypto.Curve.Secp256k1 as Secp256k1 import qualified Data.ByteString as BS-import Data.Word (Word8) import GHC.Generics (Generic)-import Lightning.Protocol.BOLT4.Codec+import qualified Lightning.Protocol.BOLT1 as BOLT1+import Lightning.Protocol.BOLT4.Blinding (unblind)+import Lightning.Protocol.BOLT4.Codec (decode_hop_payload) import Lightning.Protocol.BOLT4.Prim import Lightning.Protocol.BOLT4.Types --- | Reasons for rejecting a packet.-data RejectReason-  = InvalidVersion !Word8       -- ^ Version byte is not 0x00-  | InvalidEphemeralKey         -- ^ Malformed public key-  | HmacMismatch                -- ^ HMAC verification failed-  | InvalidPayload !String      -- ^ Malformed hop payload+-- | The result of processing an onion packet.+data ProcessResult+  = Forward !ForwardInfo+    -- ^ forward the onion to the next hop+  | Receive !ReceiveInfo+    -- ^ this node is the onion's final hop   deriving (Eq, Show, Generic) --- | Process an incoming onion packet.------ Takes the receiving node's private key, the incoming packet, and--- associated data (typically the payment hash).------ Returns either a rejection reason or the processing result--- (forward to next hop or receive at final destination).-process-  :: BS.ByteString    -- ^ 32-byte secret key of this node-  -> OnionPacket      -- ^ incoming onion packet-  -> BS.ByteString    -- ^ associated data (payment hash)-  -> Either RejectReason ProcessResult-process !secKey !packet !assocData = do-  -- Step 1: Validate version-  validateVersion packet--  -- Step 2: Parse ephemeral public key-  ephemeral <- parseEphemeralKey packet--  -- Step 3: Compute shared secret-  ss <- case computeSharedSecret secKey ephemeral of-    Nothing -> Left InvalidEphemeralKey-    Just s  -> Right s--  -- Step 4: Derive keys-  let !muKey = deriveMu ss-      !rhoKey = deriveRho ss--  -- Step 5: Verify HMAC-  if not (verifyPacketHmac muKey packet assocData)-    then Left HmacMismatch-    else pure ()--  -- Step 6: Decrypt hop payloads-  let !decrypted = decryptPayloads rhoKey (opHopPayloads packet)--  -- Step 7: Extract payload-  (payloadBytes, nextHmac, remaining) <- extractPayload decrypted--  -- Step 8: Parse payload TLV-  hopPayload <- case decodeHopPayload payloadBytes of-    Nothing -> Left (InvalidPayload "failed to decode TLV")-    Just hp -> Right hp--  -- Step 9: Check if final hop-  let SharedSecret ssBytes = ss-  if isFinalHop nextHmac-    then Right $! Receive $! ReceiveInfo-      { riPayload = hopPayload-      , riSharedSecret = ssBytes-      }-    else do-      -- Step 10: Prepare forward packet-      nextPacket <- case prepareForward ephemeral ss remaining nextHmac of-        Nothing -> Left InvalidEphemeralKey-        Just np -> Right np--      Right $! Forward $! ForwardInfo-        { fiNextPacket = nextPacket-        , fiPayload = hopPayload-        , fiSharedSecret = ssBytes-        }+instance NFData ProcessResult --- | Validate packet version is 0x00.-validateVersion :: OnionPacket -> Either RejectReason ()-validateVersion !packet-  | opVersion packet == versionByte = Right ()-  | otherwise = Left (InvalidVersion (opVersion packet))-{-# INLINE validateVersion #-}+-- | What a forwarding node learns from an onion.+data ForwardInfo = ForwardInfo+  { fwd_payload       :: !HopPayload+    -- ^ this node's payload+  , fwd_blinded       :: !(Maybe BlindedInfo)+    -- ^ the decrypted @encrypted_recipient_data@ and next path key, in+    --   a blinded route+  , fwd_next_packet   :: !OnionPacket+    -- ^ the onion for the next hop+  , fwd_shared_secret :: !SharedSecret+    -- ^ the shared secret, to wrap returned errors with+  } deriving (Eq, Show, Generic) --- | Parse and validate ephemeral public key from packet.-parseEphemeralKey :: OnionPacket -> Either RejectReason Secp256k1.Projective-parseEphemeralKey !packet =-  case Secp256k1.parse_point (opEphemeralKey packet) of-    Nothing  -> Left InvalidEphemeralKey-    Just pub -> Right pub-{-# INLINE parseEphemeralKey #-}+instance NFData ForwardInfo --- | Decrypt hop payloads by XORing with rho stream.------ Generates a stream of 2*1300 bytes and XORs with hop_payloads--- extended with 1300 zero bytes.-decryptPayloads-  :: DerivedKey      -- ^ rho key-  -> BS.ByteString   -- ^ hop_payloads (1300 bytes)-  -> BS.ByteString   -- ^ decrypted (2600 bytes, first 1300 useful)-decryptPayloads !rhoKey !payloads =-  let !streamLen = 2 * hopPayloadsSize  -- 2600 bytes-      !stream = generateStream rhoKey streamLen-      -- Extend payloads with zeros for the shift operation-      !extended = payloads <> BS.replicate hopPayloadsSize 0-  in  xorBytes stream extended-{-# INLINE decryptPayloads #-}+-- | What the final node learns from an onion.+data ReceiveInfo = ReceiveInfo+  { rcv_payload       :: !HopPayload+    -- ^ this node's payload+  , rcv_blinded       :: !(Maybe BlindedInfo)+    -- ^ the decrypted @encrypted_recipient_data@, in a blinded route+  , rcv_shared_secret :: !SharedSecret+    -- ^ the shared secret, to construct errors with+  } deriving (Eq, Show, Generic) --- | XOR two bytestrings of equal length.-xorBytes :: BS.ByteString -> BS.ByteString -> BS.ByteString-xorBytes !a !b = BS.pack (BS.zipWith xor a b)-{-# INLINE xorBytes #-}+instance NFData ReceiveInfo --- | Extract payload from decrypted buffer.+-- | Process an onion packet, given this node's private key, the packet,+--   its associated data (the payment hash, for a payment), and the+--   @path_key@ received with it (in @update_add_htlc@), if any. ----- Parses BigSize length prefix, extracts payload bytes and next HMAC.-extractPayload-  :: BS.ByteString-  -> Either RejectReason (BS.ByteString, BS.ByteString, BS.ByteString)-     -- ^ (payload_bytes, next_hmac, remaining_hop_payloads)-extractPayload !decrypted = do-  -- Parse length prefix-  (len, afterLen) <- case decodeBigSize decrypted of-    Nothing -> Left (InvalidPayload "invalid length prefix")-    Just (l, r) -> Right (fromIntegral l :: Int, r)--  -- Validate length-  if len > BS.length afterLen-    then Left (InvalidPayload "payload length exceeds buffer")-    else if len == 0-      then Left (InvalidPayload "zero-length payload")-      else pure ()--  -- Extract payload bytes-  let !payloadBytes = BS.take len afterLen-      !afterPayload = BS.drop len afterLen--  -- Extract next HMAC (32 bytes)-  if BS.length afterPayload < hmacSize-    then Left (InvalidPayload "insufficient bytes for HMAC")-    else do-      let !nextHmac = BS.take hmacSize afterPayload-          -- Remaining payloads: skip the HMAC, take first 1300 bytes-          -- This is already "shifted" by the payload extraction-          !remaining = BS.drop hmacSize afterPayload--      Right (payloadBytes, nextHmac, remaining)---- | Verify packet HMAC.+--   In a blinded route, the onion is encrypted to this node's blinded+--   node id: given a @path_key@, the matching private key is derived+--   from it. The @encrypted_recipient_data@ in the payload is decrypted+--   with the @path_key@, or, at the introduction node, with the+--   payload's @current_path_key@; a path key without+--   @encrypted_recipient_data@, or both kinds of path key, are+--   rejected. ----- Computes HMAC over (hop_payloads || associated_data) using mu key--- and compares with packet's HMAC using constant-time comparison.-verifyPacketHmac-  :: DerivedKey      -- ^ mu key-  -> OnionPacket     -- ^ packet with HMAC to verify-  -> BS.ByteString   -- ^ associated data-  -> Bool-verifyPacketHmac !muKey !packet !assocData =-  let !computed = computeHmac muKey (opHopPayloads packet) assocData-  in  verifyHmac (opHmac packet) computed-{-# INLINE verifyPacketHmac #-}---- | Prepare packet for forwarding to next hop.+--   The payload is otherwise returned as is. The caller must apply the+--   remaining reader requirements of BOLT #4: the presence of the+--   fields required for forwarding or receiving, the HTLC amount and+--   expiry checks (in a blinded route, against @payment_relay@ and+--   @payment_constraints@), @allowed_features@, the rule that+--   @encrypted_recipient_data@ hold only one of @short_channel_id@ and+--   @next_node_id@, and the rejection of replayed HMACs. ----- Computes blinded ephemeral key and constructs next OnionPacket.-prepareForward-  :: Secp256k1.Projective  -- ^ current ephemeral key-  -> SharedSecret          -- ^ shared secret (for blinding)-  -> BS.ByteString         -- ^ remaining hop_payloads (after shift)-  -> BS.ByteString         -- ^ next HMAC-  -> Maybe OnionPacket-prepareForward !ephemeral !ss !remaining !nextHmac = do-  -- Compute blinding factor and blind ephemeral key-  let !bf = computeBlindingFactor ephemeral ss-  newEphemeral <- blindPubKey ephemeral bf--  -- Serialize new ephemeral key-  let !newEphBytes = Secp256k1.serialize_point newEphemeral--  -- Truncate remaining to exactly 1300 bytes-  let !newPayloads = BS.take hopPayloadsSize remaining+--   >>> let Just session = secret_key (BS.replicate 32 0x41)+--   >>> let Just node = secret_key (BS.replicate 32 0x42)+--   >>> let pl = empty_hop_payload { hp_outgoing_cltv_value = Just 144 }+--   >>> let ad = BS.replicate 32 0+--   >>> let Right (pkt, _) = construct session [Hop (public_key node) pl] ad+--   >>> let Right (Receive info) = process node pkt ad Nothing+--   >>> hp_outgoing_cltv_value (rcv_payload info)+--   Just 144+process+  :: SecretKey+  -> OnionPacket+  -> BS.ByteString+  -> Maybe BOLT1.Point+  -> Either ProcessError ProcessResult+process sk pkt ad mpk = do+  let ver = onion_version pkt+  if ver /= 0 then Left (InvalidVersion ver) else Right ()+  eph <- note InvalidPublicKey (from_point (onion_public_key pkt))+  -- with a path_key, the onion is encrypted to the blinded node id+  blinding <- traverse path_secret mpk+  key <- case blinding of+    Nothing -> Right (sk_bytes sk)+    Just (_, bss) ->+      note InvalidPathKey (blind_scalar (sk_bytes sk) (blinded_node_tweak bss))+  ss <- note InvalidPublicKey (ecdh key eph)+  let payloads = un_hop_payloads (onion_hop_payloads pkt)+      mac = hmac (derive_mu ss) (payloads <> ad)+  if   ct_eq mac (un_hmac32 (onion_hmac pkt))+  then Right ()+  else Left HmacMismatch+  let plain = xor_bytes (payloads <> BS.replicate 1300 0)+                        (keystream (derive_rho ss) 2600)+  (pl, next_mac, rest) <- split_payload plain+  hp <- either (Left . InvalidPayload) Right (decode_hop_payload pl)+  binfo <- case (hp_encrypted_data hp, blinding, hp_current_path_key hp) of+    (Nothing, Nothing, Nothing) -> Right Nothing+    (Nothing, _, _)             -> Left UnexpectedPathKey+    (Just _, Just _, Just _)    -> Left UnexpectedPathKey+    (Just _, Nothing, Nothing)  -> Left MissingPathKey+    (Just enc, Just (e, bss), Nothing) -> Just <$> unblind bss e enc+    (Just enc, Nothing, Just cpk) -> do+      (e, bss) <- path_secret cpk+      Just <$> unblind bss e enc+  if BS.all (== 0) next_mac+    then Right (Receive (ReceiveInfo hp binfo ss))+    else do+      eph' <- note InvalidPublicKey+                (blind_pub eph (blinding_factor eph ss) >>= to_point)+      let next = OnionPacket 0 eph' (HopPayloads rest) (Hmac32 next_mac)+      Right (Forward (ForwardInfo hp binfo next ss))+  where+    note :: ProcessError -> Maybe a -> Either ProcessError a+    note e = maybe (Left e) Right -  -- Construct next packet-  pure $! OnionPacket-    { opVersion = versionByte-    , opEphemeralKey = newEphBytes-    , opHopPayloads = newPayloads-    , opHmac = nextHmac-    }+    -- a path key, and its shared secret with this node+    path_secret+      :: BOLT1.Point+      -> Either ProcessError (Secp256k1.Projective, SharedSecret)+    path_secret pk = do+      e <- note InvalidPathKey (from_point pk)+      bss <- note InvalidPathKey (ecdh (sk_bytes sk) e)+      Right (e, bss) --- | Check if this is the final hop.------ Final hop is indicated by next_hmac being all zeros.-isFinalHop :: BS.ByteString -> Bool-isFinalHop !hmac = hmac == BS.replicate hmacSize 0-{-# INLINE isFinalHop #-}+-- Split the decrypted 2600-byte buffer into the payload, the next HMAC+-- and the next hop_payloads. The length prefix, payload and HMAC must+-- fit in the 1300-byte hop_payloads.+split_payload+  :: BS.ByteString+  -> Either ProcessError (BS.ByteString, BS.ByteString, BS.ByteString)+split_payload plain = case BOLT1.decode_bigsize plain of+  Nothing -> Left InvalidPayloadLength+  Just (len, r0)+    | len < 2   -> Left InvalidPayloadLength+    | len > fromIntegral (1300 - prefix - 32) -> Left InvalidPayloadLength+    | otherwise ->+        let (pl, r1)  = BS.splitAt (fromIntegral len) r0+            (mac, r2) = BS.splitAt 32 r1+        in  Right (pl, mac, BS.take 1300 r2)+    where+      prefix = BS.length plain - BS.length r0
lib/Lightning/Protocol/BOLT4/Types.hs view
@@ -1,7 +1,5 @@-{-# OPTIONS_HADDOCK prune #-}-{-# LANGUAGE BangPatterns #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE PatternSynonyms #-}  -- | -- Module: Lightning.Protocol.BOLT4.Types@@ -9,271 +7,501 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Core data types for BOLT4 onion routing.+-- Data types for BOLT #4.  module Lightning.Protocol.BOLT4.Types (-    -- * Packet types+  -- * Onion packets     OnionPacket(..)+  , HopPayloads(..)+  , hop_payloads+  , un_hop_payloads+  , Hmac32(..)+  , hmac32+  , un_hmac32++  -- * Hop payloads   , HopPayload(..)-  , ShortChannelId(..)+  , empty_hop_payload   , PaymentData(..)-  , TlvRecord(..)+  , PaymentSecret(..)+  , payment_secret+  , un_payment_secret -    -- * Error types-  , FailureMessage(..)-  , FailureCode(..)-    -- ** Flag bits-  , pattern BADONION-  , pattern PERM-  , pattern NODE-  , pattern UPDATE-    -- ** Common failure codes-  , pattern InvalidRealm-  , pattern TemporaryNodeFailure-  , pattern PermanentNodeFailure-  , pattern RequiredNodeFeatureMissing-  , pattern InvalidOnionVersion-  , pattern InvalidOnionHmac-  , pattern InvalidOnionKey-  , pattern TemporaryChannelFailure-  , pattern PermanentChannelFailure-  , pattern AmountBelowMinimum-  , pattern FeeInsufficient-  , pattern IncorrectCltvExpiry-  , pattern ExpiryTooSoon-  , pattern IncorrectOrUnknownPaymentDetails-  , pattern FinalIncorrectCltvExpiry-  , pattern FinalIncorrectHtlcAmount-  , pattern ChannelDisabled-  , pattern ExpiryTooFar-  , pattern InvalidOnionPayload-  , pattern MppTimeout+  -- * Route blinding+  , BlindedPath(..)+  , BlindedHop(..)+  , BlindedHopData(..)+  , empty_blinded_hop_data+  , PaymentRelay(..)+  , PaymentConstraints(..)+  , BlindedInfo(..) -    -- * Processing results-  , ProcessResult(..)-  , ForwardInfo(..)-  , ReceiveInfo(..)+  -- * Failure messages+  , FailureMessage(..)+  , Failure(..)+  , OnionHash(..)+  , onion_hash+  , un_onion_hash+  , failure_code+  , is_badonion+  , is_perm+  , is_node+  , is_update -    -- * Constants-  , onionPacketSize-  , hopPayloadsSize-  , hmacSize-  , pubkeySize-  , versionByte-  , maxPayloadSize+  -- * Errors+  , DecodeError(..)+  , EncodeError(..)+  , ProcessError(..)   ) where -import Data.Bits ((.&.), (.|.))+import Control.DeepSeq (NFData(..))+import Data.Bits ((.&.)) import qualified Data.ByteString as BS import Data.Word (Word8, Word16, Word32, Word64) import GHC.Generics (Generic)+import qualified Lightning.Protocol.BOLT1 as BOLT1+import qualified Lightning.Protocol.BOLT4.Prim as P+import qualified Lightning.Protocol.BOLT9 as BOLT9 --- Packet types -------------------------------------------------------------+-- onion packets -------------------------------------------------------------- --- | Complete onion packet (1366 bytes).+-- | An onion packet (@onion_packet@): 1366 bytes on the wire. data OnionPacket = OnionPacket-  { opVersion      :: {-# UNPACK #-} !Word8-  , opEphemeralKey :: !BS.ByteString  -- ^ 33 bytes, compressed pubkey-  , opHopPayloads  :: !BS.ByteString  -- ^ 1300 bytes-  , opHmac         :: !BS.ByteString  -- ^ 32 bytes+  { onion_version      :: {-# UNPACK #-} !Word8+    -- ^ the version byte; 0 in this version of the protocol+  , onion_public_key   :: !BOLT1.Point+    -- ^ the ephemeral public key+  , onion_hop_payloads :: !HopPayloads+    -- ^ the obfuscated hop payloads+  , onion_hmac         :: !Hmac32+    -- ^ the HMAC authenticating the packet   } deriving (Eq, Show, Generic) --- | Parsed hop payload after decryption.+instance NFData OnionPacket++-- | The 1300-byte @hop_payloads@ field of an onion packet.+newtype HopPayloads = HopPayloads BS.ByteString+  deriving (Eq, Show, Generic)++instance NFData HopPayloads++-- | Construct t'HopPayloads' from exactly 1300 bytes.+--+--   >>> let Just hp = hop_payloads (BS.replicate 1300 0)+--   >>> BS.length (un_hop_payloads hp)+--   1300+--   >>> hop_payloads (BS.replicate 1299 0)+--   Nothing+hop_payloads :: BS.ByteString -> Maybe HopPayloads+hop_payloads bs+  | BS.length bs == 1300 = Just (HopPayloads bs)+  | otherwise            = Nothing+{-# INLINE hop_payloads #-}++-- | The bytes of t'HopPayloads'.+un_hop_payloads :: HopPayloads -> BS.ByteString+un_hop_payloads (HopPayloads bs) = bs+{-# INLINE un_hop_payloads #-}++-- | A 32-byte HMAC.+newtype Hmac32 = Hmac32 BS.ByteString+  deriving (Eq, Show, Generic)++instance NFData Hmac32++-- | Construct an t'Hmac32' from exactly 32 bytes.+--+--   >>> fmap (BS.length . un_hmac32) (hmac32 (BS.replicate 32 0))+--   Just 32+--   >>> hmac32 "too short"+--   Nothing+hmac32 :: BS.ByteString -> Maybe Hmac32+hmac32 bs+  | BS.length bs == 32 = Just (Hmac32 bs)+  | otherwise          = Nothing+{-# INLINE hmac32 #-}++-- | The bytes of an t'Hmac32'.+un_hmac32 :: Hmac32 -> BS.ByteString+un_hmac32 (Hmac32 bs) = bs+{-# INLINE un_hmac32 #-}++-- hop payloads ---------------------------------------------------------------++-- | A hop's @payload@ TLV stream.+--+--   Each known record has a typed field. 'hp_extra' holds the remaining+--   records: decoding fills it with the unknown odd records (unknown even+--   records are rejected), and encoding accepts any record in it whose+--   type isn't one of the known ones. data HopPayload = HopPayload-  { hpAmtToForward   :: !(Maybe Word64)         -- ^ TLV type 2-  , hpOutgoingCltv   :: !(Maybe Word32)         -- ^ TLV type 4-  , hpShortChannelId :: !(Maybe ShortChannelId) -- ^ TLV type 6-  , hpPaymentData    :: !(Maybe PaymentData)    -- ^ TLV type 8-  , hpEncryptedData  :: !(Maybe BS.ByteString)  -- ^ TLV type 10-  , hpCurrentPathKey :: !(Maybe BS.ByteString)  -- ^ TLV type 12-  , hpUnknownTlvs    :: ![TlvRecord]            -- ^ Unknown types+  { hp_amt_to_forward      :: !(Maybe BOLT1.MilliSatoshi)+    -- ^ type 2: @amt_to_forward@+  , hp_outgoing_cltv_value :: !(Maybe Word32)+    -- ^ type 4: @outgoing_cltv_value@+  , hp_short_channel_id    :: !(Maybe BOLT1.ShortChannelId)+    -- ^ type 6: @short_channel_id@+  , hp_payment_data        :: !(Maybe PaymentData)+    -- ^ type 8: @payment_data@+  , hp_encrypted_data      :: !(Maybe BS.ByteString)+    -- ^ type 10: @encrypted_recipient_data@+  , hp_current_path_key    :: !(Maybe BOLT1.Point)+    -- ^ type 12: @current_path_key@+  , hp_payment_metadata    :: !(Maybe BS.ByteString)+    -- ^ type 16: @payment_metadata@+  , hp_total_amount_msat   :: !(Maybe BOLT1.MilliSatoshi)+    -- ^ type 18: @total_amount_msat@+  , hp_extra               :: !BOLT1.TlvStream+    -- ^ other records   } deriving (Eq, Show, Generic) --- | Short channel ID (8 bytes): block height, tx index, output index.-data ShortChannelId = ShortChannelId-  { sciBlockHeight :: {-# UNPACK #-} !Word32  -- ^ 3 bytes in encoding-  , sciTxIndex     :: {-# UNPACK #-} !Word32  -- ^ 3 bytes in encoding-  , sciOutputIndex :: {-# UNPACK #-} !Word16  -- ^ 2 bytes in encoding-  } deriving (Eq, Show, Generic)+instance NFData HopPayload --- | Payment data for final hop (TLV type 8).+-- | The empty t'HopPayload', to be updated with record syntax.+--+--   >>> let Just amt = BOLT1.milli_satoshi 1000+--   >>> let hp = empty_hop_payload { hp_amt_to_forward = Just amt }+--   >>> encode_hop_payload hp+--   Right "\STX\STX\ETX\232"+empty_hop_payload :: HopPayload+empty_hop_payload = HopPayload+  Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing+  BOLT1.empty_tlv_stream++-- | The @payment_data@ record of a final hop's payload. data PaymentData = PaymentData-  { pdPaymentSecret :: !BS.ByteString         -- ^ 32 bytes-  , pdTotalMsat     :: {-# UNPACK #-} !Word64+  { pd_payment_secret :: !PaymentSecret+  , pd_total_msat     :: !BOLT1.MilliSatoshi   } deriving (Eq, Show, Generic) --- | Generic TLV record for unknown/extension types.-data TlvRecord = TlvRecord-  { tlvType  :: {-# UNPACK #-} !Word64-  , tlvValue :: !BS.ByteString-  } deriving (Eq, Show, Generic)+instance NFData PaymentData --- Error types --------------------------------------------------------------+-- | A 32-byte payment secret.+--+--   Its 'Show' instance is redacted and its 'Eq' instance runs in+--   constant time.+newtype PaymentSecret = PaymentSecret BS.ByteString --- | Failure message from intermediate or final node.-data FailureMessage = FailureMessage-  { fmCode :: {-# UNPACK #-} !FailureCode-  , fmData :: !BS.ByteString-  , fmTlvs :: ![TlvRecord]-  } deriving (Eq, Show, Generic)+instance Eq PaymentSecret where+  PaymentSecret a == PaymentSecret b = P.ct_eq a b --- | 2-byte failure code with flag bits.-newtype FailureCode = FailureCode Word16-  deriving (Eq, Show)+instance Show PaymentSecret where+  showsPrec d _ = showParen (d > 10) $+    showString "PaymentSecret <redacted>" --- Flag bits+instance NFData PaymentSecret where+  rnf (PaymentSecret bs) = rnf bs --- | BADONION flag (0x8000): error was in parsing the onion.-pattern BADONION :: Word16-pattern BADONION = 0x8000+-- | Construct a t'PaymentSecret' from exactly 32 bytes.+--+--   >>> payment_secret (BS.replicate 32 0x01)+--   Just (PaymentSecret <redacted>)+--   >>> payment_secret (BS.replicate 16 0x01)+--   Nothing+payment_secret :: BS.ByteString -> Maybe PaymentSecret+payment_secret bs+  | BS.length bs == 32 = Just (PaymentSecret bs)+  | otherwise          = Nothing+{-# INLINE payment_secret #-} --- | PERM flag (0x4000): permanent failure, do not retry.-pattern PERM :: Word16-pattern PERM = 0x4000+-- | The bytes of a t'PaymentSecret'.+un_payment_secret :: PaymentSecret -> BS.ByteString+un_payment_secret (PaymentSecret bs) = bs+{-# INLINE un_payment_secret #-} --- | NODE flag (0x2000): node failure rather than channel.-pattern NODE :: Word16-pattern NODE = 0x2000+-- route blinding ------------------------------------------------------------- --- | UPDATE flag (0x1000): channel update is attached.-pattern UPDATE :: Word16-pattern UPDATE = 0x1000+-- | A blinded route (@blinded_path@), as created by a recipient.+data BlindedPath = BlindedPath+  { bp_first_node_id  :: !BOLT1.Point+    -- ^ the (unblinded) introduction node+  , bp_first_path_key :: !BOLT1.Point+    -- ^ the path key for the introduction node+  , bp_hops           :: ![BlindedHop]+    -- ^ the blinded hops, starting with the introduction node+  } deriving (Eq, Show, Generic) --- Common failure codes+instance NFData BlindedPath --- | Invalid realm byte in onion.-pattern InvalidRealm :: FailureCode-pattern InvalidRealm = FailureCode 0x4001  -- PERM .|. 1+-- | A hop of a blinded route (@blinded_path_hop@).+data BlindedHop = BlindedHop+  { bh_blinded_node_id :: !BOLT1.Point+    -- ^ the hop's blinded node id+  , bh_encrypted_data  :: !BS.ByteString+    -- ^ the hop's @encrypted_recipient_data@+  } deriving (Eq, Show, Generic) --- | Temporary node failure.-pattern TemporaryNodeFailure :: FailureCode-pattern TemporaryNodeFailure = FailureCode 0x2002  -- NODE .|. 2+instance NFData BlindedHop --- | Permanent node failure.-pattern PermanentNodeFailure :: FailureCode-pattern PermanentNodeFailure = FailureCode 0x6002  -- PERM .|. NODE .|. 2+-- | The @encrypted_data_tlv@ stream carried, encrypted, in a hop's+--   @encrypted_recipient_data@.+--+--   'bhd_extra' plays the same role as 'hp_extra' in t'HopPayload'.+data BlindedHopData = BlindedHopData+  { bhd_padding                :: !(Maybe BS.ByteString)+    -- ^ type 1: @padding@+  , bhd_short_channel_id       :: !(Maybe BOLT1.ShortChannelId)+    -- ^ type 2: @short_channel_id@+  , bhd_next_node_id           :: !(Maybe BOLT1.Point)+    -- ^ type 4: @next_node_id@+  , bhd_path_id                :: !(Maybe BS.ByteString)+    -- ^ type 6: @path_id@+  , bhd_next_path_key_override :: !(Maybe BOLT1.Point)+    -- ^ type 8: @next_path_key_override@+  , bhd_payment_relay          :: !(Maybe PaymentRelay)+    -- ^ type 10: @payment_relay@+  , bhd_payment_constraints    :: !(Maybe PaymentConstraints)+    -- ^ type 12: @payment_constraints@+  , bhd_allowed_features       :: !(Maybe BOLT9.FeatureVector)+    -- ^ type 14: @allowed_features@+  , bhd_extra                  :: !BOLT1.TlvStream+    -- ^ other records+  } deriving (Eq, Show, Generic) --- | Required node feature missing.-pattern RequiredNodeFeatureMissing :: FailureCode-pattern RequiredNodeFeatureMissing = FailureCode 0x6003  -- PERM .|. NODE .|. 3+instance NFData BlindedHopData --- | Invalid onion version.-pattern InvalidOnionVersion :: FailureCode-pattern InvalidOnionVersion = FailureCode 0xC004  -- BADONION .|. PERM .|. 4+-- | The empty t'BlindedHopData', to be updated with record syntax.+--+--   >>> let bhd = empty_blinded_hop_data { bhd_path_id = Just "path id" }+--   >>> encode_blinded_hop_data bhd+--   Right "\ACK\apath id"+empty_blinded_hop_data :: BlindedHopData+empty_blinded_hop_data = BlindedHopData+  Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing+  BOLT1.empty_tlv_stream --- | Invalid HMAC in onion.-pattern InvalidOnionHmac :: FailureCode-pattern InvalidOnionHmac = FailureCode 0xC005  -- BADONION .|. PERM .|. 5+-- | The @payment_relay@ record of t'BlindedHopData'.+data PaymentRelay = PaymentRelay+  { pr_cltv_expiry_delta           :: {-# UNPACK #-} !Word16+  , pr_fee_proportional_millionths :: {-# UNPACK #-} !Word32+  , pr_fee_base_msat               :: {-# UNPACK #-} !Word32+  } deriving (Eq, Show, Generic) --- | Invalid ephemeral key in onion.-pattern InvalidOnionKey :: FailureCode-pattern InvalidOnionKey = FailureCode 0xC006  -- BADONION .|. PERM .|. 6+instance NFData PaymentRelay --- | Temporary channel failure.-pattern TemporaryChannelFailure :: FailureCode-pattern TemporaryChannelFailure = FailureCode 0x1007  -- UPDATE .|. 7+-- | The @payment_constraints@ record of t'BlindedHopData'.+data PaymentConstraints = PaymentConstraints+  { pc_max_cltv_expiry   :: {-# UNPACK #-} !Word32+  , pc_htlc_minimum_msat :: !BOLT1.MilliSatoshi+  } deriving (Eq, Show, Generic) --- | Permanent channel failure.-pattern PermanentChannelFailure :: FailureCode-pattern PermanentChannelFailure = FailureCode 0x4008  -- PERM .|. 8+instance NFData PaymentConstraints --- | Amount below minimum for channel.-pattern AmountBelowMinimum :: FailureCode-pattern AmountBelowMinimum = FailureCode 0x100B  -- UPDATE .|. 11+-- | What a node in a blinded route learns from its+--   @encrypted_recipient_data@.+data BlindedInfo = BlindedInfo+  { bi_data          :: !BlindedHopData+    -- ^ the decrypted @encrypted_data_tlv@+  , bi_next_path_key :: !BOLT1.Point+    -- ^ the @path_key@ to send to the next node+  } deriving (Eq, Show, Generic) --- | Fee insufficient.-pattern FeeInsufficient :: FailureCode-pattern FeeInsufficient = FailureCode 0x100C  -- UPDATE .|. 12+instance NFData BlindedInfo --- | Incorrect CLTV expiry.-pattern IncorrectCltvExpiry :: FailureCode-pattern IncorrectCltvExpiry = FailureCode 0x100D  -- UPDATE .|. 13+-- failure messages ----------------------------------------------------------- --- | Expiry too soon.-pattern ExpiryTooSoon :: FailureCode-pattern ExpiryTooSoon = FailureCode 0x100E  -- UPDATE .|. 14+-- | A failure message (@failuremsg@): a failure, followed by an optional+--   TLV stream.+data FailureMessage = FailureMessage+  { fm_failure :: !Failure+  , fm_tlvs    :: !BOLT1.TlvStream+  } deriving (Eq, Show, Generic) --- | Payment details incorrect or unknown.-pattern IncorrectOrUnknownPaymentDetails :: FailureCode-pattern IncorrectOrUnknownPaymentDetails = FailureCode 0x400F  -- PERM .|. 15+instance NFData FailureMessage --- | Final incorrect CLTV expiry.-pattern FinalIncorrectCltvExpiry :: FailureCode-pattern FinalIncorrectCltvExpiry = FailureCode 18  -- 0x12+-- | A failure: a failure code and its data.+--+--   A @channel_update@ field holds the raw bytes of the update; it is+--   empty when the update is omitted.+data Failure+  = TemporaryNodeFailure+    -- ^ NODE|2+  | PermanentNodeFailure+    -- ^ PERM|NODE|2+  | RequiredNodeFeatureMissing+    -- ^ PERM|NODE|3+  | InvalidOnionVersion !OnionHash+    -- ^ BADONION|PERM|4: @sha256_of_onion@+  | InvalidOnionHmac !OnionHash+    -- ^ BADONION|PERM|5: @sha256_of_onion@+  | InvalidOnionKey !OnionHash+    -- ^ BADONION|PERM|6: @sha256_of_onion@+  | TemporaryChannelFailure !BS.ByteString+    -- ^ UPDATE|7: @channel_update@+  | PermanentChannelFailure+    -- ^ PERM|8+  | RequiredChannelFeatureMissing+    -- ^ PERM|9+  | UnknownNextPeer+    -- ^ PERM|10+  | AmountBelowMinimum !BOLT1.MilliSatoshi !BS.ByteString+    -- ^ UPDATE|11: @htlc_msat@, @channel_update@+  | FeeInsufficient !BOLT1.MilliSatoshi !BS.ByteString+    -- ^ UPDATE|12: @htlc_msat@, @channel_update@+  | IncorrectCltvExpiry {-# UNPACK #-} !Word32 !BS.ByteString+    -- ^ UPDATE|13: @cltv_expiry@, @channel_update@+  | ExpiryTooSoon !BS.ByteString+    -- ^ UPDATE|14: @channel_update@+  | IncorrectOrUnknownPaymentDetails !BOLT1.MilliSatoshi+                                     {-# UNPACK #-} !Word32+    -- ^ PERM|15: @htlc_msat@, @height@+  | FinalIncorrectCltvExpiry {-# UNPACK #-} !Word32+    -- ^ 18: @cltv_expiry@+  | FinalIncorrectHtlcAmount !BOLT1.MilliSatoshi+    -- ^ 19: @incoming_htlc_amt@+  | ChannelDisabled {-# UNPACK #-} !Word16 !BS.ByteString+    -- ^ UPDATE|20: @disabled_flags@, @channel_update@+  | ExpiryTooFar+    -- ^ 21+  | InvalidOnionPayload !(Maybe (Word64, Word16))+    -- ^ PERM|22: the optional @type@ and @offset@+  | MppTimeout+    -- ^ 23+  | InvalidOnionBlinding !OnionHash+    -- ^ BADONION|PERM|24: @sha256_of_onion@+  | UnknownFailure {-# UNPACK #-} !Word16 !BS.ByteString+    -- ^ a failure code not defined above, and the bytes following it+  deriving (Eq, Show, Generic) --- | Final incorrect HTLC amount.-pattern FinalIncorrectHtlcAmount :: FailureCode-pattern FinalIncorrectHtlcAmount = FailureCode 19  -- 0x13+instance NFData Failure --- | Channel disabled.-pattern ChannelDisabled :: FailureCode-pattern ChannelDisabled = FailureCode 0x1014  -- UPDATE .|. 20+-- | A 32-byte @sha256_of_onion@.+newtype OnionHash = OnionHash BS.ByteString+  deriving (Eq, Show, Generic) --- | Expiry too far.-pattern ExpiryTooFar :: FailureCode-pattern ExpiryTooFar = FailureCode 21  -- 0x15+instance NFData OnionHash --- | Invalid onion payload.-pattern InvalidOnionPayload :: FailureCode-pattern InvalidOnionPayload = FailureCode 0x4016  -- PERM .|. 22+-- | Construct an t'OnionHash' from exactly 32 bytes.+--+--   >>> fmap (BS.length . un_onion_hash) (onion_hash (BS.replicate 32 0))+--   Just 32+onion_hash :: BS.ByteString -> Maybe OnionHash+onion_hash bs+  | BS.length bs == 32 = Just (OnionHash bs)+  | otherwise          = Nothing+{-# INLINE onion_hash #-} --- | MPP timeout.-pattern MppTimeout :: FailureCode-pattern MppTimeout = FailureCode 23  -- 0x17+-- | The bytes of an t'OnionHash'.+un_onion_hash :: OnionHash -> BS.ByteString+un_onion_hash (OnionHash bs) = bs+{-# INLINE un_onion_hash #-} --- Processing results -------------------------------------------------------+-- | The @failure_code@ of a 'Failure'.+--+--   >>> failure_code TemporaryNodeFailure+--   8194+--   >>> failure_code (UnknownFailure 0x1234 mempty)+--   4660+failure_code :: Failure -> Word16+failure_code f = case f of+  TemporaryNodeFailure                 -> 0x2002+  PermanentNodeFailure                 -> 0x6002+  RequiredNodeFeatureMissing           -> 0x6003+  InvalidOnionVersion _                -> 0xc004+  InvalidOnionHmac _                   -> 0xc005+  InvalidOnionKey _                    -> 0xc006+  TemporaryChannelFailure _            -> 0x1007+  PermanentChannelFailure              -> 0x4008+  RequiredChannelFeatureMissing        -> 0x4009+  UnknownNextPeer                      -> 0x400a+  AmountBelowMinimum _ _               -> 0x100b+  FeeInsufficient _ _                  -> 0x100c+  IncorrectCltvExpiry _ _              -> 0x100d+  ExpiryTooSoon _                      -> 0x100e+  IncorrectOrUnknownPaymentDetails _ _ -> 0x400f+  FinalIncorrectCltvExpiry _           -> 0x0012+  FinalIncorrectHtlcAmount _           -> 0x0013+  ChannelDisabled _ _                  -> 0x1014+  ExpiryTooFar                         -> 0x0015+  InvalidOnionPayload _                -> 0x4016+  MppTimeout                           -> 0x0017+  InvalidOnionBlinding _               -> 0xc018+  UnknownFailure c _                   -> c --- | Result of processing an onion packet.-data ProcessResult-  = Forward !ForwardInfo  -- ^ Forward to next hop-  | Receive !ReceiveInfo  -- ^ Final destination reached-  deriving (Eq, Show, Generic)+-- | Is the BADONION flag (0x8000) set: was the onion unparsable?+--+--   >>> is_badonion TemporaryNodeFailure+--   False+is_badonion :: Failure -> Bool+is_badonion f = failure_code f .&. 0x8000 /= 0+{-# INLINE is_badonion #-} --- | Information for forwarding to next hop.-data ForwardInfo = ForwardInfo-  { fiNextPacket   :: !OnionPacket-  , fiPayload      :: !HopPayload-  , fiSharedSecret :: !BS.ByteString  -- ^ For error attribution-  } deriving (Eq, Show, Generic)+-- | Is the PERM flag (0x4000) set: is the failure permanent?+--+--   >>> is_perm PermanentNodeFailure+--   True+is_perm :: Failure -> Bool+is_perm f = failure_code f .&. 0x4000 /= 0+{-# INLINE is_perm #-} --- | Information for receiving at final destination.-data ReceiveInfo = ReceiveInfo-  { riPayload      :: !HopPayload-  , riSharedSecret :: !BS.ByteString-  } deriving (Eq, Show, Generic)+-- | Is the NODE flag (0x2000) set: is it a node (not channel) failure?+--+--   >>> is_node TemporaryNodeFailure+--   True+is_node :: Failure -> Bool+is_node f = failure_code f .&. 0x2000 /= 0+{-# INLINE is_node #-} --- Constants ----------------------------------------------------------------+-- | Is the UPDATE flag (0x1000) set: was a channel forwarding parameter+--   violated?+--+--   >>> is_update (ExpiryTooSoon mempty)+--   True+is_update :: Failure -> Bool+is_update f = failure_code f .&. 0x1000 /= 0+{-# INLINE is_update #-} --- | Total onion packet size (1366 bytes).-onionPacketSize :: Int-onionPacketSize = 1366-{-# INLINE onionPacketSize #-}+-- errors --------------------------------------------------------------------- --- | Hop payloads section size (1300 bytes).-hopPayloadsSize :: Int-hopPayloadsSize = 1300-{-# INLINE hopPayloadsSize #-}+-- | Why a decoder failed.+data DecodeError+  = InvalidLength+    -- ^ the input has the wrong length+  | InvalidPoint+    -- ^ a point lacks a compressed-encoding prefix+  | InvalidTlvStream !BOLT1.TlvError+    -- ^ the TLV stream is malformed, or has an unknown even type+  | InvalidTlvValue {-# UNPACK #-} !Word64+    -- ^ the value of the record of this type is malformed+  | InvalidFailureData {-# UNPACK #-} !Word16+    -- ^ the data of a failure with this code is malformed+  deriving (Eq, Show, Generic) --- | HMAC size (32 bytes).-hmacSize :: Int-hmacSize = 32-{-# INLINE hmacSize #-}+instance NFData DecodeError --- | Compressed public key size (33 bytes).-pubkeySize :: Int-pubkeySize = 33-{-# INLINE pubkeySize #-}+-- | Why an encoder failed.+data EncodeError+  = ConflictingTlv+    -- ^ an extra TLV record has the type of a known record+  | FieldTooLong+    -- ^ a length-prefixed field exceeds its maximum length+  deriving (Eq, Show, Generic) --- | Version byte for onion packets.-versionByte :: Word8-versionByte = 0x00-{-# INLINE versionByte #-}+instance NFData EncodeError --- | Maximum payload size (1300 - 32 - 1 = 1267 bytes).-maxPayloadSize :: Int-maxPayloadSize = hopPayloadsSize - hmacSize - 1-{-# INLINE maxPayloadSize #-}+-- | Why an onion packet was rejected.+data ProcessError+  = InvalidVersion {-# UNPACK #-} !Word8+    -- ^ the version byte is not 0+  | InvalidPublicKey+    -- ^ the ephemeral public key is not a valid point+  | HmacMismatch+    -- ^ the packet's HMAC is wrong+  | InvalidPayloadLength+    -- ^ the payload length is malformed, below 2, or past the end of+    --   @hop_payloads@+  | InvalidPayload !DecodeError+    -- ^ the payload is not a valid @payload@ TLV stream+  | InvalidPathKey+    -- ^ the path key is not a valid point+  | UnexpectedPathKey+    -- ^ a path key was given with no @encrypted_recipient_data@, or+    --   both a @path_key@ and a @current_path_key@ were given+  | MissingPathKey+    -- ^ @encrypted_recipient_data@ was given with no path key+  | InvalidRecipientData+    -- ^ the @encrypted_recipient_data@ doesn't decrypt to a valid+    --   @encrypted_data_tlv@ stream+  deriving (Eq, Show, Generic) --- Silence unused import warning-_useBits :: Word16-_useBits = BADONION .&. PERM .|. NODE .|. UPDATE+instance NFData ProcessError
ppad-bolt4.cabal view
@@ -1,18 +1,20 @@ cabal-version:      3.0 name:               ppad-bolt4-version:            0.0.1-synopsis:           BOLT4 (onion routing) for Lightning Network+version:            0.1.0+synopsis:           Onion routing per BOLT #4 license:            MIT license-file:       LICENSE author:             Jared Tobin maintainer:         jared@ppad.tech category:           Cryptography build-type:         Simple-tested-with:        GHC == 9.10.3+tested-with:        GHC == { 9.10.3 } extra-doc-files:    CHANGELOG description:-  A pure Haskell implementation of BOLT4 (onion routing) from-  the Lightning Network protocol specification.+  Onion routing for the Lightning Network, per BOLT #4+  (<https://github.com/lightning/bolts/blob/master/04-onion-routing.md>):+  onion construction and processing, returning errors, and route+  blinding.  source-repository head   type:     git@@ -22,38 +24,47 @@   default-language: Haskell2010   hs-source-dirs:   lib   ghc-options:-    -Wall+      -Wall   exposed-modules:-    Lightning.Protocol.BOLT4-    Lightning.Protocol.BOLT4.Blinding-    Lightning.Protocol.BOLT4.Codec-    Lightning.Protocol.BOLT4.Construct-    Lightning.Protocol.BOLT4.Error-    Lightning.Protocol.BOLT4.Prim-    Lightning.Protocol.BOLT4.Process-    Lightning.Protocol.BOLT4.Types+      Lightning.Protocol.BOLT4+  other-modules:+      Lightning.Protocol.BOLT4.Blinding+      Lightning.Protocol.BOLT4.Codec+      Lightning.Protocol.BOLT4.Construct+      Lightning.Protocol.BOLT4.Error+      Lightning.Protocol.BOLT4.Prim+      Lightning.Protocol.BOLT4.Process+      Lightning.Protocol.BOLT4.Types   build-depends:       base >= 4.9 && < 5-    , bytestring >= 0.9 && < 0.13-    , ppad-aead >= 0.3 && < 0.4-    , ppad-chacha >= 0.2 && < 0.3-    , ppad-fixed >= 0.1 && < 0.2-    , ppad-secp256k1 >= 0.5 && < 0.6-    , ppad-sha256 >= 0.3 && < 0.4+    , bytestring >= 0.11.1 && < 0.13+    , deepseq >= 1.4 && < 1.6+    , ppad-aead >= 0.3.6 && < 0.4+    , ppad-bolt1 >= 0.1 && < 0.2+    , ppad-bolt9 >= 0.1 && < 0.2+    , ppad-chacha >= 0.3 && < 0.4+    , ppad-fixed >= 0.2 && < 0.3+    , ppad-secp256k1 >= 0.5.8 && < 0.6+    , ppad-sha256 >= 0.3.2 && < 0.4  test-suite bolt4-tests   type:             exitcode-stdio-1.0   default-language: Haskell2010   hs-source-dirs:   test   main-is:          Main.hs+  other-modules:    Vectors   ghc-options:-      -rtsopts -Wall -O2+      -rtsopts -Wall   build-depends:       base     , bytestring     , ppad-base16+    , ppad-bolt1     , ppad-bolt4+    , ppad-bolt9+    , ppad-chacha     , ppad-secp256k1+    , ppad-sha256     , tasty     , tasty-hunit     , tasty-quickcheck@@ -63,13 +74,15 @@   default-language: Haskell2010   hs-source-dirs:   bench   main-is:          Main.hs+  other-modules:    Fixtures   ghc-options:-      -rtsopts -O2 -Wall -fno-warn-orphans+      -rtsopts -O2 -Wall   build-depends:       base     , bytestring     , criterion     , deepseq+    , ppad-bolt1     , ppad-bolt4  benchmark bolt4-weigh@@ -77,10 +90,13 @@   default-language: Haskell2010   hs-source-dirs:   bench   main-is:          Weight.hs+  other-modules:    Fixtures   ghc-options:-      -rtsopts -O2 -Wall -fno-warn-orphans+      -rtsopts -O2 -Wall   build-depends:       base     , bytestring+    , deepseq+    , ppad-bolt1     , ppad-bolt4     , weigh
test/Main.hs view
@@ -2,1187 +2,1464 @@  module Main where -import Data.Bits (xor)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Base16 as B16-import qualified Crypto.Curve.Secp256k1 as Secp256k1-import Data.Word (Word8)-import Lightning.Protocol.BOLT4.Blinding-import Lightning.Protocol.BOLT4.Codec-import Lightning.Protocol.BOLT4.Construct-import Lightning.Protocol.BOLT4.Error-import Lightning.Protocol.BOLT4.Prim-import Lightning.Protocol.BOLT4.Process-import Lightning.Protocol.BOLT4.Types-import Test.Tasty-import Test.Tasty.HUnit-import Test.Tasty.QuickCheck---- | Demand a Just value in IO, failing the test on Nothing.-demand :: String -> Maybe a -> IO a-demand _ (Just a) = pure a-demand msg Nothing = assertFailure msg--main :: IO ()-main = defaultMain $ testGroup "ppad-bolt4" [-    testGroup "Prim" [-        primTests-      ]-  , testGroup "BigSize" [-        bigsizeTests-      , bigsizeRoundtripProp-      ]-  , testGroup "TLV" [-        tlvTests-      ]-  , testGroup "ShortChannelId" [-        sciTests-      ]-  , testGroup "OnionPacket" [-        onionPacketTests-      ]-  , testGroup "Construct" [-        constructTests-      ]-  , testGroup "Process" [-        processTests-      ]-  , testGroup "Error" [-        errorTests-      ]-  , testGroup "Blinding" [-        blindingKeyDerivationTests-      , blindingEphemeralKeyTests-      , blindingTlvTests-      , blindingEncryptionTests-      , blindingCreatePathTests-      , blindingProcessHopTests-      ]-  ]---- BigSize tests --------------------------------------------------------------bigsizeTests :: TestTree-bigsizeTests = testGroup "boundary values" [-    testCase "0" $-      encodeBigSize 0 @?= BS.pack [0x00]-  , testCase "0xFC" $-      encodeBigSize 0xFC @?= BS.pack [0xFC]-  , testCase "0xFD" $-      encodeBigSize 0xFD @?= BS.pack [0xFD, 0x00, 0xFD]-  , testCase "0xFFFF" $-      encodeBigSize 0xFFFF @?= BS.pack [0xFD, 0xFF, 0xFF]-  , testCase "0x10000" $-      encodeBigSize 0x10000 @?= BS.pack [0xFE, 0x00, 0x01, 0x00, 0x00]-  , testCase "0xFFFFFFFF" $-      encodeBigSize 0xFFFFFFFF @?= BS.pack [0xFE, 0xFF, 0xFF, 0xFF, 0xFF]-  , testCase "0x100000000" $-      encodeBigSize 0x100000000 @?=-        BS.pack [0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]-  , testCase "decode 0" $ do-      let result = decodeBigSize (BS.pack [0x00])-      result @?= Just (0, BS.empty)-  , testCase "decode 0xFC" $ do-      let result = decodeBigSize (BS.pack [0xFC])-      result @?= Just (0xFC, BS.empty)-  , testCase "decode 0xFD" $ do-      let result = decodeBigSize (BS.pack [0xFD, 0x00, 0xFD])-      result @?= Just (0xFD, BS.empty)-  , testCase "decode 0xFFFF" $ do-      let result = decodeBigSize (BS.pack [0xFD, 0xFF, 0xFF])-      result @?= Just (0xFFFF, BS.empty)-  , testCase "decode 0x10000" $ do-      let result = decodeBigSize (BS.pack [0xFE, 0x00, 0x01, 0x00, 0x00])-      result @?= Just (0x10000, BS.empty)-  , testCase "decode 0xFFFFFFFF" $ do-      let result = decodeBigSize (BS.pack [0xFE, 0xFF, 0xFF, 0xFF, 0xFF])-      result @?= Just (0xFFFFFFFF, BS.empty)-  , testCase "decode 0x100000000" $ do-      let result = decodeBigSize $-            BS.pack [0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]-      result @?= Just (0x100000000, BS.empty)-  , testCase "reject non-canonical 0xFD encoding of small value" $ do-      let result = decodeBigSize (BS.pack [0xFD, 0x00, 0xFC])-      result @?= Nothing-  , testCase "reject non-canonical 0xFE encoding of small value" $ do-      let result = decodeBigSize (BS.pack [0xFE, 0x00, 0x00, 0xFF, 0xFF])-      result @?= Nothing-  , testCase "bigSizeLen" $ do-      bigSizeLen 0 @?= 1-      bigSizeLen 0xFC @?= 1-      bigSizeLen 0xFD @?= 3-      bigSizeLen 0xFFFF @?= 3-      bigSizeLen 0x10000 @?= 5-      bigSizeLen 0xFFFFFFFF @?= 5-      bigSizeLen 0x100000000 @?= 9-  ]--bigsizeRoundtripProp :: TestTree-bigsizeRoundtripProp = testProperty "roundtrip" $ \n ->-  let encoded = encodeBigSize n-      decoded = decodeBigSize encoded-  in  decoded == Just (n, BS.empty)---- TLV tests ------------------------------------------------------------------tlvTests :: TestTree-tlvTests = testGroup "encoding/decoding" [-    testCase "single record" $ do-      let rec = TlvRecord 2 (BS.pack [0x01, 0x02, 0x03])-          encoded = encodeTlv rec-          decoded = decodeTlv encoded-      decoded @?= Just (rec, BS.empty)-  , testCase "stream roundtrip" $ do-      let recs = [ TlvRecord 2 (BS.pack [0x01])-                 , TlvRecord 4 (BS.pack [0x02, 0x03])-                 , TlvRecord 100 (BS.pack [0x04, 0x05, 0x06])-                 ]-          encoded = encodeTlvStream recs-          decoded = decodeTlvStream encoded-      decoded @?= Just recs-  , testCase "reject out-of-order types" $ do-      let rec1 = encodeTlv (TlvRecord 4 (BS.pack [0x01]))-          rec2 = encodeTlv (TlvRecord 2 (BS.pack [0x02]))-          badStream = rec1 <> rec2-          decoded = decodeTlvStream badStream-      decoded @?= Nothing-  , testCase "reject duplicate types" $ do-      let rec1 = encodeTlv (TlvRecord 2 (BS.pack [0x01]))-          rec2 = encodeTlv (TlvRecord 2 (BS.pack [0x02]))-          badStream = rec1 <> rec2-          decoded = decodeTlvStream badStream-      decoded @?= Nothing-  , testCase "empty stream" $ do-      let decoded = decodeTlvStream BS.empty-      decoded @?= Just []-  ]---- ShortChannelId tests -------------------------------------------------------sciTests :: TestTree-sciTests = testGroup "encoding/decoding" [-    testCase "known value" $ do-      let sci = ShortChannelId 700000 1234 5-          encoded = encodeShortChannelId sci-      BS.length encoded @?= 8-      let decoded = decodeShortChannelId encoded-      decoded @?= Just sci-  , testCase "maximum values" $ do-      let sci = ShortChannelId 0xFFFFFF 0xFFFFFF 0xFFFF-          encoded = encodeShortChannelId sci-      BS.length encoded @?= 8-      let decoded = decodeShortChannelId encoded-      decoded @?= Just sci-  , testCase "zero values" $ do-      let sci = ShortChannelId 0 0 0-          encoded = encodeShortChannelId sci-          expected = BS.pack [0, 0, 0, 0, 0, 0, 0, 0]-      encoded @?= expected-      let decoded = decodeShortChannelId encoded-      decoded @?= Just sci-  , testCase "reject wrong length" $ do-      let decoded = decodeShortChannelId (BS.pack [0, 1, 2, 3, 4, 5, 6])-      decoded @?= Nothing-  ]---- OnionPacket tests ----------------------------------------------------------onionPacketTests :: TestTree-onionPacketTests = testGroup "encoding/decoding" [-    testCase "roundtrip" $ do-      let packet = OnionPacket-            { opVersion = 0x00-            , opEphemeralKey = BS.replicate 33 0xAB-            , opHopPayloads = BS.replicate 1300 0xCD-            , opHmac = BS.replicate 32 0xEF-            }-          encoded = encodeOnionPacket packet-      BS.length encoded @?= onionPacketSize-      let decoded = decodeOnionPacket encoded-      decoded @?= Just packet-  , testCase "reject wrong size" $ do-      let decoded = decodeOnionPacket (BS.replicate 1000 0x00)-      decoded @?= Nothing-  ]---- Prim tests -----------------------------------------------------------------sessionKey :: BS.ByteString-sessionKey = BS.replicate 32 0x41--hop0PubKeyHex :: BS.ByteString-hop0PubKeyHex =-  "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"--hop0SharedSecretHex :: BS.ByteString-hop0SharedSecretHex =-  "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"--hop0BlindingFactorHex :: BS.ByteString-hop0BlindingFactorHex =-  "2ec2e5da605776054187180343287683aa6a51b4b1c04d6dd49c45d8cffb3c36"--fromHex :: BS.ByteString -> BS.ByteString-fromHex h = case B16.decode h of-  Just bs -> bs-  Nothing -> error "fromHex: invalid hex"--primTests :: TestTree-primTests = testGroup "cryptographic primitives" [-    testSharedSecret-  , testBlindingFactor-  , testKeyDerivation-  , testBlindPubKey-  , testGenerateStream-  , testHmacOperations-  ]--testSharedSecret :: TestTree-testSharedSecret = testCase "computeSharedSecret (BOLT4 spec hop 0)" $ do-  pubKey <- demand "parse_point" $-    Secp256k1.parse_point (fromHex hop0PubKeyHex)-  case computeSharedSecret sessionKey pubKey of-    Nothing -> assertFailure "computeSharedSecret returned Nothing"-    Just (SharedSecret computed) -> do-      let expected = fromHex hop0SharedSecretHex-      computed @?= expected--testBlindingFactor :: TestTree-testBlindingFactor = testCase "computeBlindingFactor (BOLT4 spec hop 0)" $ do-  sk <- demand "roll32" $ Secp256k1.roll32 sessionKey-  ephemPubKey <- demand "derive_pub" $ Secp256k1.derive_pub sk-  nodePubKey <- demand "parse_point" $-    Secp256k1.parse_point (fromHex hop0PubKeyHex)-  case computeSharedSecret sessionKey nodePubKey of-    Nothing -> assertFailure "computeSharedSecret returned Nothing"-    Just sharedSecret -> do-      let BlindingFactor computed =-            computeBlindingFactor ephemPubKey sharedSecret-          expected = fromHex hop0BlindingFactorHex-      computed @?= expected--testKeyDerivation :: TestTree-testKeyDerivation = testGroup "key derivation" [-    testCase "deriveRho produces 32 bytes" $ do-      let ss = SharedSecret (BS.replicate 32 0)-          DerivedKey rho = deriveRho ss-      BS.length rho @?= 32-  , testCase "deriveMu produces 32 bytes" $ do-      let ss = SharedSecret (BS.replicate 32 0)-          DerivedKey mu = deriveMu ss-      BS.length mu @?= 32-  , testCase "deriveUm produces 32 bytes" $ do-      let ss = SharedSecret (BS.replicate 32 0)-          DerivedKey um = deriveUm ss-      BS.length um @?= 32-  , testCase "derivePad produces 32 bytes" $ do-      let ss = SharedSecret (BS.replicate 32 0)-          DerivedKey pad = derivePad ss-      BS.length pad @?= 32-  , testCase "deriveAmmag produces 32 bytes" $ do-      let ss = SharedSecret (BS.replicate 32 0)-          DerivedKey ammag = deriveAmmag ss-      BS.length ammag @?= 32-  , testCase "different key types produce different results" $ do-      let ss = SharedSecret (BS.replicate 32 0x42)-          DerivedKey rho = deriveRho ss-          DerivedKey mu = deriveMu ss-          DerivedKey um = deriveUm ss-      assertBool "rho /= mu" (rho /= mu)-      assertBool "mu /= um" (mu /= um)-      assertBool "rho /= um" (rho /= um)-  ]--testBlindPubKey :: TestTree-testBlindPubKey = testGroup "key blinding" [-    testCase "blindPubKey produces valid key" $ do-      sk <- demand "roll32" $ Secp256k1.roll32 sessionKey-      pubKey <- demand "derive_pub" $ Secp256k1.derive_pub sk-      let bf = BlindingFactor (fromHex hop0BlindingFactorHex)-      case blindPubKey pubKey bf of-        Nothing -> assertFailure "blindPubKey returned Nothing"-        Just _blinded -> return ()-  , testCase "blindSecKey produces valid key" $ do-      let bf = BlindingFactor (fromHex hop0BlindingFactorHex)-      case blindSecKey sessionKey bf of-        Nothing -> assertFailure "blindSecKey returned Nothing"-        Just _blinded -> return ()-  ]--testGenerateStream :: TestTree-testGenerateStream = testGroup "generateStream" [-    testCase "produces correct length" $ do-      let dk = DerivedKey (BS.replicate 32 0)-          stream = generateStream dk 100-      BS.length stream @?= 100-  , testCase "1300-byte stream for hop_payloads" $ do-      let dk = DerivedKey (BS.replicate 32 0x42)-          stream = generateStream dk 1300-      BS.length stream @?= 1300-  , testCase "deterministic output" $ do-      let dk = DerivedKey (BS.replicate 32 0x55)-          stream1 = generateStream dk 64-          stream2 = generateStream dk 64-      stream1 @?= stream2-  ]--testHmacOperations :: TestTree-testHmacOperations = testGroup "HMAC operations" [-    testCase "computeHmac produces 32 bytes" $ do-      let dk = DerivedKey (BS.replicate 32 0)-          hmac = computeHmac dk "payloads" "assocdata"-      BS.length hmac @?= 32-  , testCase "verifyHmac succeeds for matching" $ do-      let dk = DerivedKey (BS.replicate 32 0)-          hmac = computeHmac dk "payloads" "assocdata"-      assertBool "verifyHmac should succeed" (verifyHmac hmac hmac)-  , testCase "verifyHmac fails for different" $ do-      let dk = DerivedKey (BS.replicate 32 0)-          hmac1 = computeHmac dk "payloads1" "assocdata"-          hmac2 = computeHmac dk "payloads2" "assocdata"-      assertBool "verifyHmac should fail" (not $ verifyHmac hmac1 hmac2)-  , testCase "verifyHmac fails for different lengths" $ do-      assertBool "verifyHmac should fail"-        (not $ verifyHmac "short" "different length")-  ]---- Construct tests ---------------------------------------------------------------- Test vectors from BOLT4 spec-hop1PubKeyHex :: BS.ByteString-hop1PubKeyHex =-  "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c"--hop2PubKeyHex :: BS.ByteString-hop2PubKeyHex =-  "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007"--hop3PubKeyHex :: BS.ByteString-hop3PubKeyHex =-  "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"--hop4PubKeyHex :: BS.ByteString-hop4PubKeyHex =-  "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"---- Expected shared secrets from BOLT4 error test vectors (in route order)-hop1SharedSecretHex :: BS.ByteString-hop1SharedSecretHex =-  "a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"--hop2SharedSecretHex :: BS.ByteString-hop2SharedSecretHex =-  "3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"--hop3SharedSecretHex :: BS.ByteString-hop3SharedSecretHex =-  "21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"--hop4SharedSecretHex :: BS.ByteString-hop4SharedSecretHex =-  "b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"--constructTests :: TestTree-constructTests = testGroup "packet construction" [-    testConstructErrorCases-  , testSharedSecretComputation-  , testPacketStructure-  , testSingleHop-  ]--testConstructErrorCases :: TestTree-testConstructErrorCases = testGroup "error cases" [-    testCase "rejects invalid session key (too short)" $ do-      let result = construct (BS.replicate 31 0x41) [] ""-      case result of-        Left InvalidSessionKey -> return ()-        _ -> assertFailure "Expected InvalidSessionKey"-  , testCase "rejects invalid session key (too long)" $ do-      let result = construct (BS.replicate 33 0x41) [] ""-      case result of-        Left InvalidSessionKey -> return ()-        _ -> assertFailure "Expected InvalidSessionKey"-  , testCase "rejects empty route" $ do-      let result = construct sessionKey [] ""-      case result of-        Left EmptyRoute -> return ()-        _ -> assertFailure "Expected EmptyRoute"-  , testCase "rejects too many hops" $ do-      pub <- demand "parse_point" $-        Secp256k1.parse_point (fromHex hop0PubKeyHex)-      let emptyPayload = HopPayload Nothing Nothing Nothing Nothing-                           Nothing Nothing []-          hop = Hop pub emptyPayload-          hops = replicate 21 hop-          result = construct sessionKey hops ""-      case result of-        Left TooManyHops -> return ()-        _ -> assertFailure "Expected TooManyHops"-  ]--testSharedSecretComputation :: TestTree-testSharedSecretComputation =-  testCase "computes correct shared secrets (BOLT4 spec)" $ do-    pub0 <- demand "parse_point" $-      Secp256k1.parse_point (fromHex hop0PubKeyHex)-    pub1 <- demand "parse_point" $-      Secp256k1.parse_point (fromHex hop1PubKeyHex)-    pub2 <- demand "parse_point" $-      Secp256k1.parse_point (fromHex hop2PubKeyHex)-    pub3 <- demand "parse_point" $-      Secp256k1.parse_point (fromHex hop3PubKeyHex)-    pub4 <- demand "parse_point" $-      Secp256k1.parse_point (fromHex hop4PubKeyHex)-    let emptyPayload = HopPayload Nothing Nothing Nothing Nothing-                         Nothing Nothing []-        hops = [ Hop pub0 emptyPayload-               , Hop pub1 emptyPayload-               , Hop pub2 emptyPayload-               , Hop pub3 emptyPayload-               , Hop pub4 emptyPayload-               ]-        result = construct sessionKey hops ""-    case result of-      Left err -> assertFailure $ "construct failed: " ++ show err-      Right (_, secrets) -> case secrets of-        [SharedSecret ss0, SharedSecret ss1, SharedSecret ss2,-         SharedSecret ss3, SharedSecret ss4] -> do-          ss0 @?= fromHex hop0SharedSecretHex-          ss1 @?= fromHex hop1SharedSecretHex-          ss2 @?= fromHex hop2SharedSecretHex-          ss3 @?= fromHex hop3SharedSecretHex-          ss4 @?= fromHex hop4SharedSecretHex-        _ -> assertFailure "expected 5 shared secrets"--testPacketStructure :: TestTree-testPacketStructure = testCase "produces valid packet structure" $ do-  pub0 <- demand "parse_point" $-    Secp256k1.parse_point (fromHex hop0PubKeyHex)-  pub1 <- demand "parse_point" $-    Secp256k1.parse_point (fromHex hop1PubKeyHex)-  let emptyPayload = HopPayload Nothing Nothing Nothing Nothing-                       Nothing Nothing []-      hops = [Hop pub0 emptyPayload, Hop pub1 emptyPayload]-      result = construct sessionKey hops ""-  case result of-    Left err -> assertFailure $ "construct failed: " ++ show err-    Right (packet, _) -> do-      opVersion packet @?= versionByte-      BS.length (opEphemeralKey packet) @?= pubkeySize-      BS.length (opHopPayloads packet) @?= hopPayloadsSize-      BS.length (opHmac packet) @?= hmacSize-      sk <- demand "roll32" $ Secp256k1.roll32 sessionKey-      expectedPub <- demand "derive_pub" $-        Secp256k1.derive_pub sk-      let expectedPubBytes = Secp256k1.serialize_point expectedPub-      opEphemeralKey packet @?= expectedPubBytes--testSingleHop :: TestTree-testSingleHop = testCase "constructs single-hop packet" $ do-  pub0 <- demand "parse_point" $-    Secp256k1.parse_point (fromHex hop0PubKeyHex)-  let payload = HopPayload-        { hpAmtToForward = Just 1000-        , hpOutgoingCltv = Just 500000-        , hpShortChannelId = Nothing-        , hpPaymentData = Nothing-        , hpEncryptedData = Nothing-        , hpCurrentPathKey = Nothing-        , hpUnknownTlvs = []-        }-      hops = [Hop pub0 payload]-      result = construct sessionKey hops ""-  case result of-    Left err -> assertFailure $ "construct failed: " ++ show err-    Right (packet, secrets) -> do-      length secrets @?= 1-      -- Packet should be valid structure-      let encoded = encodeOnionPacket packet-      BS.length encoded @?= onionPacketSize-      -- Should decode back-      decoded <- demand "decodeOnionPacket" $-        decodeOnionPacket encoded-      decoded @?= packet---- Process tests ---------------------------------------------------------------processTests :: TestTree-processTests = testGroup "packet processing" [-    testVersionValidation-  , testEphemeralKeyValidation-  , testHmacValidation-  , testProcessBasic-  ]--testVersionValidation :: TestTree-testVersionValidation = testGroup "version validation" [-    testCase "reject invalid version 0x01" $ do-      let packet = OnionPacket-            { opVersion = 0x01  -- Invalid, should be 0x00-            , opEphemeralKey = BS.replicate 33 0x02-            , opHopPayloads = BS.replicate 1300 0x00-            , opHmac = BS.replicate 32 0x00-            }-      case process sessionKey packet BS.empty of-        Left (InvalidVersion v) -> v @?= 0x01-        Left other -> assertFailure $ "expected InvalidVersion, got: "-          ++ show other-        Right _ -> assertFailure "expected rejection, got success"-  , testCase "reject invalid version 0xFF" $ do-      let packet = OnionPacket-            { opVersion = 0xFF-            , opEphemeralKey = BS.replicate 33 0x02-            , opHopPayloads = BS.replicate 1300 0x00-            , opHmac = BS.replicate 32 0x00-            }-      case process sessionKey packet BS.empty of-        Left (InvalidVersion v) -> v @?= 0xFF-        Left other -> assertFailure $ "expected InvalidVersion, got: "-          ++ show other-        Right _ -> assertFailure "expected rejection, got success"-  ]--testEphemeralKeyValidation :: TestTree-testEphemeralKeyValidation = testGroup "ephemeral key validation" [-    testCase "reject invalid ephemeral key (all zeros)" $ do-      let packet = OnionPacket-            { opVersion = 0x00-            , opEphemeralKey = BS.replicate 33 0x00  -- Invalid pubkey-            , opHopPayloads = BS.replicate 1300 0x00-            , opHmac = BS.replicate 32 0x00-            }-      case process sessionKey packet BS.empty of-        Left InvalidEphemeralKey -> return ()-        Left other -> assertFailure $ "expected InvalidEphemeralKey, got: "-          ++ show other-        Right _ -> assertFailure "expected rejection, got success"-  , testCase "reject malformed ephemeral key" $ do-      -- 0x04 prefix is for uncompressed keys, but we only have 33 bytes-      let packet = OnionPacket-            { opVersion = 0x00-            , opEphemeralKey = BS.pack (0x04 : replicate 32 0xAB)-            , opHopPayloads = BS.replicate 1300 0x00-            , opHmac = BS.replicate 32 0x00-            }-      case process sessionKey packet BS.empty of-        Left InvalidEphemeralKey -> return ()-        Left other -> assertFailure $ "expected InvalidEphemeralKey, got: "-          ++ show other-        Right _ -> assertFailure "expected rejection, got success"-  ]--testHmacValidation :: TestTree-testHmacValidation = testGroup "HMAC validation" [-    testCase "reject invalid HMAC" $ do-      -- Use a valid ephemeral key but wrong HMAC-      hop0PubKey <- demand "parse_point" $-        Secp256k1.parse_point (fromHex hop0PubKeyHex)-      let ephKeyBytes = Secp256k1.serialize_point hop0PubKey-          packet = OnionPacket-            { opVersion = 0x00-            , opEphemeralKey = ephKeyBytes-            , opHopPayloads = BS.replicate 1300 0x00-            , opHmac = BS.replicate 32 0xFF  -- Wrong HMAC-            }-      case process sessionKey packet BS.empty of-        Left HmacMismatch -> return ()-        Left other -> assertFailure $ "expected HmacMismatch, got: "-          ++ show other-        Right _ -> assertFailure "expected rejection, got success"-  ]---- | Test basic packet processing with a properly constructed packet.-testProcessBasic :: TestTree-testProcessBasic = testGroup "basic processing" [-    testCase "process valid packet (final hop, all-zero next HMAC)" $ do-      -- Construct a valid packet for a final hop-      -- The hop payload needs to be properly formatted TLV-      hop0PubKey <- demand "parse_point" $-        Secp256k1.parse_point (fromHex hop0PubKeyHex)-      let ephKeyBytes = Secp256k1.serialize_point hop0PubKey-          hopPayloadTlv = encodeHopPayload HopPayload-            { hpAmtToForward = Just 1000-            , hpOutgoingCltv = Just 500000-            , hpShortChannelId = Nothing-            , hpPaymentData = Nothing-            , hpEncryptedData = Nothing-            , hpCurrentPathKey = Nothing-            , hpUnknownTlvs = []-            }-          payloadLen = BS.length hopPayloadTlv-          lenPrefix = encodeBigSize (fromIntegral payloadLen)-          payloadWithHmac = lenPrefix <> hopPayloadTlv-            <> BS.replicate 32 0x00-          padding = BS.replicate-            (1300 - BS.length payloadWithHmac) 0x00-          rawPayloads = payloadWithHmac <> padding-      ss <- demand "computeSharedSecret" $-        computeSharedSecret sessionKey hop0PubKey-      let rhoKey = deriveRho ss-          muKey = deriveMu ss-          stream = generateStream rhoKey 1300-          encryptedPayloads =-            BS.pack (BS.zipWith xor rawPayloads stream)-          correctHmac =-            computeHmac muKey encryptedPayloads BS.empty-          packet = OnionPacket-            { opVersion = 0x00-            , opEphemeralKey = ephKeyBytes-            , opHopPayloads = encryptedPayloads-            , opHmac = correctHmac-            }--      case process sessionKey packet BS.empty of-        Left err -> assertFailure $ "expected success, got: " ++ show err-        Right (Receive ri) -> do-          -- Verify we got the payload back-          hpAmtToForward (riPayload ri) @?= Just 1000-          hpOutgoingCltv (riPayload ri) @?= Just 500000-        Right (Forward _) -> assertFailure "expected Receive, got Forward"-  ]---- Error tests -------------------------------------------------------------------errorTests :: TestTree-errorTests = testGroup "error handling" [-    testErrorConstruction-  , testErrorRoundtrip-  , testMultiHopWrapping-  , testErrorAttribution-  , testFailureMessageParsing-  ]---- Shared secrets for testing (deterministic)-testSecret1 :: SharedSecret-testSecret1 = SharedSecret (BS.replicate 32 0x11)--testSecret2 :: SharedSecret-testSecret2 = SharedSecret (BS.replicate 32 0x22)--testSecret3 :: SharedSecret-testSecret3 = SharedSecret (BS.replicate 32 0x33)--testSecret4 :: SharedSecret-testSecret4 = SharedSecret (BS.replicate 32 0x44)---- Simple failure message for testing-testFailure :: FailureMessage-testFailure = FailureMessage IncorrectOrUnknownPaymentDetails BS.empty []--testErrorConstruction :: TestTree-testErrorConstruction = testCase "error packet construction" $ do-  let errPacket = constructError testSecret1 testFailure-      ErrorPacket bs = errPacket-  -- Error packet should be at least minErrorPacketSize-  assertBool "error packet >= 256 bytes" (BS.length bs >= minErrorPacketSize)--testErrorRoundtrip :: TestTree-testErrorRoundtrip = testCase "construct and unwrap roundtrip" $ do-  let errPacket = constructError testSecret1 testFailure-      result = unwrapError [testSecret1] errPacket-  case result of-    Attributed idx msg -> do-      idx @?= 0-      fmCode msg @?= IncorrectOrUnknownPaymentDetails-    UnknownOrigin _ ->-      assertFailure "Expected Attributed, got UnknownOrigin"--testMultiHopWrapping :: TestTree-testMultiHopWrapping = testGroup "multi-hop wrapping" [-    testCase "3-hop route, error from hop 2 (final)" $ do-      -- Route: origin -> hop0 -> hop1 -> hop2 (final, fails)-      -- Error constructed at hop2, wrapped at hop1, wrapped at hop0-      let secrets = [testSecret1, testSecret2, testSecret3]-          -- Hop 2 constructs error-          err0 = constructError testSecret3 testFailure-          -- Hop 1 wraps-          err1 = wrapError testSecret2 err0-          -- Hop 0 wraps-          err2 = wrapError testSecret1 err1-          -- Origin unwraps-          result = unwrapError secrets err2-      case result of-        Attributed idx msg -> do-          idx @?= 2-          fmCode msg @?= IncorrectOrUnknownPaymentDetails-        UnknownOrigin _ ->-          assertFailure "Expected Attributed, got UnknownOrigin"--  , testCase "4-hop route, error from hop 1 (intermediate)" $ do-      -- Route: origin -> hop0 -> hop1 (fails) -> hop2 -> hop3-      let secrets = [testSecret1, testSecret2, testSecret3, testSecret4]-          -- Hop 1 constructs error-          err0 = constructError testSecret2 testFailure-          -- Hop 0 wraps-          err1 = wrapError testSecret1 err0-          -- Origin unwraps-          result = unwrapError secrets err1-      case result of-        Attributed idx msg -> do-          idx @?= 1-          fmCode msg @?= IncorrectOrUnknownPaymentDetails-        UnknownOrigin _ ->-          assertFailure "Expected Attributed, got UnknownOrigin"--  , testCase "4-hop route, error from hop 0 (first)" $ do-      let secrets = [testSecret1, testSecret2, testSecret3, testSecret4]-          -- Hop 0 constructs error (no wrapping needed)-          err0 = constructError testSecret1 testFailure-          -- Origin unwraps-          result = unwrapError secrets err0-      case result of-        Attributed idx msg -> do-          idx @?= 0-          fmCode msg @?= IncorrectOrUnknownPaymentDetails-        UnknownOrigin _ ->-          assertFailure "Expected Attributed, got UnknownOrigin"-  ]--testErrorAttribution :: TestTree-testErrorAttribution = testGroup "error attribution" [-    testCase "wrong secrets gives UnknownOrigin" $ do-      let err = constructError testSecret1 testFailure-          wrongSecrets = [testSecret2, testSecret3]-          result = unwrapError wrongSecrets err-      case result of-        UnknownOrigin _ -> return ()-        Attributed _ _ ->-          assertFailure "Expected UnknownOrigin with wrong secrets"--  , testCase "empty secrets gives UnknownOrigin" $ do-      let err = constructError testSecret1 testFailure-          result = unwrapError [] err-      case result of-        UnknownOrigin _ -> return ()-        Attributed _ _ ->-          assertFailure "Expected UnknownOrigin with empty secrets"--  , testCase "correct attribution with multiple failures" $ do-      -- Test different failure codes-      let failures =-            [ (TemporaryNodeFailure, testSecret1)-            , (PermanentNodeFailure, testSecret2)-            , (InvalidOnionHmac, testSecret3)-            ]-      mapM_ (\(code, secret) -> do-        let failure = FailureMessage code BS.empty []-            err = constructError secret failure-            result = unwrapError [secret] err-        case result of-          Attributed 0 msg -> fmCode msg @?= code-          _ -> assertFailure $ "Failed for code: " ++ show code-        ) failures-  ]--testFailureMessageParsing :: TestTree-testFailureMessageParsing = testGroup "failure message parsing" [-    testCase "code with data" $ do-      -- AmountBelowMinimum typically includes channel update data-      let failData = BS.replicate 10 0xAB-          failure = FailureMessage AmountBelowMinimum failData []-          err = constructError testSecret1 failure-          result = unwrapError [testSecret1] err-      case result of-        Attributed 0 msg -> do-          fmCode msg @?= AmountBelowMinimum-          fmData msg @?= failData-        _ -> assertFailure "Expected Attributed"--  , testCase "various failure codes roundtrip" $ do-      let codes =-            [ InvalidRealm-            , TemporaryNodeFailure-            , PermanentNodeFailure-            , InvalidOnionHmac-            , TemporaryChannelFailure-            , IncorrectOrUnknownPaymentDetails-            ]-      mapM_ (\code -> do-        let failure = FailureMessage code BS.empty []-            err = constructError testSecret1 failure-            result = unwrapError [testSecret1] err-        case result of-          Attributed 0 msg -> fmCode msg @?= code-          _ -> assertFailure $ "Failed for code: " ++ show code-        ) codes-  ]---- Blinding tests --------------------------------------------------------------- Test data setup--testSeed :: BS.ByteString-testSeed = BS.pack [1..32]--makeSecKey :: Word8 -> BS.ByteString-makeSecKey seed = BS.pack $ replicate 31 0x00 ++ [seed]--makePubKey :: Word8 -> Maybe Secp256k1.Projective-makePubKey seed = do-  sk <- Secp256k1.roll32 (makeSecKey seed)-  Secp256k1.derive_pub sk--testNodeSecKey1, testNodeSecKey2, testNodeSecKey3 :: BS.ByteString-testNodeSecKey1 = makeSecKey 0x11-testNodeSecKey2 = makeSecKey 0x22-testNodeSecKey3 = makeSecKey 0x33--testNodePubKey1, testNodePubKey2, testNodePubKey3 :: Secp256k1.Projective-testNodePubKey1 = case makePubKey 0x11 of-  Just pk -> pk-  Nothing -> error "testNodePubKey1: invalid key"-testNodePubKey2 = case makePubKey 0x22 of-  Just pk -> pk-  Nothing -> error "testNodePubKey2: invalid key"-testNodePubKey3 = case makePubKey 0x33 of-  Just pk -> pk-  Nothing -> error "testNodePubKey3: invalid key"--testSharedSecretBS :: SharedSecret-testSharedSecretBS = SharedSecret (BS.pack [0x42..0x61])--emptyHopData :: BlindedHopData-emptyHopData = BlindedHopData-  Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing--sampleHopData :: BlindedHopData-sampleHopData = BlindedHopData-  { bhdPadding = Nothing-  , bhdShortChannelId = Just (ShortChannelId 700000 1234 0)-  , bhdNextNodeId = Nothing-  , bhdPathId = Just (BS.pack [0x42, 0x42])-  , bhdNextPathKeyOverride = Nothing-  , bhdPaymentRelay = Just (PaymentRelay 40 1000 500)-  , bhdPaymentConstraints = Just (PaymentConstraints 144 1000000)-  , bhdAllowedFeatures = Nothing-  }--hopDataWithNextNode :: BlindedHopData-hopDataWithNextNode = emptyHopData-  { bhdNextNodeId = Just (Secp256k1.serialize_point testNodePubKey2) }---- 1. Key Derivation Tests ----------------------------------------------------blindingKeyDerivationTests :: TestTree-blindingKeyDerivationTests = testGroup "key derivation" [-    testCase "deriveBlindingRho produces 32 bytes" $ do-      let DerivedKey rho = deriveBlindingRho testSharedSecretBS-      BS.length rho @?= 32--  , testCase "deriveBlindingRho is deterministic" $ do-      let rho1 = deriveBlindingRho testSharedSecretBS-          rho2 = deriveBlindingRho testSharedSecretBS-      rho1 @?= rho2--  , testCase "deriveBlindingRho differs for different secrets" $ do-      let ss1 = SharedSecret (BS.replicate 32 0x00)-          ss2 = SharedSecret (BS.replicate 32 0x01)-          rho1 = deriveBlindingRho ss1-          rho2 = deriveBlindingRho ss2-      assertBool "rho values should differ" (rho1 /= rho2)--  , testCase "deriveBlindedNodeId produces 33 bytes" $ do-      case deriveBlindedNodeId testSharedSecretBS testNodePubKey1 of-        Nothing -> assertFailure "deriveBlindedNodeId returned Nothing"-        Just blindedId -> BS.length blindedId @?= 33--  , testCase "deriveBlindedNodeId is deterministic" $ do-      let result1 = deriveBlindedNodeId testSharedSecretBS testNodePubKey1-          result2 = deriveBlindedNodeId testSharedSecretBS testNodePubKey1-      result1 @?= result2--  , testCase "deriveBlindedNodeId differs for different nodes" $ do-      let result1 = deriveBlindedNodeId testSharedSecretBS testNodePubKey1-          result2 = deriveBlindedNodeId testSharedSecretBS testNodePubKey2-      assertBool "blinded node IDs should differ" (result1 /= result2)-  ]---- 2. Ephemeral Key Iteration Tests --------------------------------------------- | Derive the public key for testSeed-testSeedPubKey :: Secp256k1.Projective-testSeedPubKey = case Secp256k1.roll32 testSeed of-  Nothing -> error "testSeedPubKey: invalid seed"-  Just sk -> case Secp256k1.derive_pub sk of-    Nothing -> error "testSeedPubKey: invalid key"-    Just pk -> pk--blindingEphemeralKeyTests :: TestTree-blindingEphemeralKeyTests = testGroup "ephemeral key iteration" [-    testCase "nextEphemeral produces valid keys" $ do-      -- Use matching secret/public key pair-      case nextEphemeral testSeed testSeedPubKey testSharedSecretBS of-        Nothing -> assertFailure "nextEphemeral returned Nothing"-        Just (newSecKey, newPubKey) -> do-          BS.length newSecKey @?= 32-          let serialized = Secp256k1.serialize_point newPubKey-          BS.length serialized @?= 33--  , testCase "nextEphemeral: new secret key derives new public key" $ do-      -- Use matching secret/public key pair-      case nextEphemeral testSeed testSeedPubKey testSharedSecretBS of-        Nothing -> assertFailure "nextEphemeral returned Nothing"-        Just (newSecKey, newPubKey) -> do-          sk <- demand "roll32" $ Secp256k1.roll32 newSecKey-          derivedPub <- demand "derive_pub" $-            Secp256k1.derive_pub sk-          derivedPub @?= newPubKey--  , testCase "nextEphemeral is deterministic" $ do-      let result1 = nextEphemeral testSeed testSeedPubKey testSharedSecretBS-          result2 = nextEphemeral testSeed testSeedPubKey testSharedSecretBS-      result1 @?= result2--  , testCase "nextEphemeral differs for different shared secrets" $ do-      let ss1 = SharedSecret (BS.replicate 32 0xAA)-          ss2 = SharedSecret (BS.replicate 32 0xBB)-          result1 = nextEphemeral testSeed testSeedPubKey ss1-          result2 = nextEphemeral testSeed testSeedPubKey ss2-      assertBool "results should differ" (result1 /= result2)-  ]---- 3. TLV Encoding/Decoding Tests ---------------------------------------------blindingTlvTests :: TestTree-blindingTlvTests = testGroup "TLV encoding/decoding" [-    testCase "roundtrip: empty hop data" $ do-      let encoded = encodeBlindedHopData emptyHopData-          decoded = decodeBlindedHopData encoded-      decoded @?= Just emptyHopData--  , testCase "roundtrip: sample hop data" $ do-      let encoded = encodeBlindedHopData sampleHopData-          decoded = decodeBlindedHopData encoded-      decoded @?= Just sampleHopData--  , testCase "roundtrip: hop data with next node ID" $ do-      let encoded = encodeBlindedHopData hopDataWithNextNode-          decoded = decodeBlindedHopData encoded-      decoded @?= Just hopDataWithNextNode--  , testCase "roundtrip: hop data with padding" $ do-      let hopData = emptyHopData { bhdPadding = Just (BS.replicate 16 0x00) }-          encoded = encodeBlindedHopData hopData-          decoded = decodeBlindedHopData encoded-      decoded @?= Just hopData--  , testCase "PaymentRelay encoding/decoding" $ do-      let relay = PaymentRelay 40 1000 500-          hopData = emptyHopData { bhdPaymentRelay = Just relay }-          encoded = encodeBlindedHopData hopData-          decoded = decodeBlindedHopData encoded-      case decoded of-        Nothing -> assertFailure "decodeBlindedHopData returned Nothing"-        Just hd -> bhdPaymentRelay hd @?= Just relay--  , testCase "PaymentConstraints encoding/decoding" $ do-      let constraints = PaymentConstraints 144 1000000-          hopData = emptyHopData { bhdPaymentConstraints = Just constraints }-          encoded = encodeBlindedHopData hopData-          decoded = decodeBlindedHopData encoded-      case decoded of-        Nothing -> assertFailure "decodeBlindedHopData returned Nothing"-        Just hd -> bhdPaymentConstraints hd @?= Just constraints--  , testCase "decode empty bytestring returns empty hop data" $ do-      let decoded = decodeBlindedHopData BS.empty-      decoded @?= Just emptyHopData-  ]---- 4. Encryption/Decryption Tests ---------------------------------------------blindingEncryptionTests :: TestTree-blindingEncryptionTests = testGroup "encryption/decryption" [-    testCase "roundtrip: encrypt then decrypt" $ do-      let rho = deriveBlindingRho testSharedSecretBS-          encrypted = encryptHopData rho sampleHopData-          decrypted = decryptHopData rho encrypted-      decrypted @?= Just sampleHopData--  , testCase "roundtrip: empty hop data" $ do-      let rho = deriveBlindingRho testSharedSecretBS-          encrypted = encryptHopData rho emptyHopData-          decrypted = decryptHopData rho encrypted-      decrypted @?= Just emptyHopData--  , testCase "decryption with wrong key fails" $ do-      let rho1 = deriveBlindingRho testSharedSecretBS-          rho2 = deriveBlindingRho (SharedSecret (BS.replicate 32 0xFF))-          encrypted = encryptHopData rho1 sampleHopData-          decrypted = decryptHopData rho2 encrypted-      assertBool "decryption should fail or produce garbage"-        (decrypted /= Just sampleHopData)--  , testCase "encrypt is deterministic" $ do-      let rho = deriveBlindingRho testSharedSecretBS-          encrypted1 = encryptHopData rho sampleHopData-          encrypted2 = encryptHopData rho sampleHopData-      encrypted1 @?= encrypted2-  ]---- 5. createBlindedPath Tests -------------------------------------------------blindingCreatePathTests :: TestTree-blindingCreatePathTests = testGroup "createBlindedPath" [-    testCase "create path with 2 hops" $ do-      let nodes = [(testNodePubKey1, emptyHopData),-                   (testNodePubKey2, sampleHopData)]-      case createBlindedPath testSeed nodes of-        Left err -> assertFailure $ "createBlindedPath failed: " ++ show err-        Right path -> do-          length (bpBlindedHops path) @?= 2-          let serialized = Secp256k1.serialize_point (bpBlindingKey path)-          BS.length serialized @?= 33--  , testCase "create path with 3 hops" $ do-      let nodes = [ (testNodePubKey1, emptyHopData)-                  , (testNodePubKey2, hopDataWithNextNode)-                  , (testNodePubKey3, sampleHopData)-                  ]-      case createBlindedPath testSeed nodes of-        Left err -> assertFailure $ "createBlindedPath failed: " ++ show err-        Right path -> length (bpBlindedHops path) @?= 3--  , testCase "all blinded node IDs are 33 bytes" $ do-      let nodes = [ (testNodePubKey1, emptyHopData)-                  , (testNodePubKey2, emptyHopData)-                  , (testNodePubKey3, emptyHopData)-                  ]-      case createBlindedPath testSeed nodes of-        Left err -> assertFailure $ "createBlindedPath failed: " ++ show err-        Right path -> do-          let blindedIds = map bhBlindedNodeId (bpBlindedHops path)-          mapM_ (\bid -> BS.length bid @?= 33) blindedIds--  , testCase "empty path returns EmptyPath error" $ do-      case createBlindedPath testSeed [] of-        Left EmptyPath -> return ()-        Left err -> assertFailure $ "Expected EmptyPath, got: " ++ show err-        Right _ -> assertFailure "Expected error, got success"--  , testCase "invalid seed returns InvalidSeed error" $ do-      let invalidSeed = BS.pack [1..16]-          nodes = [(testNodePubKey1, emptyHopData)]-      case createBlindedPath invalidSeed nodes of-        Left InvalidSeed -> return ()-        Left err -> assertFailure $ "Expected InvalidSeed, got: " ++ show err-        Right _ -> assertFailure "Expected error, got success"--  , testCase "createBlindedPath is deterministic" $ do-      let nodes = [(testNodePubKey1, emptyHopData),-                   (testNodePubKey2, sampleHopData)]-          result1 = createBlindedPath testSeed nodes-          result2 = createBlindedPath testSeed nodes-      result1 @?= result2-  ]---- 6. processBlindedHop Tests -------------------------------------------------blindingProcessHopTests :: TestTree-blindingProcessHopTests = testGroup "processBlindedHop" [-    testCase "process first hop decrypts correctly" $ do-      let nodes = [(testNodePubKey1, sampleHopData),-                   (testNodePubKey2, emptyHopData)]-      case createBlindedPath testSeed nodes of-        Left err -> assertFailure $ "createBlindedPath failed: " ++ show err-        Right path -> case bpBlindedHops path of-          firstHop : _ -> do-            let pathKey = bpBlindingKey path-            case processBlindedHop testNodeSecKey1 pathKey-                   (bhEncryptedData firstHop) of-              Left err -> assertFailure $-                "processBlindedHop failed: " ++ show err-              Right (decryptedData, _) ->-                decryptedData @?= sampleHopData-          [] -> assertFailure "expected non-empty hops"--  , testCase "process hop chain correctly" $ do-      let nodes = [ (testNodePubKey1, emptyHopData)-                  , (testNodePubKey2, sampleHopData)-                  , (testNodePubKey3, hopDataWithNextNode)-                  ]-      case createBlindedPath testSeed nodes of-        Left err -> assertFailure $ "createBlindedPath failed: " ++ show err-        Right path -> case bpBlindedHops path of-          [hop1, hop2, hop3] -> do-            let pathKey1 = bpBlindingKey path-            case processBlindedHop testNodeSecKey1 pathKey1-                   (bhEncryptedData hop1) of-              Left err -> assertFailure $-                "processBlindedHop hop1 failed: " ++ show err-              Right (data1, pathKey2) -> do-                data1 @?= emptyHopData-                case processBlindedHop testNodeSecKey2 pathKey2-                       (bhEncryptedData hop2) of-                  Left err -> assertFailure $-                    "processBlindedHop hop2 failed: "-                      ++ show err-                  Right (data2, pathKey3) -> do-                    data2 @?= sampleHopData-                    case processBlindedHop testNodeSecKey3-                           pathKey3-                           (bhEncryptedData hop3) of-                      Left err -> assertFailure $-                        "processBlindedHop hop3 failed: "-                          ++ show err-                      Right (data3, _) ->-                        data3 @?= hopDataWithNextNode-          _ -> assertFailure "expected 3 blinded hops"--  , testCase "process hop with wrong node key fails" $ do-      let nodes = [(testNodePubKey1, sampleHopData)]-      case createBlindedPath testSeed nodes of-        Left err -> assertFailure $-          "createBlindedPath failed: " ++ show err-        Right path -> case bpBlindedHops path of-          firstHop : _ -> do-            let pathKey = bpBlindingKey path-            case processBlindedHop testNodeSecKey2 pathKey-                   (bhEncryptedData firstHop) of-              Left _ -> return ()-              Right (decryptedData, _) ->-                assertBool "should not decrypt correctly"-                  (decryptedData /= sampleHopData)-          [] -> assertFailure "expected non-empty hops"--  , testCase "next path key is valid point" $ do-      let nodes = [(testNodePubKey1, emptyHopData),-                   (testNodePubKey2, emptyHopData)]-      case createBlindedPath testSeed nodes of-        Left err -> assertFailure $-          "createBlindedPath failed: " ++ show err-        Right path -> case bpBlindedHops path of-          firstHop : _ -> do-            let pathKey = bpBlindingKey path-            case processBlindedHop testNodeSecKey1 pathKey-                   (bhEncryptedData firstHop) of-              Left err -> assertFailure $-                "processBlindedHop failed: " ++ show err-              Right (_, nextPathKey) -> do-                let serialized =-                      Secp256k1.serialize_point nextPathKey-                BS.length serialized @?= 33-          [] -> assertFailure "expected non-empty hops"--  , testCase "next_path_key_override is used when present" $ do-      let overrideKey = Secp256k1.serialize_point testNodePubKey3-          hopDataWithOverride' = emptyHopData-            { bhdNextPathKeyOverride = Just overrideKey }-          nodes = [(testNodePubKey1, hopDataWithOverride'),-                   (testNodePubKey2, emptyHopData)]-      case createBlindedPath testSeed nodes of-        Left err -> assertFailure $-          "createBlindedPath failed: " ++ show err-        Right path -> case bpBlindedHops path of-          firstHop : _ -> do-            let pathKey = bpBlindingKey path-            case processBlindedHop testNodeSecKey1 pathKey-                   (bhEncryptedData firstHop) of-              Left err -> assertFailure $-                "processBlindedHop failed: " ++ show err-              Right (decryptedData, nextPathKey) -> do-                bhdNextPathKeyOverride decryptedData-                  @?= Just overrideKey-                nextPathKey @?= testNodePubKey3-          [] -> assertFailure "expected non-empty hops"-  ]+import Control.Monad (forM_)+import qualified Crypto.Cipher.ChaCha20 as ChaCha+import qualified Crypto.Curve.Secp256k1 as Secp256k1+import qualified Crypto.Hash.SHA256 as SHA256+import Data.Bits (xor)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Base16 as B16+import Data.List (nubBy)+import Data.Word (Word8, Word16, Word32, Word64)+import qualified Lightning.Protocol.BOLT1 as BOLT1+import Lightning.Protocol.BOLT4+import qualified Lightning.Protocol.BOLT9 as BOLT9+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck+import qualified Vectors as V++main :: IO ()+main = defaultMain $ testGroup "ppad-bolt4" [+    key_tests+  , codec_tests+  , failure_tests+  , construct_tests+  , process_tests+  , blinding_tests+  , error_tests+  , testGroup "spec vectors" [+        onion_vectors+      , error_vectors+      , trace_vectors+      , route_blinding_vectors+      , blinded_payment_vectors+      ]+  , properties+  ]++-- helpers --------------------------------------------------------------------++demand :: String -> Maybe a -> IO a+demand _ (Just a) = pure a+demand msg Nothing = assertFailure msg++right :: Show e => String -> Either e a -> IO a+right _ (Right a) = pure a+right msg (Left e) = assertFailure (msg ++ ": " ++ show e)++unhex :: BS.ByteString -> IO BS.ByteString+unhex = demand "invalid hex" . B16.decode++hex_key :: BS.ByteString -> IO SecretKey+hex_key h = unhex h >>= demand "invalid secret key" . secret_key++hex_point :: BS.ByteString -> IO BOLT1.Point+hex_point h = unhex h >>= demand "invalid point" . BOLT1.point++hex_secret :: BS.ByteString -> IO SharedSecret+hex_secret h = unhex h >>= demand "invalid shared secret" . shared_secret++-- strip a BigSize length prefix, checking that it is exact+unprefix :: BS.ByteString -> IO BS.ByteString+unprefix bs = do+  (len, body) <- demand "invalid length prefix" (BOLT1.decode_bigsize bs)+  assertEqual "length prefix" (fromIntegral len) (BS.length body)+  pure body++msat :: Word64 -> IO BOLT1.MilliSatoshi+msat = demand "invalid amount" . BOLT1.milli_satoshi++scid :: Word32 -> Word32 -> Word16 -> IO BOLT1.ShortChannelId+scid b t o = demand "invalid scid" (BOLT1.short_channel_id b t o)++tlvs :: [BOLT1.TlvRecord] -> IO BOLT1.TlvStream+tlvs = demand "invalid tlv stream" . BOLT1.tlv_stream++no_tlvs :: BOLT1.TlvStream+no_tlvs = BOLT1.empty_tlv_stream++key :: Word8 -> IO SecretKey+key b = demand "invalid secret key" (secret_key (BS.replicate 32 b))++ad :: BS.ByteString+ad = BS.replicate 32 0x42++-- a point with a valid prefix but not on the curve+off_curve :: IO BOLT1.Point+off_curve =+  case [ p | x <- [0 .. 255 :: Word8]+           , let bs = BS.cons 0x02 (BS.replicate 31 0 <> BS.singleton x)+           , Secp256k1.parse_point bs == Nothing+           , Just p <- [BOLT1.point bs] ] of+    p : _ -> pure p+    []    -> assertFailure "no off-curve point found"++-- a payload with the given outgoing_cltv_value+cltv_payload :: Word32 -> HopPayload+cltv_payload c = empty_hop_payload { hp_outgoing_cltv_value = Just c }++expect_process+  :: ProcessError -> Either ProcessError ProcessResult -> Assertion+expect_process e r = case r of+  Left e' -> e' @?= e+  Right _ -> assertFailure ("expected " ++ show e)++forward :: Either ProcessError ProcessResult -> IO ForwardInfo+forward r = case r of+  Right (Forward f) -> pure f+  Right (Receive _) -> assertFailure "expected Forward, got Receive"+  Left e -> assertFailure ("process: " ++ show e)++receive :: Either ProcessError ProcessResult -> IO ReceiveInfo+receive r = case r of+  Right (Receive i) -> pure i+  Right (Forward _) -> assertFailure "expected Receive, got Forward"+  Left e -> assertFailure ("process: " ++ show e)++-- A single-hop onion, built independently of the library, whose+-- decrypted hop_payloads is the given 1300-byte plaintext.+raw_onion :: SecretKey -> BS.ByteString -> IO OnionPacket+raw_onion node plain = do+  let session = BS.replicate 32 0x77+  e <- demand "session" (Secp256k1.parse_int256 session)+  epub <- demand "session pub" (Secp256k1.derive_pub e)+  n <- demand "node pub"+         (Secp256k1.parse_point (BOLT1.un_point (public_key node)))+  pt <- demand "ecdh" (Secp256k1.mul n e)+  let ss = SHA256.hash (Secp256k1.serialize_point pt)+      derive l = let SHA256.MAC k = SHA256.hmac l ss in k+  stream <- right "chacha"+    (ChaCha.cipher (derive "rho") 0 (BS.replicate 12 0)+       (BS.replicate 1300 0))+  let body = BS.packZipWith xor plain stream+      SHA256.MAC mac = SHA256.hmac (derive "mu") (body <> ad)+  pk <- demand "point" (BOLT1.point (Secp256k1.serialize_point epub))+  OnionPacket 0 pk+    <$> demand "hop_payloads" (hop_payloads body)+    <*> demand "hmac" (hmac32 mac)++-- pad a plaintext region to 1300 bytes+pad1300 :: BS.ByteString -> BS.ByteString+pad1300 bs = bs <> BS.replicate (1300 - BS.length bs) 0++-- keys -----------------------------------------------------------------------++key_tests :: TestTree+key_tests = testGroup "keys" [+    testCase "secret_key accepts 32-byte keys in range" $ do+      _ <- key 0x01+      sk <- demand "n - 1" . secret_key =<< unhex+        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140"+      BS.length (BOLT1.un_point (public_key sk)) @?= 33+  , testCase "secret_key rejects other lengths" $ do+      assertNone (secret_key (BS.replicate 31 0x01))+      assertNone (secret_key (BS.replicate 33 0x01))+      assertNone (secret_key (BS.cons 0x00 (BS.replicate 32 0x01)))+      assertNone (secret_key BS.empty)+  , testCase "secret_key rejects zero and keys >= n" $ do+      assertNone (secret_key (BS.replicate 32 0x00))+      n <- unhex+        "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"+      assertNone (secret_key n)+      assertNone (secret_key (BS.replicate 32 0xff))+  , testCase "public_key matches secp256k1" $ do+      sk <- key 0x41+      expected <- unhex V.onionPubKey0+      BOLT1.un_point (public_key sk) @?= expected+  , testCase "shared_secret requires 32 bytes" $ do+      assertNone (shared_secret (BS.replicate 31 0))+      assertNone (shared_secret (BS.replicate 33 0))+      ss <- demand "ss" (shared_secret (BS.replicate 32 7))+      un_shared_secret ss @?= BS.replicate 32 7+  , testCase "shared secrets compare by value" $ do+      a <- demand "a" (shared_secret (BS.replicate 32 7))+      b <- demand "b" (shared_secret (BS.replicate 32 7))+      c <- demand "c" (shared_secret (BS.cons 8 (BS.replicate 31 7)))+      assertBool "equal" (a == b)+      assertBool "not equal" (a /= c)+  , testCase "secrets don't show their bytes" $ do+      sk <- key 0x41+      ss <- demand "ss" (shared_secret (BS.replicate 32 0x41))+      ps <- demand "ps" (payment_secret (BS.replicate 32 0x41))+      show sk @?= "SecretKey <redacted>"+      show ss @?= "SharedSecret <redacted>"+      show ps @?= "PaymentSecret <redacted>"+  ]+  where+    assertNone m = case m of+      Nothing -> pure ()+      Just _  -> assertFailure "expected Nothing"++-- codecs ---------------------------------------------------------------------++codec_tests :: TestTree+codec_tests = testGroup "codecs" [+    testCase "decode_onion_packet rejects other lengths" $ do+      decode_onion_packet (BS.replicate 1365 0) @?= Left InvalidLength+      decode_onion_packet (BS.replicate 1367 0) @?= Left InvalidLength+      decode_onion_packet BS.empty @?= Left InvalidLength+  , testCase "decode_onion_packet rejects a bad point prefix" $ do+      let bs = BS.concat [ BS.singleton 0, BS.singleton 0x04+                         , BS.replicate 1364 0 ]+      decode_onion_packet bs @?= Left InvalidPoint+  , testCase "decode_onion_packet keeps the version byte" $ do+      let bs = BS.concat [ BS.singleton 7, BS.singleton 0x02+                         , BS.replicate 1364 0 ]+      pkt <- right "decode" (decode_onion_packet bs)+      onion_version pkt @?= 7+      encode_onion_packet pkt @?= bs+  , testCase "smart constructors check lengths" $ do+      hop_payloads (BS.replicate 1299 0) @?= Nothing+      hop_payloads (BS.replicate 1301 0) @?= Nothing+      hmac32 (BS.replicate 31 0) @?= Nothing+      onion_hash (BS.replicate 33 0) @?= Nothing+      case payment_secret (BS.replicate 31 0) of+        Nothing -> pure ()+        Just _  -> assertFailure "short payment secret"++  , testCase "hop payload: all typed fields" $ do+      amt <- msat 1000+      tot <- msat 5000+      s <- scid 1 2 3+      ps <- demand "ps" (payment_secret (BS.replicate 32 0xaa))+      pk <- hex_point V.onionPubKey0+      ex <- tlvs [BOLT1.TlvRecord 19 "x"]+      let hp = HopPayload+            { hp_amt_to_forward      = Just amt+            , hp_outgoing_cltv_value = Just 500000+            , hp_short_channel_id    = Just s+            , hp_payment_data        = Just (PaymentData ps tot)+            , hp_encrypted_data      = Just "enc"+            , hp_current_path_key    = Just pk+            , hp_payment_metadata    = Just "meta"+            , hp_total_amount_msat   = Just tot+            , hp_extra               = ex+            }+      bs <- right "encode" (encode_hop_payload hp)+      expected <- unhex $ BS.concat+        [ "020203e8", "040307a120", "06080000010000020003"+        , "0822", BS.concat (replicate 32 "aa"), "1388"+        , "0a03656e63", "0c21", V.onionPubKey0, "10046d657461"+        , "12021388", "130178" ]+      bs @?= expected+      decode_hop_payload bs @?= Right hp+  , testCase "hop payload: zero values encode minimally" $ do+      zero <- msat 0+      let hp = empty_hop_payload { hp_amt_to_forward = Just zero+                                 , hp_outgoing_cltv_value = Just 0 }+      encode_hop_payload hp @?= Right "\STX\NUL\EOT\NUL"+      decode_hop_payload "\STX\NUL\EOT\NUL" @?= Right hp+  , testCase "hop payload: unknown odd types are kept" $ do+      hp <- right "decode" (decode_hop_payload "\STX\SOH\SOH\ETX\NUL")+      ex <- tlvs [BOLT1.TlvRecord 3 ""]+      hp_extra hp @?= ex+      encode_hop_payload hp @?= Right "\STX\SOH\SOH\ETX\NUL"+  , testCase "hop payload: rejects unknown even types" $ do+      decode_hop_payload "\DC4\NUL" @?=+        Left (InvalidTlvStream (BOLT1.TlvUnknownEvenType 20))+      decode_hop_payload "\SO\NUL" @?=+        Left (InvalidTlvStream (BOLT1.TlvUnknownEvenType 14))+  , testCase "hop payload: rejects malformed streams" $ do+      decode_hop_payload "\EOT\SOH\SOH\STX\SOH\SOH" @?=+        Left (InvalidTlvStream BOLT1.TlvNotStrictlyIncreasing)+      decode_hop_payload "\STX\SOH\SOH\STX\SOH\SOH" @?=+        Left (InvalidTlvStream BOLT1.TlvNotStrictlyIncreasing)+      decode_hop_payload "\STX\ENQ\SOH" @?=+        Left (InvalidTlvStream BOLT1.TlvTruncated)+      decode_hop_payload "\STX\255\255\255\255\255\255\255\255\255" @?=+        Left (InvalidTlvStream BOLT1.TlvTruncated)+      decode_hop_payload "\253\NUL\STX\NUL" @?=+        Left (InvalidTlvStream BOLT1.TlvNonMinimalBigSize)+  , testCase "hop payload: rejects malformed values" $ do+      -- non-minimal tu64 and tu32+      decode_hop_payload "\STX\STX\NUL\SOH" @?= Left (InvalidTlvValue 2)+      decode_hop_payload "\EOT\STX\NUL\SOH" @?= Left (InvalidTlvValue 4)+      -- tu32 longer than 4 bytes+      decode_hop_payload "\EOT\ENQ\SOH\SOH\SOH\SOH\SOH" @?=+        Left (InvalidTlvValue 4)+      -- amount above 21M BTC+      decode_hop_payload "\STX\b\255\255\255\255\255\255\255\255" @?=+        Left (InvalidTlvValue 2)+      -- short_channel_id of 7 bytes+      decode_hop_payload (BS.pack [6, 7] <> BS.replicate 7 0) @?=+        Left (InvalidTlvValue 6)+      -- payment_data with a 31-byte secret+      decode_hop_payload (BS.pack [8, 31] <> BS.replicate 31 0) @?=+        Left (InvalidTlvValue 8)+      -- current_path_key of the wrong length or prefix+      decode_hop_payload (BS.pack [12, 32] <> BS.replicate 32 2) @?=+        Left (InvalidTlvValue 12)+      decode_hop_payload (BS.pack [12, 33, 4] <> BS.replicate 32 2) @?=+        Left (InvalidTlvValue 12)+      -- total_amount_msat non-minimal+      decode_hop_payload "\DC2\STX\NUL\SOH" @?= Left (InvalidTlvValue 18)+  , testCase "hop payload: extra records may not have known types" $ do+      ex <- tlvs [BOLT1.TlvRecord 16 "meta"]+      encode_hop_payload empty_hop_payload { hp_extra = ex } @?=+        Left ConflictingTlv+      ex' <- tlvs [BOLT1.TlvRecord 2 "\SOH"]+      encode_hop_payload (cltv_payload 1) { hp_extra = ex' } @?=+        Left ConflictingTlv+  , testCase "hop payload: extra records may have unknown even types" $ do+      ex <- tlvs [BOLT1.TlvRecord 5482373484 (BS.replicate 32 1)]+      bs <- right "encode" (encode_hop_payload (cltv_payload 1)+                              { hp_extra = ex })+      decode_hop_payload bs @?=+        Left (InvalidTlvStream (BOLT1.TlvUnknownEvenType 5482373484))++  , testCase "blinded hop data: all typed fields" $ do+      s <- scid 0 0 1729+      pk <- hex_point V.rbDavePathKey+      m <- msat 1500+      ex <- tlvs [BOLT1.TlvRecord 561 "\DC24V"]+      let d = BlindedHopData+            { bhd_padding                = Just (BS.replicate 3 0)+            , bhd_short_channel_id       = Just s+            , bhd_next_node_id           = Just pk+            , bhd_path_id                = Just "id"+            , bhd_next_path_key_override = Just pk+            , bhd_payment_relay          = Just (PaymentRelay 36 150 10000)+            , bhd_payment_constraints    =+                Just (PaymentConstraints 748005 m)+            , bhd_allowed_features       = Just (BOLT9.parse "\STX")+            , bhd_extra                  = ex+            }+      bs <- right "encode" (encode_blinded_hop_data d)+      expected <- unhex $ BS.concat+        [ "0103000000", "020800000000000006c1", "0421", V.rbDavePathKey+        , "06026964", "0821", V.rbDavePathKey, "0a080024000000962710"+        , "0c06000b69e505dc", "0e0102", "fd023103123456" ]+      bs @?= expected+      decode_blinded_hop_data bs @?= Right d+  , testCase "blinded hop data: unknown types" $ do+      decode_blinded_hop_data "\DLE\NUL" @?=+        Left (InvalidTlvStream (BOLT1.TlvUnknownEvenType 16))+      d <- right "decode" (decode_blinded_hop_data "\ETX\NUL")+      ex <- tlvs [BOLT1.TlvRecord 3 ""]+      bhd_extra d @?= ex+  , testCase "blinded hop data: rejects malformed values" $ do+      -- payment_relay shorter than its fixed fields+      decode_blinded_hop_data "\n\ENQ\NUL\NUL\NUL\NUL\NUL" @?=+        Left (InvalidTlvValue 10)+      -- payment_relay with a non-minimal fee_base_msat+      decode_blinded_hop_data "\n\a\NUL\NUL\NUL\NUL\NUL\NUL\NUL" @?=+        Left (InvalidTlvValue 10)+      -- payment_constraints shorter than its fixed fields+      decode_blinded_hop_data "\f\ETX\NUL\NUL\NUL" @?=+        Left (InvalidTlvValue 12)+      -- next_node_id and next_path_key_override of the wrong length+      decode_blinded_hop_data (BS.pack [4, 2, 2, 2]) @?=+        Left (InvalidTlvValue 4)+      decode_blinded_hop_data (BS.pack [8, 2, 2, 2]) @?=+        Left (InvalidTlvValue 8)+      -- short_channel_id of 9 bytes+      decode_blinded_hop_data (BS.pack [2, 9] <> BS.replicate 9 0) @?=+        Left (InvalidTlvValue 2)+  , testCase "blinded hop data: extra records may not have known types" $ do+      ex <- tlvs [BOLT1.TlvRecord 1 ""]+      encode_blinded_hop_data empty_blinded_hop_data { bhd_extra = ex } @?=+        Left ConflictingTlv+  ]++-- failure messages -----------------------------------------------------------++failure_tests :: TestTree+failure_tests = testGroup "failure messages" [+    testCase "encodings and codes of every failure" $ do+      h <- demand "hash" (onion_hash (BS.replicate 32 0xab))+      m <- msat 100+      let hh = BS.concat (replicate 32 "ab")+          cases =+            [ (TemporaryNodeFailure, "2002")+            , (PermanentNodeFailure, "6002")+            , (RequiredNodeFeatureMissing, "6003")+            , (InvalidOnionVersion h, "c004" <> hh)+            , (InvalidOnionHmac h, "c005" <> hh)+            , (InvalidOnionKey h, "c006" <> hh)+            , (TemporaryChannelFailure "cu", "100700026375")+            , (PermanentChannelFailure, "4008")+            , (RequiredChannelFeatureMissing, "4009")+            , (UnknownNextPeer, "400a")+            , (AmountBelowMinimum m "", "100b00000000000000640000")+            , (FeeInsufficient m "cu", "100c000000000000006400026375")+            , (IncorrectCltvExpiry 800000 "", "100d000c35000000")+            , (ExpiryTooSoon "", "100e0000")+            , ( IncorrectOrUnknownPaymentDetails m 800000+              , "400f0000000000000064000c3500" )+            , (FinalIncorrectCltvExpiry 800000, "0012000c3500")+            , (FinalIncorrectHtlcAmount m, "00130000000000000064")+            , (ChannelDisabled 0 "", "101400000000")+            , (ExpiryTooFar, "0015")+            , (InvalidOnionPayload Nothing, "4016")+            , (InvalidOnionPayload (Just (2, 7)), "401602 0007")+            , (MppTimeout, "0017")+            , (InvalidOnionBlinding h, "c018" <> hh)+            , (UnknownFailure 0x0042 "data", "004264617461")+            ]+      forM_ cases $ \(f, hx) -> do+        bs <- unhex (BS.filter (/= 0x20) hx)+        let fm = FailureMessage f no_tlvs+        encode_failure_message fm @?= Right bs+        decode_failure_message bs @?= Right fm+        Just (failure_code f) @?= fmap fst (BOLT1.decode_u16 bs)+  , testCase "flags" $ do+      h <- demand "hash" (onion_hash (BS.replicate 32 0))+      let flags f = (is_badonion f, is_perm f, is_node f, is_update f)+      flags TemporaryNodeFailure @?= (False, False, True, False)+      flags PermanentNodeFailure @?= (False, True, True, False)+      flags (InvalidOnionBlinding h) @?= (True, True, False, False)+      flags (ExpiryTooSoon "") @?= (False, False, False, True)+      flags MppTimeout @?= (False, False, False, False)+      flags (UnknownFailure 0xf000 "") @?= (True, True, True, True)+  , testCase "spec example: incorrect_or_unknown_payment_details" $ do+      bs <- unhex "400f0000000000000064000c3500"+      m <- msat 100+      decode_failure_message bs @?= Right+        (FailureMessage (IncorrectOrUnknownPaymentDetails m 800000) no_tlvs)+  , testCase "a trailing TLV stream is kept" $ do+      ex <- tlvs [BOLT1.TlvRecord 34001 (BS.replicate 3 0x80)]+      let fm = FailureMessage TemporaryNodeFailure ex+      bs <- right "encode" (encode_failure_message fm)+      bs @?= "\x20\x02\xfd\x84\xd1\x03\x80\x80\x80"+      decode_failure_message bs @?= Right fm+  , testCase "other trailing bytes are ignored" $ do+      let ignored f bs = decode_failure_message bs @?=+            Right (FailureMessage f no_tlvs)+      ignored TemporaryNodeFailure "\x20\x02\xff"+      -- an even type: not a valid stream without known types+      ignored TemporaryNodeFailure "\x20\x02\x02\x00"+      ignored (FinalIncorrectCltvExpiry 1) "\NUL\DC2\NUL\NUL\NUL\SOH\SOH"+  , testCase "truncated data is rejected" $ do+      decode_failure_message "" @?= Left InvalidLength+      decode_failure_message "\x20" @?= Left InvalidLength+      decode_failure_message "\xc0\x05\x00" @?=+        Left (InvalidFailureData 0xc005)+      decode_failure_message "\x10\x07\x00\x02\x00" @?=+        Left (InvalidFailureData 0x1007)+      decode_failure_message "\x40\x0f\x00\x00\x00\x00\x00\x00\x00\x64" @?=+        Left (InvalidFailureData 0x400f)+      decode_failure_message "\x00\x13\x00" @?=+        Left (InvalidFailureData 0x0013)+      decode_failure_message "\x10\x14\x00\x00\x00" @?=+        Left (InvalidFailureData 0x1014)+  , testCase "amounts above 21M BTC are rejected" $+      decode_failure_message "\x00\x13\xff\xff\xff\xff\xff\xff\xff\xff" @?=+        Left (InvalidFailureData 0x0013)+  , testCase "unknown codes keep their data" $+      decode_failure_message "\x12\x34\x01\x00" @?=+        Right (FailureMessage (UnknownFailure 0x1234 "\x01\x00") no_tlvs)+  , testCase "channel_update longer than 65535 bytes" $+      encode_failure_message+        (FailureMessage (ExpiryTooSoon (BS.replicate 65536 0)) no_tlvs)+        @?= Left FieldTooLong+  ]++-- construct ------------------------------------------------------------------++construct_tests :: TestTree+construct_tests = testGroup "construct" [+    testCase "empty route" $ do+      sk <- key 0x41+      assertConstructError EmptyRoute (construct sk [] ad)+  , testCase "more than 20 hops" $ do+      sk <- key 0x41+      node <- key 0x42+      let hop = Hop (public_key node) (cltv_payload 1)+      assertConstructError TooManyHops (construct sk (replicate 21 hop) ad)+      case construct sk (replicate 20 hop) ad of+        Right (_, sss) -> length sss @?= 20+        Left e -> assertFailure (show e)+  , testCase "invalid hop public key" $ do+      sk <- key 0x41+      node <- key 0x42+      bad <- off_curve+      let good = Hop (public_key node) (cltv_payload 1)+      assertConstructError (InvalidHopPubKey 1)+        (construct sk [good, Hop bad (cltv_payload 1), good] ad)+  , testCase "payloads under 2 bytes" $ do+      sk <- key 0x41+      node <- key 0x42+      let good = Hop (public_key node) (cltv_payload 1)+      assertConstructError (InvalidHopPayload 1)+        (construct sk [good, Hop (public_key node) empty_hop_payload] ad)+  , testCase "unencodable payload" $ do+      sk <- key 0x41+      node <- key 0x42+      ex <- tlvs [BOLT1.TlvRecord 4 "\SOH"]+      let bad = Hop (public_key node) empty_hop_payload { hp_extra = ex }+      assertConstructError (InvalidHopPayload 0) (construct sk [bad] ad)+  , testCase "payloads over 1300 bytes" $ do+      sk <- key 0x41+      node <- key 0x42+      -- a 1265-byte payload exactly fills hop_payloads+      ex <- tlvs [BOLT1.TlvRecord 1 (BS.replicate 1261 0)]+      let full = Hop (public_key node) empty_hop_payload { hp_extra = ex }+      case construct sk [full] ad of+        Right _ -> pure ()+        Left e -> assertFailure (show e)+      ex' <- tlvs [BOLT1.TlvRecord 1 (BS.replicate 1262 0)]+      let over = Hop (public_key node) empty_hop_payload { hp_extra = ex' }+      assertConstructError (PayloadsTooLarge 1301) (construct sk [over] ad)+      let hops = replicate 2 (Hop (public_key node) (cltv_payload 1))+      assertConstructError (PayloadsTooLarge 1372)+        (construct sk (full : hops) ad)+  ]+  where+    assertConstructError e r = case r of+      Left e' -> e' @?= e+      Right _ -> assertFailure ("expected " ++ show e)++-- process --------------------------------------------------------------------++process_tests :: TestTree+process_tests = testGroup "process" [+    testCase "rejects versions other than 0" $ do+      (node, pkt) <- simple_onion+      expect_process (InvalidVersion 1)+        (process node pkt { onion_version = 1 } ad Nothing)+  , testCase "rejects a public key not on the curve" $ do+      (node, pkt) <- simple_onion+      bad <- off_curve+      expect_process InvalidPublicKey+        (process node pkt { onion_public_key = bad } ad Nothing)+  , testCase "rejects a wrong HMAC" $ do+      (node, pkt) <- simple_onion+      flipped <- demand "flip" $+        case BS.uncons (un_hop_payloads (onion_hop_payloads pkt)) of+          Just (h, t) -> hop_payloads (BS.cons (h `xor` 1) t)+          Nothing     -> Nothing+      expect_process HmacMismatch+        (process node pkt { onion_hop_payloads = flipped } ad Nothing)+      mac <- demand "mac" (hmac32 (BS.replicate 32 0))+      expect_process HmacMismatch+        (process node pkt { onion_hmac = mac } ad Nothing)+  , testCase "rejects other associated data" $ do+      (node, pkt) <- simple_onion+      expect_process HmacMismatch+        (process node pkt (BS.replicate 32 0) Nothing)+  , testCase "rejects other node keys" $ do+      (_, pkt) <- simple_onion+      other <- key 0x44+      expect_process HmacMismatch (process other pkt ad Nothing)+  , testCase "rejects payload lengths 0 and 1" $ do+      node <- key 0x42+      p0 <- raw_onion node (pad1300 "\NUL")+      expect_process InvalidPayloadLength (process node p0 ad Nothing)+      p1 <- raw_onion node (pad1300 "\SOH\SOH")+      expect_process InvalidPayloadLength (process node p1 ad Nothing)+  , testCase "rejects a malformed length prefix" $ do+      node <- key 0x42+      p <- raw_onion node (pad1300 "\253\NUL\DLE")+      expect_process InvalidPayloadLength (process node p ad Nothing)+  , testCase "rejects payloads past the end of hop_payloads" $ do+      node <- key 0x42+      -- 3-byte prefix, 1265-byte payload and 32-byte HMAC fill it+      let rec n = BS.concat [ "\SOH\253", BOLT1.encode_u16 n+                            , BS.replicate (fromIntegral n) 0 ]+      ok <- raw_onion node (pad1300 ("\253\EOT\241" <> rec 1261))+      _ <- receive (process node ok ad Nothing)+      over <- raw_onion node (pad1300 ("\253\EOT\242" <> rec 1262))+      expect_process InvalidPayloadLength (process node over ad Nothing)+      -- a length reaching into the zero-extended stream+      far <- raw_onion node (pad1300 ("\253\a\208" <> rec 1261))+      expect_process InvalidPayloadLength (process node far ad Nothing)+      huge <- raw_onion node+                (pad1300 ("\255\128\NUL\NUL\NUL\NUL\NUL\NUL\NUL" <> rec 1))+      expect_process InvalidPayloadLength (process node huge ad Nothing)+  , testCase "rejects malformed payloads" $ do+      node <- key 0x42+      let bad pl = raw_onion node+            (pad1300 (BOLT1.encode_bigsize (fromIntegral (BS.length pl))+                        <> pl))+      p0 <- bad "\EOT\SOH\SOH\STX\SOH\SOH"+      expect_process+        (InvalidPayload (InvalidTlvStream BOLT1.TlvNotStrictlyIncreasing))+        (process node p0 ad Nothing)+      p1 <- bad "\DC4\NUL"+      expect_process+        (InvalidPayload (InvalidTlvStream (BOLT1.TlvUnknownEvenType 20)))+        (process node p1 ad Nothing)+      p2 <- bad "\STX\STX\NUL\SOH"+      expect_process (InvalidPayload (InvalidTlvValue 2))+        (process node p2 ad Nothing)+  , testCase "the final hop receives" $ do+      (node, pkt) <- simple_onion+      info <- receive (process node pkt ad Nothing)+      rcv_payload info @?= cltv_payload 144+      rcv_blinded info @?= Nothing+  , testCase "forwarded packets are well formed" $ do+      sk <- key 0x41+      a <- key 0x42+      b <- key 0x43+      (pkt, sss) <- right "construct" $ construct sk+        [Hop (public_key a) (cltv_payload 1), Hop (public_key b)+           (cltv_payload 2)] ad+      fwd <- forward (process a pkt ad Nothing)+      let next = fwd_next_packet fwd+      onion_version next @?= 0+      decode_onion_packet (encode_onion_packet next) @?= Right next+      Just (fwd_shared_secret fwd) @?= safe_head sss+      rcv <- receive (process b next ad Nothing)+      rcv_payload rcv @?= cltv_payload 2+  , testCase "path keys without encrypted_recipient_data" $ do+      sk <- key 0x41+      alice <- key 0x42+      seed <- key 0x01+      path <- right "path" $+        create_blinded_path seed [(public_key alice, empty_blinded_hop_data)]+      bid <- blinded_id path 0+      let pk = bp_first_path_key path+      -- a path_key, onion encrypted to the blinded id+      (p0, _) <- right "construct" $+        construct sk [Hop bid (cltv_payload 1)] ad+      expect_process UnexpectedPathKey (process alice p0 ad (Just pk))+      -- a current_path_key+      let pl = (cltv_payload 1) { hp_current_path_key = Just pk }+      (p1, _) <- right "construct" $+        construct sk [Hop (public_key alice) pl] ad+      expect_process UnexpectedPathKey (process alice p1 ad Nothing)+  , testCase "both a path_key and a current_path_key" $ do+      sk <- key 0x41+      alice <- key 0x42+      seed <- key 0x01+      path <- right "path" $+        create_blinded_path seed [(public_key alice, empty_blinded_hop_data)]+      bid <- blinded_id path 0+      enc <- blinded_data path 0+      let pk = bp_first_path_key path+          pl = empty_hop_payload { hp_encrypted_data = Just enc+                                 , hp_current_path_key = Just pk }+      (pkt, _) <- right "construct" $ construct sk [Hop bid pl] ad+      expect_process UnexpectedPathKey (process alice pkt ad (Just pk))+  , testCase "encrypted_recipient_data without a path key" $ do+      sk <- key 0x41+      alice <- key 0x42+      let pl = empty_hop_payload { hp_encrypted_data = Just "data" }+      (pkt, _) <- right "construct" $+        construct sk [Hop (public_key alice) pl] ad+      expect_process MissingPathKey (process alice pkt ad Nothing)+  , testCase "undecryptable encrypted_recipient_data" $ do+      sk <- key 0x41+      alice <- key 0x42+      pk <- hex_point V.rbBobPathKey+      let pl = empty_hop_payload { hp_encrypted_data = Just+                                     (BS.replicate 40 0)+                                 , hp_current_path_key = Just pk }+      (pkt, _) <- right "construct" $+        construct sk [Hop (public_key alice) pl] ad+      expect_process InvalidRecipientData (process alice pkt ad Nothing)+  , testCase "invalid path keys" $ do+      sk <- key 0x41+      alice <- key 0x42+      bad <- off_curve+      let pl = empty_hop_payload { hp_encrypted_data = Just "data"+                                 , hp_current_path_key = Just bad }+      (pkt, _) <- right "construct" $+        construct sk [Hop (public_key alice) pl] ad+      expect_process InvalidPathKey (process alice pkt ad Nothing)+      (pkt', _) <- right "construct" $+        construct sk [Hop (public_key alice) (cltv_payload 1)] ad+      expect_process InvalidPathKey (process alice pkt' ad (Just bad))+  ]+  where+    simple_onion = do+      sk <- key 0x41+      node <- key 0x42+      (pkt, _) <- right "construct" $+        construct sk [Hop (public_key node) (cltv_payload 144)] ad+      pure (node, pkt)++three :: [a] -> IO (a, a, a)+three xs = case xs of+  [a, b, c] -> pure (a, b, c)+  _ -> assertFailure "expected three elements"++safe_head :: [a] -> Maybe a+safe_head xs = case xs of+  x : _ -> Just x+  []    -> Nothing++safe_index :: [a] -> Int -> Maybe a+safe_index xs i = safe_head (drop i xs)++blinded_id :: BlindedPath -> Int -> IO BOLT1.Point+blinded_id p i =+  demand "blinded hop" (bh_blinded_node_id <$> safe_index (bp_hops p) i)++blinded_data :: BlindedPath -> Int -> IO BS.ByteString+blinded_data p i =+  demand "blinded hop" (bh_encrypted_data <$> safe_index (bp_hops p) i)++-- route blinding -------------------------------------------------------------++blinding_tests :: TestTree+blinding_tests = testGroup "route blinding" [+    testCase "empty path" $ do+      seed <- key 0x01+      create_blinded_path seed [] @?= Left EmptyPath+  , testCase "invalid node id" $ do+      seed <- key 0x01+      alice <- key 0x42+      bad <- off_curve+      create_blinded_path seed+        [ (public_key alice, empty_blinded_hop_data)+        , (bad, empty_blinded_hop_data) ]+        @?= Left (InvalidNodeId 1)+  , testCase "unencodable hop data" $ do+      seed <- key 0x01+      alice <- key 0x42+      ex <- tlvs [BOLT1.TlvRecord 6 "id"]+      create_blinded_path seed+        [(public_key alice, empty_blinded_hop_data { bhd_extra = ex })]+        @?= Left (InvalidHopData 0 ConflictingTlv)+  , testCase "decrypt_recipient_data rejects bad input" $ do+      seed <- key 0x01+      alice <- key 0x42+      bob <- key 0x43+      path <- right "path" $+        create_blinded_path seed [(public_key alice, empty_blinded_hop_data)]+      enc <- blinded_data path 0+      let pk = bp_first_path_key path+      _ <- right "decrypt" (decrypt_recipient_data alice pk enc)+      decrypt_recipient_data bob pk enc @?= Left InvalidRecipientData+      decrypt_recipient_data alice pk (BS.take 15 enc) @?=+        Left InvalidRecipientData+      decrypt_recipient_data alice pk (BS.drop 1 enc) @?=+        Left InvalidRecipientData+      bad <- off_curve+      decrypt_recipient_data alice bad enc @?= Left InvalidPathKey+  , testCase "unknown even types in encrypted data are rejected" $ do+      seed <- key 0x01+      alice <- key 0x42+      ex <- tlvs [BOLT1.TlvRecord 20 ""]+      path <- right "path" $ create_blinded_path seed+        [(public_key alice, empty_blinded_hop_data { bhd_extra = ex })]+      enc <- blinded_data path 0+      decrypt_recipient_data alice (bp_first_path_key path) enc @?=+        Left InvalidRecipientData+  , testCase "a payment through a blinded route" $ do+      -- Carol creates a route Alice -> Bob -> Carol; Dave pays through it,+      -- reaching Alice unblinded with current_path_key.+      seed <- key 0x01+      session <- key 0x02+      alice <- key 0x42+      bob <- key 0x43+      carol <- key 0x44+      s1 <- scid 1 1 1+      s2 <- scid 2 2 2+      m <- msat 1+      let relay = Just (PaymentRelay 40 100 1000)+          cons = Just (PaymentConstraints 900000 m)+          alice_data = empty_blinded_hop_data+            { bhd_short_channel_id = Just s1, bhd_payment_relay = relay+            , bhd_payment_constraints = cons }+          bob_data = empty_blinded_hop_data+            { bhd_short_channel_id = Just s2, bhd_payment_relay = relay+            , bhd_payment_constraints = cons }+          carol_data = empty_blinded_hop_data+            { bhd_path_id = Just "invoice" }+      path <- right "path" $ create_blinded_path seed+        [ (public_key alice, alice_data), (public_key bob, bob_data)+        , (public_key carol, carol_data) ]+      bp_first_node_id path @?= public_key alice+      bp_first_path_key path @?= public_key seed+      (e0, e1, e2) <- three =<< mapM (blinded_data path) [0, 1, 2]+      (_, b1, b2) <- three =<< mapM (blinded_id path) [0, 1, 2]+      amt <- msat 5000+      let intro = empty_hop_payload+            { hp_encrypted_data = Just e0+            , hp_current_path_key = Just (bp_first_path_key path) }+          final = empty_hop_payload+            { hp_encrypted_data = Just e2, hp_amt_to_forward = Just amt+            , hp_outgoing_cltv_value = Just 800000+            , hp_total_amount_msat = Just amt }+          route = [ Hop (public_key alice) intro+                  , Hop b1 empty_hop_payload { hp_encrypted_data = Just e1 }+                  , Hop b2 final ]+      (pkt, sss) <- right "construct" (construct session route ad)+      fa <- forward (process alice pkt ad Nothing)+      ia <- demand "alice blinded" (fwd_blinded fa)+      bi_data ia @?= alice_data+      fb <- forward (process bob (fwd_next_packet fa) ad+                       (Just (bi_next_path_key ia)))+      ib <- demand "bob blinded" (fwd_blinded fb)+      bi_data ib @?= bob_data+      rc <- receive (process carol (fwd_next_packet fb) ad+                       (Just (bi_next_path_key ib)))+      ic <- demand "carol blinded" (rcv_blinded rc)+      bi_data ic @?= carol_data+      rcv_payload rc @?= final+      [fwd_shared_secret fa, fwd_shared_secret fb, rcv_shared_secret rc]+        @?= sss+      -- the wrong path key yields the wrong blinded key+      expect_process HmacMismatch+        (process bob (fwd_next_packet fa) ad+           (Just (bp_first_path_key path)))+  ]++-- returning errors -----------------------------------------------------------++error_tests :: TestTree+error_tests = testGroup "returning errors" [+    testCase "padding brings failure_len + pad_len to 256" $ do+      ss <- demand "ss" (shared_secret (BS.replicate 32 1))+      let fm = FailureMessage TemporaryNodeFailure no_tlvs+      pkt <- right "construct_error" (construct_error ss fm)+      let ErrorPacket raw = wrap_error ss pkt+      BS.length raw @?= 32 + 2 + 2 + 2 + 254+      BS.take 4 (BS.drop 32 raw) @?= "\NUL\STX\x20\x02"+      BS.take 2 (BS.drop 36 raw) @?= "\NUL\254"+  , testCase "long failure messages are not padded" $ do+      ss <- demand "ss" (shared_secret (BS.replicate 32 1))+      ex <- tlvs [BOLT1.TlvRecord 1 (BS.replicate 300 0)]+      let fm = FailureMessage TemporaryNodeFailure ex+      ErrorPacket raw <- wrap_error ss <$>+        right "construct_error" (construct_error ss fm)+      BS.length raw @?= 32 + 2 + 306 + 2+      BS.drop (32 + 2 + 306) raw @?= "\NUL\NUL"+  , testCase "failure messages over 65535 bytes" $ do+      ss <- demand "ss" (shared_secret (BS.replicate 32 1))+      ex <- tlvs [BOLT1.TlvRecord 1 (BS.replicate 65535 0)]+      construct_error ss (FailureMessage TemporaryNodeFailure ex)+        @?= Left FieldTooLong+  , testCase "attributes the failing hop" $ do+      sss <- mapM (\b -> demand "ss" (shared_secret (BS.replicate 32 b)))+               [1 .. 5]+      let fm = FailureMessage PermanentChannelFailure no_tlvs+      forM_ (zip [0 ..] sss) $ \(i, ss) -> do+        pkt <- right "construct_error" (construct_error ss fm)+        let back = foldr wrap_error pkt (take i sss)+        unwrap_error sss back @?= Attributed i fm+  , testCase "reports the hop of a malformed failure" $ do+      sss <- mapM (\b -> demand "ss" (shared_secret (BS.replicate 32 b)))+               [1 .. 3]+      ss2 <- demand "ss2" (safe_index sss 2)+      -- incorrect_cltv_expiry with no data+      let fm = FailureMessage (UnknownFailure 0x100d "") no_tlvs+      pkt <- right "construct_error" (construct_error ss2 fm)+      let back = foldr wrap_error pkt (take 2 sss)+      unwrap_error sss back @?= MalformedFailure 2+  , testCase "reports a malformed return packet" $ do+      ss <- demand "ss" (shared_secret (BS.replicate 32 1))+      -- a valid HMAC over a body whose failure_len overruns it+      let body = "\SOH\NUL\x20\x02"+          SHA256.MAC um = SHA256.hmac "um" (un_shared_secret ss)+          SHA256.MAC mac = SHA256.hmac um body+          pkt = wrap_error ss (ErrorPacket (mac <> body))+      unwrap_error [ss] pkt @?= MalformedFailure 0+  , testCase "unknown origin" $ do+      sss <- mapM (\b -> demand "ss" (shared_secret (BS.replicate 32 b)))+               [1 .. 3]+      other <- demand "ss" (shared_secret (BS.replicate 32 9))+      let fm = FailureMessage TemporaryNodeFailure no_tlvs+      pkt <- right "construct_error" (construct_error other fm)+      unwrap_error sss pkt @?= UnknownOrigin+      unwrap_error [] pkt @?= UnknownOrigin+      unwrap_error sss (ErrorPacket "short") @?= UnknownOrigin+  ]++-- spec vectors: onion-test.json ----------------------------------------------++onion_hops :: IO [(SecretKey, BOLT1.Point, BS.ByteString)]+onion_hops = mapM hop+    [ (V.onionNodeKey0, V.onionPubKey0, V.onionPayload0)+    , (V.onionNodeKey1, V.onionPubKey1, V.onionPayload1)+    , (V.onionNodeKey2, V.onionPubKey2, V.onionPayload2)+    , (V.onionNodeKey3, V.onionPubKey3, V.onionPayload3)+    , (V.onionNodeKey4, V.onionPubKey4, V.onionPayload4)+    ]+  where+    hop (k, p, pl) = (,,) <$> hex_key k <*> hex_point p+                          <*> (unhex pl >>= unprefix)++-- the shared secrets of the onion-test.json route, as listed in+-- onion-error-test.json+route_secrets :: IO [SharedSecret]+route_secrets = mapM hex_secret+  [ V.errSharedSecret0, V.errSharedSecret1, V.errSharedSecret2+  , V.errSharedSecret3, V.errSharedSecret4 ]++onion_vectors :: TestTree+onion_vectors = testGroup "onion-test.json" [+    testCase "construct reproduces the onion" $ do+      hops <- onion_hops+      sk <- hex_key V.onionSessionKey+      assoc <- unhex V.onionAssocData+      expected <- unhex V.onionPacket+      sss <- route_secrets+      route <- mapM (\(_, pub, body) -> do+                 hp <- right "decode_hop_payload" (decode_hop_payload body)+                 encode_hop_payload hp @?= Right body+                 pure (Hop pub hp)) hops+      (pkt, secrets) <- right "construct" (construct sk route assoc)+      encode_onion_packet pkt @?= expected+      secrets @?= sss+  , testCase "process peels the onion at every hop" $ do+      hops <- onion_hops+      assoc <- unhex V.onionAssocData+      sss <- route_secrets+      pkt <- unhex V.onionPacket >>= right "decode" . decode_onion_packet+      let go [] _ = assertFailure "no hops"+          go [((k, _, body), ss)] p = do+            r <- receive (process k p assoc Nothing)+            encode_hop_payload (rcv_payload r) @?= Right body+            rcv_shared_secret r @?= ss+          go (((k, _, body), ss) : rest) p = do+            f <- forward (process k p assoc Nothing)+            encode_hop_payload (fwd_payload f) @?= Right body+            fwd_shared_secret f @?= ss+            fwd_blinded f @?= Nothing+            go rest (fwd_next_packet f)+      go (zip hops sss) pkt+  ]++-- spec vectors: onion-error-test.json ----------------------------------------++-- the failing node's shared secret, and those of the hops back to the+-- origin, from hop 3 to hop 0+error_wrappers :: IO (SharedSecret, [SharedSecret])+error_wrappers = do+  sss <- route_secrets+  case reverse sss of+    ss4 : rest -> pure (ss4, rest)+    []         -> assertFailure "no shared secrets"++error_vectors :: TestTree+error_vectors = testGroup "onion-error-test.json" [+    testCase "failure message" $ do+      msg <- unhex V.errFailureMessage+      let fm = FailureMessage TemporaryNodeFailure no_tlvs+      encode_failure_message fm @?= Right msg+      decode_failure_message msg @?= Right fm+  , testCase "the failing node's return packet" $ do+      (ss4, _) <- error_wrappers+      payload <- unhex V.errPayload4+      let fm = FailureMessage TemporaryNodeFailure no_tlvs+      pkt <- right "construct_error" (construct_error ss4 fm)+      -- removing hop 4's obfuscation leaves the HMAC and payload+      let ErrorPacket raw = wrap_error ss4 pkt+      BS.drop 32 raw @?= payload+  , testCase "wrapping at each hop yields the returned packet" $ do+      (ss4, wrappers) <- error_wrappers+      expected <- unhex V.errPacket+      let fm = FailureMessage TemporaryNodeFailure no_tlvs+      pkt <- right "construct_error" (construct_error ss4 fm)+      foldl (flip wrap_error) pkt wrappers @?= ErrorPacket expected+  , testCase "the origin attributes the packet to hop 4" $ do+      sss <- route_secrets+      pkt <- unhex V.errPacket+      unwrap_error sss (ErrorPacket pkt) @?=+        Attributed 4 (FailureMessage TemporaryNodeFailure no_tlvs)+  ]++-- spec vectors: 'Returning Errors' trace -------------------------------------++trace_failure :: IO FailureMessage+trace_failure = do+  m <- msat 100+  ex <- tlvs [BOLT1.TlvRecord 34001 (BS.replicate 300 0x80)]+  pure (FailureMessage (IncorrectOrUnknownPaymentDetails m 800000) ex)++trace_vectors :: TestTree+trace_vectors = testGroup "returning errors trace" [+    testCase "failure message encoding" $ do+      fm <- trace_failure+      -- the trace lists failuremsg || pad_len || pad+      encoded <- unhex V.traceFailureMessage+      msg <- right "encode" (encode_failure_message fm)+      BS.length msg @?= 320+      BS.take 320 encoded @?= msg+      decode_failure_message msg @?= Right fm+  , testCase "each hop's packet matches the trace" $ do+      (ss4, wrappers) <- error_wrappers+      raw <- unhex V.traceRawPacket+      expected <- mapM unhex+        [ V.tracePacket4, V.tracePacket3, V.tracePacket2+        , V.tracePacket1, V.tracePacket0 ]+      let packets = drop 1 $+            scanl (flip wrap_error) (ErrorPacket raw) (ss4 : wrappers)+      packets @?= map ErrorPacket expected+  , testCase "the origin attributes the packet to hop 4" $ do+      fm <- trace_failure+      sss <- route_secrets+      pkt <- unhex V.tracePacket0+      unwrap_error sss (ErrorPacket pkt) @?= Attributed 4 fm+  ]++-- spec vectors: route-blinding-test.json -------------------------------------++empty_features :: Maybe BOLT9.FeatureVector+empty_features = Just (BOLT9.parse BS.empty)++rb_bob_data :: IO BlindedHopData+rb_bob_data = do+  s <- scid 0 0 1729+  m <- msat 1500+  ex <- tlvs [BOLT1.TlvRecord 561 "\DC24V"]+  pure empty_blinded_hop_data+    { bhd_padding = Just (BS.replicate 26 0)+    , bhd_short_channel_id = Just s+    , bhd_payment_relay = Just (PaymentRelay 36 150 10000)+    , bhd_payment_constraints = Just (PaymentConstraints 748005 m)+    , bhd_allowed_features = empty_features+    , bhd_extra = ex+    }++rb_carol_data :: IO BlindedHopData+rb_carol_data = do+  s <- scid 0 0 1105+  m <- msat 1500+  override <- hex_point V.rbDavePathKey+  pure empty_blinded_hop_data+    { bhd_short_channel_id = Just s+    , bhd_next_path_key_override = Just override+    , bhd_payment_relay = Just (PaymentRelay 48 100 500)+    , bhd_payment_constraints = Just (PaymentConstraints 747969 m)+    , bhd_allowed_features = empty_features+    }++rb_dave_data :: IO BlindedHopData+rb_dave_data = do+  s <- scid 0 0 561+  m <- msat 1500+  pure empty_blinded_hop_data+    { bhd_padding = Just (BS.replicate 35 0)+    , bhd_short_channel_id = Just s+    , bhd_payment_relay = Just (PaymentRelay 144 250 0)+    , bhd_payment_constraints = Just (PaymentConstraints 747921 m)+    , bhd_allowed_features = empty_features+    }++rb_eve_data :: IO BlindedHopData+rb_eve_data = do+  m <- msat 1500+  ex <- tlvs [BOLT1.TlvRecord 65535 "\ACK\193"]+  pure empty_blinded_hop_data+    { bhd_padding = Just (BS.replicate 26 0)+    , bhd_path_id = Just "\222\173\190\239"+    , bhd_payment_constraints = Just (PaymentConstraints 747777 m)+    , bhd_allowed_features =+        Just (BOLT9.parse (BS.cons 0x02 (BS.replicate 14 0)))+    , bhd_extra = ex+    }++-- a hop of route-blinding-test.json+data RbHop = RbHop+  { rb_data          :: !BlindedHopData+  , rb_node_id       :: !BOLT1.Point+  , rb_tlvs          :: !BS.ByteString+  , rb_path_key      :: !BOLT1.Point+  , rb_encrypted     :: !BS.ByteString+  , rb_blinded_id    :: !BOLT1.Point+  , rb_node_key      :: !SecretKey+  , rb_next_path_key :: !BOLT1.Point+  }++rb_hops :: IO [RbHop]+rb_hops = sequence+    [ hop rb_bob_data+        ( V.rbBobNodeId, V.rbBobTlvs, V.rbBobPathKey+        , V.rbBobEncryptedData, V.rbBobBlindedNodeId )+        (V.rbBobNodeKey, V.rbBobNextPathKey)+    , hop rb_carol_data+        ( V.rbCarolNodeId, V.rbCarolTlvs, V.rbCarolPathKey+        , V.rbCarolEncryptedData, V.rbCarolBlindedNodeId )+        (V.rbCarolNodeKey, V.rbCarolNextPathKey)+    , hop rb_dave_data+        ( V.rbDaveNodeId, V.rbDaveTlvs, V.rbDavePathKey+        , V.rbDaveEncryptedData, V.rbDaveBlindedNodeId )+        (V.rbDaveNodeKey, V.rbDaveNextPathKey)+    , hop rb_eve_data+        ( V.rbEveNodeId, V.rbEveTlvs, V.rbEvePathKey+        , V.rbEveEncryptedData, V.rbEveBlindedNodeId )+        (V.rbEveNodeKey, V.rbEveNextPathKey)+    ]+  where+    hop mk (nid, tl, pk, enc, bid) (nk, npk) =+      RbHop <$> mk <*> hex_point nid <*> unhex tl <*> hex_point pk+            <*> unhex enc <*> hex_point bid <*> hex_key nk+            <*> hex_point npk++route_blinding_vectors :: TestTree+route_blinding_vectors = testGroup "route-blinding-test.json" [+    testCase "encrypted_data_tlv encodings" $ do+      hops <- rb_hops+      forM_ hops $ \h -> do+        encode_blinded_hop_data (rb_data h) @?= Right (rb_tlvs h)+        decode_blinded_hop_data (rb_tlvs h) @?= Right (rb_data h)+  , testCase "create_blinded_path reproduces the route" $ do+      hops <- rb_hops+      (bob, carol, dave, eve) <- case hops of+        [b, c, d, e] -> pure (b, c, d, e)+        _ -> assertFailure "expected 4 hops"+      -- Bob creates Bob -> Carol, Eve creates Dave -> Eve+      bob_seed <- hex_key V.rbBobPathPrivKey+      dave_seed <- hex_key V.rbDavePathPrivKey+      let check seed hs = do+            path <- right "create_blinded_path" $ create_blinded_path seed+              [ (rb_node_id h, rb_data h) | h <- hs ]+            case hs of+              h : _ -> do+                bp_first_node_id path @?= rb_node_id h+                bp_first_path_key path @?= rb_path_key h+              [] -> assertFailure "no hops"+            bp_hops path @?=+              [ BlindedHop (rb_blinded_id h) (rb_encrypted h) | h <- hs ]+      check bob_seed [bob, carol]+      check dave_seed [dave, eve]+  , testCase "each hop decrypts its data and next path key" $ do+      hops <- rb_hops+      forM_ hops $ \h -> do+        info <- right "decrypt_recipient_data" $+          decrypt_recipient_data (rb_node_key h) (rb_path_key h)+            (rb_encrypted h)+        bi_data info @?= rb_data h+        encode_blinded_hop_data (bi_data info) @?= Right (rb_tlvs h)+        -- Carol's next path key is her next_path_key_override+        bi_next_path_key info @?= rb_next_path_key h+  , testCase "each hop decrypts an onion to its blinded id" $ do+      -- the onion is encrypted to blinded_node_id, so processing it+      -- requires the blinded private key of the vector+      hops <- rb_hops+      session <- key 0x55+      forM_ hops $ \h -> do+        let pl = empty_hop_payload+                   { hp_encrypted_data = Just (rb_encrypted h) }+        (pkt, sss) <- right "construct" $+          construct session [Hop (rb_blinded_id h) pl] ad+        r <- receive (process (rb_node_key h) pkt ad (Just (rb_path_key h)))+        Just (rcv_shared_secret r) @?= safe_head sss+        fmap bi_next_path_key (rcv_blinded r) @?= Just (rb_next_path_key h)+  ]++-- spec vectors: blinded-payment-onion-test.json ------------------------------++bp_bob_data :: IO BlindedHopData+bp_bob_data = do+  s <- scid 0 0 1+  m <- msat 50+  pure empty_blinded_hop_data+    { bhd_padding = Just (BS.replicate 32 0)+    , bhd_short_channel_id = Just s+    , bhd_payment_relay = Just (PaymentRelay 50 0 10000)+    , bhd_payment_constraints = Just (PaymentConstraints 750150 m)+    , bhd_allowed_features = empty_features+    }++bp_carol_data :: IO BlindedHopData+bp_carol_data = do+  s <- scid 0 0 2+  m <- msat 50+  override <- hex_point V.rbDavePathKey+  pure empty_blinded_hop_data+    { bhd_short_channel_id = Just s+    , bhd_next_path_key_override = Just override+    , bhd_payment_relay = Just (PaymentRelay 75 150 100)+    , bhd_payment_constraints = Just (PaymentConstraints 750100 m)+    , bhd_allowed_features = empty_features+    }++bp_dave_data :: IO BlindedHopData+bp_dave_data = do+  s <- scid 0 0 3+  m <- msat 50+  pure empty_blinded_hop_data+    { bhd_padding = Just (BS.replicate 34 0)+    , bhd_short_channel_id = Just s+    , bhd_payment_relay = Just (PaymentRelay 25 100 0)+    , bhd_payment_constraints = Just (PaymentConstraints 750025 m)+    , bhd_allowed_features = empty_features+    }++bp_eve_data :: IO BlindedHopData+bp_eve_data = do+  m <- msat 50+  path_id <- unhex "c9cf92f45ade68345bc20ae672e2012f4af487ed4415"+  pure empty_blinded_hop_data+    { bhd_padding = Just (BS.replicate 28 0)+    , bhd_path_id = Just path_id+    , bhd_payment_constraints = Just (PaymentConstraints 750000 m)+    , bhd_allowed_features = empty_features+    }++-- per hop: node key, public key the onion is encrypted to, onion+-- received, payload (without its length prefix)+bp_route+  :: IO [(SecretKey, BOLT1.Point, BS.ByteString, BS.ByteString)]+bp_route = mapM hop+    [ (V.onionNodeKey0, V.bpAlicePubKey, V.bpAliceOnion, V.bpAlicePayload)+    , (V.rbBobNodeKey, V.bpBobPubKey, V.bpBobOnion, V.bpBobPayload)+    , (V.rbCarolNodeKey, V.bpCarolPubKey, V.bpCarolOnion, V.bpCarolPayload)+    , (V.rbDaveNodeKey, V.bpDavePubKey, V.bpDaveOnion, V.bpDavePayload)+    , (V.rbEveNodeKey, V.bpEvePubKey, V.bpEveOnion, V.bpEvePayload)+    ]+  where+    hop (k, p, o, pl) = (,,,) <$> hex_key k <*> hex_point p <*> unhex o+                              <*> (unhex pl >>= unprefix)++blinded_payment_vectors :: TestTree+blinded_payment_vectors = testGroup "blinded-payment-onion-test.json" [+    testCase "construct reproduces the onion" $ do+      route <- bp_route+      sk <- hex_key V.bpSessionKey+      assoc <- unhex V.bpAssocData+      expected <- unhex V.bpAliceOnion+      hops <- mapM (\(_, pub, _, body) -> do+                hp <- right "decode_hop_payload" (decode_hop_payload body)+                encode_hop_payload hp @?= Right body+                pure (Hop pub hp)) route+      (pkt, _) <- right "construct" (construct sk hops assoc)+      encode_onion_packet pkt @?= expected+  , testCase "the route's payloads" $ do+      route <- bp_route+      first_pk <- hex_point V.rbBobPathKey+      s <- scid 0 0 10+      (a0, a1, a2) <- three =<< mapM msat [110125, 100000, 150000]+      payloads <- mapM (\(_, _, _, body) ->+        right "decode_hop_payload" (decode_hop_payload body)) route+      case payloads of+        [alice, bob, carol, dave, eve] -> do+          alice @?= empty_hop_payload+            { hp_short_channel_id = Just s, hp_amt_to_forward = Just a0+            , hp_outgoing_cltv_value = Just 749150 }+          hp_current_path_key bob @?= Just first_pk+          forM_ [bob, carol, dave] $ \hp -> do+            assertBool "encrypted data" (hp_encrypted_data hp /= Nothing)+            hp { hp_encrypted_data = Nothing+               , hp_current_path_key = Nothing } @?= empty_hop_payload+          eve { hp_encrypted_data = Nothing } @?= empty_hop_payload+            { hp_amt_to_forward = Just a1, hp_total_amount_msat = Just a2+            , hp_outgoing_cltv_value = Just 749000 }+        _ -> assertFailure "expected 5 payloads"+  , testCase "each hop processes its onion" $ do+      route <- bp_route+      assoc <- unhex V.bpAssocData+      datas <- sequence [bp_bob_data, bp_carol_data, bp_dave_data,+                         bp_eve_data]+      nexts <- mapM hex_point+        [ V.bpBobNextPathKey, V.bpCarolNextPathKey, V.bpDaveNextPathKey+        , V.bpEveNextPathKey ]+      -- (hop, path_key received, expected blinded info)+      let expect = zip3 route+            (Nothing : Nothing : map Just (take 3 nexts))+            (Nothing : map Just (zipWith BlindedInfo datas nexts))+          go [] = assertFailure "no hops"+          go [((k, _, onion, body), mpk, binfo)] = do+            pkt <- right "decode" (decode_onion_packet onion)+            r <- receive (process k pkt assoc mpk)+            encode_hop_payload (rcv_payload r) @?= Right body+            rcv_blinded r @?= binfo+          go (((k, _, onion, body), mpk, binfo) : rest) = do+            pkt <- right "decode" (decode_onion_packet onion)+            f <- forward (process k pkt assoc mpk)+            encode_hop_payload (fwd_payload f) @?= Right body+            fwd_blinded f @?= binfo+            forM_ (take 1 rest) $ \((_, _, next, _), _, _) ->+              encode_onion_packet (fwd_next_packet f) @?= next+            go rest+      go expect+  ]++-- properties -----------------------------------------------------------------++properties :: TestTree+properties = testGroup "properties" [+    testProperty "onion packets round-trip" $+      forAll gen_onion $ \pkt ->+        decode_onion_packet (encode_onion_packet pkt) === Right pkt+  , testProperty "hop payloads round-trip" $+      forAll gen_hop_payload $ \hp ->+        fmap decode_hop_payload (encode_hop_payload hp) === Right (Right hp)+  , testProperty "blinded hop data round-trips" $+      forAll gen_blinded_hop_data $ \d ->+        fmap decode_blinded_hop_data (encode_blinded_hop_data d)+          === Right (Right d)+  , testProperty "failure messages round-trip" $+      forAll gen_failure_message $ \fm ->+        fmap decode_failure_message (encode_failure_message fm)+          === Right (Right fm)+  , testProperty "wrap_error is an involution" $+      forAll gen_secret $ \ss ->+      forAll (gen_bytes 0 600) $ \bs ->+        wrap_error ss (wrap_error ss (ErrorPacket bs)) === ErrorPacket bs+  , testProperty "errors are attributed to the failing hop" $+      forAll (choose (1, 20)) $ \n ->+      forAll (vectorOf n gen_secret) $ \sss ->+      forAll (choose (0, n - 1)) $ \i ->+      forAll gen_failure_message $ \fm ->+        case drop i sss of+          ss : _ | Right pkt <- construct_error ss fm ->+            unwrap_error sss (foldr wrap_error pkt (take i sss))+              === Attributed i fm+          _ -> property False+  , testProperty "routes of 1 to 20 hops construct and process" $+      forAll (choose (1, 20)) $ \n ->+      forAll gen_key $ \sk ->+      forAll (vectorOf n gen_key) $ \keys ->+      forAll (vectorOf n (gen_route_payload n)) $ \pls ->+        route_roundtrip sk (zip keys pls)+  ]++-- construct an onion over a route, then process it at every hop+route_roundtrip :: SecretKey -> [(SecretKey, HopPayload)] -> Property+route_roundtrip sk route =+  case construct sk [Hop (public_key k) hp | (k, hp) <- route] ad of+    Left e -> counterexample (show e) False+    Right (pkt, sss) -> length sss === length route .&&. go route sss pkt+  where+    go [(k, hp)] [ss] pkt = case process k pkt ad Nothing of+      Right (Receive r) ->+        rcv_payload r === hp .&&. property (rcv_shared_secret r == ss)+      other -> counterexample (show other) False+    go ((k, hp) : rest) (ss : sss) pkt = case process k pkt ad Nothing of+      Right (Forward f) ->+             fwd_payload f === hp+        .&&. property (fwd_shared_secret f == ss)+        .&&. go rest sss (fwd_next_packet f)+      other -> counterexample (show other) False+    go _ _ _ = property False++-- generators -----------------------------------------------------------------++gen_bytes :: Int -> Int -> Gen BS.ByteString+gen_bytes lo hi = do+  n <- choose (lo, hi)+  BS.pack <$> vectorOf n arbitrary++gen_key :: Gen SecretKey+gen_key = gen_bytes 32 32 `suchThatMap` secret_key++gen_secret :: Gen SharedSecret+gen_secret = gen_bytes 32 32 `suchThatMap` shared_secret++gen_point :: Gen BOLT1.Point+gen_point = do+  prefix <- elements [0x02, 0x03]+  x <- gen_bytes 32 32+  pure (BS.cons prefix x) `suchThatMap` BOLT1.point++gen_msat :: Gen BOLT1.MilliSatoshi+gen_msat = oneof+  [ choose (0, 0xffff), choose (0, BOLT1.un_milli_satoshi+                                     BOLT1.max_milli_satoshi) ]+  `suchThatMap` BOLT1.milli_satoshi++gen_scid :: Gen BOLT1.ShortChannelId+gen_scid = BOLT1.ShortChannelId <$> arbitrary++gen_maybe :: Gen a -> Gen (Maybe a)+gen_maybe g = oneof [pure Nothing, Just <$> g]++-- extra records with odd types not in the given list+gen_extra :: [Word64] -> Gen BOLT1.TlvStream+gen_extra known = do+  n <- choose (0, 3)+  ts <- vectorOf n (oneof [choose (1, 300), choose (65536, 70000)])+  recs <- mapM (\t -> BOLT1.TlvRecord t <$> gen_bytes 0 20)+            [ t | t <- ts, odd t, t `notElem` known ]+  let uniq = nubBy (\a b -> BOLT1.tlv_type a == BOLT1.tlv_type b) recs+  pure uniq `suchThatMap` BOLT1.tlv_stream++gen_onion :: Gen OnionPacket+gen_onion = OnionPacket+  <$> arbitrary+  <*> gen_point+  <*> (gen_bytes 1300 1300 `suchThatMap` hop_payloads)+  <*> (gen_bytes 32 32 `suchThatMap` hmac32)++gen_hop_payload :: Gen HopPayload+gen_hop_payload = HopPayload+  <$> gen_maybe gen_msat+  <*> gen_maybe arbitrary+  <*> gen_maybe gen_scid+  <*> gen_maybe (PaymentData+                   <$> (gen_bytes 32 32 `suchThatMap` payment_secret)+                   <*> gen_msat)+  <*> gen_maybe (gen_bytes 0 50)+  <*> gen_maybe gen_point+  <*> gen_maybe (gen_bytes 0 50)+  <*> gen_maybe gen_msat+  <*> gen_extra []++gen_blinded_hop_data :: Gen BlindedHopData+gen_blinded_hop_data = BlindedHopData+  <$> gen_maybe (gen_bytes 0 40)+  <*> gen_maybe gen_scid+  <*> gen_maybe gen_point+  <*> gen_maybe (gen_bytes 0 40)+  <*> gen_maybe gen_point+  <*> gen_maybe (PaymentRelay <$> arbitrary <*> arbitrary <*> arbitrary)+  <*> gen_maybe (PaymentConstraints <$> arbitrary <*> gen_msat)+  <*> gen_maybe (BOLT9.parse <$> gen_bytes 0 8)+  <*> gen_extra [1]++gen_failure_message :: Gen FailureMessage+gen_failure_message = do+  f <- gen_failure+  ex <- case f of+    -- these take every byte after the code, or may be ambiguous with a+    -- TLV stream+    UnknownFailure _ _      -> pure no_tlvs+    InvalidOnionPayload Nothing -> pure no_tlvs+    _                       -> gen_extra []+  pure (FailureMessage f ex)++gen_failure :: Gen Failure+gen_failure = oneof+  [ pure TemporaryNodeFailure+  , pure PermanentNodeFailure+  , pure RequiredNodeFeatureMissing+  , InvalidOnionVersion <$> hash+  , InvalidOnionHmac <$> hash+  , InvalidOnionKey <$> hash+  , TemporaryChannelFailure <$> update+  , pure PermanentChannelFailure+  , pure RequiredChannelFeatureMissing+  , pure UnknownNextPeer+  , AmountBelowMinimum <$> gen_msat <*> update+  , FeeInsufficient <$> gen_msat <*> update+  , IncorrectCltvExpiry <$> arbitrary <*> update+  , ExpiryTooSoon <$> update+  , IncorrectOrUnknownPaymentDetails <$> gen_msat <*> arbitrary+  , FinalIncorrectCltvExpiry <$> arbitrary+  , FinalIncorrectHtlcAmount <$> gen_msat+  , ChannelDisabled <$> arbitrary <*> update+  , pure ExpiryTooFar+  , InvalidOnionPayload <$> gen_maybe ((,) <$> arbitrary <*> arbitrary)+  , pure MppTimeout+  , InvalidOnionBlinding <$> hash+  , UnknownFailure <$> (arbitrary `suchThat` unknown) <*> gen_bytes 0 20+  ]+  where+    hash = gen_bytes 32 32 `suchThatMap` onion_hash+    update = gen_bytes 0 100+    unknown c = c `notElem`+      [ 0x2002, 0x6002, 0x6003, 0xc004, 0xc005, 0xc006, 0x1007, 0x4008+      , 0x4009, 0x400a, 0x100b, 0x100c, 0x100d, 0x100e, 0x400f, 0x0012+      , 0x0013, 0x1014, 0x0015, 0x4016, 0x0017, 0xc018 ]++-- a payload whose shift size fits n hops in 1300 bytes+--+-- The typed fields take at most 26 bytes; an odd record of type 65537+-- adds at most 8 bytes of framing plus its value. With a 3-byte length+-- prefix and 32-byte HMAC a hop then shifts at most 69 bytes plus the+-- value's length.+gen_route_payload :: Int -> Gen HopPayload+gen_route_payload n = do+  amt <- gen_msat+  cltv <- arbitrary+  s <- gen_maybe gen_scid+  let budget = 1300 `div` n - 69+  ex <- if budget < 0+    then pure no_tlvs+    else do+      v <- gen_bytes 0 budget+      pure (BOLT1.TlvRecord 65537 v : [])+        `suchThatMap` BOLT1.tlv_stream+  pure empty_hop_payload+    { hp_amt_to_forward = Just amt+    , hp_outgoing_cltv_value = Just cltv+    , hp_short_channel_id = s+    , hp_extra = ex+    }
+ test/Vectors.hs view
@@ -0,0 +1,1100 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module: Vectors+-- Copyright: (c) 2025 Jared Tobin+-- License: MIT+-- Maintainer: Jared Tobin <jared@ppad.tech>+--+-- BOLT4 test vectors, hex-encoded.+--+-- Taken from the bolt04/*.json files in lightning/bolts, and from the+-- 'Returning Errors' trace in 04-onion-routing.md.++module Vectors where++import qualified Data.ByteString as BS++-- onion-test.json ---------------------------------------------------------++-- | Session key.+onionSessionKey :: BS.ByteString+onionSessionKey =+  "4141414141414141414141414141414141414141414141414141414141414141"++-- | Associated data.+onionAssocData :: BS.ByteString+onionAssocData =+  "4242424242424242424242424242424242424242424242424242424242424242"++-- | Hop 0 public key.+onionPubKey0 :: BS.ByteString+onionPubKey0 =+  "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"++-- | Hop 0 payload, with its length prefix.+onionPayload0 :: BS.ByteString+onionPayload0 = "1202023a98040205dc06080000000000000001"++-- | Hop 1 public key.+onionPubKey1 :: BS.ByteString+onionPubKey1 =+  "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c"++-- | Hop 1 payload, with its length prefix.+onionPayload1 :: BS.ByteString+onionPayload1 = BS.concat+  [ "52020236b00402057806080000000000000002fd02013c010203040506070809"+  , "0a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b"+  , "0c0d0e0f0102030405060708090a0b0c0d0e0f"+  ]++-- | Hop 2 public key.+onionPubKey2 :: BS.ByteString+onionPubKey2 =+  "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007"++-- | Hop 2 payload, with its length prefix.+onionPayload2 :: BS.ByteString+onionPayload2 = "12020230d4040204e206080000000000000003"++-- | Hop 3 public key.+onionPubKey3 :: BS.ByteString+onionPubKey3 =+  "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"++-- | Hop 3 payload, with its length prefix.+onionPayload3 :: BS.ByteString+onionPayload3 = "1202022710040203e806080000000000000004"++-- | Hop 4 public key.+onionPubKey4 :: BS.ByteString+onionPubKey4 =+  "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"++-- | Hop 4 payload, with its length prefix.+onionPayload4 :: BS.ByteString+onionPayload4 = BS.concat+  [ "fd011002022710040203e8082224a33562c54507a9334e79f0dc4f17d407e6d7"+  , "c61f0e2f3d0d38599502f617042710fd012de02a2a2a2a2a2a2a2a2a2a2a2a2a"+  , "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"+  , "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"+  , "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"+  , "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"+  , "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"+  , "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"+  , "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"+  ]++-- | The resulting onion packet.+onionPacket :: BS.ByteString+onionPacket = BS.concat+  [ "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f28368"+  , "6619f7f3416a5aa36dc7eeb3ec6d421e9615471ab870a33ac07fa5d5a51df0a8"+  , "823aabe3fea3f90d387529d4f72837f9e687230371ccd8d263072206dbed0234"+  , "f6505e21e282abd8c0e4f5b9ff8042800bbab065036eadd0149b37f27dde6647"+  , "25a49866e052e809d2b0198ab9610faa656bbf4ec516763a59f8f42c171b1791"+  , "66ba38958d4f51b39b3e98706e2d14a2dafd6a5df808093abfca5aeaaca16ede"+  , "d5db7d21fb0294dd1a163edf0fb445d5c8d7d688d6dd9c541762bf5a5123bf99"+  , "39d957fe648416e88f1b0928bfa034982b22548e1a4d922690eecf546275afb2"+  , "33acf4323974680779f1a964cfe687456035cc0fba8a5428430b390f0057b6d1"+  , "fe9a8875bfa89693eeb838ce59f09d207a503ee6f6299c92d6361bc335fcbf9b"+  , "5cd44747aadce2ce6069cfdc3d671daef9f8ae590cf93d957c9e873e9a1bc62d"+  , "9640dc8fc39c14902d49a1c80239b6c5b7fd91d05878cbf5ffc7db2569f47c43"+  , "d6c0d27c438abff276e87364deb8858a37e5a62c446af95d8b786eaf0b5fcf78"+  , "d98b41496794f8dcaac4eef34b2acfb94c7e8c32a9e9866a8fa0b6f2a06f00a1"+  , "ccde569f97eec05c803ba7500acc96691d8898d73d8e6a47b8f43c3d5de74458"+  , "d20eda61474c426359677001fbd75a74d7d5db6cb4feb83122f133206203e4e2"+  , "d293f838bf8c8b3a29acb321315100b87e80e0edb272ee80fda944e3fb6084ed"+  , "4d7f7c7d21c69d9da43d31a90b70693f9b0cc3eac74c11ab8ff655905688916c"+  , "fa4ef0bd04135f2e50b7c689a21d04e8e981e74c6058188b9b1f9dfc3eec6838"+  , "e9ffbcf22ce738d8a177c19318dffef090cee67e12de1a3e2a39f61247547ba5"+  , "257489cbc11d7d91ed34617fcc42f7a9da2e3cf31a94a210a1018143173913c3"+  , "8f60e62b24bf0d7518f38b5bab3e6a1f8aeb35e31d6442c8abb5178efc892d2e"+  , "787d79c6ad9e2fc271792983fa9955ac4d1d84a36c024071bc6e431b625519d5"+  , "56af38185601f70e29035ea6a09c8b676c9d88cf7e05e0f17098b584c4168735"+  , "940263f940033a220f40be4c85344128b14beb9e75696db37014107801a59b13"+  , "e89cd9d2258c169d523be6d31552c44c82ff4bb18ec9f099f3bf0e5b1bb2ba9a"+  , "87d7e26f98d294927b600b5529c47e04d98956677cbcee8fa2b60f49776d8b8c"+  , "367465b7c626da53700684fb6c918ead0eab8360e4f60edd25b4f43816a75ecf"+  , "70f909301825b512469f8389d79402311d8aecb7b3ef8599e79485a4388d8774"+  , "4d899f7c47ee644361e17040a7958c8911be6f463ab6a9b2afacd688ec55ef51"+  , "7b38f1339efc54487232798bb25522ff4572ff68567fe830f92f7b8113efce3e"+  , "98c3fffbaedce4fd8b50e41da97c0c08e423a72689cc68e68f752a5e3a9003e6"+  , "4e35c957ca2e1c48bb6f64b05f56b70b575ad2f278d57850a7ad568c24a4d32a"+  , "3d74b29f03dc125488bc7c637da582357f40b0a52d16b3b40bb2c2315d03360b"+  , "c24209e20972c200566bcf3bbe5c5b0aedd83132a8a4d5b4242ba370b6d67d9b"+  , "67eb01052d132c7866b9cb502e44796d9d356e4e3cb47cc527322cd24976fe7c"+  , "9257a2864151a38e568ef7a79f10d6ef27cc04ce382347a2488b1f404fdbf407"+  , "fe1ca1c9d0d5649e34800e25e18951c98cae9f43555eef65fee1ea8f15828807"+  , "366c3b612cd5753bf9fb8fced08855f742cddd6f765f74254f03186683d646e6"+  , "f09ac2805586c7cf11998357cafc5df3f285329366f475130c928b2dceba4aa3"+  , "83758e7a9d20705c4bb9db619e2992f608a1ba65db254bb389468741d0502e25"+  , "88aeb54390ac600c19af5c8e61383fc1bebe0029e4474051e4ef908828db9cca"+  , "13277ef65db3fd47ccc2179126aaefb627719f421e20"+  ]++-- | Hop 0 node private key.+onionNodeKey0 :: BS.ByteString+onionNodeKey0 =+  "4141414141414141414141414141414141414141414141414141414141414141"++-- | Hop 1 node private key.+onionNodeKey1 :: BS.ByteString+onionNodeKey1 =+  "4242424242424242424242424242424242424242424242424242424242424242"++-- | Hop 2 node private key.+onionNodeKey2 :: BS.ByteString+onionNodeKey2 =+  "4343434343434343434343434343434343434343434343434343434343434343"++-- | Hop 3 node private key.+onionNodeKey3 :: BS.ByteString+onionNodeKey3 =+  "4444444444444444444444444444444444444444444444444444444444444444"++-- | Hop 4 node private key.+onionNodeKey4 :: BS.ByteString+onionNodeKey4 =+  "4545454545454545454545454545454545454545454545454545454545454545"++-- onion-error-test.json ---------------------------------------------------++-- | Failure message.+errFailureMessage :: BS.ByteString+errFailureMessage = "2002"++-- | Hop 0 shared secret.+errSharedSecret0 :: BS.ByteString+errSharedSecret0 =+  "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"++-- | Hop 0 ammag key.+errAmmagKey0 :: BS.ByteString+errAmmagKey0 =+  "3761ba4d3e726d8abb16cba5950ee976b84937b61b7ad09e741724d7dee12eb5"++-- | Hop 1 shared secret.+errSharedSecret1 :: BS.ByteString+errSharedSecret1 =+  "a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"++-- | Hop 1 ammag key.+errAmmagKey1 :: BS.ByteString+errAmmagKey1 =+  "59ee5867c5c151daa31e36ee42530f429c433836286e63744f2020b980302564"++-- | Hop 2 shared secret.+errSharedSecret2 :: BS.ByteString+errSharedSecret2 =+  "3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"++-- | Hop 2 ammag key.+errAmmagKey2 :: BS.ByteString+errAmmagKey2 =+  "1bf08df8628d452141d56adfd1b25c1530d7921c23cecfc749ac03a9b694b0d3"++-- | Hop 3 shared secret.+errSharedSecret3 :: BS.ByteString+errSharedSecret3 =+  "21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"++-- | Hop 3 ammag key.+errAmmagKey3 :: BS.ByteString+errAmmagKey3 =+  "cd9ac0e09064f039fa43a31dea05f5fe5f6443d40a98be4071af4a9d704be5ad"++-- | Hop 4 shared secret.+errSharedSecret4 :: BS.ByteString+errSharedSecret4 =+  "b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"++-- | Hop 4 ammag key.+errAmmagKey4 :: BS.ByteString+errAmmagKey4 =+  "2f36bb8822e1f0d04c27b7d8bb7d7dd586e032a3218b8d414afbba6f169a4d68"++-- | Hop 4 um key.+errUmKey4 :: BS.ByteString+errUmKey4 = "4da7f2923edce6c2d85987d1d9fa6d88023e6c3a9c3d20f07d3b10b61a78d646"++-- | Hop 4 return packet, without its HMAC.+errPayload4 :: BS.ByteString+errPayload4 = BS.concat+  [ "0002200200fe0000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "00000000"+  ]++-- | The return packet received by the origin.+errPacket :: BS.ByteString+errPacket = BS.concat+  [ "9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed"+  , "3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8"+  , "bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d9"+  , "90f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc"+  , "28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7f"+  , "bb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79"+  , "f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf8"+  , "84064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c1"+  , "18f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448"+  , "b604d12d"+  ]++-- BOLT4 'Returning Errors' trace ------------------------------------------++-- | Encoded failure message, followed by pad_len and pad.+traceFailureMessage :: BS.ByteString+traceFailureMessage = BS.concat+  [ "400f0000000000000064000c3500fd84d1fd012c808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "02c0000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000"+  ]++-- | Return packet before obfuscation.+traceRawPacket :: BS.ByteString+traceRawPacket = BS.concat+  [ "fda7e11974f78ca6cc456f2d17ae54463664696e93842548245dd2a2c513a626"+  , "0140400f0000000000000064000c3500fd84d1fd012c80808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "8080808080808080808080808080808080808080808080808080808080808080"+  , "808002c000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "0000000000000000000000000000000000000000000000000000000000000000"+  , "00000000"+  ]++-- | Return packet as sent by node 4.+tracePacket4 :: BS.ByteString+tracePacket4 = BS.concat+  [ "146e94a9086dbbed6a0ab6932d00c118a7195dbf69b7d7a12b0e6956fc54b5e0"+  , "a989f165b5f12fd45edd73a5b0c48630ff5be69500d3d82a29c0803f0a0679a6"+  , "a073c33a6fb8250090a3152eba3f11a85184fa87b67f1b0354d6f48e3b342e33"+  , "2a17b7710f342f342a87cf32eccdf0afc2160808d58abb5e5840d2c760c538e6"+  , "3a6f841970f97d2e6fe5b8739dc45e2f7f5f532f227bcc2988ab0f9cc6d3f129"+  , "09cd5842c37bc8c7608475a5ebbe10626d5ecc1f3388ad5f645167b44a4d166f"+  , "87863fe34918cea25c18059b4c4d9cb414b59f6bc50c1cea749c80c43e2344f5"+  , "d23159122ed4ab9722503b212016470d9610b46c35dbeebaf2e342e09770b383"+  , "92a803bc9d2e7c8d6d384ffcbeb74943fe3f64afb2a543a6683c7db3088441c5"+  , "31eeb4647518cb41992f8954f1269fb969630944928c2d2b45593731b5da0c4e"+  , "70d04a0a57afe4af42e99912fbb4f8883a5ecb9cb29b883cb6bfa0f4db2279ff"+  , "8c6d2b56a232f55ba28fe7dfa70a9ab0433a085388f25cce8d53de6a2fbd7546"+  , "377d6ede9027ad173ba1f95767461a3689ef405ab608a21086165c64b02c1782"+  , "b04a6dba2361a7784603069124e12f2f6dcb1ec7612a4fbf94c0e14631a2bef6"+  , "190c3d5f35e0c4b32aa85201f449d830fd8f782ec758b0910428e3ec3ca1dba3"+  , "b6c7d89f69e1ee1b9df3dfbbf6d361e1463886b38d52e8f43b73a3bd48c6f36f"+  , "5897f514b93364a31d49d1d506340b1315883d425cb36f4ea553430d538fd6f3"+  , "596d4afc518db2f317dd051abc0d4bfb0a7870c3db70f19fe78d6604bbf088fc"+  , "b4613f54e67b038277fedcd9680eb97bdffc3be1ab2cbcbafd625b8a7ac34d8c"+  , "190f98d3064ecd3b95b8895157c6a37f31ef4de094b2cb9dbf8ff1f419ba0eca"+  , "cb1bb13df0253b826bec2ccca1e745dd3b3e7cc6277ce284d649e7b828572773"+  , "5ff4ef6cca6c18e2714f4e2a1ac67b25213d3bb49763b3b94e7ebf72507b71fb"+  , "2fe0329666477ee7cb7ebd6b88ad5add8b217188b1ca0fa13de1ec09cc674346"+  , "875105be6e0e0d6c8928eb0df23c39a639e04e4aedf535c4e093f08b2c905a14"+  , "f25c0c0fe47a5a1535ab9eae0d9d67bdd79de13a08d59ee05385c7ea4af1ad32"+  , "48e61dd22f8990e9e99897d653dd7b1b1433a6d464ea9f74e377f2d8ce99ba7d"+  , "bc753297644234d25ecb5bd528e2e2082824681299ac30c05354baaa9c3967d8"+  , "6d7c07736f87fc0f63e5036d47235d7ae12178ced3ae36ee5919c093a02579e4"+  , "fc9edad2c446c656c790704bfc8e2c491a42500aa1d75c8d4921ce29b753f883"+  , "e17c79b09ea324f1f32ddf1f3284cd70e847b09d90f6718c42e5c94484cc9cbb"+  , "0df659d255630a3f5a27e7d5dd14fa6b974d1719aa98f01a20fb4b7b1c77b42d"+  , "57fab3c724339d459ee4a1c6b5d3bd4e08624c786a257872acc9ad3ff62222f2"+  , "265a658d9f2a007229a5293b67ec91c84c4b4407c228434bad8a815ca9b256c7"+  , "76bd2c9f"+  ]++-- | Return packet as sent by node 3.+tracePacket3 :: BS.ByteString+tracePacket3 = BS.concat+  [ "7512354d6a26781d25e65539772ba049b7ed7c530bf75ab7ef80cf974b978a07"+  , "a1c3dabc61940011585323f70fa98cfa1d4c868da30b1f751e44a72d9b3f7980"+  , "9c8c51c9f0843daa8fe83587844fedeacb7348362003b31922cbb4d6169b2087"+  , "b6f8d192d9cfe5363254cd1fde24641bde9e422f170c3eb146f194c48a459ae2"+  , "889d706dc654235fa9dd20307ea54091d09970bf956c067a3bcc05af03c41e01"+  , "af949a131533778bf6ee3b546caf2eabe9d53d0fb2e8cc952b7e0f5326a69ed2"+  , "e58e088729a1d85971c6b2e129a5643f3ac43da031e655b27081f10543262cf9"+  , "d72d6f64d5d96387ac0d43da3e3a03da0c309af121dcf3e99192efa754eab696"+  , "0c256ffd4c546208e292e0ab9894e3605db098dc16b40f17c320aa4a0e42fc8b"+  , "105c22f08c9bc6537182c24e32062c6cd6d7ec7062a0c2c2ecdae1588c82185c"+  , "dc61d874ee916a7873ac54cddf929354f307e870011704a0e9fbc5c7802d6140"+  , "134028aca0e78a7e2f3d9e5c7e49e20c3a56b624bfea51196ec9e88e4e56be38"+  , "ff56031369f45f1e03be826d44a182f270c153ee0d9f8cf9f1f4132f33974e37"+  , "c7887d5b857365c873cb218cbf20d4be3abdb2a2011b14add0a5672e01e58454"+  , "21cf6dd6faca1f2f443757aae575c53ab797c2227ecdab03882bbbf4599318ce"+  , "fafa72fa0c9a0f5a51d13c9d0e5d25bfcfb0154ed25895260a9df8743ac18871"+  , "4a3f16960e6e2ff663c08bffda41743d50960ea2f28cda0bc3bd4a180e297b5b"+  , "41c700b674cb31d99c7f2a1445e121e772984abff2bbe3f42d757ceeda3d03fb"+  , "1ffe710aecabda21d738b1f4620e757e57b123dbc3c4aa5d9617dfa72f4a12d7"+  , "88ca596af14bea583f502f16fdc13a5e739afb0715424af2767049f6b9aa107f"+  , "69c5da0e85f6d8c5e46507e14616d5d0b797c3dea8b74a1b12d4e47ba7f57f09"+  , "d515f6c7314543f78b5e85329d50c5f96ee2f55bbe0df742b4003b24ccbd4598"+  , "a64413ee4807dc7f2a9c0b92424e4ae1b418a3cdf02ea4da5c3b12139348aa70"+  , "22cc8272a3a1714ee3e4ae111cffd1bdfd62c503c80bdf27b2feaea0d5ab8fe0"+  , "0f9cec66e570b00fd24b4a2ed9a5f6384f148a4d6325110a41ca5659ebc5b987"+  , "21d298a52819b6fb150f273383f1c5754d320be428941922da790e17f482989c"+  , "365c078f7f3ae100965e1b38c052041165295157e1a7c5b7a57671b842d4d85a"+  , "7d971323ad1f45e17a16c4656d889fc75c12fc3d8033f598306196e29571e414"+  , "281c5da19c12605f48347ad5b4648e371757cbe1c40adb93052af1d6110cfbf6"+  , "11af5c8fc682b7e2ade3bfca8b5c7717d19fc9f97964ba6025aebbc91a6671e2"+  , "59949dcf40984342118de1f6b514a7786bd4f6598ffbe1604cef476b2a4cb134"+  , "3db608aca09d1d38fc23e98ee9c65e7f6023a8d1e61fd4f34f753454bd8e858c"+  , "8ad6be6403edc599c220e03ca917db765980ac781e758179cd93983e9c1e769e"+  , "4241d47c"+  ]++-- | Return packet as sent by node 2.+tracePacket2 :: BS.ByteString+tracePacket2 = BS.concat+  [ "145bc1c63058f7204abbd2320d422e69fb1b3801a14312f81e5e29e6b5f4774c"+  , "fed8a25241d3dfb7466e749c1b3261559e49090853612e07bd669dfb5f4c5416"+  , "2fa504138dabd6ebcf0db8017840c35f12a2cfb84f89cc7c8959a6d51815b1d2"+  , "c5136cedec2e4106bb5f2af9a21bd0a02c40b44ded6e6a90a145850614fb1b0e"+  , "ef2a03389f3f2693bc8a755630fc81fff1d87a147052863a71ad5aebe8770537"+  , "f333e07d841761ec448257f948540d8f26b1d5b66f86e073746106dfdbb86ac9"+  , "475acf59d95ece037fba360670d924dce53aaa74262711e62a8fc9eb70cd8618"+  , "fbedae22853d3053c7f10b1a6f75369d7f73c419baa7dbf9f1fc5895362dcc8b"+  , "6bd60cca4943ef7143956c91992119bccbe1666a20b7de8a2ff30a46112b53a6"+  , "bb79b763903ecbd1f1f74952fb1d8eb0950c504df31fe702679c23b463f82a92"+  , "1a2c931500ab08e686cffb2d87258d254fb17843959cccd265a57ba26c740f0f"+  , "231bb76df932b50c12c10be90174b37d454a3f8b284c849e86578a6182c4a7b2"+  , "e47dd57d44730a1be9fec4ad07287a397e28dce4fda57e9cdfdb2eb5afdf0d38"+  , "ef19d982341d18d07a556bb16c1416f480a396f278373b8fd9897023a4ac506e"+  , "65cf4c306377730f9c8ca63cf47565240b59c4861e52f1dab84d938e96fb3182"+  , "0064d534aca05fd3d2600834fe4caea98f2a748eb8f200af77bd9fbf46141952"+  , "b9ddda66ef0ebea17ea1e7bb5bce65b6e71554c56dd0d4e14f4cf74c77a15077"+  , "6bf31e7419756c71e7421dc22efe9cf01de9e19fc8808d5b525431b944400db1"+  , "21a77994518d6025711cb25a18774068bba7faaa16d8f65c91bec87688483331"+  , "56dcb4a08dfbbd9fef392da3e4de13d4d74e83a7d6e46cfe530ee7a6f711e2ca"+  , "f8ad5461ba8177b2ef0a518baf9058ff9156e6aa7b08d938bd8d1485a787809d"+  , "7b4c8aed97be880708470cd2b2cdf8e2f13428cc4b04ef1f2acbc9562f3693b9"+  , "48d0aa94b0e6113cafa684f8e4a67dc431dfb835726874bef1de36f273f52ee6"+  , "94ec46b0700f77f8538067642a552968e866a72a3f2031ad116663ac17b172b4"+  , "46c5bc705b84777363a9a3fdc6443c07b2f4ef58858122168d4ebbaee920cefc"+  , "312e1cea870ed6e15eec046ab2073bbf08b0a3366f55cfc6ad4681a12ab09465"+  , "34e7b6f90ea8992d530ec3daa6b523b3cf03101c60cadd914f30dec932c1ef43"+  , "41b5a8efac3c921e203574cfe0f1f83433fddb8ccfd273f7c3cab7bc27efe3bb"+  , "61fdccd5146f1185364b9b621e7fb2b74b51f5ee6be72ab6ff46a6359dc2c855"+  , "e61469724c1dbeb273df9d2e1c1fb74891239c0019dc12d5c7535f7238f963b7"+  , "61d7102b585372cf021b64c4fc85bfb3161e59d2e298bba44cfd34d6859d9dba"+  , "9dc6271e5047d525468c814f2ae438474b0a977273036da1a2292f88fcfb8957"+  , "4a6bdca1185b40f8aa54026d5926725f99ef028da1be892e3586361efe15f4a1"+  , "48ff1bc9"+  ]++-- | Return packet as sent by node 1.+tracePacket1 :: BS.ByteString+tracePacket1 = BS.concat+  [ "1b4b09a935ce7af95b336baae307f2b400e3a7e808d9b4cf421cc4b3955620ac"+  , "b69dcdb656128dae8857adbd4e6b37fbb1be9c1f2f02e61e9e59a630c4c77cf3"+  , "83cb37b07413aa4de2f2fbf5b40ae40a91a8f4c6d74aeacef1bb1be4ecbc26ec"+  , "2c824d2bc45db4b9098e732a769788f1cff3f5b41b0d25c132d40dc5ad045ef0"+  , "043b15332ca3c5a09de2cdb17455a0f82a8f20da08346282823dab062cdbd211"+  , "1e238528141d69de13de6d83994fbc711e3e269df63a12d3a4177c5c149150eb"+  , "4dc2f589cd8acabcddba14dec3b0dada12d663b36176cd3c257c5460bab93981"+  , "ad99f58660efa9b31d7e63b39915329695b3fa60e0a3bdb93e7e29a54ca6a8f3"+  , "60d3848866198f9c3da3ba958e7730847fe1e6478ce8597848d3412b4ae48b06"+  , "e05ba9a104e648f6eaf183226b5f63ed2e68f77f7e38711b393766a6fab7921b"+  , "03eba82b5d7cb78e34dc961948d6161eadd7cf5d95d9c56df2ff5faa6ccf85ea"+  , "cdc9ff2fc3abafe41c365a5bd14fd486d6b5e2f24199319e7813e02e798877ff"+  , "e31a70ae2398d9e31b9e3727e6c1a3c0d995c67d37bb6e72e9660aaaa9232670"+  , "f382add2edd468927e3303b6142672546997fe105583e7c5a3c4c2b599731308"+  , "b5416e6c9a3f3ba55b181ad0439d3535356108b059f2cb8742eed7a58d4eba9f"+  , "e79eaa77c34b12aff1abdaea93197aabd0e74cb271269ca464b3b06aef1d6573"+  , "df5e1224179616036b368677f26479376681b772d3760e871d99efd34cca5cd6"+  , "beca95190d967da820b21e5bec60082ea46d776b0517488c84f26d12873912d1"+  , "f68fafd67bcf4c298e43cfa754959780682a2db0f75f95f0598c0d04fd014c50"+  , "e4beb86a9e37d95f2bba7e5065ae052dc306555bca203d104c44a538b438c976"+  , "2de299e1c4ad30d5b4a6460a76484661fc907682af202cd69b9a4473813b2fdc"+  , "1142f1403a49b7e69a650b7cde9ff133997dcc6d43f049ecac5fce097a21e2bc"+  , "e49c810346426585e3a5a18569b4cddd5ff6bdec66d0b69fcbc5ab3b137b34cc"+  , "8aefb8b850a764df0e685c81c326611d901c392a519866e132bbb73234f6a358"+  , "ba284fbafb21aa3605cacbaf9d0c901390a98b7a7dac9d4f0b405f7291c88b2f"+  , "f45874241c90ac6c5fc895a440453c344d3a365cb929f9c91b9e39cb98b14244"+  , "4aae03a6ae8284c77eb04b0a163813d4c21883df3c0f398f47bf127b5525f222"+  , "107a2d8fe55289f0cfd3f4bbad6c5387b0594ef8a966afc9e804ccaf75fe39f3"+  , "5c6446f7ee076d433f2f8a44dba1515acc78e589fa8c71b0a006fe14feebd51d"+  , "0e0aa4e51110d16759eee86192eee90b34432130f387e0ccd2ee71023f1f641c"+  , "ddb571c690107e08f592039fe36d81336a421e89378f351e633932a2f5f697d2"+  , "5b620ffb8e84bb6478e9bd229bf3b164b48d754ae97bd23f319e3c56b3bcdaae"+  , "b3bd7fc02ec02066b324cb72a09b6b43dec1097f49d69d3c138ce6f1a6402898"+  , "baf7568c"+  ]++-- | Return packet as sent by node 0.+tracePacket0 :: BS.ByteString+tracePacket0 = BS.concat+  [ "2dd2f49c1f5af0fcad371d96e8cddbdcd5096dc309c1d4e110f955926506b3c0"+  , "3b44c192896f45610741c85ed4074212537e0c118d472ff3a559ae244acd9d78"+  , "3c65977765c5d4e00b723d00f12475aafaafff7b31c1be5a589e6e25f8da2959"+  , "107206dd42bbcb43438129ce6cce2b6b4ae63edc76b876136ca5ea6cd1c6a04c"+  , "a86eca143d15e53ccdc9e23953e49dc2f87bb11e5238cd6536e57387225b8fff"+  , "3bf5f3e686fd08458ffe0211b87d64770db9353500af9b122828a006da754cf9"+  , "79738b4374e146ea79dd93656170b89c98c5f2299d6e9c0410c826c721950c78"+  , "0486cd6d5b7130380d7eaff994a8503a8fef3270ce94889fe996da66ed121741"+  , "987010f785494415ca991b2e8b39ef2df6bde98efd2aec7d251b2772485194c8"+  , "368451ad49c2354f9d30d95367bde316fec6cbdddc7dc0d25e99d3075e13d3de"+  , "0822669861dafcd29de74eac48b64411987285491f98d78584d0c2a163b7221e"+  , "a796f9e8671b2bb91e38ef5e18aaf32c6c02f2fb690358872a1ed28166172631"+  , "a82c2568d23238017188ebbd48944a147f6cdb3690d5f88e51371cb70adf1fa0"+  , "2afe4ed8b581afc8bcc5104922843a55d52acde09bc9d2b71a663e178788280f"+  , "3c3eae127d21b0b95777976b3eb17be40a702c244d0e5f833ff49dae6403ff44"+  , "b131e66df8b88e33ab0a58e379f2c34bf5113c66b9ea8241fc7aa2b1fa53cf4e"+  , "d3cdd91d407730c66fb039ef3a36d4050dde37d34e80bcfe02a48a6b14ae2822"+  , "7b1627b5ad07608a7763a531f2ffc96dff850e8c583461831b19feffc783bc1b"+  , "eab6301f647e9617d14c92c4b1d63f5147ccda56a35df8ca4806b8884c4aa3c3"+  , "cc6a174fdc2232404822569c01aba686c1df5eecc059ba97e9688c8b16b70f0d"+  , "24eacfdba15db1c71f72af1b2af85bd168f0b0800483f115eeccd9b02adf03bd"+  , "d4a88eab03e43ce342877af2b61f9d3d85497cd1c6b96674f3d4f07f635bb26a"+  , "dd1e36835e321d70263b1c04234e222124dad30ffb9f2a138e3ef453442df1af"+  , "7e566890aedee568093aa922dd62db188aa8361c55503f8e2c2e6ba93de744b5"+  , "5c15260f15ec8e69bb01048ca1fa7bbbd26975bde80930a5b95054688a0ea73a"+  , "f0353cc84b997626a987cc06a517e18f91e02908829d4f4efc011b9867bd9bfe"+  , "04c5f94e4b9261d30cc39982eb7b250f12aee2a4cce0484ff34eebba89bc6e35"+  , "bd48d3968e4ca2d77527212017e202141900152f2fd8af0ac3aa456aae13276a"+  , "13b9b9492a9a636e18244654b3245f07b20eb76b8e1cea8c55e5427f08a63a16"+  , "b0a633af67c8e48ef8e53519041c9138176eb14b8782c6c2ee76146b8490b979"+  , "78ee73cd0104e12f483be5a4af414404618e9f6633c55dda6f22252cb793d3d1"+  , "6fae4f0e1431434e7acc8fa2c009d4f6e345ade172313d558a4e61b4377e31b8"+  , "ed4e28f7cd13a7fe3f72a409bc3bdabfe0ba47a6d861e21f64d2fac706dab18b"+  , "3e546df4"+  ]++-- route-blinding-test.json ----------------------------------------------++-- | Bob's node id.+rbBobNodeId :: BS.ByteString+rbBobNodeId =+  "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c"++-- | Bob's encoded encrypted_data_tlv.+rbBobTlvs :: BS.ByteString+rbBobTlvs = BS.concat+  [ "011a000000000000000000000000000000000000000000000000000002080000"+  , "0000000006c10a0800240000009627100c06000b69e505dc0e00fd0231031234"+  , "56"+  ]++-- | Path private key at Bob (the session key for the first hop of a path).+rbBobPathPrivKey :: BS.ByteString+rbBobPathPrivKey =+  "0202020202020202020202020202020202020202020202020202020202020202"++-- | Path key at Bob.+rbBobPathKey :: BS.ByteString+rbBobPathKey =+  "024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766"++-- | Shared secret at Bob.+rbBobSharedSecret :: BS.ByteString+rbBobSharedSecret =+  "76771bab0cc3d0de6e6f60147fd7c9c7249a5ced3d0612bdfaeec3b15452229d"++-- | rho key at Bob.+rbBobRho :: BS.ByteString+rbBobRho = "ba217b23c0978d84c4a19be8a9ff64bc1b40ed0d7ecf59521567a5b3a9a1dd48"++-- | Bob's encrypted_data.+rbBobEncryptedData :: BS.ByteString+rbBobEncryptedData = BS.concat+  [ "cd4100ff9c09ed28102b210ac73aa12d63e90852cebc496c49f57c49982088b4"+  , "9f2e70b99287fdee0aa58aa39913ab405813b999f66783aa2fe637b3cda91ffc"+  , "0913c30324e2c6ce327e045183e4bffecb"+  ]++-- | Bob's blinded node id.+rbBobBlindedNodeId :: BS.ByteString+rbBobBlindedNodeId =+  "03da173ad2aee2f701f17e59fbd16cb708906d69838a5f088e8123fb36e89a2c25"++-- | Carol's node id.+rbCarolNodeId :: BS.ByteString+rbCarolNodeId =+  "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007"++-- | Carol's encoded encrypted_data_tlv.+rbCarolTlvs :: BS.ByteString+rbCarolTlvs = BS.concat+  [ "020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e18"+  , "34604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e"+  , "00"+  ]++-- | Path private key at Carol (the session key for the first hop of a path).+rbCarolPathPrivKey :: BS.ByteString+rbCarolPathPrivKey =+  "0a2aa791ac81265c139237b2b84564f6000b1d4d0e68d4b9cc97c5536c9b61c1"++-- | Path key at Carol.+rbCarolPathKey :: BS.ByteString+rbCarolPathKey =+  "034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0"++-- | Shared secret at Carol.+rbCarolSharedSecret :: BS.ByteString+rbCarolSharedSecret =+  "dc91516ec6b530a3d641c01f29b36ed4dc29a74e063258278c0eeed50313d9b8"++-- | rho key at Carol.+rbCarolRho :: BS.ByteString+rbCarolRho = "d1e62bae1a8e169da08e6204997b60b1a7971e0f246814c648125c35660f5416"++-- | Carol's encrypted_data.+rbCarolEncryptedData :: BS.ByteString+rbCarolEncryptedData = BS.concat+  [ "cc0f16524fd7f8bb0b1d8d40ad71709ef140174c76faa574cac401bb8992fef7"+  , "6c4d004aa485dd599ed1cf2715f57ff62da5aaec5d7b10d59b04d8a9d77e472b"+  , "9b3ecc2179334e411be22fa4c02b467c7e"+  ]++-- | Carol's blinded node id.+rbCarolBlindedNodeId :: BS.ByteString+rbCarolBlindedNodeId =+  "02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7"++-- | Dave's node id.+rbDaveNodeId :: BS.ByteString+rbDaveNodeId =+  "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"++-- | Dave's encoded encrypted_data_tlv.+rbDaveTlvs :: BS.ByteString+rbDaveTlvs = BS.concat+  [ "0123000000000000000000000000000000000000000000000000000000000000"+  , "0000000000020800000000000002310a060090000000fa0c06000b699105dc0e"+  , "00"+  ]++-- | Path private key at Dave (the session key for the first hop of a path).+rbDavePathPrivKey :: BS.ByteString+rbDavePathPrivKey =+  "0101010101010101010101010101010101010101010101010101010101010101"++-- | Path key at Dave.+rbDavePathKey :: BS.ByteString+rbDavePathKey =+  "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"++-- | Shared secret at Dave.+rbDaveSharedSecret :: BS.ByteString+rbDaveSharedSecret =+  "dc46f3d1d99a536300f17bc0512376cc24b9502c5d30144674bfaa4b923d9057"++-- | rho key at Dave.+rbDaveRho :: BS.ByteString+rbDaveRho = "393aa55d35c9e207a8f28180b81628a31dff558c84959cdc73130f8c321d6a06"++-- | Dave's encrypted_data.+rbDaveEncryptedData :: BS.ByteString+rbDaveEncryptedData = BS.concat+  [ "0fa0a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86561adee1f679ee8"+  , "db3e561d9c43815fd4bcebf6f58c546da0cd8a9bf5cebd0d554802f6c0255e28"+  , "e4a27343f761fe518cd897463187991105"+  ]++-- | Dave's blinded node id.+rbDaveBlindedNodeId :: BS.ByteString+rbDaveBlindedNodeId =+  "036861b366f284f0a11738ffbf7eda46241a8977592878fe3175ae1d1e4754eccf"++-- | Eve's node id.+rbEveNodeId :: BS.ByteString+rbEveNodeId =+  "02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"++-- | Eve's encoded encrypted_data_tlv.+rbEveTlvs :: BS.ByteString+rbEveTlvs = BS.concat+  [ "011a00000000000000000000000000000000000000000000000000000604dead"+  , "beef0c06000b690105dc0e0f020000000000000000000000000000fdffff0206"+  , "c1"+  ]++-- | Path private key at Eve (the session key for the first hop of a path).+rbEvePathPrivKey :: BS.ByteString+rbEvePathPrivKey =+  "62e8bcd6b5f7affe29bec4f0515aab2eebd1ce848f4746a9638aa14e3024fb1b"++-- | Path key at Eve.+rbEvePathKey :: BS.ByteString+rbEvePathKey =+  "03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a"++-- | Shared secret at Eve.+rbEveSharedSecret :: BS.ByteString+rbEveSharedSecret =+  "352a706b194c2b6d0a04ba1f617383fb816dc5f8f9ac0b60dd19c9ae3b517289"++-- | rho key at Eve.+rbEveRho :: BS.ByteString+rbEveRho = "719d0307340b1c68b79865111f0de6e97b093a30bc603cebd1beb9eef116f2d8"++-- | Eve's encrypted_data.+rbEveEncryptedData :: BS.ByteString+rbEveEncryptedData = BS.concat+  [ "da1a7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60724a2e4d3"+  , "f0489ad884f7f3f77149209f0df51efd6b276294a02e3949c7254fbc8b5cab58"+  , "212d9a78983e1cf86fe218b30c4ca8f6d8"+  ]++-- | Eve's blinded node id.+rbEveBlindedNodeId :: BS.ByteString+rbEveBlindedNodeId =+  "021982a48086cb8984427d3727fe35a03d396b234f0701f5249daa12e8105c8dae"++-- | Bob's node private key.+rbBobNodeKey :: BS.ByteString+rbBobNodeKey =+  "4242424242424242424242424242424242424242424242424242424242424242"++-- | Bob's blinded private key.+rbBobBlindedPrivKey :: BS.ByteString+rbBobBlindedPrivKey =+  "d12fec0332c3e9d224789a17ebd93595f37d37bd8ef8bd3d2e6ce50acb9e554f"++-- | Next path key after Bob (override if present).+rbBobNextPathKey :: BS.ByteString+rbBobNextPathKey =+  "034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0"++-- | Carol's node private key.+rbCarolNodeKey :: BS.ByteString+rbCarolNodeKey =+  "4343434343434343434343434343434343434343434343434343434343434343"++-- | Carol's blinded private key.+rbCarolBlindedPrivKey :: BS.ByteString+rbCarolBlindedPrivKey =+  "bfa697fbbc8bbc43ca076e6dd60d306038a32af216b9dc6fc4e59e5ae28823c1"++-- | Next path key after Carol (override if present).+rbCarolNextPathKey :: BS.ByteString+rbCarolNextPathKey =+  "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"++-- | Dave's node private key.+rbDaveNodeKey :: BS.ByteString+rbDaveNodeKey =+  "4444444444444444444444444444444444444444444444444444444444444444"++-- | Dave's blinded private key.+rbDaveBlindedPrivKey :: BS.ByteString+rbDaveBlindedPrivKey =+  "cebc115c7fce4c295dc396dea6c79115b289b8ceeceea2ed61cf31428d88fc4e"++-- | Next path key after Dave (override if present).+rbDaveNextPathKey :: BS.ByteString+rbDaveNextPathKey =+  "03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a"++-- | Eve's node private key.+rbEveNodeKey :: BS.ByteString+rbEveNodeKey =+  "4545454545454545454545454545454545454545454545454545454545454545"++-- | Eve's blinded private key.+rbEveBlindedPrivKey :: BS.ByteString+rbEveBlindedPrivKey =+  "ff4e07da8d92838bedd019ce532eb990ed73b574e54a67862a1df81b40c0d2af"++-- | Next path key after Eve (override if present).+rbEveNextPathKey :: BS.ByteString+rbEveNextPathKey =+  "038fc6859a402b96ce4998c537c823d6ab94d1598fca02c788ba5dd79fbae83589"++-- blinded-payment-onion-test.json ---------------------------------------++-- | Session key.+bpSessionKey :: BS.ByteString+bpSessionKey =+  "0303030303030303030303030303030303030303030303030303030303030303"++-- | Associated data.+bpAssocData :: BS.ByteString+bpAssocData =+  "4242424242424242424242424242424242424242424242424242424242424242"++-- | Public key the onion is encrypted to at Alice.+bpAlicePubKey :: BS.ByteString+bpAlicePubKey =+  "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"++-- | Alice's payload, with its length prefix.+bpAlicePayload :: BS.ByteString+bpAlicePayload = "14020301ae2d04030b6e5e0608000000000000000a"++-- | Public key the onion is encrypted to at Bob.+bpBobPubKey :: BS.ByteString+bpBobPubKey =+  "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c"++-- | Bob's payload, with its length prefix.+bpBobPayload :: BS.ByteString+bpBobPayload = BS.concat+  [ "740a4fcd7b00ff9c09ed28102b210ac73aa12d63e90852cebc496c49f57c499a"+  , "2888b49f2e72b19446f7e60a818aa2938d8c625415b992b8928a7321edb8f7ce"+  , "a40de362bed082ad51acc6156dca5532fb680c21024d4b6cd1361032ca9bd2ae"+  , "b9d900aa4d45d9ead80ac9423374c451a7254d0766"+  ]++-- | Public key the onion is encrypted to at Carol.+bpCarolPubKey :: BS.ByteString+bpCarolPubKey =+  "02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7"++-- | Carol's payload, with its length prefix.+bpCarolPayload :: BS.ByteString+bpCarolPayload = BS.concat+  [ "510a4fcc0f16524fd7f8bb0f4e8d40ad71709ef140174c76faa574cac401bb89"+  , "92fef76c4d004aa485dd599ed1cf2715f570f656a5aaecaf1ee8dc9d0fa1d424"+  , "759be1932a8f29fac08bc2d2a1ed7159f28b"+  ]++-- | Public key the onion is encrypted to at Dave.+bpDavePubKey :: BS.ByteString+bpDavePubKey =+  "036861b366f284f0a11738ffbf7eda46241a8977592878fe3175ae1d1e4754eccf"++-- | Dave's payload, with its length prefix.+bpDavePayload :: BS.ByteString+bpDavePayload = BS.concat+  [ "510a4f0fa1a72cff3b64a3d6e1e4903cf8c8b0a17144aeb249dcb86561adee1f"+  , "679ee8db3e561d9e49895fd4bcebf6f58d6f61a6d41a9bf5aa4b045343785663"+  , "2e8255c351873143ddf2bb2b0832b091e1b4"+  ]++-- | Public key the onion is encrypted to at Eve.+bpEvePubKey :: BS.ByteString+bpEvePubKey =+  "021982a48086cb8984427d3727fe35a03d396b234f0701f5249daa12e8105c8dae"++-- | Eve's payload, with its length prefix.+bpEvePayload :: BS.ByteString+bpEvePayload = BS.concat+  [ "6002030186a004030b6dc80a4fda1c7e5f7881219884beae6ae68971de73bab4"+  , "c3055d9865b1afb60722a63c688768042ade22f2c22f5724767d171fd221d3e5"+  , "79e43b354cc72e3ef146ada91a892d95fc48662f5b158add0af457da12030249"+  , "f0"+  ]++-- | Onion received by Alice.+bpAliceOnion :: BS.ByteString+bpAliceOnion = BS.concat+  [ "0002531fe6068134503d2723133227c867ac8fa6c83c537e9a44c3c5bdbdcb1f"+  , "e337dadf610256c6ab518495dce9cdedf9391e21a71dada75be905267ba82f32"+  , "6c0513dda706908cfee834996700f881b2aed106585d61a2690de4ebe5d56ad2"+  , "013b520af2a3c49316bc590ee83e8c31b1eb11ff766dad27ca993326b1ed582f"+  , "b451a2ad87fbf6601134c6341c4a2deb6850e25a355be68dbb6923dc89444fdd"+  , "74a0f700433b667bda345926099f5547b07e97ad903e8a01566a78ae17736623"+  , "9e793dac719de805565b6d0a1d290e273f705cfc56873f8b5e28225f7ded7a1d"+  , "4ceffae63f91e477be8c917c786435976102a924ba4ba3de6150c829ce01c254"+  , "28f2f5d05ef023be7d590ecdf6603730db3948f80ca1ed3d85227e64ef77200b"+  , "9b557f427b6e1073cfa0e63e4485441768b98ab11ba8104a6cee1d7af7bb5ee9"+  , "c05cf9cf4718901e92e09dfe5cb3af336a953072391c1e91fc2f4b92e124b38e"+  , "0c6d17ef6ba7bbe93f02046975bb01b7f766fcfc5a755af11a90cc7eb3505986"+  , "b56e07a7855534d03b79f0dfbfe645b0d6d4185c038771fd25b800aa26b2ed2e"+  , "30b1e713659468618a2fea04fcd0473284598f76b11b0d159d343bc9711d3bea"+  , "8d561547bcc8fff12317c0e7b1ee75bcb8082d762b6417f99d0f71ff7c060f6b"+  , "564ad6827edaffa72eefcc4ce633a8da8d41c19d8f6aebd8878869eb518ccc16"+  , "dccae6a94c690957598ce0295c1c46af5d7a2f0955b5400526bfd1430f554562"+  , "614b5d00feff3946427be520dee629b76b6a9c2b1da6701c8ca628a69d6d40e2"+  , "0dd69d6e879d7a052d9c16f544b49738c7ff3cdd0613e9ed00ead7707702d1a6"+  , "a0b88de1927a50c36beb78f4ff81e3dd97b706307596eebb363d418a891e1cb4"+  , "589ce86ce81cdc0e1473d7a7dd5f6bb6e147c1f7c46fa879b4512c25704da6cd"+  , "bb3c123a72e3585dc07b3e5cbe7fecf3a08426eee8c70ddc46ebf98b0bcb14a0"+  , "8c469cb5cfb6702acc0befd17640fa60244eca491280a95fbbc5833d26e4be70"+  , "fcf798b55e06eb9fcb156942dcf108236f32a5a6c605687ba4f037eddbb1834d"+  , "cbcd5293a0b66c621346ca5d893d239c26619b24c71f25cecc275e1ab24436ac"+  , "01c80c0006fab2d95e82e3a0c3ea02d08ec5b24eb39205c49f4b549dcab7a889"+  , "62336c4624716902f4e08f2b23cfd324f18405d66e9da3627ac34a6873ba2238"+  , "386313af20d5a13bbd507fdc73015a17e3bd38fae1145f7f70d7cb8c5e1cdf9c"+  , "f06d1246592a25d56ec2ae44cd7f75aa7f5f4a2b2ee49a41a26be4fab3f3f2ce"+  , "b7b08510c5e2b7255326e4c417325b333cafe96dde1314a15dd6779a7d5a8a40"+  , "622260041e936247eec8ec39ca29a1e18161db37497bdd4447a7d5ef3b8d22a2"+  , "acd7f486b152bb66d3a15afc41dc9245a8d75e1d33704d4471e417ccc8d31645"+  , "fdd647a2c191692675cf97664951d6ce98237d78b0962ad1433b5a3e49ddddbf"+  , "57a391b14dcce00b4d7efe5cbb1e78f30d5ef53d66c381a45e275d2dcf6be559"+  , "acb3c42494a9a2156eb8dcf03dd92b2ebaa697ea628fa0f75f125e4a7daa10f8"+  , "dcf56ebaf7814557708c75580fad2bbb33e66ad7a4788a7aaac792aaae76138d"+  , "7ff09df6a1a1920ddcf22e5e7007b15171b51ff81799355232ce39f7d5ceeaf7"+  , "04255d790041d6390a69f42816cba641ec81faa3d7c0fdec59dfe4ca41f31a69"+  , "2eaffc66b083995d86c575aea4514a3e09e8b3a1fa4d1591a2505f253ad0b6bf"+  , "d9d87f063d2be414d3a427c0506a88ac5bdbef9b50d73bce876f85c196dca435"+  , "e210e1d6713695b529ddda3350fb5065a6a8288abd265380917bac8ebbc7d5ce"+  , "d564587471dddf90c22ce6dbadea7e7a6723438d4cf6ac6dae27d033a8cadd77"+  , "ab262e8defb33445ddb2056ec364c7629c33745e2338"+  ]++-- | Onion received by Bob.+bpBobOnion :: BS.ByteString+bpBobOnion = BS.concat+  [ "000280caa47c2a0ea677f6a77529e46caa04212153a8d5f829bee1e7339b17e2"+  , "e2a9a3461d10472364a4ff12344beb6df96fb0c38ec47d1e956ddff5a665190f"+  , "cca5ed02c3a3903fd8bbd4a4b95b197867c378b67b08f0624cfe80734ba51286"+  , "9c0fa22099beb1f6f1ea325b07ce7449736d7ffad79178b428d8ea2d7bc6578f"+  , "12dbd788ef933f3b5ba352797c41f6786c3820c96726acf8bddf2cfa5d9c617d"+  , "2b0bd5ab7b93f7964c98f44cf47db8422f47d11100236a29579f1cafcd38bd97"+  , "9814e1d2bf6d625edf50e1e21bfaf6268e3180dd7aafd3892da281c6dd53c1c3"+  , "66d0fdaf670b6ad84a38d6e8a3f4a80d132d686fd3b7443bc2250023bdb93031"+  , "90f74c9220481cf99da30b5ec2bdb5a49028f5014e3eaeaa48429a0c78ebd3bb"+  , "7c7d582c22b7d547cd269f0c4490373a81bf92687e73dac2075b4bda189ce0be"+  , "225f5f510655e37a6e724a1415bede0a076b92a882cc2a82878ba67aaedf7145"+  , "4eb42b7f8638df8e21d5f708006e5112e2dc0a4afbcfed9f2c7959be812853ca"+  , "8e313fbc99a0f38f1ee4479c96ccb836632b0808401db159bd2637f7a6640132"+  , "41e4664e994a0a9a3940115a702c60381e66d291e1ade1be2802e1226e311e32"+  , "01a7c9682b6bc4354caff3d439adb1dfee53ad3fb3dd5e169d64796853bb3231"+  , "29f41213b166a7cac00f728c3e33bd7e59aa2ac0d1341cdb1532b507a0f446e5"+  , "1022a882ac16405442347b70f78c9b6e122f8e70096a4fae4c0405db5b869e0b"+  , "7b59b09519c4dbf4d4980483906e837da0bee93f668ffaad37d6a4764211a02f"+  , "95ad2dc2d942c198796741c20a3baf8efb5a53bd9c1a0148318d60a97d0013ab"+  , "63269097ea295d62c1426d064f0b31c02e74a348ee0509998e701069f5a1e0c1"+  , "086aed38d2ec87da69fb57a992d88ace3b4a16b0960f5a94936e2e684a9926cf"+  , "4f911969a2a5d31fed0c7616d30197848253170e51274278873b11f3f5cc1b04"+  , "b14aa5812524e4d86cbf08306c2aa671288324d7a009b2be533b1d7d0ce6defe"+  , "eb630b86a9655f1e6424fcb559ed67457c115fba0d0719374802ea68fab299fd"+  , "3f273be86fa3d2e7456020db2f47c6ec16c21ce6ec65de495e20af1941a5dcd6"+  , "5d910c1cb93f22e1318c173c645c81aed681c9704a8a541ac3d6ff604f46d026"+  , "0468acbfec1b771b9eb8cd49a2124468dae786571895a569aae18438eaee6343"+  , "ab2634823119fa2439634645d12e3b4a748b9cc0398b8416a834eb5d9e5cf619"+  , "bbfaba4894d1c574c738caf530d0862f4cc75eb52bd3921d2d9edb09940edb1e"+  , "3776423b0046d870ccdcc5d61f72e0440b97a93eeef21fb246a779d339be301a"+  , "5971400749d6cc9911dfbf9de8ae86fac83c860fdd0e2bfa40af37c99d50e50f"+  , "d6e5ae86597a201112ed404042b55e132f243dec481a2adc1d5e4b71e1efdea8"+  , "06ea900b2907ce877742d5ecf700ff3640f737863d0dd7207e462ee8d0e17d52"+  , "047a88ae7446f419560d23968bf64957949e36953155b0ac2511c66be2890b40"+  , "36329a21e132efb635297a64431899e0c351e50c6682c9b4d79b5d122466d02c"+  , "d84f206369417d9c194a9349d3c631d72eb7857a9cd542906fc02ad6cdcf9bcf"+  , "25ace3d826b6623fa5164351e14d3f0de5c8445a2ba3aae26595d0e31c3e307c"+  , "1d56d4274f61f056145c1b8d6880872b9b10a8bfa4a923cad2edbcf5c50eba48"+  , "936ed2bcc0be60eb721a74b46704aaae5ad24e2797852195dfacbb30a777d33b"+  , "63d4dc4f35cfbe5e88fd1944c55a54fd53581446ea061ad29f4671da819ad748"+  , "8c5dfc700f5f7a1b2af0d6a6e9d9ffc570a6d3209614ab4dc43728f3f0cd7eb4"+  , "ce36ccd98936bbcbd32627384434bd01e9c0f93b2a5173fba184685e19b9af78"+  , "afe876aa4e4b4242382b293133771d95a2bd83fa9c62"+  ]++-- | Next path key after Bob.+bpBobNextPathKey :: BS.ByteString+bpBobNextPathKey =+  "034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0"++-- | Onion received by Carol.+bpCarolOnion :: BS.ByteString+bpCarolOnion = BS.concat+  [ "000288b48876fb0dc0d7375233ccaf2910dc0dc81ba52e5a7906f00d75e0d58d"+  , "bd4bb7c2714870529410735f0951e72cbe981e2e167c0d8f3de33a36e39e7846"+  , "5aea2acad1e23c78b6fd342d63e37d214c912b4a0be344618f779138edc1b42a"+  , "5ca3218ca2fea4be427f6cd0d387160db2bf6c2ba8e82941c8cf3626bd6bed71"+  , "87f633012ef49df38f6b12963cb639e9eed1b9d269dcebcbd0b25287aa536ec8"+  , "5e7320b02e193122199a745ccbaaebd37f5d4b71f52f9b50feeb793eeef56924"+  , "a046bc5e7003f6253e0284a8d3fe2e42c3564050f1e753cd32cc258ac0ffa6e0"+  , "5eecad5ba1286f78252e60dd884a65405ab673a85ba52adfa65c1086d4bb37ba"+  , "2e0848adb2b04379775ad798492b14e8997f30ffa9cf5d432bdf5b246fce008f"+  , "d876399beed827db58195f4f6192f6ff4ec63cb17fdcb497cb7aec26846a71dd"+  , "8dca02fc3bb14dd7231a4d62a981bec54b71eb20331096dfa214a0ff4489ee96"+  , "db663826ae8c850e9f06baa52a47b8eb576363f97e742aab2dc616acc6e74588"+  , "e1d2ac16694febc90abaf5b1c684163c0e615a68d32633f01934adc8c6bf91fa"+  , "3fd7aad033b7596d60402494e45e2c1632c40f7bfbd88a81a896a1d28ed6338c"+  , "83e1eeaa467945d59998eb456c95f94bf1892e8f326ec2d5e0196b7073f106fe"+  , "bc6ab8ca5bcc23f77ffc819bc1b5debce418ccc7d8391bbf33bceee6110beba1"+  , "70121bd99f54c956e64970bdab31227b03ee0ea3f01fbd9bd74015f6f82d04fa"+  , "b072e8f85f4370d09f41ee3e48eb959767bd989abb4eea42c4daa0437a7f747d"+  , "7f9b70eb87b9f9b0b6f283b8205912601a432999b8869fd9fe5bad3572edac24"+  , "da7184f9298f21ff60923db277264d29c846dd2f228f6fc53b6b60364237de64"+  , "773f803f174ed10229c374f603ccc5fd3a62cb413ffe6f5630dc646bb33f231b"+  , "2350537ec39e5d3f2fe1a1cb019ed0b18ad14019cad27afcca8ad70387ca1103"+  , "94c0432774f1aa1fa404b2e086c84a55388d3bd102501c78ef925cce89d76fa0"+  , "4c3f20f2d1f0ce507ac8b37b7913e3949ba12bbc5a4f6bac37c2415622d365bc"+  , "8b83709a28e3d46f3850c89a3ff4d027fef6e3e4ce5c6c85f663c7eaec3c9730"+  , "106fb82f53249a905533cfabee812aae51965b24b42f7ab471967bc8e73354e6"+  , "9141ee26a1f03684d5fb9c256a34de8257210e0390dd3962db521ae0a3bdab28"+  , "300610ab2a634b699e5f092da5a061609ef6414bd805c8171f54ad6f285fb64c"+  , "e0becca0b61188badcf8ef21190dad629e3fb3e89f55ebba829919540ebf5f8a"+  , "e4283836d3c9133c1ca3365f6b9394916730411650686e0c2ab9c53b6cda9efd"+  , "d5cfcb53ba9b6962bb6aa49d0a83a87460b60a9c7d2643ee99afe652883795f1"+  , "4014ec5df61b1e30c041c1fa6487f3c82f1ded5f83ffbef5017e197b7fb77be3"+  , "b36e284a15e57d45bf9316dcaf97eb78ee4642b731ba05c5063bce1333fab4af"+  , "6da97c80a96ee599b4df823efbedc250c0abba9783da7ddf2414b2a4774ff288"+  , "0a7dc6791103e18b8631e39743cf9e87aed71700daa5dc72fdae520324741f92"+  , "ea3d510ff555dea5e45f15cda87272d4559a12d4777680acb06993840e3c748d"+  , "a82c16cae556015fb2acd0335da11a3388575394048ab71199793ab706abc9d6"+  , "8add2075d79a5cc0f779845ee8b98951be61fd293d6c15b9d4653935bf17cf50"+  , "bd31f8b79e60dba0e7fd6864754fd94262485a4f65e7eb3e1922f51b1a4dd2b4"+  , "fd2c20d94d1213fbe90bd603dfc7e15176382e3ce0f43f980d44d23bf3c57f54"+  , "a15f42c171a8f2511e28ac178c6f01396e50397a57ffb09c5e6c315bd3ae7983"+  , "577c1a0386c6d5d9a2223438e321b0fedfdee58fa452d57dc11a256834bb49ac"+  , "9deeec88e4bf563c7340f44a240caec941c7e50f09cf"+  ]++-- | Next path key after Carol.+bpCarolNextPathKey :: BS.ByteString+bpCarolNextPathKey =+  "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"++-- | Onion received by Dave.+bpDaveOnion :: BS.ByteString+bpDaveOnion = BS.concat+  [ "0003f25471c0f2ff549a7fd7859100306bb6c294c209f34c77f782897f184b96"+  , "7c498efc246bdb8e060a6d1cf8dd0d4d732e33311fb96c9e9f1274005fa3d08b"+  , "41704a1b7224c6300a7caead7baa0a8263eba2e0de6956ee8e4a1958264f47e4"+  , "cf20d194eb576f5bd249ee4fece563f80fd76dc3eaca8f956188406d83195752"+  , "b5c90c4b2a5e7ac3a8d5c62b17b551aff48ef6842a7e9326832c9a4a2fd41501"+  , "1150a9e71beb901fd9747bac8add1c694b612730dc86b5b19a0bbbc675947a95"+  , "3316e3303d7b30c182f94def9206671edac9a3ec3e52d28fc28247a1c73ab751"+  , "bf61c82c3950f617e758f79bd0ba294defb20466eaf1e801462046baad3aec3e"+  , "5b8868a7b037f23d73a47a7e74c77107334f37388cff863e452820c61d89728f"+  , "a75c84bc7cdfc06dcdd1911f5f803353926d073efd65251380e174913aae0331"+  , "8ea5b6f0ec83998c55ab99bef62803ea2da9f6d1ea892b90efc4f8ffb685a520"+  , "1a781da2e6ac5923645638c9709ae32171a00c0cd3d8c7eedfb06b4eedc7d3e5"+  , "66987e2e3805a038f21d78ded5d6c7137a5e8e592f3180ee4d5f4e1289176f67"+  , "fc38690d0958bc82e240b72b10577f340f1e14b8633f0b6d9729ff4618be2a97"+  , "2400a015a871ba33be70335f652a8d70f2bd32421d6ac2af781d667dad787d6a"+  , "ef4505a15d046579e46eebe757444cffca6d0610f0dd36a7ce57af969bd0c3f7"+  , "006298ef406a25f689daf58f875d44d2423ebf195b503f11c37c506ea6abe50a"+  , "463f7bb5e9b964604d832724de768513f6b38bf71715e1feea8a6e86797788d4"+  , "87146891564919da1372016ed8f08c7fcbff66a4a65a3d0fcd8e3daac6eba41f"+  , "5d65ef2d8075364a9e78b3a273549f6eac4abb72e912e237990367e0d43e8994"+  , "5f8ac3907be5a6c662139485a50cb5ce3f0ba08586c39f6c515368ec3f91b722"+  , "95f1b7a73a9df322ae9a45d363d6c616be3300083764cbdee31221f25a318f09"+  , "5feacb09f957c96db30fccca47a0215b576c3ed925a0bad05d6400abe318c11f"+  , "36628c387a4ee38832182cd44b3cd48e5422c1f1e3b57218dfe72c611f541512"+  , "7720e60f6e2400607e61841b76de1704bcbeb0daf1377ccb2253916de2b6d490"+  , "bb71ba0a44fea2e94f2423d723934557d5905e01b2b80232a884e258d46dc92e"+  , "a11e0818d0ece5b914f02049866e151801ab8c9aea155479b354dc91151fb9ba"+  , "43277458f9760dd859faaa139e3b9ab36a1dbc36a93ef2c90598b20cb30ef3c4"+  , "f23a2d6178b4d1da668fb328a25d84d30a132d9f2a6a988cbe2e5c2be01cb6db"+  , "4b4725a50d6cdacf5fb083e7d650a25bec1407fbc047d26076c7596429a29606"+  , "ad527e97ef0824ad6c1b05831a3e5b71c63a528918a3301cdd4061fc1fcce3da"+  , "601961f2602a2b002ac8404125c2d52666263858a923e197efcda873c32d8689"+  , "7352e4f2264ad6a1b48acc0fe78ff55cb442cb2bb5fa2880810e1d00aa024705"+  , "7fb80b7ed36cf9647af41b44ee4a63ee2d6f652526404572520a7d2d9dcde4e6"+  , "2df0c3be89f8471550594cdd16a51a9cacc58729c092c68506162fe65edc2314"+  , "055d389f724ced189d826a546b5c4d08a43d977b3cf033de5760b71a7cc38ee5"+  , "851592031aafb467a89b3b6c7ed67b15d44c48d6baedce3e95e08ec7c55038f3"+  , "eba90ccb900895734f0fb7efe54961ce493369cc56416898a9bed7c2482871c1"+  , "5a7f1eb5ed17c33657fc31333539c2dfb59461af09e7049228113b5c9feea5a6"+  , "e9959c18c51b19c90995afb9c76f2c0c820964cd7989c993a73925818a656c6a"+  , "18dcd1a1e3782b2eae06dd5a41250ec2d1c203626ab9920c1673339eff04b1eb"+  , "0cab85ef5909f571f9b83cdf21697c9f5cfa1c76e7bca955510e2126b3bb989a"+  , "4ac21cf948f965e48bc363d2997437797b4f770e8b65"+  ]++-- | Next path key after Dave.+bpDaveNextPathKey :: BS.ByteString+bpDaveNextPathKey =+  "03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a"++-- | Onion received by Eve.+bpEveOnion :: BS.ByteString+bpEveOnion = BS.concat+  [ "0002ef43c4dfe9aee14248c445406ac7980474ce106c504d9025f57963739130"+  , "adfd06eb26201baee8866af2d1b7a7ab26595349dad002af0590193aaa8f400a"+  , "b394f5994ec831aeeecb64421c566e3556cbdd7e7e50deb1fc49fd5e007308ab"+  , "6494415514abff978899623f9b6065ca1e243bb78170118e8b8c8b53b750b59c"+  , "c1ec017d167adbb3aabab7c2d84fbf94f5d827239f4c2b9d2c3cfe68fe5641f2"+  , "5e386202a4b6edff2a71e700229df7230c8ca31bd5588f04799e9640c9c20a47"+  , "cba713f3cc5ad3202e14bb520880f2a8409d8e7835cae21b48a651c2d47fe6af"+  , "785889ab98f1416f6e4ad67a66ae681e9a8828bad3f9b6890221c4a7ec80531d"+  , "6b63eb30843f613ce644795bc8bcee60e8f7b36f3fd04de762f103c52efaf36a"+  , "2f3bbbaac482d6271dc4180c10bcc076c04d06ea7fd8fb6a647e0e10523b05da"+  , "2d89e4139fb55c2315cd01bdcbd57587fef8442d7ff5620630fd2d2e79739d90"+  , "be811bf2cba60415d6cba2cea14ba1859f3122cd905c4e12e3e2a1ab6fab54b2"+  , "ec40e434626e2d3c3195c02c82a8bd64d226c2328ac72ca12197d9908eaf5433"+  , "3717448ce6ed73adc0ac05e2ee1d735131d87918beb8995993dc8f63fe10f2c8"+  , "eba2be7ab8bb44d9f78f59ef3e4c180bd75e4eef2381450c6f0480d543997305"+  , "f1d07815993b5aca8d88d474966d9abec93bb069a16aa2da75b87f94576e01d0"+  , "8a17d3e0e3d0370f010733a7d7affb12cdf94c259a62607fce71003535c47273"+  , "05de5ff7bba3840922844b3a45f62c29715fccf440517ef121450f6962396fba"+  , "9b07036d085582405dcae6ee95964b66bc7c85b8d02d90091500db3cebf6de58"+  , "4f86b7b55335a8c9aa26381b00747f055cc458a2cadfccf9c29702bf941447be"+  , "aca6583cca09492a57d4b03b2ca00dbaf41dfd6a9b249381626a7debe475735a"+  , "7e39e77a363eccf14669046f656cc09ad448da8d8b545e6a604f46dc481786d0"+  , "9a94c63cf23f49ba367d2929466364dbce2a8ffce3dadf8f4cef8a56e1fefa1a"+  , "3304a953fe83018e57d8a95694b02d994fea2630a9a3d5f1e2f6d6142d503ec4"+  , "152871f7122d7e566a03261f554639e7a759e0e73846f71d5cace37d91336fc9"+  , "ca9396bf64ca2cf45fa2db779b3b5c63b04f1c0c1fb79fdfcf5a82b0202df934"+  , "ae1720a7ce1e047cbec3f82737b50168c974f4623cacce87e3f5bd5232caca79"+  , "56d28ffedcf11ac5998662c5f6b13c6126584ca2e894d3fcbad4d130bbe22e88"+  , "a135e0020cdd43853e0b3af3800e9544854d211e873cf68ab683578d501d69ec"+  , "5dc7fce42ac436d58243880c1b88227b0681c6c9dd8a8ad0793202b15ab63b78"+  , "7b748e258da3e68d0e649fc4ac081a71de8adbc891c113d5f722686b6ac4ed9e"+  , "3cc247bc4a4643416f480627e9de20f7307f434a499f5c6951c2e8b3ff51d455"+  , "bf65ceb5ee3dee47b968ac2642e13d8a68f903b73627c2e75788fecca5836371"+  , "a908eea4f1ea44db2315bc185f77e478efeaaa4da2da13fe7aeaa79ed1d04876"+  , "a8b2b7b333c5de8c4c9a50274c2eb7b9bd2a3630c57173174781fc9785235f83"+  , "0cefa1c82080eaffdef257f18eedc9ddfd25a696a11a3dc56cd836be72f5f4a2"+  , "cbb6316d5d3b1ad91a7ec7d877f28d2c29a5525b0b24362699281b0e3b48f38c"+  , "af1085045fe9089f9e6fb29e4b47aa4cecf68c9bf72073469bd9beeea5e88bfe"+  , "554cb6a81231149ba7fe7784c154fd8b0f9179ecdf1e9fd5c2939ec1ab16df9c"+  , "be9359101ebce933d4f65d3f66f87afaecfe9c046b52f4878b6c430329df7bd8"+  , "79fba8864fcbd9b782bf545734699b9b5a66b466dcedc0c9368803b5b0f12329"+  , "50cef398ad3e057a5db964bd3e5c8a5717b30b41601a4f11ad63afe404cb6f1e"+  , "8ea5fd7a8e085b65ca5136146febf4d47928dcc9a9e0"+  ]++-- | Next path key after Eve.+bpEveNextPathKey :: BS.ByteString+bpEveNextPathKey =+  "038fc6859a402b96ce4998c537c823d6ab94d1598fca02c788ba5dd79fbae83589"