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 }