commit 6a81645d68127087b8ab800dd034c74dadac9be5
parent d5d564b902d72569c6b28836a0e0064322c7ed43
Author: Jared Tobin <jared@jtobin.io>
Date: Sat, 10 Oct 2026 15:15:24 +0400
lib: port to ppad-bolt1 0.1.0, ppad-bolt3 0.1.0 and ppad-tx 0.2.0
Amounts, points and hashes are bolt1's types; ChannelFeatures is
replaced by bolt3's CommitmentFormat. Classification reads each HTLC
output's HTLC from bolt3's OutputHTLC rather than matching rebuilt
scripts against a caller-supplied HTLC list, so the classifiers no
longer take one (nor the parameters only the matching used).
spend_htlc_tx replaces spend_htlc_timeout and spend_htlc_success,
wrapping bolt3's build_htlc_tx and htlc_to_tx. spending_fee
saturates at 21 million BTC.
Only Lightning.Protocol.BOLT5 is exposed; the Internal module is
removed. Names are snake_case (htlc_output_type,
htlc_output_type_weight) and every public type has an NFData
instance.
Diffstat:
6 files changed, 541 insertions(+), 798 deletions(-)
diff --git a/lib/Lightning/Protocol/BOLT5.hs b/lib/Lightning/Protocol/BOLT5.hs
@@ -9,84 +9,50 @@
-- On-chain transaction handling for the Lightning Network, per
-- [BOLT #5](https://github.com/lightning/bolts/blob/master/05-onchain.md).
--
--- This module implements the logic for handling channel closures:
---
--- * Mutual close - cooperative closure agreed by both parties
--- * Unilateral close - one party publishes their commitment
--- transaction
--- * Revoked transaction close - penalty for publishing old state
+-- This library's scope is being redesigned, and it is not yet ready
+-- for release. It covers unilateral closes only partially: it
+-- identifies a local or remote commitment on chain, classifies its
+-- outputs, extracts preimages from HTLC spends, and builds unsigned,
+-- single-input spending transactions. It doesn't detect mutual or
+-- revoked closes, assemble witnesses, or attach fee inputs to anchor
+-- spends and zero-fee HTLC transactions.
--
-- = Design
--
--- This is a stateless toolkit of pure functions. The caller
--- manages channel state (which outputs are resolved, current
--- block height, etc.) and provides explicit inputs. Functions
--- produce unsigned 'SpendingTx' values; the caller signs and
--- assembles witnesses using bolt3 constructors.
+-- This is a stateless toolkit of pure functions. The caller manages
+-- channel state (which outputs are resolved, the current block
+-- height, etc.) and provides explicit inputs. Commitment
+-- transactions are those built by ppad-bolt3, and amounts are
+-- ppad-bolt1's. Functions produce unsigned 'SpendingTx' values; the
+-- caller signs them and assembles witnesses with bolt3's witness
+-- functions.
--
-- = Usage
--
-- @
+-- import qualified Lightning.Protocol.BOLT1 as BOLT1
-- import Lightning.Protocol.BOLT3
-- import Lightning.Protocol.BOLT5
--
--- -- Classify outputs of our local commitment
--- let outputs = classify_local_commit_outputs
--- commitTx keys delay features htlcs
+-- -- classify the outputs of our local commitment
+-- let outputs = classify_local_commit_outputs commit keys delay fmt
--
--- -- For each unresolved output, construct spending tx
+-- -- for each unresolved output, build a spending transaction
-- case uo_type output of
-- SpendToLocal delay revpk delayedpk ->
--- spend_to_local (uo_outpoint output)
--- (uo_value output) revpk delay delayedpk
--- destScript feerate
+-- spend_to_local (uo_outpoint output) (uo_value output)
+-- revpk delay delayedpk dest feerate
-- ...
-- @
module Lightning.Protocol.BOLT5 (
- -- * Types
- -- ** Close identification
- CloseType(..)
-
- -- ** Output classification
- , UnresolvedOutput(..)
- , OutputResolution(..)
-
- -- ** HTLC output type
- , HTLCOutputType(..)
- , htlcOutputType
- , htlcOutputTypeWeight
-
- -- ** Spending transactions
- , SpendingTx(..)
-
- -- ** Revoked outputs
- , RevokedOutput(..)
- , RevokedOutputType(..)
- , revoked_output_weight
-
- -- * Weight constants (Appendix A)
- , to_local_penalty_witness_weight
- , offered_htlc_penalty_witness_weight
- , accepted_htlc_penalty_witness_weight
- , offered_htlc_penalty_witness_weight_anchors
- , accepted_htlc_penalty_witness_weight_anchors
- , to_local_penalty_input_weight
- , offered_htlc_penalty_input_weight
- , accepted_htlc_penalty_input_weight
- , offered_htlc_penalty_input_weight_anchors
- , accepted_htlc_penalty_input_weight_anchors
- , to_remote_input_weight
- , penalty_tx_base_weight
- , max_standard_weight
-
- -- * Fee calculation
- , spending_fee
-
-- * Close identification
+ CloseType(..)
, identify_close
-- * Output classification
+ , UnresolvedOutput(..)
+ , OutputResolution(..)
, classify_local_commit_outputs
, classify_remote_commit_outputs
, classify_revoked_commit_outputs
@@ -98,11 +64,12 @@ module Lightning.Protocol.BOLT5 (
-- * Timeout check
, htlc_timed_out
- -- * Spending transaction construction
+ -- * Spending transactions
+ , SpendingTx(..)
+
-- ** Local commitment
, spend_to_local
- , spend_htlc_timeout
- , spend_htlc_success
+ , spend_htlc_tx
, spend_htlc_output
-- ** Remote commitment
@@ -117,8 +84,34 @@ module Lightning.Protocol.BOLT5 (
-- ** Anchor outputs
, spend_anchor_owner
, spend_anchor_anyone
+
+ -- * Revoked outputs
+ , HTLCOutputType(..)
+ , htlc_output_type
+ , RevokedOutput(..)
+ , RevokedOutputType(..)
+
+ -- * Weights (Appendix A)
+ , revoked_output_weight
+ , htlc_output_type_weight
+ , to_local_penalty_witness_weight
+ , offered_htlc_penalty_witness_weight
+ , accepted_htlc_penalty_witness_weight
+ , offered_htlc_penalty_witness_weight_anchors
+ , accepted_htlc_penalty_witness_weight_anchors
+ , to_local_penalty_input_weight
+ , offered_htlc_penalty_input_weight
+ , accepted_htlc_penalty_input_weight
+ , offered_htlc_penalty_input_weight_anchors
+ , accepted_htlc_penalty_input_weight_anchors
+ , to_remote_input_weight
+ , penalty_tx_base_weight
+ , max_standard_weight
+
+ -- * Fees
+ , spending_fee
) where
-import Lightning.Protocol.BOLT5.Types
import Lightning.Protocol.BOLT5.Detect
import Lightning.Protocol.BOLT5.Spend
+import Lightning.Protocol.BOLT5.Types
diff --git a/lib/Lightning/Protocol/BOLT5/Detect.hs b/lib/Lightning/Protocol/BOLT5/Detect.hs
@@ -1,4 +1,4 @@
-{-# OPTIONS_HADDOCK prune #-}
+{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE BangPatterns #-}
-- |
@@ -27,11 +27,14 @@ module Lightning.Protocol.BOLT5.Detect (
, htlc_timed_out
) where
+import Bitcoin.Prim.Tx (OutPoint(..), TxId, Witness(..))
import qualified Bitcoin.Prim.Tx as BT
import qualified Crypto.Hash.SHA256 as SHA256
-import Data.List (mapAccumL)
-import Data.Word (Word32)
import qualified Data.ByteString as BS
+import qualified Data.List.NonEmpty as NE
+import Data.Word (Word32)
+import Lightning.Protocol.BOLT1 (PaymentHash, PaymentPreimage)
+import qualified Lightning.Protocol.BOLT1 as BOLT1
import Lightning.Protocol.BOLT3
import Lightning.Protocol.BOLT5.Types
@@ -40,17 +43,16 @@ import Lightning.Protocol.BOLT5.Types
-- | Identify the type of channel close from a transaction that
-- spends the funding output.
--
--- Parses the on-chain transaction and compares its txid against
--- the txids of the known local and remote commitment
--- transactions. Comparing txids rather than bytes means the
--- signed (segwit) serialization found on chain matches the
--- unsigned commitment.
+-- Parses the on-chain transaction and compares its txid with the
+-- txids of the known local and remote commitment transactions.
+-- Comparing txids rather than bytes means the signed (segwit)
+-- serialization found on chain matches the unsigned commitment.
--
--- Returns 'Nothing' if the bytes don't parse as a transaction or
--- the transaction doesn't match either commitment. Mutual close
--- and revoked commitment detection require additional checks by
--- the caller (e.g. comparing closing tx format, checking a secret
--- store for older commitment numbers).
+-- Returns 'Nothing' if the bytes don't parse as a transaction or
+-- the transaction matches neither commitment. Mutual close and
+-- revoked commitment detection are left to the caller (e.g.
+-- comparing with the closing transaction, or checking a secret
+-- store for older commitment numbers).
identify_close
:: CommitmentTx
-- ^ Our local commitment tx.
@@ -59,31 +61,27 @@ identify_close
-> BS.ByteString
-- ^ Raw serialized transaction found on chain.
-> Maybe CloseType
-identify_close !localCommitTx !remoteCommitTx
- !onChainBytes =
- case BT.from_bytes onChainBytes of
- Nothing -> Nothing
- Just !onChainTx ->
- let !onChainTxid = Just (BT.txid onChainTx)
- in if commitment_txid localCommitTx == onChainTxid
- then Just LocalCommitClose
- else if commitment_txid remoteCommitTx == onChainTxid
- then Just RemoteCommitClose
- else Nothing
+identify_close !local !remote !bytes = do
+ !tx <- BT.from_bytes bytes
+ let !txid = BT.txid tx
+ if commitment_txid local == txid
+ then Just LocalCommitClose
+ else if commitment_txid remote == txid
+ then Just RemoteCommitClose
+ else Nothing
-- output classification ----------------------------------------------
--- | Classify outputs of our local commitment transaction.
+-- | Classify the outputs of our local commitment transaction.
--
--- Per BOLT #5: when we discover our local commitment on chain,
--- we must resolve each output. to_local requires a CSV-delayed
--- spend, to_remote is resolved by the commitment itself, HTLC
--- outputs need second-stage transactions, and anchors can be
--- spent immediately.
+-- Per BOLT #5: when our local commitment is on chain, we must
+-- resolve each output. to_local requires a CSV-delayed spend,
+-- to_remote is resolved by the commitment itself, HTLC outputs
+-- need second-stage transactions, and our anchor can be spent
+-- immediately.
--
--- An HTLC output is matched to the HTLC with the same direction,
--- script, amount and CLTV expiry, and each HTLC matches at most one
--- output.
+-- Each HTLC output's HTLC is read from its bolt3 'OutputHTLC', so
+-- the commitment must be the one bolt3 built for this state.
classify_local_commit_outputs
:: CommitmentTx
-- ^ Our local commitment transaction.
@@ -91,306 +89,166 @@ classify_local_commit_outputs
-- ^ Derived keys for this commitment.
-> ToSelfDelay
-- ^ Remote's to_self_delay (CSV delay for our outputs).
- -> ChannelFeatures
- -- ^ Channel feature flags.
- -> [HTLC]
- -- ^ HTLCs in this commitment.
+ -> CommitmentFormat
-> [UnresolvedOutput]
-classify_local_commit_outputs !commitTx !keys !delay
- !features !htlcs =
- case commitment_txid commitTx of
- Nothing -> []
- Just !txid ->
- let !outputs = ctx_outputs commitTx
- !revpk = ck_revocation_pubkey keys
- !delayedpk = ck_local_delayed keys
- classify cands (idx, out) =
- classifyLocalOutput txid revpk delayedpk
- delay features keys cands idx out
- in snd (mapAccumL classify
- (htlc_candidates keys features htlcs)
- (zip [0..] outputs))
-
--- | Classify a single output from a local commitment tx, removing
--- any matched HTLC from the candidates.
-classifyLocalOutput
- :: TxId
- -> RevocationPubkey
- -> LocalDelayedPubkey
- -> ToSelfDelay
- -> ChannelFeatures
- -> CommitmentKeys
- -> [(HTLC, Script)]
- -> Word32
- -> TxOutput
- -> ([(HTLC, Script)], UnresolvedOutput)
-classifyLocalOutput !txid !revpk !delayedpk !delay
- !features !keys !cands !idx !out =
- let !op = OutPoint txid idx
- !val = txout_value out
- !(rest, resolution) = case txout_type out of
- OutputToLocal ->
- (cands, SpendToLocal delay revpk delayedpk)
- OutputToRemote ->
- (cands, Resolved)
- OutputLocalAnchor ->
- (cands, AnchorSpend (ck_local_funding keys))
- OutputRemoteAnchor ->
- (cands, Resolved)
- OutputOfferedHTLC expiry ->
- case take_htlc HTLCOffered expiry out cands of
- (Just htlc, cands') ->
- (cands', SpendHTLCTimeout htlc keys features)
- (Nothing, cands') -> (cands', Resolved)
- OutputReceivedHTLC expiry ->
- case take_htlc HTLCReceived expiry out cands of
- (Just htlc, cands') ->
- (cands', SpendHTLCSuccess htlc keys features)
- (Nothing, cands') -> (cands', Resolved)
- in (rest, UnresolvedOutput op val resolution)
-
--- | Classify outputs of the remote commitment transaction.
---
--- Per BOLT #5: when we discover the remote commitment on chain,
--- there are no CSV delays on our outputs. We can spend offered
--- HTLCs directly after timeout, and received HTLCs directly
--- with the preimage.
---
--- HTLC outputs are matched as in 'classify_local_commit_outputs'.
+classify_local_commit_outputs !tx !keys !delay !fmt =
+ classify_outputs resolve tx
+ where
+ resolve t = case t of
+ OutputToLocal ->
+ SpendToLocal delay (ck_revocation_pubkey keys)
+ (ck_local_delayed keys)
+ OutputToRemote -> Resolved
+ OutputLocalAnchor -> AnchorSpend (ck_local_funding keys)
+ OutputRemoteAnchor -> Resolved
+ OutputHTLC h -> case htlc_direction h of
+ HTLCOffered -> SpendHTLCTimeout h keys fmt
+ HTLCReceived -> SpendHTLCSuccess h keys fmt
+
+-- | Classify the outputs of the remote commitment transaction.
+--
+-- Per BOLT #5: when the remote commitment is on chain, there are
+-- no CSV delays on our outputs. We can spend HTLCs we offered
+-- directly after timeout, and HTLCs we received directly with the
+-- preimage.
+--
+-- The commitment, its keys and its HTLCs are from the remote's
+-- point of view (as bolt3 builds the remote's commitment): an
+-- HTLC the remote offered is one we received.
classify_remote_commit_outputs
:: CommitmentTx
-- ^ The remote commitment transaction.
-> CommitmentKeys
- -- ^ Derived keys for this commitment (from remote's
- -- perspective, so local/remote are swapped).
- -> ChannelFeatures
- -- ^ Channel feature flags.
- -> [HTLC]
- -- ^ HTLCs in this commitment.
+ -- ^ Derived keys for this commitment (from the remote's point
+ -- of view, so local and remote are swapped).
-> [UnresolvedOutput]
-classify_remote_commit_outputs !commitTx !keys
- !features !htlcs =
- case commitment_txid commitTx of
- Nothing -> []
- Just !txid ->
- let !outputs = ctx_outputs commitTx
- classify cands (idx, out) =
- classifyRemoteOutput txid keys cands idx out
- in snd (mapAccumL classify
- (htlc_candidates keys features htlcs)
- (zip [0..] outputs))
-
--- | Classify a single output from a remote commitment tx, removing
--- any matched HTLC from the candidates.
-classifyRemoteOutput
- :: TxId
- -> CommitmentKeys
- -> [(HTLC, Script)]
- -> Word32
- -> TxOutput
- -> ([(HTLC, Script)], UnresolvedOutput)
-classifyRemoteOutput !txid !keys !cands !idx !out =
- let !op = OutPoint txid idx
- !val = txout_value out
- !(rest, resolution) = case txout_type out of
- OutputToLocal ->
- (cands, Resolved) -- Remote's to_local; not ours
- OutputToRemote ->
- (cands, Resolved) -- Our to_remote; resolved by commitment
- OutputLocalAnchor ->
- (cands, Resolved) -- Remote's anchor
- OutputRemoteAnchor ->
- (cands, AnchorSpend (ck_remote_funding keys))
- OutputOfferedHTLC expiry ->
- -- On remote's commit, their offered = our received.
- -- We can claim with preimage.
- case take_htlc HTLCOffered expiry out cands of
- (Just htlc, cands') ->
- (cands', SpendHTLCPreimageDirect htlc)
- (Nothing, cands') -> (cands', Resolved)
- OutputReceivedHTLC expiry ->
- -- On remote's commit, their received = our offered.
- -- We can claim after timeout.
- case take_htlc HTLCReceived expiry out cands of
- (Just htlc, cands') ->
- (cands', SpendHTLCTimeoutDirect htlc)
- (Nothing, cands') -> (cands', Resolved)
- in (rest, UnresolvedOutput op val resolution)
-
--- | Classify outputs of a revoked commitment transaction.
---
--- Per BOLT #5: when we discover a revoked commitment, we can
--- claim everything using the revocation key. to_local is spent
--- via revocation, HTLCs are spent via revocation, and we can
--- also optionally sweep to_remote.
+classify_remote_commit_outputs !tx !keys =
+ classify_outputs resolve tx
+ where
+ resolve t = case t of
+ OutputToLocal -> Resolved -- the remote's to_local
+ OutputToRemote -> Resolved -- ours; resolved by the commitment
+ OutputLocalAnchor -> Resolved -- the remote's anchor
+ OutputRemoteAnchor -> AnchorSpend (ck_remote_funding keys)
+ OutputHTLC h -> case htlc_direction h of
+ HTLCOffered -> SpendHTLCPreimageDirect h -- we received it
+ HTLCReceived -> SpendHTLCTimeoutDirect h -- we offered it
+
+-- | Classify the outputs of a revoked commitment transaction.
+--
+-- Per BOLT #5: when a revoked commitment is on chain, we can
+-- claim to_local and every HTLC output with the revocation key.
+-- to_remote (ours) and the anchors are marked 'Resolved'.
classify_revoked_commit_outputs
:: CommitmentTx
-- ^ The revoked commitment transaction.
- -> CommitmentKeys
- -- ^ Derived keys for the revoked commitment.
-> RevocationPubkey
-- ^ Revocation pubkey (derived from the revealed secret).
- -> ChannelFeatures
- -- ^ Channel feature flags.
- -> [HTLC]
- -- ^ HTLCs in the revoked commitment.
-> [UnresolvedOutput]
-classify_revoked_commit_outputs !commitTx !_keys
- !revpk !_features !_htlcs =
- case commitment_txid commitTx of
- Nothing -> []
- Just !txid ->
- let !outputs = ctx_outputs commitTx
- in zipWith (classifyRevokedOutput txid revpk)
- [0..] outputs
-
--- | Classify a single output from a revoked commitment tx.
-classifyRevokedOutput
- :: TxId
- -> RevocationPubkey
- -> Word32
- -> TxOutput
- -> UnresolvedOutput
-classifyRevokedOutput !txid !revpk !idx !out =
- let !op = OutPoint txid idx
- !val = txout_value out
- !resolution = case txout_type out of
- OutputToLocal ->
- Revoke revpk
- OutputToRemote ->
- Resolved -- Our funds; resolved by commitment
- OutputLocalAnchor ->
- Resolved -- Can be swept by anyone after 16 blocks
- OutputRemoteAnchor ->
- Resolved -- Our anchor
- OutputOfferedHTLC expiry ->
- RevokeHTLC revpk (HTLCOfferedOutput expiry)
- OutputReceivedHTLC expiry ->
- RevokeHTLC revpk (HTLCReceivedOutput expiry)
- in UnresolvedOutput op val resolution
+classify_revoked_commit_outputs !tx !revpk =
+ classify_outputs resolve tx
+ where
+ resolve t = case t of
+ OutputToLocal -> Revoke revpk
+ OutputToRemote -> Resolved
+ OutputLocalAnchor -> Resolved
+ OutputRemoteAnchor -> Resolved
+ OutputHTLC h -> RevokeHTLC revpk $ case htlc_direction h of
+ HTLCOffered -> HTLCOfferedOutput (htlc_cltv_expiry h)
+ HTLCReceived -> HTLCReceivedOutput (htlc_cltv_expiry h)
+
+-- | Pair each commitment output with its outpoint, value and
+-- resolution.
+classify_outputs
+ :: (OutputType -> OutputResolution)
+ -> CommitmentTx
+ -> [UnresolvedOutput]
+classify_outputs resolve tx =
+ zipWith classify [0..] (NE.toList (ctx_outputs tx))
+ where
+ !txid = commitment_txid tx
+ classify :: Word32 -> CommitmentOutput -> UnresolvedOutput
+ classify !idx !out = UnresolvedOutput
+ { uo_outpoint = OutPoint txid idx
+ , uo_value = co_value out
+ , uo_type = resolve (co_type out)
+ }
-- preimage extraction ------------------------------------------------
--- | Extract a payment preimage from an offered HTLC witness.
+-- | Extract a payment preimage from the witness of an input
+-- spending an offered HTLC output.
--
--- When the remote party claims an offered HTLC on our local
--- commitment, the witness contains the preimage. The witness
--- stack for a preimage claim is:
+-- When the remote party claims an offered HTLC on our local
+-- commitment, the witness (see bolt3's
+-- 'offered_htlc_witness_preimage') is:
--
--- @\<remotehtlcsig\> \<paymentPreimage\> \<witnessScript\>@
+-- @\<remotehtlcsig\> \<payment_preimage\> \<witness_script\>@
--
--- Returns the preimage only if it hashes to the given payment
--- hash. Any other spend of the output, e.g. by revocation, yields
--- 'Nothing'.
+-- Returns the preimage only if it hashes to the given payment
+-- hash. Any other spend of the output, e.g. by revocation, yields
+-- 'Nothing'.
extract_preimage_offered
:: PaymentHash
-- ^ Payment hash of the HTLC.
-> Witness
-- ^ Witness of the input spending the HTLC output.
-> Maybe PaymentPreimage
-extract_preimage_offered !ph (Witness items) =
- case items of
- [_sig, preimageBytes, _script] ->
- check_preimage ph preimageBytes
- _ -> Nothing
+extract_preimage_offered !ph (Witness items) = case items of
+ [_sig, preimage, _script] -> check_preimage ph preimage
+ _ -> Nothing
--- | Extract a payment preimage from an HTLC-success transaction
--- witness.
+-- | Extract a payment preimage from the witness of an HTLC-success
+-- transaction's input.
--
--- When the remote party uses an HTLC-success tx on their
--- commitment to claim a received HTLC, the witness contains the
--- preimage. The witness stack is:
+-- When the remote party claims an HTLC it received on its
+-- commitment with an HTLC-success transaction, the witness (see
+-- bolt3's 'htlc_success_witness') is:
--
--- @0 \<remotehtlcsig\> \<localhtlcsig\> \<paymentPreimage\>
--- \<witnessScript\>@
+-- @0 \<remotehtlcsig\> \<localhtlcsig\> \<payment_preimage\>
+-- \<witness_script\>@
--
--- Returns the preimage only if it hashes to the given payment
--- hash.
+-- Returns the preimage only if it hashes to the given payment
+-- hash.
extract_preimage_htlc_success
:: PaymentHash
-- ^ Payment hash of the HTLC.
-> Witness
-- ^ Witness of the HTLC-success transaction input.
-> Maybe PaymentPreimage
-extract_preimage_htlc_success !ph (Witness items) =
- case items of
- [_zero, _remoteSig, _localSig, preimageBytes, _script] ->
- check_preimage ph preimageBytes
- _ -> Nothing
+extract_preimage_htlc_success !ph (Witness items) = case items of
+ [_zero, _remote_sig, _local_sig, preimage, _script] ->
+ check_preimage ph preimage
+ _ -> Nothing
-- | Accept a candidate preimage only if it hashes to the payment
-- hash.
check_preimage :: PaymentHash -> BS.ByteString -> Maybe PaymentPreimage
check_preimage !ph !bs
- | SHA256.hash bs == unPaymentHash ph = paymentPreimage bs
+ | SHA256.hash bs == BOLT1.un_payment_hash ph = BOLT1.payment_preimage bs
| otherwise = Nothing
-- timeout check ------------------------------------------------------
--- | Check if an HTLC has timed out at the given block height.
+-- | Whether an HTLC has timed out at the given block height: whether
+-- the height is at least the HTLC's CLTV expiry.
--
--- An HTLC has timed out when the current block height is equal
--- to or greater than the HTLC's CLTV expiry.
+-- >>> let Just amt = BOLT1.milli_satoshi 1000000
+-- >>> let Just ph = BOLT1.payment_hash (BS.replicate 32 0xaa)
+-- >>> let htlc = HTLC HTLCOffered amt ph (CltvExpiry 800000)
+-- >>> htlc_timed_out 799999 htlc
+-- False
+-- >>> htlc_timed_out 800000 htlc
+-- True
htlc_timed_out :: Word32 -> HTLC -> Bool
-htlc_timed_out !currentHeight !htlc =
- currentHeight >= unCltvExpiry (htlc_cltv_expiry htlc)
+htlc_timed_out !height !htlc =
+ let CltvExpiry expiry = htlc_cltv_expiry htlc
+ in height >= expiry
{-# INLINE htlc_timed_out #-}
-- internal helpers ---------------------------------------------------
--- | Compute the txid of a commitment transaction.
---
--- Returns 'Nothing' if the commitment has no outputs.
-commitment_txid :: CommitmentTx -> Maybe TxId
-commitment_txid !tx = fmap BT.txid (commitment_to_tx tx)
-
--- | Pair each HTLC with the P2WSH script of its commitment output.
-htlc_candidates
- :: CommitmentKeys
- -> ChannelFeatures
- -> [HTLC]
- -> [(HTLC, Script)]
-htlc_candidates !keys !features = map (\htlc -> (htlc, script htlc))
- where
- script htlc = case htlc_direction htlc of
- HTLCOffered ->
- to_p2wsh $ offered_htlc_script
- (ck_revocation_pubkey keys)
- (ck_remote_htlc keys)
- (ck_local_htlc keys)
- (htlc_payment_hash htlc)
- features
- HTLCReceived ->
- to_p2wsh $ received_htlc_script
- (ck_revocation_pubkey keys)
- (ck_remote_htlc keys)
- (ck_local_htlc keys)
- (htlc_payment_hash htlc)
- (htlc_cltv_expiry htlc)
- features
-
--- | Find the HTLC for an HTLC output and remove it from the
--- candidates.
---
--- The HTLC must agree with the output on direction, script, amount
--- and CLTV expiry. The offered HTLC script doesn't commit to the
--- expiry and no HTLC script commits to the amount, so HTLCs sharing
--- a payment hash differ only in these. Removing the match ensures
--- no two outputs resolve to the same HTLC.
-take_htlc
- :: HTLCDirection
- -> CltvExpiry
- -- ^ Expiry recorded for the output.
- -> TxOutput
- -> [(HTLC, Script)]
- -> (Maybe HTLC, [(HTLC, Script)])
-take_htlc !dir !expiry !out !cands =
- case break matches cands of
- (pre, (htlc, _) : post) -> (Just htlc, pre ++ post)
- (_, []) -> (Nothing, cands)
- where
- matches (htlc, script) =
- htlc_direction htlc == dir
- && htlc_cltv_expiry htlc == expiry
- && msatToSat (htlc_amount_msat htlc) == txout_value out
- && script == txout_script out
+-- | The txid of a commitment transaction.
+commitment_txid :: CommitmentTx -> TxId
+commitment_txid = BT.txid . commitment_to_tx
+{-# INLINE commitment_txid #-}
diff --git a/lib/Lightning/Protocol/BOLT5/Internal.hs b/lib/Lightning/Protocol/BOLT5/Internal.hs
@@ -1,26 +0,0 @@
-{-# OPTIONS_HADDOCK hide #-}
-
--- |
--- Module: Lightning.Protocol.BOLT5.Internal
--- Copyright: (c) 2025 Jared Tobin
--- License: MIT
--- Maintainer: Jared Tobin <jared@ppad.tech>
---
--- Internal definitions for BOLT #5.
---
--- This module re-exports all constructors from the Types
--- module, including those not exported by the public API.
--- Use only in tests or trusted internal code.
-
-module Lightning.Protocol.BOLT5.Internal (
- -- * All type constructors (for test use)
- CloseType(..)
- , UnresolvedOutput(..)
- , OutputResolution(..)
- , HTLCOutputType(..)
- , SpendingTx(..)
- , RevokedOutput(..)
- , RevokedOutputType(..)
- ) where
-
-import Lightning.Protocol.BOLT5.Types
diff --git a/lib/Lightning/Protocol/BOLT5/Spend.hs b/lib/Lightning/Protocol/BOLT5/Spend.hs
@@ -1,4 +1,4 @@
-{-# OPTIONS_HADDOCK prune #-}
+{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE BangPatterns #-}
-- |
@@ -11,15 +11,13 @@
-- transaction handling.
--
-- All functions produce unsigned 'SpendingTx' values. The caller
--- is responsible for signing (using the sighash metadata
--- provided) and assembling final witnesses via bolt3 witness
--- constructors.
+-- signs (using the sighash metadata provided) and assembles the
+-- witness with bolt3's witness functions.
module Lightning.Protocol.BOLT5.Spend (
-- * Local commitment spends
spend_to_local
- , spend_htlc_timeout
- , spend_htlc_success
+ , spend_htlc_tx
, spend_htlc_output
-- * Remote commitment spends
@@ -36,27 +34,27 @@ module Lightning.Protocol.BOLT5.Spend (
, spend_anchor_anyone
) where
-import Bitcoin.Prim.Tx (TxOut(..))
+import Bitcoin.Prim.Tx (OutPoint, Tx(..), TxIn(..), TxOut(..))
import Bitcoin.Prim.Tx.Sighash (SighashType(..))
+import qualified Data.ByteString as BS
import Data.List.NonEmpty (NonEmpty(..))
import Data.Word (Word32, Word64)
-import qualified Data.ByteString as BS
-import Lightning.Protocol.BOLT3 hiding
- (txout_value, txout_script)
+import Lightning.Protocol.BOLT1 (PaymentHash, Satoshi)
+import qualified Lightning.Protocol.BOLT1 as BOLT1
+import Lightning.Protocol.BOLT3
import Lightning.Protocol.BOLT5.Types
-- local commitment spends --------------------------------------------
-- | Spend the to_local output of our local commitment tx.
--
--- Requires waiting for the CSV delay (to_self_delay) before
--- broadcasting. The caller signs with the local delayed privkey
--- and uses 'to_local_witness_spend' from bolt3.
---
--- Returns 'Nothing' if the fee would exceed the output value,
--- or leave an output below the destination's dust threshold.
+-- The input's nSequence is the to_self_delay, so the transaction
+-- is valid only once the delay has passed. The caller signs with
+-- the local delayed privkey and uses bolt3's
+-- 'to_local_witness_spend'.
--
--- The input nSequence is set to the to_self_delay value.
+-- Returns 'Nothing' if the fee would exceed the output value, or
+-- leave an output below the destination's dust threshold.
spend_to_local
:: OutPoint
-- ^ Outpoint of the to_local output.
@@ -69,94 +67,47 @@ spend_to_local
-- ^ Destination scriptPubKey.
-> FeeratePerKw
-> Maybe SpendingTx
-spend_to_local !op !value !revpk !delay !delayedpk
- !destScript !feerate =
- let !witnessScript =
- to_local_script revpk delay delayedpk
- !weight = to_local_penalty_input_weight
- + penalty_tx_base_weight
- !fee = spending_fee feerate weight
- in case sweep_value value fee destScript of
- Nothing -> Nothing
- Just !outputValue ->
- let !tx = mk_spending_tx op
- (fromIntegral (unToSelfDelay delay))
- destScript outputValue 0
- in Just (SpendingTx tx witnessScript value
- SIGHASH_ALL)
+spend_to_local !op !value !revpk delay@(ToSelfDelay d) !delayedpk
+ !dest !feerate =
+ sweep op value (to_local_script revpk delay delayedpk)
+ to_local_penalty_input_weight (fromIntegral d) 0 dest feerate
--- | Construct an HTLC-timeout second-stage transaction.
+-- | Build the HTLC-timeout (for an offered HTLC) or HTLC-success
+-- (for a received HTLC) transaction spending an HTLC output of
+-- our local commitment, with bolt3's 'build_htlc_tx'.
--
--- Used when we offered an HTLC on our local commitment and it
--- has timed out. The bolt3 'build_htlc_timeout_tx' function
--- constructs the HTLC-timeout tx; this wraps it as a
--- 'SpendingTx' with the witness script and sighash metadata.
---
--- The sighash type is for the local signature, which is always
--- @SIGHASH_ALL@. With @option_anchors@ the remote's
--- @htlc_signature@ instead uses
--- @SIGHASH_SINGLE|SIGHASH_ANYONECANPAY@.
-spend_htlc_timeout
- :: HTLCContext
- -> CommitmentKeys
- -- ^ Full commitment keys (needed for witness script).
- -> SpendingTx
-spend_htlc_timeout !ctx !keys =
- let !htlcTx = build_htlc_timeout_tx ctx
- !htlc = hc_htlc ctx
- !features = hc_features ctx
- !witnessScript = offered_htlc_script
- (ck_revocation_pubkey keys)
- (ck_remote_htlc keys)
- (ck_local_htlc keys)
- (htlc_payment_hash htlc)
- features
- !inputValue =
- msatToSat (htlc_amount_msat htlc)
- !tx = htlc_tx_to_tx htlcTx
- in SpendingTx tx witnessScript inputValue SIGHASH_ALL
-
--- | Construct an HTLC-success second-stage transaction.
+-- The sighash type is that of the local signature, which is
+-- always @SIGHASH_ALL@. The remote's @htlc_signature@ uses bolt3's
+-- 'remote_htlc_sighash' (@SIGHASH_SINGLE|SIGHASH_ANYONECANPAY@
+-- under 'Anchors'). The witness is bolt3's 'htlc_timeout_witness'
+-- or 'htlc_success_witness'.
--
--- Used when we received an HTLC on our local commitment and
--- have the preimage. The bolt3 'build_htlc_success_tx' function
--- constructs the HTLC-success tx; this wraps it as a
--- 'SpendingTx'.
+-- Under 'Anchors' the transaction pays no fee; it must be
+-- combined with other inputs before broadcast.
--
--- The sighash type is for the local signature, which is always
--- @SIGHASH_ALL@. With @option_anchors@ the remote's
--- @htlc_signature@ instead uses
--- @SIGHASH_SINGLE|SIGHASH_ANYONECANPAY@.
-spend_htlc_success
+-- Returns 'Nothing' if bolt3 can't build the transaction (the fee
+-- exceeds the HTLC amount).
+spend_htlc_tx
:: HTLCContext
-> CommitmentKeys
- -- ^ Full commitment keys (needed for witness script).
- -> SpendingTx
-spend_htlc_success !ctx !keys =
- let !htlcTx = build_htlc_success_tx ctx
- !htlc = hc_htlc ctx
- !features = hc_features ctx
- !witnessScript = received_htlc_script
- (ck_revocation_pubkey keys)
- (ck_remote_htlc keys)
- (ck_local_htlc keys)
- (htlc_payment_hash htlc)
- (htlc_cltv_expiry htlc)
- features
- !inputValue =
- msatToSat (htlc_amount_msat htlc)
- !tx = htlc_tx_to_tx htlcTx
- in SpendingTx tx witnessScript inputValue SIGHASH_ALL
+ -- ^ Keys for our local commitment (for the witness script).
+ -> Maybe SpendingTx
+spend_htlc_tx !ctx !keys = do
+ htx <- build_htlc_tx ctx
+ let !h = hc_htlc ctx
+ pure SpendingTx
+ { stx_tx = htlc_to_tx htx
+ , stx_input_script = htlc_script keys (hc_format ctx) h
+ , stx_input_value = BOLT1.msat_to_sat (htlc_amount_msat h)
+ , stx_sighash_type = SIGHASH_ALL
+ }
--- | Spend a second-stage HTLC output (HTLC-timeout or
--- HTLC-success output) after the CSV delay.
+-- | Spend the output of an HTLC-timeout or HTLC-success
+-- transaction after the CSV delay.
--
--- The output of an HTLC-timeout or HTLC-success tx uses the
--- same to_local script. The caller signs with the local
--- delayed privkey and uses 'htlc_output_witness_spend'.
---
--- Returns 'Nothing' if the fee would exceed the output value,
--- or leave an output below the destination's dust threshold.
+-- The output uses the to_local script, so this is
+-- 'spend_to_local'. The caller signs with the local delayed
+-- privkey and uses bolt3's 'to_local_witness_spend'.
spend_htlc_output
:: OutPoint
-- ^ Outpoint of the second-stage output.
@@ -173,14 +124,15 @@ spend_htlc_output = spend_to_local
-- remote commitment spends -------------------------------------------
--- | Spend an offered HTLC directly after timeout on the remote
--- commitment.
+-- | Spend an HTLC we offered directly, after it times out, from the
+-- remote commitment (where it is a received HTLC).
--
--- On the remote commitment, their received HTLCs (our offered)
--- have timed out and we can sweep them directly.
+-- The locktime is the HTLC's CLTV expiry. The caller signs with
+-- our HTLC privkey and uses bolt3's
+-- 'received_htlc_witness_timeout'.
--
--- Returns 'Nothing' if the fee would exceed the output value,
--- or leave an output below the destination's dust threshold.
+-- Returns 'Nothing' if the fee would exceed the output value, or
+-- leave an output below the destination's dust threshold.
spend_remote_htlc_timeout
:: OutPoint
-- ^ Outpoint of the HTLC output.
@@ -190,44 +142,28 @@ spend_remote_htlc_timeout
-- ^ The HTLC being spent.
-> CommitmentKeys
-- ^ Keys for the remote commitment.
- -> ChannelFeatures
+ -> CommitmentFormat
-> Script
-- ^ Destination scriptPubKey.
-> FeeratePerKw
-> Maybe SpendingTx
-spend_remote_htlc_timeout !op !value !htlc !keys
- !features !destScript !feerate =
- let !witnessScript = received_htlc_script
- (ck_revocation_pubkey keys)
- (ck_remote_htlc keys)
- (ck_local_htlc keys)
- (htlc_payment_hash htlc)
- (htlc_cltv_expiry htlc)
- features
- !weight = htlcOutputTypeWeight features
- (HTLCReceivedOutput (htlc_cltv_expiry htlc))
- + penalty_tx_base_weight
- !fee = spending_fee feerate weight
- in case sweep_value value fee destScript of
- Nothing -> Nothing
- Just !outputValue ->
- let !locktime =
- unCltvExpiry (htlc_cltv_expiry htlc)
- !seqNo =
- if has_anchors features then 1 else 0
- !tx = mk_spending_tx op seqNo destScript
- outputValue locktime
- in Just (SpendingTx tx witnessScript value
- SIGHASH_ALL)
+spend_remote_htlc_timeout !op !value !htlc !keys !fmt !dest !feerate =
+ let expiry@(CltvExpiry e) = htlc_cltv_expiry htlc
+ !script = received_htlc_script (ck_revocation_pubkey keys)
+ (ck_remote_htlc keys) (ck_local_htlc keys)
+ (htlc_payment_hash htlc) expiry fmt
+ in sweep op value script
+ (htlc_output_type_weight fmt (HTLCReceivedOutput expiry))
+ (remote_htlc_sequence fmt) e dest feerate
--- | Spend a received HTLC directly with preimage on the remote
--- commitment.
+-- | Spend an HTLC we received directly with the preimage, from the
+-- remote commitment (where it is an offered HTLC).
--
--- On the remote commitment, their offered HTLCs (our received)
--- can be claimed with the payment preimage.
+-- The caller signs with our HTLC privkey and uses bolt3's
+-- 'offered_htlc_witness_preimage'.
--
--- Returns 'Nothing' if the fee would exceed the output value,
--- or leave an output below the destination's dust threshold.
+-- Returns 'Nothing' if the fee would exceed the output value, or
+-- leave an output below the destination's dust threshold.
spend_remote_htlc_preimage
:: OutPoint
-- ^ Outpoint of the HTLC output.
@@ -237,42 +173,29 @@ spend_remote_htlc_preimage
-- ^ The HTLC being spent.
-> CommitmentKeys
-- ^ Keys for the remote commitment.
- -> ChannelFeatures
+ -> CommitmentFormat
-> Script
-- ^ Destination scriptPubKey.
-> FeeratePerKw
-> Maybe SpendingTx
-spend_remote_htlc_preimage !op !value !htlc !keys
- !features !destScript !feerate =
- let !witnessScript = offered_htlc_script
- (ck_revocation_pubkey keys)
- (ck_remote_htlc keys)
- (ck_local_htlc keys)
- (htlc_payment_hash htlc)
- features
- !weight = htlcOutputTypeWeight features
- (HTLCOfferedOutput (htlc_cltv_expiry htlc))
- + penalty_tx_base_weight
- !fee = spending_fee feerate weight
- in case sweep_value value fee destScript of
- Nothing -> Nothing
- Just !outputValue ->
- let !seqNo =
- if has_anchors features then 1 else 0
- !tx = mk_spending_tx op seqNo destScript
- outputValue 0
- in Just (SpendingTx tx witnessScript value
- SIGHASH_ALL)
+spend_remote_htlc_preimage !op !value !htlc !keys !fmt !dest !feerate =
+ let !expiry = htlc_cltv_expiry htlc
+ !script = offered_htlc_script (ck_revocation_pubkey keys)
+ (ck_remote_htlc keys) (ck_local_htlc keys)
+ (htlc_payment_hash htlc) fmt
+ in sweep op value script
+ (htlc_output_type_weight fmt (HTLCOfferedOutput expiry))
+ (remote_htlc_sequence fmt) 0 dest feerate
-- revoked commitment spends ------------------------------------------
--- | Spend a revoked to_local output using the revocation key.
+-- | Spend a revoked to_local output with the revocation key.
--
--- The caller signs with the revocation privkey and uses
--- 'to_local_witness_revoke' from bolt3.
+-- The caller signs with the revocation privkey and uses bolt3's
+-- 'to_local_witness_revoke'.
--
--- Returns 'Nothing' if the fee would exceed the output value,
--- or leave an output below the destination's dust threshold.
+-- Returns 'Nothing' if the fee would exceed the output value, or
+-- leave an output below the destination's dust threshold.
spend_revoked_to_local
:: OutPoint
-- ^ Outpoint of the to_local output.
@@ -285,30 +208,19 @@ spend_revoked_to_local
-- ^ Destination scriptPubKey.
-> FeeratePerKw
-> Maybe SpendingTx
-spend_revoked_to_local !op !value !revpk !delay
- !delayedpk !destScript !feerate =
- let !witnessScript =
- to_local_script revpk delay delayedpk
- !weight = to_local_penalty_input_weight
- + penalty_tx_base_weight
- !fee = spending_fee feerate weight
- in case sweep_value value fee destScript of
- Nothing -> Nothing
- Just !outputValue ->
- let !tx = mk_spending_tx op 0xFFFFFFFF destScript
- outputValue 0
- in Just (SpendingTx tx witnessScript value
- SIGHASH_ALL)
+spend_revoked_to_local !op !value !revpk !delay !delayedpk !dest
+ !feerate =
+ sweep op value (to_local_script revpk delay delayedpk)
+ to_local_penalty_input_weight 0xFFFFFFFF 0 dest feerate
--- | Spend a revoked HTLC output using the revocation key.
+-- | Spend a revoked HTLC output with the revocation key.
--
--- The caller signs with the revocation privkey and uses
--- 'offered_htlc_witness_revoke' or
--- 'received_htlc_witness_revoke' from bolt3, depending on
--- the output type.
+-- The caller signs with the revocation privkey and uses bolt3's
+-- 'offered_htlc_witness_revoke' or 'received_htlc_witness_revoke',
+-- according to the output type.
--
--- Returns 'Nothing' if the fee would exceed the output value,
--- or leave an output below the destination's dust threshold.
+-- Returns 'Nothing' if the fee would exceed the output value, or
+-- leave an output below the destination's dust threshold.
spend_revoked_htlc
:: OutPoint
-- ^ Outpoint of the HTLC output.
@@ -318,60 +230,30 @@ spend_revoked_htlc
-- ^ Whether offered or received HTLC.
-> RevocationPubkey
-> CommitmentKeys
- -> ChannelFeatures
+ -> CommitmentFormat
-> PaymentHash
-> Script
-- ^ Destination scriptPubKey.
-> FeeratePerKw
-> Maybe SpendingTx
-spend_revoked_htlc !op !value !htype !revpk !keys
- !features !ph !destScript !feerate =
- case htype of
- HTLCOfferedOutput _ ->
- let !witnessScript = offered_htlc_script
- revpk
- (ck_remote_htlc keys)
- (ck_local_htlc keys)
- ph
- features
- !weight = htlcOutputTypeWeight features htype
- + penalty_tx_base_weight
- !fee = spending_fee feerate weight
- in case sweep_value value fee destScript of
- Nothing -> Nothing
- Just !outputValue ->
- let !tx = mk_spending_tx op 0xFFFFFFFF
- destScript outputValue 0
- in Just (SpendingTx tx witnessScript value
- SIGHASH_ALL)
- HTLCReceivedOutput expiry ->
- let !witnessScript = received_htlc_script
- revpk
- (ck_remote_htlc keys)
- (ck_local_htlc keys)
- ph
- expiry
- features
- !weight = htlcOutputTypeWeight features htype
- + penalty_tx_base_weight
- !fee = spending_fee feerate weight
- in case sweep_value value fee destScript of
- Nothing -> Nothing
- Just !outputValue ->
- let !tx = mk_spending_tx op 0xFFFFFFFF
- destScript outputValue 0
- in Just (SpendingTx tx witnessScript value
- SIGHASH_ALL)
+spend_revoked_htlc !op !value !htype !revpk !keys !fmt !ph !dest
+ !feerate =
+ let !remote = ck_remote_htlc keys
+ !local = ck_local_htlc keys
+ !script = case htype of
+ HTLCOfferedOutput _ ->
+ offered_htlc_script revpk remote local ph fmt
+ HTLCReceivedOutput expiry ->
+ received_htlc_script revpk remote local ph expiry fmt
+ in sweep op value script (htlc_output_type_weight fmt htype)
+ 0xFFFFFFFF 0 dest feerate
--- | Spend a revoked second-stage HTLC output (HTLC-timeout or
--- HTLC-success output) using the revocation key.
---
--- The output of a revoked HTLC-timeout/success tx uses the
--- to_local script. The caller signs with the revocation privkey
--- and uses 'htlc_output_witness_revoke'.
+-- | Spend the output of a revoked HTLC-timeout or HTLC-success
+-- transaction with the revocation key.
--
--- Returns 'Nothing' if the fee would exceed the output value,
--- or leave an output below the destination's dust threshold.
+-- The output uses the to_local script, so this is
+-- 'spend_revoked_to_local'. The caller signs with the revocation
+-- privkey and uses bolt3's 'to_local_witness_revoke'.
spend_revoked_htlc_output
:: OutPoint
-- ^ Outpoint of the second-stage output.
@@ -384,27 +266,16 @@ spend_revoked_htlc_output
-- ^ Destination scriptPubKey.
-> FeeratePerKw
-> Maybe SpendingTx
-spend_revoked_htlc_output !op !value !revpk !delay
- !delayedpk !destScript !feerate =
- let !witnessScript =
- to_local_script revpk delay delayedpk
- !weight = to_local_penalty_input_weight
- + penalty_tx_base_weight
- !fee = spending_fee feerate weight
- in case sweep_value value fee destScript of
- Nothing -> Nothing
- Just !outputValue ->
- let !tx = mk_spending_tx op 0xFFFFFFFF destScript
- outputValue 0
- in Just (SpendingTx tx witnessScript value
- SIGHASH_ALL)
+spend_revoked_htlc_output = spend_revoked_to_local
-- anchor spends ------------------------------------------------------
--- | Spend an anchor output as the owner (immediately).
+-- | Spend an anchor output as its owner (immediately).
--
--- The caller signs with the funding privkey and uses
--- 'anchor_witness_owner' from bolt3.
+-- The transaction pays no fee and has a single input: it must be
+-- combined with other inputs before broadcast. The caller signs
+-- with the funding privkey and uses bolt3's
+-- 'anchor_witness_owner'.
spend_anchor_owner
:: OutPoint
-- ^ Outpoint of the anchor output.
@@ -414,15 +285,16 @@ spend_anchor_owner
-> Script
-- ^ Destination scriptPubKey.
-> SpendingTx
-spend_anchor_owner !op !value !fundpk !destScript =
- let !witnessScript = anchor_script fundpk
- !tx = mk_spending_tx op 0xFFFFFFFE destScript
- value 0
- in SpendingTx tx witnessScript value SIGHASH_ALL
+spend_anchor_owner !op !value !fundpk !dest =
+ SpendingTx (mk_spending_tx op 0xFFFFFFFE dest value 0)
+ (anchor_script fundpk) value SIGHASH_ALL
--- | Spend an anchor output as anyone (after 16 blocks).
+-- | Spend an anchor output as anyone (16 blocks after the
+-- commitment confirms).
--
--- Uses 'anchor_witness_anyone' from bolt3 (empty signature).
+-- As 'spend_anchor_owner', the transaction pays no fee. The
+-- witness is bolt3's 'anchor_witness_anyone', which needs no
+-- signature.
spend_anchor_anyone
:: OutPoint
-- ^ Outpoint of the anchor output.
@@ -432,98 +304,108 @@ spend_anchor_anyone
-> Script
-- ^ Destination scriptPubKey.
-> SpendingTx
-spend_anchor_anyone !op !value !fundpk !destScript =
- let !witnessScript = anchor_script fundpk
- !tx = mk_spending_tx op 16 destScript value 0
- in SpendingTx tx witnessScript value SIGHASH_ALL
+spend_anchor_anyone !op !value !fundpk !dest =
+ SpendingTx (mk_spending_tx op 16 dest value 0)
+ (anchor_script fundpk) value SIGHASH_ALL
-- internal helpers ---------------------------------------------------
+-- | A single-input, single-output sweep paying the input value, less
+-- the fee for the given input weight, to the destination.
+sweep
+ :: OutPoint
+ -> Satoshi -- ^ input value
+ -> Script -- ^ input witness script
+ -> Word64 -- ^ input weight
+ -> Word32 -- ^ input nSequence
+ -> Word32 -- ^ locktime
+ -> Script -- ^ destination scriptPubKey
+ -> FeeratePerKw
+ -> Maybe SpendingTx
+sweep !op !value !script !weight !sq !locktime !dest !feerate = do
+ let !fee = spending_fee feerate (weight + penalty_tx_base_weight)
+ out <- sweep_value value fee dest
+ pure (SpendingTx (mk_spending_tx op sq dest out locktime) script
+ value SIGHASH_ALL)
+
-- | Value of a sweep's single output: the input value less the fee.
--
--- 'Nothing' if the fee meets or exceeds the input value, or if the
--- output would be below the dust threshold of its script.
+-- 'Nothing' if the fee exceeds the input value, or if the output
+-- would be below the dust threshold of its script.
sweep_value :: Satoshi -> Satoshi -> Script -> Maybe Satoshi
-sweep_value !value !fee !destScript
- | unSatoshi fee >= unSatoshi value = Nothing
- | unSatoshi out < unSatoshi (dust_threshold destScript) = Nothing
- | otherwise = Just out
- where
- out = Satoshi (unSatoshi value - unSatoshi fee)
+sweep_value !value !fee (Script dest) = do
+ out <- BOLT1.sub_sat value fee
+ if BOLT1.un_satoshi out < dust_threshold dest
+ then Nothing
+ else Just out
--- | Dust threshold for an output paying to a script.
+-- | Dust threshold, in satoshis, for an output paying to a script.
--
--- As Bitcoin Core computes it for spendable outputs at the default
--- dust relay feerate of 3 sat/vbyte: three times the size of the
--- output plus that of an input spending it (67 vbytes for a
--- witness program, 148 otherwise). This gives 546 sats for P2PKH,
--- 540 for P2SH, 294 for P2WPKH, and 330 for P2WSH and P2TR.
-dust_threshold :: Script -> Satoshi
-dust_threshold (Script s) =
+-- As Bitcoin Core computes it for spendable outputs at the default
+-- dust relay feerate of 3 sat/vbyte: three times the size of the
+-- output plus that of an input spending it (67 vbytes for a
+-- witness program, 148 otherwise). This gives 546 sats for P2PKH,
+-- 540 for P2SH, 294 for P2WPKH, and 330 for P2WSH and P2TR.
+dust_threshold :: BS.ByteString -> Word64
+dust_threshold s =
let !len = fromIntegral (BS.length s) :: Word64
- !lenSize = if len < 0xfd then 1
- else if len <= 0xffff then 3
- else 5
- !spendSize = if is_witness_program s then 67 else 148
- in Satoshi (3 * (8 + lenSize + len + spendSize))
+ !len_size
+ | len < 0xfd = 1
+ | len <= 0xffff = 3
+ | otherwise = 5
+ !spend_size = if is_witness_program s then 67 else 148
+ in 3 * (8 + len_size + len + spend_size)
-- | Whether a script is a segwit witness program: a version opcode
-- (OP_0 or OP_1 to OP_16) followed by a single 2-40 byte push.
is_witness_program :: BS.ByteString -> Bool
is_witness_program s = case BS.unpack (BS.take 2 s) of
- [version, pushLen] ->
+ [version, push_len] ->
(version == 0x00 || (version >= 0x51 && version <= 0x60))
- && pushLen >= 2 && pushLen <= 40
- && fromIntegral pushLen + 2 == BS.length s
+ && push_len >= 2 && push_len <= 40
+ && fromIntegral push_len + 2 == BS.length s
_ -> False
--- | Build a simple single-input single-output spending tx.
+-- | The nSequence of a direct HTLC spend from the remote commitment:
+-- 1 under 'Anchors' (for the HTLC scripts' CSV), 0 otherwise.
+remote_htlc_sequence :: CommitmentFormat -> Word32
+remote_htlc_sequence fmt = case fmt of
+ StaticRemotekey -> 0
+ Anchors -> 1
+
+-- | The witness script of an HTLC output of a commitment with the
+-- given keys.
+htlc_script :: CommitmentKeys -> CommitmentFormat -> HTLC -> Script
+htlc_script !keys !fmt !h =
+ let !revpk = ck_revocation_pubkey keys
+ !remote = ck_remote_htlc keys
+ !local = ck_local_htlc keys
+ !ph = htlc_payment_hash h
+ in case htlc_direction h of
+ HTLCOffered -> offered_htlc_script revpk remote local ph fmt
+ HTLCReceived ->
+ received_htlc_script revpk remote local ph
+ (htlc_cltv_expiry h) fmt
+
+-- | Build a version 2, single-input, single-output transaction.
mk_spending_tx
- :: OutPoint -- ^ Input outpoint
- -> Word32 -- ^ Input nSequence
- -> Script -- ^ Output scriptPubKey
- -> Satoshi -- ^ Output value
- -> Word32 -- ^ Locktime
+ :: OutPoint -- ^ input outpoint
+ -> Word32 -- ^ input nSequence
+ -> Script -- ^ output scriptPubKey
+ -> Satoshi -- ^ output value
+ -> Word32 -- ^ locktime
-> Tx
-mk_spending_tx !op !seqNo !destScript !outputValue
- !locktime =
- let !txIn = TxIn
- { txin_prevout = op
- , txin_script_sig = BS.empty
- , txin_sequence = seqNo
- }
- !txOut = TxOut
- { txout_value = unSatoshi outputValue
- , txout_script_pubkey = unScript destScript
- }
- in Tx
- { tx_version = 2
- , tx_inputs = txIn :| []
- , tx_outputs = txOut :| []
- , tx_witnesses = []
- , tx_locktime = locktime
- }
-
--- | Convert a bolt3 HTLCTx to a ppad-tx Tx.
-htlc_tx_to_tx :: HTLCTx -> Tx
-htlc_tx_to_tx !htx =
- let !txIn = TxIn
- { txin_prevout = htx_input_outpoint htx
- , txin_script_sig = BS.empty
- , txin_sequence =
- unSequence (htx_input_sequence htx)
- }
- !txOut = TxOut
- { txout_value =
- unSatoshi (htx_output_value htx)
- , txout_script_pubkey =
- unScript (htx_output_script htx)
- }
- in Tx
- { tx_version = htx_version htx
- , tx_inputs = txIn :| []
- , tx_outputs = txOut :| []
- , tx_witnesses = []
- , tx_locktime =
- unLocktime (htx_locktime htx)
- }
+mk_spending_tx !op !sq (Script dest) !value !locktime = Tx
+ { tx_version = 2
+ , tx_inputs = TxIn
+ { txin_prevout = op
+ , txin_script_sig = BS.empty
+ , txin_sequence = sq
+ } :| []
+ , tx_outputs = TxOut
+ { txout_value = BOLT1.un_satoshi value
+ , txout_script_pubkey = dest
+ } :| []
+ , tx_witnesses = []
+ , tx_locktime = locktime
+ }
diff --git a/lib/Lightning/Protocol/BOLT5/Types.hs b/lib/Lightning/Protocol/BOLT5/Types.hs
@@ -1,4 +1,4 @@
-{-# OPTIONS_HADDOCK prune #-}
+{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
@@ -20,8 +20,8 @@ module Lightning.Protocol.BOLT5.Types (
-- * HTLC output type
, HTLCOutputType(..)
- , htlcOutputType
- , htlcOutputTypeWeight
+ , htlc_output_type
+ , htlc_output_type_weight
-- * Spending transactions
, SpendingTx(..)
@@ -50,19 +50,22 @@ module Lightning.Protocol.BOLT5.Types (
, spending_fee
) where
-import Bitcoin.Prim.Tx (Tx(..))
-import Bitcoin.Prim.Tx.Sighash (SighashType(..))
+import Bitcoin.Prim.Tx (OutPoint, Tx)
+import Bitcoin.Prim.Tx.Sighash (SighashType)
+import Control.DeepSeq (NFData(..))
import Data.Word (Word64)
import GHC.Generics (Generic)
-import Lightning.Protocol.BOLT3.Types
-import Lightning.Protocol.BOLT3.Tx (
- CommitmentKeys(..)
- , OutputType(..)
- )
+import Lightning.Protocol.BOLT1 (Satoshi)
+import qualified Lightning.Protocol.BOLT1 as BOLT1
+import Lightning.Protocol.BOLT3
-- close identification -----------------------------------------------
-- | What kind of close was detected on chain.
+--
+-- @identify_close@ produces only 'LocalCommitClose' and
+-- 'RemoteCommitClose'; recognising mutual and revoked closes is
+-- left to the caller.
data CloseType
= MutualClose
-- ^ Cooperative closure agreed by both parties.
@@ -74,6 +77,8 @@ data CloseType
-- ^ A revoked (outdated) commitment transaction was broadcast.
deriving (Eq, Show, Generic)
+instance NFData CloseType
+
-- output classification ----------------------------------------------
-- | An unresolved commitment transaction output.
@@ -83,42 +88,46 @@ data UnresolvedOutput = UnresolvedOutput
, uo_type :: !OutputResolution
} deriving (Eq, Show, Generic)
+instance NFData UnresolvedOutput
+
-- | How to resolve an output, per BOLT #5 rules.
data OutputResolution
= Resolved
- -- ^ Already resolved (e.g. to_remote on local commit).
+ -- ^ Nothing to do (e.g. to_remote on our local commitment).
| SpendToLocal
!ToSelfDelay !RevocationPubkey !LocalDelayedPubkey
- -- ^ Spend to_local after CSV delay.
+ -- ^ Spend to_local after the CSV delay.
| SpendHTLCTimeout
- !HTLC !CommitmentKeys !ChannelFeatures
- -- ^ Spend via HTLC-timeout second-stage tx (local commit,
- -- local offer).
+ !HTLC !CommitmentKeys !CommitmentFormat
+ -- ^ Spend via an HTLC-timeout transaction (our commitment, an
+ -- HTLC we offered).
| SpendHTLCSuccess
- !HTLC !CommitmentKeys !ChannelFeatures
- -- ^ Spend via HTLC-success second-stage tx (local commit,
- -- remote offer).
+ !HTLC !CommitmentKeys !CommitmentFormat
+ -- ^ Spend via an HTLC-success transaction (our commitment, an
+ -- HTLC we received).
| SpendHTLCTimeoutDirect !HTLC
- -- ^ Spend HTLC directly after timeout (remote commit,
- -- local offer).
+ -- ^ Spend the HTLC directly after it times out (the remote
+ -- commitment, an HTLC we offered).
| SpendHTLCPreimageDirect !HTLC
- -- ^ Spend HTLC directly with preimage (remote commit,
- -- remote offer).
+ -- ^ Spend the HTLC directly with the preimage (the remote
+ -- commitment, an HTLC we received).
| Revoke !RevocationPubkey
- -- ^ Spend revoked to_local with revocation key.
+ -- ^ Spend a revoked to_local output with the revocation key.
| RevokeHTLC !RevocationPubkey !HTLCOutputType
- -- ^ Spend revoked HTLC output with revocation key.
+ -- ^ Spend a revoked HTLC output with the revocation key.
| AnchorSpend !FundingPubkey
- -- ^ Spend anchor output.
+ -- ^ Spend our anchor output.
deriving (Eq, Show, Generic)
+instance NFData OutputResolution
+
-- HTLC output type -------------------------------------------------
--- | Type of HTLC output, restricted to only HTLC variants.
+-- | The kind of an HTLC output, from the commitment owner's point of
+-- view.
--
--- Unlike bolt3's 'OutputType' which includes all six output
--- types, this ADT makes it impossible to confuse HTLC outputs
--- with to_local, to_remote, or anchor outputs.
+-- Unlike bolt3's 'OutputType', this can't describe a to_local,
+-- to_remote or anchor output.
data HTLCOutputType
= HTLCOfferedOutput {-# UNPACK #-} !CltvExpiry
-- ^ Offered HTLC output with CLTV expiry.
@@ -126,63 +135,74 @@ data HTLCOutputType
-- ^ Received HTLC output with CLTV expiry.
deriving (Eq, Show, Generic)
--- | Extract an 'HTLCOutputType' from a bolt3 'OutputType'.
+instance NFData HTLCOutputType
+
+-- | The 'HTLCOutputType' of a bolt3 'OutputType', or 'Nothing' if
+-- it isn't an HTLC output.
--
--- Returns 'Nothing' for non-HTLC output types.
+-- >>> htlc_output_type OutputToLocal
+-- Nothing
+htlc_output_type :: OutputType -> Maybe HTLCOutputType
+htlc_output_type t = case t of
+ OutputHTLC h -> Just $ case htlc_direction h of
+ HTLCOffered -> HTLCOfferedOutput (htlc_cltv_expiry h)
+ HTLCReceived -> HTLCReceivedOutput (htlc_cltv_expiry h)
+ OutputToLocal -> Nothing
+ OutputToRemote -> Nothing
+ OutputLocalAnchor -> Nothing
+ OutputRemoteAnchor -> Nothing
+{-# INLINE htlc_output_type #-}
+
+-- | Penalty input weight for an HTLC output.
--
--- >>> htlcOutputType (OutputOfferedHTLC (CltvExpiry 500))
--- Just (HTLCOfferedOutput (CltvExpiry ...))
--- >>> htlcOutputType OutputToLocal
--- Nothing
-htlcOutputType :: OutputType -> Maybe HTLCOutputType
-htlcOutputType (OutputOfferedHTLC e) =
- Just (HTLCOfferedOutput e)
-htlcOutputType (OutputReceivedHTLC e) =
- Just (HTLCReceivedOutput e)
-htlcOutputType _ = Nothing
-{-# INLINE htlcOutputType #-}
-
--- | Penalty input weight for an HTLC output type.
-htlcOutputTypeWeight :: ChannelFeatures -> HTLCOutputType -> Word64
-htlcOutputTypeWeight !features (HTLCOfferedOutput _)
- | has_anchors features = offered_htlc_penalty_input_weight_anchors
- | otherwise = offered_htlc_penalty_input_weight
-htlcOutputTypeWeight !features (HTLCReceivedOutput _)
- | has_anchors features = accepted_htlc_penalty_input_weight_anchors
- | otherwise = accepted_htlc_penalty_input_weight
-{-# INLINE htlcOutputTypeWeight #-}
+-- >>> htlc_output_type_weight Anchors (HTLCOfferedOutput (CltvExpiry 500))
+-- 410
+htlc_output_type_weight :: CommitmentFormat -> HTLCOutputType -> Word64
+htlc_output_type_weight !fmt !t = case (fmt, t) of
+ (StaticRemotekey, HTLCOfferedOutput _) ->
+ offered_htlc_penalty_input_weight
+ (StaticRemotekey, HTLCReceivedOutput _) ->
+ accepted_htlc_penalty_input_weight
+ (Anchors, HTLCOfferedOutput _) ->
+ offered_htlc_penalty_input_weight_anchors
+ (Anchors, HTLCReceivedOutput _) ->
+ accepted_htlc_penalty_input_weight_anchors
+{-# INLINE htlc_output_type_weight #-}
-- spending transactions ----------------------------------------------
--- | Unsigned spending transaction, ready for caller to sign.
+-- | An unsigned spending transaction, with what the caller needs to
+-- sign its (single) input.
--
--- The caller uses bolt3 witness constructors to assemble the
--- final witness after signing.
+-- The caller signs the input's BIP143 sighash (ppad-tx's
+-- @sighash_segwit@ over 'stx_tx', input 0, 'stx_input_script',
+-- 'stx_input_value' and 'stx_sighash_type') and assembles the
+-- witness with bolt3's witness functions.
data SpendingTx = SpendingTx
{ stx_tx :: !Tx
-- ^ The unsigned transaction.
, stx_input_script :: !Script
- -- ^ Witness script for the input being spent.
+ -- ^ Witness script of the output being spent.
, stx_input_value :: {-# UNPACK #-} !Satoshi
- -- ^ Value of the input being spent (for sighash).
+ -- ^ Value of the output being spent.
, stx_sighash_type :: !SighashType
- -- ^ Sighash type for the local signature.
+ -- ^ Sighash type of the local signature.
} deriving (Eq, Show, Generic)
+instance NFData SpendingTx where
+ rnf (SpendingTx tx s v h) = rnf tx `seq` rnf s `seq` rnf v `seq` h `seq` ()
+
-- revoked outputs ----------------------------------------------------
-- | A revoked output that can be swept with a revocation key.
---
--- This type restricts penalty transaction inputs to only
--- valid revocation targets: to_local outputs and HTLC outputs.
--- Other output types (to_remote, anchors) cannot appear in
--- penalty transactions.
data RevokedOutput = RevokedOutput
{ ro_outpoint :: !OutPoint
, ro_value :: {-# UNPACK #-} !Satoshi
, ro_type :: !RevokedOutputType
} deriving (Eq, Show, Generic)
+instance NFData RevokedOutput
+
-- | What kind of revoked output is being swept.
data RevokedOutputType
= RevokedToLocal
@@ -191,93 +211,104 @@ data RevokedOutputType
-- ^ Revoked HTLC output.
deriving (Eq, Show, Generic)
+instance NFData RevokedOutputType
+
-- | Penalty input weight for a revoked output.
-revoked_output_weight :: ChannelFeatures -> RevokedOutput -> Word64
-revoked_output_weight !features !ro = case ro_type ro of
+revoked_output_weight :: CommitmentFormat -> RevokedOutput -> Word64
+revoked_output_weight !fmt !ro = case ro_type ro of
RevokedToLocal ->
to_local_penalty_input_weight
RevokedHTLC !htype ->
- htlcOutputTypeWeight features htype
+ htlc_output_type_weight fmt htype
{-# INLINE revoked_output_weight #-}
-- weight constants (BOLT #5 Appendix A) ------------------------------
-- | Expected weight of the to_local penalty transaction witness
--- (160 bytes).
+-- (160 weight units).
to_local_penalty_witness_weight :: Word64
to_local_penalty_witness_weight = 160
-- | Expected weight of the offered_htlc penalty transaction
--- witness (243 bytes), for a 133-byte witness script (no
--- option_anchors).
+-- witness (243 weight units), for a 133-byte witness script
+-- (no option_anchors).
offered_htlc_penalty_witness_weight :: Word64
offered_htlc_penalty_witness_weight = 243
-- | Expected weight of the accepted_htlc penalty transaction
--- witness (249 bytes), for a 139-byte witness script (no
--- option_anchors).
+-- witness (249 weight units), for a 139-byte witness script
+-- (no option_anchors).
accepted_htlc_penalty_witness_weight :: Word64
accepted_htlc_penalty_witness_weight = 249
-- | Expected weight of the offered_htlc penalty transaction
--- witness with option_anchors (246 bytes), whose witness script
--- is 136 bytes.
+-- witness with option_anchors (246 weight units), whose witness
+-- script is 136 bytes.
offered_htlc_penalty_witness_weight_anchors :: Word64
offered_htlc_penalty_witness_weight_anchors = 246
-- | Expected weight of the accepted_htlc penalty transaction
--- witness with option_anchors (252 bytes), whose witness script
--- is 142 bytes.
+-- witness with option_anchors (252 weight units), whose witness
+-- script is 142 bytes.
accepted_htlc_penalty_witness_weight_anchors :: Word64
accepted_htlc_penalty_witness_weight_anchors = 252
--- | Weight of a to_local penalty input (164 + 160 = 324 bytes).
+-- | Weight of a to_local penalty input (164 + 160 = 324).
to_local_penalty_input_weight :: Word64
to_local_penalty_input_weight = 324
--- | Weight of an offered_htlc penalty input
--- (164 + 243 = 407 bytes).
+-- | Weight of an offered_htlc penalty input (164 + 243 = 407).
offered_htlc_penalty_input_weight :: Word64
offered_htlc_penalty_input_weight = 407
--- | Weight of an accepted_htlc penalty input
--- (164 + 249 = 413 bytes).
+-- | Weight of an accepted_htlc penalty input (164 + 249 = 413).
accepted_htlc_penalty_input_weight :: Word64
accepted_htlc_penalty_input_weight = 413
-- | Weight of an offered_htlc penalty input with option_anchors
--- (164 + 246 = 410 bytes).
+-- (164 + 246 = 410).
offered_htlc_penalty_input_weight_anchors :: Word64
offered_htlc_penalty_input_weight_anchors = 410
-- | Weight of an accepted_htlc penalty input with option_anchors
--- (164 + 252 = 416 bytes).
+-- (164 + 252 = 416).
accepted_htlc_penalty_input_weight_anchors :: Word64
accepted_htlc_penalty_input_weight_anchors = 416
--- | Weight of a to_remote P2WPKH input
--- (108 + 164 = 272 bytes).
+-- | Weight of a to_remote P2WPKH input (108 + 164 = 272).
to_remote_input_weight :: Word64
to_remote_input_weight = 272
--- | Base weight of a penalty transaction (4*53 + 2 = 214 bytes).
+-- | Base weight of a penalty transaction (4 * 53 + 2 = 214).
--
--- Non-witness: version(4) + input_count(1) + output_count(1) +
--- value(8) + script_len(1) + p2wsh_script(34) + locktime(4) = 53
--- Witness header: 2 bytes.
+-- Non-witness: version (4) + input count (1) + output count (1) +
+-- value (8) + script length (1) + P2WSH script (34) + locktime (4)
+-- = 53 bytes. Witness header: 2 bytes.
+--
+-- The spending transactions here use it as their base weight
+-- whatever their destination script.
penalty_tx_base_weight :: Word64
penalty_tx_base_weight = 214
--- | Maximum standard transaction weight (400,000 bytes).
+-- | Maximum standard transaction weight (400,000).
max_standard_weight :: Word64
max_standard_weight = 400000
-- fee calculation ----------------------------------------------------
--- | Calculate the fee for a spending transaction given its weight.
+-- | The fee for a transaction of the given weight:
+--
+-- @fee = feerate_per_kw * weight / 1000@
+--
+-- rounded down, and saturating at 21 million BTC.
--
--- @fee = feerate_per_kw * weight / 1000@
+-- >>> spending_fee (FeeratePerKw 253) 324
+-- Satoshi 81
spending_fee :: FeeratePerKw -> Word64 -> Satoshi
spending_fee (FeeratePerKw !rate) !weight =
- Satoshi ((fromIntegral rate * weight) `div` 1000)
+ let !f = toInteger rate * toInteger weight `quot` 1000
+ !m = BOLT1.un_satoshi BOLT1.max_satoshi
+ in case BOLT1.satoshi (fromInteger (min f (toInteger m))) of
+ Just s -> s
+ Nothing -> BOLT1.max_satoshi
{-# INLINE spending_fee #-}
diff --git a/ppad-bolt5.cabal b/ppad-bolt5.cabal
@@ -25,16 +25,18 @@ library
-Wall
exposed-modules:
Lightning.Protocol.BOLT5
+ other-modules:
Lightning.Protocol.BOLT5.Detect
- Lightning.Protocol.BOLT5.Internal
Lightning.Protocol.BOLT5.Spend
Lightning.Protocol.BOLT5.Types
build-depends:
base >= 4.9 && < 5
, bytestring >= 0.9 && < 0.13
- , ppad-bolt3 >= 0.0.1 && < 0.1
+ , deepseq >= 1.4 && < 1.6
+ , ppad-bolt1 >= 0.1 && < 0.2
+ , ppad-bolt3 >= 0.1 && < 0.2
, ppad-sha256 >= 0.3.2 && < 0.4
- , ppad-tx >= 0.1 && < 0.2
+ , ppad-tx >= 0.2 && < 0.3
test-suite bolt5-tests
type: exitcode-stdio-1.0
@@ -48,6 +50,7 @@ test-suite bolt5-tests
build-depends:
base
, bytestring
+ , ppad-bolt1
, ppad-bolt3
, ppad-bolt5
, ppad-sha256
@@ -71,6 +74,7 @@ benchmark bolt5-bench
, bytestring
, criterion
, deepseq
+ , ppad-bolt1
, ppad-bolt3
, ppad-bolt5
, ppad-tx
@@ -88,6 +92,7 @@ benchmark bolt5-weigh
base
, bytestring
, deepseq
+ , ppad-bolt1
, ppad-bolt3
, ppad-bolt5
, ppad-tx