packages feed

ppad-bolt1 0.0.1 → 0.1.0

raw patch · 11 files changed

+2001/−3215 lines, 11 filesdep +ppad-bolt9dep ~deepseqPVP ok

version bump matches the API change (PVP)

Dependencies added: ppad-bolt9

Dependency ranges changed: deepseq

API changes (from Hackage documentation)

- Lightning.Protocol.BOLT1: DecodeInvalidChannelId :: DecodeError
- Lightning.Protocol.BOLT1: DecodeInvalidExtension :: !TlvError -> DecodeError
- Lightning.Protocol.BOLT1: DecodeInvalidLength :: DecodeError
- Lightning.Protocol.BOLT1: Envelope :: !MsgType -> !ByteString -> !Maybe TlvStream -> Envelope
- Lightning.Protocol.BOLT1: InitNetworks :: ![ChainHash] -> InitTlv
- Lightning.Protocol.BOLT1: InitRemoteAddr :: !ByteString -> InitTlv
- Lightning.Protocol.BOLT1: MsgErrorVal :: !Error -> Message
- Lightning.Protocol.BOLT1: MsgInitVal :: !Init -> Message
- Lightning.Protocol.BOLT1: MsgPeerStorageRet :: MsgType
- Lightning.Protocol.BOLT1: MsgPeerStorageRetrievalVal :: !PeerStorageRetrieval -> Message
- Lightning.Protocol.BOLT1: MsgPeerStorageVal :: !PeerStorage -> Message
- Lightning.Protocol.BOLT1: MsgPingVal :: !Ping -> Message
- Lightning.Protocol.BOLT1: MsgPongVal :: !Pong -> Message
- Lightning.Protocol.BOLT1: MsgUnknown :: !Word16 -> MsgType
- Lightning.Protocol.BOLT1: MsgWarningVal :: !Warning -> Message
- Lightning.Protocol.BOLT1: TlvInvalidKnownType :: !Word64 -> TlvError
- Lightning.Protocol.BOLT1: TlvLengthExceedsBounds :: TlvError
- Lightning.Protocol.BOLT1: TlvNonMinimalEncoding :: TlvError
- Lightning.Protocol.BOLT1: [envExtension] :: Envelope -> !Maybe TlvStream
- Lightning.Protocol.BOLT1: [envPayload] :: Envelope -> !ByteString
- Lightning.Protocol.BOLT1: [envType] :: Envelope -> !MsgType
- Lightning.Protocol.BOLT1: [errorChannelId] :: Error -> !ChannelId
- Lightning.Protocol.BOLT1: [errorData] :: Error -> !ByteString
- Lightning.Protocol.BOLT1: [initFeatures] :: Init -> !ByteString
- Lightning.Protocol.BOLT1: [initGlobalFeatures] :: Init -> !ByteString
- Lightning.Protocol.BOLT1: [initTlvs] :: Init -> ![InitTlv]
- Lightning.Protocol.BOLT1: [peerStorageBlob] :: PeerStorage -> !ByteString
- Lightning.Protocol.BOLT1: [peerStorageRetrievalBlob] :: PeerStorageRetrieval -> !ByteString
- Lightning.Protocol.BOLT1: [pingIgnored] :: Ping -> !ByteString
- Lightning.Protocol.BOLT1: [pingNumPongBytes] :: Ping -> {-# UNPACK #-} !Word16
- Lightning.Protocol.BOLT1: [pongIgnored] :: Pong -> !ByteString
- Lightning.Protocol.BOLT1: [tlvType] :: TlvRecord -> {-# UNPACK #-} !Word64
- Lightning.Protocol.BOLT1: [tlvValue] :: TlvRecord -> !ByteString
- Lightning.Protocol.BOLT1: [warningChannelId] :: Warning -> !ChannelId
- Lightning.Protocol.BOLT1: [warningData] :: Warning -> !ByteString
- Lightning.Protocol.BOLT1: allChannels :: ChannelId
- Lightning.Protocol.BOLT1: chainHash :: ByteString -> Maybe ChainHash
- Lightning.Protocol.BOLT1: channelId :: ByteString -> Maybe ChannelId
- Lightning.Protocol.BOLT1: data Envelope
- Lightning.Protocol.BOLT1: data InitTlv
- Lightning.Protocol.BOLT1: data MsgType
- Lightning.Protocol.BOLT1: decodeBigSize :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1: decodeEnvelope :: ByteString -> Either DecodeError (Maybe Message, Maybe TlvStream)
- Lightning.Protocol.BOLT1: decodeEnvelopeWith :: (Word64 -> Bool) -> ByteString -> Either DecodeError (Maybe Message, Maybe TlvStream)
- Lightning.Protocol.BOLT1: decodeMessage :: MsgType -> ByteString -> Either DecodeError (Message, ByteString)
- Lightning.Protocol.BOLT1: decodeMinSigned :: Int -> ByteString -> Maybe (Int64, ByteString)
- Lightning.Protocol.BOLT1: decodeS16 :: ByteString -> Maybe (Int16, ByteString)
- Lightning.Protocol.BOLT1: decodeS32 :: ByteString -> Maybe (Int32, ByteString)
- Lightning.Protocol.BOLT1: decodeS64 :: ByteString -> Maybe (Int64, ByteString)
- Lightning.Protocol.BOLT1: decodeS8 :: ByteString -> Maybe (Int8, ByteString)
- Lightning.Protocol.BOLT1: decodeTlvStream :: ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1: decodeTlvStreamRaw :: ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1: decodeTlvStreamWith :: (Word64 -> Bool) -> ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1: decodeTu16 :: Int -> ByteString -> Maybe (Word16, ByteString)
- Lightning.Protocol.BOLT1: decodeTu32 :: Int -> ByteString -> Maybe (Word32, ByteString)
- Lightning.Protocol.BOLT1: decodeTu64 :: Int -> ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1: decodeU16 :: ByteString -> Maybe (Word16, ByteString)
- Lightning.Protocol.BOLT1: decodeU32 :: ByteString -> Maybe (Word32, ByteString)
- Lightning.Protocol.BOLT1: decodeU64 :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1: encodeBigSize :: Word64 -> ByteString
- Lightning.Protocol.BOLT1: encodeEnvelope :: Message -> Maybe TlvStream -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1: encodeMessage :: Message -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1: encodeMinSigned :: Int64 -> ByteString
- Lightning.Protocol.BOLT1: encodeS16 :: Int16 -> ByteString
- Lightning.Protocol.BOLT1: encodeS32 :: Int32 -> ByteString
- Lightning.Protocol.BOLT1: encodeS64 :: Int64 -> ByteString
- Lightning.Protocol.BOLT1: encodeS8 :: Int8 -> ByteString
- Lightning.Protocol.BOLT1: encodeTlvStream :: TlvStream -> ByteString
- Lightning.Protocol.BOLT1: encodeTu16 :: Word16 -> ByteString
- Lightning.Protocol.BOLT1: encodeTu32 :: Word32 -> ByteString
- Lightning.Protocol.BOLT1: encodeTu64 :: Word64 -> ByteString
- Lightning.Protocol.BOLT1: encodeU16 :: Word16 -> ByteString
- Lightning.Protocol.BOLT1: encodeU32 :: Word32 -> ByteString
- Lightning.Protocol.BOLT1: encodeU64 :: Word64 -> ByteString
- Lightning.Protocol.BOLT1: msgTypeWord :: MsgType -> Word16
- Lightning.Protocol.BOLT1: tlvStream :: [TlvRecord] -> Maybe TlvStream
- Lightning.Protocol.BOLT1: unChainHash :: ChainHash -> ByteString
- Lightning.Protocol.BOLT1: unsafeTlvStream :: [TlvRecord] -> TlvStream
- Lightning.Protocol.BOLT1.Codec: DecodeInsufficientBytes :: DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeInvalidChannelId :: DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeInvalidExtension :: !TlvError -> DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeInvalidLength :: DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeTlvError :: !TlvError -> DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeUnknownEvenType :: !Word16 -> DecodeError
- Lightning.Protocol.BOLT1.Codec: DecodeUnknownOddType :: !Word16 -> DecodeError
- Lightning.Protocol.BOLT1.Codec: EncodeLengthOverflow :: EncodeError
- Lightning.Protocol.BOLT1.Codec: EncodeMessageTooLarge :: EncodeError
- Lightning.Protocol.BOLT1.Codec: data DecodeError
- Lightning.Protocol.BOLT1.Codec: data EncodeError
- Lightning.Protocol.BOLT1.Codec: decodeEnvelope :: ByteString -> Either DecodeError (Maybe Message, Maybe TlvStream)
- Lightning.Protocol.BOLT1.Codec: decodeEnvelopeWith :: (Word64 -> Bool) -> ByteString -> Either DecodeError (Maybe Message, Maybe TlvStream)
- Lightning.Protocol.BOLT1.Codec: decodeError :: ByteString -> Either DecodeError (Error, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodeInit :: ByteString -> Either DecodeError (Init, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodeMessage :: MsgType -> ByteString -> Either DecodeError (Message, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodePeerStorage :: ByteString -> Either DecodeError (PeerStorage, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodePeerStorageRetrieval :: ByteString -> Either DecodeError (PeerStorageRetrieval, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodePing :: ByteString -> Either DecodeError (Ping, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodePong :: ByteString -> Either DecodeError (Pong, ByteString)
- Lightning.Protocol.BOLT1.Codec: decodeWarning :: ByteString -> Either DecodeError (Warning, ByteString)
- Lightning.Protocol.BOLT1.Codec: encodeEnvelope :: Message -> Maybe TlvStream -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodeError :: Error -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodeInit :: Init -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodeMessage :: Message -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodePeerStorage :: PeerStorage -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodePeerStorageRetrieval :: PeerStorageRetrieval -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodePing :: Ping -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodePong :: Pong -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: encodeWarning :: Warning -> Either EncodeError ByteString
- Lightning.Protocol.BOLT1.Codec: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Codec.DecodeError
- Lightning.Protocol.BOLT1.Codec: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Codec.EncodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Codec.DecodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Codec.EncodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Codec.DecodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Codec.EncodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Show.Show Lightning.Protocol.BOLT1.Codec.DecodeError
- Lightning.Protocol.BOLT1.Codec: instance GHC.Show.Show Lightning.Protocol.BOLT1.Codec.EncodeError
- Lightning.Protocol.BOLT1.Message: Envelope :: !MsgType -> !ByteString -> !Maybe TlvStream -> Envelope
- Lightning.Protocol.BOLT1.Message: Error :: !ChannelId -> !ByteString -> Error
- Lightning.Protocol.BOLT1.Message: Init :: !ByteString -> !ByteString -> ![InitTlv] -> Init
- Lightning.Protocol.BOLT1.Message: MsgError :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgErrorVal :: !Error -> Message
- Lightning.Protocol.BOLT1.Message: MsgInit :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgInitVal :: !Init -> Message
- Lightning.Protocol.BOLT1.Message: MsgPeerStorage :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgPeerStorageRet :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgPeerStorageRetrievalVal :: !PeerStorageRetrieval -> Message
- Lightning.Protocol.BOLT1.Message: MsgPeerStorageVal :: !PeerStorage -> Message
- Lightning.Protocol.BOLT1.Message: MsgPing :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgPingVal :: !Ping -> Message
- Lightning.Protocol.BOLT1.Message: MsgPong :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgPongVal :: !Pong -> Message
- Lightning.Protocol.BOLT1.Message: MsgUnknown :: !Word16 -> MsgType
- Lightning.Protocol.BOLT1.Message: MsgWarning :: MsgType
- Lightning.Protocol.BOLT1.Message: MsgWarningVal :: !Warning -> Message
- Lightning.Protocol.BOLT1.Message: PeerStorage :: !ByteString -> PeerStorage
- Lightning.Protocol.BOLT1.Message: PeerStorageRetrieval :: !ByteString -> PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: Ping :: {-# UNPACK #-} !Word16 -> !ByteString -> Ping
- Lightning.Protocol.BOLT1.Message: Pong :: !ByteString -> Pong
- Lightning.Protocol.BOLT1.Message: Warning :: !ChannelId -> !ByteString -> Warning
- Lightning.Protocol.BOLT1.Message: [envExtension] :: Envelope -> !Maybe TlvStream
- Lightning.Protocol.BOLT1.Message: [envPayload] :: Envelope -> !ByteString
- Lightning.Protocol.BOLT1.Message: [envType] :: Envelope -> !MsgType
- Lightning.Protocol.BOLT1.Message: [errorChannelId] :: Error -> !ChannelId
- Lightning.Protocol.BOLT1.Message: [errorData] :: Error -> !ByteString
- Lightning.Protocol.BOLT1.Message: [initFeatures] :: Init -> !ByteString
- Lightning.Protocol.BOLT1.Message: [initGlobalFeatures] :: Init -> !ByteString
- Lightning.Protocol.BOLT1.Message: [initTlvs] :: Init -> ![InitTlv]
- Lightning.Protocol.BOLT1.Message: [peerStorageBlob] :: PeerStorage -> !ByteString
- Lightning.Protocol.BOLT1.Message: [peerStorageRetrievalBlob] :: PeerStorageRetrieval -> !ByteString
- Lightning.Protocol.BOLT1.Message: [pingIgnored] :: Ping -> !ByteString
- Lightning.Protocol.BOLT1.Message: [pingNumPongBytes] :: Ping -> {-# UNPACK #-} !Word16
- Lightning.Protocol.BOLT1.Message: [pongIgnored] :: Pong -> !ByteString
- Lightning.Protocol.BOLT1.Message: [warningChannelId] :: Warning -> !ChannelId
- Lightning.Protocol.BOLT1.Message: [warningData] :: Warning -> !ByteString
- Lightning.Protocol.BOLT1.Message: allChannels :: ChannelId
- Lightning.Protocol.BOLT1.Message: channelId :: ByteString -> Maybe ChannelId
- Lightning.Protocol.BOLT1.Message: data ChannelId
- Lightning.Protocol.BOLT1.Message: data Envelope
- Lightning.Protocol.BOLT1.Message: data Error
- Lightning.Protocol.BOLT1.Message: data Init
- Lightning.Protocol.BOLT1.Message: data Message
- Lightning.Protocol.BOLT1.Message: data MsgType
- Lightning.Protocol.BOLT1.Message: data PeerStorage
- Lightning.Protocol.BOLT1.Message: data PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: data Ping
- Lightning.Protocol.BOLT1.Message: data Pong
- Lightning.Protocol.BOLT1.Message: data Warning
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.ChannelId
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Envelope
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Error
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Init
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Message
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.MsgType
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.PeerStorage
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Ping
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Pong
- Lightning.Protocol.BOLT1.Message: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Message.Warning
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.ChannelId
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Envelope
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Error
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Init
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Message
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.MsgType
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.PeerStorage
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Ping
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Pong
- Lightning.Protocol.BOLT1.Message: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Message.Warning
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.ChannelId
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Envelope
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Error
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Init
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Message
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.MsgType
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.PeerStorage
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Ping
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Pong
- Lightning.Protocol.BOLT1.Message: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Message.Warning
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.ChannelId
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Envelope
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Error
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Init
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Message
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.MsgType
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.PeerStorage
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.PeerStorageRetrieval
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Ping
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Pong
- Lightning.Protocol.BOLT1.Message: instance GHC.Show.Show Lightning.Protocol.BOLT1.Message.Warning
- Lightning.Protocol.BOLT1.Message: messageType :: Message -> MsgType
- Lightning.Protocol.BOLT1.Message: msgTypeWord :: MsgType -> Word16
- Lightning.Protocol.BOLT1.Message: parseMsgType :: Word16 -> MsgType
- Lightning.Protocol.BOLT1.Message: unChannelId :: ChannelId -> ByteString
- Lightning.Protocol.BOLT1.Prim: chainHash :: ByteString -> Maybe ChainHash
- Lightning.Protocol.BOLT1.Prim: data ChainHash
- Lightning.Protocol.BOLT1.Prim: decodeBigSize :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeMinSigned :: Int -> ByteString -> Maybe (Int64, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeS16 :: ByteString -> Maybe (Int16, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeS32 :: ByteString -> Maybe (Int32, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeS64 :: ByteString -> Maybe (Int64, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeS8 :: ByteString -> Maybe (Int8, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeTu16 :: Int -> ByteString -> Maybe (Word16, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeTu32 :: Int -> ByteString -> Maybe (Word32, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeTu64 :: Int -> ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeU16 :: ByteString -> Maybe (Word16, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeU32 :: ByteString -> Maybe (Word32, ByteString)
- Lightning.Protocol.BOLT1.Prim: decodeU64 :: ByteString -> Maybe (Word64, ByteString)
- Lightning.Protocol.BOLT1.Prim: encodeBigSize :: Word64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeLength :: ByteString -> Maybe ByteString
- Lightning.Protocol.BOLT1.Prim: encodeMinSigned :: Int64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeS16 :: Int16 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeS32 :: Int32 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeS64 :: Int64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeS8 :: Int8 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeTu16 :: Word16 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeTu32 :: Word32 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeTu64 :: Word64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeU16 :: Word16 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeU32 :: Word32 -> ByteString
- Lightning.Protocol.BOLT1.Prim: encodeU64 :: Word64 -> ByteString
- Lightning.Protocol.BOLT1.Prim: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.Prim.ChainHash
- Lightning.Protocol.BOLT1.Prim: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.Prim.ChainHash
- Lightning.Protocol.BOLT1.Prim: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.Prim.ChainHash
- Lightning.Protocol.BOLT1.Prim: instance GHC.Show.Show Lightning.Protocol.BOLT1.Prim.ChainHash
- Lightning.Protocol.BOLT1.Prim: unChainHash :: ChainHash -> ByteString
- Lightning.Protocol.BOLT1.TLV: InitNetworks :: ![ChainHash] -> InitTlv
- Lightning.Protocol.BOLT1.TLV: InitRemoteAddr :: !ByteString -> InitTlv
- Lightning.Protocol.BOLT1.TLV: TlvInvalidKnownType :: !Word64 -> TlvError
- Lightning.Protocol.BOLT1.TLV: TlvLengthExceedsBounds :: TlvError
- Lightning.Protocol.BOLT1.TLV: TlvNonMinimalEncoding :: TlvError
- Lightning.Protocol.BOLT1.TLV: TlvNotStrictlyIncreasing :: TlvError
- Lightning.Protocol.BOLT1.TLV: TlvRecord :: {-# UNPACK #-} !Word64 -> !ByteString -> TlvRecord
- Lightning.Protocol.BOLT1.TLV: TlvUnknownEvenType :: !Word64 -> TlvError
- Lightning.Protocol.BOLT1.TLV: [tlvType] :: TlvRecord -> {-# UNPACK #-} !Word64
- Lightning.Protocol.BOLT1.TLV: [tlvValue] :: TlvRecord -> !ByteString
- Lightning.Protocol.BOLT1.TLV: chainHash :: ByteString -> Maybe ChainHash
- Lightning.Protocol.BOLT1.TLV: data ChainHash
- Lightning.Protocol.BOLT1.TLV: data InitTlv
- Lightning.Protocol.BOLT1.TLV: data TlvError
- Lightning.Protocol.BOLT1.TLV: data TlvRecord
- Lightning.Protocol.BOLT1.TLV: data TlvStream
- Lightning.Protocol.BOLT1.TLV: decodeTlvStream :: ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1.TLV: decodeTlvStreamRaw :: ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1.TLV: decodeTlvStreamWith :: (Word64 -> Bool) -> ByteString -> Either TlvError TlvStream
- Lightning.Protocol.BOLT1.TLV: encodeInitTlvs :: [InitTlv] -> TlvStream
- Lightning.Protocol.BOLT1.TLV: encodeTlvRecord :: TlvRecord -> ByteString
- Lightning.Protocol.BOLT1.TLV: encodeTlvStream :: TlvStream -> ByteString
- Lightning.Protocol.BOLT1.TLV: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.TLV.InitTlv
- Lightning.Protocol.BOLT1.TLV: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.TLV.TlvError
- Lightning.Protocol.BOLT1.TLV: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.TLV.TlvRecord
- Lightning.Protocol.BOLT1.TLV: instance Control.DeepSeq.NFData Lightning.Protocol.BOLT1.TLV.TlvStream
- Lightning.Protocol.BOLT1.TLV: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.TLV.InitTlv
- Lightning.Protocol.BOLT1.TLV: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.TLV.TlvError
- Lightning.Protocol.BOLT1.TLV: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.TLV.TlvRecord
- Lightning.Protocol.BOLT1.TLV: instance GHC.Classes.Eq Lightning.Protocol.BOLT1.TLV.TlvStream
- Lightning.Protocol.BOLT1.TLV: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.TLV.InitTlv
- Lightning.Protocol.BOLT1.TLV: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.TLV.TlvError
- Lightning.Protocol.BOLT1.TLV: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.TLV.TlvRecord
- Lightning.Protocol.BOLT1.TLV: instance GHC.Generics.Generic Lightning.Protocol.BOLT1.TLV.TlvStream
- Lightning.Protocol.BOLT1.TLV: instance GHC.Show.Show Lightning.Protocol.BOLT1.TLV.InitTlv
- Lightning.Protocol.BOLT1.TLV: instance GHC.Show.Show Lightning.Protocol.BOLT1.TLV.TlvError
- Lightning.Protocol.BOLT1.TLV: instance GHC.Show.Show Lightning.Protocol.BOLT1.TLV.TlvRecord
- Lightning.Protocol.BOLT1.TLV: instance GHC.Show.Show Lightning.Protocol.BOLT1.TLV.TlvStream
- Lightning.Protocol.BOLT1.TLV: parseInitTlvs :: TlvStream -> Either TlvError [InitTlv]
- Lightning.Protocol.BOLT1.TLV: tlvStream :: [TlvRecord] -> Maybe TlvStream
- Lightning.Protocol.BOLT1.TLV: unChainHash :: ChainHash -> ByteString
- Lightning.Protocol.BOLT1.TLV: unsafeTlvStream :: [TlvRecord] -> TlvStream
+ Lightning.Protocol.BOLT1: DecodeInvalidTlvValue :: !Word64 -> DecodeError
+ Lightning.Protocol.BOLT1: EncodeInvalidTlvs :: EncodeError
+ Lightning.Protocol.BOLT1: MsgPeerStorageRetrieval :: !PeerStorageRetrieval -> Message
+ Lightning.Protocol.BOLT1: ShortChannelId :: Word64 -> ShortChannelId
+ Lightning.Protocol.BOLT1: TlvNonMinimalBigSize :: TlvError
+ Lightning.Protocol.BOLT1: TlvTruncated :: TlvError
+ Lightning.Protocol.BOLT1: [error_channel_id] :: Error -> !ChannelId
+ Lightning.Protocol.BOLT1: [error_data] :: Error -> !ByteString
+ Lightning.Protocol.BOLT1: [error_tlvs] :: Error -> !TlvStream
+ Lightning.Protocol.BOLT1: [init_features] :: Init -> !FeatureVector
+ Lightning.Protocol.BOLT1: [init_global_features] :: Init -> !FeatureVector
+ Lightning.Protocol.BOLT1: [init_networks] :: Init -> !Maybe [ChainHash]
+ Lightning.Protocol.BOLT1: [init_remote_addr] :: Init -> !Maybe ByteString
+ Lightning.Protocol.BOLT1: [init_tlvs] :: Init -> !TlvStream
+ Lightning.Protocol.BOLT1: [peer_storage_blob] :: PeerStorage -> !ByteString
+ Lightning.Protocol.BOLT1: [peer_storage_retrieval_blob] :: PeerStorageRetrieval -> !ByteString
+ Lightning.Protocol.BOLT1: [peer_storage_retrieval_tlvs] :: PeerStorageRetrieval -> !TlvStream
+ Lightning.Protocol.BOLT1: [peer_storage_tlvs] :: PeerStorage -> !TlvStream
+ Lightning.Protocol.BOLT1: [ping_ignored] :: Ping -> !ByteString
+ Lightning.Protocol.BOLT1: [ping_num_pong_bytes] :: Ping -> {-# UNPACK #-} !Word16
+ Lightning.Protocol.BOLT1: [ping_tlvs] :: Ping -> !TlvStream
+ Lightning.Protocol.BOLT1: [pong_ignored] :: Pong -> !ByteString
+ Lightning.Protocol.BOLT1: [pong_tlvs] :: Pong -> !TlvStream
+ Lightning.Protocol.BOLT1: [tlv_type] :: TlvRecord -> {-# UNPACK #-} !Word64
+ Lightning.Protocol.BOLT1: [tlv_value] :: TlvRecord -> !ByteString
+ Lightning.Protocol.BOLT1: [warning_channel_id] :: Warning -> !ChannelId
+ Lightning.Protocol.BOLT1: [warning_data] :: Warning -> !ByteString
+ Lightning.Protocol.BOLT1: [warning_tlvs] :: Warning -> !TlvStream
+ Lightning.Protocol.BOLT1: add_msat :: MilliSatoshi -> MilliSatoshi -> Maybe MilliSatoshi
+ Lightning.Protocol.BOLT1: add_sat :: Satoshi -> Satoshi -> Maybe Satoshi
+ Lightning.Protocol.BOLT1: all_channels :: ChannelId
+ Lightning.Protocol.BOLT1: chain_hash :: ByteString -> Maybe ChainHash
+ Lightning.Protocol.BOLT1: channel_id :: ByteString -> Maybe ChannelId
+ Lightning.Protocol.BOLT1: data MilliSatoshi
+ Lightning.Protocol.BOLT1: data PaymentHash
+ Lightning.Protocol.BOLT1: data PaymentPreimage
+ Lightning.Protocol.BOLT1: data PerCommitmentSecret
+ Lightning.Protocol.BOLT1: data Point
+ Lightning.Protocol.BOLT1: data Satoshi
+ Lightning.Protocol.BOLT1: data Signature
+ Lightning.Protocol.BOLT1: decode_bigsize :: ByteString -> Maybe (Word64, ByteString)
+ Lightning.Protocol.BOLT1: decode_chain_hash :: ByteString -> Maybe (ChainHash, ByteString)
+ Lightning.Protocol.BOLT1: decode_channel_id :: ByteString -> Maybe (ChannelId, ByteString)
+ Lightning.Protocol.BOLT1: decode_envelope :: ByteString -> Either DecodeError (Word16, ByteString)
+ Lightning.Protocol.BOLT1: decode_error :: ByteString -> Either DecodeError Error
+ Lightning.Protocol.BOLT1: decode_init :: ByteString -> Either DecodeError Init
+ Lightning.Protocol.BOLT1: decode_message :: ByteString -> Either DecodeError Message
+ Lightning.Protocol.BOLT1: decode_milli_satoshi :: ByteString -> Maybe (MilliSatoshi, ByteString)
+ Lightning.Protocol.BOLT1: decode_payment_hash :: ByteString -> Maybe (PaymentHash, ByteString)
+ Lightning.Protocol.BOLT1: decode_payment_preimage :: ByteString -> Maybe (PaymentPreimage, ByteString)
+ Lightning.Protocol.BOLT1: decode_peer_storage :: ByteString -> Either DecodeError PeerStorage
+ Lightning.Protocol.BOLT1: decode_peer_storage_retrieval :: ByteString -> Either DecodeError PeerStorageRetrieval
+ Lightning.Protocol.BOLT1: decode_per_commitment_secret :: ByteString -> Maybe (PerCommitmentSecret, ByteString)
+ Lightning.Protocol.BOLT1: decode_ping :: ByteString -> Either DecodeError Ping
+ Lightning.Protocol.BOLT1: decode_point :: ByteString -> Maybe (Point, ByteString)
+ Lightning.Protocol.BOLT1: decode_pong :: ByteString -> Either DecodeError Pong
+ Lightning.Protocol.BOLT1: decode_s16 :: ByteString -> Maybe (Int16, ByteString)
+ Lightning.Protocol.BOLT1: decode_s32 :: ByteString -> Maybe (Int32, ByteString)
+ Lightning.Protocol.BOLT1: decode_s64 :: ByteString -> Maybe (Int64, ByteString)
+ Lightning.Protocol.BOLT1: decode_s8 :: ByteString -> Maybe (Int8, ByteString)
+ Lightning.Protocol.BOLT1: decode_satoshi :: ByteString -> Maybe (Satoshi, ByteString)
+ Lightning.Protocol.BOLT1: decode_short_channel_id :: ByteString -> Maybe (ShortChannelId, ByteString)
+ Lightning.Protocol.BOLT1: decode_signature :: ByteString -> Maybe (Signature, ByteString)
+ Lightning.Protocol.BOLT1: decode_tlv_stream :: (Word64 -> Bool) -> ByteString -> Either TlvError TlvStream
+ Lightning.Protocol.BOLT1: decode_tu16 :: ByteString -> Maybe Word16
+ Lightning.Protocol.BOLT1: decode_tu32 :: ByteString -> Maybe Word32
+ Lightning.Protocol.BOLT1: decode_tu64 :: ByteString -> Maybe Word64
+ Lightning.Protocol.BOLT1: decode_u16 :: ByteString -> Maybe (Word16, ByteString)
+ Lightning.Protocol.BOLT1: decode_u16_prefixed :: ByteString -> Maybe (ByteString, ByteString)
+ Lightning.Protocol.BOLT1: decode_u32 :: ByteString -> Maybe (Word32, ByteString)
+ Lightning.Protocol.BOLT1: decode_u64 :: ByteString -> Maybe (Word64, ByteString)
+ Lightning.Protocol.BOLT1: decode_warning :: ByteString -> Either DecodeError Warning
+ Lightning.Protocol.BOLT1: empty_tlv_stream :: TlvStream
+ Lightning.Protocol.BOLT1: encode_bigsize :: Word64 -> ByteString
+ Lightning.Protocol.BOLT1: encode_envelope :: Word16 -> ByteString -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_error :: Error -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_init :: Init -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_message :: Message -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_milli_satoshi :: MilliSatoshi -> ByteString
+ Lightning.Protocol.BOLT1: encode_peer_storage :: PeerStorage -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_peer_storage_retrieval :: PeerStorageRetrieval -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_ping :: Ping -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_pong :: Pong -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: encode_s16 :: Int16 -> ByteString
+ Lightning.Protocol.BOLT1: encode_s32 :: Int32 -> ByteString
+ Lightning.Protocol.BOLT1: encode_s64 :: Int64 -> ByteString
+ Lightning.Protocol.BOLT1: encode_s8 :: Int8 -> ByteString
+ Lightning.Protocol.BOLT1: encode_satoshi :: Satoshi -> ByteString
+ Lightning.Protocol.BOLT1: encode_short_channel_id :: ShortChannelId -> ByteString
+ Lightning.Protocol.BOLT1: encode_tlv_stream :: TlvStream -> ByteString
+ Lightning.Protocol.BOLT1: encode_tu16 :: Word16 -> ByteString
+ Lightning.Protocol.BOLT1: encode_tu32 :: Word32 -> ByteString
+ Lightning.Protocol.BOLT1: encode_tu64 :: Word64 -> ByteString
+ Lightning.Protocol.BOLT1: encode_u16 :: Word16 -> ByteString
+ Lightning.Protocol.BOLT1: encode_u16_prefixed :: ByteString -> Maybe ByteString
+ Lightning.Protocol.BOLT1: encode_u32 :: Word32 -> ByteString
+ Lightning.Protocol.BOLT1: encode_u64 :: Word64 -> ByteString
+ Lightning.Protocol.BOLT1: encode_warning :: Warning -> Either EncodeError ByteString
+ Lightning.Protocol.BOLT1: filter_tlv_stream :: (Word64 -> Bool) -> TlvStream -> TlvStream
+ Lightning.Protocol.BOLT1: lookup_tlv :: Word64 -> TlvStream -> Maybe ByteString
+ Lightning.Protocol.BOLT1: max_milli_satoshi :: MilliSatoshi
+ Lightning.Protocol.BOLT1: max_satoshi :: Satoshi
+ Lightning.Protocol.BOLT1: message_type :: Message -> Word16
+ Lightning.Protocol.BOLT1: milli_satoshi :: Word64 -> Maybe MilliSatoshi
+ Lightning.Protocol.BOLT1: msat_to_sat :: MilliSatoshi -> Satoshi
+ Lightning.Protocol.BOLT1: newtype ShortChannelId
+ Lightning.Protocol.BOLT1: payment_hash :: ByteString -> Maybe PaymentHash
+ Lightning.Protocol.BOLT1: payment_preimage :: ByteString -> Maybe PaymentPreimage
+ Lightning.Protocol.BOLT1: per_commitment_secret :: ByteString -> Maybe PerCommitmentSecret
+ Lightning.Protocol.BOLT1: ping_response :: Ping -> Maybe Pong
+ Lightning.Protocol.BOLT1: point :: ByteString -> Maybe Point
+ Lightning.Protocol.BOLT1: sat_to_msat :: Satoshi -> MilliSatoshi
+ Lightning.Protocol.BOLT1: satoshi :: Word64 -> Maybe Satoshi
+ Lightning.Protocol.BOLT1: scid_block_height :: ShortChannelId -> Word32
+ Lightning.Protocol.BOLT1: scid_output_index :: ShortChannelId -> Word16
+ Lightning.Protocol.BOLT1: scid_tx_index :: ShortChannelId -> Word32
+ Lightning.Protocol.BOLT1: short_channel_id :: Word32 -> Word32 -> Word16 -> Maybe ShortChannelId
+ Lightning.Protocol.BOLT1: signature :: ByteString -> Maybe Signature
+ Lightning.Protocol.BOLT1: sub_msat :: MilliSatoshi -> MilliSatoshi -> Maybe MilliSatoshi
+ Lightning.Protocol.BOLT1: sub_sat :: Satoshi -> Satoshi -> Maybe Satoshi
+ Lightning.Protocol.BOLT1: tlv_stream :: [TlvRecord] -> Maybe TlvStream
+ Lightning.Protocol.BOLT1: un_chain_hash :: ChainHash -> ByteString
+ Lightning.Protocol.BOLT1: un_channel_id :: ChannelId -> ByteString
+ Lightning.Protocol.BOLT1: un_milli_satoshi :: MilliSatoshi -> Word64
+ Lightning.Protocol.BOLT1: un_payment_hash :: PaymentHash -> ByteString
+ Lightning.Protocol.BOLT1: un_payment_preimage :: PaymentPreimage -> ByteString
+ Lightning.Protocol.BOLT1: un_per_commitment_secret :: PerCommitmentSecret -> ByteString
+ Lightning.Protocol.BOLT1: un_point :: Point -> ByteString
+ Lightning.Protocol.BOLT1: un_satoshi :: Satoshi -> Word64
+ Lightning.Protocol.BOLT1: un_signature :: Signature -> ByteString
+ Lightning.Protocol.BOLT1: un_tlv_stream :: TlvStream -> [TlvRecord]
- Lightning.Protocol.BOLT1: Error :: !ChannelId -> !ByteString -> Error
+ Lightning.Protocol.BOLT1: Error :: !ChannelId -> !ByteString -> !TlvStream -> Error
- Lightning.Protocol.BOLT1: Init :: !ByteString -> !ByteString -> ![InitTlv] -> Init
+ Lightning.Protocol.BOLT1: Init :: !FeatureVector -> !FeatureVector -> !Maybe [ChainHash] -> !Maybe ByteString -> !TlvStream -> Init
- Lightning.Protocol.BOLT1: MsgError :: MsgType
+ Lightning.Protocol.BOLT1: MsgError :: !Error -> Message
- Lightning.Protocol.BOLT1: MsgInit :: MsgType
+ Lightning.Protocol.BOLT1: MsgInit :: !Init -> Message
- Lightning.Protocol.BOLT1: MsgPeerStorage :: MsgType
+ Lightning.Protocol.BOLT1: MsgPeerStorage :: !PeerStorage -> Message
- Lightning.Protocol.BOLT1: MsgPing :: MsgType
+ Lightning.Protocol.BOLT1: MsgPing :: !Ping -> Message
- Lightning.Protocol.BOLT1: MsgPong :: MsgType
+ Lightning.Protocol.BOLT1: MsgPong :: !Pong -> Message
- Lightning.Protocol.BOLT1: MsgWarning :: MsgType
+ Lightning.Protocol.BOLT1: MsgWarning :: !Warning -> Message
- Lightning.Protocol.BOLT1: PeerStorage :: !ByteString -> PeerStorage
+ Lightning.Protocol.BOLT1: PeerStorage :: !ByteString -> !TlvStream -> PeerStorage
- Lightning.Protocol.BOLT1: PeerStorageRetrieval :: !ByteString -> PeerStorageRetrieval
+ Lightning.Protocol.BOLT1: PeerStorageRetrieval :: !ByteString -> !TlvStream -> PeerStorageRetrieval
- Lightning.Protocol.BOLT1: Ping :: {-# UNPACK #-} !Word16 -> !ByteString -> Ping
+ Lightning.Protocol.BOLT1: Ping :: {-# UNPACK #-} !Word16 -> !ByteString -> !TlvStream -> Ping
- Lightning.Protocol.BOLT1: Pong :: !ByteString -> Pong
+ Lightning.Protocol.BOLT1: Pong :: !ByteString -> !TlvStream -> Pong
- Lightning.Protocol.BOLT1: Warning :: !ChannelId -> !ByteString -> Warning
+ Lightning.Protocol.BOLT1: Warning :: !ChannelId -> !ByteString -> !TlvStream -> Warning

Files

CHANGELOG view
@@ -1,4 +1,34 @@ # Changelog -- 0.0.1 (2025-01-25)+- 0.1.0 (2026-10-10)+  * A substantial rewrite, with breaking changes throughout:++    * The API is exported from Lightning.Protocol.BOLT1 alone, and uses+      snake_case names (e.g. encode_message, channel_id, un_channel_id).++    * Fundamental types are abstract, constructed only via validating+      smart constructors. Points must carry a compressed-encoding+      prefix. Satoshi and MilliSatoshi are bounded at 21M BTC and+      provide checked arithmetic instead of Num.++    * Every message decoder consumes its whole payload and preserves+      unknown odd TLV records, so encoding a decoded message reproduces+      its wire bytes. Init has typed networks and remote_addr fields,+      and uses ppad-bolt9 feature vectors.++    * A single TLV decoder takes the set of known types, rejects+      unknown even types, and keeps unknown odd ones.++    * Removed the Envelope type, the minimal signed integer codecs and+      the Internal module; added ping_response and fixed-size field+      codecs.++  * Fixes TLV decoding, which accepted lengths of 2^63 or more as+    empty values.++  * Fixed-width encoders no longer allocate kilobytes per call.++  * Tests now cover every BOLT #1 appendix vector.++- 0.0.1 (2026-04-18)   * Initial release.
bench/Fixtures.hs view
@@ -1,433 +1,50 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} --- |--- Module: Fixtures--- Copyright: (c) 2025 Jared Tobin--- License: MIT--- Maintainer: Jared Tobin <jared@ppad.tech>------ Test fixtures for BOLT #1 benchmarks.- module Fixtures where  import qualified Data.ByteString as BS-import Data.Maybe (fromJust)+import Data.Maybe (fromMaybe) import Lightning.Protocol.BOLT1---- Sample ByteStrings --------------------------------------------------------- | 64-byte sample data.-bytes64 :: BS.ByteString-bytes64 = BS.replicate 64 0xAB-{-# NOINLINE bytes64 #-}---- | 1KB sample data.-bytes1k :: BS.ByteString-bytes1k = BS.replicate 1024 0xCD-{-# NOINLINE bytes1k #-}---- | 16KB sample data.-bytes16k :: BS.ByteString-bytes16k = BS.replicate 16384 0xEF-{-# NOINLINE bytes16k #-}---- Sample chain hashes (32 bytes each) ---------------------------------------- | Bitcoin mainnet genesis block hash (reversed, as used in LN).-mainnetChainHash :: ChainHash-mainnetChainHash = fromJust $ chainHash $ BS.pack-  [ 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72-  , 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f-  , 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c-  , 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00-  ]-{-# NOINLINE mainnetChainHash #-}---- | Bitcoin testnet genesis block hash (reversed, as used in LN).-testnetChainHash :: ChainHash-testnetChainHash = fromJust $ chainHash $ BS.pack-  [ 0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71-  , 0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3, 0xae-  , 0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad-  , 0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00-  ]-{-# NOINLINE testnetChainHash #-}---- Sample channel IDs (32 bytes each) -------------------------------------+import qualified Lightning.Protocol.BOLT9 as BOLT9 --- | Sample channel ID (non-zero).-sampleChannelId :: ChannelId-sampleChannelId = fromJust $ channelId $ BS.pack-  [ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08-  , 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10-  , 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18-  , 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20-  ]-{-# NOINLINE sampleChannelId #-}+{-# NOINLINE tlvs #-}+tlvs :: TlvStream+tlvs = fromMaybe empty_tlv_stream $+  tlv_stream [ TlvRecord t (BS.replicate 32 0x2a) | t <- [1, 3 .. 41] ] --- Sample Init messages ---------------------------------------------------+{-# NOINLINE tlvs_bytes #-}+tlvs_bytes :: BS.ByteString+tlvs_bytes = encode_tlv_stream tlvs --- | Minimal Init message (empty features, no TLVs).-minimalInit :: Init-minimalInit = Init-  { initGlobalFeatures = BS.empty-  , initFeatures       = BS.empty-  , initTlvs           = []+{-# NOINLINE init_msg #-}+init_msg :: Message+init_msg = MsgInit Init {+    init_global_features = BOLT9.parse ""+  , init_features        = BOLT9.parse "\x02\xaa\x52\x69\xa1"+  , init_networks        = fmap pure (chain_hash (BS.replicate 32 0x6f))+  , init_remote_addr     = Just "\x01\x7f\x00\x00\x01\x26\x07"+  , init_tlvs            = empty_tlv_stream   }-{-# NOINLINE minimalInit #-} --- | Init with feature bits set.-initWithFeatures :: Init-initWithFeatures = Init-  { initGlobalFeatures = BS.pack [0x00, 0x01]  -- 2 bytes-  , initFeatures       = BS.pack [0x02, 0xa2]  -- data_loss_protect, etc.-  , initTlvs           = []-  }-{-# NOINLINE initWithFeatures #-}---- | Init with TLV extensions.-initWithTlvs :: Init-initWithTlvs = Init-  { initGlobalFeatures = BS.empty-  , initFeatures       = BS.pack [0x02, 0xa2]-  , initTlvs           = [InitNetworks [mainnetChainHash]]-  }-{-# NOINLINE initWithTlvs #-}---- | Init with multiple chain hashes.-initWithMultipleChains :: Init-initWithMultipleChains = Init-  { initGlobalFeatures = BS.empty-  , initFeatures       = BS.pack [0x02, 0xa2]-  , initTlvs           = [InitNetworks [mainnetChainHash, testnetChainHash]]-  }-{-# NOINLINE initWithMultipleChains #-}---- | Full Init with features and remote_addr TLV.-fullInit :: Init-fullInit = Init-  { initGlobalFeatures = BS.pack [0x00, 0x01]-  , initFeatures       = BS.pack [0x02, 0xa2, 0x01]-  , initTlvs           =-      [ InitNetworks [mainnetChainHash]-      , InitRemoteAddr (BS.pack [0x01, 0x7f, 0x00, 0x00, 0x01, 0x27, 0x10])-      ]-  }-{-# NOINLINE fullInit #-}---- Sample Error messages ------------------------------------------------------ | Minimal Error message (connection-level, empty data).-minimalError :: Error-minimalError = Error-  { errorChannelId = allChannels-  , errorData      = BS.empty-  }-{-# NOINLINE minimalError #-}---- | Error with channel ID and message.-errorWithData :: Error-errorWithData = Error-  { errorChannelId = sampleChannelId-  , errorData      = "funding transaction failed"-  }-{-# NOINLINE errorWithData #-}---- | Error with longer data.-errorWithLongData :: Error-errorWithLongData = Error-  { errorChannelId = sampleChannelId-  , errorData      = bytes1k-  }-{-# NOINLINE errorWithLongData #-}---- Sample Warning messages ---------------------------------------------------- | Minimal Warning message.-minimalWarning :: Warning-minimalWarning = Warning-  { warningChannelId = allChannels-  , warningData      = BS.empty-  }-{-# NOINLINE minimalWarning #-}---- | Warning with message.-warningWithData :: Warning-warningWithData = Warning-  { warningChannelId = sampleChannelId-  , warningData      = "channel fee too low"-  }-{-# NOINLINE warningWithData #-}---- Sample Ping messages ------------------------------------------------------- | Minimal Ping (no padding, no response requested).-minimalPing :: Ping-minimalPing = Ping-  { pingNumPongBytes = 0-  , pingIgnored      = BS.empty-  }-{-# NOINLINE minimalPing #-}---- | Ping with response requested but no padding.-pingWithResponse :: Ping-pingWithResponse = Ping-  { pingNumPongBytes = 64-  , pingIgnored      = BS.empty-  }-{-# NOINLINE pingWithResponse #-}---- | Ping with padding (64 bytes).-pingWithPadding :: Ping-pingWithPadding = Ping-  { pingNumPongBytes = 64-  , pingIgnored      = bytes64-  }-{-# NOINLINE pingWithPadding #-}---- | Ping with large padding (1KB).-pingWithLargePadding :: Ping-pingWithLargePadding = Ping-  { pingNumPongBytes = 128-  , pingIgnored      = bytes1k-  }-{-# NOINLINE pingWithLargePadding #-}---- Sample Pong messages ------------------------------------------------------- | Minimal Pong (no ignored bytes).-minimalPong :: Pong-minimalPong = Pong-  { pongIgnored = BS.empty-  }-{-# NOINLINE minimalPong #-}---- | Pong with padding (64 bytes).-pongWithPadding :: Pong-pongWithPadding = Pong-  { pongIgnored = bytes64-  }-{-# NOINLINE pongWithPadding #-}---- | Pong with large padding (1KB).-pongWithLargePadding :: Pong-pongWithLargePadding = Pong-  { pongIgnored = bytes1k-  }-{-# NOINLINE pongWithLargePadding #-}---- Sample PeerStorage messages ------------------------------------------------ | Minimal PeerStorage (empty blob).-minimalPeerStorage :: PeerStorage-minimalPeerStorage = PeerStorage-  { peerStorageBlob = BS.empty-  }-{-# NOINLINE minimalPeerStorage #-}---- | PeerStorage with 1KB blob.-peerStorageSmall :: PeerStorage-peerStorageSmall = PeerStorage-  { peerStorageBlob = bytes1k-  }-{-# NOINLINE peerStorageSmall #-}---- | PeerStorage with 16KB blob.-peerStorageLarge :: PeerStorage-peerStorageLarge = PeerStorage-  { peerStorageBlob = bytes16k-  }-{-# NOINLINE peerStorageLarge #-}---- Sample PeerStorageRetrieval messages --------------------------------------- | Minimal PeerStorageRetrieval (empty blob).-minimalPeerStorageRetrieval :: PeerStorageRetrieval-minimalPeerStorageRetrieval = PeerStorageRetrieval-  { peerStorageRetrievalBlob = BS.empty-  }-{-# NOINLINE minimalPeerStorageRetrieval #-}---- | PeerStorageRetrieval with 1KB blob.-peerStorageRetrievalSmall :: PeerStorageRetrieval-peerStorageRetrievalSmall = PeerStorageRetrieval-  { peerStorageRetrievalBlob = bytes1k-  }-{-# NOINLINE peerStorageRetrievalSmall #-}---- | PeerStorageRetrieval with 16KB blob.-peerStorageRetrievalLarge :: PeerStorageRetrieval-peerStorageRetrievalLarge = PeerStorageRetrieval-  { peerStorageRetrievalBlob = bytes16k-  }-{-# NOINLINE peerStorageRetrievalLarge #-}---- Sample TLV streams --------------------------------------------------------- | Empty TLV stream.-emptyTlvStream :: TlvStream-emptyTlvStream = unsafeTlvStream []-{-# NOINLINE emptyTlvStream #-}---- | TLV stream with 1 record.-smallTlvStream :: TlvStream-smallTlvStream = unsafeTlvStream-  [ TlvRecord 1 (BS.replicate 32 0x01)-  ]-{-# NOINLINE smallTlvStream #-}---- | TLV stream with 5 records.-mediumTlvStream :: TlvStream-mediumTlvStream = unsafeTlvStream-  [ TlvRecord 1 (BS.replicate 8 0x01)-  , TlvRecord 3 (BS.replicate 16 0x03)-  , TlvRecord 5 (BS.replicate 32 0x05)-  , TlvRecord 7 (BS.replicate 64 0x07)-  , TlvRecord 9 (BS.replicate 128 0x09)-  ]-{-# NOINLINE mediumTlvStream #-}---- | TLV stream with 20 records.-largeTlvStream :: TlvStream-largeTlvStream = unsafeTlvStream-  [ TlvRecord 1  (BS.replicate 8 0x01)-  , TlvRecord 3  (BS.replicate 16 0x02)-  , TlvRecord 5  (BS.replicate 8 0x03)-  , TlvRecord 7  (BS.replicate 16 0x04)-  , TlvRecord 9  (BS.replicate 8 0x05)-  , TlvRecord 11 (BS.replicate 16 0x06)-  , TlvRecord 13 (BS.replicate 8 0x07)-  , TlvRecord 15 (BS.replicate 16 0x08)-  , TlvRecord 17 (BS.replicate 8 0x09)-  , TlvRecord 19 (BS.replicate 16 0x0a)-  , TlvRecord 21 (BS.replicate 8 0x0b)-  , TlvRecord 23 (BS.replicate 16 0x0c)-  , TlvRecord 25 (BS.replicate 8 0x0d)-  , TlvRecord 27 (BS.replicate 16 0x0e)-  , TlvRecord 29 (BS.replicate 8 0x0f)-  , TlvRecord 31 (BS.replicate 16 0x10)-  , TlvRecord 33 (BS.replicate 8 0x11)-  , TlvRecord 35 (BS.replicate 16 0x12)-  , TlvRecord 37 (BS.replicate 8 0x13)-  , TlvRecord 39 (BS.replicate 16 0x14)-  ]-{-# NOINLINE largeTlvStream #-}---- Encoded message bytes (for decode benchmarks) ------------------------------ Helper to encode or fail.-encodeOrFail :: Either EncodeError BS.ByteString -> BS.ByteString-encodeOrFail (Right bs) = bs-encodeOrFail (Left _)   = error "encodeOrFail: encoding failed"---- | Encoded minimal Init.-encodedMinimalInit :: BS.ByteString-encodedMinimalInit = encodeOrFail $ encodeEnvelope (MsgInitVal minimalInit) Nothing-{-# NOINLINE encodedMinimalInit #-}---- | Encoded Init with TLVs.-encodedInitWithTlvs :: BS.ByteString-encodedInitWithTlvs = encodeOrFail $ encodeEnvelope (MsgInitVal initWithTlvs) Nothing-{-# NOINLINE encodedInitWithTlvs #-}---- | Encoded full Init.-encodedFullInit :: BS.ByteString-encodedFullInit = encodeOrFail $ encodeEnvelope (MsgInitVal fullInit) Nothing-{-# NOINLINE encodedFullInit #-}---- | Encoded minimal Error.-encodedMinimalError :: BS.ByteString-encodedMinimalError = encodeOrFail $ encodeEnvelope (MsgErrorVal minimalError) Nothing-{-# NOINLINE encodedMinimalError #-}---- | Encoded Error with data.-encodedErrorWithData :: BS.ByteString-encodedErrorWithData = encodeOrFail $ encodeEnvelope (MsgErrorVal errorWithData) Nothing-{-# NOINLINE encodedErrorWithData #-}---- | Encoded minimal Warning.-encodedMinimalWarning :: BS.ByteString-encodedMinimalWarning = encodeOrFail $-  encodeEnvelope (MsgWarningVal minimalWarning) Nothing-{-# NOINLINE encodedMinimalWarning #-}---- | Encoded Warning with data.-encodedWarningWithData :: BS.ByteString-encodedWarningWithData = encodeOrFail $-  encodeEnvelope (MsgWarningVal warningWithData) Nothing-{-# NOINLINE encodedWarningWithData #-}---- | Encoded minimal Ping.-encodedMinimalPing :: BS.ByteString-encodedMinimalPing = encodeOrFail $ encodeEnvelope (MsgPingVal minimalPing) Nothing-{-# NOINLINE encodedMinimalPing #-}---- | Encoded Ping with padding.-encodedPingWithPadding :: BS.ByteString-encodedPingWithPadding = encodeOrFail $-  encodeEnvelope (MsgPingVal pingWithPadding) Nothing-{-# NOINLINE encodedPingWithPadding #-}---- | Encoded Ping with large padding.-encodedPingWithLargePadding :: BS.ByteString-encodedPingWithLargePadding = encodeOrFail $-  encodeEnvelope (MsgPingVal pingWithLargePadding) Nothing-{-# NOINLINE encodedPingWithLargePadding #-}---- | Encoded minimal Pong.-encodedMinimalPong :: BS.ByteString-encodedMinimalPong = encodeOrFail $ encodeEnvelope (MsgPongVal minimalPong) Nothing-{-# NOINLINE encodedMinimalPong #-}---- | Encoded Pong with padding.-encodedPongWithPadding :: BS.ByteString-encodedPongWithPadding = encodeOrFail $-  encodeEnvelope (MsgPongVal pongWithPadding) Nothing-{-# NOINLINE encodedPongWithPadding #-}---- | Encoded minimal PeerStorage.-encodedMinimalPeerStorage :: BS.ByteString-encodedMinimalPeerStorage = encodeOrFail $-  encodeEnvelope (MsgPeerStorageVal minimalPeerStorage) Nothing-{-# NOINLINE encodedMinimalPeerStorage #-}---- | Encoded PeerStorage with 1KB blob.-encodedPeerStorageSmall :: BS.ByteString-encodedPeerStorageSmall = encodeOrFail $-  encodeEnvelope (MsgPeerStorageVal peerStorageSmall) Nothing-{-# NOINLINE encodedPeerStorageSmall #-}---- | Encoded minimal PeerStorageRetrieval.-encodedMinimalPeerStorageRetrieval :: BS.ByteString-encodedMinimalPeerStorageRetrieval = encodeOrFail $-  encodeEnvelope (MsgPeerStorageRetrievalVal minimalPeerStorageRetrieval) Nothing-{-# NOINLINE encodedMinimalPeerStorageRetrieval #-}---- | Encoded PeerStorageRetrieval with 1KB blob.-encodedPeerStorageRetrievalSmall :: BS.ByteString-encodedPeerStorageRetrievalSmall = encodeOrFail $-  encodeEnvelope (MsgPeerStorageRetrievalVal peerStorageRetrievalSmall) Nothing-{-# NOINLINE encodedPeerStorageRetrievalSmall #-}+{-# NOINLINE ping_msg #-}+ping_msg :: Message+ping_msg = MsgPing (Ping 128 (BS.replicate 64 0) empty_tlv_stream) --- Encoded TLV streams (for decode benchmarks) ----------------------------+{-# NOINLINE error_msg #-}+error_msg :: Message+error_msg = MsgError (Error all_channels (BS.replicate 128 0x61) tlvs) --- | Encoded empty TLV stream.-encodedEmptyTlvStream :: BS.ByteString-encodedEmptyTlvStream = encodeTlvStream emptyTlvStream-{-# NOINLINE encodedEmptyTlvStream #-}+wire :: Message -> BS.ByteString+wire m = either (const BS.empty) id (encode_message m) --- | Encoded small TLV stream (1 record).-encodedSmallTlvStream :: BS.ByteString-encodedSmallTlvStream = encodeTlvStream smallTlvStream-{-# NOINLINE encodedSmallTlvStream #-}+{-# NOINLINE init_bytes #-}+init_bytes :: BS.ByteString+init_bytes = wire init_msg --- | Encoded medium TLV stream (5 records).-encodedMediumTlvStream :: BS.ByteString-encodedMediumTlvStream = encodeTlvStream mediumTlvStream-{-# NOINLINE encodedMediumTlvStream #-}+{-# NOINLINE ping_bytes #-}+ping_bytes :: BS.ByteString+ping_bytes = wire ping_msg --- | Encoded large TLV stream (20 records).-encodedLargeTlvStream :: BS.ByteString-encodedLargeTlvStream = encodeTlvStream largeTlvStream-{-# NOINLINE encodedLargeTlvStream #-}+{-# NOINLINE error_bytes #-}+error_bytes :: BS.ByteString+error_bytes = wire error_msg
bench/Main.hs view
@@ -1,526 +1,33 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-}  module Main where  import Criterion.Main-import qualified Data.ByteString as BS-import Data.Word (Word16, Word32, Word64)-import Data.Int (Int8, Int16, Int32, Int64)+import Fixtures import Lightning.Protocol.BOLT1-import Lightning.Protocol.BOLT1.Codec-import Lightning.Protocol.BOLT1.TLV (encodeInitTlvs, encodeTlvRecord, parseInitTlvs) --- Fixtures ------------------------------------------------------------------------ Prevent constant folding by marking fixtures as NOINLINE.--{-# NOINLINE u16Val #-}-u16Val :: Word16-u16Val = 0x1234--{-# NOINLINE u32Val #-}-u32Val :: Word32-u32Val = 0x12345678--{-# NOINLINE u64Val #-}-u64Val :: Word64-u64Val = 0x123456789ABCDEF0--{-# NOINLINE s8Val #-}-s8Val :: Int8-s8Val = -42--{-# NOINLINE s16Val #-}-s16Val :: Int16-s16Val = -1234--{-# NOINLINE s32Val #-}-s32Val :: Int32-s32Val = -12345678--{-# NOINLINE s64Val #-}-s64Val :: Int64-s64Val = -123456789012345---- Truncated values--{-# NOINLINE tu16Zero #-}-tu16Zero :: Word16-tu16Zero = 0--{-# NOINLINE tu16Small #-}-tu16Small :: Word16-tu16Small = 0x42--{-# NOINLINE tu16Max #-}-tu16Max :: Word16-tu16Max = 0xFFFF--{-# NOINLINE tu32Zero #-}-tu32Zero :: Word32-tu32Zero = 0--{-# NOINLINE tu32Small #-}-tu32Small :: Word32-tu32Small = 0x42--{-# NOINLINE tu32Max #-}-tu32Max :: Word32-tu32Max = 0xFFFFFFFF--{-# NOINLINE tu64Zero #-}-tu64Zero :: Word64-tu64Zero = 0--{-# NOINLINE tu64Small #-}-tu64Small :: Word64-tu64Small = 0x42--{-# NOINLINE tu64Max #-}-tu64Max :: Word64-tu64Max = 0xFFFFFFFFFFFFFFFF---- MinSigned values--{-# NOINLINE ms0 #-}-ms0 :: Int64-ms0 = 0--{-# NOINLINE ms127 #-}-ms127 :: Int64-ms127 = 127--{-# NOINLINE ms128 #-}-ms128 :: Int64-ms128 = 128--{-# NOINLINE msNeg128 #-}-msNeg128 :: Int64-msNeg128 = -128--{-# NOINLINE msNeg129 #-}-msNeg129 :: Int64-msNeg129 = -129---- BigSize values--{-# NOINLINE bs0 #-}-bs0 :: Word64-bs0 = 0--{-# NOINLINE bs252 #-}-bs252 :: Word64-bs252 = 252--{-# NOINLINE bs253 #-}-bs253 :: Word64-bs253 = 253--{-# NOINLINE bs65535 #-}-bs65535 :: Word64-bs65535 = 65535--{-# NOINLINE bs65536 #-}-bs65536 :: Word64-bs65536 = 65536--{-# NOINLINE bsLarge #-}-bsLarge :: Word64-bsLarge = 0x100000000---- Encoded bytes for decode benchmarks--{-# NOINLINE encodedU16 #-}-encodedU16 :: BS.ByteString-encodedU16 = encodeU16 u16Val--{-# NOINLINE encodedU32 #-}-encodedU32 :: BS.ByteString-encodedU32 = encodeU32 u32Val--{-# NOINLINE encodedU64 #-}-encodedU64 :: BS.ByteString-encodedU64 = encodeU64 u64Val--{-# NOINLINE encodedS8 #-}-encodedS8 :: BS.ByteString-encodedS8 = encodeS8 s8Val--{-# NOINLINE encodedS16 #-}-encodedS16 :: BS.ByteString-encodedS16 = encodeS16 s16Val--{-# NOINLINE encodedS32 #-}-encodedS32 :: BS.ByteString-encodedS32 = encodeS32 s32Val--{-# NOINLINE encodedS64 #-}-encodedS64 :: BS.ByteString-encodedS64 = encodeS64 s64Val--{-# NOINLINE encodedTu16Small #-}-encodedTu16Small :: BS.ByteString-encodedTu16Small = encodeTu16 tu16Small--{-# NOINLINE encodedTu32Small #-}-encodedTu32Small :: BS.ByteString-encodedTu32Small = encodeTu32 tu32Small--{-# NOINLINE encodedTu64Small #-}-encodedTu64Small :: BS.ByteString-encodedTu64Small = encodeTu64 tu64Small--{-# NOINLINE encodedMs127 #-}-encodedMs127 :: BS.ByteString-encodedMs127 = encodeMinSigned ms127--{-# NOINLINE encodedMsNeg129 #-}-encodedMsNeg129 :: BS.ByteString-encodedMsNeg129 = encodeMinSigned msNeg129--{-# NOINLINE encodedBs0 #-}-encodedBs0 :: BS.ByteString-encodedBs0 = encodeBigSize bs0--{-# NOINLINE encodedBs253 #-}-encodedBs253 :: BS.ByteString-encodedBs253 = encodeBigSize bs253--{-# NOINLINE encodedBs65536 #-}-encodedBs65536 :: BS.ByteString-encodedBs65536 = encodeBigSize bs65536--{-# NOINLINE encodedBsLarge #-}-encodedBsLarge :: BS.ByteString-encodedBsLarge = encodeBigSize bsLarge---- TLV fixtures--{-# NOINLINE tlvRec1 #-}-tlvRec1 :: TlvRecord-tlvRec1 = TlvRecord 1 "test"--{-# NOINLINE tlvRec3 #-}-tlvRec3 :: TlvRecord-tlvRec3 = TlvRecord 3 "addr"--{-# NOINLINE tlvRec5 #-}-tlvRec5 :: TlvRecord-tlvRec5 = TlvRecord 5 "value"--{-# NOINLINE tlvStream1 #-}-tlvStream1 :: TlvStream-tlvStream1 = unsafeTlvStream [tlvRec1]--{-# NOINLINE tlvStream5 #-}-tlvStream5 :: TlvStream-tlvStream5 = unsafeTlvStream-  [ TlvRecord 1 "one"-  , TlvRecord 3 "three"-  , TlvRecord 5 "five"-  , TlvRecord 7 "seven"-  , TlvRecord 9 "nine"-  ]--{-# NOINLINE tlvStream20 #-}-tlvStream20 :: TlvStream-tlvStream20 = unsafeTlvStream-  [ TlvRecord (2*i + 1) (BS.replicate 10 (fromIntegral i))-  | i <- [0..19]-  ]--{-# NOINLINE encodedTlvStream1 #-}-encodedTlvStream1 :: BS.ByteString-encodedTlvStream1 = encodeTlvStream tlvStream1--{-# NOINLINE encodedTlvStream5 #-}-encodedTlvStream5 :: BS.ByteString-encodedTlvStream5 = encodeTlvStream tlvStream5--{-# NOINLINE encodedTlvStream20 #-}-encodedTlvStream20 :: BS.ByteString-encodedTlvStream20 = encodeTlvStream tlvStream20---- Init TLV fixtures--{-# NOINLINE chainHash1 #-}-chainHash1 :: ChainHash-chainHash1 = case chainHash (BS.replicate 32 0x01) of-  Just ch -> ch-  Nothing -> error "impossible"--{-# NOINLINE initTlvNetworks #-}-initTlvNetworks :: [InitTlv]-initTlvNetworks = [InitNetworks [chainHash1]]--{-# NOINLINE initTlvRemoteAddr #-}-initTlvRemoteAddr :: [InitTlv]-initTlvRemoteAddr = [InitRemoteAddr "127.0.0.1"]--{-# NOINLINE encodedInitTlvs #-}-encodedInitTlvs :: BS.ByteString-encodedInitTlvs = encodeTlvStream (encodeInitTlvs initTlvNetworks)---- Message fixtures--{-# NOINLINE initMinimal #-}-initMinimal :: Init-initMinimal = Init BS.empty BS.empty []--{-# NOINLINE initWithTlvs #-}-initWithTlvs :: Init-initWithTlvs = Init (BS.pack [0x00, 0x01]) (BS.pack [0x02, 0x03]) initTlvNetworks--{-# NOINLINE errorMinimal #-}-errorMinimal :: Error-errorMinimal = Error allChannels BS.empty--{-# NOINLINE errorWithData #-}-errorWithData :: Error-errorWithData = Error allChannels "Connection reset by peer"--{-# NOINLINE warningMsg #-}-warningMsg :: Warning-warningMsg = Warning allChannels "Low disk space"--{-# NOINLINE pingMinimal #-}-pingMinimal :: Ping-pingMinimal = Ping 64 BS.empty--{-# NOINLINE pingWithPadding #-}-pingWithPadding :: Ping-pingWithPadding = Ping 64 (BS.replicate 64 0x00)--{-# NOINLINE pongMsg #-}-pongMsg :: Pong-pongMsg = Pong (BS.replicate 64 0x00)--{-# NOINLINE peerStorageMsg #-}-peerStorageMsg :: PeerStorage-peerStorageMsg = PeerStorage (BS.replicate 100 0xAB)--{-# NOINLINE peerStorageRetMsg #-}-peerStorageRetMsg :: PeerStorageRetrieval-peerStorageRetMsg = PeerStorageRetrieval (BS.replicate 100 0xCD)---- Encoded messages for decode benchmarks--{-# NOINLINE encodedInitMinimal #-}-encodedInitMinimal :: BS.ByteString-encodedInitMinimal = case encodeInit initMinimal of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedInitWithTlvs #-}-encodedInitWithTlvs :: BS.ByteString-encodedInitWithTlvs = case encodeInit initWithTlvs of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedErrorMinimal #-}-encodedErrorMinimal :: BS.ByteString-encodedErrorMinimal = case encodeError errorMinimal of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedErrorWithData #-}-encodedErrorWithData :: BS.ByteString-encodedErrorWithData = case encodeError errorWithData of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedWarning #-}-encodedWarning :: BS.ByteString-encodedWarning = case encodeWarning warningMsg of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedPingMinimal #-}-encodedPingMinimal :: BS.ByteString-encodedPingMinimal = case encodePing pingMinimal of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedPingWithPadding #-}-encodedPingWithPadding :: BS.ByteString-encodedPingWithPadding = case encodePing pingWithPadding of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedPong #-}-encodedPong :: BS.ByteString-encodedPong = case encodePong pongMsg of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedPeerStorage #-}-encodedPeerStorage :: BS.ByteString-encodedPeerStorage = case encodePeerStorage peerStorageMsg of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedPeerStorageRet #-}-encodedPeerStorageRet :: BS.ByteString-encodedPeerStorageRet = case encodePeerStorageRetrieval peerStorageRetMsg of-  Right bs -> bs-  Left _ -> error "impossible"---- Envelope fixtures--{-# NOINLINE msgInit #-}-msgInit :: Message-msgInit = MsgInitVal initMinimal--{-# NOINLINE msgPing #-}-msgPing :: Message-msgPing = MsgPingVal pingMinimal--{-# NOINLINE encodedEnvelopeNoExt #-}-encodedEnvelopeNoExt :: BS.ByteString-encodedEnvelopeNoExt = case encodeEnvelope msgPing Nothing of-  Right bs -> bs-  Left _ -> error "impossible"--{-# NOINLINE encodedEnvelopeWithExt #-}-encodedEnvelopeWithExt :: BS.ByteString-encodedEnvelopeWithExt = case encodeEnvelope msgPing (Just tlvStream5) of-  Right bs -> bs-  Left _ -> error "impossible"---- Main ------------------------------------------------------------------------- main :: IO ()-main = defaultMain-  [ bgroup "prim/encode"-      [ bench "encodeU16" $ whnf encodeU16 u16Val-      , bench "encodeU32" $ whnf encodeU32 u32Val-      , bench "encodeU64" $ whnf encodeU64 u64Val-      , bench "encodeS8" $ whnf encodeS8 s8Val-      , bench "encodeS16" $ whnf encodeS16 s16Val-      , bench "encodeS32" $ whnf encodeS32 s32Val-      , bench "encodeS64" $ whnf encodeS64 s64Val-      , bench "encodeTu16/0" $ whnf encodeTu16 tu16Zero-      , bench "encodeTu16/small" $ whnf encodeTu16 tu16Small-      , bench "encodeTu16/max" $ whnf encodeTu16 tu16Max-      , bench "encodeTu32/0" $ whnf encodeTu32 tu32Zero-      , bench "encodeTu32/small" $ whnf encodeTu32 tu32Small-      , bench "encodeTu32/max" $ whnf encodeTu32 tu32Max-      , bench "encodeTu64/0" $ whnf encodeTu64 tu64Zero-      , bench "encodeTu64/small" $ whnf encodeTu64 tu64Small-      , bench "encodeTu64/max" $ whnf encodeTu64 tu64Max-      , bench "encodeMinSigned/0" $ whnf encodeMinSigned ms0-      , bench "encodeMinSigned/127" $ whnf encodeMinSigned ms127-      , bench "encodeMinSigned/128" $ whnf encodeMinSigned ms128-      , bench "encodeMinSigned/-128" $ whnf encodeMinSigned msNeg128-      , bench "encodeMinSigned/-129" $ whnf encodeMinSigned msNeg129-      , bench "encodeBigSize/0" $ whnf encodeBigSize bs0-      , bench "encodeBigSize/252" $ whnf encodeBigSize bs252-      , bench "encodeBigSize/253" $ whnf encodeBigSize bs253-      , bench "encodeBigSize/65535" $ whnf encodeBigSize bs65535-      , bench "encodeBigSize/65536" $ whnf encodeBigSize bs65536-      , bench "encodeBigSize/large" $ whnf encodeBigSize bsLarge-      ]--  , bgroup "prim/decode"-      [ bench "decodeU16" $ nf decodeU16 encodedU16-      , bench "decodeU32" $ nf decodeU32 encodedU32-      , bench "decodeU64" $ nf decodeU64 encodedU64-      , bench "decodeS8" $ nf decodeS8 encodedS8-      , bench "decodeS16" $ nf decodeS16 encodedS16-      , bench "decodeS32" $ nf decodeS32 encodedS32-      , bench "decodeS64" $ nf decodeS64 encodedS64-      , bench "decodeTu16" $ nf (decodeTu16 1) encodedTu16Small-      , bench "decodeTu32" $ nf (decodeTu32 1) encodedTu32Small-      , bench "decodeTu64" $ nf (decodeTu64 1) encodedTu64Small-      , bench "decodeMinSigned/1" $ nf (decodeMinSigned 1) encodedMs127-      , bench "decodeMinSigned/2" $ nf (decodeMinSigned 2) encodedMsNeg129-      , bench "decodeBigSize/0" $ nf decodeBigSize encodedBs0-      , bench "decodeBigSize/253" $ nf decodeBigSize encodedBs253-      , bench "decodeBigSize/65536" $ nf decodeBigSize encodedBs65536-      , bench "decodeBigSize/large" $ nf decodeBigSize encodedBsLarge-      ]--  , bgroup "tlv/encode"-      [ bench "encodeTlvRecord" $ whnf encodeTlvRecord tlvRec1-      , bench "encodeTlvStream/1" $ whnf encodeTlvStream tlvStream1-      , bench "encodeTlvStream/5" $ whnf encodeTlvStream tlvStream5-      , bench "encodeTlvStream/20" $ whnf encodeTlvStream tlvStream20-      , bench "encodeInitTlvs" $ nf encodeInitTlvs initTlvNetworks-      ]--  , bgroup "tlv/decode"-      [ bench "decodeTlvStreamRaw/1" $ nf decodeTlvStreamRaw encodedTlvStream1-      , bench "decodeTlvStreamRaw/5" $ nf decodeTlvStreamRaw encodedTlvStream5-      , bench "decodeTlvStreamRaw/20" $ nf decodeTlvStreamRaw encodedTlvStream20-      , bench "decodeTlvStream" $ nf decodeTlvStream encodedInitTlvs-      , bench "decodeTlvStreamWith" $-          nf (decodeTlvStreamWith (const True)) encodedTlvStream5-      , bench "parseInitTlvs" $-          nf parseInitTlvs (encodeInitTlvs initTlvNetworks)-      ]--  , bgroup "message/encode"-      [ bench "encodeInit/minimal" $ nf encodeInit initMinimal-      , bench "encodeInit/with-tlvs" $ nf encodeInit initWithTlvs-      , bench "encodeError/minimal" $ nf encodeError errorMinimal-      , bench "encodeError/with-data" $ nf encodeError errorWithData-      , bench "encodeWarning" $ nf encodeWarning warningMsg-      , bench "encodePing/minimal" $ nf encodePing pingMinimal-      , bench "encodePing/with-padding" $ nf encodePing pingWithPadding-      , bench "encodePong" $ nf encodePong pongMsg-      , bench "encodePeerStorage" $ nf encodePeerStorage peerStorageMsg-      , bench "encodePeerStorageRetrieval" $-          nf encodePeerStorageRetrieval peerStorageRetMsg-      ]--  , bgroup "message/decode"-      [ bench "decodeInit/minimal" $ nf decodeInit encodedInitMinimal-      , bench "decodeInit/with-tlvs" $ nf decodeInit encodedInitWithTlvs-      , bench "decodeError/minimal" $ nf decodeError encodedErrorMinimal-      , bench "decodeError/with-data" $ nf decodeError encodedErrorWithData-      , bench "decodeWarning" $ nf decodeWarning encodedWarning-      , bench "decodePing/minimal" $ nf decodePing encodedPingMinimal-      , bench "decodePing/with-padding" $ nf decodePing encodedPingWithPadding-      , bench "decodePong" $ nf decodePong encodedPong-      , bench "decodePeerStorage" $ nf decodePeerStorage encodedPeerStorage-      , bench "decodePeerStorageRetrieval" $-          nf decodePeerStorageRetrieval encodedPeerStorageRet-      ]--  , bgroup "envelope"-      [ bench "encodeEnvelope/no-ext" $ nf (encodeEnvelope msgPing) Nothing-      , bench "encodeEnvelope/with-ext" $-          nf (encodeEnvelope msgPing) (Just tlvStream5)-      , bench "decodeEnvelope/no-ext" $ nf decodeEnvelope encodedEnvelopeNoExt-      , bench "decodeEnvelope/with-ext" $-          nf decodeEnvelope encodedEnvelopeWithExt-      , bench "decodeEnvelopeWith" $-          nf (decodeEnvelopeWith (const True)) encodedEnvelopeWithExt-      ]--  , bgroup "roundtrip"-      [ bench "init/minimal" $ nf (decodeInit . forceRight . encodeInit)-          initMinimal-      , bench "init/with-tlvs" $ nf (decodeInit . forceRight . encodeInit)-          initWithTlvs-      , bench "error" $ nf (decodeError . forceRight . encodeError) errorWithData-      , bench "warning" $ nf (decodeWarning . forceRight . encodeWarning)-          warningMsg-      , bench "ping" $ nf (decodePing . forceRight . encodePing) pingWithPadding-      , bench "pong" $ nf (decodePong . forceRight . encodePong) pongMsg-      , bench "peer-storage" $-          nf (decodePeerStorage . forceRight . encodePeerStorage) peerStorageMsg-      , bench "peer-storage-retrieval" $-          nf (decodePeerStorageRetrieval . forceRight . encodePeerStorageRetrieval)-            peerStorageRetMsg-      , bench "envelope" $ nf-          (decodeEnvelope . forceRight . encodeEnvelope msgPing) (Just tlvStream5)-      ]+main = defaultMain [+    bgroup "primitives" [+      bench "encode_u64" $ nf encode_u64 0x0102030405060708+    , bench "decode_u64" $ nf decode_u64 "\x01\x02\x03\x04\x05\x06\x07\x08"+    , bench "encode_tu64" $ nf encode_tu64 0x010203+    , bench "decode_tu64" $ nf decode_tu64 "\x01\x02\x03"+    , bench "encode_bigsize (9 bytes)" $ nf encode_bigsize 0x100000000+    , bench "decode_bigsize (9 bytes)" $+        nf decode_bigsize "\xff\x00\x00\x00\x01\x00\x00\x00\x00"+    ]+  , bgroup "tlv" [+      bench "encode_tlv_stream (21 records)" $ nf encode_tlv_stream tlvs+    , bench "decode_tlv_stream (21 records)" $+        nf (decode_tlv_stream (const False)) tlvs_bytes+    ]+  , bgroup "messages" [+      bench "encode init" $ nf encode_message init_msg+    , bench "decode init" $ nf decode_message init_bytes+    , bench "encode ping" $ nf encode_message ping_msg+    , bench "decode ping" $ nf decode_message ping_bytes+    , bench "encode error (with tlvs)" $ nf encode_message error_msg+    , bench "decode error (with tlvs)" $ nf decode_message error_bytes+    ]   ]---- Helper for roundtrip benchmarks-forceRight :: Either a b -> b-forceRight (Right b) = b-forceRight (Left _) = error "forceRight: Left"-{-# INLINE forceRight #-}
bench/Weight.hs view
@@ -1,347 +1,28 @@-{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-}  module Main where -import qualified Data.ByteString as BS-import Data.Word (Word16, Word32, Word64)-import Data.Int (Int8, Int16, Int32, Int64)+import Fixtures import Lightning.Protocol.BOLT1-import Lightning.Protocol.BOLT1.Codec-import Lightning.Protocol.BOLT1.TLV (encodeTlvRecord) import Weigh --- Fixtures ------------------------------------------------------------------------ Prevent constant folding with NOINLINE--{-# NOINLINE w16Val #-}-w16Val :: Word16-w16Val = 0x1234--{-# NOINLINE w32Val #-}-w32Val :: Word32-w32Val = 0x12345678--{-# NOINLINE w64Val #-}-w64Val :: Word64-w64Val = 0x0102030405060708--{-# NOINLINE s8Val #-}-s8Val :: Int8-s8Val = -42--{-# NOINLINE s16Val #-}-s16Val :: Int16-s16Val = -1000--{-# NOINLINE s32Val #-}-s32Val :: Int32-s32Val = -100000--{-# NOINLINE s64Val #-}-s64Val :: Int64-s64Val = -10000000000--{-# NOINLINE tu16Small #-}-tu16Small :: Word16-tu16Small = 0x7f--{-# NOINLINE tu16Full #-}-tu16Full :: Word16-tu16Full = 0xffff--{-# NOINLINE tu32Small #-}-tu32Small :: Word32-tu32Small = 0x42--{-# NOINLINE tu32Full #-}-tu32Full :: Word32-tu32Full = 0xffffffff--{-# NOINLINE tu64Small #-}-tu64Small :: Word64-tu64Small = 0x10--{-# NOINLINE tu64Full #-}-tu64Full :: Word64-tu64Full = 0xffffffffffffffff--{-# NOINLINE bigSizeSmall #-}-bigSizeSmall :: Word64-bigSizeSmall = 0xfc--{-# NOINLINE bigSizeMedium #-}-bigSizeMedium :: Word64-bigSizeMedium = 0x1000--{-# NOINLINE bigSizeLarge #-}-bigSizeLarge :: Word64-bigSizeLarge = 0x100000000---- Pre-encoded bytes for decoder benchmarks--{-# NOINLINE u16Bytes #-}-u16Bytes :: BS.ByteString-u16Bytes = encodeU16 w16Val--{-# NOINLINE u32Bytes #-}-u32Bytes :: BS.ByteString-u32Bytes = encodeU32 w32Val--{-# NOINLINE u64Bytes #-}-u64Bytes :: BS.ByteString-u64Bytes = encodeU64 w64Val--{-# NOINLINE s8Bytes #-}-s8Bytes :: BS.ByteString-s8Bytes = encodeS8 s8Val--{-# NOINLINE s16Bytes #-}-s16Bytes :: BS.ByteString-s16Bytes = encodeS16 s16Val--{-# NOINLINE s32Bytes #-}-s32Bytes :: BS.ByteString-s32Bytes = encodeS32 s32Val--{-# NOINLINE s64Bytes #-}-s64Bytes :: BS.ByteString-s64Bytes = encodeS64 s64Val--{-# NOINLINE bigSizeBytes #-}-bigSizeBytes :: BS.ByteString-bigSizeBytes = encodeBigSize bigSizeLarge---- TLV fixtures--{-# NOINLINE tlvRecord1 #-}-tlvRecord1 :: TlvRecord-tlvRecord1 = TlvRecord 1 "test-value"--{-# NOINLINE tlvStream1 #-}-tlvStream1 :: TlvStream-tlvStream1 = unsafeTlvStream [tlvRecord1]--{-# NOINLINE tlvStream5 #-}-tlvStream5 :: TlvStream-tlvStream5 = unsafeTlvStream-  [ TlvRecord 1 "value1"-  , TlvRecord 3 "value3"-  , TlvRecord 5 "value5"-  , TlvRecord 7 "value7"-  , TlvRecord 9 "value9"-  ]--{-# NOINLINE tlvStream20 #-}-tlvStream20 :: TlvStream-tlvStream20 = unsafeTlvStream-  [ TlvRecord (2 * i + 1) (BS.replicate 10 (fromIntegral i))-  | i <- [0..19]-  ]--{-# NOINLINE tlvStreamBytes1 #-}-tlvStreamBytes1 :: BS.ByteString-tlvStreamBytes1 = encodeTlvStream tlvStream1--{-# NOINLINE tlvStreamBytes5 #-}-tlvStreamBytes5 :: BS.ByteString-tlvStreamBytes5 = encodeTlvStream tlvStream5--{-# NOINLINE tlvStreamBytes20 #-}-tlvStreamBytes20 :: BS.ByteString-tlvStreamBytes20 = encodeTlvStream tlvStream20---- Message fixtures--{-# NOINLINE minimalInit #-}-minimalInit :: Init-minimalInit = Init BS.empty BS.empty []--{-# NOINLINE initWithFeatures #-}-initWithFeatures :: Init-initWithFeatures = Init "\x00\x08" "\x00\x0a\x8a" []--{-# NOINLINE initWithTlvs #-}-initWithTlvs :: Init-initWithTlvs = Init BS.empty "\x00\x01" [InitRemoteAddr "127.0.0.1"]--{-# NOINLINE errorMsg #-}-errorMsg :: Error-errorMsg = Error allChannels "something bad happened"--{-# NOINLINE warningMsg #-}-warningMsg :: Warning-warningMsg = Warning allChannels "something concerning"--{-# NOINLINE pingMinimal #-}-pingMinimal :: Ping-pingMinimal = Ping 4 BS.empty--{-# NOINLINE pingWithPadding #-}-pingWithPadding :: Ping-pingWithPadding = Ping 4 (BS.replicate 64 0x00)--{-# NOINLINE pongMsg #-}-pongMsg :: Pong-pongMsg = Pong (BS.replicate 4 0x00)--{-# NOINLINE peerStorageMsg #-}-peerStorageMsg :: PeerStorage-peerStorageMsg = PeerStorage (BS.replicate 100 0xab)--{-# NOINLINE peerStorageRetrievalMsg #-}-peerStorageRetrievalMsg :: PeerStorageRetrieval-peerStorageRetrievalMsg = PeerStorageRetrieval (BS.replicate 50 0xcd)---- Pre-encoded message bytes for decoder benchmarks--{-# NOINLINE initMinimalBytes #-}-initMinimalBytes :: BS.ByteString-initMinimalBytes = either (const BS.empty) id (encodeInit minimalInit)--{-# NOINLINE initWithTlvsBytes #-}-initWithTlvsBytes :: BS.ByteString-initWithTlvsBytes = either (const BS.empty) id (encodeInit initWithTlvs)--{-# NOINLINE errorBytes #-}-errorBytes :: BS.ByteString-errorBytes = either (const BS.empty) id (encodeError errorMsg)--{-# NOINLINE warningBytes #-}-warningBytes :: BS.ByteString-warningBytes = either (const BS.empty) id (encodeWarning warningMsg)--{-# NOINLINE pingMinimalBytes #-}-pingMinimalBytes :: BS.ByteString-pingMinimalBytes = either (const BS.empty) id (encodePing pingMinimal)--{-# NOINLINE pingWithPaddingBytes #-}-pingWithPaddingBytes :: BS.ByteString-pingWithPaddingBytes = either (const BS.empty) id (encodePing pingWithPadding)--{-# NOINLINE pongBytes #-}-pongBytes :: BS.ByteString-pongBytes = either (const BS.empty) id (encodePong pongMsg)--{-# NOINLINE peerStorageBytes #-}-peerStorageBytes :: BS.ByteString-peerStorageBytes = either (const BS.empty) id (encodePeerStorage peerStorageMsg)--{-# NOINLINE peerStorageRetrievalBytes #-}-peerStorageRetrievalBytes :: BS.ByteString-peerStorageRetrievalBytes =-  either (const BS.empty) id (encodePeerStorageRetrieval peerStorageRetrievalMsg)---- Envelope fixtures--{-# NOINLINE initMessage #-}-initMessage :: Message-initMessage = MsgInitVal minimalInit--{-# NOINLINE pingMessage #-}-pingMessage :: Message-pingMessage = MsgPingVal pingMinimal--{-# NOINLINE envelopeBytes #-}-envelopeBytes :: BS.ByteString-envelopeBytes = either (const BS.empty) id (encodeEnvelope initMessage Nothing)---- Main ------------------------------------------------------------------------- main :: IO () main = mainWith $ do-  setColumns [Case, Allocated, GCs, Max]--  -- Primitive encoders ----------------------------------------------------------  wgroup "Primitive Encoders" $ do-    func "encodeU16" encodeU16 w16Val-    func "encodeU32" encodeU32 w32Val-    func "encodeU64" encodeU64 w64Val-    func "encodeS8" encodeS8 s8Val-    func "encodeS16" encodeS16 s16Val-    func "encodeS32" encodeS32 s32Val-    func "encodeS64" encodeS64 s64Val--  wgroup "Truncated Unsigned Encoders" $ do-    func "encodeTu16/small" encodeTu16 tu16Small-    func "encodeTu16/full" encodeTu16 tu16Full-    func "encodeTu32/small" encodeTu32 tu32Small-    func "encodeTu32/full" encodeTu32 tu32Full-    func "encodeTu64/small" encodeTu64 tu64Small-    func "encodeTu64/full" encodeTu64 tu64Full--  wgroup "Minimal Signed Encoder" $ do-    func "encodeMinSigned/1-byte" encodeMinSigned (0 :: Int64)-    func "encodeMinSigned/2-byte" encodeMinSigned (1000 :: Int64)-    func "encodeMinSigned/4-byte" encodeMinSigned (100000 :: Int64)-    func "encodeMinSigned/8-byte" encodeMinSigned s64Val--  wgroup "BigSize Encoder" $ do-    func "encodeBigSize/1-byte" encodeBigSize bigSizeSmall-    func "encodeBigSize/3-byte" encodeBigSize bigSizeMedium-    func "encodeBigSize/9-byte" encodeBigSize bigSizeLarge--  -- Primitive decoders ----------------------------------------------------------  wgroup "Primitive Decoders" $ do-    func "decodeU16" decodeU16 u16Bytes-    func "decodeU32" decodeU32 u32Bytes-    func "decodeU64" decodeU64 u64Bytes-    func "decodeS8" decodeS8 s8Bytes-    func "decodeS16" decodeS16 s16Bytes-    func "decodeS32" decodeS32 s32Bytes-    func "decodeS64" decodeS64 s64Bytes-    func "decodeBigSize" decodeBigSize bigSizeBytes--  -- TLV operations --------------------------------------------------------------  wgroup "TLV Encoding" $ do-    func "encodeTlvRecord" encodeTlvRecord tlvRecord1-    func "encodeTlvStream/1-record" encodeTlvStream tlvStream1-    func "encodeTlvStream/5-records" encodeTlvStream tlvStream5-    func "encodeTlvStream/20-records" encodeTlvStream tlvStream20--  wgroup "TLV Decoding" $ do-    func "decodeTlvStreamRaw/1-record" decodeTlvStreamRaw tlvStreamBytes1-    func "decodeTlvStreamRaw/5-records" decodeTlvStreamRaw tlvStreamBytes5-    func "decodeTlvStreamRaw/20-records" decodeTlvStreamRaw tlvStreamBytes20-    func "decodeTlvStream/1-record" decodeTlvStream tlvStreamBytes1-    func "decodeTlvStream/5-records" decodeTlvStream tlvStreamBytes5--  -- Message encoders ------------------------------------------------------------  wgroup "Message Encoders" $ do-    func "encodeInit/minimal" encodeInit minimalInit-    func "encodeInit/with-features" encodeInit initWithFeatures-    func "encodeInit/with-tlvs" encodeInit initWithTlvs-    func "encodeError" encodeError errorMsg-    func "encodeWarning" encodeWarning warningMsg-    func "encodePing/minimal" encodePing pingMinimal-    func "encodePing/with-padding" encodePing pingWithPadding-    func "encodePong" encodePong pongMsg-    func "encodePeerStorage" encodePeerStorage peerStorageMsg-    func "encodePeerStorageRetrieval" encodePeerStorageRetrieval-      peerStorageRetrievalMsg--  -- Message decoders ------------------------------------------------------------  wgroup "Message Decoders" $ do-    func "decodeInit/minimal" decodeInit initMinimalBytes-    func "decodeInit/with-tlvs" decodeInit initWithTlvsBytes-    func "decodeError" decodeError errorBytes-    func "decodeWarning" decodeWarning warningBytes-    func "decodePing/minimal" decodePing pingMinimalBytes-    func "decodePing/with-padding" decodePing pingWithPaddingBytes-    func "decodePong" decodePong pongBytes-    func "decodePeerStorage" decodePeerStorage peerStorageBytes-    func "decodePeerStorageRetrieval" decodePeerStorageRetrieval-      peerStorageRetrievalBytes--  -- Envelope operations ---------------------------------------------------------  wgroup "Envelope Operations" $ do-    func "encodeEnvelope/init" (flip encodeEnvelope Nothing) initMessage-    func "encodeEnvelope/ping" (flip encodeEnvelope Nothing) pingMessage-    func "decodeEnvelope" decodeEnvelope envelopeBytes+  wgroup "primitives" $ do+    func "baseline (weigh overhead)" (+ (1 :: Int)) 1+    func "encode_u16" encode_u16 0x0102+    func "encode_u64" encode_u64 0x0102030405060708+    func "decode_u64" decode_u64 "\x01\x02\x03\x04\x05\x06\x07\x08"+    func "encode_tu64" encode_tu64 0x010203+    func "encode_bigsize (9 bytes)" encode_bigsize 0x100000000+  wgroup "tlv" $ do+    func "encode_tlv_stream (21 records)" encode_tlv_stream tlvs+    func "decode_tlv_stream (21 records)"+      (decode_tlv_stream (const False)) tlvs_bytes+  wgroup "messages" $ do+    func "encode init" encode_message init_msg+    func "decode init" decode_message init_bytes+    func "encode ping" encode_message ping_msg+    func "decode ping" decode_message ping_bytes+    func "encode error (with tlvs)" encode_message error_msg+    func "decode error (with tlvs)" decode_message error_bytes
lib/Lightning/Protocol/BOLT1.hs view
@@ -6,96 +6,152 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Base protocol for the Lightning Network, per--- [BOLT #1](https://github.com/lightning/bolts/blob/master/01-messaging.md).+-- The base protocol of the Lightning Network, per+-- [BOLT #1](https://github.com/lightning/bolts/blob/master/01-messaging.md):+-- fundamental types and their encodings, the TLV format, and the+-- @init@, @error@, @warning@, @ping@, @pong@, @peer_storage@ and+-- @peer_storage_retrieval@ messages.+--+-- The fundamental types and TLV machinery here are shared by the other+-- ppad BOLT libraries.  module Lightning.Protocol.BOLT1 (-  -- * Message types+  -- * Messages     Message(..)-  , MsgType(..)-  , msgTypeWord--  -- * Channel identifiers-  , ChannelId-  , channelId-  , allChannels--  -- ** Setup messages+  , message_type   , Init(..)   , Error(..)   , Warning(..)--  -- ** Control messages   , Ping(..)   , Pong(..)--  -- ** Peer storage+  , ping_response   , PeerStorage(..)   , PeerStorageRetrieval(..) +  -- * Encoding and decoding messages+  , encode_message+  , decode_message+  , encode_envelope+  , decode_envelope+  , EncodeError(..)+  , DecodeError(..)++  -- ** Payloads+  , encode_init+  , decode_init+  , encode_error+  , decode_error+  , encode_warning+  , decode_warning+  , encode_ping+  , decode_ping+  , encode_pong+  , decode_pong+  , encode_peer_storage+  , decode_peer_storage+  , encode_peer_storage_retrieval+  , decode_peer_storage_retrieval+   -- * TLV   , TlvRecord(..)   , TlvStream-  , unTlvStream-  , tlvStream-  , unsafeTlvStream+  , tlv_stream+  , un_tlv_stream+  , empty_tlv_stream+  , lookup_tlv+  , filter_tlv_stream+  , encode_tlv_stream+  , decode_tlv_stream   , TlvError(..)-  , encodeTlvStream-  , decodeTlvStream-  , decodeTlvStreamWith-  , decodeTlvStreamRaw -  -- ** Init TLVs-  , InitTlv(..)+  -- * Fundamental types   , ChainHash-  , chainHash-  , unChainHash--  -- * Message envelope-  , Envelope(..)--  -- * Encoding-  , EncodeError(..)-  , encodeMessage-  , encodeEnvelope--  -- * Decoding-  , DecodeError(..)-  , decodeMessage-  , decodeEnvelope-  , decodeEnvelopeWith+  , chain_hash+  , un_chain_hash+  , ChannelId+  , channel_id+  , un_channel_id+  , all_channels+  , Signature+  , signature+  , un_signature+  , Point+  , point+  , un_point+  , PaymentHash+  , payment_hash+  , un_payment_hash+  , PaymentPreimage+  , payment_preimage+  , un_payment_preimage+  , PerCommitmentSecret+  , per_commitment_secret+  , un_per_commitment_secret+  , ShortChannelId(..)+  , short_channel_id+  , scid_block_height+  , scid_tx_index+  , scid_output_index -  -- * Primitive encoding-  , encodeU16-  , encodeU32-  , encodeU64-  , encodeS8-  , encodeS16-  , encodeS32-  , encodeS64-  , encodeTu16-  , encodeTu32-  , encodeTu64-  , encodeMinSigned-  , encodeBigSize+  -- ** Amounts+  , Satoshi+  , satoshi+  , un_satoshi+  , max_satoshi+  , MilliSatoshi+  , milli_satoshi+  , un_milli_satoshi+  , max_milli_satoshi+  , sat_to_msat+  , msat_to_sat+  , add_sat+  , sub_sat+  , add_msat+  , sub_msat -  -- * Primitive decoding-  , decodeU16-  , decodeU32-  , decodeU64-  , decodeS8-  , decodeS16-  , decodeS32-  , decodeS64-  , decodeTu16-  , decodeTu32-  , decodeTu64-  , decodeMinSigned-  , decodeBigSize+  -- * Primitive encodings+  -- | Decoders return the decoded value and the remaining input, except+  --   for the truncated integers, which occupy their whole input.+  , encode_u16+  , encode_u32+  , encode_u64+  , decode_u16+  , decode_u32+  , decode_u64+  , encode_s8+  , encode_s16+  , encode_s32+  , encode_s64+  , decode_s8+  , decode_s16+  , decode_s32+  , decode_s64+  , encode_tu16+  , encode_tu32+  , encode_tu64+  , decode_tu16+  , decode_tu32+  , decode_tu64+  , encode_bigsize+  , decode_bigsize+  , encode_u16_prefixed+  , decode_u16_prefixed+  , decode_chain_hash+  , decode_channel_id+  , decode_signature+  , decode_point+  , decode_payment_hash+  , decode_payment_preimage+  , decode_per_commitment_secret+  , encode_short_channel_id+  , decode_short_channel_id+  , encode_satoshi+  , decode_satoshi+  , encode_milli_satoshi+  , decode_milli_satoshi   ) where --- Re-export from sub-modules+import Lightning.Protocol.BOLT1.Codec+import Lightning.Protocol.BOLT1.Message import Lightning.Protocol.BOLT1.Prim import Lightning.Protocol.BOLT1.TLV-import Lightning.Protocol.BOLT1.Message-import Lightning.Protocol.BOLT1.Codec
lib/Lightning/Protocol/BOLT1/Codec.hs view
@@ -1,8 +1,6 @@-{-# OPTIONS_HADDOCK prune #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE LambdaCase #-}  -- | -- Module: Lightning.Protocol.BOLT1.Codec@@ -10,340 +8,277 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Message encoding and decoding for BOLT #1.+-- Encoding and decoding of BOLT #1 messages.  module Lightning.Protocol.BOLT1.Codec (-  -- * Encoding errors     EncodeError(..)+  , DecodeError(..) -  -- * Message encoding-  , encodeInit-  , encodeError-  , encodeWarning-  , encodePing-  , encodePong-  , encodePeerStorage-  , encodePeerStorageRetrieval-  , encodeMessage-  , encodeEnvelope+  , encode_envelope+  , decode_envelope -  -- * Decoding errors-  , DecodeError(..)+  , encode_message+  , decode_message -  -- * Message decoding-  , decodeInit-  , decodeError-  , decodeWarning-  , decodePing-  , decodePong-  , decodePeerStorage-  , decodePeerStorageRetrieval-  , decodeMessage-  , decodeEnvelope-  , decodeEnvelopeWith+  , encode_init+  , decode_init+  , encode_error+  , decode_error+  , encode_warning+  , decode_warning+  , encode_ping+  , decode_ping+  , encode_pong+  , decode_pong+  , encode_peer_storage+  , decode_peer_storage+  , encode_peer_storage_retrieval+  , decode_peer_storage_retrieval++  , ping_response   ) where  import Control.DeepSeq (NFData)-import Control.Monad (when, unless) import qualified Data.ByteString as BS import Data.Word (Word16, Word64) import GHC.Generics (Generic)+import Lightning.Protocol.BOLT1.Message import Lightning.Protocol.BOLT1.Prim import Lightning.Protocol.BOLT1.TLV-import Lightning.Protocol.BOLT1.Message---- Encoding errors -------------------------------------------------------------+import qualified Lightning.Protocol.BOLT9 as BOLT9 --- | Encoding errors.+-- | Why a message failed to encode. data EncodeError-  = EncodeLengthOverflow   -- ^ Field length exceeds u16 max (65535 bytes)-  | EncodeMessageTooLarge  -- ^ Total message size exceeds 65535 bytes-  deriving stock (Eq, Show, Generic)+  = EncodeLengthOverflow+    -- ^ a length-prefixed field exceeds 65535 bytes+  | EncodeMessageTooLarge+    -- ^ the message (type and payload) exceeds 65535 bytes+  | EncodeInvalidTlvs+    -- ^ a record in a @_tlvs@ field duplicates a typed field+  deriving (Eq, Show, Generic)  instance NFData EncodeError --- Message encoding ------------------------------------------------------------+-- | Why a message failed to decode.+data DecodeError+  = DecodeInsufficientBytes+    -- ^ the input ended before a field did+  | DecodeTlvError !TlvError+    -- ^ the message's TLV stream is malformed+  | DecodeInvalidTlvValue !Word64+    -- ^ a known TLV record (of the given type) has a malformed value+  | DecodeUnknownEvenType !Word16+    -- ^ an unknown even message type; the connection must be closed+  | DecodeUnknownOddType !Word16+    -- ^ an unknown odd message type; the message must be ignored+  deriving (Eq, Show, Generic) --- | Encode an Init message payload.-encodeInit :: Init -> Either EncodeError BS.ByteString-encodeInit (Init gf feat tlvs) = do-  gfLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength gf)-  featLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength feat)-  Right $ mconcat-    [ gfLen-    , gf-    , featLen-    , feat-    , encodeTlvStream (encodeInitTlvs tlvs)-    ]+instance NFData DecodeError --- | Encode an Error message payload.-encodeError :: Error -> Either EncodeError BS.ByteString-encodeError (Error cid dat) = do-  datLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength dat)-  Right $ mconcat [unChannelId cid, datLen, dat]+-- framing -------------------------------------------------------------------- --- | Encode a Warning message payload.-encodeWarning :: Warning -> Either EncodeError BS.ByteString-encodeWarning (Warning cid dat) = do-  datLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength dat)-  Right $ mconcat [unChannelId cid, datLen, dat]+-- | Prefix a payload with its message type. Fails if the result+--   exceeds the 65535-byte message limit.+--+--   >>> encode_envelope 19 "\NUL\NUL"+--   Right "\NUL\DC3\NUL\NUL"+encode_envelope :: Word16 -> BS.ByteString -> Either EncodeError BS.ByteString+encode_envelope t payload+  | BS.length payload > 65533 = Left EncodeMessageTooLarge+  | otherwise                 = Right (encode_u16 t <> payload) --- | Encode a Ping message payload.-encodePing :: Ping -> Either EncodeError BS.ByteString-encodePing (Ping numPong ignored) = do-  ignoredLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength ignored)-  Right $ mconcat [encodeU16 numPong, ignoredLen, ignored]+-- | Split a message into its type and payload.+--+--   >>> decode_envelope "\NUL\DC3\NUL\NUL"+--   Right (19,"\NUL\NUL")+decode_envelope :: BS.ByteString -> Either DecodeError (Word16, BS.ByteString)+decode_envelope bs = case decode_u16 bs of+  Just r  -> Right r+  Nothing -> Left DecodeInsufficientBytes --- | Encode a Pong message payload.-encodePong :: Pong -> Either EncodeError BS.ByteString-encodePong (Pong ignored) = do-  ignoredLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength ignored)-  Right $ mconcat [ignoredLen, ignored]+-- | Encode a BOLT #1 message, including its type.+--+--   >>> encode_message (MsgPong (Pong "" empty_tlv_stream))+--   Right "\NUL\DC3\NUL\NUL"+encode_message :: Message -> Either EncodeError BS.ByteString+encode_message m = do+  payload <- case m of+    MsgInit a                 -> encode_init a+    MsgError a                -> encode_error a+    MsgWarning a              -> encode_warning a+    MsgPing a                 -> encode_ping a+    MsgPong a                 -> encode_pong a+    MsgPeerStorage a          -> encode_peer_storage a+    MsgPeerStorageRetrieval a -> encode_peer_storage_retrieval a+  encode_envelope (message_type m) payload --- | Encode a PeerStorage message payload.-encodePeerStorage :: PeerStorage -> Either EncodeError BS.ByteString-encodePeerStorage (PeerStorage blob) = do-  blobLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength blob)-  Right $ mconcat [blobLen, blob]+-- | Decode a BOLT #1 message, including its type.+--+--   A type that BOLT #1 doesn't define yields 'DecodeUnknownEvenType'+--   or 'DecodeUnknownOddType'; callers dispatching messages from other+--   BOLTs should use 'decode_envelope' first.+--+--   >>> decode_message "\NUL\DC3\NUL\NUL"+--   Right (MsgPong (Pong {pong_ignored = "", pong_tlvs = TlvStream []}))+--   >>> decode_message "\NUL\DC4\NUL\NUL"+--   Left (DecodeUnknownEvenType 20)+decode_message :: BS.ByteString -> Either DecodeError Message+decode_message bs = do+  (t, payload) <- decode_envelope bs+  case t of+    16 -> MsgInit <$> decode_init payload+    17 -> MsgError <$> decode_error payload+    1  -> MsgWarning <$> decode_warning payload+    18 -> MsgPing <$> decode_ping payload+    19 -> MsgPong <$> decode_pong payload+    7  -> MsgPeerStorage <$> decode_peer_storage payload+    9  -> MsgPeerStorageRetrieval <$> decode_peer_storage_retrieval payload+    _ | even t    -> Left (DecodeUnknownEvenType t)+      | otherwise -> Left (DecodeUnknownOddType t) --- | Encode a PeerStorageRetrieval message payload.-encodePeerStorageRetrieval-  :: PeerStorageRetrieval -> Either EncodeError BS.ByteString-encodePeerStorageRetrieval (PeerStorageRetrieval blob) = do-  blobLen <- maybe (Left EncodeLengthOverflow) Right (encodeLength blob)-  Right $ mconcat [blobLen, blob]+-- helpers -------------------------------------------------------------------- --- | Encode a message to its payload bytes.------ Checks that the payload does not exceed 65533 bytes (the maximum--- possible given the 2-byte type field and 65535-byte message limit).-encodeMessage :: Message -> Either EncodeError BS.ByteString-encodeMessage msg = do-  payload <- case msg of-    MsgInitVal m                 -> encodeInit m-    MsgErrorVal m                -> encodeError m-    MsgWarningVal m              -> encodeWarning m-    MsgPingVal m                 -> encodePing m-    MsgPongVal m                 -> encodePong m-    MsgPeerStorageVal m          -> encodePeerStorage m-    MsgPeerStorageRetrievalVal m -> encodePeerStorageRetrieval m-  -- Payload must leave room for 2-byte type (max 65533 bytes)-  when (BS.length payload > 65533) $-    Left EncodeMessageTooLarge-  Right payload+prefixed :: BS.ByteString -> Either EncodeError BS.ByteString+prefixed bs = maybe (Left EncodeLengthOverflow) Right (encode_u16_prefixed bs) --- | Encode a message as a complete envelope (type + payload + extension).------ Per BOLT #1, the total message size must not exceed 65535 bytes.-encodeEnvelope :: Message -> Maybe TlvStream -> Either EncodeError BS.ByteString-encodeEnvelope msg mext = do-  payload <- encodeMessage msg-  let !typeBytes = encodeU16 (msgTypeWord (messageType msg))-      !extBytes = maybe BS.empty encodeTlvStream mext-      !result = mconcat [typeBytes, payload, extBytes]-  -- Per BOLT #1: message size must fit in 2 bytes (max 65535)-  when (BS.length result > 65535) $-    Left EncodeMessageTooLarge-  Right result+need :: Maybe a -> Either DecodeError a+need = maybe (Left DecodeInsufficientBytes) Right+{-# INLINE need #-} --- Decoding errors -------------------------------------------------------------+-- an extension stream in which no types are known+extension :: BS.ByteString -> Either DecodeError TlvStream+extension bs = either (Left . DecodeTlvError) Right+  (decode_tlv_stream (const False) bs) --- | Decoding errors.-data DecodeError-  = DecodeInsufficientBytes-  | DecodeInvalidLength-  | DecodeUnknownEvenType !Word16-  | DecodeUnknownOddType !Word16-  | DecodeTlvError !TlvError-  | DecodeInvalidChannelId-  | DecodeInvalidExtension !TlvError-  deriving stock (Eq, Show, Generic)+-- init ----------------------------------------------------------------------- -instance NFData DecodeError+-- | Encode an 'Init' payload.+encode_init :: Init -> Either EncodeError BS.ByteString+encode_init (Init gf f nets addr tlvs) = do+  gf' <- prefixed (BOLT9.render gf)+  f'  <- prefixed (BOLT9.render f)+  let known = maybe [] (\cs -> [TlvRecord 1 (mconcat (map un_chain_hash cs))])+                nets+           <> maybe [] (\a -> [TlvRecord 3 a]) addr+  stream <- maybe (Left EncodeInvalidTlvs) Right+              (tlv_stream (known <> un_tlv_stream tlvs))+  pure (gf' <> f' <> encode_tlv_stream stream) --- Message decoding ------------------------------------------------------------+-- | Decode an 'Init' payload.+decode_init :: BS.ByteString -> Either DecodeError Init+decode_init bs = do+  (gf, r0) <- need (decode_u16_prefixed bs)+  (f, r1)  <- need (decode_u16_prefixed r0)+  stream   <- either (Left . DecodeTlvError) Right+                (decode_tlv_stream (\t -> t == 1 || t == 3) r1)+  nets <- traverse networks (lookup_tlv 1 stream)+  pure Init+    { init_global_features = BOLT9.parse gf+    , init_features        = BOLT9.parse f+    , init_networks        = nets+    , init_remote_addr     = lookup_tlv 3 stream+    , init_tlvs            = filter_tlv_stream (\t -> t /= 1 && t /= 3) stream+    }+  where+    networks v+      | BS.length v `rem` 32 /= 0 = Left (DecodeInvalidTlvValue 1)+      | otherwise = Right (chunks v)+    chunks !v = case decode_chain_hash v of+      Just (h, rest) -> h : chunks rest+      Nothing        -> [] --- | Decode an Init message from payload bytes.------ Returns the decoded message and any remaining bytes.-decodeInit :: BS.ByteString -> Either DecodeError (Init, BS.ByteString)-decodeInit !bs = do-  (gfLen, rest1) <- maybe (Left DecodeInsufficientBytes) Right-                      (decodeU16 bs)-  unless (BS.length rest1 >= fromIntegral gfLen) $-    Left DecodeInsufficientBytes-  let !gf = BS.take (fromIntegral gfLen) rest1-      !rest2 = BS.drop (fromIntegral gfLen) rest1-  (fLen, rest3) <- maybe (Left DecodeInsufficientBytes) Right-                     (decodeU16 rest2)-  unless (BS.length rest3 >= fromIntegral fLen) $-    Left DecodeInsufficientBytes-  let !feat = BS.take (fromIntegral fLen) rest3-      !rest4 = BS.drop (fromIntegral fLen) rest3-  -- Parse optional TLV stream (consumes all remaining bytes for init)-  tlvs <- if BS.null rest4-    then Right (unsafeTlvStream [])-    else either (Left . DecodeTlvError) Right (decodeTlvStream rest4)-  initTlvList <- either (Left . DecodeTlvError) Right-                   (parseInitTlvs tlvs)-  -- Init consumes all bytes (TLVs are part of init, not extensions)-  Right (Init gf feat initTlvList, BS.empty)+-- error, warning ------------------------------------------------------------- --- | Decode an Error message from payload bytes.-decodeError :: BS.ByteString -> Either DecodeError (Error, BS.ByteString)-decodeError !bs = do-  unless (BS.length bs >= 32) $ Left DecodeInsufficientBytes-  let !cidBytes = BS.take 32 bs-      !rest1 = BS.drop 32 bs-  cid <- maybe (Left DecodeInvalidChannelId) Right (channelId cidBytes)-  (dLen, rest2) <- maybe (Left DecodeInsufficientBytes) Right-                     (decodeU16 rest1)-  unless (BS.length rest2 >= fromIntegral dLen) $-    Left DecodeInsufficientBytes-  let !dat = BS.take (fromIntegral dLen) rest2-      !rest3 = BS.drop (fromIntegral dLen) rest2-  Right (Error cid dat, rest3)+-- | Encode an 'Error' payload.+encode_error :: Error -> Either EncodeError BS.ByteString+encode_error (Error cid dat tlvs) = do+  dat' <- prefixed dat+  pure (un_channel_id cid <> dat' <> encode_tlv_stream tlvs) --- | Decode a Warning message from payload bytes.-decodeWarning :: BS.ByteString -> Either DecodeError (Warning, BS.ByteString)-decodeWarning !bs = do-  unless (BS.length bs >= 32) $ Left DecodeInsufficientBytes-  let !cidBytes = BS.take 32 bs-      !rest1 = BS.drop 32 bs-  cid <- maybe (Left DecodeInvalidChannelId) Right (channelId cidBytes)-  (dLen, rest2) <- maybe (Left DecodeInsufficientBytes) Right-                     (decodeU16 rest1)-  unless (BS.length rest2 >= fromIntegral dLen) $-    Left DecodeInsufficientBytes-  let !dat = BS.take (fromIntegral dLen) rest2-      !rest3 = BS.drop (fromIntegral dLen) rest2-  Right (Warning cid dat, rest3)+-- | Decode an 'Error' payload.+decode_error :: BS.ByteString -> Either DecodeError Error+decode_error bs = do+  (cid, r0) <- need (decode_channel_id bs)+  (dat, r1) <- need (decode_u16_prefixed r0)+  Error cid dat <$> extension r1 --- | Decode a Ping message from payload bytes.-decodePing :: BS.ByteString -> Either DecodeError (Ping, BS.ByteString)-decodePing !bs = do-  (numPong, rest1) <- maybe (Left DecodeInsufficientBytes) Right-                        (decodeU16 bs)-  (bLen, rest2) <- maybe (Left DecodeInsufficientBytes) Right-                     (decodeU16 rest1)-  unless (BS.length rest2 >= fromIntegral bLen) $-    Left DecodeInsufficientBytes-  let !ignored = BS.take (fromIntegral bLen) rest2-      !rest3 = BS.drop (fromIntegral bLen) rest2-  Right (Ping numPong ignored, rest3)+-- | Encode a 'Warning' payload.+encode_warning :: Warning -> Either EncodeError BS.ByteString+encode_warning (Warning cid dat tlvs) = do+  dat' <- prefixed dat+  pure (un_channel_id cid <> dat' <> encode_tlv_stream tlvs) --- | Decode a Pong message from payload bytes.-decodePong :: BS.ByteString -> Either DecodeError (Pong, BS.ByteString)-decodePong !bs = do-  (bLen, rest1) <- maybe (Left DecodeInsufficientBytes) Right-                     (decodeU16 bs)-  unless (BS.length rest1 >= fromIntegral bLen) $-    Left DecodeInsufficientBytes-  let !ignored = BS.take (fromIntegral bLen) rest1-      !rest2 = BS.drop (fromIntegral bLen) rest1-  Right (Pong ignored, rest2)+-- | Decode a 'Warning' payload.+decode_warning :: BS.ByteString -> Either DecodeError Warning+decode_warning bs = do+  (cid, r0) <- need (decode_channel_id bs)+  (dat, r1) <- need (decode_u16_prefixed r0)+  Warning cid dat <$> extension r1 --- | Decode a PeerStorage message from payload bytes.-decodePeerStorage-  :: BS.ByteString -> Either DecodeError (PeerStorage, BS.ByteString)-decodePeerStorage !bs = do-  (bLen, rest1) <- maybe (Left DecodeInsufficientBytes) Right-                     (decodeU16 bs)-  unless (BS.length rest1 >= fromIntegral bLen) $-    Left DecodeInsufficientBytes-  let !blob = BS.take (fromIntegral bLen) rest1-      !rest2 = BS.drop (fromIntegral bLen) rest1-  Right (PeerStorage blob, rest2)+-- ping, pong ----------------------------------------------------------------- --- | Decode a PeerStorageRetrieval message from payload bytes.-decodePeerStorageRetrieval-  :: BS.ByteString-  -> Either DecodeError (PeerStorageRetrieval, BS.ByteString)-decodePeerStorageRetrieval !bs = do-  (bLen, rest1) <- maybe (Left DecodeInsufficientBytes) Right-                     (decodeU16 bs)-  unless (BS.length rest1 >= fromIntegral bLen) $-    Left DecodeInsufficientBytes-  let !blob = BS.take (fromIntegral bLen) rest1-      !rest2 = BS.drop (fromIntegral bLen) rest1-  Right (PeerStorageRetrieval blob, rest2)+-- | Encode a 'Ping' payload.+encode_ping :: Ping -> Either EncodeError BS.ByteString+encode_ping (Ping n ign tlvs) = do+  ign' <- prefixed ign+  pure (encode_u16 n <> ign' <> encode_tlv_stream tlvs) --- | Decode a message from its type and payload.------ Returns the decoded message and any remaining bytes (for extensions).--- For unknown types, returns an appropriate error.-decodeMessage-  :: MsgType -> BS.ByteString -> Either DecodeError (Message, BS.ByteString)-decodeMessage MsgInit bs = do-  (m, rest) <- decodeInit bs-  Right (MsgInitVal m, rest)-decodeMessage MsgError bs = do-  (m, rest) <- decodeError bs-  Right (MsgErrorVal m, rest)-decodeMessage MsgWarning bs = do-  (m, rest) <- decodeWarning bs-  Right (MsgWarningVal m, rest)-decodeMessage MsgPing bs = do-  (m, rest) <- decodePing bs-  Right (MsgPingVal m, rest)-decodeMessage MsgPong bs = do-  (m, rest) <- decodePong bs-  Right (MsgPongVal m, rest)-decodeMessage MsgPeerStorage bs = do-  (m, rest) <- decodePeerStorage bs-  Right (MsgPeerStorageVal m, rest)-decodeMessage MsgPeerStorageRet bs = do-  (m, rest) <- decodePeerStorageRetrieval bs-  Right (MsgPeerStorageRetrievalVal m, rest)-decodeMessage (MsgUnknown w) _-  | even w    = Left (DecodeUnknownEvenType w)-  | otherwise = Left (DecodeUnknownOddType w)+-- | Decode a 'Ping' payload.+decode_ping :: BS.ByteString -> Either DecodeError Ping+decode_ping bs = do+  (n, r0)   <- need (decode_u16 bs)+  (ign, r1) <- need (decode_u16_prefixed r0)+  Ping n ign <$> extension r1 --- | Decode a complete envelope (type + payload + optional extension).------ Per BOLT #1:--- - Unknown odd message types are ignored (returns Nothing for message)--- - Unknown even message types cause connection close (returns error)--- - Invalid extension TLV causes connection close (returns error)------ This uses the default policy of treating all extension TLV types as--- unknown. Use 'decodeEnvelopeWith' for configurable extension handling.------ Returns the decoded message (if known) and any extension TLVs.-decodeEnvelope-  :: BS.ByteString-  -> Either DecodeError (Maybe Message, Maybe TlvStream)-decodeEnvelope = decodeEnvelopeWith (const False)+-- | Encode a 'Pong' payload.+encode_pong :: Pong -> Either EncodeError BS.ByteString+encode_pong (Pong ign tlvs) = do+  ign' <- prefixed ign+  pure (ign' <> encode_tlv_stream tlvs) --- | Decode a complete envelope with configurable extension TLV handling.------ The predicate determines which extension TLV types are "known" and--- should be preserved. Unknown even types cause failure; unknown odd--- types are skipped.------ Use @decodeEnvelopeWith (const False)@ to reject all even extension--- types (the default behavior of 'decodeEnvelope').+-- | Decode a 'Pong' payload.+decode_pong :: BS.ByteString -> Either DecodeError Pong+decode_pong bs = do+  (ign, r0) <- need (decode_u16_prefixed bs)+  Pong ign <$> extension r0++-- | The 'Pong' a node must send in reply to a 'Ping', if any. Per+--   BOLT #1, a ping requesting 65532 or more bytes is ignored. ----- Use @decodeEnvelopeWith (const True)@ to accept all extension types.-decodeEnvelopeWith-  :: (Word64 -> Bool)  -- ^ Predicate: is this extension TLV type known?-  -> BS.ByteString-  -> Either DecodeError (Maybe Message, Maybe TlvStream)-decodeEnvelopeWith isKnownExt !bs = do-  (typeWord, rest1) <- maybe (Left DecodeInsufficientBytes) Right-                         (decodeU16 bs)-  let !msgType = parseMsgType typeWord-  case msgType of-    MsgUnknown w-      | even w    -> Left (DecodeUnknownEvenType w)-      | otherwise -> Right (Nothing, Nothing)  -- Ignore unknown odd types-    _ -> do-      (msg, rest2) <- decodeMessage msgType rest1-      -- Parse any remaining bytes as extension TLV-      ext <- if BS.null rest2-        then Right Nothing-        else case decodeTlvStreamWith isKnownExt rest2 of-          Left e  -> Left (DecodeInvalidExtension e)-          Right s -> Right (Just s)-      Right (Just msg, ext)+--   >>> ping_response (Ping 4 "" empty_tlv_stream)+--   Just (Pong {pong_ignored = "\NUL\NUL\NUL\NUL", pong_tlvs = TlvStream []})+--   >>> ping_response (Ping 65532 "" empty_tlv_stream)+--   Nothing+ping_response :: Ping -> Maybe Pong+ping_response (Ping n _ _)+  | n >= 65532 = Nothing+  | otherwise  =+      Just (Pong (BS.replicate (fromIntegral n) 0x00) empty_tlv_stream)++-- peer storage ---------------------------------------------------------------++-- | Encode a 'PeerStorage' payload.+encode_peer_storage :: PeerStorage -> Either EncodeError BS.ByteString+encode_peer_storage (PeerStorage blob tlvs) = do+  blob' <- prefixed blob+  pure (blob' <> encode_tlv_stream tlvs)++-- | Decode a 'PeerStorage' payload.+decode_peer_storage :: BS.ByteString -> Either DecodeError PeerStorage+decode_peer_storage bs = do+  (blob, r0) <- need (decode_u16_prefixed bs)+  PeerStorage blob <$> extension r0++-- | Encode a 'PeerStorageRetrieval' payload.+encode_peer_storage_retrieval+  :: PeerStorageRetrieval -> Either EncodeError BS.ByteString+encode_peer_storage_retrieval (PeerStorageRetrieval blob tlvs) = do+  blob' <- prefixed blob+  pure (blob' <> encode_tlv_stream tlvs)++-- | Decode a 'PeerStorageRetrieval' payload.+decode_peer_storage_retrieval+  :: BS.ByteString -> Either DecodeError PeerStorageRetrieval+decode_peer_storage_retrieval bs = do+  (blob, r0) <- need (decode_u16_prefixed bs)+  PeerStorageRetrieval blob <$> extension r0
lib/Lightning/Protocol/BOLT1/Message.hs view
@@ -1,6 +1,5 @@-{-# OPTIONS_HADDOCK prune #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}  -- | -- Module: Lightning.Protocol.BOLT1.Message@@ -8,206 +7,126 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- Message types for BOLT #1.+-- The messages defined by BOLT #1.  module Lightning.Protocol.BOLT1.Message (-  -- * Message types-    MsgType(..)-  , msgTypeWord-  , parseMsgType--  -- * Channel identifiers-  , ChannelId-  , channelId-  , unChannelId-  , allChannels--  -- * Setup messages-  , Init(..)+    Init(..)   , Error(..)   , Warning(..)--  -- * Control messages   , Ping(..)   , Pong(..)--  -- * Peer storage messages   , PeerStorage(..)   , PeerStorageRetrieval(..)--  -- * Message envelope   , Message(..)-  , messageType-  , Envelope(..)+  , message_type   ) where  import Control.DeepSeq (NFData) import qualified Data.ByteString as BS import Data.Word (Word16) import GHC.Generics (Generic)+import Lightning.Protocol.BOLT1.Prim import Lightning.Protocol.BOLT1.TLV---- Message types ------------------------------------------------------------------- | BOLT #1 message type codes.-data MsgType-  = MsgInit              -- ^ 16-  | MsgError             -- ^ 17-  | MsgPing              -- ^ 18-  | MsgPong              -- ^ 19-  | MsgWarning           -- ^ 1-  | MsgPeerStorage       -- ^ 7-  | MsgPeerStorageRet    -- ^ 9-  | MsgUnknown !Word16   -- ^ Unknown type-  deriving stock (Eq, Show, Generic)--instance NFData MsgType---- | Get the numeric type code for a message type.-msgTypeWord :: MsgType -> Word16-msgTypeWord MsgInit            = 16-msgTypeWord MsgError           = 17-msgTypeWord MsgPing            = 18-msgTypeWord MsgPong            = 19-msgTypeWord MsgWarning         = 1-msgTypeWord MsgPeerStorage     = 7-msgTypeWord MsgPeerStorageRet  = 9-msgTypeWord (MsgUnknown w)     = w---- | Parse a message type from a word.-parseMsgType :: Word16 -> MsgType-parseMsgType 16 = MsgInit-parseMsgType 17 = MsgError-parseMsgType 18 = MsgPing-parseMsgType 19 = MsgPong-parseMsgType 1  = MsgWarning-parseMsgType 7  = MsgPeerStorage-parseMsgType 9  = MsgPeerStorageRet-parseMsgType w  = MsgUnknown w---- Channel identifiers ------------------------------------------------------------- | A 32-byte channel identifier.------ Use 'channelId' to construct, which validates the length.--- Use 'allChannels' for connection-level errors (all-zeros channel ID).-newtype ChannelId = ChannelId BS.ByteString-  deriving stock (Eq, Show, Generic)--instance NFData ChannelId---- | Construct a 'ChannelId' from a 32-byte 'BS.ByteString'.------ Returns 'Nothing' if the input is not exactly 32 bytes.------ >>> channelId (BS.replicate 32 0x00)--- Just (ChannelId "\NUL\NUL...")--- >>> channelId "too short"--- Nothing-channelId :: BS.ByteString -> Maybe ChannelId-channelId bs-  | BS.length bs == 32 = Just (ChannelId bs)-  | otherwise          = Nothing-{-# INLINE channelId #-}+import qualified Lightning.Protocol.BOLT9 as BOLT9 --- | The all-zeros channel ID, used for connection-level errors.+-- | The @init@ message (type 16). ----- Per BOLT #1, setting channel_id to all zeros means the error applies--- to the connection rather than a specific channel.-allChannels :: ChannelId-allChannels = ChannelId (BS.replicate 32 0x00)---- | Extract the raw bytes from a 'ChannelId'.-unChannelId :: ChannelId -> BS.ByteString-unChannelId (ChannelId bs) = bs-{-# INLINE unChannelId #-}---- Message ADTs -------------------------------------------------------------------- | The init message (type 16).+--   The two feature vectors are kept as received. Per BOLT #1, a+--   receiver treats their union ('BOLT9.union') as the peer's+--   features. data Init = Init-  { initGlobalFeatures :: !BS.ByteString-  , initFeatures       :: !BS.ByteString-  , initTlvs           :: ![InitTlv]-  } deriving stock (Eq, Show, Generic)+  { init_global_features :: !BOLT9.FeatureVector+  , init_features        :: !BOLT9.FeatureVector+  , init_networks        :: !(Maybe [ChainHash])+    -- ^ @networks@ (TLV type 1): chains the node is interested in+  , init_remote_addr     :: !(Maybe BS.ByteString)+    -- ^ @remote_addr@ (TLV type 3): a raw BOLT #7 address descriptor+  , init_tlvs            :: !TlvStream+    -- ^ unknown odd TLV records+  } deriving (Eq, Show, Generic)  instance NFData Init --- | The error message (type 17).+-- | The @error@ message (type 17). data Error = Error-  { errorChannelId :: !ChannelId-  , errorData      :: !BS.ByteString-  } deriving stock (Eq, Show, Generic)+  { error_channel_id :: !ChannelId+  , error_data       :: !BS.ByteString+  , error_tlvs       :: !TlvStream+    -- ^ extension TLV records (all unknown odd)+  } deriving (Eq, Show, Generic)  instance NFData Error --- | The warning message (type 1).+-- | The @warning@ message (type 1). data Warning = Warning-  { warningChannelId :: !ChannelId-  , warningData      :: !BS.ByteString-  } deriving stock (Eq, Show, Generic)+  { warning_channel_id :: !ChannelId+  , warning_data       :: !BS.ByteString+  , warning_tlvs       :: !TlvStream+    -- ^ extension TLV records (all unknown odd)+  } deriving (Eq, Show, Generic)  instance NFData Warning --- | The ping message (type 18).+-- | The @ping@ message (type 18). data Ping = Ping-  { pingNumPongBytes :: {-# UNPACK #-} !Word16-  , pingIgnored      :: !BS.ByteString-  } deriving stock (Eq, Show, Generic)+  { ping_num_pong_bytes :: {-# UNPACK #-} !Word16+  , ping_ignored        :: !BS.ByteString+  , ping_tlvs           :: !TlvStream+    -- ^ extension TLV records (all unknown odd)+  } deriving (Eq, Show, Generic)  instance NFData Ping --- | The pong message (type 19).+-- | The @pong@ message (type 19). data Pong = Pong-  { pongIgnored :: !BS.ByteString-  } deriving stock (Eq, Show, Generic)+  { pong_ignored :: !BS.ByteString+  , pong_tlvs    :: !TlvStream+    -- ^ extension TLV records (all unknown odd)+  } deriving (Eq, Show, Generic)  instance NFData Pong --- | The peer_storage message (type 7).+-- | The @peer_storage@ message (type 7). data PeerStorage = PeerStorage-  { peerStorageBlob :: !BS.ByteString-  } deriving stock (Eq, Show, Generic)+  { peer_storage_blob :: !BS.ByteString+  , peer_storage_tlvs :: !TlvStream+    -- ^ extension TLV records (all unknown odd)+  } deriving (Eq, Show, Generic)  instance NFData PeerStorage --- | The peer_storage_retrieval message (type 9).+-- | The @peer_storage_retrieval@ message (type 9). data PeerStorageRetrieval = PeerStorageRetrieval-  { peerStorageRetrievalBlob :: !BS.ByteString-  } deriving stock (Eq, Show, Generic)+  { peer_storage_retrieval_blob :: !BS.ByteString+  , peer_storage_retrieval_tlvs :: !TlvStream+    -- ^ extension TLV records (all unknown odd)+  } deriving (Eq, Show, Generic)  instance NFData PeerStorageRetrieval --- | All BOLT #1 messages.+-- | A BOLT #1 message. data Message-  = MsgInitVal !Init-  | MsgErrorVal !Error-  | MsgWarningVal !Warning-  | MsgPingVal !Ping-  | MsgPongVal !Pong-  | MsgPeerStorageVal !PeerStorage-  | MsgPeerStorageRetrievalVal !PeerStorageRetrieval-  deriving stock (Eq, Show, Generic)+  = MsgInit !Init+  | MsgError !Error+  | MsgWarning !Warning+  | MsgPing !Ping+  | MsgPong !Pong+  | MsgPeerStorage !PeerStorage+  | MsgPeerStorageRetrieval !PeerStorageRetrieval+  deriving (Eq, Show, Generic)  instance NFData Message --- | Get the message type for a message.-messageType :: Message -> MsgType-messageType (MsgInitVal _)                 = MsgInit-messageType (MsgErrorVal _)                = MsgError-messageType (MsgWarningVal _)              = MsgWarning-messageType (MsgPingVal _)                 = MsgPing-messageType (MsgPongVal _)                 = MsgPong-messageType (MsgPeerStorageVal _)          = MsgPeerStorage-messageType (MsgPeerStorageRetrievalVal _) = MsgPeerStorageRet---- Message envelope ---------------------------------------------------------------- | A complete message envelope with type, payload, and optional extension.-data Envelope = Envelope-  { envType      :: !MsgType-  , envPayload   :: !BS.ByteString-  , envExtension :: !(Maybe TlvStream)-  } deriving stock (Eq, Show, Generic)--instance NFData Envelope+-- | The wire type of a 'Message'.+--+--   >>> message_type (MsgPong (Pong "" empty_tlv_stream))+--   19+message_type :: Message -> Word16+message_type m = case m of+  MsgInit {}                 -> 16+  MsgError {}                -> 17+  MsgWarning {}              -> 1+  MsgPing {}                 -> 18+  MsgPong {}                 -> 19+  MsgPeerStorage {}          -> 7+  MsgPeerStorageRetrieval {} -> 9
lib/Lightning/Protocol/BOLT1/Prim.hs view
@@ -1,527 +1,804 @@-{-# OPTIONS_HADDOCK prune #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}---- |--- Module: Lightning.Protocol.BOLT1.Prim--- Copyright: (c) 2025 Jared Tobin--- License: MIT--- Maintainer: Jared Tobin <jared@ppad.tech>------ Primitive type encoding and decoding for BOLT #1.--module Lightning.Protocol.BOLT1.Prim (-  -- * Chain hash-    ChainHash-  , chainHash-  , unChainHash--  -- * Unsigned integer encoding-  , encodeU16-  , encodeU32-  , encodeU64--  -- * Signed integer encoding-  , encodeS8-  , encodeS16-  , encodeS32-  , encodeS64--  -- * Truncated unsigned integer encoding-  , encodeTu16-  , encodeTu32-  , encodeTu64--  -- * Minimal signed integer encoding-  , encodeMinSigned--  -- * BigSize encoding-  , encodeBigSize--  -- * Unsigned integer decoding-  , decodeU16-  , decodeU32-  , decodeU64--  -- * Signed integer decoding-  , decodeS8-  , decodeS16-  , decodeS32-  , decodeS64--  -- * Truncated unsigned integer decoding-  , decodeTu16-  , decodeTu32-  , decodeTu64--  -- * Minimal signed integer decoding-  , decodeMinSigned--  -- * BigSize decoding-  , decodeBigSize--  -- * Internal helpers-  , encodeLength-  ) where--import Control.DeepSeq (NFData)-import Data.Bits (unsafeShiftL, unsafeShiftR, (.|.))-import qualified Data.ByteString as BS-import qualified Data.ByteString.Builder as BSB-import qualified Data.ByteString.Lazy as BSL-import Data.Int (Int8, Int16, Int32, Int64)-import Data.Word (Word16, Word32, Word64)-import GHC.Generics (Generic)---- Chain hash ---------------------------------------------------------------------- | A chain hash (32-byte hash identifying a blockchain).-newtype ChainHash = ChainHash BS.ByteString-  deriving stock (Eq, Show, Generic)--instance NFData ChainHash---- | Construct a chain hash from a 32-byte bytestring.------ Returns 'Nothing' if the input is not exactly 32 bytes.-chainHash :: BS.ByteString -> Maybe ChainHash-chainHash bs-  | BS.length bs == 32 = Just (ChainHash bs)-  | otherwise = Nothing-{-# INLINE chainHash #-}---- | Extract the raw bytes from a chain hash.-unChainHash :: ChainHash -> BS.ByteString-unChainHash (ChainHash bs) = bs-{-# INLINE unChainHash #-}---- Unsigned integer encoding ------------------------------------------------------- | Encode a 16-bit unsigned integer (big-endian).------ >>> encodeU16 0x0102--- "\SOH\STX"-encodeU16 :: Word16 -> BS.ByteString-encodeU16 = BSL.toStrict . BSB.toLazyByteString . BSB.word16BE-{-# INLINE encodeU16 #-}---- | Encode a 32-bit unsigned integer (big-endian).------ >>> encodeU32 0x01020304--- "\SOH\STX\ETX\EOT"-encodeU32 :: Word32 -> BS.ByteString-encodeU32 = BSL.toStrict . BSB.toLazyByteString . BSB.word32BE-{-# INLINE encodeU32 #-}---- | Encode a 64-bit unsigned integer (big-endian).------ >>> encodeU64 0x0102030405060708--- "\SOH\STX\ETX\EOT\ENQ\ACK\a\b"-encodeU64 :: Word64 -> BS.ByteString-encodeU64 = BSL.toStrict . BSB.toLazyByteString . BSB.word64BE-{-# INLINE encodeU64 #-}---- Signed integer encoding --------------------------------------------------------- | Encode an 8-bit signed integer.------ >>> encodeS8 42--- "*"--- >>> encodeS8 (-42)--- "\214"-encodeS8 :: Int8 -> BS.ByteString-encodeS8 = BS.singleton . fromIntegral-{-# INLINE encodeS8 #-}---- | Encode a 16-bit signed integer (big-endian two's complement).------ >>> encodeS16 0x0102--- "\SOH\STX"--- >>> encodeS16 (-1)--- "\255\255"-encodeS16 :: Int16 -> BS.ByteString-encodeS16 = BSL.toStrict . BSB.toLazyByteString . BSB.int16BE-{-# INLINE encodeS16 #-}---- | Encode a 32-bit signed integer (big-endian two's complement).------ >>> encodeS32 0x01020304--- "\SOH\STX\ETX\EOT"--- >>> encodeS32 (-1)--- "\255\255\255\255"-encodeS32 :: Int32 -> BS.ByteString-encodeS32 = BSL.toStrict . BSB.toLazyByteString . BSB.int32BE-{-# INLINE encodeS32 #-}---- | Encode a 64-bit signed integer (big-endian two's complement).------ >>> encodeS64 0x0102030405060708--- "\SOH\STX\ETX\EOT\ENQ\ACK\a\b"--- >>> encodeS64 (-1)--- "\255\255\255\255\255\255\255\255"-encodeS64 :: Int64 -> BS.ByteString-encodeS64 = BSL.toStrict . BSB.toLazyByteString . BSB.int64BE-{-# INLINE encodeS64 #-}---- Truncated unsigned integer encoding --------------------------------------------- | Encode a truncated 16-bit unsigned integer (0-2 bytes).------ Leading zeros are omitted per BOLT #1. Zero encodes to empty.------ >>> encodeTu16 0--- ""--- >>> encodeTu16 1--- "\SOH"--- >>> encodeTu16 256--- "\SOH\NUL"-encodeTu16 :: Word16 -> BS.ByteString-encodeTu16 0 = BS.empty-encodeTu16 !x-  | x < 0x100 = BS.singleton (fromIntegral x)-  | otherwise = encodeU16 x-{-# INLINE encodeTu16 #-}---- | Encode a truncated 32-bit unsigned integer (0-4 bytes).------ Leading zeros are omitted per BOLT #1. Zero encodes to empty.------ >>> encodeTu32 0--- ""--- >>> encodeTu32 1--- "\SOH"--- >>> encodeTu32 0x010000--- "\SOH\NUL\NUL"-encodeTu32 :: Word32 -> BS.ByteString-encodeTu32 0 = BS.empty-encodeTu32 !x-  | x < 0x100       = BS.singleton (fromIntegral x)-  | x < 0x10000     = encodeU16 (fromIntegral x)-  | x < 0x1000000   = BS.pack [ fromIntegral (x `unsafeShiftR` 16)-                              , fromIntegral (x `unsafeShiftR` 8)-                              , fromIntegral x-                              ]-  | otherwise       = encodeU32 x-{-# INLINE encodeTu32 #-}---- | Encode a truncated 64-bit unsigned integer (0-8 bytes).------ Leading zeros are omitted per BOLT #1. Zero encodes to empty.------ >>> encodeTu64 0--- ""--- >>> encodeTu64 1--- "\SOH"--- >>> encodeTu64 0x0100000000--- "\SOH\NUL\NUL\NUL\NUL"-encodeTu64 :: Word64 -> BS.ByteString-encodeTu64 0 = BS.empty-encodeTu64 !x-  | x < 0x100             = BS.singleton (fromIntegral x)-  | x < 0x10000           = encodeU16 (fromIntegral x)-  | x < 0x1000000         = BS.pack [ fromIntegral (x `unsafeShiftR` 16)-                                    , fromIntegral (x `unsafeShiftR` 8)-                                    , fromIntegral x-                                    ]-  | x < 0x100000000       = encodeU32 (fromIntegral x)-  | x < 0x10000000000     = BS.pack [ fromIntegral (x `unsafeShiftR` 32)-                                    , fromIntegral (x `unsafeShiftR` 24)-                                    , fromIntegral (x `unsafeShiftR` 16)-                                    , fromIntegral (x `unsafeShiftR` 8)-                                    , fromIntegral x-                                    ]-  | x < 0x1000000000000   = BS.pack [ fromIntegral (x `unsafeShiftR` 40)-                                    , fromIntegral (x `unsafeShiftR` 32)-                                    , fromIntegral (x `unsafeShiftR` 24)-                                    , fromIntegral (x `unsafeShiftR` 16)-                                    , fromIntegral (x `unsafeShiftR` 8)-                                    , fromIntegral x-                                    ]-  | x < 0x100000000000000 = BS.pack [ fromIntegral (x `unsafeShiftR` 48)-                                    , fromIntegral (x `unsafeShiftR` 40)-                                    , fromIntegral (x `unsafeShiftR` 32)-                                    , fromIntegral (x `unsafeShiftR` 24)-                                    , fromIntegral (x `unsafeShiftR` 16)-                                    , fromIntegral (x `unsafeShiftR` 8)-                                    , fromIntegral x-                                    ]-  | otherwise             = encodeU64 x-{-# INLINE encodeTu64 #-}---- Minimal signed integer encoding ------------------------------------------------- | Encode a signed 64-bit integer using minimal bytes.------ Uses the smallest number of bytes that can represent the value--- in two's complement. Per BOLT #1 Appendix D test vectors.------ >>> encodeMinSigned 0--- "\NUL"--- >>> encodeMinSigned 127--- "\DEL"--- >>> encodeMinSigned 128--- "\NUL\128"--- >>> encodeMinSigned (-1)--- "\255"--- >>> encodeMinSigned (-128)--- "\128"--- >>> encodeMinSigned (-129)--- "\255\DEL"-encodeMinSigned :: Int64 -> BS.ByteString-encodeMinSigned !x-  | x >= -128 && x <= 127 =-      -- Fits in 1 byte-      BS.singleton (fromIntegral x)-  | x >= -32768 && x <= 32767 =-      -- Fits in 2 bytes-      encodeS16 (fromIntegral x)-  | x >= -2147483648 && x <= 2147483647 =-      -- Fits in 4 bytes-      encodeS32 (fromIntegral x)-  | otherwise =-      -- Need 8 bytes-      encodeS64 x-{-# INLINE encodeMinSigned #-}---- BigSize encoding ---------------------------------------------------------------- | Encode a BigSize value (variable-length unsigned integer).------ >>> encodeBigSize 0--- "\NUL"--- >>> encodeBigSize 252--- "\252"--- >>> encodeBigSize 253--- "\253\NUL\253"--- >>> encodeBigSize 65536--- "\254\NUL\SOH\NUL\NUL"-encodeBigSize :: Word64 -> BS.ByteString-encodeBigSize !x-  | x < 0xfd = BS.singleton (fromIntegral x)-  | x < 0x10000 = BS.cons 0xfd (encodeU16 (fromIntegral x))-  | x < 0x100000000 = BS.cons 0xfe (encodeU32 (fromIntegral x))-  | otherwise = BS.cons 0xff (encodeU64 x)-{-# INLINE encodeBigSize #-}---- Length encoding ----------------------------------------------------------------- | Encode a length as u16, checking bounds.------ Returns Nothing if the length exceeds 65535.-encodeLength :: BS.ByteString -> Maybe BS.ByteString-encodeLength !bs-  | BS.length bs > 65535 = Nothing-  | otherwise = Just (encodeU16 (fromIntegral (BS.length bs)))-{-# INLINE encodeLength #-}---- Unsigned integer decoding ------------------------------------------------------- | Decode a 16-bit unsigned integer (big-endian).-decodeU16 :: BS.ByteString -> Maybe (Word16, BS.ByteString)-decodeU16 !bs-  | BS.length bs < 2 = Nothing-  | otherwise =-      let !b0 = fromIntegral (BS.index bs 0)-          !b1 = fromIntegral (BS.index bs 1)-          !val = (b0 `unsafeShiftL` 8) .|. b1-      in  Just (val, BS.drop 2 bs)-{-# INLINE decodeU16 #-}---- | Decode a 32-bit unsigned integer (big-endian).-decodeU32 :: BS.ByteString -> Maybe (Word32, BS.ByteString)-decodeU32 !bs-  | BS.length bs < 4 = Nothing-  | otherwise =-      let !b0 = fromIntegral (BS.index bs 0)-          !b1 = fromIntegral (BS.index bs 1)-          !b2 = fromIntegral (BS.index bs 2)-          !b3 = fromIntegral (BS.index bs 3)-          !val = (b0 `unsafeShiftL` 24) .|. (b1 `unsafeShiftL` 16)-              .|. (b2 `unsafeShiftL` 8) .|. b3-      in  Just (val, BS.drop 4 bs)-{-# INLINE decodeU32 #-}---- | Decode a 64-bit unsigned integer (big-endian).-decodeU64 :: BS.ByteString -> Maybe (Word64, BS.ByteString)-decodeU64 !bs-  | BS.length bs < 8 = Nothing-  | otherwise =-      let !b0 = fromIntegral (BS.index bs 0)-          !b1 = fromIntegral (BS.index bs 1)-          !b2 = fromIntegral (BS.index bs 2)-          !b3 = fromIntegral (BS.index bs 3)-          !b4 = fromIntegral (BS.index bs 4)-          !b5 = fromIntegral (BS.index bs 5)-          !b6 = fromIntegral (BS.index bs 6)-          !b7 = fromIntegral (BS.index bs 7)-          !val = (b0 `unsafeShiftL` 56) .|. (b1 `unsafeShiftL` 48)-              .|. (b2 `unsafeShiftL` 40) .|. (b3 `unsafeShiftL` 32)-              .|. (b4 `unsafeShiftL` 24) .|. (b5 `unsafeShiftL` 16)-              .|. (b6 `unsafeShiftL` 8) .|. b7-      in  Just (val, BS.drop 8 bs)-{-# INLINE decodeU64 #-}---- Signed integer decoding --------------------------------------------------------- | Decode an 8-bit signed integer.-decodeS8 :: BS.ByteString -> Maybe (Int8, BS.ByteString)-decodeS8 !bs-  | BS.null bs = Nothing-  | otherwise  = Just (fromIntegral (BS.index bs 0), BS.drop 1 bs)-{-# INLINE decodeS8 #-}---- | Decode a 16-bit signed integer (big-endian two's complement).-decodeS16 :: BS.ByteString -> Maybe (Int16, BS.ByteString)-decodeS16 !bs = do-  (w, rest) <- decodeU16 bs-  Just (fromIntegral w, rest)-{-# INLINE decodeS16 #-}---- | Decode a 32-bit signed integer (big-endian two's complement).-decodeS32 :: BS.ByteString -> Maybe (Int32, BS.ByteString)-decodeS32 !bs = do-  (w, rest) <- decodeU32 bs-  Just (fromIntegral w, rest)-{-# INLINE decodeS32 #-}---- | Decode a 64-bit signed integer (big-endian two's complement).-decodeS64 :: BS.ByteString -> Maybe (Int64, BS.ByteString)-decodeS64 !bs = do-  (w, rest) <- decodeU64 bs-  Just (fromIntegral w, rest)-{-# INLINE decodeS64 #-}---- Truncated unsigned integer decoding --------------------------------------------- | Decode a truncated 16-bit unsigned integer (0-2 bytes).------ Returns Nothing if the encoding is non-minimal (has leading zeros).-decodeTu16 :: Int -> BS.ByteString -> Maybe (Word16, BS.ByteString)-decodeTu16 !len !bs-  | len < 0 || len > 2 = Nothing-  | BS.length bs < len = Nothing-  | len == 0 = Just (0, bs)-  | otherwise =-      let !bytes = BS.take len bs-          !rest = BS.drop len bs-      in  if BS.index bytes 0 == 0-            then Nothing  -- non-minimal: leading zero-            else Just (decodeBeWord16 bytes, rest)-  where-    decodeBeWord16 :: BS.ByteString -> Word16-    decodeBeWord16 b = case BS.length b of-      1 -> fromIntegral (BS.index b 0)-      2 -> (fromIntegral (BS.index b 0) `unsafeShiftL` 8)-        .|. fromIntegral (BS.index b 1)-      _ -> 0-{-# INLINE decodeTu16 #-}---- | Decode a truncated 32-bit unsigned integer (0-4 bytes).------ Returns Nothing if the encoding is non-minimal (has leading zeros).-decodeTu32 :: Int -> BS.ByteString -> Maybe (Word32, BS.ByteString)-decodeTu32 !len !bs-  | len < 0 || len > 4 = Nothing-  | BS.length bs < len = Nothing-  | len == 0 = Just (0, bs)-  | otherwise =-      let !bytes = BS.take len bs-          !rest = BS.drop len bs-      in  if BS.index bytes 0 == 0-            then Nothing  -- non-minimal: leading zero-            else Just (decodeBeWord32 len bytes, rest)-  where-    decodeBeWord32 :: Int -> BS.ByteString -> Word32-    decodeBeWord32 n b = go 0 0-      where-        go !acc !i-          | i >= n    = acc-          | otherwise = go ((acc `unsafeShiftL` 8)-                           .|. fromIntegral (BS.index b i)) (i + 1)-{-# INLINE decodeTu32 #-}---- | Decode a truncated 64-bit unsigned integer (0-8 bytes).------ Returns Nothing if the encoding is non-minimal (has leading zeros).-decodeTu64 :: Int -> BS.ByteString -> Maybe (Word64, BS.ByteString)-decodeTu64 !len !bs-  | len < 0 || len > 8 = Nothing-  | BS.length bs < len = Nothing-  | len == 0 = Just (0, bs)-  | otherwise =-      let !bytes = BS.take len bs-          !rest = BS.drop len bs-      in  if BS.index bytes 0 == 0-            then Nothing  -- non-minimal: leading zero-            else Just (decodeBeWord64 len bytes, rest)-  where-    decodeBeWord64 :: Int -> BS.ByteString -> Word64-    decodeBeWord64 n b = go 0 0-      where-        go !acc !i-          | i >= n    = acc-          | otherwise = go ((acc `unsafeShiftL` 8)-                           .|. fromIntegral (BS.index b i)) (i + 1)-{-# INLINE decodeTu64 #-}---- Minimal signed integer decoding ------------------------------------------------- | Decode a minimal signed integer (1, 2, 4, or 8 bytes).------ Validates that the encoding is minimal: the value could not be--- represented in fewer bytes. Per BOLT #1 Appendix D test vectors.-decodeMinSigned :: Int -> BS.ByteString -> Maybe (Int64, BS.ByteString)-decodeMinSigned !len !bs-  | BS.length bs < len = Nothing-  | otherwise = case len of-      1 -> do-        (v, rest) <- decodeS8 bs-        Just (fromIntegral v, rest)-      2 -> do-        (v, rest) <- decodeS16 bs-        -- Must not fit in 1 byte-        if v >= -128 && v <= 127-          then Nothing-          else Just (fromIntegral v, rest)-      4 -> do-        (v, rest) <- decodeS32 bs-        -- Must not fit in 2 bytes-        if v >= -32768 && v <= 32767-          then Nothing-          else Just (fromIntegral v, rest)-      8 -> do-        (v, rest) <- decodeS64 bs-        -- Must not fit in 4 bytes-        if v >= -2147483648 && v <= 2147483647-          then Nothing-          else Just (v, rest)-      _ -> Nothing-{-# INLINE decodeMinSigned #-}---- BigSize decoding ---------------------------------------------------------------- | Decode a BigSize value with minimality check.-decodeBigSize :: BS.ByteString -> Maybe (Word64, BS.ByteString)-decodeBigSize !bs-  | BS.null bs = Nothing-  | otherwise = case BS.index bs 0 of-      0xff -> do-        (val, rest) <- decodeU64 (BS.drop 1 bs)-        -- Must be >= 0x100000000 for minimal encoding-        if val >= 0x100000000-          then Just (val, rest)-          else Nothing-      0xfe -> do-        (val, rest) <- decodeU32 (BS.drop 1 bs)-        -- Must be >= 0x10000 for minimal encoding-        if val >= 0x10000-          then Just (fromIntegral val, rest)-          else Nothing-      0xfd -> do-        (val, rest) <- decodeU16 (BS.drop 1 bs)-        -- Must be >= 0xfd for minimal encoding-        if val >= 0xfd-          then Just (fromIntegral val, rest)-          else Nothing-      b -> Just (fromIntegral b, BS.drop 1 bs)+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveGeneric #-}++-- |+-- Module: Lightning.Protocol.BOLT1.Prim+-- Copyright: (c) 2025 Jared Tobin+-- License: MIT+-- Maintainer: Jared Tobin <jared@ppad.tech>+--+-- Fundamental types and primitive encodings for BOLT #1.++module Lightning.Protocol.BOLT1.Prim (+  -- * Fixed-size byte types+    ChainHash+  , chain_hash+  , un_chain_hash+  , ChannelId+  , channel_id+  , un_channel_id+  , all_channels+  , Signature+  , signature+  , un_signature+  , Point+  , point+  , un_point+  , PaymentHash+  , payment_hash+  , un_payment_hash+  , PaymentPreimage+  , payment_preimage+  , un_payment_preimage+  , PerCommitmentSecret+  , per_commitment_secret+  , un_per_commitment_secret++  -- * Short channel identifiers+  , ShortChannelId(..)+  , short_channel_id+  , scid_block_height+  , scid_tx_index+  , scid_output_index++  -- * Amounts+  , Satoshi+  , satoshi+  , un_satoshi+  , max_satoshi+  , MilliSatoshi+  , milli_satoshi+  , un_milli_satoshi+  , max_milli_satoshi+  , sat_to_msat+  , msat_to_sat+  , add_sat+  , sub_sat+  , add_msat+  , sub_msat++  -- * Unsigned integers+  , encode_u16+  , encode_u32+  , encode_u64+  , decode_u16+  , decode_u32+  , decode_u64++  -- * Signed integers+  , encode_s8+  , encode_s16+  , encode_s32+  , encode_s64+  , decode_s8+  , decode_s16+  , decode_s32+  , decode_s64++  -- * Truncated unsigned integers+  , encode_tu16+  , encode_tu32+  , encode_tu64+  , decode_tu16+  , decode_tu32+  , decode_tu64++  -- * BigSize+  , encode_bigsize+  , decode_bigsize+  , BigSizeError(..)+  , decode_bigsize_detailed++  -- * Length-prefixed bytes+  , encode_u16_prefixed+  , decode_u16_prefixed++  -- * Fixed-size field codecs+  , decode_chain_hash+  , decode_channel_id+  , decode_signature+  , decode_point+  , decode_payment_hash+  , decode_payment_preimage+  , decode_per_commitment_secret+  , encode_short_channel_id+  , decode_short_channel_id+  , encode_satoshi+  , decode_satoshi+  , encode_milli_satoshi+  , decode_milli_satoshi+  ) where++import Control.DeepSeq (NFData(..))+import Data.Bits ((.&.), (.|.), unsafeShiftL, unsafeShiftR, xor)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Internal as BI+import qualified Data.ByteString.Unsafe as BU+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Word (Word8, Word16, Word32, Word64)+import Foreign.Ptr (Ptr)+import Foreign.Storable (pokeByteOff)+import GHC.Generics (Generic)++fi :: (Integral a, Num b) => a -> b+fi = fromIntegral+{-# INLINE fi #-}++-- constant-time equality on equal-length bytestrings+ct_eq :: BS.ByteString -> BS.ByteString -> Bool+ct_eq a b+  | BS.length a /= BS.length b = False+  | otherwise = go 0 0+  where+    !n = BS.length a+    go :: Word8 -> Int -> Bool+    go !acc !i+      | i == n    = acc == 0+      | otherwise =+          go (acc .|. (BU.unsafeIndex a i `xor` BU.unsafeIndex b i))+             (i + 1)++-- take exactly n bytes from the front of the input+take_n :: Int -> BS.ByteString -> Maybe (BS.ByteString, BS.ByteString)+take_n !n !bs+  | BS.length bs < n = Nothing+  | otherwise        = Just (BU.unsafeTake n bs, BU.unsafeDrop n bs)+{-# INLINE take_n #-}++-- fixed-size byte types ------------------------------------------------------++-- | A 32-byte chain hash, identifying the chain a channel or message+--   belongs to.+newtype ChainHash = ChainHash BS.ByteString+  deriving (Eq, Ord, Show, Generic)++instance NFData ChainHash++-- | Construct a 'ChainHash' from exactly 32 bytes.+--+--   >>> fmap (BS.length . un_chain_hash) (chain_hash (BS.replicate 32 0))+--   Just 32+--   >>> chain_hash "too short"+--   Nothing+chain_hash :: BS.ByteString -> Maybe ChainHash+chain_hash bs+  | BS.length bs == 32 = Just (ChainHash bs)+  | otherwise          = Nothing+{-# INLINE chain_hash #-}++-- | The bytes of a 'ChainHash'.+un_chain_hash :: ChainHash -> BS.ByteString+un_chain_hash (ChainHash bs) = bs+{-# INLINE un_chain_hash #-}++-- | A 32-byte channel identifier.+newtype ChannelId = ChannelId BS.ByteString+  deriving (Eq, Ord, Show, Generic)++instance NFData ChannelId++-- | Construct a 'ChannelId' from exactly 32 bytes.+--+--   >>> fmap (BS.length . un_channel_id) (channel_id (BS.replicate 32 1))+--   Just 32+channel_id :: BS.ByteString -> Maybe ChannelId+channel_id bs+  | BS.length bs == 32 = Just (ChannelId bs)+  | otherwise          = Nothing+{-# INLINE channel_id #-}++-- | The bytes of a 'ChannelId'.+un_channel_id :: ChannelId -> BS.ByteString+un_channel_id (ChannelId bs) = bs+{-# INLINE un_channel_id #-}++-- | The all-zero channel identifier, which refers to all channels+--   (e.g. in connection-level @error@ and @warning@ messages).+all_channels :: ChannelId+all_channels = ChannelId (BS.replicate 32 0x00)++-- | A 64-byte compact ECDSA signature.+newtype Signature = Signature BS.ByteString+  deriving (Eq, Ord, Show, Generic)++instance NFData Signature++-- | Construct a 'Signature' from exactly 64 bytes.+--+--   >>> signature (BS.replicate 63 0x00)+--   Nothing+signature :: BS.ByteString -> Maybe Signature+signature bs+  | BS.length bs == 64 = Just (Signature bs)+  | otherwise          = Nothing+{-# INLINE signature #-}++-- | The bytes of a 'Signature'.+un_signature :: Signature -> BS.ByteString+un_signature (Signature bs) = bs+{-# INLINE un_signature #-}++-- | A 33-byte compressed secp256k1 point.+newtype Point = Point BS.ByteString+  deriving (Eq, Ord, Show, Generic)++instance NFData Point++-- | Construct a 'Point' from 33 bytes with a compressed-encoding+--   prefix (0x02 or 0x03). The curve equation is not checked; use+--   ppad-secp256k1 to parse the point if it must be on the curve.+--+--   >>> point (BS.cons 0x04 (BS.replicate 32 0x01))+--   Nothing+point :: BS.ByteString -> Maybe Point+point bs+  | BS.length bs == 33+  , let !h = BU.unsafeIndex bs 0+  , h == 0x02 || h == 0x03 = Just (Point bs)+  | otherwise              = Nothing+{-# INLINE point #-}++-- | The bytes of a 'Point'.+un_point :: Point -> BS.ByteString+un_point (Point bs) = bs+{-# INLINE un_point #-}++-- | A 32-byte SHA256 payment hash.+newtype PaymentHash = PaymentHash BS.ByteString+  deriving (Eq, Ord, Show, Generic)++instance NFData PaymentHash++-- | Construct a 'PaymentHash' from exactly 32 bytes.+payment_hash :: BS.ByteString -> Maybe PaymentHash+payment_hash bs+  | BS.length bs == 32 = Just (PaymentHash bs)+  | otherwise          = Nothing+{-# INLINE payment_hash #-}++-- | The bytes of a 'PaymentHash'.+un_payment_hash :: PaymentHash -> BS.ByteString+un_payment_hash (PaymentHash bs) = bs+{-# INLINE un_payment_hash #-}++-- | A 32-byte payment preimage.+--+--   This is secret material: its 'Show' instance is redacted and its+--   'Eq' instance runs in constant time.+newtype PaymentPreimage = PaymentPreimage BS.ByteString++instance Eq PaymentPreimage where+  PaymentPreimage a == PaymentPreimage b = ct_eq a b++instance Show PaymentPreimage where+  showsPrec d _ = showParen (d > 10) $+    showString "PaymentPreimage <redacted>"++instance NFData PaymentPreimage where+  rnf (PaymentPreimage bs) = rnf bs++-- | Construct a 'PaymentPreimage' from exactly 32 bytes.+payment_preimage :: BS.ByteString -> Maybe PaymentPreimage+payment_preimage bs+  | BS.length bs == 32 = Just (PaymentPreimage bs)+  | otherwise          = Nothing+{-# INLINE payment_preimage #-}++-- | The bytes of a 'PaymentPreimage'.+un_payment_preimage :: PaymentPreimage -> BS.ByteString+un_payment_preimage (PaymentPreimage bs) = bs+{-# INLINE un_payment_preimage #-}++-- | A 32-byte per-commitment secret.+--+--   This is secret material: its 'Show' instance is redacted and its+--   'Eq' instance runs in constant time.+newtype PerCommitmentSecret = PerCommitmentSecret BS.ByteString++instance Eq PerCommitmentSecret where+  PerCommitmentSecret a == PerCommitmentSecret b = ct_eq a b++instance Show PerCommitmentSecret where+  showsPrec d _ = showParen (d > 10) $+    showString "PerCommitmentSecret <redacted>"++instance NFData PerCommitmentSecret where+  rnf (PerCommitmentSecret bs) = rnf bs++-- | Construct a 'PerCommitmentSecret' from exactly 32 bytes.+per_commitment_secret :: BS.ByteString -> Maybe PerCommitmentSecret+per_commitment_secret bs+  | BS.length bs == 32 = Just (PerCommitmentSecret bs)+  | otherwise          = Nothing+{-# INLINE per_commitment_secret #-}++-- | The bytes of a 'PerCommitmentSecret'.+un_per_commitment_secret :: PerCommitmentSecret -> BS.ByteString+un_per_commitment_secret (PerCommitmentSecret bs) = bs+{-# INLINE un_per_commitment_secret #-}++-- short channel identifiers --------------------------------------------------++-- | A short channel identifier: block height (3 bytes), transaction+--   index (3 bytes) and output index (2 bytes), packed big-endian into+--   a 'Word64'. Every 'Word64' is a valid packing.+newtype ShortChannelId = ShortChannelId Word64+  deriving (Eq, Ord, Show, Generic)++instance NFData ShortChannelId++-- | Construct a 'ShortChannelId' from its components. Fails if the+--   block height or transaction index exceed 24 bits.+--+--   >>> short_channel_id 539268 845 1+--   Just (ShortChannelId 592931436542885889)+short_channel_id+  :: Word32 -- ^ block height+  -> Word32 -- ^ transaction index+  -> Word16 -- ^ output index+  -> Maybe ShortChannelId+short_channel_id h t o+  | h > 0xFFFFFF = Nothing+  | t > 0xFFFFFF = Nothing+  | otherwise    = Just $! ShortChannelId $!+          (fi h `unsafeShiftL` 40)+      .|. (fi t `unsafeShiftL` 16)+      .|. fi o+{-# INLINE short_channel_id #-}++-- | The block height of a 'ShortChannelId'.+scid_block_height :: ShortChannelId -> Word32+scid_block_height (ShortChannelId w) = fi (w `unsafeShiftR` 40)+{-# INLINE scid_block_height #-}++-- | The transaction index of a 'ShortChannelId'.+scid_tx_index :: ShortChannelId -> Word32+scid_tx_index (ShortChannelId w) = fi ((w `unsafeShiftR` 16) .&. 0xFFFFFF)+{-# INLINE scid_tx_index #-}++-- | The output index of a 'ShortChannelId'.+scid_output_index :: ShortChannelId -> Word16+scid_output_index (ShortChannelId w) = fi (w .&. 0xFFFF)+{-# INLINE scid_output_index #-}++-- amounts --------------------------------------------------------------------++-- | An amount in satoshis, at most 21 million BTC ('max_satoshi').+newtype Satoshi = Satoshi Word64+  deriving (Eq, Ord, Show, Generic)++instance NFData Satoshi++-- | An amount in millisatoshis, at most 21 million BTC+--   ('max_milli_satoshi').+newtype MilliSatoshi = MilliSatoshi Word64+  deriving (Eq, Ord, Show, Generic)++instance NFData MilliSatoshi++-- | 21 million BTC, in satoshis (0x000775f05a074000).+max_satoshi :: Satoshi+max_satoshi = Satoshi 0x000775f05a074000++-- | 21 million BTC, in millisatoshis (0x1d24b2dfac520000).+max_milli_satoshi :: MilliSatoshi+max_milli_satoshi = MilliSatoshi 0x1d24b2dfac520000++-- | Construct a 'Satoshi' amount, failing above 21 million BTC.+--+--   >>> satoshi 1000+--   Just (Satoshi 1000)+--   >>> satoshi 0xffffffffffffffff+--   Nothing+satoshi :: Word64 -> Maybe Satoshi+satoshi w+  | w <= 0x000775f05a074000 = Just (Satoshi w)+  | otherwise               = Nothing+{-# INLINE satoshi #-}++-- | The number of satoshis.+un_satoshi :: Satoshi -> Word64+un_satoshi (Satoshi w) = w+{-# INLINE un_satoshi #-}++-- | Construct a 'MilliSatoshi' amount, failing above 21 million BTC.+milli_satoshi :: Word64 -> Maybe MilliSatoshi+milli_satoshi w+  | w <= 0x1d24b2dfac520000 = Just (MilliSatoshi w)+  | otherwise               = Nothing+{-# INLINE milli_satoshi #-}++-- | The number of millisatoshis.+un_milli_satoshi :: MilliSatoshi -> Word64+un_milli_satoshi (MilliSatoshi w) = w+{-# INLINE un_milli_satoshi #-}++-- | Convert satoshis to millisatoshis. Total, since the bounds+--   correspond.+--+--   >>> fmap sat_to_msat (satoshi 5)+--   Just (MilliSatoshi 5000)+sat_to_msat :: Satoshi -> MilliSatoshi+sat_to_msat (Satoshi s) = MilliSatoshi (s * 1000)+{-# INLINE sat_to_msat #-}++-- | Convert millisatoshis to satoshis, rounding down.+msat_to_sat :: MilliSatoshi -> Satoshi+msat_to_sat (MilliSatoshi m) = Satoshi (m `quot` 1000)+{-# INLINE msat_to_sat #-}++-- | Add two amounts, failing above 21 million BTC. (Amounts are+--   bounded well below 2^63, so the sum can't wrap.)+add_sat :: Satoshi -> Satoshi -> Maybe Satoshi+add_sat (Satoshi a) (Satoshi b) = satoshi (a + b)+{-# INLINE add_sat #-}++-- | Subtract the second amount from the first, failing if the result+--   would be negative.+sub_sat :: Satoshi -> Satoshi -> Maybe Satoshi+sub_sat (Satoshi a) (Satoshi b)+  | b > a     = Nothing+  | otherwise = Just (Satoshi (a - b))+{-# INLINE sub_sat #-}++-- | Add two amounts, failing above 21 million BTC.+add_msat :: MilliSatoshi -> MilliSatoshi -> Maybe MilliSatoshi+add_msat (MilliSatoshi a) (MilliSatoshi b) = milli_satoshi (a + b)+{-# INLINE add_msat #-}++-- | Subtract the second amount from the first, failing if the result+--   would be negative.+sub_msat :: MilliSatoshi -> MilliSatoshi -> Maybe MilliSatoshi+sub_msat (MilliSatoshi a) (MilliSatoshi b)+  | b > a     = Nothing+  | otherwise = Just (MilliSatoshi (a - b))+{-# INLINE sub_msat #-}++-- unsigned integers ----------------------------------------------------------++poke8 :: Ptr Word8 -> Int -> Word8 -> IO ()+poke8 = pokeByteOff+{-# INLINE poke8 #-}++-- | Encode a 'Word16' as 2 big-endian bytes.+--+--   >>> encode_u16 0x0102+--   "\SOH\STX"+encode_u16 :: Word16 -> BS.ByteString+encode_u16 w = BI.unsafeCreate 2 $ \p -> do+  poke8 p 0 (fi (w `unsafeShiftR` 8))+  poke8 p 1 (fi w)+{-# INLINE encode_u16 #-}++-- | Encode a 'Word32' as 4 big-endian bytes.+encode_u32 :: Word32 -> BS.ByteString+encode_u32 w = BI.unsafeCreate 4 $ \p -> do+  poke8 p 0 (fi (w `unsafeShiftR` 24))+  poke8 p 1 (fi (w `unsafeShiftR` 16))+  poke8 p 2 (fi (w `unsafeShiftR` 8))+  poke8 p 3 (fi w)+{-# INLINE encode_u32 #-}++-- | Encode a 'Word64' as 8 big-endian bytes.+encode_u64 :: Word64 -> BS.ByteString+encode_u64 w = BI.unsafeCreate 8 $ \p -> do+  poke8 p 0 (fi (w `unsafeShiftR` 56))+  poke8 p 1 (fi (w `unsafeShiftR` 48))+  poke8 p 2 (fi (w `unsafeShiftR` 40))+  poke8 p 3 (fi (w `unsafeShiftR` 32))+  poke8 p 4 (fi (w `unsafeShiftR` 24))+  poke8 p 5 (fi (w `unsafeShiftR` 16))+  poke8 p 6 (fi (w `unsafeShiftR` 8))+  poke8 p 7 (fi w)+{-# INLINE encode_u64 #-}++-- big-endian word from the first n bytes (n <= 8, length checked)+be :: Int -> BS.ByteString -> Word64+be n bs = go 0 0+  where+    go !acc !i+      | i == n    = acc+      | otherwise =+          go ((acc `unsafeShiftL` 8) .|. fi (BU.unsafeIndex bs i)) (i + 1)+{-# INLINE be #-}++-- | Decode 2 big-endian bytes, returning the remaining input.+--+--   >>> decode_u16 "\SOH\STXrest"+--   Just (258,"rest")+decode_u16 :: BS.ByteString -> Maybe (Word16, BS.ByteString)+decode_u16 bs+  | BS.length bs < 2 = Nothing+  | otherwise        = Just (fi (be 2 bs), BU.unsafeDrop 2 bs)+{-# INLINE decode_u16 #-}++-- | Decode 4 big-endian bytes, returning the remaining input.+decode_u32 :: BS.ByteString -> Maybe (Word32, BS.ByteString)+decode_u32 bs+  | BS.length bs < 4 = Nothing+  | otherwise        = Just (fi (be 4 bs), BU.unsafeDrop 4 bs)+{-# INLINE decode_u32 #-}++-- | Decode 8 big-endian bytes, returning the remaining input.+decode_u64 :: BS.ByteString -> Maybe (Word64, BS.ByteString)+decode_u64 bs+  | BS.length bs < 8 = Nothing+  | otherwise        = Just (be 8 bs, BU.unsafeDrop 8 bs)+{-# INLINE decode_u64 #-}++-- signed integers ------------------------------------------------------------++-- | Encode an 'Int8' as 1 byte (two's complement).+--+--   >>> encode_s8 (-42)+--   "\214"+encode_s8 :: Int8 -> BS.ByteString+encode_s8 = BS.singleton . fi+{-# INLINE encode_s8 #-}++-- | Encode an 'Int16' as 2 big-endian bytes (two's complement).+encode_s16 :: Int16 -> BS.ByteString+encode_s16 = encode_u16 . fi+{-# INLINE encode_s16 #-}++-- | Encode an 'Int32' as 4 big-endian bytes (two's complement).+encode_s32 :: Int32 -> BS.ByteString+encode_s32 = encode_u32 . fi+{-# INLINE encode_s32 #-}++-- | Encode an 'Int64' as 8 big-endian bytes (two's complement).+encode_s64 :: Int64 -> BS.ByteString+encode_s64 = encode_u64 . fi+{-# INLINE encode_s64 #-}++-- | Decode 1 byte (two's complement), returning the remaining input.+decode_s8 :: BS.ByteString -> Maybe (Int8, BS.ByteString)+decode_s8 bs = case BS.uncons bs of+  Just (h, t) -> Just (fi h, t)+  Nothing     -> Nothing+{-# INLINE decode_s8 #-}++-- | Decode 2 big-endian bytes (two's complement).+decode_s16 :: BS.ByteString -> Maybe (Int16, BS.ByteString)+decode_s16 bs = case decode_u16 bs of+  Just (w, t) -> Just (fi w, t)+  Nothing     -> Nothing+{-# INLINE decode_s16 #-}++-- | Decode 4 big-endian bytes (two's complement).+decode_s32 :: BS.ByteString -> Maybe (Int32, BS.ByteString)+decode_s32 bs = case decode_u32 bs of+  Just (w, t) -> Just (fi w, t)+  Nothing     -> Nothing+{-# INLINE decode_s32 #-}++-- | Decode 8 big-endian bytes (two's complement).+decode_s64 :: BS.ByteString -> Maybe (Int64, BS.ByteString)+decode_s64 bs = case decode_u64 bs of+  Just (w, t) -> Just (fi w, t)+  Nothing     -> Nothing+{-# INLINE decode_s64 #-}++-- truncated unsigned integers ------------------------------------------------++-- minimal big-endian encoding: no leading zero bytes (zero is empty)+encode_tu :: Word64 -> BS.ByteString+encode_tu w = BS.drop (lz 0) (encode_u64 w)+  where+    lz :: Int -> Int+    lz !i+      | i < 8 && (w `unsafeShiftR` (56 - 8 * i)) .&. 0xff == 0 = lz (i + 1)+      | otherwise = i++decode_tu :: Int -> BS.ByteString -> Maybe Word64+decode_tu maxlen bs+  | n > maxlen                       = Nothing+  | n > 0 && BU.unsafeIndex bs 0 == 0 = Nothing+  | otherwise                        = Just (be n bs)+  where+    !n = BS.length bs++-- | Encode a 'Word16' as a truncated integer (no leading zero bytes).+--+--   >>> encode_tu16 0+--   ""+--   >>> encode_tu16 0x0100+--   "\SOH\NUL"+encode_tu16 :: Word16 -> BS.ByteString+encode_tu16 = encode_tu . fi++-- | Encode a 'Word32' as a truncated integer (no leading zero bytes).+encode_tu32 :: Word32 -> BS.ByteString+encode_tu32 = encode_tu . fi++-- | Encode a 'Word64' as a truncated integer (no leading zero bytes).+encode_tu64 :: Word64 -> BS.ByteString+encode_tu64 = encode_tu++-- | Decode a truncated 16-bit integer occupying the whole input. Fails+--   on more than 2 bytes or a non-minimal encoding.+--+--   >>> decode_tu16 "\SOH\NUL"+--   Just 256+--   >>> decode_tu16 "\NUL\SOH"+--   Nothing+decode_tu16 :: BS.ByteString -> Maybe Word16+decode_tu16 = fmap fi . decode_tu 2++-- | Decode a truncated 32-bit integer occupying the whole input. Fails+--   on more than 4 bytes or a non-minimal encoding.+decode_tu32 :: BS.ByteString -> Maybe Word32+decode_tu32 = fmap fi . decode_tu 4++-- | Decode a truncated 64-bit integer occupying the whole input. Fails+--   on more than 8 bytes or a non-minimal encoding.+decode_tu64 :: BS.ByteString -> Maybe Word64+decode_tu64 = decode_tu 8++-- bigsize --------------------------------------------------------------------++-- | Encode a 'Word64' in the minimal BigSize format.+--+--   >>> encode_bigsize 252+--   "\252"+--   >>> encode_bigsize 253+--   "\253\NUL\253"+encode_bigsize :: Word64 -> BS.ByteString+encode_bigsize w+  | w < 0xfd        = BS.singleton (fi w)+  | w <= 0xffff     = BS.cons 0xfd (encode_u16 (fi w))+  | w <= 0xffffffff = BS.cons 0xfe (encode_u32 (fi w))+  | otherwise       = BS.cons 0xff (encode_u64 w)++-- | Why a BigSize failed to decode.+data BigSizeError+  = BigSizeTruncated   -- ^ input ended early+  | BigSizeNonMinimal  -- ^ a shorter encoding exists+  deriving (Eq, Show, Generic)++instance NFData BigSizeError++-- | Decode a BigSize, distinguishing truncation from non-minimal+--   encodings.+decode_bigsize_detailed+  :: BS.ByteString -> Either BigSizeError (Word64, BS.ByteString)+decode_bigsize_detailed bs = case BS.uncons bs of+  Nothing -> Left BigSizeTruncated+  Just (h, t) -> case h of+    0xfd -> wide 2 0xfd t+    0xfe -> wide 4 0x10000 t+    0xff -> wide 8 0x100000000 t+    _    -> Right (fi h, t)+  where+    wide n lo t = case take_n n t of+      Nothing -> Left BigSizeTruncated+      Just (v, r)+        | w < lo    -> Left BigSizeNonMinimal+        | otherwise -> Right (w, r)+        where+          !w = be n v++-- | Decode a minimally-encoded BigSize, returning the remaining input.+--+--   >>> decode_bigsize "\253\NUL\253rest"+--   Just (253,"rest")+--   >>> decode_bigsize "\253\NUL\252"+--   Nothing+decode_bigsize :: BS.ByteString -> Maybe (Word64, BS.ByteString)+decode_bigsize bs = case decode_bigsize_detailed bs of+  Right r -> Just r+  Left _  -> Nothing++-- length-prefixed bytes ------------------------------------------------------++-- | Prefix bytes with their length as a u16. Fails if the input+--   exceeds 65535 bytes.+--+--   >>> encode_u16_prefixed "abc"+--   Just "\NUL\ETXabc"+encode_u16_prefixed :: BS.ByteString -> Maybe BS.ByteString+encode_u16_prefixed bs+  | BS.length bs > 0xffff = Nothing+  | otherwise = Just (encode_u16 (fi (BS.length bs)) <> bs)++-- | Decode u16-length-prefixed bytes, returning the remaining input.+--+--   >>> decode_u16_prefixed "\NUL\ETXabcrest"+--   Just ("abc","rest")+decode_u16_prefixed+  :: BS.ByteString -> Maybe (BS.ByteString, BS.ByteString)+decode_u16_prefixed bs = do+  (n, rest) <- decode_u16 bs+  take_n (fi n) rest++-- fixed-size field codecs ----------------------------------------------------++-- | Decode a 32-byte 'ChainHash', returning the remaining input.+decode_chain_hash :: BS.ByteString -> Maybe (ChainHash, BS.ByteString)+decode_chain_hash bs = do+  (h, r) <- take_n 32 bs+  pure (ChainHash h, r)++-- | Decode a 32-byte 'ChannelId', returning the remaining input.+decode_channel_id :: BS.ByteString -> Maybe (ChannelId, BS.ByteString)+decode_channel_id bs = do+  (h, r) <- take_n 32 bs+  pure (ChannelId h, r)++-- | Decode a 64-byte 'Signature', returning the remaining input.+decode_signature :: BS.ByteString -> Maybe (Signature, BS.ByteString)+decode_signature bs = do+  (h, r) <- take_n 64 bs+  pure (Signature h, r)++-- | Decode a 33-byte 'Point', returning the remaining input. Fails if+--   the prefix byte is not 0x02 or 0x03.+decode_point :: BS.ByteString -> Maybe (Point, BS.ByteString)+decode_point bs = do+  (h, r) <- take_n 33 bs+  p <- point h+  pure (p, r)++-- | Decode a 32-byte 'PaymentHash', returning the remaining input.+decode_payment_hash :: BS.ByteString -> Maybe (PaymentHash, BS.ByteString)+decode_payment_hash bs = do+  (h, r) <- take_n 32 bs+  pure (PaymentHash h, r)++-- | Decode a 32-byte 'PaymentPreimage', returning the remaining input.+decode_payment_preimage+  :: BS.ByteString -> Maybe (PaymentPreimage, BS.ByteString)+decode_payment_preimage bs = do+  (h, r) <- take_n 32 bs+  pure (PaymentPreimage h, r)++-- | Decode a 32-byte 'PerCommitmentSecret', returning the remaining+--   input.+decode_per_commitment_secret+  :: BS.ByteString -> Maybe (PerCommitmentSecret, BS.ByteString)+decode_per_commitment_secret bs = do+  (h, r) <- take_n 32 bs+  pure (PerCommitmentSecret h, r)++-- | Encode a 'ShortChannelId' as 8 big-endian bytes.+encode_short_channel_id :: ShortChannelId -> BS.ByteString+encode_short_channel_id (ShortChannelId w) = encode_u64 w+{-# INLINE encode_short_channel_id #-}++-- | Decode an 8-byte 'ShortChannelId', returning the remaining input.+decode_short_channel_id+  :: BS.ByteString -> Maybe (ShortChannelId, BS.ByteString)+decode_short_channel_id bs = do+  (w, r) <- decode_u64 bs+  pure (ShortChannelId w, r)+{-# INLINE decode_short_channel_id #-}++-- | Encode a 'Satoshi' amount as a u64.+encode_satoshi :: Satoshi -> BS.ByteString+encode_satoshi (Satoshi w) = encode_u64 w+{-# INLINE encode_satoshi #-}++-- | Decode a u64 'Satoshi' amount, returning the remaining input. Fails+--   above 21 million BTC.+decode_satoshi :: BS.ByteString -> Maybe (Satoshi, BS.ByteString)+decode_satoshi bs = do+  (w, r) <- decode_u64 bs+  s <- satoshi w+  pure (s, r)+{-# INLINE decode_satoshi #-}++-- | Encode a 'MilliSatoshi' amount as a u64.+encode_milli_satoshi :: MilliSatoshi -> BS.ByteString+encode_milli_satoshi (MilliSatoshi w) = encode_u64 w+{-# INLINE encode_milli_satoshi #-}++-- | Decode a u64 'MilliSatoshi' amount, returning the remaining input.+--   Fails above 21 million BTC.+decode_milli_satoshi+  :: BS.ByteString -> Maybe (MilliSatoshi, BS.ByteString)+decode_milli_satoshi bs = do+  (w, r) <- decode_u64 bs+  m <- milli_satoshi w+  pure (m, r)+{-# INLINE decode_milli_satoshi #-}
lib/Lightning/Protocol/BOLT1/TLV.hs view
@@ -1,7 +1,6 @@-{-# OPTIONS_HADDOCK prune #-}+{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DerivingStrategies #-}  -- | -- Module: Lightning.Protocol.BOLT1.TLV@@ -9,236 +8,149 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> ----- TLV (Type-Length-Value) format for BOLT #1.+-- The TLV (type-length-value) format of BOLT #1.  module Lightning.Protocol.BOLT1.TLV (-  -- * TLV types     TlvRecord(..)   , TlvStream-  , unTlvStream-  , tlvStream-  , unsafeTlvStream+  , tlv_stream+  , un_tlv_stream+  , empty_tlv_stream+  , lookup_tlv+  , filter_tlv_stream   , TlvError(..)--  -- * TLV encoding-  , encodeTlvRecord-  , encodeTlvStream--  -- * TLV decoding-  , decodeTlvStream-  , decodeTlvStreamWith-  , decodeTlvStreamRaw--  -- * Init TLV types-  , InitTlv(..)-  , parseInitTlvs-  , encodeInitTlvs--  -- * Re-exports-  , ChainHash-  , chainHash-  , unChainHash+  , encode_tlv_stream+  , decode_tlv_stream   ) where  import Control.DeepSeq (NFData)-import Control.Monad (when) import qualified Data.ByteString as BS+import qualified Data.ByteString.Unsafe as BU+import qualified Data.List as L import Data.Word (Word64) import GHC.Generics (Generic) import Lightning.Protocol.BOLT1.Prim --- TLV types -------------------------------------------------------------------- -- | A single TLV record. data TlvRecord = TlvRecord-  { tlvType   :: {-# UNPACK #-} !Word64-  , tlvValue  :: !BS.ByteString-  } deriving stock (Eq, Show, Generic)+  { tlv_type  :: {-# UNPACK #-} !Word64+  , tlv_value :: !BS.ByteString+  } deriving (Eq, Show, Generic)  instance NFData TlvRecord --- | A TLV stream (series of TLV records).-newtype TlvStream = TlvStream { unTlvStream :: [TlvRecord] }-  deriving stock (Eq, Show, Generic)+-- | A TLV stream: records in strictly increasing type order.+newtype TlvStream = TlvStream [TlvRecord]+  deriving (Eq, Show, Generic)  instance NFData TlvStream --- | Smart constructor for 'TlvStream' that validates records are--- strictly increasing by type.+-- | Construct a 'TlvStream' from records in any order. Fails if two+--   records share a type. ----- Returns 'Nothing' if types are not strictly increasing.-tlvStream :: [TlvRecord] -> Maybe TlvStream-tlvStream recs-  | isStrictlyIncreasing (map tlvType recs) = Just (TlvStream recs)-  | otherwise = Nothing+--   >>> let Just s = tlv_stream [TlvRecord 3 "b", TlvRecord 1 "a"]+--   >>> map tlv_type (un_tlv_stream s)+--   [1,3]+--   >>> tlv_stream [TlvRecord 1 "a", TlvRecord 1 "b"]+--   Nothing+tlv_stream :: [TlvRecord] -> Maybe TlvStream+tlv_stream rs+  | increasing sorted = Just (TlvStream sorted)+  | otherwise         = Nothing   where-    isStrictlyIncreasing :: [Word64] -> Bool-    isStrictlyIncreasing [] = True-    isStrictlyIncreasing [_] = True-    isStrictlyIncreasing (x:y:rest) = x < y && isStrictlyIncreasing (y:rest)+    sorted = L.sortOn tlv_type rs --- | Unsafe constructor for 'TlvStream' that skips validation.------ Use only when ordering is already guaranteed (e.g., in decode functions).-unsafeTlvStream :: [TlvRecord] -> TlvStream-unsafeTlvStream = TlvStream+increasing :: [TlvRecord] -> Bool+increasing (a:rest@(b:_)) = tlv_type a < tlv_type b && increasing rest+increasing _ = True --- | TLV decoding errors.-data TlvError-  = TlvNonMinimalEncoding-  | TlvNotStrictlyIncreasing-  | TlvLengthExceedsBounds-  | TlvUnknownEvenType !Word64-  | TlvInvalidKnownType !Word64-  deriving stock (Eq, Show, Generic)+-- | The records of a 'TlvStream', in increasing type order.+un_tlv_stream :: TlvStream -> [TlvRecord]+un_tlv_stream (TlvStream rs) = rs+{-# INLINE un_tlv_stream #-} -instance NFData TlvError+-- | The empty 'TlvStream'.+empty_tlv_stream :: TlvStream+empty_tlv_stream = TlvStream [] --- TLV encoding ----------------------------------------------------------------+-- | The value of the record with the given type, if present.+--+--   >>> lookup_tlv 3 =<< tlv_stream [TlvRecord 1 "a", TlvRecord 3 "b"]+--   Just "b"+lookup_tlv :: Word64 -> TlvStream -> Maybe BS.ByteString+lookup_tlv t (TlvStream rs) = go rs+  where+    go [] = Nothing+    go (TlvRecord u v : more)+      | u == t    = Just v+      | u > t     = Nothing+      | otherwise = go more --- | Encode a TLV record.-encodeTlvRecord :: TlvRecord -> BS.ByteString-encodeTlvRecord (TlvRecord typ val) = mconcat-  [ encodeBigSize typ-  , encodeBigSize (fromIntegral (BS.length val))-  , val-  ]+-- | Keep the records whose types satisfy the predicate.+--+--   >>> let Just s = tlv_stream [TlvRecord 1 "", TlvRecord 2 ""]+--   >>> map tlv_type (un_tlv_stream (filter_tlv_stream odd s))+--   [1]+filter_tlv_stream :: (Word64 -> Bool) -> TlvStream -> TlvStream+filter_tlv_stream p (TlvStream rs) = TlvStream (filter (p . tlv_type) rs) --- | Encode a TLV stream.-encodeTlvStream :: TlvStream -> BS.ByteString-encodeTlvStream (TlvStream recs) = mconcat (map encodeTlvRecord recs)+-- | Why a TLV stream failed to decode.+data TlvError+  = TlvTruncated               -- ^ a type, length or value was cut off+  | TlvNonMinimalBigSize       -- ^ a type or length wasn't minimal+  | TlvNotStrictlyIncreasing   -- ^ types out of order or repeated+  | TlvUnknownEvenType !Word64 -- ^ an even type the context doesn't know+  deriving (Eq, Show, Generic) --- TLV decoding ----------------------------------------------------------------+instance NFData TlvError --- | Decode a TLV stream without any known-type validation.------ This decoder only enforces structural validity:--- - Types must be strictly increasing--- - Lengths must not exceed bounds+-- | Encode a 'TlvStream'. ----- All records are returned regardless of type. Note: this does NOT--- enforce the BOLT #1 unknown-even-type rule. Use 'decodeTlvStreamWith'--- with an appropriate predicate for spec-compliant parsing.-decodeTlvStreamRaw :: BS.ByteString -> Either TlvError TlvStream-decodeTlvStreamRaw = go Nothing []+--   >>> fmap encode_tlv_stream (tlv_stream [TlvRecord 1 "a"])+--   Just "\SOH\SOHa"+encode_tlv_stream :: TlvStream -> BS.ByteString+encode_tlv_stream (TlvStream rs) = mconcat (concatMap enc rs)   where-    go :: Maybe Word64 -> [TlvRecord] -> BS.ByteString-       -> Either TlvError TlvStream-    go !_ !acc !bs-      | BS.null bs = Right (unsafeTlvStream (reverse acc))-    go !mPrevType !acc !bs = do-      (typ, rest1) <- maybe (Left TlvNonMinimalEncoding) Right-                        (decodeBigSize bs)-      -- Strictly increasing check-      case mPrevType of-        Just prevType -> when (typ <= prevType) $-          Left TlvNotStrictlyIncreasing-        Nothing -> pure ()-      (len, rest2) <- maybe (Left TlvNonMinimalEncoding) Right-                        (decodeBigSize rest1)-      -- Length bounds check-      when (fromIntegral len > BS.length rest2) $-        Left TlvLengthExceedsBounds-      let !val = BS.take (fromIntegral len) rest2-          !rest3 = BS.drop (fromIntegral len) rest2-          !rec = TlvRecord typ val-      go (Just typ) (rec : acc) rest3+    enc (TlvRecord t v) =+      [encode_bigsize t, encode_bigsize (fromIntegral (BS.length v)), v] --- | Decode a TLV stream with configurable known-type predicate.+-- | Decode a TLV stream occupying the whole input, given a predicate+--   identifying the types known in the stream's context. ----- Per BOLT #1:--- - Types must be strictly increasing--- - Unknown even types cause failure--- - Unknown odd types are skipped+--   Per BOLT #1, decoding fails on truncation, non-minimal BigSize+--   encodings, types that are not strictly increasing, and unknown+--   even types. Every record is kept, including unknown odd ones, so+--   re-encoding reproduces the input. ----- The predicate determines which types are "known" for the context.-decodeTlvStreamWith-  :: (Word64 -> Bool)  -- ^ Predicate: is this type known?+--   >>> let Right s = decode_tlv_stream (== 1) "\SOH\SOHa\ETX\NUL"+--   >>> map tlv_type (un_tlv_stream s)+--   [1,3]+--   >>> decode_tlv_stream (== 1) "\STX\NUL"+--   Left (TlvUnknownEvenType 2)+decode_tlv_stream+  :: (Word64 -> Bool)  -- ^ is this type known?   -> BS.ByteString   -> Either TlvError TlvStream-decodeTlvStreamWith isKnown = go Nothing []-  where-    go :: Maybe Word64 -> [TlvRecord] -> BS.ByteString-       -> Either TlvError TlvStream-    go !_ !acc !bs-      | BS.null bs = Right (unsafeTlvStream (reverse acc))-    go !mPrevType !acc !bs = do-      (typ, rest1) <- maybe (Left TlvNonMinimalEncoding) Right-                        (decodeBigSize bs)-      -- Strictly increasing check-      case mPrevType of-        Just prevType -> when (typ <= prevType) $-          Left TlvNotStrictlyIncreasing-        Nothing -> pure ()-      (len, rest2) <- maybe (Left TlvNonMinimalEncoding) Right-                        (decodeBigSize rest1)-      -- Length bounds check-      when (fromIntegral len > BS.length rest2) $-        Left TlvLengthExceedsBounds-      let !val = BS.take (fromIntegral len) rest2-          !rest3 = BS.drop (fromIntegral len) rest2-          !rec = TlvRecord typ val-      -- Unknown type handling: even = fail, odd = skip-      if isKnown typ-        then go (Just typ) (rec : acc) rest3-        else if even typ-          then Left (TlvUnknownEvenType typ)-          else go (Just typ) acc rest3  -- skip unknown odd---- | Decode a TLV stream with BOLT #1 init_tlvs validation.------ This uses the default known types for init messages (1 and 3).--- For other contexts, use 'decodeTlvStreamWith' with an appropriate--- predicate.-decodeTlvStream :: BS.ByteString -> Either TlvError TlvStream-decodeTlvStream = decodeTlvStreamWith isInitTlvType-  where-    isInitTlvType :: Word64 -> Bool-    isInitTlvType 1 = True  -- networks-    isInitTlvType 3 = True  -- remote_addr-    isInitTlvType _ = False---- Init TLV types ------------------------------------------------------------------ | TLV records for init message.-data InitTlv-  = InitNetworks ![ChainHash]      -- ^ Type 1: chain hashes (32 bytes each)-  | InitRemoteAddr !BS.ByteString  -- ^ Type 3: remote address-  deriving stock (Eq, Show, Generic)--instance NFData InitTlv---- | Parse init TLVs from a TLV stream.-parseInitTlvs :: TlvStream -> Either TlvError [InitTlv]-parseInitTlvs (TlvStream recs) = traverse parseOne recs+decode_tlv_stream known = go Nothing []   where-    parseOne (TlvRecord 1 val)-      | BS.length val `mod` 32 == 0 =-          Right (InitNetworks (map mkChainHash (chunksOf 32 val)))-      | otherwise = Left (TlvInvalidKnownType 1)-    parseOne (TlvRecord 3 val) = Right (InitRemoteAddr val)-    parseOne (TlvRecord t _) = Left (TlvUnknownEvenType t)--    -- Each chunk is exactly 32 bytes from chunksOf, so chainHash always-    -- succeeds. We use a partial pattern match as the Nothing case is-    -- unreachable given our chunksOf guarantee.-    mkChainHash bs = case chainHash bs of-      Just ch -> ch-      Nothing -> error "parseInitTlvs: impossible - chunk is not 32 bytes"---- | Split bytestring into chunks of given size.-chunksOf :: Int -> BS.ByteString -> [BS.ByteString]-chunksOf !n !bs-  | BS.null bs = []-  | otherwise =-      let (!chunk, !rest) = BS.splitAt n bs-      in  chunk : chunksOf n rest+    go !_ !acc !bs | BS.null bs = Right (TlvStream (reverse acc))+    go !prev !acc !bs = do+      (t, r0) <- bigsize bs+      case prev of+        Just p | t <= p -> Left TlvNotStrictlyIncreasing+        _ -> pure ()+      (l, r1) <- bigsize r0+      if l > fromIntegral (BS.length r1)+        then Left TlvTruncated+        else do+          let !n = fromIntegral l+              !v = BU.unsafeTake n r1+              !r2 = BU.unsafeDrop n r1+          if not (known t) && even t+            then Left (TlvUnknownEvenType t)+            else go (Just t) (TlvRecord t v : acc) r2 --- | Encode init TLVs to a TLV stream.-encodeInitTlvs :: [InitTlv] -> TlvStream-encodeInitTlvs = unsafeTlvStream . map toRecord-  where-    toRecord (InitNetworks chains) =-      TlvRecord 1 (mconcat (map unChainHash chains))-    toRecord (InitRemoteAddr addr) =-      TlvRecord 3 addr+    bigsize b = case decode_bigsize_detailed b of+      Right r                -> Right r+      Left BigSizeTruncated  -> Left TlvTruncated+      Left BigSizeNonMinimal -> Left TlvNonMinimalBigSize
ppad-bolt1.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               ppad-bolt1-version:            0.0.1+version:            0.1.0 synopsis:           Base protocol per BOLT #1 license:            MIT license-file:       LICENSE@@ -8,7 +8,7 @@ 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:   Base protocol, per@@ -25,6 +25,7 @@       -Wall   exposed-modules:       Lightning.Protocol.BOLT1+  other-modules:       Lightning.Protocol.BOLT1.Codec       Lightning.Protocol.BOLT1.Message       Lightning.Protocol.BOLT1.Prim@@ -33,6 +34,7 @@       base >= 4.9 && < 5     , bytestring >= 0.9 && < 0.13     , deepseq >= 1.4 && < 1.6+    , ppad-bolt9 >= 0.1 && < 0.2  test-suite bolt1-tests   type:                exitcode-stdio-1.0@@ -41,13 +43,14 @@   main-is:             Main.hs    ghc-options:-    -rtsopts -Wall -O2+    -rtsopts -Wall    build-depends:       base     , bytestring     , ppad-base16     , ppad-bolt1+    , ppad-bolt9     , tasty     , tasty-hunit     , tasty-quickcheck@@ -66,8 +69,8 @@       base     , bytestring     , criterion-    , deepseq     , ppad-bolt1+    , ppad-bolt9  benchmark bolt1-weigh   type:                exitcode-stdio-1.0@@ -82,6 +85,6 @@   build-depends:       base     , bytestring-    , deepseq     , ppad-bolt1+    , ppad-bolt9     , weigh
test/Main.hs view
@@ -4,696 +4,545 @@  import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16-import Lightning.Protocol.BOLT1-import Test.Tasty-import Test.Tasty.HUnit-import Test.Tasty.QuickCheck--main :: IO ()-main = defaultMain $ testGroup "ppad-bolt1" [-    bigsize_tests-  , primitive_tests-  , signed_tests-  , truncated_tests-  , minsigned_tests-  , tlv_tests-  , message_tests-  , envelope_tests-  , extension_tests-  , bounds_tests-  , property_tests-  ]---- BigSize test vectors from BOLT #1 Appendix A ---------------------------------bigsize_tests :: TestTree-bigsize_tests = testGroup "BigSize (Appendix A)" [-    testCase "zero" $-      encodeBigSize 0 @?= unhex "00"-  , testCase "one byte high (252)" $-      encodeBigSize 252 @?= unhex "fc"-  , testCase "two byte low (253)" $-      encodeBigSize 253 @?= unhex "fd00fd"-  , testCase "two byte high (65535)" $-      encodeBigSize 65535 @?= unhex "fdffff"-  , testCase "four byte low (65536)" $-      encodeBigSize 65536 @?= unhex "fe00010000"-  , testCase "four byte high (4294967295)" $-      encodeBigSize 4294967295 @?= unhex "feffffffff"-  , testCase "eight byte low (4294967296)" $-      encodeBigSize 4294967296 @?= unhex "ff0000000100000000"-  , testCase "eight byte high (max u64)" $-      encodeBigSize 18446744073709551615 @?= unhex "ffffffffffffffffff"-  , testCase "decode zero" $-      decodeBigSize (unhex "00") @?= Just (0, "")-  , testCase "decode 252" $-      decodeBigSize (unhex "fc") @?= Just (252, "")-  , testCase "decode 253" $-      decodeBigSize (unhex "fd00fd") @?= Just (253, "")-  , testCase "decode 65535" $-      decodeBigSize (unhex "fdffff") @?= Just (65535, "")-  , testCase "decode 65536" $-      decodeBigSize (unhex "fe00010000") @?= Just (65536, "")-  , testCase "decode 4294967295" $-      decodeBigSize (unhex "feffffffff") @?= Just (4294967295, "")-  , testCase "decode 4294967296" $-      decodeBigSize (unhex "ff0000000100000000") @?= Just (4294967296, "")-  , testCase "decode max u64" $-      decodeBigSize (unhex "ffffffffffffffffff") @?=-        Just (18446744073709551615, "")-  , testCase "non-minimal 2-byte fails" $-      decodeBigSize (unhex "fd00fc") @?= Nothing-  , testCase "non-minimal 4-byte fails" $-      decodeBigSize (unhex "fe0000ffff") @?= Nothing-  , testCase "non-minimal 8-byte fails" $-      decodeBigSize (unhex "ff00000000ffffffff") @?= Nothing-  ]---- Primitive encode/decode tests -------------------------------------------------primitive_tests :: TestTree-primitive_tests = testGroup "Primitives" [-    testCase "encodeU16 0x0102" $-      encodeU16 0x0102 @?= BS.pack [0x01, 0x02]-  , testCase "decodeU16 0x0102" $-      decodeU16 (BS.pack [0x01, 0x02]) @?= Just (0x0102, "")-  , testCase "encodeU32 0x01020304" $-      encodeU32 0x01020304 @?= BS.pack [0x01, 0x02, 0x03, 0x04]-  , testCase "decodeU32 0x01020304" $-      decodeU32 (BS.pack [0x01, 0x02, 0x03, 0x04]) @?= Just (0x01020304, "")-  , testCase "encodeU64" $-      encodeU64 0x0102030405060708 @?=-        BS.pack [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]-  , testCase "decodeU64" $-      decodeU64 (BS.pack [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]) @?=-        Just (0x0102030405060708, "")-  , testCase "decodeU16 insufficient" $-      decodeU16 (BS.pack [0x01]) @?= Nothing-  , testCase "decodeU32 insufficient" $-      decodeU32 (BS.pack [0x01, 0x02]) @?= Nothing-  , testCase "decodeU64 insufficient" $-      decodeU64 (BS.pack [0x01, 0x02, 0x03, 0x04]) @?= Nothing-  ]---- Signed integer tests -----------------------------------------------------------signed_tests :: TestTree-signed_tests = testGroup "Signed integers" [-    testCase "encodeS8 42" $-      encodeS8 42 @?= BS.pack [0x2a]-  , testCase "encodeS8 -42" $-      encodeS8 (-42) @?= BS.pack [0xd6]-  , testCase "encodeS8 127" $-      encodeS8 127 @?= BS.pack [0x7f]-  , testCase "encodeS8 -128" $-      encodeS8 (-128) @?= BS.pack [0x80]-  , testCase "decodeS8 42" $-      decodeS8 (BS.pack [0x2a]) @?= Just (42, "")-  , testCase "decodeS8 -42" $-      decodeS8 (BS.pack [0xd6]) @?= Just (-42, "")-  , testCase "encodeS16 -1" $-      encodeS16 (-1) @?= BS.pack [0xff, 0xff]-  , testCase "encodeS16 32767" $-      encodeS16 32767 @?= BS.pack [0x7f, 0xff]-  , testCase "encodeS16 -32768" $-      encodeS16 (-32768) @?= BS.pack [0x80, 0x00]-  , testCase "decodeS16 -1" $-      decodeS16 (BS.pack [0xff, 0xff]) @?= Just (-1, "")-  , testCase "encodeS32 -1" $-      encodeS32 (-1) @?= BS.pack [0xff, 0xff, 0xff, 0xff]-  , testCase "encodeS32 2147483647" $-      encodeS32 2147483647 @?= BS.pack [0x7f, 0xff, 0xff, 0xff]-  , testCase "encodeS32 -2147483648" $-      encodeS32 (-2147483648) @?= BS.pack [0x80, 0x00, 0x00, 0x00]-  , testCase "decodeS32 -1" $-      decodeS32 (BS.pack [0xff, 0xff, 0xff, 0xff]) @?= Just (-1, "")-  , testCase "encodeS64 -1" $-      encodeS64 (-1) @?=-        BS.pack [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]-  , testCase "decodeS64 -1" $-      decodeS64 (BS.pack [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]) @?=-        Just (-1, "")-  ]---- Truncated unsigned integer tests -----------------------------------------------truncated_tests :: TestTree-truncated_tests = testGroup "Truncated unsigned integers" [-    testCase "encodeTu16 0" $-      encodeTu16 0 @?= ""-  , testCase "encodeTu16 1" $-      encodeTu16 1 @?= BS.pack [0x01]-  , testCase "encodeTu16 255" $-      encodeTu16 255 @?= BS.pack [0xff]-  , testCase "encodeTu16 256" $-      encodeTu16 256 @?= BS.pack [0x01, 0x00]-  , testCase "encodeTu16 65535" $-      encodeTu16 65535 @?= BS.pack [0xff, 0xff]-  , testCase "decodeTu16 0 bytes" $-      decodeTu16 0 "" @?= Just (0, "")-  , testCase "decodeTu16 1 byte" $-      decodeTu16 1 (BS.pack [0x01]) @?= Just (1, "")-  , testCase "decodeTu16 2 bytes" $-      decodeTu16 2 (BS.pack [0x01, 0x00]) @?= Just (256, "")-  , testCase "decodeTu16 non-minimal fails" $-      decodeTu16 2 (BS.pack [0x00, 0x01]) @?= Nothing-  , testCase "encodeTu32 0" $-      encodeTu32 0 @?= ""-  , testCase "encodeTu32 1" $-      encodeTu32 1 @?= BS.pack [0x01]-  , testCase "encodeTu32 0x010000" $-      encodeTu32 0x010000 @?= BS.pack [0x01, 0x00, 0x00]-  , testCase "encodeTu32 0x01000000" $-      encodeTu32 0x01000000 @?= BS.pack [0x01, 0x00, 0x00, 0x00]-  , testCase "decodeTu32 0 bytes" $-      decodeTu32 0 "" @?= Just (0, "")-  , testCase "decodeTu32 3 bytes" $-      decodeTu32 3 (BS.pack [0x01, 0x00, 0x00]) @?= Just (0x010000, "")-  , testCase "decodeTu32 non-minimal fails" $-      decodeTu32 3 (BS.pack [0x00, 0x01, 0x00]) @?= Nothing-  , testCase "encodeTu64 0" $-      encodeTu64 0 @?= ""-  , testCase "encodeTu64 0x0100000000" $-      encodeTu64 0x0100000000 @?= BS.pack [0x01, 0x00, 0x00, 0x00, 0x00]-  , testCase "decodeTu64 5 bytes" $-      decodeTu64 5 (BS.pack [0x01, 0x00, 0x00, 0x00, 0x00]) @?=-        Just (0x0100000000, "")-  , testCase "decodeTu64 non-minimal fails" $-      decodeTu64 5 (BS.pack [0x00, 0x01, 0x00, 0x00, 0x00]) @?= Nothing-  ]---- Minimal signed integer tests (Appendix D) --------------------------------------minsigned_tests :: TestTree-minsigned_tests = testGroup "Minimal signed (Appendix D)" [-    -- Test vectors from BOLT #1 Appendix D-    testCase "encode 0" $-      encodeMinSigned 0 @?= unhex "00"-  , testCase "encode 42" $-      encodeMinSigned 42 @?= unhex "2a"-  , testCase "encode -42" $-      encodeMinSigned (-42) @?= unhex "d6"-  , testCase "encode 127" $-      encodeMinSigned 127 @?= unhex "7f"-  , testCase "encode -128" $-      encodeMinSigned (-128) @?= unhex "80"-  , testCase "encode 128" $-      encodeMinSigned 128 @?= unhex "0080"-  , testCase "encode -129" $-      encodeMinSigned (-129) @?= unhex "ff7f"-  , testCase "encode 15000" $-      encodeMinSigned 15000 @?= unhex "3a98"-  , testCase "encode -15000" $-      encodeMinSigned (-15000) @?= unhex "c568"-  , testCase "encode 32767" $-      encodeMinSigned 32767 @?= unhex "7fff"-  , testCase "encode -32768" $-      encodeMinSigned (-32768) @?= unhex "8000"-  , testCase "encode 32768" $-      encodeMinSigned 32768 @?= unhex "00008000"-  , testCase "encode -32769" $-      encodeMinSigned (-32769) @?= unhex "ffff7fff"-  , testCase "encode 21000000" $-      encodeMinSigned 21000000 @?= unhex "01406f40"-  , testCase "encode -21000000" $-      encodeMinSigned (-21000000) @?= unhex "febf90c0"-  , testCase "encode 2147483647" $-      encodeMinSigned 2147483647 @?= unhex "7fffffff"-  , testCase "encode -2147483648" $-      encodeMinSigned (-2147483648) @?= unhex "80000000"-  , testCase "encode 2147483648" $-      encodeMinSigned 2147483648 @?= unhex "0000000080000000"-  , testCase "encode -2147483649" $-      encodeMinSigned (-2147483649) @?= unhex "ffffffff7fffffff"-  , testCase "encode 500000000000" $-      encodeMinSigned 500000000000 @?= unhex "000000746a528800"-  , testCase "encode -500000000000" $-      encodeMinSigned (-500000000000) @?= unhex "ffffff8b95ad7800"-  , testCase "encode max int64" $-      encodeMinSigned 9223372036854775807 @?= unhex "7fffffffffffffff"-  , testCase "encode min int64" $-      encodeMinSigned (-9223372036854775808) @?= unhex "8000000000000000"-  -- Decode tests-  , testCase "decode 1-byte 42" $-      decodeMinSigned 1 (unhex "2a") @?= Just (42, "")-  , testCase "decode 1-byte -42" $-      decodeMinSigned 1 (unhex "d6") @?= Just (-42, "")-  , testCase "decode 2-byte 128" $-      decodeMinSigned 2 (unhex "0080") @?= Just (128, "")-  , testCase "decode 2-byte -129" $-      decodeMinSigned 2 (unhex "ff7f") @?= Just (-129, "")-  , testCase "decode 4-byte 32768" $-      decodeMinSigned 4 (unhex "00008000") @?= Just (32768, "")-  , testCase "decode 8-byte 2147483648" $-      decodeMinSigned 8 (unhex "0000000080000000") @?= Just (2147483648, "")-  -- Minimality rejection-  , testCase "decode 2-byte for 1-byte value fails" $-      decodeMinSigned 2 (unhex "0042") @?= Nothing  -- 42 fits in 1 byte-  , testCase "decode 4-byte for 2-byte value fails" $-      decodeMinSigned 4 (unhex "00000080") @?= Nothing  -- 128 fits in 2 bytes-  , testCase "decode 8-byte for 4-byte value fails" $-      decodeMinSigned 8 (unhex "0000000000008000") @?= Nothing  -- 32768 fits in 4-  ]---- TLV tests ---------------------------------------------------------------------tlv_tests :: TestTree-tlv_tests = testGroup "TLV" [-    testGroup "tlvStream smart constructor" [-      testCase "empty list succeeds" $-        tlvStream [] @?= Just (unsafeTlvStream [])-    , testCase "single record succeeds" $-        tlvStream [TlvRecord 1 "a"] @?= Just (unsafeTlvStream [TlvRecord 1 "a"])-    , testCase "strictly increasing succeeds" $-        tlvStream [TlvRecord 1 "a", TlvRecord 3 "b", TlvRecord 5 "c"] @?=-          Just (unsafeTlvStream [TlvRecord 1 "a", TlvRecord 3 "b",-                                 TlvRecord 5 "c"])-    , testCase "non-increasing fails" $-        tlvStream [TlvRecord 5 "a", TlvRecord 3 "b"] @?= Nothing-    , testCase "duplicate types fails" $-        tlvStream [TlvRecord 1 "a", TlvRecord 1 "b"] @?= Nothing-    , testCase "equal adjacent types fails" $-        tlvStream [TlvRecord 1 "a", TlvRecord 2 "b", TlvRecord 2 "c"] @?=-          Nothing-    ]-  , testCase "empty stream" $-      decodeTlvStream "" @?= Right (unsafeTlvStream [])-  , testCase "single record type 1" $ do-      let bs = mconcat [-              encodeBigSize 1      -- type-            , encodeBigSize 32     -- length-            , BS.replicate 32 0x00 -- value (chain hash)-            ]-      case decodeTlvStream bs of-        Right stream -> case unTlvStream stream of-          [r] -> do-            tlvType r @?= 1-            BS.length (tlvValue r) @?= 32-          _ -> assertFailure "expected single record"-        Left e -> assertFailure $ "unexpected error: " ++ show e-  , testCase "strictly increasing types" $ do-      let bs = mconcat [-              encodeBigSize 1, encodeBigSize 0-            , encodeBigSize 3, encodeBigSize 4, "test"-            ]-      case decodeTlvStream bs of-        Right stream -> length (unTlvStream stream) @?= 2-        Left e -> assertFailure $ "unexpected error: " ++ show e-  , testCase "non-increasing types fails" $ do-      let bs = mconcat [-              encodeBigSize 3, encodeBigSize 0-            , encodeBigSize 1, encodeBigSize 0-            ]-      case decodeTlvStream bs of-        Left TlvNotStrictlyIncreasing -> pure ()-        other -> assertFailure $ "expected TlvNotStrictlyIncreasing: " ++-                                 show other-  , testCase "duplicate types fails" $ do-      let bs = mconcat [-              encodeBigSize 1, encodeBigSize 0-            , encodeBigSize 1, encodeBigSize 0-            ]-      case decodeTlvStream bs of-        Left TlvNotStrictlyIncreasing -> pure ()-        other -> assertFailure $ "expected TlvNotStrictlyIncreasing: " ++-                                 show other-  , testCase "unknown even type fails" $ do-      let bs = mconcat [encodeBigSize 2, encodeBigSize 0]-      case decodeTlvStream bs of-        Left (TlvUnknownEvenType 2) -> pure ()-        other -> assertFailure $ "expected TlvUnknownEvenType: " ++ show other-  , testCase "unknown odd type skipped" $ do-      let bs = mconcat [-              encodeBigSize 5, encodeBigSize 2, "hi"-            , encodeBigSize 7, encodeBigSize 0-            ]-      case decodeTlvStream bs of-        Right stream | null (unTlvStream stream) -> pure ()  -- both skipped-        other -> assertFailure $ "expected empty stream: " ++ show other-  , testCase "length exceeds bounds fails" $ do-      let bs = mconcat [encodeBigSize 1, encodeBigSize 100, "short"]-      case decodeTlvStream bs of-        Left TlvLengthExceedsBounds -> pure ()-        other -> assertFailure $ "expected TlvLengthExceedsBounds: " ++-                                 show other-  , testCase "decodeTlvStreamWith custom predicate" $ do-      -- Use a predicate that only knows type 5-      let isKnown t = t == 5-          bs = mconcat [-              encodeBigSize 5, encodeBigSize 2, "hi"-            ]-      case decodeTlvStreamWith isKnown bs of-        Right stream -> case unTlvStream stream of-          [r] -> tlvType r @?= 5-          _ -> assertFailure "expected single record"-        Left e -> assertFailure $ "unexpected error: " ++ show e-  , testCase "decodeTlvStreamRaw returns all records" $ do-      let bs = mconcat [-              encodeBigSize 2, encodeBigSize 1, "a"  -- even type-            , encodeBigSize 5, encodeBigSize 1, "b"  -- odd type-            ]-      case decodeTlvStreamRaw bs of-        Right stream -> length (unTlvStream stream) @?= 2-        Left e -> assertFailure $ "unexpected error: " ++ show e-  ]---- Message encode/decode tests ---------------------------------------------------message_tests :: TestTree-message_tests = testGroup "Messages" [-    testGroup "Init" [-      testCase "encode/decode minimal init" $ do-        let msg = Init "" "" []-        case encodeMessage (MsgInitVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgInit encoded of-            Right (MsgInitVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    , testCase "encode/decode init with features" $ do-        let msg = Init (BS.pack [0x01]) (BS.pack [0x02, 0x0a]) []-        case encodeMessage (MsgInitVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgInit encoded of-            Right (MsgInitVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    , testCase "encode/decode init with networks TLV" $ do-        let ch = unsafeChainHash (BS.replicate 32 0xab)-            msg = Init "" "" [InitNetworks [ch]]-        case encodeMessage (MsgInitVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgInit encoded of-            Right (MsgInitVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    ]-  , testGroup "Error" [-      testCase "encode/decode error" $ do-        let cid = unsafeChannelId (BS.replicate 32 0xff)-            msg = Error cid "something went wrong"-        case encodeMessage (MsgErrorVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgError encoded of-            Right (MsgErrorVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    , testCase "error insufficient channel_id" $ do-        case decodeMessage MsgError (BS.replicate 31 0x00) of-          Left DecodeInsufficientBytes -> pure ()-          other -> assertFailure $ "expected insufficient: " ++ show other-    ]-  , testGroup "Warning" [-      testCase "encode/decode warning" $ do-        let cid = unsafeChannelId (BS.replicate 32 0x00)-            msg = Warning cid "be careful"-        case encodeMessage (MsgWarningVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgWarning encoded of-            Right (MsgWarningVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    ]-  , testGroup "Ping" [-      testCase "encode/decode ping" $ do-        let msg = Ping 100 (BS.replicate 10 0x00)-        case encodeMessage (MsgPingVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgPing encoded of-            Right (MsgPingVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    , testCase "ping with zero ignored" $ do-        let msg = Ping 50 ""-        case encodeMessage (MsgPingVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgPing encoded of-            Right (MsgPingVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    ]-  , testGroup "Pong" [-      testCase "encode/decode pong" $ do-        let msg = Pong (BS.replicate 100 0x00)-        case encodeMessage (MsgPongVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgPong encoded of-            Right (MsgPongVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    ]-  , testGroup "PeerStorage" [-      testCase "encode/decode peer_storage" $ do-        let msg = PeerStorage "encrypted blob data"-        case encodeMessage (MsgPeerStorageVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgPeerStorage encoded of-            Right (MsgPeerStorageVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    ]-  , testGroup "PeerStorageRetrieval" [-      testCase "encode/decode peer_storage_retrieval" $ do-        let msg = PeerStorageRetrieval "retrieved blob"-        case encodeMessage (MsgPeerStorageRetrievalVal msg) of-          Left e -> assertFailure $ "encode failed: " ++ show e-          Right encoded -> case decodeMessage MsgPeerStorageRet encoded of-            Right (MsgPeerStorageRetrievalVal decoded, _) -> decoded @?= msg-            other -> assertFailure $ "unexpected: " ++ show other-    ]-  , testGroup "Unknown types" [-      testCase "decodeMessage unknown even type" $ do-        case decodeMessage (MsgUnknown 100) "payload" of-          Left (DecodeUnknownEvenType 100) -> pure ()-          other -> assertFailure $ "expected unknown even: " ++ show other-    , testCase "decodeMessage unknown odd type" $ do-        case decodeMessage (MsgUnknown 101) "payload" of-          Left (DecodeUnknownOddType 101) -> pure ()-          other -> assertFailure $ "expected unknown odd: " ++ show other-    ]-  ]---- Envelope tests ----------------------------------------------------------------envelope_tests :: TestTree-envelope_tests = testGroup "Envelope" [-    testCase "encode/decode init envelope" $ do-      let msg = MsgInitVal (Init "" "" [])-      case encodeEnvelope msg Nothing of-        Left e -> assertFailure $ "encode failed: " ++ show e-        Right encoded -> case decodeEnvelope encoded of-          Right (Just decoded, _) -> decoded @?= msg-          other -> assertFailure $ "unexpected: " ++ show other-  , testCase "encode/decode ping envelope" $ do-      let msg = MsgPingVal (Ping 10 "")-      case encodeEnvelope msg Nothing of-        Left e -> assertFailure $ "encode failed: " ++ show e-        Right encoded -> case decodeEnvelope encoded of-          Right (Just decoded, _) -> decoded @?= msg-          other -> assertFailure $ "unexpected: " ++ show other-  , testCase "unknown even type fails" $ do-      let bs = encodeU16 100 <> "payload"  -- 100 is even, unknown-      case decodeEnvelope bs of-        Left (DecodeUnknownEvenType 100) -> pure ()-        other -> assertFailure $ "expected unknown even: " ++ show other-  , testCase "unknown odd type ignored" $ do-      let bs = encodeU16 101 <> "payload"  -- 101 is odd, unknown-      case decodeEnvelope bs of-        Right (Nothing, Nothing) -> pure ()  -- ignored-        other -> assertFailure $ "expected (Nothing, Nothing): " ++ show other-  , testCase "insufficient bytes for type" $ do-      case decodeEnvelope (BS.pack [0x00]) of-        Left DecodeInsufficientBytes -> pure ()-        other -> assertFailure $ "expected insufficient: " ++ show other-  , testCase "message type codes" $ do-      msgTypeWord MsgInit @?= 16-      msgTypeWord MsgError @?= 17-      msgTypeWord MsgPing @?= 18-      msgTypeWord MsgPong @?= 19-      msgTypeWord MsgWarning @?= 1-      msgTypeWord MsgPeerStorage @?= 7-      msgTypeWord MsgPeerStorageRet @?= 9-  ]---- Extension TLV tests -----------------------------------------------------------extension_tests :: TestTree-extension_tests = testGroup "Extension TLV" [-    testCase "encode envelope with extension (odd type)" $ do-      let msg = MsgPingVal (Ping 10 "")-          ext = unsafeTlvStream [TlvRecord 101 "extension data"]  -- odd type-      case encodeEnvelope msg (Just ext) of-        Left e -> assertFailure $ "encode failed: " ++ show e-        Right encoded -> do-          -- Should contain message + extension-          assertBool "encoded should be longer" (BS.length encoded > 6)-  , testCase "decode envelope with odd extension - skipped per BOLT#1" $ do-      -- Per BOLT #1: unknown odd types are ignored (skipped)-      let msg = MsgPingVal (Ping 10 "")-          ext = unsafeTlvStream [TlvRecord 101 "ext"]  -- odd type-      case encodeEnvelope msg (Just ext) of-        Left e -> assertFailure $ "encode failed: " ++ show e-        Right encoded -> case decodeEnvelope encoded of-          Right (Just decoded, Just stream)-            | null (unTlvStream stream) -> do-                -- Extension is empty because unknown odd types are skipped-                decoded @?= msg-          other -> assertFailure $ "unexpected: " ++ show other-  , testCase "decode envelope with unknown even extension fails" $ do-      -- Per BOLT #1: unknown even types must cause failure-      let pingPayload = mconcat [encodeU16 10, encodeU16 0]  -- numPong=10, len=0-          extTlv = mconcat [encodeBigSize 100, encodeBigSize 3, "abc"]  -- even!-          envelope = encodeU16 18 <> pingPayload <> extTlv  -- type 18 = ping-      case decodeEnvelope envelope of-        Left (DecodeInvalidExtension (TlvUnknownEvenType 100)) -> pure ()-        other -> assertFailure $ "expected unknown even error: " ++ show other-  , testCase "decode envelope with invalid extension fails" $ do-      -- Ping + invalid TLV (non-strictly-increasing)-      let pingPayload = mconcat [encodeU16 10, encodeU16 0]-          badTlv = mconcat [-              encodeBigSize 101, encodeBigSize 1, "a"  -- odd types for this test-            , encodeBigSize 51, encodeBigSize 1, "b"   -- 51 < 101, invalid-            ]-          envelope = encodeU16 18 <> pingPayload <> badTlv-      case decodeEnvelope envelope of-        Left (DecodeInvalidExtension TlvNotStrictlyIncreasing) -> pure ()-        other -> assertFailure $ "expected invalid extension: " ++ show other-  , testCase "unknown even in extension fails even with odd types present" $ do-      -- Mixed odd and even - should fail on the even type-      let pingPayload = mconcat [encodeU16 10, encodeU16 0]-          extTlv = mconcat [-              encodeBigSize 101, encodeBigSize 1, "a"  -- odd, would be skipped-            , encodeBigSize 200, encodeBigSize 1, "b"  -- even, must fail-            ]-          envelope = encodeU16 18 <> pingPayload <> extTlv-      case decodeEnvelope envelope of-        Left (DecodeInvalidExtension (TlvUnknownEvenType 200)) -> pure ()-        other -> assertFailure $ "expected unknown even error: " ++ show other-  ]---- Bounds checking tests ---------------------------------------------------------bounds_tests :: TestTree-bounds_tests = testGroup "Bounds checking" [-    testCase "encode ping with oversized ignored fails" $ do-      let msg = Ping 10 (BS.replicate 70000 0x00)  -- > 65535-      case encodeMessage (MsgPingVal msg) of-        Left EncodeLengthOverflow -> pure ()-        other -> assertFailure $ "expected overflow: " ++ show other-  , testCase "encode pong with oversized ignored fails" $ do-      let msg = Pong (BS.replicate 70000 0x00)-      case encodeMessage (MsgPongVal msg) of-        Left EncodeLengthOverflow -> pure ()-        other -> assertFailure $ "expected overflow: " ++ show other-  , testCase "encode error with oversized data fails" $ do-      let cid = unsafeChannelId (BS.replicate 32 0x00)-          msg = Error cid (BS.replicate 70000 0x00)-      case encodeMessage (MsgErrorVal msg) of-        Left EncodeLengthOverflow -> pure ()-        other -> assertFailure $ "expected overflow: " ++ show other-  , testCase "encode init with oversized features fails" $ do-      let msg = Init "" (BS.replicate 70000 0x00) []-      case encodeMessage (MsgInitVal msg) of-        Left EncodeLengthOverflow -> pure ()-        other -> assertFailure $ "expected overflow: " ++ show other-  , testCase "encode peer_storage with oversized blob fails" $ do-      let msg = PeerStorage (BS.replicate 70000 0x00)-      case encodeMessage (MsgPeerStorageVal msg) of-        Left EncodeLengthOverflow -> pure ()-        other -> assertFailure $ "expected overflow: " ++ show other-  , testCase "encode envelope exceeding 65535 bytes fails" $ do-      -- Create a message that fits in encodeMessage but combined with-      -- extension exceeds 65535 bytes total-      let msg = MsgPongVal (Pong (BS.replicate 60000 0x00))-          ext = unsafeTlvStream [TlvRecord 101 (BS.replicate 10000 0x00)]-      case encodeEnvelope msg (Just ext) of-        Left EncodeMessageTooLarge -> pure ()-        other -> assertFailure $ "expected message too large: " ++ show other-  ]---- Property tests ----------------------------------------------------------------property_tests :: TestTree-property_tests = testGroup "Properties" [-    testProperty "BigSize roundtrip" $ \(NonNegative n) ->-      case decodeBigSize (encodeBigSize n) of-        Just (m, rest) -> m == n && BS.null rest-        Nothing -> False-  , testProperty "U16 roundtrip" $ \w ->-      decodeU16 (encodeU16 w) == Just (w, "")-  , testProperty "U32 roundtrip" $ \w ->-      decodeU32 (encodeU32 w) == Just (w, "")-  , testProperty "U64 roundtrip" $ \w ->-      decodeU64 (encodeU64 w) == Just (w, "")-  , testProperty "Ping roundtrip" $ \(NonNegative num) bs ->-      let ignored = BS.pack (take 1000 bs)  -- limit size-          msg = Ping (fromIntegral (num `mod` 65536 :: Integer)) ignored-      in case encodeMessage (MsgPingVal msg) of-           Left _ -> False-           Right encoded -> case decodeMessage MsgPing encoded of-             Right (MsgPingVal decoded, rest) ->-               decoded == msg && BS.null rest-             _ -> False-  , testProperty "Pong roundtrip" $ \bs ->-      let ignored = BS.pack (take 1000 bs)-          msg = Pong ignored-      in case encodeMessage (MsgPongVal msg) of-           Left _ -> False-           Right encoded -> case decodeMessage MsgPong encoded of-             Right (MsgPongVal decoded, rest) ->-               decoded == msg && BS.null rest-             _ -> False-  , testProperty "PeerStorage roundtrip" $ \bs ->-      let blob = BS.pack (take 1000 bs)-          msg = PeerStorage blob-      in case encodeMessage (MsgPeerStorageVal msg) of-           Left _ -> False-           Right encoded -> case decodeMessage MsgPeerStorage encoded of-             Right (MsgPeerStorageVal decoded, rest) ->-               decoded == msg && BS.null rest-             _ -> False-  , testProperty "Error roundtrip" $ \bs ->-      let cid = unsafeChannelId (BS.replicate 32 0x00)-          dat = BS.pack (take 1000 bs)-          msg = Error cid dat-      in case encodeMessage (MsgErrorVal msg) of-           Left _ -> False-           Right encoded -> case decodeMessage MsgError encoded of-             Right (MsgErrorVal decoded, rest) ->-               decoded == msg && BS.null rest-             _ -> False-  , testProperty "Envelope with odd extension (skipped per BOLT#1)" $ \bs ->-      -- Unknown odd types in extensions are skipped per BOLT #1-      let msg = MsgPingVal (Ping 42 "")-          extData = BS.pack (take 100 bs)-          ext = unsafeTlvStream [TlvRecord 101 extData]  -- odd type, skipped-      in case encodeEnvelope msg (Just ext) of-           Left _ -> False-           Right encoded -> case decodeEnvelope encoded of-             -- Extension should be empty (odd types skipped)-             Right (Just decoded, Just stream) ->-               null (unTlvStream stream) && decoded == msg-             _ -> False-  ]---- Helpers ------------------------------------------------------------------------- | Construct a 'ChannelId' from a known-valid 32-byte 'BS.ByteString'.------ Uses 'error' for invalid input since all channel IDs in tests are--- known-valid compile-time constants.-unsafeChannelId :: BS.ByteString -> ChannelId-unsafeChannelId bs = case channelId bs of-  Just cid -> cid-  Nothing  -> error $ "unsafeChannelId: invalid length: " ++ show (BS.length bs)---- | Decode hex string (test-only helper).------ Uses 'error' for invalid hex since all hex literals in tests are--- known-valid compile-time constants. This is acceptable in test code--- where the failure would indicate a bug in the test itself.-unhex :: BS.ByteString -> BS.ByteString-unhex bs = case B16.decode bs of-  Just r  -> r-  Nothing -> error $ "unhex: invalid hex literal: " ++ show bs---- | Construct a ChainHash from a bytestring (test-only helper).------ Uses 'error' for invalid input since all chain hashes in tests are--- known-valid 32-byte constants. This is acceptable in test code where--- the failure would indicate a bug in the test itself.-unsafeChainHash :: BS.ByteString -> ChainHash-unsafeChainHash bs = case chainHash bs of-  Just c  -> c-  Nothing -> error $ "unsafeChainHash: not 32 bytes: " ++ show (BS.length bs)+import qualified Data.ByteString.Char8 as B8+import Data.Int (Int64)+import qualified Data.List as L+import Data.Maybe (fromMaybe, isJust, isNothing)+import Data.Word (Word16, Word32, Word64)+import Lightning.Protocol.BOLT1+import qualified Lightning.Protocol.BOLT9 as BOLT9+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++main :: IO ()+main = defaultMain $ testGroup "ppad-bolt1" [+    appendix_a+  , appendix_b+  , appendix_c+  , appendix_d+  , primitives+  , tlv+  , messages+  , properties+  ]++-- helpers --------------------------------------------------------------------++-- run an assertion on decoded hex (spaces and a 0x prefix are allowed),+-- failing the test if the literal isn't valid hex+with_hex :: String -> (BS.ByteString -> Assertion) -> Assertion+with_hex s k = case B16.decode (B8.pack (strip s)) of+  Nothing -> assertFailure ("invalid hex literal: " ++ s)+  Just bs -> k bs+  where+    strip = filter (/= ' ') . drop0x+    drop0x ('0':'x':r) = r+    drop0x r = r++is_left :: Either a b -> Bool+is_left = either (const True) (const False)++empty_fv :: BOLT9.FeatureVector+empty_fv = BOLT9.parse ""++-- Appendix A: BigSize --------------------------------------------------------++appendix_a :: TestTree+appendix_a = testGroup "Appendix A (BigSize)" [+    testGroup "decoding" (map dec ok_vectors)+  , testGroup "encoding" (map enc ok_vectors)+  , testGroup "decoding failures" (map bad bad_vectors)+  ]+  where+    dec (v, h) = testCase ("decode " ++ h) $ with_hex h $ \bs ->+      decode_bigsize bs @?= Just (v, "")+    enc (v, h) = testCase ("encode " ++ show v) $ with_hex h $ \bs ->+      encode_bigsize v @?= bs+    bad h = testCase ("reject " ++ show h) $ with_hex h $ \bs ->+      decode_bigsize bs @?= Nothing++    ok_vectors :: [(Word64, String)]+    ok_vectors = [+        (0, "00"), (252, "fc"), (253, "fd00fd"), (65535, "fdffff")+      , (65536, "fe00010000"), (4294967295, "feffffffff")+      , (4294967296, "ff0000000100000000")+      , (18446744073709551615, "ffffffffffffffffff")+      ]++    -- non-canonical encodings, short reads and no reads+    bad_vectors = [+        "fd00fc", "fe0000ffff", "ff00000000ffffffff"+      , "fd00", "feffff", "ffffffffff"+      , "", "fd", "fe", "ff"+      ]++-- Appendix B: TLV ------------------------------------------------------------++-- the n1 and n2 test namespaces, decoded with bolt1's primitives++data N1 = N1Tlv1 !Word64+        | N1Tlv2 !ShortChannelId+        | N1Tlv3 !Point !Word64 !Word64+        | N1Tlv4 !Word16+  deriving (Eq, Show)++decode_n1 :: BS.ByteString -> Either String [N1]+decode_n1 bs = do+  s <- either (Left . show) Right (decode_tlv_stream (`elem` n1_types) bs)+  sequence [ one r | r <- un_tlv_stream s, tlv_type r `elem` n1_types ]+  where+    n1_types = [1, 2, 3, 254]+    one (TlvRecord 1 v) = maybe (Left "tlv1") (Right . N1Tlv1)+      (decode_tu64 v)+    one (TlvRecord 2 v) = case decode_short_channel_id v of+      Just (c, "") -> Right (N1Tlv2 c)+      _ -> Left "tlv2"+    one (TlvRecord 3 v) = maybe (Left "tlv3") Right $ do+      (p, r0) <- decode_point v+      (a, r1) <- decode_u64 r0+      (b, r2) <- decode_u64 r1+      if BS.null r2 then pure (N1Tlv3 p a b) else Nothing+    one (TlvRecord 254 v) = case decode_u16 v of+      Just (c, "") -> Right (N1Tlv4 c)+      _ -> Left "tlv4"+    one _ = Left "unreachable"++decode_n2 :: BS.ByteString -> Either String [(Word64, Word64)]+decode_n2 bs = do+  s <- either (Left . show) Right (decode_tlv_stream (`elem` [0, 11]) bs)+  sequence [ one r | r <- un_tlv_stream s, tlv_type r `elem` [0, 11] ]+  where+    one (TlvRecord 0 v) = maybe (Left "tlv1") (Right . (,) 0) (decode_tu64 v)+    one (TlvRecord 11 v) =+      maybe (Left "tlv2") (Right . (,) 11 . fromIntegral) (decode_tu32 v)+    one _ = Left "unreachable"++appendix_b :: TestTree+appendix_b = testGroup "Appendix B (TLV)" [+    testGroup "failures in any namespace" [+      testCase h $ with_hex h $ \bs -> do+        assertBool "n1" (is_left (decode_n1 bs))+        assertBool "n2" (is_left (decode_n2 bs))+    | h <- any_fail ]+  , testGroup "n1 failures" [+      testCase h $ with_hex h $ \bs -> assertBool "n1" (is_left (decode_n1 bs))+    | h <- n1_fail ]+  , testGroup "n2 failures" [+      testCase h $ with_hex h $ \bs -> assertBool "n2" (is_left (decode_n2 bs))+    | h <- n2_fail ]+  , testGroup "ignored successes" [+      testCase h $ with_hex h $ \bs -> do+        decode_n1 bs @?= Right []+        decode_n2 bs @?= Right []+    | h <- any_ok ]+  , testGroup "n1 successes" [+      testCase h $ with_hex h $ \bs -> decode_n1 bs @?= Right [v]+    | (h, v) <- n1_ok ]+  , testCase "n1 tlv3" $ with_hex n1_tlv3 $ \bs ->+      with_hex node_id $ \pk -> case point pk of+        Nothing -> assertFailure "bad node_id"+        Just p  -> decode_n1 bs @?= Right [N1Tlv3 p 1 2]+  , testCase "unknown even types fail when no types are known" $+      mapM_ (\h -> with_hex h $ \bs ->+        assertBool h (is_left (decode_tlv_stream (const False) bs)))+        ["12 00", "fd0102 00", "fe01000002 00", "ff0100000000000002 00"]+  , testCase "appending a higher-numbered valid stream succeeds" $+      with_hex "01 01 01" $ \a -> with_hex "fd00fe 02 0226" $ \b ->+        decode_n1 (a <> b) @?= Right [N1Tlv1 1, N1Tlv4 550]+  , testCase "appending an invalid stream fails" $+      with_hex "01 01 01" $ \a -> mapM_ (\h -> with_hex h $ \b ->+        assertBool h (is_left (decode_n1 (a <> b)))) any_fail+  ]+  where+    any_fail = [+        "0xfd", "0xfd01", "0xfd0001 00", "0xfd0101", "0x0f fd", "0x0f fd26"+      , "0x0f fd2602", "0x0f fd0001 00", "0x0f fd0201 " ++ replicate 1024 '0'+      , "0x12 00", "0xfd0102 00", "0xfe01000002 00"+      , "0xff0100000000000002 00"+      ]+    n1_fail = [+        "0x01 09 ffffffffffffffffff", "0x01 01 00", "0x01 02 0001"+      , "0x01 03 000100", "0x01 04 00010000", "0x01 05 0001000000"+      , "0x01 06 000100000000", "0x01 07 00010000000000"+      , "0x01 08 0001000000000000", "0x02 07 01010101010101"+      , "0x02 09 010101010101010101"+      , "0x03 21 " ++ node_id+      , "0x03 29 " ++ node_id ++ "0000000000000001"+      , "0x03 30 " ++ node_id ++ "000000000000000100000000000001"+      , "0x03 31 04" ++ drop 2 node_id+          ++ "00000000000000010000000000000002"+      , "0x03 32 " ++ node_id ++ "0000000000000001000000000000000001"+      , "0xfd00fe 00", "0xfd00fe 01 01", "0xfd00fe 03 010101", "0x00 00"+      , "0x02 08 0000000000000226 01 01 2a"+      , "0x02 08 0000000000000231 02 08 0000000000000451"+      , "0x1f 00 0f 01 2a", "0x1f 00 1f 01 2a"+      ]+    n2_fail = ["0xffffffffffffffffff 00 00 00"]+    any_ok = [+        "0x", "0x21 00", "0xfd0201 00", "0xfd00fd 00", "0xfd00ff 00"+      , "0xfe02000001 00", "0xff0200000000000001 00"+      ]+    n1_ok = [+        ("0x01 00", N1Tlv1 0), ("0x01 01 01", N1Tlv1 1)+      , ("0x01 02 0100", N1Tlv1 256), ("0x01 03 010000", N1Tlv1 65536)+      , ("0x01 04 01000000", N1Tlv1 16777216)+      , ("0x01 05 0100000000", N1Tlv1 4294967296)+      , ("0x01 06 010000000000", N1Tlv1 1099511627776)+      , ("0x01 07 01000000000000", N1Tlv1 281474976710656)+      , ("0x01 08 0100000000000000", N1Tlv1 72057594037927936)+      , ("0x02 08 0000000000000226", N1Tlv2 (ShortChannelId 0x226))+      , ("0xfd00fe 02 0226", N1Tlv4 550)+      ]+    node_id =+      "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb"+    n1_tlv3 = "0x03 31 " ++ node_id ++ "00000000000000010000000000000002"++-- Appendix C: message extension ----------------------------------------------++appendix_c :: TestTree+appendix_c = testGroup "Appendix C (message extension)" [+    testCase "no extension" $ with_hex "001000000000" $ \bs ->+      case decode_message bs of+        Right (MsgInit i) -> do+          init_networks i @?= Nothing+          init_tlvs i @?= empty_tlv_stream+        other -> assertFailure (show other)+  , testCase "two unknown odd records are kept" $+      with_hex "001000000000c9012acb0104" $ \bs ->+        case decode_message bs of+          Right m@(MsgInit i) -> do+            map tlv_type (un_tlv_stream (init_tlvs i)) @?= [0xc9, 0xcb]+            encode_message m @?= Right bs+          other -> assertFailure (show other)+  , testCase "truncated extension" $ with_hex "00100000000001" $ \bs ->+      decode_message bs @?= Left (DecodeTlvError TlvTruncated)+  , testCase "unknown even record" $ with_hex "001000000000ca012a" $ \bs ->+      decode_message bs @?= Left (DecodeTlvError (TlvUnknownEvenType 0xca))+  , testCase "duplicate record" $ with_hex "001000000000c90101c90102" $ \bs ->+      decode_message bs @?= Left (DecodeTlvError TlvNotStrictlyIncreasing)+  ]++-- Appendix D: signed integers ------------------------------------------------++appendix_d :: TestTree+appendix_d = testGroup "Appendix D (signed integers)" [+    testCase (show v) $ with_hex h $ \bs -> case BS.length bs of+      1 -> do+        encode_s8 (fromIntegral v) @?= bs+        decode_s8 bs @?= Just (fromIntegral v, "")+      2 -> do+        encode_s16 (fromIntegral v) @?= bs+        decode_s16 bs @?= Just (fromIntegral v, "")+      4 -> do+        encode_s32 (fromIntegral v) @?= bs+        decode_s32 bs @?= Just (fromIntegral v, "")+      8 -> do+        encode_s64 v @?= bs+        decode_s64 bs @?= Just (v, "")+      _ -> assertFailure "bad vector width"+  | (v, h) <- vectors ]+  where+    vectors :: [(Int64, String)]+    vectors = [+        (0, "00"), (42, "2a"), (-42, "d6"), (127, "7f"), (-128, "80")+      , (128, "0080"), (-129, "ff7f"), (15000, "3a98"), (-15000, "c568")+      , (32767, "7fff"), (-32768, "8000"), (32768, "00008000")+      , (-32769, "ffff7fff"), (21000000, "01406f40")+      , (-21000000, "febf90c0"), (2147483647, "7fffffff")+      , (-2147483648, "80000000"), (2147483648, "0000000080000000")+      , (-2147483649, "ffffffff7fffffff"), (500000000000, "000000746a528800")+      , (-500000000000, "ffffff8b95ad7800")+      , (9223372036854775807, "7fffffffffffffff")+      , (-9223372036854775808, "8000000000000000")+      ]++-- primitives -----------------------------------------------------------------++primitives :: TestTree+primitives = testGroup "Primitives" [+    testCase "u16/u32/u64 known answers" $ do+      encode_u16 0x0102 @?= "\x01\x02"+      encode_u32 0x01020304 @?= "\x01\x02\x03\x04"+      encode_u64 0x0102030405060708 @?= "\x01\x02\x03\x04\x05\x06\x07\x08"+      decode_u32 "\x01\x02\x03\x04rest" @?= Just (0x01020304, "rest")+  , testCase "short reads fail" $ do+      decode_u16 "\x01" @?= Nothing+      decode_u32 "\x01\x02\x03" @?= Nothing+      decode_u64 "\x01\x02\x03\x04\x05\x06\x07" @?= Nothing+      decode_s8 "" @?= Nothing+  , testCase "truncated integers" $ do+      encode_tu64 0 @?= ""+      encode_tu64 1 @?= "\x01"+      encode_tu32 0x010000 @?= "\x01\x00\x00"+      decode_tu16 "" @?= Just 0+      decode_tu16 "\x00\x01" @?= Nothing+      decode_tu16 "\x01\x00\x00" @?= Nothing+      decode_tu32 "\x01\x00\x00\x00\x00" @?= Nothing+      decode_tu64 "\x01\x00\x00\x00\x00\x00\x00\x00\x00" @?= Nothing+  , testCase "length-prefixed bytes" $ do+      encode_u16_prefixed "abc" @?= Just "\x00\x03\&abc"+      encode_u16_prefixed (BS.replicate 65536 0) @?= Nothing+      decode_u16_prefixed "\x00\x03\&abcde" @?= Just ("abc", "de")+      decode_u16_prefixed "\x00\x03\&ab" @?= Nothing+  , testCase "fixed-size types check their lengths" $ do+      assertBool "chain_hash" (isNothing (chain_hash (BS.replicate 31 0)))+      assertBool "channel_id" (isNothing (channel_id (BS.replicate 33 0)))+      assertBool "signature" (isNothing (signature (BS.replicate 65 0)))+      assertBool "payment_hash" (isNothing (payment_hash ""))+      assertBool "preimage" (isNothing (payment_preimage ""))+      assertBool "secret" (isNothing (per_commitment_secret ""))+  , testCase "point prefix" $ do+      assertBool "02" (isJust (point (BS.cons 0x02 (BS.replicate 32 1))))+      assertBool "03" (isJust (point (BS.cons 0x03 (BS.replicate 32 1))))+      assertBool "04" (isNothing (point (BS.cons 0x04 (BS.replicate 32 1))))+      assertBool "len" (isNothing (point (BS.cons 0x02 (BS.replicate 31 1))))+  , testCase "short_channel_id" $ case short_channel_id 539268 845 1 of+      Nothing -> assertFailure "valid scid rejected"+      Just c -> do+        scid_block_height c @?= 539268+        scid_tx_index c @?= 845+        scid_output_index c @?= 1+        encode_short_channel_id c @?= "\x08\x3a\x84\x00\x03\x4d\x00\x01"+  , testCase "short_channel_id rejects 25-bit components" $ do+      short_channel_id 0x1000000 0 0 @?= Nothing+      short_channel_id 0 0x1000000 0 @?= Nothing+  , testCase "amount bounds" $ do+      fmap un_satoshi (satoshi 0x000775f05a074000) @?= Just 0x000775f05a074000+      satoshi 0x000775f05a074001 @?= Nothing+      fmap un_milli_satoshi (milli_satoshi 0x1d24b2dfac520000)+        @?= Just 0x1d24b2dfac520000+      milli_satoshi 0x1d24b2dfac520001 @?= Nothing+      sat_to_msat max_satoshi @?= max_milli_satoshi+      decode_satoshi (encode_u64 0x000775f05a074001) @?= Nothing+      decode_milli_satoshi (encode_u64 maxBound) @?= Nothing+  , testCase "checked arithmetic" $ do+      add_sat max_satoshi max_satoshi @?= Nothing+      (do a <- satoshi 1; b <- satoshi 2; sub_sat a b) @?= Nothing+      (do a <- satoshi 2; b <- satoshi 1; sub_sat a b) @?= satoshi 1+      (do a <- milli_satoshi 1; b <- milli_satoshi 2; sub_msat a b)+        @?= Nothing+      add_msat max_milli_satoshi max_milli_satoshi @?= Nothing+  , testCase "secrets don't show" $+      case payment_preimage (BS.replicate 32 7) of+        Nothing -> assertFailure "valid preimage rejected"+        Just p -> do+          show p @?= "PaymentPreimage <redacted>"+          show (Just p) @?= "Just (PaymentPreimage <redacted>)"+  , testCase "secret equality" $ do+      let a = payment_preimage (BS.replicate 32 7)+          b = payment_preimage (BS.replicate 31 7 <> "\x08")+      assertBool "equal" (a == a)+      assertBool "unequal" (a /= b)+  ]++-- TLV ------------------------------------------------------------------------++tlv :: TestTree+tlv = testGroup "TLV" [+    testCase "tlv_stream sorts" $+      fmap (map tlv_type . un_tlv_stream)+        (tlv_stream [TlvRecord 5 "", TlvRecord 1 "", TlvRecord 3 ""])+        @?= Just [1, 3, 5]+  , testCase "tlv_stream rejects duplicates" $+      tlv_stream [TlvRecord 1 "a", TlvRecord 1 "b"] @?= Nothing+  , testCase "lookup_tlv" $ do+      (lookup_tlv 3 =<< tlv_stream [TlvRecord 1 "a", TlvRecord 3 "b"])+        @?= Just "b"+      (lookup_tlv 2 =<< tlv_stream [TlvRecord 1 "a", TlvRecord 3 "b"])+        @?= Nothing+  , testCase "huge lengths are rejected, not wrapped" $+      mapM_ (\h -> with_hex h $ \bs ->+               decode_tlv_stream (const True) bs @?= Left TlvTruncated)+        [ "01ffffffffffffffffff", "01ff800000000000000067"+        , "01ffffffffffffffffff6701aa" ]+  , testCase "truncation vs non-minimal" $ do+      decode_tlv_stream (const True) "\xfd" @?= Left TlvTruncated+      decode_tlv_stream (const True) "\xfd\x00\x01\x00"+        @?= Left TlvNonMinimalBigSize+  , testCase "unknown odd records are kept" $+      fmap (map tlv_type . un_tlv_stream)+        (decode_tlv_stream (const False) "\x01\x00\x03\x01\x2a")+        @?= Right [1, 3]+  ]++-- messages -------------------------------------------------------------------++messages :: TestTree+messages = testGroup "Messages" [+    testCase "pong known answer" $+      encode_message (MsgPong (Pong "\x00\x00" empty_tlv_stream))+        @?= Right "\x00\x13\x00\x02\x00\x00"+  , testCase "ping known answer" $+      encode_message (MsgPing (Ping 4 "" empty_tlv_stream))+        @?= Right "\x00\x12\x00\x04\x00\x00"+  , testCase "error known answer" $+      encode_message (MsgError (Error all_channels "x" empty_tlv_stream))+        @?= Right ("\x00\x11" <> BS.replicate 32 0 <> "\x00\x01x")+  , testCase "init with networks and remote_addr" $+      case chain_hash (BS.replicate 32 0x6f) of+        Nothing -> assertFailure "valid chain hash rejected"+        Just ch -> do+          let addr = "\x01\x7f\x00\x00\x01\x26\x07"+              i = Init empty_fv (BOLT9.parse "\x02") (Just [ch])+                    (Just addr) empty_tlv_stream+              wire = "\x00\x10\x00\x00\x00\x01\x02"+                  <> "\x01\x20" <> un_chain_hash ch <> "\x03\x07" <> addr+          encode_message (MsgInit i) @?= Right wire+          decode_message wire @?= Right (MsgInit i)+  , testCase "init networks must be a multiple of 32 bytes" $+      decode_init "\x00\x00\x00\x00\x01\x01\x00"+        @?= Left (DecodeInvalidTlvValue 1)+  , testCase "init rejects tlvs clashing with typed fields" $+      case tlv_stream [TlvRecord 3 "x"] of+        Nothing -> assertFailure "valid stream rejected"+        Just s  -> encode_init (Init empty_fv empty_fv Nothing (Just "y") s)+          @?= Left EncodeInvalidTlvs+  , testCase "unknown message types" $ do+      decode_message "\x00\x20" @?= Left (DecodeUnknownEvenType 32)+      decode_message "\x80\x01" @?= Left (DecodeUnknownOddType 32769)+      decode_message "\x00" @?= Left DecodeInsufficientBytes+  , testCase "truncated payloads" $ do+      decode_init "\x00" @?= Left DecodeInsufficientBytes+      decode_init "\x00\x01" @?= Left DecodeInsufficientBytes+      decode_error (BS.replicate 31 0) @?= Left DecodeInsufficientBytes+      decode_warning (BS.replicate 33 0) @?= Left DecodeInsufficientBytes+      decode_ping "\x00\x04\x00\x02\x00" @?= Left DecodeInsufficientBytes+      decode_pong "\x00\x02\x00" @?= Left DecodeInsufficientBytes+      decode_peer_storage "\x00\x01" @?= Left DecodeInsufficientBytes+      decode_peer_storage_retrieval "\x00" @?= Left DecodeInsufficientBytes+  , testCase "invalid extensions" $ do+      decode_pong "\x00\x00\x02\x00" @?= Left+        (DecodeTlvError (TlvUnknownEvenType 2))+      decode_ping "\x00\x00\x00\x00\x01" @?= Left (DecodeTlvError TlvTruncated)+  , testCase "message size limit" $ do+      encode_envelope 7 (BS.replicate 65533 0) @?=+        Right (encode_u16 7 <> BS.replicate 65533 0)+      encode_envelope 7 (BS.replicate 65534 0) @?= Left EncodeMessageTooLarge+      encode_message (MsgPeerStorage (PeerStorage (BS.replicate 65532 0)+        empty_tlv_stream)) @?= Left EncodeMessageTooLarge+      encode_pong (Pong (BS.replicate 65536 0) empty_tlv_stream)+        @?= Left EncodeLengthOverflow+  , testCase "ping_response" $ do+      ping_response (Ping 3 "ab" empty_tlv_stream)+        @?= Just (Pong "\x00\x00\x00" empty_tlv_stream)+      ping_response (Ping 65531 "" empty_tlv_stream) @?=+        Just (Pong (BS.replicate 65531 0) empty_tlv_stream)+      ping_response (Ping 65532 "" empty_tlv_stream) @?= Nothing+  ]++-- properties -----------------------------------------------------------------++newtype Bytes = Bytes BS.ByteString+  deriving Show++instance Arbitrary Bytes where+  arbitrary = Bytes . BS.pack <$> (choose (0, 64) >>= vector)++-- streams of unknown odd records+newtype OddTlvs = OddTlvs TlvStream+  deriving Show++instance Arbitrary OddTlvs where+  arbitrary = do+    ts <- L.nub . map (\t -> 2 * (t `div` 2) + 1)+            <$> listOf (arbitrary :: Gen Word64)+    vs <- vectorOf (length ts) arbitrary+    pure . OddTlvs . fromMaybe empty_tlv_stream $+      tlv_stream [ TlvRecord t v | (t, Bytes v) <- zip ts vs ]++newtype Bytes32 = Bytes32 BS.ByteString+  deriving Show++instance Arbitrary Bytes32 where+  arbitrary = Bytes32 . BS.pack <$> vector 32++newtype Msg = Msg Message+  deriving Show++instance Arbitrary Msg where+  arbitrary = Msg <$> oneof [+      do Bytes a <- arbitrary+         Bytes b <- arbitrary+         nets <- oneof [pure Nothing, Just <$> listOf chain]+         addr <- oneof [pure Nothing, (\(Bytes x) -> Just x) <$> arbitrary]+         OddTlvs s <- arbitrary+         let s' = filter_tlv_stream (\t -> t /= 1 && t /= 3) s+         pure (MsgInit (Init (BOLT9.parse a) (BOLT9.parse b) nets addr s'))+    , do c <- cid+         Bytes d <- arbitrary+         OddTlvs s <- arbitrary+         pure (MsgError (Error c d s))+    , do c <- cid+         Bytes d <- arbitrary+         OddTlvs s <- arbitrary+         pure (MsgWarning (Warning c d s))+    , do n <- arbitrary+         Bytes d <- arbitrary+         OddTlvs s <- arbitrary+         pure (MsgPing (Ping n d s))+    , do Bytes d <- arbitrary+         OddTlvs s <- arbitrary+         pure (MsgPong (Pong d s))+    , do Bytes d <- arbitrary+         OddTlvs s <- arbitrary+         pure (MsgPeerStorage (PeerStorage d s))+    , do Bytes d <- arbitrary+         OddTlvs s <- arbitrary+         pure (MsgPeerStorageRetrieval (PeerStorageRetrieval d s))+    ]+    where+      chain = do+        Bytes32 b <- arbitrary+        maybe chain pure (chain_hash b)+      cid = do+        Bytes32 b <- arbitrary+        pure (fromMaybe all_channels (channel_id b))++properties :: TestTree+properties = testGroup "Properties" [+    testProperty "bigsize round-trips" $ \w ->+      decode_bigsize (encode_bigsize w) === Just (w, "")+  , testProperty "tu16 round-trips" $ \w ->+      decode_tu16 (encode_tu16 w) === Just w+  , testProperty "tu32 round-trips" $ \w ->+      decode_tu32 (encode_tu32 w) === Just w+  , testProperty "tu64 round-trips" $ \w ->+      decode_tu64 (encode_tu64 w) === Just w+  , testProperty "u16 round-trips" $ \w ->+      decode_u16 (encode_u16 w) === Just (w, "")+  , testProperty "u32 round-trips" $ \w ->+      decode_u32 (encode_u32 w) === Just (w, "")+  , testProperty "u64 round-trips" $ \w ->+      decode_u64 (encode_u64 w) === Just (w, "")+  , testProperty "s64 round-trips" $ \w ->+      decode_s64 (encode_s64 w) === Just (w, "")+  , testProperty "scid components round-trip" $ \h t o ->+      let h' = h `mod` 0x1000000 :: Word32+          t' = t `mod` 0x1000000 :: Word32+      in  fmap (\c -> (scid_block_height c, scid_tx_index c,+                       scid_output_index c)) (short_channel_id h' t' o)+            === Just (h', t', o)+  , testProperty "sat -> msat -> sat is the identity" $+      forAll (choose (0, 0x000775f05a074000)) $ \w ->+        fmap (msat_to_sat . sat_to_msat) (satoshi w) === satoshi w+  , testProperty "add then sub is the identity" $+      forAll (choose (0, 0x000775f05a074000)) $ \a ->+      forAll (choose (0, 0x000775f05a074000 - a)) $ \b ->+        (do x <- satoshi a+            y <- satoshi b+            z <- add_sat x y+            sub_sat z y) === satoshi a+  , testProperty "tlv streams round-trip" $ \(OddTlvs s) ->+      decode_tlv_stream (const False) (encode_tlv_stream s) === Right s+  , testProperty "messages round-trip" $ \(Msg m) ->+      case encode_message m of+        Left e   -> counterexample (show e) False+        Right bs -> decode_message bs === Right m+  , testProperty "re-encoding reproduces the wire bytes" $ \(Msg m) ->+      case encode_message m of+        Left e   -> counterexample (show e) False+        Right bs -> case decode_message bs of+          Left e   -> counterexample (show e) False+          Right m' -> encode_message m' === Right bs+  ]