bolt2

Lightning peer protocol, per BOLT #2 (docs.ppad.tech/bolt2).
git clone git://git.ppad.tech/bolt2.git
Log | Files | Refs | README | LICENSE

commit 6f295dd9af0ea3a78db14f5020744bef902a55a1
parent 0d587f9d1d42c2fdb26cfe0e6c96136faba4ee67
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 10 Oct 2026 14:05:29 +0400

lib: port to ppad-bolt1 0.1.0 with typed TLVs

Rewrites the library against the ppad-bolt1 0.1.0 API.

* Exposes Lightning.Protocol.BOLT2 alone, with snake_case names and
  per-message field prefixes (open_channel_chain_hash, ...).
* Every payload decoder consumes its whole input; the bytes after the
  fixed fields are the message's TLV stream, decoded with the
  message's known types. Unknown even types fail, unknown odd records
  are kept in the _tlvs field, and known records get typed fields
  (upfront_shutdown_script, channel_type as a bolt9 FeatureVector,
  require_confirmed_inputs, funding_output_contribution, short_channel_id
  alias, closing signatures, fee_range, blinded_path, attribution_data,
  fulfillment_payload, commitment_signed funding_txid, next_funding).
* Known TLV values are length-checked; points, amounts and the stfu
  initiator byte are validated.
* Amounts use bolt1's bounded Satoshi/MilliSatoshi, except
  max_htlc_value_in_flight_msat, which stays Word64.
* Encoders return Either EncodeError only where they can fail.
* Adds a Message dispatcher (message_type, encode_message,
  decode_message) framed by bolt1's envelope codec.
* Removes FeatureBits, Initiator, MsgType and the bolt1/ppad-tx
  re-exports; renames OnionPacket to OnionRoutingPacket; adds
  OnionHash and AttributionData; bounds ScriptPubKey and Witness at
  65535 bytes.
* Catches up with the spec: channel_reestablish next_funding at type 1,
  update_fulfill_htlc fulfillment_payload, TLV streams on
  commitment_signed, tx_add_input and tx_signatures.

Diffstat:
Mlib/Lightning/Protocol/BOLT2.hs | 287++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mlib/Lightning/Protocol/BOLT2/Codec.hs | 2598++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mlib/Lightning/Protocol/BOLT2/Messages.hs | 860++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mlib/Lightning/Protocol/BOLT2/Types.hs | 398++++++++++++++++++++++++++++++++++---------------------------------------------
Mppad-bolt2.cabal | 26+++++++++++++++++---------
5 files changed, 2104 insertions(+), 2065 deletions(-)

diff --git a/lib/Lightning/Protocol/BOLT2.hs b/lib/Lightning/Protocol/BOLT2.hs @@ -6,128 +6,187 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> -- --- Peer protocol for the Lightning Network, per --- [BOLT #2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md). +-- The peer protocol of the Lightning Network, per +-- [BOLT #2](https://github.com/lightning/bolts/blob/master/02-peer-protocol.md): +-- the messages for channel establishment (v1 and v2), interactive +-- transaction construction, quiescence, channel close, normal operation +-- and message retransmission. +-- +-- Fundamental types (channel ids, points, signatures, amounts, TLV +-- streams) come from ppad-bolt1, transaction ids from ppad-tx, and +-- feature vectors from ppad-bolt9. +-- +-- Each message has a payload codec, @encode_\<msg\>@ and +-- @decode_\<msg\>@; 'encode_message' and 'decode_message' handle +-- complete messages, including the type. Decoders consume the whole +-- payload: bytes after the fixed fields are the message's TLV stream. +-- Known TLV records are decoded into typed fields, an unknown even +-- record fails the decode, and unknown odd records are kept in the +-- message's @_tlvs@ field, so re-encoding a decoded message reproduces +-- its bytes. +-- +-- The examples below assume: +-- +-- >>> :set -XOverloadedStrings +-- >>> import qualified Data.ByteString as BS +-- >>> import qualified Lightning.Protocol.BOLT1 as BOLT1 +-- >>> import Lightning.Protocol.BOLT2 +-- >>> let Just cid = BOLT1.channel_id (BS.replicate 32 0xab) +-- +-- Encode a message, and decode it again: +-- +-- >>> let fee = UpdateFee cid 2500 BOLT1.empty_tlv_stream +-- >>> let Right wire = encode_message (MsgUpdateFee fee) +-- >>> BS.length wire +-- 38 +-- >>> decode_message wire == Right (MsgUpdateFee fee) +-- True +-- +-- Unknown odd TLV records are preserved, and unknown even ones +-- rejected: +-- +-- >>> let payload = encode_update_fee fee +-- >>> fmap update_fee_tlvs (decode_update_fee (payload <> "\x05\x01\x2a")) +-- Right (TlvStream [TlvRecord {tlv_type = 5, tlv_value = "*"}]) +-- >>> decode_update_fee (payload <> "\x04\x01\x2a") +-- Left (DecodeTlvError (TlvUnknownEvenType 4)) module Lightning.Protocol.BOLT2 ( - -- * Core types - -- | Re-exported from "Lightning.Protocol.BOLT2.Types". - module Lightning.Protocol.BOLT2.Types - - -- * Message types - -- | Re-exported from "Lightning.Protocol.BOLT2.Messages". - , module Lightning.Protocol.BOLT2.Messages - - -- * Codec functions - -- | Re-exported from "Lightning.Protocol.BOLT2.Codec". - , module Lightning.Protocol.BOLT2.Codec - - -- $messagetypes - - -- ** Channel establishment (v1) - -- $v1establishment - - -- ** Channel establishment (v2) - -- $v2establishment + -- * Messages + Message(..) + , message_type + + -- ** Channel establishment v1 + , OpenChannel(..) + , AcceptChannel(..) + , FundingCreated(..) + , FundingSigned(..) + , ChannelReady(..) + + -- ** Channel establishment v2 + , OpenChannel2(..) + , AcceptChannel2(..) + + -- ** Interactive transaction construction + , TxAddInput(..) + , TxAddOutput(..) + , TxRemoveInput(..) + , TxRemoveOutput(..) + , TxComplete(..) + , TxSignatures(..) + , TxInitRbf(..) + , TxAckRbf(..) + , TxAbort(..) + + -- ** Channel quiescence + , Stfu(..) -- ** Channel close - -- $close + , Shutdown(..) + , ClosingComplete(..) + , ClosingSig(..) + , ClosingSigned(..) -- ** Normal operation - -- $normal - - -- ** Message reestablishment - -- $reestablish + , UpdateAddHtlc(..) + , UpdateFulfillHtlc(..) + , UpdateFailHtlc(..) + , UpdateFailMalformedHtlc(..) + , CommitmentSigned(..) + , RevokeAndAck(..) + , UpdateFee(..) + + -- ** Message retransmission + , ChannelReestablish(..) + + -- * Field types + , HtlcId(..) + , SerialId(..) + , ScriptPubKey + , script_pubkey + , un_script_pubkey + , Witness + , witness + , un_witness + , OnionRoutingPacket + , onion_routing_packet + , un_onion_routing_packet + , OnionHash + , onion_hash + , un_onion_hash + , AttributionData + , attribution_data + , un_attribution_data + , FeeRange(..) + , NextFunding(..) + + -- * Encoding and decoding messages + , encode_message + , decode_message + , EncodeError(..) + , DecodeError(..) + + -- ** Payloads + , encode_open_channel + , decode_open_channel + , encode_accept_channel + , decode_accept_channel + , encode_funding_created + , decode_funding_created + , encode_funding_signed + , decode_funding_signed + , encode_channel_ready + , decode_channel_ready + , encode_open_channel2 + , decode_open_channel2 + , encode_accept_channel2 + , decode_accept_channel2 + , encode_tx_add_input + , decode_tx_add_input + , encode_tx_add_output + , decode_tx_add_output + , encode_tx_remove_input + , decode_tx_remove_input + , encode_tx_remove_output + , decode_tx_remove_output + , encode_tx_complete + , decode_tx_complete + , encode_tx_signatures + , decode_tx_signatures + , encode_tx_init_rbf + , decode_tx_init_rbf + , encode_tx_ack_rbf + , decode_tx_ack_rbf + , encode_tx_abort + , decode_tx_abort + , encode_stfu + , decode_stfu + , encode_shutdown + , decode_shutdown + , encode_closing_complete + , decode_closing_complete + , encode_closing_sig + , decode_closing_sig + , encode_closing_signed + , decode_closing_signed + , encode_update_add_htlc + , decode_update_add_htlc + , encode_update_fulfill_htlc + , decode_update_fulfill_htlc + , encode_update_fail_htlc + , decode_update_fail_htlc + , encode_update_fail_malformed_htlc + , decode_update_fail_malformed_htlc + , encode_commitment_signed + , decode_commitment_signed + , encode_revoke_and_ack + , decode_revoke_and_ack + , encode_update_fee + , decode_update_fee + , encode_channel_reestablish + , decode_channel_reestablish ) where import Lightning.Protocol.BOLT2.Codec import Lightning.Protocol.BOLT2.Messages import Lightning.Protocol.BOLT2.Types - --- $messagetypes --- --- BOLT #2 defines the following message types: --- --- * 2: stfu --- * 32: open_channel --- * 33: accept_channel --- * 34: funding_created --- * 35: funding_signed --- * 36: channel_ready --- * 38: shutdown --- * 39: closing_signed --- * 40: closing_complete --- * 41: closing_sig --- * 64: open_channel2 --- * 65: accept_channel2 --- * 66: tx_add_input --- * 67: tx_add_output --- * 68: tx_remove_input --- * 69: tx_remove_output --- * 70: tx_complete --- * 71: tx_signatures --- * 72: tx_init_rbf --- * 73: tx_ack_rbf --- * 74: tx_abort --- * 128: update_add_htlc --- * 130: update_fulfill_htlc --- * 131: update_fail_htlc --- * 132: commitment_signed --- * 133: revoke_and_ack --- * 134: update_fee --- * 135: update_fail_malformed_htlc --- * 136: channel_reestablish - --- $v1establishment --- --- Channel establishment v1 messages: --- --- * open_channel (32) --- * accept_channel (33) --- * funding_created (34) --- * funding_signed (35) --- * channel_ready (36) - --- $v2establishment --- --- Channel establishment v2 (interactive-tx) messages: --- --- * open_channel2 (64) --- * accept_channel2 (65) --- * tx_add_input (66) --- * tx_add_output (67) --- * tx_remove_input (68) --- * tx_remove_output (69) --- * tx_complete (70) --- * tx_signatures (71) --- * tx_init_rbf (72) --- * tx_ack_rbf (73) --- * tx_abort (74) - --- $close --- --- Channel close messages: --- --- * stfu (2) --- * shutdown (38) --- * closing_signed (39) --- * closing_complete (40) --- * closing_sig (41) - --- $normal --- --- Normal operation messages: --- --- * update_add_htlc (128) --- * update_fulfill_htlc (130) --- * update_fail_htlc (131) --- * commitment_signed (132) --- * revoke_and_ack (133) --- * update_fee (134) --- * update_fail_malformed_htlc (135) - --- $reestablish --- --- Message reestablishment: --- --- * channel_reestablish (136) diff --git a/lib/Lightning/Protocol/BOLT2/Codec.hs b/lib/Lightning/Protocol/BOLT2/Codec.hs @@ -1,7 +1,6 @@ -{-# OPTIONS_HADDOCK prune #-} +{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} -- | -- Module: Lightning.Protocol.BOLT2.Codec @@ -9,1348 +8,1381 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> -- --- Encode/decode functions for BOLT #2 messages. +-- Encoding and decoding of BOLT #2 messages. module Lightning.Protocol.BOLT2.Codec ( - -- * Error types EncodeError(..) , DecodeError(..) - -- * Channel establishment v1 - , encodeOpenChannel - , decodeOpenChannel - , encodeAcceptChannel - , decodeAcceptChannel - , encodeFundingCreated - , decodeFundingCreated - , encodeFundingSigned - , decodeFundingSigned - , encodeChannelReady - , decodeChannelReady - - -- * Channel establishment v2 (interactive-tx) - , encodeOpenChannel2 - , decodeOpenChannel2 - , encodeAcceptChannel2 - , decodeAcceptChannel2 - , encodeTxAddInput - , decodeTxAddInput - , encodeTxAddOutput - , decodeTxAddOutput - , encodeTxRemoveInput - , decodeTxRemoveInput - , encodeTxRemoveOutput - , decodeTxRemoveOutput - , encodeTxComplete - , decodeTxComplete - , encodeTxSignatures - , decodeTxSignatures - , encodeTxInitRbf - , decodeTxInitRbf - , encodeTxAckRbf - , decodeTxAckRbf - , encodeTxAbort - , decodeTxAbort - - -- * Channel close - , encodeStfu - , decodeStfu - , encodeShutdown - , decodeShutdown - , encodeClosingSigned - , decodeClosingSigned - , encodeClosingComplete - , decodeClosingComplete - , encodeClosingSig - , decodeClosingSig - - -- * Normal operation - , encodeUpdateAddHtlc - , decodeUpdateAddHtlc - , encodeUpdateFulfillHtlc - , decodeUpdateFulfillHtlc - , encodeUpdateFailHtlc - , decodeUpdateFailHtlc - , encodeUpdateFailMalformedHtlc - , decodeUpdateFailMalformedHtlc - , encodeCommitmentSigned - , decodeCommitmentSigned - , encodeRevokeAndAck - , decodeRevokeAndAck - , encodeUpdateFee - , decodeUpdateFee - - -- * Channel reestablishment - , encodeChannelReestablish - , decodeChannelReestablish + , encode_message + , decode_message + + , encode_open_channel + , decode_open_channel + , encode_accept_channel + , decode_accept_channel + , encode_funding_created + , decode_funding_created + , encode_funding_signed + , decode_funding_signed + , encode_channel_ready + , decode_channel_ready + + , encode_open_channel2 + , decode_open_channel2 + , encode_accept_channel2 + , decode_accept_channel2 + , encode_tx_add_input + , decode_tx_add_input + , encode_tx_add_output + , decode_tx_add_output + , encode_tx_remove_input + , decode_tx_remove_input + , encode_tx_remove_output + , decode_tx_remove_output + , encode_tx_complete + , decode_tx_complete + , encode_tx_signatures + , decode_tx_signatures + , encode_tx_init_rbf + , decode_tx_init_rbf + , encode_tx_ack_rbf + , decode_tx_ack_rbf + , encode_tx_abort + , decode_tx_abort + + , encode_stfu + , decode_stfu + + , encode_shutdown + , decode_shutdown + , encode_closing_complete + , decode_closing_complete + , encode_closing_sig + , decode_closing_sig + , encode_closing_signed + , decode_closing_signed + + , encode_update_add_htlc + , decode_update_add_htlc + , encode_update_fulfill_htlc + , decode_update_fulfill_htlc + , encode_update_fail_htlc + , decode_update_fail_htlc + , encode_update_fail_malformed_htlc + , decode_update_fail_malformed_htlc + , encode_commitment_signed + , decode_commitment_signed + , encode_revoke_and_ack + , decode_revoke_and_ack + , encode_update_fee + , decode_update_fee + + , encode_channel_reestablish + , decode_channel_reestablish ) where +import Bitcoin.Prim.Tx (TxId(..), mkTxId) import Control.DeepSeq (NFData) -import Control.Monad (unless) import qualified Data.ByteString as BS -import Data.Word (Word8, Word16, Word32) +import qualified Data.ByteString.Unsafe as BU +import Data.Int (Int64) +import Data.Word (Word8, Word16, Word32, Word64) import GHC.Generics (Generic) +import qualified Lightning.Protocol.BOLT1 as BOLT1 import Lightning.Protocol.BOLT1 - ( TlvStream - , unsafeTlvStream - , TlvError - , encodeU16 - , encodeU32 - , encodeU64 - , decodeU16 - , decodeU32 - , decodeU64 - , encodeTlvStream - , decodeTlvStreamRaw - ) -import Lightning.Protocol.BOLT2.Types + ( ChannelId, Signature, Point, TlvRecord(..), TlvStream, TlvError ) +import qualified Lightning.Protocol.BOLT9 as BOLT9 import Lightning.Protocol.BOLT2.Messages +import Lightning.Protocol.BOLT2.Types --- Error types ----------------------------------------------------------------- +-- errors --------------------------------------------------------------------- --- | Encoding errors. +-- | Why a message failed to encode. data EncodeError - = EncodeLengthOverflow -- ^ Payload exceeds u16 max (65535 bytes) - deriving stock (Eq, Show, Generic) + = EncodeLengthOverflow + -- ^ a length-prefixed field or a counted list exceeds 65535 + | EncodeInvalidTlvs + -- ^ a @_tlvs@ field contains a record of a type the message knows + | EncodeMessageTooLarge + -- ^ the message (type and payload) exceeds 65535 bytes + deriving (Eq, Show, Generic) instance NFData EncodeError --- | Decoding errors. +-- | Why a message failed to decode. data DecodeError = DecodeInsufficientBytes - | DecodeInvalidLength - | DecodeInvalidChannelId - | DecodeInvalidChainHash - | DecodeInvalidSignature + -- ^ the input ended before a field did | DecodeInvalidPoint - | DecodeInvalidTxId - | DecodeInvalidPaymentHash - | DecodeInvalidPaymentPreimage - | DecodeInvalidOnionPacket - | DecodeInvalidSecret + -- ^ a point lacks a compressed-encoding prefix + | DecodeInvalidAmount + -- ^ an amount exceeds 21 million BTC | DecodeInvalidInitiator + -- ^ the @initiator@ byte of @stfu@ is neither 0 nor 1 | DecodeTlvError !TlvError - deriving stock (Eq, Show, Generic) + -- ^ the message's TLV stream is malformed, or has an unknown even + -- type + | DecodeInvalidTlvValue !Word64 + -- ^ a known TLV record (of the given type) has a malformed value + | DecodeUnknownEvenType !Word16 + -- ^ a message type that BOLT #2 doesn't define (even) + | DecodeUnknownOddType !Word16 + -- ^ a message type that BOLT #2 doesn't define (odd) + deriving (Eq, Show, Generic) instance NFData DecodeError --- Helpers --------------------------------------------------------------------- - --- | Decode a single byte. -decodeU8 :: BS.ByteString -> Maybe (Word8, BS.ByteString) -decodeU8 !bs - | BS.null bs = Nothing - | otherwise = Just (BS.index bs 0, BS.drop 1 bs) -{-# INLINE decodeU8 #-} - --- | Decode fixed-size bytes. -decodeBytes :: Int -> BS.ByteString -> Maybe (BS.ByteString, BS.ByteString) -decodeBytes !n !bs - | BS.length bs < n = Nothing - | otherwise = Just (BS.take n bs, BS.drop n bs) -{-# INLINE decodeBytes #-} - --- | Decode a ChannelId (32 bytes). -decodeChannelIdBytes - :: BS.ByteString -> Either DecodeError (ChannelId, BS.ByteString) -decodeChannelIdBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes channelIdLen bs) - cid <- maybe (Left DecodeInvalidChannelId) Right (channelId raw) - Right (cid, rest) -{-# INLINE decodeChannelIdBytes #-} - --- | Decode a ChainHash (32 bytes). -decodeChainHashBytes - :: BS.ByteString -> Either DecodeError (ChainHash, BS.ByteString) -decodeChainHashBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes chainHashLen bs) - ch <- maybe (Left DecodeInvalidChainHash) Right (chainHash raw) - Right (ch, rest) -{-# INLINE decodeChainHashBytes #-} - --- | Decode a Signature (64 bytes). -decodeSignatureBytes - :: BS.ByteString -> Either DecodeError (Signature, BS.ByteString) -decodeSignatureBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes signatureLen bs) - sig <- maybe (Left DecodeInvalidSignature) Right (signature raw) - Right (sig, rest) -{-# INLINE decodeSignatureBytes #-} - --- | Decode a Point (33 bytes). -decodePointBytes - :: BS.ByteString -> Either DecodeError (Point, BS.ByteString) -decodePointBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes pointLen bs) - pt <- maybe (Left DecodeInvalidPoint) Right (point raw) - Right (pt, rest) -{-# INLINE decodePointBytes #-} - --- | Decode a TxId (32 bytes). -decodeTxIdBytes - :: BS.ByteString -> Either DecodeError (TxId, BS.ByteString) -decodeTxIdBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes 32 bs) - tid <- maybe (Left DecodeInvalidTxId) Right (mkTxId raw) - Right (tid, rest) -{-# INLINE decodeTxIdBytes #-} - --- | Decode a u16 with error handling. -decodeU16E :: BS.ByteString -> Either DecodeError (Word16, BS.ByteString) -decodeU16E !bs = maybe (Left DecodeInsufficientBytes) Right (decodeU16 bs) -{-# INLINE decodeU16E #-} - --- | Decode a u32 with error handling. -decodeU32E :: BS.ByteString -> Either DecodeError (Word32, BS.ByteString) -decodeU32E !bs = maybe (Left DecodeInsufficientBytes) Right (decodeU32 bs) -{-# INLINE decodeU32E #-} - --- | Decode a u64 as Satoshi. -decodeSatoshi - :: BS.ByteString -> Either DecodeError (Satoshi, BS.ByteString) -decodeSatoshi !bs = do - (val, rest) <- maybe (Left DecodeInsufficientBytes) Right (decodeU64 bs) - Right (Satoshi val, rest) -{-# INLINE decodeSatoshi #-} - --- | Decode a u64 as MilliSatoshi. -decodeMilliSatoshi - :: BS.ByteString -> Either DecodeError (MilliSatoshi, BS.ByteString) -decodeMilliSatoshi !bs = do - (val, rest) <- maybe (Left DecodeInsufficientBytes) Right (decodeU64 bs) - Right (MilliSatoshi val, rest) -{-# INLINE decodeMilliSatoshi #-} - --- | Decode optional TLV stream from remaining bytes. -decodeTlvs :: BS.ByteString -> Either DecodeError TlvStream -decodeTlvs !bs - | BS.null bs = Right (unsafeTlvStream []) - | otherwise = either (Left . DecodeTlvError) Right (decodeTlvStreamRaw bs) -{-# INLINE decodeTlvs #-} - --- | Decode a length-prefixed script (u16 length prefix). -decodeScriptPubKey - :: BS.ByteString -> Either DecodeError (ScriptPubKey, BS.ByteString) -decodeScriptPubKey !bs = do - (len, rest1) <- decodeU16E bs - let !scriptLen = fromIntegral len - unless (BS.length rest1 >= scriptLen) $ Left DecodeInsufficientBytes - let !script = BS.take scriptLen rest1 - !rest2 = BS.drop scriptLen rest1 - Right (scriptPubKey script, rest2) -{-# INLINE decodeScriptPubKey #-} - --- | Decode a PaymentHash (32 bytes). -decodePaymentHashBytes - :: BS.ByteString -> Either DecodeError (PaymentHash, BS.ByteString) -decodePaymentHashBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes paymentHashLen bs) - ph <- maybe (Left DecodeInvalidPaymentHash) Right (paymentHash raw) - Right (ph, rest) -{-# INLINE decodePaymentHashBytes #-} - --- | Decode a PaymentPreimage (32 bytes). -decodePaymentPreimageBytes - :: BS.ByteString -> Either DecodeError (PaymentPreimage, BS.ByteString) -decodePaymentPreimageBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes paymentPreimageLen bs) - pp <- maybe (Left DecodeInvalidPaymentPreimage) Right (paymentPreimage raw) - Right (pp, rest) -{-# INLINE decodePaymentPreimageBytes #-} - --- | Decode an OnionPacket (1366 bytes). -decodeOnionPacketBytes - :: BS.ByteString -> Either DecodeError (OnionPacket, BS.ByteString) -decodeOnionPacketBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes onionPacketLen bs) - op <- maybe (Left DecodeInvalidOnionPacket) Right (onionPacket raw) - Right (op, rest) -{-# INLINE decodeOnionPacketBytes #-} - --- | Decode a PerCommitmentSecret (32 bytes). -decodePerCommitmentSecretBytes - :: BS.ByteString - -> Either DecodeError (PerCommitmentSecret, BS.ByteString) -decodePerCommitmentSecretBytes !bs = do - (raw, rest) <- maybe (Left DecodeInsufficientBytes) Right - (decodeBytes perCommitmentSecretLen bs) - sec <- maybe (Left DecodeInvalidSecret) Right - (perCommitmentSecret raw) - Right (sec, rest) -{-# INLINE decodePerCommitmentSecretBytes #-} - --- | Encode a u16-prefixed byte string with bounds checking. -encodeU16BytesE :: BS.ByteString -> Either EncodeError BS.ByteString -encodeU16BytesE !bs - | BS.length bs > 65535 = Left EncodeLengthOverflow - | otherwise = Right $! encodeU16 (fromIntegral (BS.length bs)) <> bs -{-# INLINE encodeU16BytesE #-} - --- | Check that a list count fits in u16. -checkListCountU16 :: Int -> Either EncodeError Word16 -checkListCountU16 !n +-- parsing -------------------------------------------------------------------- + +newtype Parser a = Parser + { run_parser :: BS.ByteString -> Either DecodeError (a, BS.ByteString) } + +instance Functor Parser where + fmap f (Parser p) = Parser $ \bs -> case p bs of + Left e -> Left e + Right (a, r) -> Right (f a, r) + {-# INLINE fmap #-} + +instance Applicative Parser where + pure a = Parser $ \bs -> Right (a, bs) + {-# INLINE pure #-} + Parser pf <*> Parser pa = Parser $ \bs -> case pf bs of + Left e -> Left e + Right (f, r) -> case pa r of + Left e -> Left e + Right (a, r') -> Right (f a, r') + {-# INLINE (<*>) #-} + +instance Monad Parser where + Parser p >>= k = Parser $ \bs -> case p bs of + Left e -> Left e + Right (a, r) -> run_parser (k a) r + {-# INLINE (>>=) #-} + +-- run a parser whose last step consumes the remaining input +parse :: Parser a -> BS.ByteString -> Either DecodeError a +parse p bs = fmap fst (run_parser p bs) +{-# INLINE parse #-} + +failure :: DecodeError -> Parser a +failure e = Parser $ \_ -> Left e +{-# INLINE failure #-} + +-- lift a bolt1 field decoder, whose only failure is truncation +field :: (BS.ByteString -> Maybe (a, BS.ByteString)) -> Parser a +field f = Parser $ \bs -> case f bs of + Nothing -> Left DecodeInsufficientBytes + Just r -> Right r +{-# INLINE field #-} + +bytes :: Int -> Parser BS.ByteString +bytes n = Parser $ \bs -> + if BS.length bs < n + then Left DecodeInsufficientBytes + else Right (BU.unsafeTake n bs, BU.unsafeDrop n bs) +{-# INLINE bytes #-} + +-- a fixed-size field whose constructor checks only the length +sized :: Int -> (BS.ByteString -> Maybe a) -> Parser a +sized n f = do + b <- bytes n + maybe (failure DecodeInsufficientBytes) pure (f b) +{-# INLINE sized #-} + +u8 :: Parser Word8 +u8 = Parser $ \bs -> case BS.uncons bs of + Nothing -> Left DecodeInsufficientBytes + Just r -> Right r +{-# INLINE u8 #-} + +u16 :: Parser Word16 +u16 = field BOLT1.decode_u16 +{-# INLINE u16 #-} + +u32 :: Parser Word32 +u32 = field BOLT1.decode_u32 +{-# INLINE u32 #-} + +u64 :: Parser Word64 +u64 = field BOLT1.decode_u64 +{-# INLINE u64 #-} + +chain_hash :: Parser BOLT1.ChainHash +chain_hash = field BOLT1.decode_chain_hash +{-# INLINE chain_hash #-} + +channel_id :: Parser ChannelId +channel_id = field BOLT1.decode_channel_id +{-# INLINE channel_id #-} + +signature :: Parser Signature +signature = field BOLT1.decode_signature +{-# INLINE signature #-} + +payment_hash :: Parser BOLT1.PaymentHash +payment_hash = field BOLT1.decode_payment_hash +{-# INLINE payment_hash #-} + +payment_preimage :: Parser BOLT1.PaymentPreimage +payment_preimage = field BOLT1.decode_payment_preimage +{-# INLINE payment_preimage #-} + +per_commitment_secret :: Parser BOLT1.PerCommitmentSecret +per_commitment_secret = field BOLT1.decode_per_commitment_secret +{-# INLINE per_commitment_secret #-} + +point :: Parser Point +point = do + b <- bytes 33 + maybe (failure DecodeInvalidPoint) pure (BOLT1.point b) +{-# INLINE point #-} + +satoshi :: Parser BOLT1.Satoshi +satoshi = do + w <- u64 + maybe (failure DecodeInvalidAmount) pure (BOLT1.satoshi w) +{-# INLINE satoshi #-} + +milli_satoshi :: Parser BOLT1.MilliSatoshi +milli_satoshi = do + w <- u64 + maybe (failure DecodeInvalidAmount) pure (BOLT1.milli_satoshi w) +{-# INLINE milli_satoshi #-} + +txid :: Parser TxId +txid = sized 32 mkTxId +{-# INLINE txid #-} + +htlc_id :: Parser HtlcId +htlc_id = fmap HtlcId u64 +{-# INLINE htlc_id #-} + +serial_id :: Parser SerialId +serial_id = fmap SerialId u64 +{-# INLINE serial_id #-} + +prefixed :: Parser BS.ByteString +prefixed = field BOLT1.decode_u16_prefixed +{-# INLINE prefixed #-} + +script :: Parser ScriptPubKey +script = do + b <- prefixed + maybe (failure DecodeInsufficientBytes) pure (script_pubkey b) +{-# INLINE script #-} + +-- n items, each parsed by p +count :: Int -> Parser a -> Parser [a] +count n0 p = go n0 [] + where + go !n !acc + | n <= 0 = pure (reverse acc) + | otherwise = p >>= \a -> go (n - 1) (a : acc) +{-# INLINE count #-} + +-- the TLV stream occupying the rest of the payload, given the message's +-- known types +tlvs :: [Word64] -> Parser TlvStream +tlvs known = Parser $ \bs -> + case BOLT1.decode_tlv_stream (`elem` known) bs of + Left e -> Left (DecodeTlvError e) + Right s -> Right (s, BS.empty) +{-# INLINE tlvs #-} + +-- the unknown records of a stream +unknown :: [Word64] -> TlvStream -> TlvStream +unknown known = BOLT1.filter_tlv_stream (`notElem` known) +{-# INLINE unknown #-} + +-- the typed value of a known record, if present +record :: Word64 -> (BS.ByteString -> Maybe a) -> TlvStream -> Parser (Maybe a) +record t f s = case BOLT1.lookup_tlv t s of + Nothing -> pure Nothing + Just v -> maybe (failure (DecodeInvalidTlvValue t)) (pure . Just) (f v) +{-# INLINE record #-} + +-- whether a known record with an empty value is present +flag :: Word64 -> TlvStream -> Parser Bool +flag t s = fmap (maybe False (const True)) (record t empty_value s) + where + empty_value v + | BS.null v = Just () + | otherwise = Nothing +{-# INLINE flag #-} + +-- TLV values ----------------------------------------------------------------- + +-- run a field decoder over a whole TLV value +whole :: (BS.ByteString -> Maybe (a, BS.ByteString)) -> BS.ByteString + -> Maybe a +whole f v = case f v of + Just (a, r) | BS.null r -> Just a + _ -> Nothing +{-# INLINE whole #-} + +tlv_point :: BS.ByteString -> Maybe Point +tlv_point v + | BS.length v == 33 = BOLT1.point v + | otherwise = Nothing + +tlv_s64 :: BS.ByteString -> Maybe Int64 +tlv_s64 = whole BOLT1.decode_s64 + +tlv_fee_range :: BS.ByteString -> Maybe FeeRange +tlv_fee_range v = do + (lo, r0) <- BOLT1.decode_satoshi v + hi <- whole BOLT1.decode_satoshi r0 + pure (FeeRange lo hi) + +tlv_next_funding :: BS.ByteString -> Maybe NextFunding +tlv_next_funding v + | BS.length v == 33 = do + t <- mkTxId (BU.unsafeTake 32 v) + pure (NextFunding t (BU.unsafeIndex v 32)) + | otherwise = Nothing + +-- encoding ------------------------------------------------------------------- + +u16_prefixed :: BS.ByteString -> Either EncodeError BS.ByteString +u16_prefixed bs = maybe (Left EncodeLengthOverflow) Right + (BOLT1.encode_u16_prefixed bs) +{-# INLINE u16_prefixed #-} + +-- scripts are bounded, so their prefix can't overflow +script_bytes :: ScriptPubKey -> BS.ByteString +script_bytes s = + let !b = un_script_pubkey s + in BOLT1.encode_u16 (fromIntegral (BS.length b)) <> b +{-# INLINE script_bytes #-} + +count_prefix :: [a] -> Either EncodeError BS.ByteString +count_prefix xs | n > 65535 = Left EncodeLengthOverflow - | otherwise = Right $! fromIntegral n -{-# INLINE checkListCountU16 #-} - --- | Decode a u16-prefixed byte string. -decodeU16Bytes - :: BS.ByteString -> Either DecodeError (BS.ByteString, BS.ByteString) -decodeU16Bytes !bs = do - (len, rest1) <- decodeU16E bs - let !n = fromIntegral len - unless (BS.length rest1 >= n) $ Left DecodeInsufficientBytes - Right (BS.take n rest1, BS.drop n rest1) -{-# INLINE decodeU16Bytes #-} - --- | Decode optional trailing TLV stream. -decodeOptionalTlvs - :: BS.ByteString -> Either DecodeError (TlvStream, BS.ByteString) -decodeOptionalTlvs !bs - | BS.null bs = Right (unsafeTlvStream [], BS.empty) - | otherwise = case decodeTlvStreamRaw bs of - Left e -> Left (DecodeTlvError e) - Right t -> Right (t, BS.empty) -{-# INLINE decodeOptionalTlvs #-} - --- Channel establishment v1 ---------------------------------------------------- - --- | Encode an OpenChannel message (type 32). --- --- Wire format: --- - chain_hash: 32 bytes --- - temporary_channel_id: 32 bytes --- - funding_satoshis: u64 --- - push_msat: u64 --- - dust_limit_satoshis: u64 --- - max_htlc_value_in_flight_msat: u64 --- - channel_reserve_satoshis: u64 --- - htlc_minimum_msat: u64 --- - feerate_per_kw: u32 --- - to_self_delay: u16 --- - max_accepted_htlcs: u16 --- - funding_pubkey: 33 bytes --- - revocation_basepoint: 33 bytes --- - payment_basepoint: 33 bytes --- - delayed_payment_basepoint: 33 bytes --- - htlc_basepoint: 33 bytes --- - first_per_commitment_point: 33 bytes --- - channel_flags: 1 byte --- - tlvs: TLV stream -encodeOpenChannel :: OpenChannel -> BS.ByteString -encodeOpenChannel !msg = mconcat - [ unChainHash (openChannelChainHash msg) - , unChannelId (openChannelTempChannelId msg) - , encodeU64 (unSatoshi (openChannelFundingSatoshi msg)) - , encodeU64 (unMilliSatoshi (openChannelPushMsat msg)) - , encodeU64 (unSatoshi (openChannelDustLimitSatoshi msg)) - , encodeU64 (unMilliSatoshi (openChannelMaxHtlcValueInFlight msg)) - , encodeU64 (unSatoshi (openChannelChannelReserveSat msg)) - , encodeU64 (unMilliSatoshi (openChannelHtlcMinimumMsat msg)) - , encodeU32 (openChannelFeeratePerKw msg) - , encodeU16 (openChannelToSelfDelay msg) - , encodeU16 (openChannelMaxAcceptedHtlcs msg) - , unPoint (openChannelFundingPubkey msg) - , unPoint (openChannelRevocationBasepoint msg) - , unPoint (openChannelPaymentBasepoint msg) - , unPoint (openChannelDelayedPaymentBase msg) - , unPoint (openChannelHtlcBasepoint msg) - , unPoint (openChannelFirstPerCommitPoint msg) - , BS.singleton (openChannelChannelFlags msg) - , encodeTlvStream (openChannelTlvs msg) - ] + | otherwise = Right (BOLT1.encode_u16 (fromIntegral n)) + where + !n = length xs +{-# INLINE count_prefix #-} + +txid_bytes :: TxId -> BS.ByteString +txid_bytes (TxId b) = b +{-# INLINE txid_bytes #-} + +word8 :: Word8 -> BS.ByteString +word8 = BS.singleton +{-# INLINE word8 #-} + +-- encode a message's TLV stream: its typed records plus the unknown +-- ones, which mustn't use a known type +encode_tlvs + :: [Word64] -> [TlvRecord] -> TlvStream -> Either EncodeError BS.ByteString +encode_tlvs known recs extra + | any ((`elem` known) . tlv_type) others = Left EncodeInvalidTlvs + | otherwise = case BOLT1.tlv_stream (recs <> others) of + Nothing -> Left EncodeInvalidTlvs + Just s -> Right (BOLT1.encode_tlv_stream s) + where + others = BOLT1.un_tlv_stream extra +{-# INLINE encode_tlvs #-} --- | Decode an OpenChannel message (type 32). -decodeOpenChannel - :: BS.ByteString -> Either DecodeError (OpenChannel, BS.ByteString) -decodeOpenChannel !bs = do - (chainHash', rest1) <- decodeChainHashBytes bs - (tempChanId, rest2) <- decodeChannelIdBytes rest1 - (fundingSats, rest3) <- decodeSatoshi rest2 - (pushMsat, rest4) <- decodeMilliSatoshi rest3 - (dustLimit, rest5) <- decodeSatoshi rest4 - (maxHtlcVal, rest6) <- decodeMilliSatoshi rest5 - (chanReserve, rest7) <- decodeSatoshi rest6 - (htlcMin, rest8) <- decodeMilliSatoshi rest7 - (feerate, rest9) <- decodeU32E rest8 - (toSelfDelay, rest10) <- decodeU16E rest9 - (maxHtlcs, rest11) <- decodeU16E rest10 - (fundingPk, rest12) <- decodePointBytes rest11 - (revocBase, rest13) <- decodePointBytes rest12 - (paymentBase, rest14) <- decodePointBytes rest13 - (delayedBase, rest15) <- decodePointBytes rest14 - (htlcBase, rest16) <- decodePointBytes rest15 - (firstCommit, rest17) <- decodePointBytes rest16 - (flags, rest18) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU8 rest17) - tlvs <- decodeTlvs rest18 - let !msg = OpenChannel - { openChannelChainHash = chainHash' - , openChannelTempChannelId = tempChanId - , openChannelFundingSatoshi = fundingSats - , openChannelPushMsat = pushMsat - , openChannelDustLimitSatoshi = dustLimit - , openChannelMaxHtlcValueInFlight = maxHtlcVal - , openChannelChannelReserveSat = chanReserve - , openChannelHtlcMinimumMsat = htlcMin - , openChannelFeeratePerKw = feerate - , openChannelToSelfDelay = toSelfDelay - , openChannelMaxAcceptedHtlcs = maxHtlcs - , openChannelFundingPubkey = fundingPk - , openChannelRevocationBasepoint = revocBase - , openChannelPaymentBasepoint = paymentBase - , openChannelDelayedPaymentBase = delayedBase - , openChannelHtlcBasepoint = htlcBase - , openChannelFirstPerCommitPoint = firstCommit - , openChannelChannelFlags = flags - , openChannelTlvs = tlvs - } - Right (msg, BS.empty) - --- | Encode an AcceptChannel message (type 33). --- --- Wire format: --- - temporary_channel_id: 32 bytes --- - dust_limit_satoshis: u64 --- - max_htlc_value_in_flight_msat: u64 --- - channel_reserve_satoshis: u64 --- - htlc_minimum_msat: u64 --- - minimum_depth: u32 --- - to_self_delay: u16 --- - max_accepted_htlcs: u16 --- - funding_pubkey: 33 bytes --- - revocation_basepoint: 33 bytes --- - payment_basepoint: 33 bytes --- - delayed_payment_basepoint: 33 bytes --- - htlc_basepoint: 33 bytes --- - first_per_commitment_point: 33 bytes --- - tlvs: TLV stream -encodeAcceptChannel :: AcceptChannel -> BS.ByteString -encodeAcceptChannel !msg = mconcat - [ unChannelId (acceptChannelTempChannelId msg) - , encodeU64 (unSatoshi (acceptChannelDustLimitSatoshi msg)) - , encodeU64 (unMilliSatoshi (acceptChannelMaxHtlcValueInFlight msg)) - , encodeU64 (unSatoshi (acceptChannelChannelReserveSat msg)) - , encodeU64 (unMilliSatoshi (acceptChannelHtlcMinimumMsat msg)) - , encodeU32 (acceptChannelMinimumDepth msg) - , encodeU16 (acceptChannelToSelfDelay msg) - , encodeU16 (acceptChannelMaxAcceptedHtlcs msg) - , unPoint (acceptChannelFundingPubkey msg) - , unPoint (acceptChannelRevocationBasepoint msg) - , unPoint (acceptChannelPaymentBasepoint msg) - , unPoint (acceptChannelDelayedPaymentBase msg) - , unPoint (acceptChannelHtlcBasepoint msg) - , unPoint (acceptChannelFirstPerCommitPoint msg) - , encodeTlvStream (acceptChannelTlvs msg) - ] +opt :: Word64 -> (a -> BS.ByteString) -> Maybe a -> [TlvRecord] +opt t f = maybe [] (\a -> [TlvRecord t (f a)]) +{-# INLINE opt #-} --- | Decode an AcceptChannel message (type 33). -decodeAcceptChannel - :: BS.ByteString -> Either DecodeError (AcceptChannel, BS.ByteString) -decodeAcceptChannel !bs = do - (tempChanId, rest1) <- decodeChannelIdBytes bs - (dustLimit, rest2) <- decodeSatoshi rest1 - (maxHtlcVal, rest3) <- decodeMilliSatoshi rest2 - (chanReserve, rest4) <- decodeSatoshi rest3 - (htlcMin, rest5) <- decodeMilliSatoshi rest4 - (minDepth, rest6) <- decodeU32E rest5 - (toSelfDelay, rest7) <- decodeU16E rest6 - (maxHtlcs, rest8) <- decodeU16E rest7 - (fundingPk, rest9) <- decodePointBytes rest8 - (revocBase, rest10) <- decodePointBytes rest9 - (paymentBase, rest11) <- decodePointBytes rest10 - (delayedBase, rest12) <- decodePointBytes rest11 - (htlcBase, rest13) <- decodePointBytes rest12 - (firstCommit, rest14) <- decodePointBytes rest13 - tlvs <- decodeTlvs rest14 - let !msg = AcceptChannel - { acceptChannelTempChannelId = tempChanId - , acceptChannelDustLimitSatoshi = dustLimit - , acceptChannelMaxHtlcValueInFlight = maxHtlcVal - , acceptChannelChannelReserveSat = chanReserve - , acceptChannelHtlcMinimumMsat = htlcMin - , acceptChannelMinimumDepth = minDepth - , acceptChannelToSelfDelay = toSelfDelay - , acceptChannelMaxAcceptedHtlcs = maxHtlcs - , acceptChannelFundingPubkey = fundingPk - , acceptChannelRevocationBasepoint = revocBase - , acceptChannelPaymentBasepoint = paymentBase - , acceptChannelDelayedPaymentBase = delayedBase - , acceptChannelHtlcBasepoint = htlcBase - , acceptChannelFirstPerCommitPoint = firstCommit - , acceptChannelTlvs = tlvs - } - Right (msg, BS.empty) - --- | Encode a FundingCreated message (type 34). --- --- Wire format: --- - temporary_channel_id: 32 bytes --- - funding_txid: 32 bytes --- - funding_output_index: u16 --- - signature: 64 bytes -encodeFundingCreated :: FundingCreated -> BS.ByteString -encodeFundingCreated !msg = mconcat - [ unChannelId (fundingCreatedTempChannelId msg) - , let (TxId bs) = fundingCreatedFundingTxid msg in bs - , encodeU16 (fundingCreatedFundingOutIdx msg) - , unSignature (fundingCreatedSignature msg) - ] +opt_flag :: Word64 -> Bool -> [TlvRecord] +opt_flag t b = if b then [TlvRecord t BS.empty] else [] +{-# INLINE opt_flag #-} --- | Decode a FundingCreated message (type 34). -decodeFundingCreated - :: BS.ByteString -> Either DecodeError (FundingCreated, BS.ByteString) -decodeFundingCreated !bs = do - (tempChanId, rest1) <- decodeChannelIdBytes bs - (fundingTxid, rest2) <- decodeTxIdBytes rest1 - (outIdx, rest3) <- decodeU16E rest2 - (sig, rest4) <- decodeSignatureBytes rest3 - let !msg = FundingCreated - { fundingCreatedTempChannelId = tempChanId - , fundingCreatedFundingTxid = fundingTxid - , fundingCreatedFundingOutIdx = outIdx - , fundingCreatedSignature = sig - } - Right (msg, rest4) - --- | Encode a FundingSigned message (type 35). --- --- Wire format: --- - channel_id: 32 bytes --- - signature: 64 bytes -encodeFundingSigned :: FundingSigned -> BS.ByteString -encodeFundingSigned !msg = mconcat - [ unChannelId (fundingSignedChannelId msg) - , unSignature (fundingSignedSignature msg) - ] +fee_range_bytes :: FeeRange -> BS.ByteString +fee_range_bytes (FeeRange lo hi) = + BOLT1.encode_satoshi lo <> BOLT1.encode_satoshi hi --- | Decode a FundingSigned message (type 35). -decodeFundingSigned - :: BS.ByteString -> Either DecodeError (FundingSigned, BS.ByteString) -decodeFundingSigned !bs = do - (chanId, rest1) <- decodeChannelIdBytes bs - (sig, rest2) <- decodeSignatureBytes rest1 - let !msg = FundingSigned - { fundingSignedChannelId = chanId - , fundingSignedSignature = sig - } - Right (msg, rest2) - --- | Encode a ChannelReady message (type 36). --- --- Wire format: --- - channel_id: 32 bytes --- - second_per_commitment_point: 33 bytes --- - tlvs: TLV stream -encodeChannelReady :: ChannelReady -> BS.ByteString -encodeChannelReady !msg = mconcat - [ unChannelId (channelReadyChannelId msg) - , unPoint (channelReadySecondPerCommitPoint msg) - , encodeTlvStream (channelReadyTlvs msg) - ] +next_funding_bytes :: NextFunding -> BS.ByteString +next_funding_bytes (NextFunding t f) = txid_bytes t <> word8 f --- | Decode a ChannelReady message (type 36). -decodeChannelReady - :: BS.ByteString -> Either DecodeError (ChannelReady, BS.ByteString) -decodeChannelReady !bs = do - (chanId, rest1) <- decodeChannelIdBytes bs - (secondCommit, rest2) <- decodePointBytes rest1 - tlvs <- decodeTlvs rest2 - let !msg = ChannelReady - { channelReadyChannelId = chanId - , channelReadySecondPerCommitPoint = secondCommit - , channelReadyTlvs = tlvs - } - Right (msg, BS.empty) - --- Channel close --------------------------------------------------------------- - --- | Encode a Stfu message (type 2). --- --- Wire format: --- - channel_id: 32 bytes --- - initiator: 1 byte -encodeStfu :: Stfu -> BS.ByteString -encodeStfu !msg = mconcat - [ unChannelId (stfuChannelId msg) - , BS.singleton $! case stfuInitiator msg of - IsInitiator -> 1 - NotInitiator -> 0 - ] +-- messages ------------------------------------------------------------------- --- | Decode a Stfu message (type 2). -decodeStfu :: BS.ByteString -> Either DecodeError (Stfu, BS.ByteString) -decodeStfu !bs = do - (chanId, rest1) <- decodeChannelIdBytes bs - (raw, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU8 rest1) - ini <- case raw of - 1 -> Right IsInitiator - 0 -> Right NotInitiator - _ -> Left DecodeInvalidInitiator - let !msg = Stfu - { stfuChannelId = chanId - , stfuInitiator = ini - } - Right (msg, rest2) - --- | Encode a Shutdown message (type 38). --- --- Wire format: --- - channel_id: 32 bytes --- - len: u16 --- - scriptpubkey: len bytes -encodeShutdown :: Shutdown -> Either EncodeError BS.ByteString -encodeShutdown !msg = do - let !script = unScriptPubKey (shutdownScriptPubkey msg) - !scriptLen = BS.length script - if scriptLen > 65535 - then Left EncodeLengthOverflow - else Right $ mconcat - [ unChannelId (shutdownChannelId msg) - , encodeU16 (fromIntegral scriptLen) - , script - ] - --- | Decode a Shutdown message (type 38). -decodeShutdown - :: BS.ByteString -> Either DecodeError (Shutdown, BS.ByteString) -decodeShutdown !bs = do - (chanId, rest1) <- decodeChannelIdBytes bs - (script, rest2) <- decodeScriptPubKey rest1 - let !msg = Shutdown - { shutdownChannelId = chanId - , shutdownScriptPubkey = script - } - Right (msg, rest2) - --- | Encode a ClosingSigned message (type 39). +-- | Encode a BOLT #2 message, including its type. Fails if a field +-- can't be encoded or the result exceeds 65535 bytes. -- --- Wire format: --- - channel_id: 32 bytes --- - fee_satoshis: u64 --- - signature: 64 bytes --- - tlvs: TLV stream -encodeClosingSigned :: ClosingSigned -> BS.ByteString -encodeClosingSigned !msg = mconcat - [ unChannelId (closingSignedChannelId msg) - , encodeU64 (unSatoshi (closingSignedFeeSatoshi msg)) - , unSignature (closingSignedSignature msg) - , encodeTlvStream (closingSignedTlvs msg) - ] - --- | Decode a ClosingSigned message (type 39). -decodeClosingSigned - :: BS.ByteString -> Either DecodeError (ClosingSigned, BS.ByteString) -decodeClosingSigned !bs = do - (chanId, rest1) <- decodeChannelIdBytes bs - (feeSats, rest2) <- decodeSatoshi rest1 - (sig, rest3) <- decodeSignatureBytes rest2 - tlvs <- decodeTlvs rest3 - let !msg = ClosingSigned - { closingSignedChannelId = chanId - , closingSignedFeeSatoshi = feeSats - , closingSignedSignature = sig - , closingSignedTlvs = tlvs - } - Right (msg, BS.empty) - --- | Encode a ClosingComplete message (type 40). +-- >>> let stfu = Stfu cid True BOLT1.empty_tlv_stream +-- >>> fmap BS.length (encode_message (MsgStfu stfu)) +-- Right 35 +encode_message :: Message -> Either EncodeError BS.ByteString +encode_message m = do + payload <- case m of + MsgOpenChannel a -> encode_open_channel a + MsgAcceptChannel a -> encode_accept_channel a + MsgFundingCreated a -> Right (encode_funding_created a) + MsgFundingSigned a -> Right (encode_funding_signed a) + MsgChannelReady a -> encode_channel_ready a + MsgOpenChannel2 a -> encode_open_channel2 a + MsgAcceptChannel2 a -> encode_accept_channel2 a + MsgTxAddInput a -> encode_tx_add_input a + MsgTxAddOutput a -> Right (encode_tx_add_output a) + MsgTxRemoveInput a -> Right (encode_tx_remove_input a) + MsgTxRemoveOutput a -> Right (encode_tx_remove_output a) + MsgTxComplete a -> Right (encode_tx_complete a) + MsgTxSignatures a -> encode_tx_signatures a + MsgTxInitRbf a -> encode_tx_init_rbf a + MsgTxAckRbf a -> encode_tx_ack_rbf a + MsgTxAbort a -> encode_tx_abort a + MsgStfu a -> Right (encode_stfu a) + MsgShutdown a -> Right (encode_shutdown a) + MsgClosingComplete a -> encode_closing_complete a + MsgClosingSig a -> encode_closing_sig a + MsgClosingSigned a -> encode_closing_signed a + MsgUpdateAddHtlc a -> encode_update_add_htlc a + MsgUpdateFulfillHtlc a -> encode_update_fulfill_htlc a + MsgUpdateFailHtlc a -> encode_update_fail_htlc a + MsgUpdateFailMalformedHtlc a -> + Right (encode_update_fail_malformed_htlc a) + MsgCommitmentSigned a -> encode_commitment_signed a + MsgRevokeAndAck a -> Right (encode_revoke_and_ack a) + MsgUpdateFee a -> Right (encode_update_fee a) + MsgChannelReestablish a -> encode_channel_reestablish a + case BOLT1.encode_envelope (message_type m) payload of + Left _ -> Left EncodeMessageTooLarge + Right w -> Right w + +-- | Decode a BOLT #2 message, including its type. -- --- Wire format: --- - channel_id: 32 bytes --- - len: u16 (closer script length) --- - closer_script: len bytes --- - len: u16 (closee script length) --- - closee_script: len bytes --- - fee_satoshis: u64 --- - locktime: u32 --- - tlvs: TLV stream -encodeClosingComplete :: ClosingComplete -> Either EncodeError BS.ByteString -encodeClosingComplete !msg = do - let !closerScript = unScriptPubKey (closingCompleteCloserScript msg) - !closeeScript = unScriptPubKey (closingCompleteCloseeScript msg) - !closerLen = BS.length closerScript - !closeeLen = BS.length closeeScript - if closerLen > 65535 || closeeLen > 65535 - then Left EncodeLengthOverflow - else Right $ mconcat - [ unChannelId (closingCompleteChannelId msg) - , encodeU16 (fromIntegral closerLen) - , closerScript - , encodeU16 (fromIntegral closeeLen) - , closeeScript - , encodeU64 (unSatoshi (closingCompleteFeeSatoshi msg)) - , encodeU32 (closingCompleteLocktime msg) - , encodeTlvStream (closingCompleteTlvs msg) - ] - --- | Decode a ClosingComplete message (type 40). -decodeClosingComplete - :: BS.ByteString -> Either DecodeError (ClosingComplete, BS.ByteString) -decodeClosingComplete !bs = do - (chanId, rest1) <- decodeChannelIdBytes bs - (closerScript, rest2) <- decodeScriptPubKey rest1 - (closeeScript, rest3) <- decodeScriptPubKey rest2 - (feeSats, rest4) <- decodeSatoshi rest3 - (locktime, rest5) <- decodeU32E rest4 - tlvs <- decodeTlvs rest5 - let !msg = ClosingComplete - { closingCompleteChannelId = chanId - , closingCompleteCloserScript = closerScript - , closingCompleteCloseeScript = closeeScript - , closingCompleteFeeSatoshi = feeSats - , closingCompleteLocktime = locktime - , closingCompleteTlvs = tlvs - } - Right (msg, BS.empty) - --- | Encode a ClosingSig message (type 41). +-- A type that BOLT #2 doesn't define (including the splicing +-- messages, which this library doesn't implement) yields +-- 'DecodeUnknownEvenType' or 'DecodeUnknownOddType'. -- --- Wire format: --- - channel_id: 32 bytes --- - len: u16 (closer script length) --- - closer_script: len bytes --- - len: u16 (closee script length) --- - closee_script: len bytes --- - fee_satoshis: u64 --- - locktime: u32 --- - tlvs: TLV stream -encodeClosingSig :: ClosingSig -> Either EncodeError BS.ByteString -encodeClosingSig !msg = do - let !closerScript = unScriptPubKey (closingSigCloserScript msg) - !closeeScript = unScriptPubKey (closingSigCloseeScript msg) - !closerLen = BS.length closerScript - !closeeLen = BS.length closeeScript - if closerLen > 65535 || closeeLen > 65535 - then Left EncodeLengthOverflow - else Right $ mconcat - [ unChannelId (closingSigChannelId msg) - , encodeU16 (fromIntegral closerLen) - , closerScript - , encodeU16 (fromIntegral closeeLen) - , closeeScript - , encodeU64 (unSatoshi (closingSigFeeSatoshi msg)) - , encodeU32 (closingSigLocktime msg) - , encodeTlvStream (closingSigTlvs msg) - ] - --- | Decode a ClosingSig message (type 41). -decodeClosingSig - :: BS.ByteString -> Either DecodeError (ClosingSig, BS.ByteString) -decodeClosingSig !bs = do - (chanId, rest1) <- decodeChannelIdBytes bs - (closerScript, rest2) <- decodeScriptPubKey rest1 - (closeeScript, rest3) <- decodeScriptPubKey rest2 - (feeSats, rest4) <- decodeSatoshi rest3 - (locktime, rest5) <- decodeU32E rest4 - tlvs <- decodeTlvs rest5 - let !msg = ClosingSig - { closingSigChannelId = chanId - , closingSigCloserScript = closerScript - , closingSigCloseeScript = closeeScript - , closingSigFeeSatoshi = feeSats - , closingSigLocktime = locktime - , closingSigTlvs = tlvs - } - Right (msg, BS.empty) - --- Channel establishment v2 (interactive-tx) ----------------------------------- - --- | Encode an OpenChannel2 message (type 64). -encodeOpenChannel2 :: OpenChannel2 -> BS.ByteString -encodeOpenChannel2 !msg = mconcat - [ unChainHash (openChannel2ChainHash msg) - , unChannelId (openChannel2TempChannelId msg) - , encodeU32 (openChannel2FundingFeeratePerkw msg) - , encodeU32 (openChannel2CommitFeeratePerkw msg) - , encodeU64 (unSatoshi (openChannel2FundingSatoshi msg)) - , encodeU64 (unSatoshi (openChannel2DustLimitSatoshi msg)) - , encodeU64 (unMilliSatoshi (openChannel2MaxHtlcValueInFlight msg)) - , encodeU64 (unMilliSatoshi (openChannel2HtlcMinimumMsat msg)) - , encodeU16 (openChannel2ToSelfDelay msg) - , encodeU16 (openChannel2MaxAcceptedHtlcs msg) - , encodeU32 (openChannel2Locktime msg) - , unPoint (openChannel2FundingPubkey msg) - , unPoint (openChannel2RevocationBasepoint msg) - , unPoint (openChannel2PaymentBasepoint msg) - , unPoint (openChannel2DelayedPaymentBase msg) - , unPoint (openChannel2HtlcBasepoint msg) - , unPoint (openChannel2FirstPerCommitPoint msg) - , unPoint (openChannel2SecondPerCommitPoint msg) - , BS.singleton (openChannel2ChannelFlags msg) - , encodeTlvStream (openChannel2Tlvs msg) +-- >>> let wire = "\x00\x02" <> BS.replicate 32 0xab <> "\x01" +-- >>> fmap message_type (decode_message wire) +-- Right 2 +-- >>> decode_message "\x00\x50" +-- Left (DecodeUnknownEvenType 80) +decode_message :: BS.ByteString -> Either DecodeError Message +decode_message bs = do + (t, payload) <- case BOLT1.decode_envelope bs of + Left _ -> Left DecodeInsufficientBytes + Right r -> Right r + case t of + 32 -> MsgOpenChannel <$> decode_open_channel payload + 33 -> MsgAcceptChannel <$> decode_accept_channel payload + 34 -> MsgFundingCreated <$> decode_funding_created payload + 35 -> MsgFundingSigned <$> decode_funding_signed payload + 36 -> MsgChannelReady <$> decode_channel_ready payload + 64 -> MsgOpenChannel2 <$> decode_open_channel2 payload + 65 -> MsgAcceptChannel2 <$> decode_accept_channel2 payload + 66 -> MsgTxAddInput <$> decode_tx_add_input payload + 67 -> MsgTxAddOutput <$> decode_tx_add_output payload + 68 -> MsgTxRemoveInput <$> decode_tx_remove_input payload + 69 -> MsgTxRemoveOutput <$> decode_tx_remove_output payload + 70 -> MsgTxComplete <$> decode_tx_complete payload + 71 -> MsgTxSignatures <$> decode_tx_signatures payload + 72 -> MsgTxInitRbf <$> decode_tx_init_rbf payload + 73 -> MsgTxAckRbf <$> decode_tx_ack_rbf payload + 74 -> MsgTxAbort <$> decode_tx_abort payload + 2 -> MsgStfu <$> decode_stfu payload + 38 -> MsgShutdown <$> decode_shutdown payload + 40 -> MsgClosingComplete <$> decode_closing_complete payload + 41 -> MsgClosingSig <$> decode_closing_sig payload + 39 -> MsgClosingSigned <$> decode_closing_signed payload + 128 -> MsgUpdateAddHtlc <$> decode_update_add_htlc payload + 130 -> MsgUpdateFulfillHtlc <$> decode_update_fulfill_htlc payload + 131 -> MsgUpdateFailHtlc <$> decode_update_fail_htlc payload + 135 -> MsgUpdateFailMalformedHtlc <$> + decode_update_fail_malformed_htlc payload + 132 -> MsgCommitmentSigned <$> decode_commitment_signed payload + 133 -> MsgRevokeAndAck <$> decode_revoke_and_ack payload + 134 -> MsgUpdateFee <$> decode_update_fee payload + 136 -> MsgChannelReestablish <$> decode_channel_reestablish payload + _ | even t -> Left (DecodeUnknownEvenType t) + | otherwise -> Left (DecodeUnknownOddType t) + +-- open_channel --------------------------------------------------------------- + +open_channel_known :: [Word64] +open_channel_known = [0, 1] + +-- | Encode an 'OpenChannel' payload. +encode_open_channel :: OpenChannel -> Either EncodeError BS.ByteString +encode_open_channel m = do + ts <- encode_tlvs open_channel_known + ( opt 0 un_script_pubkey (open_channel_upfront_shutdown_script m) + <> opt 1 BOLT9.render (open_channel_channel_type m) ) + (open_channel_tlvs m) + pure $ mconcat [ + BOLT1.un_chain_hash (open_channel_chain_hash m) + , BOLT1.un_channel_id (open_channel_temporary_channel_id m) + , BOLT1.encode_satoshi (open_channel_funding_satoshis m) + , BOLT1.encode_milli_satoshi (open_channel_push_msat m) + , BOLT1.encode_satoshi (open_channel_dust_limit_satoshis m) + , BOLT1.encode_u64 (open_channel_max_htlc_value_in_flight_msat m) + , BOLT1.encode_satoshi (open_channel_channel_reserve_satoshis m) + , BOLT1.encode_milli_satoshi (open_channel_htlc_minimum_msat m) + , BOLT1.encode_u32 (open_channel_feerate_per_kw m) + , BOLT1.encode_u16 (open_channel_to_self_delay m) + , BOLT1.encode_u16 (open_channel_max_accepted_htlcs m) + , BOLT1.un_point (open_channel_funding_pubkey m) + , BOLT1.un_point (open_channel_revocation_basepoint m) + , BOLT1.un_point (open_channel_payment_basepoint m) + , BOLT1.un_point (open_channel_delayed_payment_basepoint m) + , BOLT1.un_point (open_channel_htlc_basepoint m) + , BOLT1.un_point (open_channel_first_per_commitment_point m) + , word8 (open_channel_channel_flags m) + , ts + ] + +-- | Decode an 'OpenChannel' payload. +decode_open_channel :: BS.ByteString -> Either DecodeError OpenChannel +decode_open_channel = parse $ do + ch <- chain_hash + tcid <- channel_id + fund <- satoshi + push <- milli_satoshi + dust <- satoshi + maxv <- u64 + res <- satoshi + hmin <- milli_satoshi + fee <- u32 + delay <- u16 + maxa <- u16 + fpk <- point + rev <- point + pay <- point + del <- point + htlc <- point + pcp <- point + flags <- u8 + s <- tlvs open_channel_known + shut <- record 0 script_pubkey s + ctype <- record 1 (Just . BOLT9.parse) s + pure OpenChannel { + open_channel_chain_hash = ch + , open_channel_temporary_channel_id = tcid + , open_channel_funding_satoshis = fund + , open_channel_push_msat = push + , open_channel_dust_limit_satoshis = dust + , open_channel_max_htlc_value_in_flight_msat = maxv + , open_channel_channel_reserve_satoshis = res + , open_channel_htlc_minimum_msat = hmin + , open_channel_feerate_per_kw = fee + , open_channel_to_self_delay = delay + , open_channel_max_accepted_htlcs = maxa + , open_channel_funding_pubkey = fpk + , open_channel_revocation_basepoint = rev + , open_channel_payment_basepoint = pay + , open_channel_delayed_payment_basepoint = del + , open_channel_htlc_basepoint = htlc + , open_channel_first_per_commitment_point = pcp + , open_channel_channel_flags = flags + , open_channel_upfront_shutdown_script = shut + , open_channel_channel_type = ctype + , open_channel_tlvs = unknown open_channel_known s + } + +-- accept_channel ------------------------------------------------------------- + +accept_channel_known :: [Word64] +accept_channel_known = [0, 1] + +-- | Encode an 'AcceptChannel' payload. +encode_accept_channel :: AcceptChannel -> Either EncodeError BS.ByteString +encode_accept_channel m = do + ts <- encode_tlvs accept_channel_known + ( opt 0 un_script_pubkey (accept_channel_upfront_shutdown_script m) + <> opt 1 BOLT9.render (accept_channel_channel_type m) ) + (accept_channel_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (accept_channel_temporary_channel_id m) + , BOLT1.encode_satoshi (accept_channel_dust_limit_satoshis m) + , BOLT1.encode_u64 (accept_channel_max_htlc_value_in_flight_msat m) + , BOLT1.encode_satoshi (accept_channel_channel_reserve_satoshis m) + , BOLT1.encode_milli_satoshi (accept_channel_htlc_minimum_msat m) + , BOLT1.encode_u32 (accept_channel_minimum_depth m) + , BOLT1.encode_u16 (accept_channel_to_self_delay m) + , BOLT1.encode_u16 (accept_channel_max_accepted_htlcs m) + , BOLT1.un_point (accept_channel_funding_pubkey m) + , BOLT1.un_point (accept_channel_revocation_basepoint m) + , BOLT1.un_point (accept_channel_payment_basepoint m) + , BOLT1.un_point (accept_channel_delayed_payment_basepoint m) + , BOLT1.un_point (accept_channel_htlc_basepoint m) + , BOLT1.un_point (accept_channel_first_per_commitment_point m) + , ts + ] + +-- | Decode an 'AcceptChannel' payload. +decode_accept_channel :: BS.ByteString -> Either DecodeError AcceptChannel +decode_accept_channel = parse $ do + tcid <- channel_id + dust <- satoshi + maxv <- u64 + res <- satoshi + hmin <- milli_satoshi + depth <- u32 + delay <- u16 + maxa <- u16 + fpk <- point + rev <- point + pay <- point + del <- point + htlc <- point + pcp <- point + s <- tlvs accept_channel_known + shut <- record 0 script_pubkey s + ctype <- record 1 (Just . BOLT9.parse) s + pure AcceptChannel { + accept_channel_temporary_channel_id = tcid + , accept_channel_dust_limit_satoshis = dust + , accept_channel_max_htlc_value_in_flight_msat = maxv + , accept_channel_channel_reserve_satoshis = res + , accept_channel_htlc_minimum_msat = hmin + , accept_channel_minimum_depth = depth + , accept_channel_to_self_delay = delay + , accept_channel_max_accepted_htlcs = maxa + , accept_channel_funding_pubkey = fpk + , accept_channel_revocation_basepoint = rev + , accept_channel_payment_basepoint = pay + , accept_channel_delayed_payment_basepoint = del + , accept_channel_htlc_basepoint = htlc + , accept_channel_first_per_commitment_point = pcp + , accept_channel_upfront_shutdown_script = shut + , accept_channel_channel_type = ctype + , accept_channel_tlvs = unknown accept_channel_known s + } + +-- funding_created, funding_signed -------------------------------------------- + +-- | Encode a 'FundingCreated' payload. +encode_funding_created :: FundingCreated -> BS.ByteString +encode_funding_created m = mconcat [ + BOLT1.un_channel_id (funding_created_temporary_channel_id m) + , txid_bytes (funding_created_funding_txid m) + , BOLT1.encode_u16 (funding_created_funding_output_index m) + , BOLT1.un_signature (funding_created_signature m) + , BOLT1.encode_tlv_stream (funding_created_tlvs m) ] --- | Decode an OpenChannel2 message (type 64). -decodeOpenChannel2 - :: BS.ByteString -> Either DecodeError (OpenChannel2, BS.ByteString) -decodeOpenChannel2 !bs = do - (ch, rest1) <- decodeChainHashBytes bs - (tempCid, rest2) <- decodeChannelIdBytes rest1 - (fundingFeerate, rest3) <- decodeU32E rest2 - (commitFeerate, rest4) <- decodeU32E rest3 - (fundingSats, rest5) <- decodeSatoshi rest4 - (dustLimit, rest6) <- decodeSatoshi rest5 - (maxHtlcVal, rest7) <- decodeMilliSatoshi rest6 - (htlcMin, rest8) <- decodeMilliSatoshi rest7 - (toSelfDelay, rest9) <- decodeU16E rest8 - (maxHtlcs, rest10) <- decodeU16E rest9 - (locktime, rest11) <- decodeU32E rest10 - (fundingPk, rest12) <- decodePointBytes rest11 - (revBase, rest13) <- decodePointBytes rest12 - (payBase, rest14) <- decodePointBytes rest13 - (delayBase, rest15) <- decodePointBytes rest14 - (htlcBase, rest16) <- decodePointBytes rest15 - (firstPt, rest17) <- decodePointBytes rest16 - (secondPt, rest18) <- decodePointBytes rest17 - (flags, rest19) <- maybe (Left DecodeInsufficientBytes) Right (decodeU8 rest18) - tlvs <- decodeTlvs rest19 - let !msg = OpenChannel2 - { openChannel2ChainHash = ch - , openChannel2TempChannelId = tempCid - , openChannel2FundingFeeratePerkw = fundingFeerate - , openChannel2CommitFeeratePerkw = commitFeerate - , openChannel2FundingSatoshi = fundingSats - , openChannel2DustLimitSatoshi = dustLimit - , openChannel2MaxHtlcValueInFlight = maxHtlcVal - , openChannel2HtlcMinimumMsat = htlcMin - , openChannel2ToSelfDelay = toSelfDelay - , openChannel2MaxAcceptedHtlcs = maxHtlcs - , openChannel2Locktime = locktime - , openChannel2FundingPubkey = fundingPk - , openChannel2RevocationBasepoint = revBase - , openChannel2PaymentBasepoint = payBase - , openChannel2DelayedPaymentBase = delayBase - , openChannel2HtlcBasepoint = htlcBase - , openChannel2FirstPerCommitPoint = firstPt - , openChannel2SecondPerCommitPoint = secondPt - , openChannel2ChannelFlags = flags - , openChannel2Tlvs = tlvs - } - Right (msg, BS.empty) - --- | Encode an AcceptChannel2 message (type 65). -encodeAcceptChannel2 :: AcceptChannel2 -> BS.ByteString -encodeAcceptChannel2 !msg = mconcat - [ unChannelId (acceptChannel2TempChannelId msg) - , encodeU64 (unSatoshi (acceptChannel2FundingSatoshi msg)) - , encodeU64 (unSatoshi (acceptChannel2DustLimitSatoshi msg)) - , encodeU64 (unMilliSatoshi (acceptChannel2MaxHtlcValueInFlight msg)) - , encodeU64 (unMilliSatoshi (acceptChannel2HtlcMinimumMsat msg)) - , encodeU32 (acceptChannel2MinimumDepth msg) - , encodeU16 (acceptChannel2ToSelfDelay msg) - , encodeU16 (acceptChannel2MaxAcceptedHtlcs msg) - , unPoint (acceptChannel2FundingPubkey msg) - , unPoint (acceptChannel2RevocationBasepoint msg) - , unPoint (acceptChannel2PaymentBasepoint msg) - , unPoint (acceptChannel2DelayedPaymentBase msg) - , unPoint (acceptChannel2HtlcBasepoint msg) - , unPoint (acceptChannel2FirstPerCommitPoint msg) - , unPoint (acceptChannel2SecondPerCommitPoint msg) - , encodeTlvStream (acceptChannel2Tlvs msg) +-- | Decode a 'FundingCreated' payload. +decode_funding_created :: BS.ByteString -> Either DecodeError FundingCreated +decode_funding_created = parse $ + FundingCreated <$> channel_id <*> txid <*> u16 <*> signature <*> tlvs [] + +-- | Encode a 'FundingSigned' payload. +encode_funding_signed :: FundingSigned -> BS.ByteString +encode_funding_signed m = mconcat [ + BOLT1.un_channel_id (funding_signed_channel_id m) + , BOLT1.un_signature (funding_signed_signature m) + , BOLT1.encode_tlv_stream (funding_signed_tlvs m) ] --- | Decode an AcceptChannel2 message (type 65). -decodeAcceptChannel2 - :: BS.ByteString -> Either DecodeError (AcceptChannel2, BS.ByteString) -decodeAcceptChannel2 !bs = do - (tempCid, rest1) <- decodeChannelIdBytes bs - (fundingSats, rest2) <- decodeSatoshi rest1 - (dustLimit, rest3) <- decodeSatoshi rest2 - (maxHtlcVal, rest4) <- decodeMilliSatoshi rest3 - (htlcMin, rest5) <- decodeMilliSatoshi rest4 - (minDepth, rest6) <- decodeU32E rest5 - (toSelfDelay, rest7) <- decodeU16E rest6 - (maxHtlcs, rest8) <- decodeU16E rest7 - (fundingPk, rest9) <- decodePointBytes rest8 - (revBase, rest10) <- decodePointBytes rest9 - (payBase, rest11) <- decodePointBytes rest10 - (delayBase, rest12) <- decodePointBytes rest11 - (htlcBase, rest13) <- decodePointBytes rest12 - (firstPt, rest14) <- decodePointBytes rest13 - (secondPt, rest15) <- decodePointBytes rest14 - tlvs <- decodeTlvs rest15 - let !msg = AcceptChannel2 - { acceptChannel2TempChannelId = tempCid - , acceptChannel2FundingSatoshi = fundingSats - , acceptChannel2DustLimitSatoshi = dustLimit - , acceptChannel2MaxHtlcValueInFlight = maxHtlcVal - , acceptChannel2HtlcMinimumMsat = htlcMin - , acceptChannel2MinimumDepth = minDepth - , acceptChannel2ToSelfDelay = toSelfDelay - , acceptChannel2MaxAcceptedHtlcs = maxHtlcs - , acceptChannel2FundingPubkey = fundingPk - , acceptChannel2RevocationBasepoint = revBase - , acceptChannel2PaymentBasepoint = payBase - , acceptChannel2DelayedPaymentBase = delayBase - , acceptChannel2HtlcBasepoint = htlcBase - , acceptChannel2FirstPerCommitPoint = firstPt - , acceptChannel2SecondPerCommitPoint = secondPt - , acceptChannel2Tlvs = tlvs - } - Right (msg, BS.empty) - --- | Encode a TxAddInput message (type 66). -encodeTxAddInput :: TxAddInput -> Either EncodeError BS.ByteString -encodeTxAddInput !msg = do - prevTxEnc <- encodeU16BytesE (txAddInputPrevTx msg) - Right $! mconcat - [ unChannelId (txAddInputChannelId msg) - , encodeU64 (unSerialId (txAddInputSerialId msg)) - , prevTxEnc - , encodeU32 (txAddInputPrevVout msg) - , encodeU32 (txAddInputSequence msg) +-- | Decode a 'FundingSigned' payload. +decode_funding_signed :: BS.ByteString -> Either DecodeError FundingSigned +decode_funding_signed = parse $ + FundingSigned <$> channel_id <*> signature <*> tlvs [] + +-- channel_ready -------------------------------------------------------------- + +channel_ready_known :: [Word64] +channel_ready_known = [1] + +-- | Encode a 'ChannelReady' payload. +encode_channel_ready :: ChannelReady -> Either EncodeError BS.ByteString +encode_channel_ready m = do + ts <- encode_tlvs channel_ready_known + (opt 1 BOLT1.encode_short_channel_id (channel_ready_alias m)) + (channel_ready_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (channel_ready_channel_id m) + , BOLT1.un_point (channel_ready_second_per_commitment_point m) + , ts ] --- | Decode a TxAddInput message (type 66). -decodeTxAddInput - :: BS.ByteString -> Either DecodeError (TxAddInput, BS.ByteString) -decodeTxAddInput !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (sid, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - (prevTx, rest3) <- decodeU16Bytes rest2 - (prevVout, rest4) <- decodeU32E rest3 - (seqNum, rest5) <- decodeU32E rest4 - let !msg = TxAddInput - { txAddInputChannelId = cid - , txAddInputSerialId = serialId sid - , txAddInputPrevTx = prevTx - , txAddInputPrevVout = prevVout - , txAddInputSequence = seqNum - } - Right (msg, rest5) - --- | Encode a TxAddOutput message (type 67). -encodeTxAddOutput :: TxAddOutput -> Either EncodeError BS.ByteString -encodeTxAddOutput !msg = do - scriptEnc <- encodeU16BytesE (unScriptPubKey (txAddOutputScript msg)) - Right $! mconcat - [ unChannelId (txAddOutputChannelId msg) - , encodeU64 (unSerialId (txAddOutputSerialId msg)) - , encodeU64 (unSatoshi (txAddOutputSats msg)) - , scriptEnc +-- | Decode a 'ChannelReady' payload. +decode_channel_ready :: BS.ByteString -> Either DecodeError ChannelReady +decode_channel_ready = parse $ do + cid <- channel_id + pcp <- point + s <- tlvs channel_ready_known + alias <- record 1 (whole BOLT1.decode_short_channel_id) s + pure (ChannelReady cid pcp alias (unknown channel_ready_known s)) + +-- open_channel2 -------------------------------------------------------------- + +opening_known :: [Word64] +opening_known = [0, 1, 2] + +-- | Encode an 'OpenChannel2' payload. +encode_open_channel2 :: OpenChannel2 -> Either EncodeError BS.ByteString +encode_open_channel2 m = do + ts <- encode_tlvs opening_known + ( opt 0 un_script_pubkey (open_channel2_upfront_shutdown_script m) + <> opt 1 BOLT9.render (open_channel2_channel_type m) + <> opt_flag 2 (open_channel2_require_confirmed_inputs m) ) + (open_channel2_tlvs m) + pure $ mconcat [ + BOLT1.un_chain_hash (open_channel2_chain_hash m) + , BOLT1.un_channel_id (open_channel2_temporary_channel_id m) + , BOLT1.encode_u32 (open_channel2_funding_feerate_perkw m) + , BOLT1.encode_u32 (open_channel2_commitment_feerate_perkw m) + , BOLT1.encode_satoshi (open_channel2_funding_satoshis m) + , BOLT1.encode_satoshi (open_channel2_dust_limit_satoshis m) + , BOLT1.encode_u64 (open_channel2_max_htlc_value_in_flight_msat m) + , BOLT1.encode_milli_satoshi (open_channel2_htlc_minimum_msat m) + , BOLT1.encode_u16 (open_channel2_to_self_delay m) + , BOLT1.encode_u16 (open_channel2_max_accepted_htlcs m) + , BOLT1.encode_u32 (open_channel2_locktime m) + , BOLT1.un_point (open_channel2_funding_pubkey m) + , BOLT1.un_point (open_channel2_revocation_basepoint m) + , BOLT1.un_point (open_channel2_payment_basepoint m) + , BOLT1.un_point (open_channel2_delayed_payment_basepoint m) + , BOLT1.un_point (open_channel2_htlc_basepoint m) + , BOLT1.un_point (open_channel2_first_per_commitment_point m) + , BOLT1.un_point (open_channel2_second_per_commitment_point m) + , word8 (open_channel2_channel_flags m) + , ts ] --- | Decode a TxAddOutput message (type 67). -decodeTxAddOutput - :: BS.ByteString -> Either DecodeError (TxAddOutput, BS.ByteString) -decodeTxAddOutput !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (sid, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - (sats, rest3) <- decodeSatoshi rest2 - (scriptBs, rest4) <- decodeU16Bytes rest3 - let !msg = TxAddOutput - { txAddOutputChannelId = cid - , txAddOutputSerialId = serialId sid - , txAddOutputSats = sats - , txAddOutputScript = scriptPubKey scriptBs - } - Right (msg, rest4) - --- | Encode a TxRemoveInput message (type 68). -encodeTxRemoveInput :: TxRemoveInput -> BS.ByteString -encodeTxRemoveInput !msg = mconcat - [ unChannelId (txRemoveInputChannelId msg) - , encodeU64 (unSerialId (txRemoveInputSerialId msg)) +-- | Decode an 'OpenChannel2' payload. +decode_open_channel2 :: BS.ByteString -> Either DecodeError OpenChannel2 +decode_open_channel2 = parse $ do + ch <- chain_hash + tcid <- channel_id + ffee <- u32 + cfee <- u32 + fund <- satoshi + dust <- satoshi + maxv <- u64 + hmin <- milli_satoshi + delay <- u16 + maxa <- u16 + lock <- u32 + fpk <- point + rev <- point + pay <- point + del <- point + htlc <- point + pcp1 <- point + pcp2 <- point + flags <- u8 + s <- tlvs opening_known + shut <- record 0 script_pubkey s + ctype <- record 1 (Just . BOLT9.parse) s + conf <- flag 2 s + pure OpenChannel2 { + open_channel2_chain_hash = ch + , open_channel2_temporary_channel_id = tcid + , open_channel2_funding_feerate_perkw = ffee + , open_channel2_commitment_feerate_perkw = cfee + , open_channel2_funding_satoshis = fund + , open_channel2_dust_limit_satoshis = dust + , open_channel2_max_htlc_value_in_flight_msat = maxv + , open_channel2_htlc_minimum_msat = hmin + , open_channel2_to_self_delay = delay + , open_channel2_max_accepted_htlcs = maxa + , open_channel2_locktime = lock + , open_channel2_funding_pubkey = fpk + , open_channel2_revocation_basepoint = rev + , open_channel2_payment_basepoint = pay + , open_channel2_delayed_payment_basepoint = del + , open_channel2_htlc_basepoint = htlc + , open_channel2_first_per_commitment_point = pcp1 + , open_channel2_second_per_commitment_point = pcp2 + , open_channel2_channel_flags = flags + , open_channel2_upfront_shutdown_script = shut + , open_channel2_channel_type = ctype + , open_channel2_require_confirmed_inputs = conf + , open_channel2_tlvs = unknown opening_known s + } + +-- accept_channel2 ------------------------------------------------------------ + +-- | Encode an 'AcceptChannel2' payload. +encode_accept_channel2 :: AcceptChannel2 -> Either EncodeError BS.ByteString +encode_accept_channel2 m = do + ts <- encode_tlvs opening_known + ( opt 0 un_script_pubkey (accept_channel2_upfront_shutdown_script m) + <> opt 1 BOLT9.render (accept_channel2_channel_type m) + <> opt_flag 2 (accept_channel2_require_confirmed_inputs m) ) + (accept_channel2_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (accept_channel2_temporary_channel_id m) + , BOLT1.encode_satoshi (accept_channel2_funding_satoshis m) + , BOLT1.encode_satoshi (accept_channel2_dust_limit_satoshis m) + , BOLT1.encode_u64 (accept_channel2_max_htlc_value_in_flight_msat m) + , BOLT1.encode_milli_satoshi (accept_channel2_htlc_minimum_msat m) + , BOLT1.encode_u32 (accept_channel2_minimum_depth m) + , BOLT1.encode_u16 (accept_channel2_to_self_delay m) + , BOLT1.encode_u16 (accept_channel2_max_accepted_htlcs m) + , BOLT1.un_point (accept_channel2_funding_pubkey m) + , BOLT1.un_point (accept_channel2_revocation_basepoint m) + , BOLT1.un_point (accept_channel2_payment_basepoint m) + , BOLT1.un_point (accept_channel2_delayed_payment_basepoint m) + , BOLT1.un_point (accept_channel2_htlc_basepoint m) + , BOLT1.un_point (accept_channel2_first_per_commitment_point m) + , BOLT1.un_point (accept_channel2_second_per_commitment_point m) + , ts + ] + +-- | Decode an 'AcceptChannel2' payload. +decode_accept_channel2 :: BS.ByteString -> Either DecodeError AcceptChannel2 +decode_accept_channel2 = parse $ do + tcid <- channel_id + fund <- satoshi + dust <- satoshi + maxv <- u64 + hmin <- milli_satoshi + depth <- u32 + delay <- u16 + maxa <- u16 + fpk <- point + rev <- point + pay <- point + del <- point + htlc <- point + pcp1 <- point + pcp2 <- point + s <- tlvs opening_known + shut <- record 0 script_pubkey s + ctype <- record 1 (Just . BOLT9.parse) s + conf <- flag 2 s + pure AcceptChannel2 { + accept_channel2_temporary_channel_id = tcid + , accept_channel2_funding_satoshis = fund + , accept_channel2_dust_limit_satoshis = dust + , accept_channel2_max_htlc_value_in_flight_msat = maxv + , accept_channel2_htlc_minimum_msat = hmin + , accept_channel2_minimum_depth = depth + , accept_channel2_to_self_delay = delay + , accept_channel2_max_accepted_htlcs = maxa + , accept_channel2_funding_pubkey = fpk + , accept_channel2_revocation_basepoint = rev + , accept_channel2_payment_basepoint = pay + , accept_channel2_delayed_payment_basepoint = del + , accept_channel2_htlc_basepoint = htlc + , accept_channel2_first_per_commitment_point = pcp1 + , accept_channel2_second_per_commitment_point = pcp2 + , accept_channel2_upfront_shutdown_script = shut + , accept_channel2_channel_type = ctype + , accept_channel2_require_confirmed_inputs = conf + , accept_channel2_tlvs = unknown opening_known s + } + +-- interactive transaction construction --------------------------------------- + +-- | Encode a 'TxAddInput' payload. Fails if @prevtx@ exceeds 65535 +-- bytes. +encode_tx_add_input :: TxAddInput -> Either EncodeError BS.ByteString +encode_tx_add_input m = do + prev <- u16_prefixed (tx_add_input_prevtx m) + let SerialId sid = tx_add_input_serial_id m + pure $ mconcat [ + BOLT1.un_channel_id (tx_add_input_channel_id m) + , BOLT1.encode_u64 sid + , prev + , BOLT1.encode_u32 (tx_add_input_prevtx_vout m) + , BOLT1.encode_u32 (tx_add_input_sequence m) + , BOLT1.encode_tlv_stream (tx_add_input_tlvs m) + ] + +-- | Decode a 'TxAddInput' payload. +decode_tx_add_input :: BS.ByteString -> Either DecodeError TxAddInput +decode_tx_add_input = parse $ + TxAddInput <$> channel_id <*> serial_id <*> prefixed <*> u32 <*> u32 + <*> tlvs [] + +-- | Encode a 'TxAddOutput' payload. +encode_tx_add_output :: TxAddOutput -> BS.ByteString +encode_tx_add_output m = + let SerialId sid = tx_add_output_serial_id m + in mconcat [ + BOLT1.un_channel_id (tx_add_output_channel_id m) + , BOLT1.encode_u64 sid + , BOLT1.encode_satoshi (tx_add_output_sats m) + , script_bytes (tx_add_output_script m) + , BOLT1.encode_tlv_stream (tx_add_output_tlvs m) + ] + +-- | Decode a 'TxAddOutput' payload. +decode_tx_add_output :: BS.ByteString -> Either DecodeError TxAddOutput +decode_tx_add_output = parse $ + TxAddOutput <$> channel_id <*> serial_id <*> satoshi <*> script + <*> tlvs [] + +-- | Encode a 'TxRemoveInput' payload. +encode_tx_remove_input :: TxRemoveInput -> BS.ByteString +encode_tx_remove_input (TxRemoveInput cid (SerialId sid) ts) = mconcat [ + BOLT1.un_channel_id cid + , BOLT1.encode_u64 sid + , BOLT1.encode_tlv_stream ts ] --- | Decode a TxRemoveInput message (type 68). -decodeTxRemoveInput - :: BS.ByteString -> Either DecodeError (TxRemoveInput, BS.ByteString) -decodeTxRemoveInput !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (sid, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - let !msg = TxRemoveInput - { txRemoveInputChannelId = cid - , txRemoveInputSerialId = serialId sid - } - Right (msg, rest2) - --- | Encode a TxRemoveOutput message (type 69). -encodeTxRemoveOutput :: TxRemoveOutput -> BS.ByteString -encodeTxRemoveOutput !msg = mconcat - [ unChannelId (txRemoveOutputChannelId msg) - , encodeU64 (unSerialId (txRemoveOutputSerialId msg)) +-- | Decode a 'TxRemoveInput' payload. +decode_tx_remove_input :: BS.ByteString -> Either DecodeError TxRemoveInput +decode_tx_remove_input = parse $ + TxRemoveInput <$> channel_id <*> serial_id <*> tlvs [] + +-- | Encode a 'TxRemoveOutput' payload. +encode_tx_remove_output :: TxRemoveOutput -> BS.ByteString +encode_tx_remove_output (TxRemoveOutput cid (SerialId sid) ts) = mconcat [ + BOLT1.un_channel_id cid + , BOLT1.encode_u64 sid + , BOLT1.encode_tlv_stream ts ] --- | Decode a TxRemoveOutput message (type 69). -decodeTxRemoveOutput - :: BS.ByteString -> Either DecodeError (TxRemoveOutput, BS.ByteString) -decodeTxRemoveOutput !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (sid, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - let !msg = TxRemoveOutput - { txRemoveOutputChannelId = cid - , txRemoveOutputSerialId = serialId sid - } - Right (msg, rest2) - --- | Encode a TxComplete message (type 70). -encodeTxComplete :: TxComplete -> BS.ByteString -encodeTxComplete !msg = unChannelId (txCompleteChannelId msg) - --- | Decode a TxComplete message (type 70). -decodeTxComplete - :: BS.ByteString -> Either DecodeError (TxComplete, BS.ByteString) -decodeTxComplete !bs = do - (cid, rest) <- decodeChannelIdBytes bs - let !msg = TxComplete { txCompleteChannelId = cid } - Right (msg, rest) - --- | Encode a single witness with bounds checking. -encodeWitnessE :: Witness -> Either EncodeError BS.ByteString -encodeWitnessE (Witness !wdata) = encodeU16BytesE wdata - --- | Decode a single witness. -decodeWitness :: BS.ByteString -> Either DecodeError (Witness, BS.ByteString) -decodeWitness !bs = do - (wdata, rest) <- decodeU16Bytes bs - Right (Witness wdata, rest) - --- | Encode a TxSignatures message (type 71). -encodeTxSignatures :: TxSignatures -> Either EncodeError BS.ByteString -encodeTxSignatures !msg = do - let !witnesses = txSignaturesWitnesses msg - numWit <- checkListCountU16 (length witnesses) - encodedWits <- traverse encodeWitnessE witnesses - Right $! mconcat $ - [ unChannelId (txSignaturesChannelId msg) - , let (TxId bs) = txSignaturesTxid msg in bs - , encodeU16 numWit - ] ++ encodedWits - --- | Decode a TxSignatures message (type 71). -decodeTxSignatures - :: BS.ByteString -> Either DecodeError (TxSignatures, BS.ByteString) -decodeTxSignatures !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (tid, rest2) <- decodeTxIdBytes rest1 - (numWit, rest3) <- decodeU16E rest2 - (witnesses, rest4) <- decodeWitnesses (fromIntegral numWit) rest3 - let !msg = TxSignatures - { txSignaturesChannelId = cid - , txSignaturesTxid = tid - , txSignaturesWitnesses = witnesses - } - Right (msg, rest4) +-- | Decode a 'TxRemoveOutput' payload. +decode_tx_remove_output + :: BS.ByteString -> Either DecodeError TxRemoveOutput +decode_tx_remove_output = parse $ + TxRemoveOutput <$> channel_id <*> serial_id <*> tlvs [] + +-- | Encode a 'TxComplete' payload. +encode_tx_complete :: TxComplete -> BS.ByteString +encode_tx_complete (TxComplete cid ts) = + BOLT1.un_channel_id cid <> BOLT1.encode_tlv_stream ts + +-- | Decode a 'TxComplete' payload. +decode_tx_complete :: BS.ByteString -> Either DecodeError TxComplete +decode_tx_complete = parse $ TxComplete <$> channel_id <*> tlvs [] + +-- | Encode a 'TxSignatures' payload. Fails if there are more than 65535 +-- witnesses. +encode_tx_signatures :: TxSignatures -> Either EncodeError BS.ByteString +encode_tx_signatures m = do + let ws = tx_signatures_witnesses m + n <- count_prefix ws + pure $ mconcat [ + BOLT1.un_channel_id (tx_signatures_channel_id m) + , txid_bytes (tx_signatures_txid m) + , n + , mconcat [ BOLT1.encode_u16 (fromIntegral (BS.length w)) <> w + | w <- map un_witness ws ] + , BOLT1.encode_tlv_stream (tx_signatures_tlvs m) + ] + +-- | Decode a 'TxSignatures' payload. +decode_tx_signatures :: BS.ByteString -> Either DecodeError TxSignatures +decode_tx_signatures = parse $ do + cid <- channel_id + t <- txid + n <- u16 + ws <- count (fromIntegral n) wit + TxSignatures cid t ws <$> tlvs [] where - decodeWitnesses :: Int -> BS.ByteString - -> Either DecodeError ([Witness], BS.ByteString) - decodeWitnesses 0 !rest = Right ([], rest) - decodeWitnesses !n !rest = do - (w, rest') <- decodeWitness rest - (ws, rest'') <- decodeWitnesses (n - 1) rest' - Right (w : ws, rest'') - --- | Encode a TxInitRbf message (type 72). -encodeTxInitRbf :: TxInitRbf -> BS.ByteString -encodeTxInitRbf !msg = mconcat - [ unChannelId (txInitRbfChannelId msg) - , encodeU32 (txInitRbfLocktime msg) - , encodeU32 (txInitRbfFeerate msg) - , encodeTlvStream (txInitRbfTlvs msg) + wit = do + b <- prefixed + maybe (failure DecodeInsufficientBytes) pure (witness b) + +rbf_known :: [Word64] +rbf_known = [0, 2] + +-- | Encode a 'TxInitRbf' payload. +encode_tx_init_rbf :: TxInitRbf -> Either EncodeError BS.ByteString +encode_tx_init_rbf m = do + ts <- encode_tlvs rbf_known + ( opt 0 BOLT1.encode_s64 (tx_init_rbf_funding_output_contribution m) + <> opt_flag 2 (tx_init_rbf_require_confirmed_inputs m) ) + (tx_init_rbf_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (tx_init_rbf_channel_id m) + , BOLT1.encode_u32 (tx_init_rbf_locktime m) + , BOLT1.encode_u32 (tx_init_rbf_feerate m) + , ts + ] + +-- | Decode a 'TxInitRbf' payload. +decode_tx_init_rbf :: BS.ByteString -> Either DecodeError TxInitRbf +decode_tx_init_rbf = parse $ do + cid <- channel_id + lock <- u32 + fee <- u32 + s <- tlvs rbf_known + contrib <- record 0 tlv_s64 s + conf <- flag 2 s + pure (TxInitRbf cid lock fee contrib conf (unknown rbf_known s)) + +-- | Encode a 'TxAckRbf' payload. +encode_tx_ack_rbf :: TxAckRbf -> Either EncodeError BS.ByteString +encode_tx_ack_rbf m = do + ts <- encode_tlvs rbf_known + ( opt 0 BOLT1.encode_s64 (tx_ack_rbf_funding_output_contribution m) + <> opt_flag 2 (tx_ack_rbf_require_confirmed_inputs m) ) + (tx_ack_rbf_tlvs m) + pure (BOLT1.un_channel_id (tx_ack_rbf_channel_id m) <> ts) + +-- | Decode a 'TxAckRbf' payload. +decode_tx_ack_rbf :: BS.ByteString -> Either DecodeError TxAckRbf +decode_tx_ack_rbf = parse $ do + cid <- channel_id + s <- tlvs rbf_known + contrib <- record 0 tlv_s64 s + conf <- flag 2 s + pure (TxAckRbf cid contrib conf (unknown rbf_known s)) + +-- | Encode a 'TxAbort' payload. Fails if @data@ exceeds 65535 bytes. +encode_tx_abort :: TxAbort -> Either EncodeError BS.ByteString +encode_tx_abort (TxAbort cid dat ts) = do + dat' <- u16_prefixed dat + pure (BOLT1.un_channel_id cid <> dat' <> BOLT1.encode_tlv_stream ts) + +-- | Decode a 'TxAbort' payload. +decode_tx_abort :: BS.ByteString -> Either DecodeError TxAbort +decode_tx_abort = parse $ TxAbort <$> channel_id <*> prefixed <*> tlvs [] + +-- stfu ----------------------------------------------------------------------- + +-- | Encode a 'Stfu' payload. +encode_stfu :: Stfu -> BS.ByteString +encode_stfu (Stfu cid ini ts) = mconcat [ + BOLT1.un_channel_id cid + , word8 (if ini then 1 else 0) + , BOLT1.encode_tlv_stream ts ] --- | Decode a TxInitRbf message (type 72). -decodeTxInitRbf - :: BS.ByteString -> Either DecodeError (TxInitRbf, BS.ByteString) -decodeTxInitRbf !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (locktime, rest2) <- decodeU32E rest1 - (feerate, rest3) <- decodeU32E rest2 - tlvs <- decodeTlvs rest3 - let !msg = TxInitRbf - { txInitRbfChannelId = cid - , txInitRbfLocktime = locktime - , txInitRbfFeerate = feerate - , txInitRbfTlvs = tlvs - } - Right (msg, BS.empty) - --- | Encode a TxAckRbf message (type 73). -encodeTxAckRbf :: TxAckRbf -> BS.ByteString -encodeTxAckRbf !msg = mconcat - [ unChannelId (txAckRbfChannelId msg) - , encodeTlvStream (txAckRbfTlvs msg) +-- | Decode a 'Stfu' payload. The @initiator@ byte must be 0 or 1. +decode_stfu :: BS.ByteString -> Either DecodeError Stfu +decode_stfu = parse $ do + cid <- channel_id + b <- u8 + ini <- case b of + 0 -> pure False + 1 -> pure True + _ -> failure DecodeInvalidInitiator + Stfu cid ini <$> tlvs [] + +-- shutdown ------------------------------------------------------------------- + +-- | Encode a 'Shutdown' payload. +encode_shutdown :: Shutdown -> BS.ByteString +encode_shutdown (Shutdown cid spk ts) = mconcat [ + BOLT1.un_channel_id cid + , script_bytes spk + , BOLT1.encode_tlv_stream ts ] --- | Decode a TxAckRbf message (type 73). -decodeTxAckRbf - :: BS.ByteString -> Either DecodeError (TxAckRbf, BS.ByteString) -decodeTxAckRbf !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - tlvs <- decodeTlvs rest1 - let !msg = TxAckRbf - { txAckRbfChannelId = cid - , txAckRbfTlvs = tlvs - } - Right (msg, BS.empty) - --- | Encode a TxAbort message (type 74). -encodeTxAbort :: TxAbort -> Either EncodeError BS.ByteString -encodeTxAbort !msg = do - dataEnc <- encodeU16BytesE (txAbortData msg) - Right $! mconcat - [ unChannelId (txAbortChannelId msg) - , dataEnc +-- | Decode a 'Shutdown' payload. +decode_shutdown :: BS.ByteString -> Either DecodeError Shutdown +decode_shutdown = parse $ Shutdown <$> channel_id <*> script <*> tlvs [] + +-- closing_complete, closing_sig ---------------------------------------------- + +closing_known :: [Word64] +closing_known = [1, 2, 3] + +closing_records + :: Maybe Signature -> Maybe Signature -> Maybe Signature -> [TlvRecord] +closing_records a b c = + opt 1 BOLT1.un_signature a + <> opt 2 BOLT1.un_signature b + <> opt 3 BOLT1.un_signature c + +-- | Encode a 'ClosingComplete' payload. +encode_closing_complete :: ClosingComplete -> Either EncodeError BS.ByteString +encode_closing_complete m = do + ts <- encode_tlvs closing_known + (closing_records + (closing_complete_closer_output_only m) + (closing_complete_closee_output_only m) + (closing_complete_closer_and_closee_outputs m)) + (closing_complete_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (closing_complete_channel_id m) + , script_bytes (closing_complete_closer_scriptpubkey m) + , script_bytes (closing_complete_closee_scriptpubkey m) + , BOLT1.encode_satoshi (closing_complete_fee_satoshis m) + , BOLT1.encode_u32 (closing_complete_locktime m) + , ts ] --- | Decode a TxAbort message (type 74). -decodeTxAbort - :: BS.ByteString -> Either DecodeError (TxAbort, BS.ByteString) -decodeTxAbort !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (dat, rest2) <- decodeU16Bytes rest1 - let !msg = TxAbort - { txAbortChannelId = cid - , txAbortData = dat - } - Right (msg, rest2) - --- Normal operation ------------------------------------------------------------ - --- | Encode an UpdateAddHtlc message (type 128). -encodeUpdateAddHtlc :: UpdateAddHtlc -> BS.ByteString -encodeUpdateAddHtlc !m = mconcat - [ unChannelId (updateAddHtlcChannelId m) - , encodeU64 (unHtlcId (updateAddHtlcId m)) - , encodeU64 (unMilliSatoshi (updateAddHtlcAmountMsat m)) - , unPaymentHash (updateAddHtlcPaymentHash m) - , encodeU32 (updateAddHtlcCltvExpiry m) - , unOnionPacket (updateAddHtlcOnionPacket m) - , encodeTlvStream (updateAddHtlcTlvs m) - ] -{-# INLINABLE encodeUpdateAddHtlc #-} - --- | Decode an UpdateAddHtlc message (type 128). -decodeUpdateAddHtlc - :: BS.ByteString -> Either DecodeError (UpdateAddHtlc, BS.ByteString) -decodeUpdateAddHtlc !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (hid, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - (amtMsat, rest3) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest2) - (pHash, rest4) <- decodePaymentHashBytes rest3 - (cltvExp, rest5) <- decodeU32E rest4 - (onion, rest6) <- decodeOnionPacketBytes rest5 - (tlvs, rest7) <- decodeOptionalTlvs rest6 - let !msg = UpdateAddHtlc - { updateAddHtlcChannelId = cid - , updateAddHtlcId = htlcId hid - , updateAddHtlcAmountMsat = MilliSatoshi amtMsat - , updateAddHtlcPaymentHash = pHash - , updateAddHtlcCltvExpiry = cltvExp - , updateAddHtlcOnionPacket = onion - , updateAddHtlcTlvs = tlvs - } - Right (msg, rest7) -{-# INLINABLE decodeUpdateAddHtlc #-} - --- | Encode an UpdateFulfillHtlc message (type 130). -encodeUpdateFulfillHtlc :: UpdateFulfillHtlc -> BS.ByteString -encodeUpdateFulfillHtlc !m = mconcat - [ unChannelId (updateFulfillHtlcChannelId m) - , encodeU64 (unHtlcId (updateFulfillHtlcId m)) - , unPaymentPreimage (updateFulfillHtlcPaymentPreimage m) - , encodeTlvStream (updateFulfillHtlcTlvs m) - ] +-- | Decode a 'ClosingComplete' payload. +decode_closing_complete + :: BS.ByteString -> Either DecodeError ClosingComplete +decode_closing_complete = parse $ do + cid <- channel_id + closer <- script + closee <- script + fee <- satoshi + lock <- u32 + s <- tlvs closing_known + a <- record 1 BOLT1.signature s + b <- record 2 BOLT1.signature s + c <- record 3 BOLT1.signature s + pure (ClosingComplete cid closer closee fee lock a b c + (unknown closing_known s)) + +-- | Encode a 'ClosingSig' payload. +encode_closing_sig :: ClosingSig -> Either EncodeError BS.ByteString +encode_closing_sig m = do + ts <- encode_tlvs closing_known + (closing_records + (closing_sig_closer_output_only m) + (closing_sig_closee_output_only m) + (closing_sig_closer_and_closee_outputs m)) + (closing_sig_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (closing_sig_channel_id m) + , script_bytes (closing_sig_closer_scriptpubkey m) + , script_bytes (closing_sig_closee_scriptpubkey m) + , BOLT1.encode_satoshi (closing_sig_fee_satoshis m) + , BOLT1.encode_u32 (closing_sig_locktime m) + , ts + ] --- | Decode an UpdateFulfillHtlc message (type 130). -decodeUpdateFulfillHtlc - :: BS.ByteString -> Either DecodeError (UpdateFulfillHtlc, BS.ByteString) -decodeUpdateFulfillHtlc !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (hid, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - (preimage, rest3) <- decodePaymentPreimageBytes rest2 - (tlvs, rest4) <- decodeOptionalTlvs rest3 - let !msg = UpdateFulfillHtlc - { updateFulfillHtlcChannelId = cid - , updateFulfillHtlcId = htlcId hid - , updateFulfillHtlcPaymentPreimage = preimage - , updateFulfillHtlcTlvs = tlvs - } - Right (msg, rest4) - --- | Encode an UpdateFailHtlc message (type 131). -encodeUpdateFailHtlc :: UpdateFailHtlc -> Either EncodeError BS.ByteString -encodeUpdateFailHtlc !m = do - reasonEnc <- encodeU16BytesE (updateFailHtlcReason m) - Right $! mconcat - [ unChannelId (updateFailHtlcChannelId m) - , encodeU64 (unHtlcId (updateFailHtlcId m)) - , reasonEnc - , encodeTlvStream (updateFailHtlcTlvs m) +-- | Decode a 'ClosingSig' payload. +decode_closing_sig :: BS.ByteString -> Either DecodeError ClosingSig +decode_closing_sig = parse $ do + cid <- channel_id + closer <- script + closee <- script + fee <- satoshi + lock <- u32 + s <- tlvs closing_known + a <- record 1 BOLT1.signature s + b <- record 2 BOLT1.signature s + c <- record 3 BOLT1.signature s + pure (ClosingSig cid closer closee fee lock a b c + (unknown closing_known s)) + +-- closing_signed ------------------------------------------------------------- + +closing_signed_known :: [Word64] +closing_signed_known = [1] + +-- | Encode a 'ClosingSigned' payload. +encode_closing_signed :: ClosingSigned -> Either EncodeError BS.ByteString +encode_closing_signed m = do + ts <- encode_tlvs closing_signed_known + (opt 1 fee_range_bytes (closing_signed_fee_range m)) + (closing_signed_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (closing_signed_channel_id m) + , BOLT1.encode_satoshi (closing_signed_fee_satoshis m) + , BOLT1.un_signature (closing_signed_signature m) + , ts ] --- | Decode an UpdateFailHtlc message (type 131). -decodeUpdateFailHtlc - :: BS.ByteString -> Either DecodeError (UpdateFailHtlc, BS.ByteString) -decodeUpdateFailHtlc !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (hid, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - (reason, rest3) <- decodeU16Bytes rest2 - (tlvs, rest4) <- decodeOptionalTlvs rest3 - let !msg = UpdateFailHtlc - { updateFailHtlcChannelId = cid - , updateFailHtlcId = htlcId hid - , updateFailHtlcReason = reason - , updateFailHtlcTlvs = tlvs - } - Right (msg, rest4) - --- | Encode an UpdateFailMalformedHtlc message (type 135). -encodeUpdateFailMalformedHtlc :: UpdateFailMalformedHtlc -> BS.ByteString -encodeUpdateFailMalformedHtlc !m = mconcat - [ unChannelId (updateFailMalformedHtlcChannelId m) - , encodeU64 (unHtlcId (updateFailMalformedHtlcId m)) - , unPaymentHash (updateFailMalformedHtlcSha256Onion m) - , encodeU16 (updateFailMalformedHtlcFailureCode m) - ] +-- | Decode a 'ClosingSigned' payload. +decode_closing_signed :: BS.ByteString -> Either DecodeError ClosingSigned +decode_closing_signed = parse $ do + cid <- channel_id + fee <- satoshi + sig <- signature + s <- tlvs closing_signed_known + range <- record 1 tlv_fee_range s + pure (ClosingSigned cid fee sig range (unknown closing_signed_known s)) + +-- update_add_htlc ------------------------------------------------------------ + +update_add_htlc_known :: [Word64] +update_add_htlc_known = [0] + +-- | Encode an 'UpdateAddHtlc' payload. +encode_update_add_htlc :: UpdateAddHtlc -> Either EncodeError BS.ByteString +encode_update_add_htlc m = do + ts <- encode_tlvs update_add_htlc_known + (opt 0 BOLT1.un_point (update_add_htlc_path_key m)) + (update_add_htlc_tlvs m) + let HtlcId hid = update_add_htlc_id m + pure $ mconcat [ + BOLT1.un_channel_id (update_add_htlc_channel_id m) + , BOLT1.encode_u64 hid + , BOLT1.encode_milli_satoshi (update_add_htlc_amount_msat m) + , BOLT1.un_payment_hash (update_add_htlc_payment_hash m) + , BOLT1.encode_u32 (update_add_htlc_cltv_expiry m) + , un_onion_routing_packet (update_add_htlc_onion_routing_packet m) + , ts + ] --- | Decode an UpdateFailMalformedHtlc message (type 135). -decodeUpdateFailMalformedHtlc - :: BS.ByteString -> Either DecodeError (UpdateFailMalformedHtlc, BS.ByteString) -decodeUpdateFailMalformedHtlc !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (hid, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - (sha256Onion, rest3) <- decodePaymentHashBytes rest2 - (failCode, rest4) <- decodeU16E rest3 - let !msg = UpdateFailMalformedHtlc - { updateFailMalformedHtlcChannelId = cid - , updateFailMalformedHtlcId = htlcId hid - , updateFailMalformedHtlcSha256Onion = sha256Onion - , updateFailMalformedHtlcFailureCode = failCode - } - Right (msg, rest4) - --- | Encode a CommitmentSigned message (type 132). -encodeCommitmentSigned :: CommitmentSigned -> Either EncodeError BS.ByteString -encodeCommitmentSigned !m = do - let !sigs = commitmentSignedHtlcSignatures m - numHtlcs <- checkListCountU16 (length sigs) - Right $! mconcat $ - [ unChannelId (commitmentSignedChannelId m) - , unSignature (commitmentSignedSignature m) - , encodeU16 numHtlcs - ] ++ map unSignature sigs -{-# INLINABLE encodeCommitmentSigned #-} - --- | Decode a CommitmentSigned message (type 132). -decodeCommitmentSigned - :: BS.ByteString -> Either DecodeError (CommitmentSigned, BS.ByteString) -decodeCommitmentSigned !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (sig, rest2) <- decodeSignatureBytes rest1 - (numHtlcs, rest3) <- decodeU16E rest2 - (htlcSigs, rest4) <- decodeSignatures (fromIntegral numHtlcs) rest3 - let !msg = CommitmentSigned - { commitmentSignedChannelId = cid - , commitmentSignedSignature = sig - , commitmentSignedHtlcSignatures = htlcSigs - } - Right (msg, rest4) - where - decodeSignatures :: Int -> BS.ByteString - -> Either DecodeError ([Signature], BS.ByteString) - decodeSignatures !n !input = go n input [] - where - go :: Int -> BS.ByteString -> [Signature] - -> Either DecodeError ([Signature], BS.ByteString) - go 0 !remaining !acc = Right (reverse acc, remaining) - go !count !remaining !acc = do - (s, rest) <- decodeSignatureBytes remaining - go (count - 1) rest (s : acc) -{-# INLINABLE decodeCommitmentSigned #-} - --- | Encode a RevokeAndAck message (type 133). -encodeRevokeAndAck :: RevokeAndAck -> BS.ByteString -encodeRevokeAndAck !m = mconcat - [ unChannelId (revokeAndAckChannelId m) - , unPerCommitmentSecret (revokeAndAckPerCommitmentSecret m) - , unPoint (revokeAndAckNextPerCommitPoint m) - ] +-- | Decode an 'UpdateAddHtlc' payload. +decode_update_add_htlc :: BS.ByteString -> Either DecodeError UpdateAddHtlc +decode_update_add_htlc = parse $ do + cid <- channel_id + hid <- htlc_id + amt <- milli_satoshi + ph <- payment_hash + cltv <- u32 + onion <- sized 1366 onion_routing_packet + s <- tlvs update_add_htlc_known + pk <- record 0 tlv_point s + pure (UpdateAddHtlc cid hid amt ph cltv onion pk + (unknown update_add_htlc_known s)) + +-- update_fulfill_htlc -------------------------------------------------------- + +update_fulfill_htlc_known :: [Word64] +update_fulfill_htlc_known = [1, 3] + +-- | Encode an 'UpdateFulfillHtlc' payload. +encode_update_fulfill_htlc + :: UpdateFulfillHtlc -> Either EncodeError BS.ByteString +encode_update_fulfill_htlc m = do + ts <- encode_tlvs update_fulfill_htlc_known + ( opt 1 un_attribution_data (update_fulfill_htlc_attribution_data m) + <> opt 3 id (update_fulfill_htlc_fulfillment_payload m) ) + (update_fulfill_htlc_tlvs m) + let HtlcId hid = update_fulfill_htlc_id m + pure $ mconcat [ + BOLT1.un_channel_id (update_fulfill_htlc_channel_id m) + , BOLT1.encode_u64 hid + , BOLT1.un_payment_preimage (update_fulfill_htlc_payment_preimage m) + , ts + ] + +-- | Decode an 'UpdateFulfillHtlc' payload. +decode_update_fulfill_htlc + :: BS.ByteString -> Either DecodeError UpdateFulfillHtlc +decode_update_fulfill_htlc = parse $ do + cid <- channel_id + hid <- htlc_id + pre <- payment_preimage + s <- tlvs update_fulfill_htlc_known + att <- record 1 attribution_data s + pay <- record 3 Just s + pure (UpdateFulfillHtlc cid hid pre att pay + (unknown update_fulfill_htlc_known s)) + +-- update_fail_htlc ----------------------------------------------------------- + +update_fail_htlc_known :: [Word64] +update_fail_htlc_known = [1] + +-- | Encode an 'UpdateFailHtlc' payload. Fails if @reason@ exceeds 65535 +-- bytes. +encode_update_fail_htlc :: UpdateFailHtlc -> Either EncodeError BS.ByteString +encode_update_fail_htlc m = do + reason <- u16_prefixed (update_fail_htlc_reason m) + ts <- encode_tlvs update_fail_htlc_known + (opt 1 un_attribution_data (update_fail_htlc_attribution_data m)) + (update_fail_htlc_tlvs m) + let HtlcId hid = update_fail_htlc_id m + pure $ mconcat [ + BOLT1.un_channel_id (update_fail_htlc_channel_id m) + , BOLT1.encode_u64 hid + , reason + , ts + ] --- | Decode a RevokeAndAck message (type 133). -decodeRevokeAndAck - :: BS.ByteString -> Either DecodeError (RevokeAndAck, BS.ByteString) -decodeRevokeAndAck !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (sec, rest2) <- decodePerCommitmentSecretBytes rest1 - (nextPoint, rest3) <- decodePointBytes rest2 - let !msg = RevokeAndAck - { revokeAndAckChannelId = cid - , revokeAndAckPerCommitmentSecret = sec - , revokeAndAckNextPerCommitPoint = nextPoint - } - Right (msg, rest3) - --- | Encode an UpdateFee message (type 134). -encodeUpdateFee :: UpdateFee -> BS.ByteString -encodeUpdateFee !m = mconcat - [ unChannelId (updateFeeChannelId m) - , encodeU32 (updateFeeFeeratePerKw m) +-- | Decode an 'UpdateFailHtlc' payload. +decode_update_fail_htlc :: BS.ByteString -> Either DecodeError UpdateFailHtlc +decode_update_fail_htlc = parse $ do + cid <- channel_id + hid <- htlc_id + reason <- prefixed + s <- tlvs update_fail_htlc_known + att <- record 1 attribution_data s + pure (UpdateFailHtlc cid hid reason att (unknown update_fail_htlc_known s)) + +-- update_fail_malformed_htlc ------------------------------------------------- + +-- | Encode an 'UpdateFailMalformedHtlc' payload. +encode_update_fail_malformed_htlc :: UpdateFailMalformedHtlc -> BS.ByteString +encode_update_fail_malformed_htlc + (UpdateFailMalformedHtlc cid (HtlcId hid) oh code ts) = mconcat [ + BOLT1.un_channel_id cid + , BOLT1.encode_u64 hid + , un_onion_hash oh + , BOLT1.encode_u16 code + , BOLT1.encode_tlv_stream ts + ] + +-- | Decode an 'UpdateFailMalformedHtlc' payload. +decode_update_fail_malformed_htlc + :: BS.ByteString -> Either DecodeError UpdateFailMalformedHtlc +decode_update_fail_malformed_htlc = parse $ + UpdateFailMalformedHtlc <$> channel_id <*> htlc_id <*> sized 32 onion_hash + <*> u16 <*> tlvs [] + +-- commitment_signed ---------------------------------------------------------- + +commitment_signed_known :: [Word64] +commitment_signed_known = [1] + +-- | Encode a 'CommitmentSigned' payload. Fails if there are more than +-- 65535 HTLC signatures. +encode_commitment_signed + :: CommitmentSigned -> Either EncodeError BS.ByteString +encode_commitment_signed m = do + let sigs = commitment_signed_htlc_signatures m + n <- count_prefix sigs + ts <- encode_tlvs commitment_signed_known + (opt 1 txid_bytes (commitment_signed_funding_txid m)) + (commitment_signed_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (commitment_signed_channel_id m) + , BOLT1.un_signature (commitment_signed_signature m) + , n + , mconcat (map BOLT1.un_signature sigs) + , ts + ] + +-- | Decode a 'CommitmentSigned' payload. +decode_commitment_signed + :: BS.ByteString -> Either DecodeError CommitmentSigned +decode_commitment_signed = parse $ do + cid <- channel_id + sig <- signature + n <- u16 + sigs <- count (fromIntegral n) signature + s <- tlvs commitment_signed_known + fund <- record 1 mkTxId s + pure (CommitmentSigned cid sig sigs fund (unknown commitment_signed_known s)) + +-- revoke_and_ack, update_fee ------------------------------------------------- + +-- | Encode a 'RevokeAndAck' payload. +encode_revoke_and_ack :: RevokeAndAck -> BS.ByteString +encode_revoke_and_ack (RevokeAndAck cid sec pcp ts) = mconcat [ + BOLT1.un_channel_id cid + , BOLT1.un_per_commitment_secret sec + , BOLT1.un_point pcp + , BOLT1.encode_tlv_stream ts ] --- | Decode an UpdateFee message (type 134). -decodeUpdateFee - :: BS.ByteString -> Either DecodeError (UpdateFee, BS.ByteString) -decodeUpdateFee !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (feerate, rest2) <- decodeU32E rest1 - let !msg = UpdateFee - { updateFeeChannelId = cid - , updateFeeFeeratePerKw = feerate - } - Right (msg, rest2) - --- Channel reestablishment ----------------------------------------------------- - --- | Encode a ChannelReestablish message (type 136). -encodeChannelReestablish :: ChannelReestablish -> BS.ByteString -encodeChannelReestablish !m = mconcat - [ unChannelId (channelReestablishChannelId m) - , encodeU64 (channelReestablishNextCommitNum m) - , encodeU64 (channelReestablishNextRevocationNum m) - , unPerCommitmentSecret (channelReestablishYourLastCommitSecret m) - , unPoint (channelReestablishMyCurrentCommitPoint m) - , encodeTlvStream (channelReestablishTlvs m) +-- | Decode a 'RevokeAndAck' payload. +decode_revoke_and_ack :: BS.ByteString -> Either DecodeError RevokeAndAck +decode_revoke_and_ack = parse $ + RevokeAndAck <$> channel_id <*> per_commitment_secret <*> point <*> tlvs [] + +-- | Encode an 'UpdateFee' payload. +encode_update_fee :: UpdateFee -> BS.ByteString +encode_update_fee (UpdateFee cid fee ts) = mconcat [ + BOLT1.un_channel_id cid + , BOLT1.encode_u32 fee + , BOLT1.encode_tlv_stream ts ] --- | Decode a ChannelReestablish message (type 136). -decodeChannelReestablish - :: BS.ByteString -> Either DecodeError (ChannelReestablish, BS.ByteString) -decodeChannelReestablish !bs = do - (cid, rest1) <- decodeChannelIdBytes bs - (nextCommit, rest2) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest1) - (nextRevoke, rest3) <- maybe (Left DecodeInsufficientBytes) Right - (decodeU64 rest2) - (sec, rest4) <- decodePerCommitmentSecretBytes rest3 - (myPoint, rest5) <- decodePointBytes rest4 - (tlvs, rest6) <- decodeOptionalTlvs rest5 - let !msg = ChannelReestablish - { channelReestablishChannelId = cid - , channelReestablishNextCommitNum = nextCommit - , channelReestablishNextRevocationNum = nextRevoke - , channelReestablishYourLastCommitSecret = sec - , channelReestablishMyCurrentCommitPoint = myPoint - , channelReestablishTlvs = tlvs - } - Right (msg, rest6) +-- | Decode an 'UpdateFee' payload. +decode_update_fee :: BS.ByteString -> Either DecodeError UpdateFee +decode_update_fee = parse $ UpdateFee <$> channel_id <*> u32 <*> tlvs [] + +-- channel_reestablish -------------------------------------------------------- + +channel_reestablish_known :: [Word64] +channel_reestablish_known = [1] + +-- | Encode a 'ChannelReestablish' payload. +encode_channel_reestablish + :: ChannelReestablish -> Either EncodeError BS.ByteString +encode_channel_reestablish m = do + ts <- encode_tlvs channel_reestablish_known + (opt 1 next_funding_bytes (channel_reestablish_next_funding m)) + (channel_reestablish_tlvs m) + pure $ mconcat [ + BOLT1.un_channel_id (channel_reestablish_channel_id m) + , BOLT1.encode_u64 (channel_reestablish_next_commitment_number m) + , BOLT1.encode_u64 (channel_reestablish_next_revocation_number m) + , BOLT1.un_per_commitment_secret + (channel_reestablish_your_last_per_commitment_secret m) + , BOLT1.un_point (channel_reestablish_my_current_per_commitment_point m) + , ts + ] + +-- | Decode a 'ChannelReestablish' payload. +decode_channel_reestablish + :: BS.ByteString -> Either DecodeError ChannelReestablish +decode_channel_reestablish = parse $ do + cid <- channel_id + nc <- u64 + nr <- u64 + sec <- per_commitment_secret + pcp <- point + s <- tlvs channel_reestablish_known + next <- record 1 tlv_next_funding s + pure (ChannelReestablish cid nc nr sec pcp next + (unknown channel_reestablish_known s)) diff --git a/lib/Lightning/Protocol/BOLT2/Messages.hs b/lib/Lightning/Protocol/BOLT2/Messages.hs @@ -1,7 +1,5 @@ -{-# OPTIONS_HADDOCK prune #-} -{-# LANGUAGE BangPatterns #-} +{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} -- | -- Module: Lightning.Protocol.BOLT2.Messages @@ -9,27 +7,18 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> -- --- Message types for BOLT #2 peer protocol. --- --- This module defines per-message record types and a top-level Message --- sum type for all BOLT #2 messages. +-- The messages defined by BOLT #2. module Lightning.Protocol.BOLT2.Messages ( - -- * Message type codes - MsgType(..) - , msgTypeWord - - -- * Top-level message type - , Message(..) + Message(..) + , message_type - -- * Channel establishment v1 , OpenChannel(..) , AcceptChannel(..) , FundingCreated(..) , FundingSigned(..) , ChannelReady(..) - -- * Channel establishment v2 , OpenChannel2(..) , AcceptChannel2(..) , TxAddInput(..) @@ -42,14 +31,13 @@ module Lightning.Protocol.BOLT2.Messages ( , TxAckRbf(..) , TxAbort(..) - -- * Channel close , Stfu(..) + , Shutdown(..) - , ClosingSigned(..) , ClosingComplete(..) , ClosingSig(..) + , ClosingSigned(..) - -- * Normal operation , UpdateAddHtlc(..) , UpdateFulfillHtlc(..) , UpdateFailHtlc(..) @@ -58,544 +46,556 @@ module Lightning.Protocol.BOLT2.Messages ( , RevokeAndAck(..) , UpdateFee(..) - -- * Reestablishment , ChannelReestablish(..) - - -- * Witness data - , Witness(..) ) where +import Bitcoin.Prim.Tx (TxId) import Control.DeepSeq (NFData) import qualified Data.ByteString as BS +import Data.Int (Int64) import Data.Word (Word8, Word16, Word32, Word64) import GHC.Generics (Generic) -import Lightning.Protocol.BOLT1 (TlvStream) +import Lightning.Protocol.BOLT1 + ( ChainHash, ChannelId, Signature, Point, PaymentHash, PaymentPreimage + , PerCommitmentSecret, ShortChannelId, Satoshi, MilliSatoshi, TlvStream ) +import qualified Lightning.Protocol.BOLT9 as BOLT9 import Lightning.Protocol.BOLT2.Types --- Message type codes ---------------------------------------------------------- - --- | BOLT #2 message type codes. -data MsgType - = MsgStfu -- ^ 2 - | MsgOpenChannel -- ^ 32 - | MsgAcceptChannel -- ^ 33 - | MsgFundingCreated -- ^ 34 - | MsgFundingSigned -- ^ 35 - | MsgChannelReady -- ^ 36 - | MsgShutdown -- ^ 38 - | MsgClosingSigned -- ^ 39 - | MsgClosingComplete -- ^ 40 - | MsgClosingSig -- ^ 41 - | MsgOpenChannel2 -- ^ 64 - | MsgAcceptChannel2 -- ^ 65 - | MsgTxAddInput -- ^ 66 - | MsgTxAddOutput -- ^ 67 - | MsgTxRemoveInput -- ^ 68 - | MsgTxRemoveOutput -- ^ 69 - | MsgTxComplete -- ^ 70 - | MsgTxSignatures -- ^ 71 - | MsgTxInitRbf -- ^ 72 - | MsgTxAckRbf -- ^ 73 - | MsgTxAbort -- ^ 74 - | MsgUpdateAddHtlc -- ^ 128 - | MsgUpdateFulfillHtlc -- ^ 130 - | MsgUpdateFailHtlc -- ^ 131 - | MsgCommitmentSigned -- ^ 132 - | MsgRevokeAndAck -- ^ 133 - | MsgUpdateFee -- ^ 134 - | MsgUpdateFailMalformedHtlc -- ^ 135 - | MsgChannelReestablish -- ^ 136 - deriving stock (Eq, Ord, Show, Generic) - -instance NFData MsgType - --- | Get the numeric type code for a message type. -msgTypeWord :: MsgType -> Word16 -msgTypeWord MsgStfu = 2 -msgTypeWord MsgOpenChannel = 32 -msgTypeWord MsgAcceptChannel = 33 -msgTypeWord MsgFundingCreated = 34 -msgTypeWord MsgFundingSigned = 35 -msgTypeWord MsgChannelReady = 36 -msgTypeWord MsgShutdown = 38 -msgTypeWord MsgClosingSigned = 39 -msgTypeWord MsgClosingComplete = 40 -msgTypeWord MsgClosingSig = 41 -msgTypeWord MsgOpenChannel2 = 64 -msgTypeWord MsgAcceptChannel2 = 65 -msgTypeWord MsgTxAddInput = 66 -msgTypeWord MsgTxAddOutput = 67 -msgTypeWord MsgTxRemoveInput = 68 -msgTypeWord MsgTxRemoveOutput = 69 -msgTypeWord MsgTxComplete = 70 -msgTypeWord MsgTxSignatures = 71 -msgTypeWord MsgTxInitRbf = 72 -msgTypeWord MsgTxAckRbf = 73 -msgTypeWord MsgTxAbort = 74 -msgTypeWord MsgUpdateAddHtlc = 128 -msgTypeWord MsgUpdateFulfillHtlc = 130 -msgTypeWord MsgUpdateFailHtlc = 131 -msgTypeWord MsgCommitmentSigned = 132 -msgTypeWord MsgRevokeAndAck = 133 -msgTypeWord MsgUpdateFee = 134 -msgTypeWord MsgUpdateFailMalformedHtlc = 135 -msgTypeWord MsgChannelReestablish = 136 -{-# INLINE msgTypeWord #-} - --- Channel establishment v1 ---------------------------------------------------- - --- | The open_channel message (type 32). --- --- Contains information about a node and indicates its desire to set up --- a new channel. +-- channel establishment v1 --------------------------------------------------- + +-- | The @open_channel@ message (type 32). data OpenChannel = OpenChannel - { openChannelChainHash :: !ChainHash - , openChannelTempChannelId :: !ChannelId - , openChannelFundingSatoshi :: {-# UNPACK #-} !Satoshi - , openChannelPushMsat :: {-# UNPACK #-} !MilliSatoshi - , openChannelDustLimitSatoshi :: {-# UNPACK #-} !Satoshi - , openChannelMaxHtlcValueInFlight :: {-# UNPACK #-} !MilliSatoshi - , openChannelChannelReserveSat :: {-# UNPACK #-} !Satoshi - , openChannelHtlcMinimumMsat :: {-# UNPACK #-} !MilliSatoshi - , openChannelFeeratePerKw :: {-# UNPACK #-} !Word32 - , openChannelToSelfDelay :: {-# UNPACK #-} !Word16 - , openChannelMaxAcceptedHtlcs :: {-# UNPACK #-} !Word16 - , openChannelFundingPubkey :: !Point - , openChannelRevocationBasepoint :: !Point - , openChannelPaymentBasepoint :: !Point - , openChannelDelayedPaymentBase :: !Point - , openChannelHtlcBasepoint :: !Point - , openChannelFirstPerCommitPoint :: !Point - , openChannelChannelFlags :: {-# UNPACK #-} !Word8 - , openChannelTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { open_channel_chain_hash :: !ChainHash + , open_channel_temporary_channel_id :: !ChannelId + , open_channel_funding_satoshis :: {-# UNPACK #-} !Satoshi + , open_channel_push_msat :: {-# UNPACK #-} !MilliSatoshi + , open_channel_dust_limit_satoshis :: {-# UNPACK #-} !Satoshi + , open_channel_max_htlc_value_in_flight_msat :: {-# UNPACK #-} !Word64 + -- ^ a cap, often @maxBound@, so not a bounded amount + , open_channel_channel_reserve_satoshis :: {-# UNPACK #-} !Satoshi + , open_channel_htlc_minimum_msat :: {-# UNPACK #-} !MilliSatoshi + , open_channel_feerate_per_kw :: {-# UNPACK #-} !Word32 + , open_channel_to_self_delay :: {-# UNPACK #-} !Word16 + , open_channel_max_accepted_htlcs :: {-# UNPACK #-} !Word16 + , open_channel_funding_pubkey :: !Point + , open_channel_revocation_basepoint :: !Point + , open_channel_payment_basepoint :: !Point + , open_channel_delayed_payment_basepoint :: !Point + , open_channel_htlc_basepoint :: !Point + , open_channel_first_per_commitment_point :: !Point + , open_channel_channel_flags :: {-# UNPACK #-} !Word8 + -- ^ bit 0: @announce_channel@ + , open_channel_upfront_shutdown_script :: !(Maybe ScriptPubKey) + -- ^ TLV type 0; an empty script commits to no particular script + , open_channel_channel_type :: !(Maybe BOLT9.FeatureVector) + -- ^ TLV type 1 + , open_channel_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData OpenChannel --- | The accept_channel message (type 33). --- --- Contains information about a node and indicates its acceptance of --- the new channel. +-- | The @accept_channel@ message (type 33). data AcceptChannel = AcceptChannel - { acceptChannelTempChannelId :: !ChannelId - , acceptChannelDustLimitSatoshi :: {-# UNPACK #-} !Satoshi - , acceptChannelMaxHtlcValueInFlight :: {-# UNPACK #-} !MilliSatoshi - , acceptChannelChannelReserveSat :: {-# UNPACK #-} !Satoshi - , acceptChannelHtlcMinimumMsat :: {-# UNPACK #-} !MilliSatoshi - , acceptChannelMinimumDepth :: {-# UNPACK #-} !Word32 - , acceptChannelToSelfDelay :: {-# UNPACK #-} !Word16 - , acceptChannelMaxAcceptedHtlcs :: {-# UNPACK #-} !Word16 - , acceptChannelFundingPubkey :: !Point - , acceptChannelRevocationBasepoint :: !Point - , acceptChannelPaymentBasepoint :: !Point - , acceptChannelDelayedPaymentBase :: !Point - , acceptChannelHtlcBasepoint :: !Point - , acceptChannelFirstPerCommitPoint :: !Point - , acceptChannelTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { accept_channel_temporary_channel_id :: !ChannelId + , accept_channel_dust_limit_satoshis :: {-# UNPACK #-} !Satoshi + , accept_channel_max_htlc_value_in_flight_msat :: {-# UNPACK #-} !Word64 + -- ^ a cap, often @maxBound@, so not a bounded amount + , accept_channel_channel_reserve_satoshis :: {-# UNPACK #-} !Satoshi + , accept_channel_htlc_minimum_msat + :: {-# UNPACK #-} !MilliSatoshi + , accept_channel_minimum_depth :: {-# UNPACK #-} !Word32 + , accept_channel_to_self_delay :: {-# UNPACK #-} !Word16 + , accept_channel_max_accepted_htlcs :: {-# UNPACK #-} !Word16 + , accept_channel_funding_pubkey :: !Point + , accept_channel_revocation_basepoint :: !Point + , accept_channel_payment_basepoint :: !Point + , accept_channel_delayed_payment_basepoint :: !Point + , accept_channel_htlc_basepoint :: !Point + , accept_channel_first_per_commitment_point :: !Point + , accept_channel_upfront_shutdown_script :: !(Maybe ScriptPubKey) + -- ^ TLV type 0; an empty script commits to no particular script + , accept_channel_channel_type + :: !(Maybe BOLT9.FeatureVector) + -- ^ TLV type 1 + , accept_channel_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData AcceptChannel --- | The funding_created message (type 34). --- --- Describes the outpoint which the funder has created for the initial --- commitment transactions. +-- | The @funding_created@ message (type 34). data FundingCreated = FundingCreated - { fundingCreatedTempChannelId :: !ChannelId - , fundingCreatedFundingTxid :: !TxId - , fundingCreatedFundingOutIdx :: {-# UNPACK #-} !Word16 - , fundingCreatedSignature :: !Signature - } deriving stock (Eq, Show, Generic) + { funding_created_temporary_channel_id :: !ChannelId + , funding_created_funding_txid :: !TxId + , funding_created_funding_output_index :: {-# UNPACK #-} !Word16 + , funding_created_signature :: !Signature + , funding_created_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData FundingCreated --- | The funding_signed message (type 35). --- --- Gives the funder the signature for the first commitment transaction. +-- | The @funding_signed@ message (type 35). data FundingSigned = FundingSigned - { fundingSignedChannelId :: !ChannelId - , fundingSignedSignature :: !Signature - } deriving stock (Eq, Show, Generic) + { funding_signed_channel_id :: !ChannelId + , funding_signed_signature :: !Signature + , funding_signed_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData FundingSigned --- | The channel_ready message (type 36). --- --- Indicates that the funding transaction has sufficient confirms for --- channel use. +-- | The @channel_ready@ message (type 36). data ChannelReady = ChannelReady - { channelReadyChannelId :: !ChannelId - , channelReadySecondPerCommitPoint :: !Point - , channelReadyTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { channel_ready_channel_id :: !ChannelId + , channel_ready_second_per_commitment_point :: !Point + , channel_ready_alias :: !(Maybe ShortChannelId) + -- ^ the @short_channel_id@ record (TLV type 1) + , channel_ready_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData ChannelReady --- Channel establishment v2 ---------------------------------------------------- +-- channel establishment v2 --------------------------------------------------- --- | The open_channel2 message (type 64). --- --- Initiates the v2 channel establishment workflow. +-- | The @open_channel2@ message (type 64). data OpenChannel2 = OpenChannel2 - { openChannel2ChainHash :: !ChainHash - , openChannel2TempChannelId :: !ChannelId - , openChannel2FundingFeeratePerkw :: {-# UNPACK #-} !Word32 - , openChannel2CommitFeeratePerkw :: {-# UNPACK #-} !Word32 - , openChannel2FundingSatoshi :: {-# UNPACK #-} !Satoshi - , openChannel2DustLimitSatoshi :: {-# UNPACK #-} !Satoshi - , openChannel2MaxHtlcValueInFlight :: {-# UNPACK #-} !MilliSatoshi - , openChannel2HtlcMinimumMsat :: {-# UNPACK #-} !MilliSatoshi - , openChannel2ToSelfDelay :: {-# UNPACK #-} !Word16 - , openChannel2MaxAcceptedHtlcs :: {-# UNPACK #-} !Word16 - , openChannel2Locktime :: {-# UNPACK #-} !Word32 - , openChannel2FundingPubkey :: !Point - , openChannel2RevocationBasepoint :: !Point - , openChannel2PaymentBasepoint :: !Point - , openChannel2DelayedPaymentBase :: !Point - , openChannel2HtlcBasepoint :: !Point - , openChannel2FirstPerCommitPoint :: !Point - , openChannel2SecondPerCommitPoint :: !Point - , openChannel2ChannelFlags :: {-# UNPACK #-} !Word8 - , openChannel2Tlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { open_channel2_chain_hash :: !ChainHash + , open_channel2_temporary_channel_id :: !ChannelId + , open_channel2_funding_feerate_perkw :: {-# UNPACK #-} !Word32 + , open_channel2_commitment_feerate_perkw :: {-# UNPACK #-} !Word32 + , open_channel2_funding_satoshis :: {-# UNPACK #-} !Satoshi + , open_channel2_dust_limit_satoshis :: {-# UNPACK #-} !Satoshi + , open_channel2_max_htlc_value_in_flight_msat :: {-# UNPACK #-} !Word64 + -- ^ a cap, often @maxBound@, so not a bounded amount + , open_channel2_htlc_minimum_msat + :: {-# UNPACK #-} !MilliSatoshi + , open_channel2_to_self_delay :: {-# UNPACK #-} !Word16 + , open_channel2_max_accepted_htlcs :: {-# UNPACK #-} !Word16 + , open_channel2_locktime :: {-# UNPACK #-} !Word32 + , open_channel2_funding_pubkey :: !Point + , open_channel2_revocation_basepoint :: !Point + , open_channel2_payment_basepoint :: !Point + , open_channel2_delayed_payment_basepoint :: !Point + , open_channel2_htlc_basepoint :: !Point + , open_channel2_first_per_commitment_point :: !Point + , open_channel2_second_per_commitment_point :: !Point + , open_channel2_channel_flags :: {-# UNPACK #-} !Word8 + -- ^ bit 0: @announce_channel@ + , open_channel2_upfront_shutdown_script :: !(Maybe ScriptPubKey) + -- ^ TLV type 0; an empty script commits to no particular script + , open_channel2_channel_type + :: !(Maybe BOLT9.FeatureVector) + -- ^ TLV type 1 + , open_channel2_require_confirmed_inputs :: !Bool + -- ^ TLV type 2 + , open_channel2_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData OpenChannel2 --- | The accept_channel2 message (type 65). --- --- Indicates acceptance of the v2 channel. +-- | The @accept_channel2@ message (type 65). data AcceptChannel2 = AcceptChannel2 - { acceptChannel2TempChannelId :: !ChannelId - , acceptChannel2FundingSatoshi :: {-# UNPACK #-} !Satoshi - , acceptChannel2DustLimitSatoshi :: {-# UNPACK #-} !Satoshi - , acceptChannel2MaxHtlcValueInFlight :: {-# UNPACK #-} !MilliSatoshi - , acceptChannel2HtlcMinimumMsat :: {-# UNPACK #-} !MilliSatoshi - , acceptChannel2MinimumDepth :: {-# UNPACK #-} !Word32 - , acceptChannel2ToSelfDelay :: {-# UNPACK #-} !Word16 - , acceptChannel2MaxAcceptedHtlcs :: {-# UNPACK #-} !Word16 - , acceptChannel2FundingPubkey :: !Point - , acceptChannel2RevocationBasepoint :: !Point - , acceptChannel2PaymentBasepoint :: !Point - , acceptChannel2DelayedPaymentBase :: !Point - , acceptChannel2HtlcBasepoint :: !Point - , acceptChannel2FirstPerCommitPoint :: !Point - , acceptChannel2SecondPerCommitPoint :: !Point - , acceptChannel2Tlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { accept_channel2_temporary_channel_id :: !ChannelId + , accept_channel2_funding_satoshis :: {-# UNPACK #-} !Satoshi + , accept_channel2_dust_limit_satoshis :: {-# UNPACK #-} !Satoshi + , accept_channel2_max_htlc_value_in_flight_msat :: {-# UNPACK #-} !Word64 + -- ^ a cap, often @maxBound@, so not a bounded amount + , accept_channel2_htlc_minimum_msat + :: {-# UNPACK #-} !MilliSatoshi + , accept_channel2_minimum_depth :: {-# UNPACK #-} !Word32 + , accept_channel2_to_self_delay :: {-# UNPACK #-} !Word16 + , accept_channel2_max_accepted_htlcs :: {-# UNPACK #-} !Word16 + , accept_channel2_funding_pubkey :: !Point + , accept_channel2_revocation_basepoint :: !Point + , accept_channel2_payment_basepoint :: !Point + , accept_channel2_delayed_payment_basepoint :: !Point + , accept_channel2_htlc_basepoint :: !Point + , accept_channel2_first_per_commitment_point :: !Point + , accept_channel2_second_per_commitment_point :: !Point + , accept_channel2_upfront_shutdown_script + :: !(Maybe ScriptPubKey) + -- ^ TLV type 0; an empty script commits to no particular script + , accept_channel2_channel_type + :: !(Maybe BOLT9.FeatureVector) + -- ^ TLV type 1 + , accept_channel2_require_confirmed_inputs :: !Bool + -- ^ TLV type 2 + , accept_channel2_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData AcceptChannel2 --- Interactive transaction construction ---------------------------------------- - --- | The tx_add_input message (type 66). --- --- Adds a transaction input to the collaborative transaction. +-- | The @tx_add_input@ message (type 66). data TxAddInput = TxAddInput - { txAddInputChannelId :: !ChannelId - , txAddInputSerialId :: !SerialId - , txAddInputPrevTx :: !BS.ByteString - , txAddInputPrevVout :: {-# UNPACK #-} !Word32 - , txAddInputSequence :: {-# UNPACK #-} !Word32 - } deriving stock (Eq, Show, Generic) + { tx_add_input_channel_id :: !ChannelId + , tx_add_input_serial_id :: !SerialId + , tx_add_input_prevtx :: !BS.ByteString + -- ^ the serialized previous transaction + , tx_add_input_prevtx_vout :: {-# UNPACK #-} !Word32 + , tx_add_input_sequence :: {-# UNPACK #-} !Word32 + , tx_add_input_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxAddInput --- | The tx_add_output message (type 67). --- --- Adds a transaction output to the collaborative transaction. +-- | The @tx_add_output@ message (type 67). data TxAddOutput = TxAddOutput - { txAddOutputChannelId :: !ChannelId - , txAddOutputSerialId :: !SerialId - , txAddOutputSats :: {-# UNPACK #-} !Satoshi - , txAddOutputScript :: !ScriptPubKey - } deriving stock (Eq, Show, Generic) + { tx_add_output_channel_id :: !ChannelId + , tx_add_output_serial_id :: !SerialId + , tx_add_output_sats :: {-# UNPACK #-} !Satoshi + , tx_add_output_script :: !ScriptPubKey + , tx_add_output_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxAddOutput --- | The tx_remove_input message (type 68). --- --- Removes a previously added input from the collaborative transaction. +-- | The @tx_remove_input@ message (type 68). data TxRemoveInput = TxRemoveInput - { txRemoveInputChannelId :: !ChannelId - , txRemoveInputSerialId :: !SerialId - } deriving stock (Eq, Show, Generic) + { tx_remove_input_channel_id :: !ChannelId + , tx_remove_input_serial_id :: !SerialId + , tx_remove_input_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxRemoveInput --- | The tx_remove_output message (type 69). --- --- Removes a previously added output from the collaborative transaction. +-- | The @tx_remove_output@ message (type 69). data TxRemoveOutput = TxRemoveOutput - { txRemoveOutputChannelId :: !ChannelId - , txRemoveOutputSerialId :: !SerialId - } deriving stock (Eq, Show, Generic) + { tx_remove_output_channel_id :: !ChannelId + , tx_remove_output_serial_id :: !SerialId + , tx_remove_output_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxRemoveOutput --- | The tx_complete message (type 70). --- --- Signals the conclusion of a peer's transaction contributions. +-- | The @tx_complete@ message (type 70). data TxComplete = TxComplete - { txCompleteChannelId :: !ChannelId - } deriving stock (Eq, Show, Generic) + { tx_complete_channel_id :: !ChannelId + , tx_complete_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxComplete --- | Witness data for tx_signatures. -data Witness = Witness - { witnessData :: !BS.ByteString - } deriving stock (Eq, Show, Generic) - -instance NFData Witness - --- | The tx_signatures message (type 71). --- --- Contains signatures for the collaborative transaction. +-- | The @tx_signatures@ message (type 71). data TxSignatures = TxSignatures - { txSignaturesChannelId :: !ChannelId - , txSignaturesTxid :: !TxId - , txSignaturesWitnesses :: ![Witness] - } deriving stock (Eq, Show, Generic) + { tx_signatures_channel_id :: !ChannelId + , tx_signatures_txid :: !TxId + , tx_signatures_witnesses :: ![Witness] + -- ^ ordered by the @serial_id@ of the inputs they spend + , tx_signatures_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxSignatures --- | The tx_init_rbf message (type 72). --- --- Initiates a replacement of the transaction after it's been completed. +-- | The @tx_init_rbf@ message (type 72). data TxInitRbf = TxInitRbf - { txInitRbfChannelId :: !ChannelId - , txInitRbfLocktime :: {-# UNPACK #-} !Word32 - , txInitRbfFeerate :: {-# UNPACK #-} !Word32 - , txInitRbfTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { tx_init_rbf_channel_id :: !ChannelId + , tx_init_rbf_locktime :: {-# UNPACK #-} !Word32 + , tx_init_rbf_feerate :: {-# UNPACK #-} !Word32 + , tx_init_rbf_funding_output_contribution :: !(Maybe Int64) + -- ^ TLV type 0, in satoshis + , tx_init_rbf_require_confirmed_inputs :: !Bool + -- ^ TLV type 2 + , tx_init_rbf_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxInitRbf --- | The tx_ack_rbf message (type 73). --- --- Acknowledges an RBF attempt. +-- | The @tx_ack_rbf@ message (type 73). data TxAckRbf = TxAckRbf - { txAckRbfChannelId :: !ChannelId - , txAckRbfTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { tx_ack_rbf_channel_id :: !ChannelId + , tx_ack_rbf_funding_output_contribution :: !(Maybe Int64) + -- ^ TLV type 0, in satoshis + , tx_ack_rbf_require_confirmed_inputs :: !Bool + -- ^ TLV type 2 + , tx_ack_rbf_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxAckRbf --- | The tx_abort message (type 74). --- --- Aborts the collaborative transaction negotiation. +-- | The @tx_abort@ message (type 74). data TxAbort = TxAbort - { txAbortChannelId :: !ChannelId - , txAbortData :: !BS.ByteString - } deriving stock (Eq, Show, Generic) + { tx_abort_channel_id :: !ChannelId + , tx_abort_data :: !BS.ByteString + , tx_abort_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData TxAbort --- Channel close --------------------------------------------------------------- +-- channel quiescence --------------------------------------------------------- --- | The stfu message (type 2). --- --- Indicates "SomeThing Fundamental is Underway" - used for channel --- quiescence. +-- | The @stfu@ message (type 2). data Stfu = Stfu - { stfuChannelId :: !ChannelId - , stfuInitiator :: !Initiator - } deriving stock (Eq, Show, Generic) + { stfu_channel_id :: !ChannelId + , stfu_initiator :: !Bool + -- ^ whether the sender initiated quiescence (sent as 1 or 0) + , stfu_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData Stfu --- | The shutdown message (type 38). --- --- Initiates closing of the channel. +-- channel close -------------------------------------------------------------- + +-- | The @shutdown@ message (type 38). data Shutdown = Shutdown - { shutdownChannelId :: !ChannelId - , shutdownScriptPubkey :: !ScriptPubKey - } deriving stock (Eq, Show, Generic) + { shutdown_channel_id :: !ChannelId + , shutdown_scriptpubkey :: !ScriptPubKey + , shutdown_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData Shutdown --- | The closing_signed message (type 39). --- --- Used in legacy closing negotiation. -data ClosingSigned = ClosingSigned - { closingSignedChannelId :: !ChannelId - , closingSignedFeeSatoshi :: {-# UNPACK #-} !Satoshi - , closingSignedSignature :: !Signature - , closingSignedTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) - -instance NFData ClosingSigned - --- | The closing_complete message (type 40). --- --- Proposes a closing transaction in the new closing protocol. +-- | The @closing_complete@ message (type 40). data ClosingComplete = ClosingComplete - { closingCompleteChannelId :: !ChannelId - , closingCompleteCloserScript :: !ScriptPubKey - , closingCompleteCloseeScript :: !ScriptPubKey - , closingCompleteFeeSatoshi :: {-# UNPACK #-} !Satoshi - , closingCompleteLocktime :: {-# UNPACK #-} !Word32 - , closingCompleteTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { closing_complete_channel_id :: !ChannelId + , closing_complete_closer_scriptpubkey :: !ScriptPubKey + , closing_complete_closee_scriptpubkey :: !ScriptPubKey + , closing_complete_fee_satoshis :: {-# UNPACK #-} !Satoshi + , closing_complete_locktime :: {-# UNPACK #-} !Word32 + , closing_complete_closer_output_only :: !(Maybe Signature) + -- ^ TLV type 1 + , closing_complete_closee_output_only :: !(Maybe Signature) + -- ^ TLV type 2 + , closing_complete_closer_and_closee_outputs :: !(Maybe Signature) + -- ^ TLV type 3 + , closing_complete_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData ClosingComplete --- | The closing_sig message (type 41). --- --- Signs a closing transaction in the new closing protocol. +-- | The @closing_sig@ message (type 41). A valid @closing_sig@ carries +-- exactly one signature, in a field set in the @closing_complete@ it +-- answers; checking that is left to the caller. data ClosingSig = ClosingSig - { closingSigChannelId :: !ChannelId - , closingSigCloserScript :: !ScriptPubKey - , closingSigCloseeScript :: !ScriptPubKey - , closingSigFeeSatoshi :: {-# UNPACK #-} !Satoshi - , closingSigLocktime :: {-# UNPACK #-} !Word32 - , closingSigTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { closing_sig_channel_id :: !ChannelId + , closing_sig_closer_scriptpubkey :: !ScriptPubKey + , closing_sig_closee_scriptpubkey :: !ScriptPubKey + , closing_sig_fee_satoshis :: {-# UNPACK #-} !Satoshi + , closing_sig_locktime :: {-# UNPACK #-} !Word32 + , closing_sig_closer_output_only :: !(Maybe Signature) + -- ^ TLV type 1 + , closing_sig_closee_output_only :: !(Maybe Signature) + -- ^ TLV type 2 + , closing_sig_closer_and_closee_outputs :: !(Maybe Signature) + -- ^ TLV type 3 + , closing_sig_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData ClosingSig --- Normal operation ------------------------------------------------------------ +-- | The legacy @closing_signed@ message (type 39), used when +-- @option_simple_close@ is not negotiated. +data ClosingSigned = ClosingSigned + { closing_signed_channel_id :: !ChannelId + , closing_signed_fee_satoshis :: {-# UNPACK #-} !Satoshi + , closing_signed_signature :: !Signature + , closing_signed_fee_range :: !(Maybe FeeRange) + -- ^ TLV type 1 + , closing_signed_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) --- | The update_add_htlc message (type 128). --- --- Offers an HTLC to the other node, redeemable in return for a payment --- preimage. +instance NFData ClosingSigned + +-- normal operation ----------------------------------------------------------- + +-- | The @update_add_htlc@ message (type 128). data UpdateAddHtlc = UpdateAddHtlc - { updateAddHtlcChannelId :: !ChannelId - , updateAddHtlcId :: !HtlcId - , updateAddHtlcAmountMsat :: {-# UNPACK #-} !MilliSatoshi - , updateAddHtlcPaymentHash :: !PaymentHash - , updateAddHtlcCltvExpiry :: {-# UNPACK #-} !Word32 - , updateAddHtlcOnionPacket :: !OnionPacket - , updateAddHtlcTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { update_add_htlc_channel_id :: !ChannelId + , update_add_htlc_id :: !HtlcId + , update_add_htlc_amount_msat :: {-# UNPACK #-} !MilliSatoshi + , update_add_htlc_payment_hash :: !PaymentHash + , update_add_htlc_cltv_expiry :: {-# UNPACK #-} !Word32 + , update_add_htlc_onion_routing_packet :: !OnionRoutingPacket + , update_add_htlc_path_key :: !(Maybe Point) + -- ^ the @blinded_path@ record (TLV type 0) + , update_add_htlc_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData UpdateAddHtlc --- | The update_fulfill_htlc message (type 130). --- --- Supplies the preimage to fulfill an HTLC. +-- | The @update_fulfill_htlc@ message (type 130). data UpdateFulfillHtlc = UpdateFulfillHtlc - { updateFulfillHtlcChannelId :: !ChannelId - , updateFulfillHtlcId :: !HtlcId - , updateFulfillHtlcPaymentPreimage :: !PaymentPreimage - , updateFulfillHtlcTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { update_fulfill_htlc_channel_id :: !ChannelId + , update_fulfill_htlc_id :: !HtlcId + , update_fulfill_htlc_payment_preimage :: !PaymentPreimage + , update_fulfill_htlc_attribution_data :: !(Maybe AttributionData) + -- ^ TLV type 1 + , update_fulfill_htlc_fulfillment_payload :: !(Maybe BS.ByteString) + -- ^ TLV type 3 + , update_fulfill_htlc_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData UpdateFulfillHtlc --- | The update_fail_htlc message (type 131). --- --- Indicates an HTLC has failed. +-- | The @update_fail_htlc@ message (type 131). data UpdateFailHtlc = UpdateFailHtlc - { updateFailHtlcChannelId :: !ChannelId - , updateFailHtlcId :: !HtlcId - , updateFailHtlcReason :: !BS.ByteString - , updateFailHtlcTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { update_fail_htlc_channel_id :: !ChannelId + , update_fail_htlc_id :: !HtlcId + , update_fail_htlc_reason :: !BS.ByteString + , update_fail_htlc_attribution_data :: !(Maybe AttributionData) + -- ^ TLV type 1 + , update_fail_htlc_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData UpdateFailHtlc --- | The update_fail_malformed_htlc message (type 135). --- --- Indicates an HTLC could not be parsed. +-- | The @update_fail_malformed_htlc@ message (type 135). data UpdateFailMalformedHtlc = UpdateFailMalformedHtlc - { updateFailMalformedHtlcChannelId :: !ChannelId - , updateFailMalformedHtlcId :: !HtlcId - , updateFailMalformedHtlcSha256Onion :: !PaymentHash - , updateFailMalformedHtlcFailureCode :: {-# UNPACK #-} !Word16 - } deriving stock (Eq, Show, Generic) + { update_fail_malformed_htlc_channel_id :: !ChannelId + , update_fail_malformed_htlc_id :: !HtlcId + , update_fail_malformed_htlc_sha256_of_onion :: !OnionHash + , update_fail_malformed_htlc_failure_code :: {-# UNPACK #-} !Word16 + , update_fail_malformed_htlc_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData UpdateFailMalformedHtlc --- | The commitment_signed message (type 132). --- --- Applies pending changes and provides signatures for the commitment --- transaction. +-- | The @commitment_signed@ message (type 132). data CommitmentSigned = CommitmentSigned - { commitmentSignedChannelId :: !ChannelId - , commitmentSignedSignature :: !Signature - , commitmentSignedHtlcSignatures :: ![Signature] - } deriving stock (Eq, Show, Generic) + { commitment_signed_channel_id :: !ChannelId + , commitment_signed_signature :: !Signature + , commitment_signed_htlc_signatures :: ![Signature] + , commitment_signed_funding_txid :: !(Maybe TxId) + -- ^ TLV type 1 + , commitment_signed_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData CommitmentSigned --- | The revoke_and_ack message (type 133). --- --- Revokes the previous commitment transaction and acknowledges receipt --- of the commitment_signed. +-- | The @revoke_and_ack@ message (type 133). data RevokeAndAck = RevokeAndAck - { revokeAndAckChannelId :: !ChannelId - , revokeAndAckPerCommitmentSecret :: !PerCommitmentSecret - , revokeAndAckNextPerCommitPoint :: !Point - } deriving stock (Eq, Show, Generic) + { revoke_and_ack_channel_id :: !ChannelId + , revoke_and_ack_per_commitment_secret :: !PerCommitmentSecret + , revoke_and_ack_next_per_commitment_point :: !Point + , revoke_and_ack_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData RevokeAndAck --- | The update_fee message (type 134). --- --- Updates the fee rate for commitment transactions. +-- | The @update_fee@ message (type 134). data UpdateFee = UpdateFee - { updateFeeChannelId :: !ChannelId - , updateFeeFeeratePerKw :: {-# UNPACK #-} !Word32 - } deriving stock (Eq, Show, Generic) + { update_fee_channel_id :: !ChannelId + , update_fee_feerate_per_kw :: {-# UNPACK #-} !Word32 + , update_fee_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData UpdateFee --- Reestablishment ------------------------------------------------------------- +-- message retransmission ----------------------------------------------------- --- | The channel_reestablish message (type 136). --- --- Used to re-establish a channel after reconnection. +-- | The @channel_reestablish@ message (type 136). data ChannelReestablish = ChannelReestablish - { channelReestablishChannelId :: !ChannelId - , channelReestablishNextCommitNum :: {-# UNPACK #-} !Word64 - , channelReestablishNextRevocationNum :: {-# UNPACK #-} !Word64 - , channelReestablishYourLastCommitSecret :: !PerCommitmentSecret - , channelReestablishMyCurrentCommitPoint :: !Point - , channelReestablishTlvs :: !TlvStream - } deriving stock (Eq, Show, Generic) + { channel_reestablish_channel_id :: !ChannelId + , channel_reestablish_next_commitment_number + :: {-# UNPACK #-} !Word64 + , channel_reestablish_next_revocation_number + :: {-# UNPACK #-} !Word64 + , channel_reestablish_your_last_per_commitment_secret + :: !PerCommitmentSecret + , channel_reestablish_my_current_per_commitment_point :: !Point + , channel_reestablish_next_funding + :: !(Maybe NextFunding) + -- ^ TLV type 1 + , channel_reestablish_tlvs :: !TlvStream + -- ^ unknown TLV records + } deriving (Eq, Show, Generic) instance NFData ChannelReestablish --- Top-level message type ------------------------------------------------------ +-- messages ------------------------------------------------------------------- --- | All BOLT #2 messages. +-- | A BOLT #2 message. data Message - -- Channel establishment v1 - = MsgOpenChannelVal !OpenChannel - | MsgAcceptChannelVal !AcceptChannel - | MsgFundingCreatedVal !FundingCreated - | MsgFundingSignedVal !FundingSigned - | MsgChannelReadyVal !ChannelReady - -- Channel establishment v2 - | MsgOpenChannel2Val !OpenChannel2 - | MsgAcceptChannel2Val !AcceptChannel2 - | MsgTxAddInputVal !TxAddInput - | MsgTxAddOutputVal !TxAddOutput - | MsgTxRemoveInputVal !TxRemoveInput - | MsgTxRemoveOutputVal !TxRemoveOutput - | MsgTxCompleteVal !TxComplete - | MsgTxSignaturesVal !TxSignatures - | MsgTxInitRbfVal !TxInitRbf - | MsgTxAckRbfVal !TxAckRbf - | MsgTxAbortVal !TxAbort - -- Channel close - | MsgStfuVal !Stfu - | MsgShutdownVal !Shutdown - | MsgClosingSignedVal !ClosingSigned - | MsgClosingCompleteVal !ClosingComplete - | MsgClosingSigVal !ClosingSig - -- Normal operation - | MsgUpdateAddHtlcVal !UpdateAddHtlc - | MsgUpdateFulfillHtlcVal !UpdateFulfillHtlc - | MsgUpdateFailHtlcVal !UpdateFailHtlc - | MsgUpdateFailMalformedHtlcVal !UpdateFailMalformedHtlc - | MsgCommitmentSignedVal !CommitmentSigned - | MsgRevokeAndAckVal !RevokeAndAck - | MsgUpdateFeeVal !UpdateFee - -- Reestablishment - | MsgChannelReestablishVal !ChannelReestablish - deriving stock (Eq, Show, Generic) + = MsgOpenChannel !OpenChannel + | MsgAcceptChannel !AcceptChannel + | MsgFundingCreated !FundingCreated + | MsgFundingSigned !FundingSigned + | MsgChannelReady !ChannelReady + | MsgOpenChannel2 !OpenChannel2 + | MsgAcceptChannel2 !AcceptChannel2 + | MsgTxAddInput !TxAddInput + | MsgTxAddOutput !TxAddOutput + | MsgTxRemoveInput !TxRemoveInput + | MsgTxRemoveOutput !TxRemoveOutput + | MsgTxComplete !TxComplete + | MsgTxSignatures !TxSignatures + | MsgTxInitRbf !TxInitRbf + | MsgTxAckRbf !TxAckRbf + | MsgTxAbort !TxAbort + | MsgStfu !Stfu + | MsgShutdown !Shutdown + | MsgClosingComplete !ClosingComplete + | MsgClosingSig !ClosingSig + | MsgClosingSigned !ClosingSigned + | MsgUpdateAddHtlc !UpdateAddHtlc + | MsgUpdateFulfillHtlc !UpdateFulfillHtlc + | MsgUpdateFailHtlc !UpdateFailHtlc + | MsgUpdateFailMalformedHtlc !UpdateFailMalformedHtlc + | MsgCommitmentSigned !CommitmentSigned + | MsgRevokeAndAck !RevokeAndAck + | MsgUpdateFee !UpdateFee + | MsgChannelReestablish !ChannelReestablish + deriving (Eq, Show, Generic) instance NFData Message + +-- | The wire type of a 'Message'. +-- +-- >>> message_type (MsgTxComplete (TxComplete cid BOLT1.empty_tlv_stream)) +-- 70 +message_type :: Message -> Word16 +message_type m = case m of + MsgOpenChannel {} -> 32 + MsgAcceptChannel {} -> 33 + MsgFundingCreated {} -> 34 + MsgFundingSigned {} -> 35 + MsgChannelReady {} -> 36 + MsgOpenChannel2 {} -> 64 + MsgAcceptChannel2 {} -> 65 + MsgTxAddInput {} -> 66 + MsgTxAddOutput {} -> 67 + MsgTxRemoveInput {} -> 68 + MsgTxRemoveOutput {} -> 69 + MsgTxComplete {} -> 70 + MsgTxSignatures {} -> 71 + MsgTxInitRbf {} -> 72 + MsgTxAckRbf {} -> 73 + MsgTxAbort {} -> 74 + MsgStfu {} -> 2 + MsgShutdown {} -> 38 + MsgClosingComplete {} -> 40 + MsgClosingSig {} -> 41 + MsgClosingSigned {} -> 39 + MsgUpdateAddHtlc {} -> 128 + MsgUpdateFulfillHtlc {} -> 130 + MsgUpdateFailHtlc {} -> 131 + MsgUpdateFailMalformedHtlc {} -> 135 + MsgCommitmentSigned {} -> 132 + MsgRevokeAndAck {} -> 133 + MsgUpdateFee {} -> 134 + MsgChannelReestablish {} -> 136 diff --git a/lib/Lightning/Protocol/BOLT2/Types.hs b/lib/Lightning/Protocol/BOLT2/Types.hs @@ -1,8 +1,5 @@ -{-# OPTIONS_HADDOCK prune #-} -{-# LANGUAGE BangPatterns #-} +{-# OPTIONS_HADDOCK hide #-} {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module: Lightning.Protocol.BOLT2.Types @@ -10,246 +7,189 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> -- --- Core types for BOLT #2 peer protocol. --- --- This module provides newtypes for identifiers, amounts, hashes, and --- keys used in the Lightning Network peer protocol. +-- Field types specific to BOLT #2. module Lightning.Protocol.BOLT2.Types ( - -- * Identifiers (re-exported from BOLT1) - ChannelId(..) - , channelId - , unChannelId - - -- * Amounts (re-exported from BOLT1) - , Satoshi(..) - , MilliSatoshi(..) - , satToMsat - , msatToSat - - -- * Cryptographic types (re-exported from BOLT1) - , Signature(..) - , signature - , unSignature - , Point(..) - , point - , unPoint - , PaymentHash(..) - , paymentHash - , unPaymentHash - , PaymentPreimage(..) - , paymentPreimage - , unPaymentPreimage - , PerCommitmentSecret(..) - , perCommitmentSecret - , unPerCommitmentSecret - - -- * Chain types (re-exported from BOLT1) - , ChainHash(..) - , chainHash - , unChainHash - , ShortChannelId(..) - , shortChannelId - , scidBlockHeight - , scidTxIndex - , scidOutputIndex - - -- * Transaction types - , TxId(..) - , mkTxId - , OutPoint(..) + HtlcId(..) + , SerialId(..) , ScriptPubKey - , scriptPubKey - , unScriptPubKey - - -- * Protocol identifiers - , HtlcId - , htlcId - , unHtlcId - , SerialId - , serialId - , unSerialId - - -- * Quiescence - , Initiator(..) - - -- * Protocol types - , FeatureBits - , featureBits - , unFeatureBits - , OnionPacket - , onionPacket - , unOnionPacket - - -- * Constants - , channelIdLen - , signatureLen - , pointLen - , chainHashLen - , shortChannelIdLen - , paymentHashLen - , paymentPreimageLen - , onionPacketLen - , perCommitmentSecretLen + , script_pubkey + , un_script_pubkey + , Witness + , witness + , un_witness + , OnionRoutingPacket + , onion_routing_packet + , un_onion_routing_packet + , OnionHash + , onion_hash + , un_onion_hash + , AttributionData + , attribution_data + , un_attribution_data + , FeeRange(..) + , NextFunding(..) ) where -import Bitcoin.Prim.Tx (TxId(..), mkTxId, OutPoint(..)) +import Bitcoin.Prim.Tx (TxId) import Control.DeepSeq (NFData) import qualified Data.ByteString as BS -import Data.Word (Word64) +import Data.Word (Word8, Word64) import GHC.Generics (Generic) -import Lightning.Protocol.BOLT1.Prim - ( ChannelId(..), channelId, unChannelId - , Satoshi(..), MilliSatoshi(..), satToMsat, msatToSat - , Signature(..), signature, unSignature - , Point(..), point, unPoint - , PaymentHash(..), paymentHash, unPaymentHash - , PaymentPreimage(..), paymentPreimage, unPaymentPreimage - , PerCommitmentSecret(..), perCommitmentSecret - , unPerCommitmentSecret - , ChainHash(..), chainHash, unChainHash - , ShortChannelId(..), shortChannelId - , scidBlockHeight, scidTxIndex, scidOutputIndex - ) - --- constants ------------------------------------------------------------------- - --- | Length of a channel_id in bytes (32). -channelIdLen :: Int -channelIdLen = 32 -{-# INLINE channelIdLen #-} - --- | Length of a signature in bytes (64, compact format). -signatureLen :: Int -signatureLen = 64 -{-# INLINE signatureLen #-} - --- | Length of a compressed secp256k1 public key in bytes (33). -pointLen :: Int -pointLen = 33 -{-# INLINE pointLen #-} - --- | Length of a chain hash in bytes (32). -chainHashLen :: Int -chainHashLen = 32 -{-# INLINE chainHashLen #-} - --- | Length of a short_channel_id in bytes (8). -shortChannelIdLen :: Int -shortChannelIdLen = 8 -{-# INLINE shortChannelIdLen #-} - --- | Length of a payment hash in bytes (32). -paymentHashLen :: Int -paymentHashLen = 32 -{-# INLINE paymentHashLen #-} - --- | Length of a payment preimage in bytes (32). -paymentPreimageLen :: Int -paymentPreimageLen = 32 -{-# INLINE paymentPreimageLen #-} - --- | Length of an onion routing packet in bytes (1366). -onionPacketLen :: Int -onionPacketLen = 1366 -{-# INLINE onionPacketLen #-} - --- | Length of a per-commitment secret in bytes (32). -perCommitmentSecretLen :: Int -perCommitmentSecretLen = 32 -{-# INLINE perCommitmentSecretLen #-} - --- transaction types ----------------------------------------------------------- - --- | A script pubkey (output script). -newtype ScriptPubKey = ScriptPubKey BS.ByteString - deriving stock (Eq, Ord, Show, Generic) - deriving newtype NFData - --- | Construct a 'ScriptPubKey' from a 'BS.ByteString'. -scriptPubKey :: BS.ByteString -> ScriptPubKey -scriptPubKey = ScriptPubKey -{-# INLINE scriptPubKey #-} - --- | Extract the underlying 'BS.ByteString' from a 'ScriptPubKey'. -unScriptPubKey :: ScriptPubKey -> BS.ByteString -unScriptPubKey (ScriptPubKey bs) = bs -{-# INLINE unScriptPubKey #-} - --- protocol identifiers ------------------------------------------------------- +import Lightning.Protocol.BOLT1 (Satoshi) --- | An HTLC identifier, unique per channel per direction. +-- | An HTLC identifier (the @id@ field of the HTLC messages). The +-- offerer numbers its HTLCs from 0, incrementing by 1. newtype HtlcId = HtlcId Word64 - deriving stock (Eq, Ord, Show, Generic) - deriving newtype NFData + deriving (Eq, Ord, Show, Generic) --- | Construct an 'HtlcId' from a 'Word64'. -htlcId :: Word64 -> HtlcId -htlcId = HtlcId -{-# INLINE htlcId #-} +instance NFData HtlcId --- | Extract the underlying 'Word64' from an 'HtlcId'. -unHtlcId :: HtlcId -> Word64 -unHtlcId (HtlcId w) = w -{-# INLINE unHtlcId #-} - --- | A serial identifier for interactive transaction construction. +-- | The @serial_id@ of an input or output in interactive transaction +-- construction. The initiator sends even values, the non-initiator +-- odd ones. newtype SerialId = SerialId Word64 - deriving stock (Eq, Ord, Show, Generic) - deriving newtype NFData - --- | Construct a 'SerialId' from a 'Word64'. -serialId :: Word64 -> SerialId -serialId = SerialId -{-# INLINE serialId #-} - --- | Extract the underlying 'Word64' from a 'SerialId'. -unSerialId :: SerialId -> Word64 -unSerialId (SerialId w) = w -{-# INLINE unSerialId #-} - --- quiescence ----------------------------------------------------------------- + deriving (Eq, Ord, Show, Generic) --- | Role in quiescence (STFU) protocol. -data Initiator - = IsInitiator -- ^ This node initiated quiescence. - | NotInitiator -- ^ This node did not initiate quiescence. - deriving stock (Eq, Ord, Show, Generic) +instance NFData SerialId -instance NFData Initiator - --- protocol types -------------------------------------------------------------- - --- | Feature bits (variable length). -newtype FeatureBits = FeatureBits BS.ByteString - deriving stock (Eq, Ord, Show, Generic) - deriving newtype NFData - --- | Construct 'FeatureBits' from a 'BS.ByteString'. -featureBits :: BS.ByteString -> FeatureBits -featureBits = FeatureBits -{-# INLINE featureBits #-} - --- | Extract the underlying 'BS.ByteString' from 'FeatureBits'. -unFeatureBits :: FeatureBits -> BS.ByteString -unFeatureBits (FeatureBits bs) = bs -{-# INLINE unFeatureBits #-} +-- | A scriptPubKey, at most 65535 bytes (the most a u16 length prefix +-- can describe). +newtype ScriptPubKey = ScriptPubKey BS.ByteString + deriving (Eq, Ord, Show, Generic) --- | A 1366-byte onion routing packet. -newtype OnionPacket = OnionPacket BS.ByteString - deriving stock (Eq, Ord, Show, Generic) - deriving newtype NFData +instance NFData ScriptPubKey --- | Construct an 'OnionPacket' from a 1366-byte 'BS.ByteString'. +-- | Construct a 'ScriptPubKey', failing above 65535 bytes. The script +-- itself isn't checked; the forms acceptable in @shutdown@ depend on +-- negotiated features. +-- +-- >>> script_pubkey "\x51\x02\x4e\x73" +-- Just (ScriptPubKey "Q\STXNs") +-- >>> script_pubkey (BS.replicate 65536 0x00) +-- Nothing +script_pubkey :: BS.ByteString -> Maybe ScriptPubKey +script_pubkey bs + | BS.length bs <= 65535 = Just (ScriptPubKey bs) + | otherwise = Nothing +{-# INLINE script_pubkey #-} + +-- | The bytes of a 'ScriptPubKey'. +un_script_pubkey :: ScriptPubKey -> BS.ByteString +un_script_pubkey (ScriptPubKey bs) = bs +{-# INLINE un_script_pubkey #-} + +-- | The serialized witness of one input in @tx_signatures@ (encoded as +-- in Bitcoin's wire protocol), at most 65535 bytes. +newtype Witness = Witness BS.ByteString + deriving (Eq, Ord, Show, Generic) + +instance NFData Witness + +-- | Construct a 'Witness', failing above 65535 bytes. The contents +-- aren't checked. +-- +-- >>> witness "\x01\x01\x2a" +-- Just (Witness "\SOH\SOH*") +witness :: BS.ByteString -> Maybe Witness +witness bs + | BS.length bs <= 65535 = Just (Witness bs) + | otherwise = Nothing +{-# INLINE witness #-} + +-- | The bytes of a 'Witness'. +un_witness :: Witness -> BS.ByteString +un_witness (Witness bs) = bs +{-# INLINE un_witness #-} + +-- | The 1366-byte @onion_routing_packet@ of @update_add_htlc@, kept +-- opaque (BOLT #4 defines its structure). +newtype OnionRoutingPacket = OnionRoutingPacket BS.ByteString + deriving (Eq, Ord, Show, Generic) + +instance NFData OnionRoutingPacket + +-- | Construct an 'OnionRoutingPacket' from exactly 1366 bytes. +-- +-- >>> let bs = BS.replicate 1366 0x00 +-- >>> fmap un_onion_routing_packet (onion_routing_packet bs) == Just bs +-- True +-- >>> onion_routing_packet (BS.take 1365 bs) +-- Nothing +onion_routing_packet :: BS.ByteString -> Maybe OnionRoutingPacket +onion_routing_packet bs + | BS.length bs == 1366 = Just (OnionRoutingPacket bs) + | otherwise = Nothing +{-# INLINE onion_routing_packet #-} + +-- | The bytes of an 'OnionRoutingPacket'. +un_onion_routing_packet :: OnionRoutingPacket -> BS.ByteString +un_onion_routing_packet (OnionRoutingPacket bs) = bs +{-# INLINE un_onion_routing_packet #-} + +-- | The SHA256 hash of an onion routing packet (the @sha256_of_onion@ +-- field of @update_fail_malformed_htlc@). +newtype OnionHash = OnionHash BS.ByteString + deriving (Eq, Ord, Show, Generic) + +instance NFData OnionHash + +-- | Construct an 'OnionHash' from exactly 32 bytes. +-- +-- >>> onion_hash (BS.replicate 31 0x00) +-- Nothing +onion_hash :: BS.ByteString -> Maybe OnionHash +onion_hash bs + | BS.length bs == 32 = Just (OnionHash bs) + | otherwise = Nothing +{-# INLINE onion_hash #-} + +-- | The bytes of an 'OnionHash'. +un_onion_hash :: OnionHash -> BS.ByteString +un_onion_hash (OnionHash bs) = bs +{-# INLINE un_onion_hash #-} + +-- | The 920-byte @attribution_data@ of @update_fulfill_htlc@ and +-- @update_fail_htlc@: twenty u32 HTLC hold times followed by 210 +-- four-byte truncated HMACs. Kept opaque (BOLT #4 defines its use). +newtype AttributionData = AttributionData BS.ByteString + deriving (Eq, Ord, Show, Generic) + +instance NFData AttributionData + +-- | Construct an 'AttributionData' from exactly 920 bytes. -- --- Returns 'Nothing' if the input is not exactly 1366 bytes. -onionPacket :: BS.ByteString -> Maybe OnionPacket -onionPacket !bs - | BS.length bs == onionPacketLen = Just $! OnionPacket bs - | otherwise = Nothing -{-# INLINABLE onionPacket #-} - --- | Extract the underlying 'BS.ByteString' from an 'OnionPacket'. -unOnionPacket :: OnionPacket -> BS.ByteString -unOnionPacket (OnionPacket bs) = bs -{-# INLINE unOnionPacket #-} +-- >>> let bs = BS.replicate 920 0x00 +-- >>> fmap un_attribution_data (attribution_data bs) == Just bs +-- True +-- >>> attribution_data (BS.drop 1 bs) +-- Nothing +attribution_data :: BS.ByteString -> Maybe AttributionData +attribution_data bs + | BS.length bs == 920 = Just (AttributionData bs) + | otherwise = Nothing +{-# INLINE attribution_data #-} + +-- | The bytes of an 'AttributionData'. +un_attribution_data :: AttributionData -> BS.ByteString +un_attribution_data (AttributionData bs) = bs +{-# INLINE un_attribution_data #-} + +-- | The @fee_range@ of @closing_signed@: the fees the sender is +-- prepared to pay for the closing transaction. +data FeeRange = FeeRange + { fee_range_min_fee_satoshis :: {-# UNPACK #-} !Satoshi + , fee_range_max_fee_satoshis :: {-# UNPACK #-} !Satoshi + } deriving (Eq, Show, Generic) + +instance NFData FeeRange + +-- | The @next_funding@ record of @channel_reestablish@: an interactive +-- funding transaction whose signatures are still being exchanged. +data NextFunding = NextFunding + { next_funding_txid :: !TxId + , next_funding_retransmit_flags :: {-# UNPACK #-} !Word8 + -- ^ bit 0: the sender wants @commitment_signed@ retransmitted + } deriving (Eq, Show, Generic) + +instance NFData NextFunding diff --git a/ppad-bolt2.cabal b/ppad-bolt2.cabal @@ -1,6 +1,6 @@ cabal-version: 3.0 name: ppad-bolt2 -version: 0.0.1 +version: 0.1.0 synopsis: Peer protocol per BOLT #2 license: MIT license-file: LICENSE @@ -8,7 +8,7 @@ author: Jared Tobin maintainer: jared@ppad.tech category: Cryptography build-type: Simple -tested-with: GHC == 9.10.3 +tested-with: GHC == { 9.10.3 } extra-doc-files: CHANGELOG description: Peer protocol, per @@ -25,6 +25,7 @@ library -Wall exposed-modules: Lightning.Protocol.BOLT2 + other-modules: Lightning.Protocol.BOLT2.Codec Lightning.Protocol.BOLT2.Messages Lightning.Protocol.BOLT2.Types @@ -32,8 +33,9 @@ library base >= 4.9 && < 5 , bytestring >= 0.9 && < 0.13 , deepseq >= 1.4 && < 1.6 - , ppad-bolt1 >= 0.0.1 && < 0.1 - , ppad-tx >= 0.1 && < 0.2 + , ppad-bolt1 >= 0.1 && < 0.2 + , ppad-bolt9 >= 0.1 && < 0.2 + , ppad-tx >= 0.2 && < 0.3 test-suite bolt2-tests type: exitcode-stdio-1.0 @@ -42,7 +44,7 @@ test-suite bolt2-tests main-is: Main.hs ghc-options: - -rtsopts -Wall -O2 + -rtsopts -Wall build-depends: base @@ -50,6 +52,8 @@ test-suite bolt2-tests , ppad-base16 , ppad-bolt1 , ppad-bolt2 + , ppad-bolt9 + , ppad-tx , tasty , tasty-hunit , tasty-quickcheck @@ -59,31 +63,35 @@ benchmark bolt2-bench default-language: Haskell2010 hs-source-dirs: bench main-is: Main.hs + other-modules: Fixtures ghc-options: - -rtsopts -O2 -Wall -fno-warn-orphans + -rtsopts -O2 -Wall build-depends: base , bytestring , criterion - , deepseq , ppad-bolt1 , ppad-bolt2 + , ppad-bolt9 + , ppad-tx benchmark bolt2-weigh type: exitcode-stdio-1.0 default-language: Haskell2010 hs-source-dirs: bench main-is: Weight.hs + other-modules: Fixtures ghc-options: - -rtsopts -O2 -Wall -fno-warn-orphans + -rtsopts -O2 -Wall build-depends: base , bytestring - , deepseq , ppad-bolt1 , ppad-bolt2 + , ppad-bolt9 + , ppad-tx , weigh