Skip to content

dlc: build a valid script signature for P2SH-wrapped funding inputs - #195

Merged
bennyhodl merged 2 commits into
masterfrom
fix/p2sh-wrapped-funding-inputs
Sep 14, 2026
Merged

bennyhodl merged 2 commits into
masterfrom
fix/p2sh-wrapped-funding-inputs

Conversation

@bennyhodl

Copy link
Copy Markdown
Owner

Summary

Fixes the Bitcoin Red Team finding FND-004, "P2SH-P2WPKH funding inputs produce an invalid funding transaction in the stateless contract API", with a corrected root cause. The report blamed the witness-only copy in apply_funding_signatures; the final transaction is in fact rebuilt from create_dlc_transactions, which sets the script signature. The real defect sits one step earlier: redeem_script_to_script_sig wrapped the redeem script in a fresh witness program, so a 22-byte P2SH-P2WPKH redeem script failed the V0 length check and panicked inside WitnessProgram::new. Any funding input with a redeem script, including one sent by a counterparty, crashed the accepting side during fee weighting, and no P2SH input could ever fund a contract.

Changes

  • redeem_script_to_script_sig now returns a single push of the redeem script, which is what spending a P2SH output requires, and returns an error instead of panicking on an oversized script. get_unsigned_tx_inputs_and_serial_ids propagates that error.
  • The manager clears script signatures before building its funding PSBT, as Psbt::from_unsigned_tx demands, and restores the redeem script push on every wrapped input before extraction, so the manager path can fund from a P2SH-P2WPKH input too. Before this the manager returned "Tried to create PSBT from signed tx" for any such input.
  • The stateless path needs no change beyond the helper: build_funding_psbt already strips and the finalizer already restores the push, and the final transaction is rebuilt with the correct script signature.

Testing

  • New ddk-dlc unit tests: an empty redeem script yields an empty script signature; 22-byte P2SH-P2WPKH and 34-byte P2SH-P2WSH redeem scripts are pushed whole.
  • New stateless test p2sh_p2wpkh_funding_inputs_carry_the_redeem_script_push: a full offer, accept, sign, and finalize cycle where the offerer funds from a wrapped output. The funding transaction's wrapped input carries the redeem script push and a two-element witness, native inputs stay empty. The harness's funding transaction check now understands wrapped inputs.
  • cargo test -p ddk-dlc (22), the stateless suite (33), and the regtest enum_single_oracle_test pass. cargo check --all-features --tests and cargo +1.89.0 clippy -- -D warnings are clean.

redeem_script_to_script_sig wrapped the redeem script in a fresh
witness program, so a 22-byte P2SH-P2WPKH redeem script hit the V0
length check and panicked inside WitnessProgram::new. Any funding input
with a redeem script, including one sent by a counterparty, crashed the
accepting side during fee weighting, and no P2SH input could ever fund
a contract.

The script signature is now a single push of the redeem script, as P2SH
requires, and the helper returns an error instead of panicking on an
oversized script. The manager clears script signatures before building
its funding PSBT, which Psbt::from_unsigned_tx demands, and restores the
redeem script push on extraction so the wrapped input remains spendable.
Two stateless tests build each party from a bdk_wallet Bip49 template,
so every funding input, payout, and change script is P2SH-P2WPKH. One
test hands the funding PSBT to each wallet and lets BDK sign and
finalize its own input from the witness UTXO alone; the other signs
through the sh(wpkh()) descriptor path, which was documented as
supported but had no test.

PartySetup gains a payout_spk so a party can pay out to a script other
than its native P2WPKH key. The BIP49 party spends a non-null dummy
outpoint because BDK rejects a coinbase as an unconfirmed transaction.
@bennyhodl
bennyhodl merged commit fc32490 into master Sep 14, 2026
136 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant