Repository navigation
dualopend: verify the peer's input witnesses before the funding tx is broadcast - #51
Merged
Merged
Conversation
kwsantiago
force-pushed
the
dualopen-verify-peer-witnesses
branch
from
October 19, 2026 00:33
2fca0df to
bc07616
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
dualopend now checks the witness the peer sends for each of its funding inputs as its
tx_signaturesarrive, as #45 does for splices, and fails the channel if one is invalid. That covers the opener and the accepter, reconnection and RBF, which all receive them inhandle_tx_sigs.Until now nothing checked them: dualopend applied the peer's witnesses to the PSBT, and lightningd broadcast the funding transaction once both sides had signed. A peer that sends a bad witness for its own input, while we still have to sign, gets our signatures for a funding transaction bitcoind rejects (
mempool-script-verify-flag-failed), and may hold a valid copy with the same txid to broadcast whenever it likes, or never. BOLT 2 also says the receiver oftx_signaturesMUST fail the negotiation if the witnesses are non-standard, or a signature uses a hash type other than 0x01 (or 0x21 underoption_unified_sigs), and neither was enforced on opens.Which inputs are checked depends on whether we have sent our own
tx_signatures:to_remote, as in channeld: verify the peer's input witnesses before signing a splice #45. Anything else fails the channel before we sign. An opener usingfundchannelormultifundchannelis always in this case, since those wait for the accepter'stx_signaturesbefore signing; a peer that withholds its signatures just stalls the open.openchannel_signedsends ours as soon as it is called, so a peer that waits for them gets the check below instead. CLN's accepter always signs first, so it is never in this case.Changes:
common/psbt_internal.c:psbt_tx_with_spent_outputs()builds the transaction with each input's spent output attached,psbt_input_witness_known()says whether an input is one the verifier can judge, andpsbt_their_witnesses_invalid()checks a side's inputs, skipping unknown ones unless strict. channeld calls it strict, so splices behave as before.tx_signaturesrepeated after it has the peer's witnesses (lightningd keeps that flag with the PSBT holding them), and fails the negotiation when the peer sends more witnesses than it added inputs.doc/blake2b-upgrade.mdno longer says no peer verifies funding input signatures.Tests, with
EXPERIMENTAL_DUAL_FUND=1:test_v2_open_bad_input_witness_from_accepter: the accepter (a test plugin,df_bad_witness.py) puts in an input and corrupts its signature. It signs first, so the opener fails the channel without sending its owntx_signatures,openchannel_signedthen fails, and the funding transaction is never broadcast.test_v2_open_bad_input_witness_from_opener: the opener corrupts its signature in the PSBT it passes toopenchannel_signed. The accepter has signed already, so it fails the channel without broadcasting. The opener then broadcasts the valid copy, and the accepter recovers its funds through its commitment (ONCHAIN).test_v2_open_unrecognized_input_script: the opener funds from a 1-of-1 multisig P2WSH output in bitcoind's wallet, signed by bitcoind. The accepter has signed already, so it lets the witness through, and the channel opens.test_v2_open_unrecognized_input_before_we_sign: the accepter (a test plugin,df_external_input.py) funds from such an output and has bitcoind sign it. It signs first, so the opener fails the channel withP2WSH input N is not a 2-of-2 spendwithout sending its owntx_signatures.run-psbt_input_witness: which inputs are known (including an annex, a P2TR script path, a P2WSH script that doesn't match its output, a v2 program and P2A), and strict versus non-strict checking by role and ignored index. Reversing the strict test, or treating an annex or other witness programs as unknown, makes it fail.Locally, the splicing, unified, opening and connection suites pass without and with
EXPERIMENTAL_DUAL_FUND(246 and 164 passed), the opening and splicing suites again after the last commit (40 and 28 passed with it),check-units, the source checks and flake8 pass, and the four dual-open tests above pass 12 of 12 runs.Not in this PR:
openchannel_signed,openchannel_updateandopenchannel_bumpcrash lightningd on a version 0 PSBT, whichopenchannel_initconverts. That is a separate fix.Changelog-None