commit 3fca308b65c9f8a40c88c2bedb53eb3bcfd1e412
parent d1c7117b5bc7ad0292dcef0c4a6651218f3f6ced
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 25 Jan 2026 11:09:02 +0400
impl: unified Codec.hs with all BOLT2 message codecs
Integrates encode/decode functions for all 29 BOLT2 message types:
Channel establishment v1 (32-36):
OpenChannel, AcceptChannel, FundingCreated, FundingSigned, ChannelReady
Channel establishment v2 / interactive-tx (64-74):
OpenChannel2, AcceptChannel2, TxAddInput, TxAddOutput,
TxRemoveInput, TxRemoveOutput, TxComplete, TxSignatures,
TxInitRbf, TxAckRbf, TxAbort
Channel close (2, 38-41):
Stfu, Shutdown, ClosingSigned, ClosingComplete, ClosingSig
Normal operation (128-135):
UpdateAddHtlc, UpdateFulfillHtlc, UpdateFailHtlc,
UpdateFailMalformedHtlc, CommitmentSigned, RevokeAndAck, UpdateFee
Channel reestablishment (136):
ChannelReestablish
Adds EncodeError and DecodeError types with NFData instances.
Uses local path for ppad-bolt1 dependency in flake.nix.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
4 files changed, 847 insertions(+), 4 deletions(-)
diff --git a/flake.lock b/flake.lock
@@ -0,0 +1,151 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1766840161,
+ "narHash": "sha256-Ss/LHpJJsng8vz1Pe33RSGIWUOcqM1fjrehjUkdrWio=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "3edc4a30ed3903fdf6f90c837f961fa6b49582d1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "ppad-base16": {
+ "inputs": {
+ "flake-utils": [
+ "ppad-bolt1",
+ "ppad-base16",
+ "ppad-nixpkgs",
+ "flake-utils"
+ ],
+ "nixpkgs": [
+ "ppad-bolt1",
+ "ppad-base16",
+ "ppad-nixpkgs",
+ "nixpkgs"
+ ],
+ "ppad-nixpkgs": [
+ "ppad-bolt1",
+ "ppad-nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1766934151,
+ "narHash": "sha256-BUFpuLfrGXE2xi3Wa9TYCEhhRhFp175Ghxnr0JRbG2I=",
+ "ref": "master",
+ "rev": "58dfb7922401a60d5de76825fcd5f6ecbcd7afe0",
+ "revCount": 26,
+ "type": "git",
+ "url": "git://git.ppad.tech/base16.git"
+ },
+ "original": {
+ "ref": "master",
+ "type": "git",
+ "url": "git://git.ppad.tech/base16.git"
+ }
+ },
+ "ppad-bolt1": {
+ "inputs": {
+ "flake-utils": [
+ "ppad-bolt1",
+ "ppad-nixpkgs",
+ "flake-utils"
+ ],
+ "nixpkgs": [
+ "ppad-bolt1",
+ "ppad-nixpkgs",
+ "nixpkgs"
+ ],
+ "ppad-base16": "ppad-base16",
+ "ppad-nixpkgs": [
+ "ppad-nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1769324859,
+ "narHash": "sha256-qQsiRQMGUZ8TZimz9yFFED3SEt8BAjYhHp6r2qvEuQA=",
+ "path": "/Users/jtobin/src/ppad/bolt1",
+ "type": "path"
+ },
+ "original": {
+ "path": "/Users/jtobin/src/ppad/bolt1",
+ "type": "path"
+ }
+ },
+ "ppad-nixpkgs": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1766932084,
+ "narHash": "sha256-GvVsbTfW+B7IQ9K/QP2xcXJAm1lhBin1jYZWNjOzT+o=",
+ "ref": "master",
+ "rev": "353e61763b959b960a55321a85423501e3e9ed7a",
+ "revCount": 2,
+ "type": "git",
+ "url": "git://git.ppad.tech/nixpkgs.git"
+ },
+ "original": {
+ "ref": "master",
+ "type": "git",
+ "url": "git://git.ppad.tech/nixpkgs.git"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": [
+ "ppad-nixpkgs",
+ "flake-utils"
+ ],
+ "nixpkgs": [
+ "ppad-nixpkgs",
+ "nixpkgs"
+ ],
+ "ppad-bolt1": "ppad-bolt1",
+ "ppad-nixpkgs": "ppad-nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
@@ -3,9 +3,8 @@
inputs = {
ppad-bolt1 = {
- type = "git";
- url = "git://git.ppad.tech/bolt1.git";
- ref = "master";
+ type = "path";
+ path = "/Users/jtobin/src/ppad/bolt1";
inputs.ppad-nixpkgs.follows = "ppad-nixpkgs";
};
ppad-nixpkgs = {
diff --git a/lib/Lightning/Protocol/BOLT2.hs b/lib/Lightning/Protocol/BOLT2.hs
@@ -18,6 +18,10 @@ module Lightning.Protocol.BOLT2 (
-- | 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)
@@ -36,6 +40,7 @@ module Lightning.Protocol.BOLT2 (
-- $reestablish
) where
+import Lightning.Protocol.BOLT2.Codec
import Lightning.Protocol.BOLT2.Messages
import Lightning.Protocol.BOLT2.Types
diff --git a/lib/Lightning/Protocol/BOLT2/Codec.hs b/lib/Lightning/Protocol/BOLT2/Codec.hs
@@ -9,7 +9,7 @@
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
--- Encode/decode functions for V1 channel establishment and close messages.
+-- Encode/decode functions for BOLT #2 messages.
module Lightning.Protocol.BOLT2.Codec (
-- * Error types
@@ -28,6 +28,30 @@ module Lightning.Protocol.BOLT2.Codec (
, 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
@@ -39,6 +63,26 @@ module Lightning.Protocol.BOLT2.Codec (
, decodeClosingComplete
, encodeClosingSig
, decodeClosingSig
+
+ -- * Normal operation
+ , encodeUpdateAddHtlc
+ , decodeUpdateAddHtlc
+ , encodeUpdateFulfillHtlc
+ , decodeUpdateFulfillHtlc
+ , encodeUpdateFailHtlc
+ , decodeUpdateFailHtlc
+ , encodeUpdateFailMalformedHtlc
+ , decodeUpdateFailMalformedHtlc
+ , encodeCommitmentSigned
+ , decodeCommitmentSigned
+ , encodeRevokeAndAck
+ , decodeRevokeAndAck
+ , encodeUpdateFee
+ , decodeUpdateFee
+
+ -- * Channel reestablishment
+ , encodeChannelReestablish
+ , decodeChannelReestablish
) where
import Control.DeepSeq (NFData)
@@ -79,6 +123,9 @@ data DecodeError
| DecodeInvalidSignature
| DecodeInvalidPoint
| DecodeInvalidTxId
+ | DecodeInvalidPaymentHash
+ | DecodeInvalidPaymentPreimage
+ | DecodeInvalidOnionPacket
| DecodeTlvError !TlvError
deriving stock (Eq, Show, Generic)
@@ -195,6 +242,68 @@ decodeScriptPubKey !bs = do
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 bytes using Either.
+decodeBytesE
+ :: Int -> BS.ByteString -> Either DecodeError (BS.ByteString, BS.ByteString)
+decodeBytesE !n !bs = maybe (Left DecodeInsufficientBytes) Right
+ (decodeBytes n bs)
+{-# INLINE decodeBytesE #-}
+
+-- | Encode a u16-prefixed byte string.
+encodeU16Bytes :: BS.ByteString -> BS.ByteString
+encodeU16Bytes !bs = encodeU16 (fromIntegral (BS.length bs)) <> bs
+{-# INLINE encodeU16Bytes #-}
+
+-- | 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 (TlvStream [], 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).
@@ -630,3 +739,582 @@ decodeClosingSig !bs = do
, 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 (unSatoshis (openChannel2FundingSatoshis msg))
+ , encodeU64 (unSatoshis (openChannel2DustLimitSatoshis msg))
+ , encodeU64 (unMilliSatoshis (openChannel2MaxHtlcValueInFlight msg))
+ , encodeU64 (unMilliSatoshis (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)
+ ]
+
+-- | 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) <- decodeSatoshis rest4
+ (dustLimit, rest6) <- decodeSatoshis rest5
+ (maxHtlcVal, rest7) <- decodeMilliSatoshis rest6
+ (htlcMin, rest8) <- decodeMilliSatoshis 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
+ , openChannel2FundingSatoshis = fundingSats
+ , openChannel2DustLimitSatoshis = 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 (unSatoshis (acceptChannel2FundingSatoshis msg))
+ , encodeU64 (unSatoshis (acceptChannel2DustLimitSatoshis msg))
+ , encodeU64 (unMilliSatoshis (acceptChannel2MaxHtlcValueInFlight msg))
+ , encodeU64 (unMilliSatoshis (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 an AcceptChannel2 message (type 65).
+decodeAcceptChannel2
+ :: BS.ByteString -> Either DecodeError (AcceptChannel2, BS.ByteString)
+decodeAcceptChannel2 !bs = do
+ (tempCid, rest1) <- decodeChannelIdBytes bs
+ (fundingSats, rest2) <- decodeSatoshis rest1
+ (dustLimit, rest3) <- decodeSatoshis rest2
+ (maxHtlcVal, rest4) <- decodeMilliSatoshis rest3
+ (htlcMin, rest5) <- decodeMilliSatoshis 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
+ , acceptChannel2FundingSatoshis = fundingSats
+ , acceptChannel2DustLimitSatoshis = 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 -> BS.ByteString
+encodeTxAddInput !msg = mconcat
+ [ unChannelId (txAddInputChannelId msg)
+ , encodeU64 (txAddInputSerialId msg)
+ , encodeU16Bytes (txAddInputPrevTx msg)
+ , encodeU32 (txAddInputPrevVout msg)
+ , encodeU32 (txAddInputSequence msg)
+ ]
+
+-- | Decode a TxAddInput message (type 66).
+decodeTxAddInput
+ :: BS.ByteString -> Either DecodeError (TxAddInput, BS.ByteString)
+decodeTxAddInput !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (serialId, 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
+ , txAddInputPrevTx = prevTx
+ , txAddInputPrevVout = prevVout
+ , txAddInputSequence = seqNum
+ }
+ Right (msg, rest5)
+
+-- | Encode a TxAddOutput message (type 67).
+encodeTxAddOutput :: TxAddOutput -> BS.ByteString
+encodeTxAddOutput !msg = mconcat
+ [ unChannelId (txAddOutputChannelId msg)
+ , encodeU64 (txAddOutputSerialId msg)
+ , encodeU64 (unSatoshis (txAddOutputSats msg))
+ , encodeU16Bytes (unScriptPubKey (txAddOutputScript msg))
+ ]
+
+-- | Decode a TxAddOutput message (type 67).
+decodeTxAddOutput
+ :: BS.ByteString -> Either DecodeError (TxAddOutput, BS.ByteString)
+decodeTxAddOutput !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (serialId, rest2) <- maybe (Left DecodeInsufficientBytes) Right
+ (decodeU64 rest1)
+ (sats, rest3) <- decodeSatoshis rest2
+ (scriptBs, rest4) <- decodeU16Bytes rest3
+ let !msg = TxAddOutput
+ { txAddOutputChannelId = cid
+ , txAddOutputSerialId = serialId
+ , 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 (txRemoveInputSerialId msg)
+ ]
+
+-- | Decode a TxRemoveInput message (type 68).
+decodeTxRemoveInput
+ :: BS.ByteString -> Either DecodeError (TxRemoveInput, BS.ByteString)
+decodeTxRemoveInput !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (serialId, rest2) <- maybe (Left DecodeInsufficientBytes) Right
+ (decodeU64 rest1)
+ let !msg = TxRemoveInput
+ { txRemoveInputChannelId = cid
+ , txRemoveInputSerialId = serialId
+ }
+ Right (msg, rest2)
+
+-- | Encode a TxRemoveOutput message (type 69).
+encodeTxRemoveOutput :: TxRemoveOutput -> BS.ByteString
+encodeTxRemoveOutput !msg = mconcat
+ [ unChannelId (txRemoveOutputChannelId msg)
+ , encodeU64 (txRemoveOutputSerialId msg)
+ ]
+
+-- | Decode a TxRemoveOutput message (type 69).
+decodeTxRemoveOutput
+ :: BS.ByteString -> Either DecodeError (TxRemoveOutput, BS.ByteString)
+decodeTxRemoveOutput !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (serialId, rest2) <- maybe (Left DecodeInsufficientBytes) Right
+ (decodeU64 rest1)
+ let !msg = TxRemoveOutput
+ { txRemoveOutputChannelId = cid
+ , txRemoveOutputSerialId = serialId
+ }
+ 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.
+encodeWitness :: Witness -> BS.ByteString
+encodeWitness (Witness !wdata) = encodeU16Bytes 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 -> BS.ByteString
+encodeTxSignatures !msg =
+ let !witnesses = txSignaturesWitnesses msg
+ !numWit = fromIntegral (length witnesses) :: Word16
+ in mconcat $
+ [ unChannelId (txSignaturesChannelId msg)
+ , unTxId (txSignaturesTxid msg)
+ , encodeU16 numWit
+ ] ++ map encodeWitness witnesses
+
+-- | 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)
+ 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)
+ ]
+
+-- | 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 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 -> BS.ByteString
+encodeTxAbort !msg = mconcat
+ [ unChannelId (txAbortChannelId msg)
+ , encodeU16Bytes (txAbortData msg)
+ ]
+
+-- | 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 (updateAddHtlcId m)
+ , encodeU64 (unMilliSatoshis (updateAddHtlcAmountMsat m))
+ , unPaymentHash (updateAddHtlcPaymentHash m)
+ , encodeU32 (updateAddHtlcCltvExpiry m)
+ , unOnionPacket (updateAddHtlcOnionPacket m)
+ , encodeTlvStream (updateAddHtlcTlvs m)
+ ]
+
+-- | Decode an UpdateAddHtlc message (type 128).
+decodeUpdateAddHtlc
+ :: BS.ByteString -> Either DecodeError (UpdateAddHtlc, BS.ByteString)
+decodeUpdateAddHtlc !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (htlcId, 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
+ , updateAddHtlcAmountMsat = MilliSatoshis amtMsat
+ , updateAddHtlcPaymentHash = pHash
+ , updateAddHtlcCltvExpiry = cltvExp
+ , updateAddHtlcOnionPacket = onion
+ , updateAddHtlcTlvs = tlvs
+ }
+ Right (msg, rest7)
+
+-- | Encode an UpdateFulfillHtlc message (type 130).
+encodeUpdateFulfillHtlc :: UpdateFulfillHtlc -> BS.ByteString
+encodeUpdateFulfillHtlc !m = mconcat
+ [ unChannelId (updateFulfillHtlcChannelId m)
+ , encodeU64 (updateFulfillHtlcId m)
+ , unPaymentPreimage (updateFulfillHtlcPaymentPreimage m)
+ , encodeTlvStream (updateFulfillHtlcTlvs m)
+ ]
+
+-- | Decode an UpdateFulfillHtlc message (type 130).
+decodeUpdateFulfillHtlc
+ :: BS.ByteString -> Either DecodeError (UpdateFulfillHtlc, BS.ByteString)
+decodeUpdateFulfillHtlc !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (htlcId, rest2) <- maybe (Left DecodeInsufficientBytes) Right
+ (decodeU64 rest1)
+ (preimage, rest3) <- decodePaymentPreimageBytes rest2
+ (tlvs, rest4) <- decodeOptionalTlvs rest3
+ let !msg = UpdateFulfillHtlc
+ { updateFulfillHtlcChannelId = cid
+ , updateFulfillHtlcId = htlcId
+ , updateFulfillHtlcPaymentPreimage = preimage
+ , updateFulfillHtlcTlvs = tlvs
+ }
+ Right (msg, rest4)
+
+-- | Encode an UpdateFailHtlc message (type 131).
+encodeUpdateFailHtlc :: UpdateFailHtlc -> BS.ByteString
+encodeUpdateFailHtlc !m = mconcat
+ [ unChannelId (updateFailHtlcChannelId m)
+ , encodeU64 (updateFailHtlcId m)
+ , encodeU16Bytes (updateFailHtlcReason m)
+ , encodeTlvStream (updateFailHtlcTlvs m)
+ ]
+
+-- | Decode an UpdateFailHtlc message (type 131).
+decodeUpdateFailHtlc
+ :: BS.ByteString -> Either DecodeError (UpdateFailHtlc, BS.ByteString)
+decodeUpdateFailHtlc !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (htlcId, rest2) <- maybe (Left DecodeInsufficientBytes) Right
+ (decodeU64 rest1)
+ (reason, rest3) <- decodeU16Bytes rest2
+ (tlvs, rest4) <- decodeOptionalTlvs rest3
+ let !msg = UpdateFailHtlc
+ { updateFailHtlcChannelId = cid
+ , updateFailHtlcId = htlcId
+ , updateFailHtlcReason = reason
+ , updateFailHtlcTlvs = tlvs
+ }
+ Right (msg, rest4)
+
+-- | Encode an UpdateFailMalformedHtlc message (type 135).
+encodeUpdateFailMalformedHtlc :: UpdateFailMalformedHtlc -> BS.ByteString
+encodeUpdateFailMalformedHtlc !m = mconcat
+ [ unChannelId (updateFailMalformedHtlcChannelId m)
+ , encodeU64 (updateFailMalformedHtlcId m)
+ , unPaymentHash (updateFailMalformedHtlcSha256Onion m)
+ , encodeU16 (updateFailMalformedHtlcFailureCode m)
+ ]
+
+-- | Decode an UpdateFailMalformedHtlc message (type 135).
+decodeUpdateFailMalformedHtlc
+ :: BS.ByteString -> Either DecodeError (UpdateFailMalformedHtlc, BS.ByteString)
+decodeUpdateFailMalformedHtlc !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (htlcId, rest2) <- maybe (Left DecodeInsufficientBytes) Right
+ (decodeU64 rest1)
+ (sha256Onion, rest3) <- decodePaymentHashBytes rest2
+ (failCode, rest4) <- decodeU16E rest3
+ let !msg = UpdateFailMalformedHtlc
+ { updateFailMalformedHtlcChannelId = cid
+ , updateFailMalformedHtlcId = htlcId
+ , updateFailMalformedHtlcSha256Onion = sha256Onion
+ , updateFailMalformedHtlcFailureCode = failCode
+ }
+ Right (msg, rest4)
+
+-- | Encode a CommitmentSigned message (type 132).
+encodeCommitmentSigned :: CommitmentSigned -> BS.ByteString
+encodeCommitmentSigned !m = mconcat $
+ [ unChannelId (commitmentSignedChannelId m)
+ , unSignature (commitmentSignedSignature m)
+ , encodeU16 numHtlcs
+ ] ++ map unSignature sigs
+ where
+ !sigs = commitmentSignedHtlcSignatures m
+ !numHtlcs = fromIntegral (length sigs) :: Word16
+
+-- | 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)
+
+-- | Encode a RevokeAndAck message (type 133).
+encodeRevokeAndAck :: RevokeAndAck -> BS.ByteString
+encodeRevokeAndAck !m = mconcat
+ [ unChannelId (revokeAndAckChannelId m)
+ , revokeAndAckPerCommitmentSecret m
+ , unPoint (revokeAndAckNextPerCommitPoint m)
+ ]
+
+-- | Decode a RevokeAndAck message (type 133).
+decodeRevokeAndAck
+ :: BS.ByteString -> Either DecodeError (RevokeAndAck, BS.ByteString)
+decodeRevokeAndAck !bs = do
+ (cid, rest1) <- decodeChannelIdBytes bs
+ (secret, rest2) <- decodeBytesE 32 rest1
+ (nextPoint, rest3) <- decodePointBytes rest2
+ let !msg = RevokeAndAck
+ { revokeAndAckChannelId = cid
+ , revokeAndAckPerCommitmentSecret = secret
+ , 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 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)
+ , channelReestablishYourLastCommitSecret m
+ , unPoint (channelReestablishMyCurrentCommitPoint m)
+ , encodeTlvStream (channelReestablishTlvs m)
+ ]
+
+-- | 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)
+ (lastSecret, rest4) <- decodeBytesE 32 rest3
+ (myPoint, rest5) <- decodePointBytes rest4
+ (tlvs, rest6) <- decodeOptionalTlvs rest5
+ let !msg = ChannelReestablish
+ { channelReestablishChannelId = cid
+ , channelReestablishNextCommitNum = nextCommit
+ , channelReestablishNextRevocationNum = nextRevoke
+ , channelReestablishYourLastCommitSecret = lastSecret
+ , channelReestablishMyCurrentCommitPoint = myPoint
+ , channelReestablishTlvs = tlvs
+ }
+ Right (msg, rest6)