bolt5

On-chain transaction handling for Lightning (docs.ppad.tech/bolt5).
git clone git://git.ppad.tech/bolt5.git
Log | Files | Refs | README | LICENSE

Spend.hs (17858B)


      1 {-# OPTIONS_HADDOCK prune #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 
      4 -- |
      5 -- Module: Lightning.Protocol.BOLT5.Spend
      6 -- Copyright: (c) 2025 Jared Tobin
      7 -- License: MIT
      8 -- Maintainer: Jared Tobin <jared@ppad.tech>
      9 --
     10 -- Spending transaction construction for BOLT #5 on-chain
     11 -- transaction handling.
     12 --
     13 -- All functions produce unsigned 'SpendingTx' values. The caller
     14 -- is responsible for signing (using the sighash metadata
     15 -- provided) and assembling final witnesses via bolt3 witness
     16 -- constructors.
     17 
     18 module Lightning.Protocol.BOLT5.Spend (
     19     -- * Local commitment spends
     20     spend_to_local
     21   , spend_htlc_timeout
     22   , spend_htlc_success
     23   , spend_htlc_output
     24 
     25     -- * Remote commitment spends
     26   , spend_remote_htlc_timeout
     27   , spend_remote_htlc_preimage
     28 
     29     -- * Revoked commitment spends
     30   , spend_revoked_to_local
     31   , spend_revoked_htlc
     32   , spend_revoked_htlc_output
     33   , spend_revoked_batch
     34 
     35     -- * Anchor spends
     36   , spend_anchor_owner
     37   , spend_anchor_anyone
     38   ) where
     39 
     40 import Bitcoin.Prim.Tx (TxOut(..))
     41 import Bitcoin.Prim.Tx.Sighash (SighashType(..))
     42 import Data.List.NonEmpty (NonEmpty(..))
     43 import qualified Data.List.NonEmpty as NE
     44 import Data.Word (Word32)
     45 import qualified Data.ByteString as BS
     46 import Lightning.Protocol.BOLT3 hiding
     47   (txout_value, txout_script)
     48 import Lightning.Protocol.BOLT5.Types
     49 
     50 -- local commitment spends --------------------------------------------
     51 
     52 -- | Spend the to_local output of our local commitment tx.
     53 --
     54 -- Requires waiting for the CSV delay (to_self_delay) before
     55 -- broadcasting. The caller signs with the local delayed privkey
     56 -- and uses 'to_local_witness_spend' from bolt3.
     57 --
     58 -- Returns 'Nothing' if the fee would exceed the output value.
     59 --
     60 -- The input nSequence is set to the to_self_delay value.
     61 spend_to_local
     62   :: OutPoint
     63   -- ^ Outpoint of the to_local output.
     64   -> Satoshi
     65   -- ^ Value of the to_local output.
     66   -> RevocationPubkey
     67   -> ToSelfDelay
     68   -> LocalDelayedPubkey
     69   -> Script
     70   -- ^ Destination scriptPubKey.
     71   -> FeeratePerKw
     72   -> Maybe SpendingTx
     73 spend_to_local !op !value !revpk !delay !delayedpk
     74     !destScript !feerate =
     75   let !witnessScript =
     76         to_local_script revpk delay delayedpk
     77       !weight = to_local_penalty_input_weight
     78               + penalty_tx_base_weight
     79       !fee = spending_fee feerate weight
     80   in if unSatoshi fee >= unSatoshi value
     81      then Nothing
     82      else
     83        let !outputValue =
     84              Satoshi (unSatoshi value - unSatoshi fee)
     85            !tx = mk_spending_tx op
     86                    (fromIntegral (unToSelfDelay delay))
     87                    destScript outputValue 0
     88        in Just (SpendingTx tx witnessScript value
     89                   SIGHASH_ALL)
     90 
     91 -- | Construct an HTLC-timeout second-stage transaction.
     92 --
     93 -- Used when we offered an HTLC on our local commitment and it
     94 -- has timed out. The bolt3 'build_htlc_timeout_tx' function
     95 -- constructs the HTLC-timeout tx; this wraps it as a
     96 -- 'SpendingTx' with the witness script and sighash metadata.
     97 spend_htlc_timeout
     98   :: HTLCContext
     99   -> CommitmentKeys
    100   -- ^ Full commitment keys (needed for witness script).
    101   -> SpendingTx
    102 spend_htlc_timeout !ctx !keys =
    103   let !htlcTx = build_htlc_timeout_tx ctx
    104       !htlc = hc_htlc ctx
    105       !features = hc_features ctx
    106       !witnessScript = offered_htlc_script
    107         (ck_revocation_pubkey keys)
    108         (ck_remote_htlc keys)
    109         (ck_local_htlc keys)
    110         (htlc_payment_hash htlc)
    111         features
    112       !inputValue =
    113         msatToSat (htlc_amount_msat htlc)
    114       !sighashType = if has_anchors features
    115         then SIGHASH_SINGLE_ANYONECANPAY
    116         else SIGHASH_ALL
    117       !tx = htlc_tx_to_tx htlcTx
    118   in SpendingTx tx witnessScript inputValue sighashType
    119 
    120 -- | Construct an HTLC-success second-stage transaction.
    121 --
    122 -- Used when we received an HTLC on our local commitment and
    123 -- have the preimage. The bolt3 'build_htlc_success_tx' function
    124 -- constructs the HTLC-success tx; this wraps it as a
    125 -- 'SpendingTx'.
    126 spend_htlc_success
    127   :: HTLCContext
    128   -> CommitmentKeys
    129   -- ^ Full commitment keys (needed for witness script).
    130   -> SpendingTx
    131 spend_htlc_success !ctx !keys =
    132   let !htlcTx = build_htlc_success_tx ctx
    133       !htlc = hc_htlc ctx
    134       !features = hc_features ctx
    135       !witnessScript = received_htlc_script
    136         (ck_revocation_pubkey keys)
    137         (ck_remote_htlc keys)
    138         (ck_local_htlc keys)
    139         (htlc_payment_hash htlc)
    140         (htlc_cltv_expiry htlc)
    141         features
    142       !inputValue =
    143         msatToSat (htlc_amount_msat htlc)
    144       !sighashType = if has_anchors features
    145         then SIGHASH_SINGLE_ANYONECANPAY
    146         else SIGHASH_ALL
    147       !tx = htlc_tx_to_tx htlcTx
    148   in SpendingTx tx witnessScript inputValue sighashType
    149 
    150 -- | Spend a second-stage HTLC output (HTLC-timeout or
    151 --   HTLC-success output) after the CSV delay.
    152 --
    153 -- The output of an HTLC-timeout or HTLC-success tx uses the
    154 -- same to_local script. The caller signs with the local
    155 -- delayed privkey and uses 'htlc_output_witness_spend'.
    156 --
    157 -- Returns 'Nothing' if the fee would exceed the output value.
    158 spend_htlc_output
    159   :: OutPoint
    160   -- ^ Outpoint of the second-stage output.
    161   -> Satoshi
    162   -- ^ Value of the second-stage output.
    163   -> RevocationPubkey
    164   -> ToSelfDelay
    165   -> LocalDelayedPubkey
    166   -> Script
    167   -- ^ Destination scriptPubKey.
    168   -> FeeratePerKw
    169   -> Maybe SpendingTx
    170 spend_htlc_output = spend_to_local
    171 
    172 -- remote commitment spends -------------------------------------------
    173 
    174 -- | Spend an offered HTLC directly after timeout on the remote
    175 --   commitment.
    176 --
    177 -- On the remote commitment, their received HTLCs (our offered)
    178 -- have timed out and we can sweep them directly.
    179 --
    180 -- Returns 'Nothing' if the fee would exceed the output value.
    181 spend_remote_htlc_timeout
    182   :: OutPoint
    183   -- ^ Outpoint of the HTLC output.
    184   -> Satoshi
    185   -- ^ Value of the HTLC output.
    186   -> HTLC
    187   -- ^ The HTLC being spent.
    188   -> CommitmentKeys
    189   -- ^ Keys for the remote commitment.
    190   -> ChannelFeatures
    191   -> Script
    192   -- ^ Destination scriptPubKey.
    193   -> FeeratePerKw
    194   -> Maybe SpendingTx
    195 spend_remote_htlc_timeout !op !value !htlc !keys
    196     !features !destScript !feerate =
    197   let !witnessScript = received_htlc_script
    198         (ck_revocation_pubkey keys)
    199         (ck_remote_htlc keys)
    200         (ck_local_htlc keys)
    201         (htlc_payment_hash htlc)
    202         (htlc_cltv_expiry htlc)
    203         features
    204       !weight = accepted_htlc_penalty_input_weight
    205               + penalty_tx_base_weight
    206       !fee = spending_fee feerate weight
    207   in if unSatoshi fee >= unSatoshi value
    208      then Nothing
    209      else
    210        let !outputValue =
    211              Satoshi (unSatoshi value - unSatoshi fee)
    212            !locktime =
    213              unCltvExpiry (htlc_cltv_expiry htlc)
    214            !seqNo =
    215              if has_anchors features then 1 else 0
    216            !tx = mk_spending_tx op seqNo destScript
    217                    outputValue locktime
    218        in Just (SpendingTx tx witnessScript value
    219                   SIGHASH_ALL)
    220 
    221 -- | Spend a received HTLC directly with preimage on the remote
    222 --   commitment.
    223 --
    224 -- On the remote commitment, their offered HTLCs (our received)
    225 -- can be claimed with the payment preimage.
    226 --
    227 -- Returns 'Nothing' if the fee would exceed the output value.
    228 spend_remote_htlc_preimage
    229   :: OutPoint
    230   -- ^ Outpoint of the HTLC output.
    231   -> Satoshi
    232   -- ^ Value of the HTLC output.
    233   -> HTLC
    234   -- ^ The HTLC being spent.
    235   -> CommitmentKeys
    236   -- ^ Keys for the remote commitment.
    237   -> ChannelFeatures
    238   -> Script
    239   -- ^ Destination scriptPubKey.
    240   -> FeeratePerKw
    241   -> Maybe SpendingTx
    242 spend_remote_htlc_preimage !op !value !htlc !keys
    243     !features !destScript !feerate =
    244   let !witnessScript = offered_htlc_script
    245         (ck_revocation_pubkey keys)
    246         (ck_remote_htlc keys)
    247         (ck_local_htlc keys)
    248         (htlc_payment_hash htlc)
    249         features
    250       !weight = offered_htlc_penalty_input_weight
    251               + penalty_tx_base_weight
    252       !fee = spending_fee feerate weight
    253   in if unSatoshi fee >= unSatoshi value
    254      then Nothing
    255      else
    256        let !outputValue =
    257              Satoshi (unSatoshi value - unSatoshi fee)
    258            !seqNo =
    259              if has_anchors features then 1 else 0
    260            !tx = mk_spending_tx op seqNo destScript
    261                    outputValue 0
    262        in Just (SpendingTx tx witnessScript value
    263                   SIGHASH_ALL)
    264 
    265 -- revoked commitment spends ------------------------------------------
    266 
    267 -- | Spend a revoked to_local output using the revocation key.
    268 --
    269 -- The caller signs with the revocation privkey and uses
    270 -- 'to_local_witness_revoke' from bolt3.
    271 --
    272 -- Returns 'Nothing' if the fee would exceed the output value.
    273 spend_revoked_to_local
    274   :: OutPoint
    275   -- ^ Outpoint of the to_local output.
    276   -> Satoshi
    277   -- ^ Value of the to_local output.
    278   -> RevocationPubkey
    279   -> ToSelfDelay
    280   -> LocalDelayedPubkey
    281   -> Script
    282   -- ^ Destination scriptPubKey.
    283   -> FeeratePerKw
    284   -> Maybe SpendingTx
    285 spend_revoked_to_local !op !value !revpk !delay
    286     !delayedpk !destScript !feerate =
    287   let !witnessScript =
    288         to_local_script revpk delay delayedpk
    289       !weight = to_local_penalty_input_weight
    290               + penalty_tx_base_weight
    291       !fee = spending_fee feerate weight
    292   in if unSatoshi fee >= unSatoshi value
    293      then Nothing
    294      else
    295        let !outputValue =
    296              Satoshi (unSatoshi value - unSatoshi fee)
    297            !tx = mk_spending_tx op 0xFFFFFFFF destScript
    298                    outputValue 0
    299        in Just (SpendingTx tx witnessScript value
    300                   SIGHASH_ALL)
    301 
    302 -- | Spend a revoked HTLC output using the revocation key.
    303 --
    304 -- The caller signs with the revocation privkey and uses
    305 -- 'offered_htlc_witness_revoke' or
    306 -- 'received_htlc_witness_revoke' from bolt3, depending on
    307 -- the output type.
    308 --
    309 -- Returns 'Nothing' if the output type is not an HTLC, or
    310 -- if the fee would exceed the output value.
    311 spend_revoked_htlc
    312   :: OutPoint
    313   -- ^ Outpoint of the HTLC output.
    314   -> Satoshi
    315   -- ^ Value of the HTLC output.
    316   -> OutputType
    317   -- ^ Whether offered or received HTLC.
    318   -> RevocationPubkey
    319   -> CommitmentKeys
    320   -> ChannelFeatures
    321   -> PaymentHash
    322   -> Script
    323   -- ^ Destination scriptPubKey.
    324   -> FeeratePerKw
    325   -> Maybe SpendingTx
    326 spend_revoked_htlc !op !value !otype !revpk !keys
    327     !features !ph !destScript !feerate =
    328   case otype of
    329     OutputOfferedHTLC _ ->
    330       let !witnessScript = offered_htlc_script
    331             revpk
    332             (ck_remote_htlc keys)
    333             (ck_local_htlc keys)
    334             ph
    335             features
    336           !weight = offered_htlc_penalty_input_weight
    337                   + penalty_tx_base_weight
    338           !fee = spending_fee feerate weight
    339       in if unSatoshi fee >= unSatoshi value
    340          then Nothing
    341          else
    342            let !outputValue =
    343                  Satoshi
    344                    (unSatoshi value - unSatoshi fee)
    345                !tx = mk_spending_tx op 0xFFFFFFFF
    346                        destScript outputValue 0
    347            in Just (SpendingTx tx witnessScript value
    348                       SIGHASH_ALL)
    349     OutputReceivedHTLC expiry ->
    350       let !witnessScript = received_htlc_script
    351             revpk
    352             (ck_remote_htlc keys)
    353             (ck_local_htlc keys)
    354             ph
    355             expiry
    356             features
    357           !weight = accepted_htlc_penalty_input_weight
    358                   + penalty_tx_base_weight
    359           !fee = spending_fee feerate weight
    360       in if unSatoshi fee >= unSatoshi value
    361          then Nothing
    362          else
    363            let !outputValue =
    364                  Satoshi
    365                    (unSatoshi value - unSatoshi fee)
    366                !tx = mk_spending_tx op 0xFFFFFFFF
    367                        destScript outputValue 0
    368            in Just (SpendingTx tx witnessScript value
    369                       SIGHASH_ALL)
    370     _ -> Nothing
    371 
    372 -- | Spend a revoked second-stage HTLC output (HTLC-timeout or
    373 --   HTLC-success output) using the revocation key.
    374 --
    375 -- The output of a revoked HTLC-timeout/success tx uses the
    376 -- to_local script. The caller signs with the revocation privkey
    377 -- and uses 'htlc_output_witness_revoke'.
    378 --
    379 -- Returns 'Nothing' if the fee would exceed the output value.
    380 spend_revoked_htlc_output
    381   :: OutPoint
    382   -- ^ Outpoint of the second-stage output.
    383   -> Satoshi
    384   -- ^ Value of the second-stage output.
    385   -> RevocationPubkey
    386   -> ToSelfDelay
    387   -> LocalDelayedPubkey
    388   -> Script
    389   -- ^ Destination scriptPubKey.
    390   -> FeeratePerKw
    391   -> Maybe SpendingTx
    392 spend_revoked_htlc_output !op !value !revpk !delay
    393     !delayedpk !destScript !feerate =
    394   let !witnessScript =
    395         to_local_script revpk delay delayedpk
    396       !weight = to_local_penalty_input_weight
    397               + penalty_tx_base_weight
    398       !fee = spending_fee feerate weight
    399   in if unSatoshi fee >= unSatoshi value
    400      then Nothing
    401      else
    402        let !outputValue =
    403              Satoshi (unSatoshi value - unSatoshi fee)
    404            !tx = mk_spending_tx op 0xFFFFFFFF destScript
    405                    outputValue 0
    406        in Just (SpendingTx tx witnessScript value
    407                   SIGHASH_ALL)
    408 
    409 -- | Construct a batched penalty transaction spending multiple
    410 --   revoked outputs.
    411 --
    412 -- Per BOLT #5, up to 483 bidirectional HTLCs plus to_local can
    413 -- be resolved in a single penalty transaction (within the
    414 -- 400,000 weight limit). The caller signs each input with the
    415 -- revocation privkey.
    416 -- | Returns 'Nothing' if the total fee would exceed the
    417 --   total input value.
    418 spend_revoked_batch :: PenaltyContext -> Maybe SpendingTx
    419 spend_revoked_batch !ctx =
    420   let !outs = pc_outputs ctx
    421       !destScript = pc_destination ctx
    422       !feerate = pc_feerate ctx
    423 
    424       -- Calculate total input value and weight
    425       !(totalValue, totalWeight) =
    426         go (Satoshi 0) penalty_tx_base_weight
    427           (NE.toList outs)
    428 
    429       !fee = spending_fee feerate totalWeight
    430   in if unSatoshi fee >= unSatoshi totalValue
    431      then Nothing
    432      else
    433        let !outputValue =
    434              Satoshi
    435                (unSatoshi totalValue - unSatoshi fee)
    436 
    437            -- Build inputs
    438            !txInputs = fmap mkPenaltyInput outs
    439 
    440            -- Single output
    441            !txOutput = TxOut
    442              (unSatoshi outputValue)
    443              (unScript destScript)
    444 
    445            !tx = Tx
    446              { tx_version   = 2
    447              , tx_inputs    = txInputs
    448              , tx_outputs   = txOutput :| []
    449              , tx_witnesses = []
    450              , tx_locktime  = 0
    451              }
    452 
    453            !witnessScript = Script BS.empty
    454        in Just (SpendingTx tx witnessScript totalValue
    455                   SIGHASH_ALL)
    456   where
    457     go !totalVal !totalWt [] = (totalVal, totalWt)
    458     go !totalVal !totalWt (uo:rest) =
    459       let !w = case uo_type uo of
    460             Revoke _ ->
    461               to_local_penalty_input_weight
    462             RevokeHTLC _ (OutputOfferedHTLC _) ->
    463               offered_htlc_penalty_input_weight
    464             RevokeHTLC _ (OutputReceivedHTLC _) ->
    465               accepted_htlc_penalty_input_weight
    466             _ -> 0
    467           !v = Satoshi
    468             (unSatoshi totalVal + unSatoshi (uo_value uo))
    469       in go v (totalWt + w) rest
    470 
    471     mkPenaltyInput !uo =
    472       TxIn
    473         { txin_prevout = uo_outpoint uo
    474         , txin_script_sig = BS.empty
    475         , txin_sequence = 0xFFFFFFFF
    476         }
    477 
    478 -- anchor spends ------------------------------------------------------
    479 
    480 -- | Spend an anchor output as the owner (immediately).
    481 --
    482 -- The caller signs with the funding privkey and uses
    483 -- 'anchor_witness_owner' from bolt3.
    484 spend_anchor_owner
    485   :: OutPoint
    486   -- ^ Outpoint of the anchor output.
    487   -> Satoshi
    488   -- ^ Value of the anchor output (330 sats).
    489   -> FundingPubkey
    490   -> Script
    491   -- ^ Destination scriptPubKey.
    492   -> SpendingTx
    493 spend_anchor_owner !op !value !fundpk !destScript =
    494   let !witnessScript = anchor_script fundpk
    495       !tx = mk_spending_tx op 0xFFFFFFFE destScript
    496               value 0
    497   in SpendingTx tx witnessScript value SIGHASH_ALL
    498 
    499 -- | Spend an anchor output as anyone (after 16 blocks).
    500 --
    501 -- Uses 'anchor_witness_anyone' from bolt3 (empty signature).
    502 spend_anchor_anyone
    503   :: OutPoint
    504   -- ^ Outpoint of the anchor output.
    505   -> Satoshi
    506   -- ^ Value of the anchor output (330 sats).
    507   -> FundingPubkey
    508   -> Script
    509   -- ^ Destination scriptPubKey.
    510   -> SpendingTx
    511 spend_anchor_anyone !op !value !fundpk !destScript =
    512   let !witnessScript = anchor_script fundpk
    513       !tx = mk_spending_tx op 16 destScript value 0
    514   in SpendingTx tx witnessScript value SIGHASH_ALL
    515 
    516 -- internal helpers ---------------------------------------------------
    517 
    518 -- | Build a simple single-input single-output spending tx.
    519 mk_spending_tx
    520   :: OutPoint     -- ^ Input outpoint
    521   -> Word32       -- ^ Input nSequence
    522   -> Script       -- ^ Output scriptPubKey
    523   -> Satoshi      -- ^ Output value
    524   -> Word32       -- ^ Locktime
    525   -> Tx
    526 mk_spending_tx !op !seqNo !destScript !outputValue
    527     !locktime =
    528   let !txIn = TxIn
    529         { txin_prevout = op
    530         , txin_script_sig = BS.empty
    531         , txin_sequence = seqNo
    532         }
    533       !txOut = TxOut
    534         { txout_value = unSatoshi outputValue
    535         , txout_script_pubkey = unScript destScript
    536         }
    537   in Tx
    538        { tx_version   = 2
    539        , tx_inputs    = txIn :| []
    540        , tx_outputs   = txOut :| []
    541        , tx_witnesses = []
    542        , tx_locktime  = locktime
    543        }
    544 
    545 -- | Convert a bolt3 HTLCTx to a ppad-tx Tx.
    546 htlc_tx_to_tx :: HTLCTx -> Tx
    547 htlc_tx_to_tx !htx =
    548   let !txIn = TxIn
    549         { txin_prevout = htx_input_outpoint htx
    550         , txin_script_sig = BS.empty
    551         , txin_sequence =
    552             unSequence (htx_input_sequence htx)
    553         }
    554       !txOut = TxOut
    555         { txout_value =
    556             unSatoshi (htx_output_value htx)
    557         , txout_script_pubkey =
    558             unScript (htx_output_script htx)
    559         }
    560   in Tx
    561        { tx_version = htx_version htx
    562        , tx_inputs = txIn :| []
    563        , tx_outputs = txOut :| []
    564        , tx_witnesses = []
    565        , tx_locktime =
    566            unLocktime (htx_locktime htx)
    567        }