wallet: Fix FillPSBT failing to sign owned inputs when UTXOs disagree #35747

pull nervana21 wants to merge 2 commits into bitcoin:master from nervana21:fix-fillpsbt changing 4 files +93 −7
  1. nervana21 commented at 4:45 PM on July 19, 2026: contributor

    tl;dr

    Previously, the wallet could fail to sign an intended spend when an input's non_witness_utxo and witness_utxo disagreed. This PR fixes FillPSBT by routing its UTXO-selection logic through GetUTXO.

    Problem

    A PSBT input can carry both a non_witness_utxo and a witness_utxo. GetUTXO prefers a verified non_witness_utxo and does not fall back to witness_utxo when the non_witness_utxo is present but unusable.

    Previously, FillPSBT selected the SigningProvider from the witness_utxo whenever it was set. When the two disagreed, FillPSBT loaded keys for the witness_utxo script while SignPSBTInput signed the non_witness_utxo spend. This could lead to an incomplete input even though the wallet owns the appropriate non_witness_utxo script.

    Fix

    Route FillPSBT through GetUTXO so it shares SignPSBTInput's prefer/reject rule.

    Behavior change

    FillPSBT now uses GetUTXO for script selection, so an input whose non_witness_utxo is present but unusable (out-of-range or txid-mismatched) returns MISSING_INPUTS instead of first selecting a script from witness_utxo.

    SignPSBTInput rejects the same input with MISSING_INPUTS, and FillPSBT already propagates any non-INCOMPLETE error for the remaining ScriptPubKeyMans and inputs. The change is that the failure is detected at script selection rather than after a mistaken key lookup.

    Well-formed PSBTs are unaffected, since witness_utxo and non_witness_utxo resolve to the same script. Txid handling is also tightened in the selection path. A txid-mismatched non_witness_utxo is now rejected by GetUTXO rather than used for script selection when no witness_utxo is present.

    Testing

    fillpsbt_signs_despite_conflicting_witness_utxo covers the regression. psbt2_getutxo pins the GetUTXO prefer/reject rules the fix relies on.

    Follow-up

    SignPSBTInput, PSBTInputSignedAndVerified, and decodepsbt fee totaling still reimplement GetUTXO's logic and risk the same divergence. Refactoring them is left for a follow-up.

  2. DrahtBot added the label Wallet on Jul 19, 2026
  3. DrahtBot commented at 4:45 PM on July 19, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/35747.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35848 (test: Cover IsNull() for PSBT, PSBTInput, PSBTOutput by nebula-21)
    • #35797 (psbt: support output metadata updates before inputs are added by l0rinc)
    • #35569 (Encapsulation for CTransaction by purpleKarrot)
    • #35069 (Refactor keypath parser by pythcoiner)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. DrahtBot added the label CI failed on Jul 19, 2026
  5. DrahtBot commented at 5:45 PM on July 19, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/29695490423/job/88215409202</sub> <sub>LLM reason (✨ experimental): CI failed because clang-tidy reported a bugprone-argument-comment error (commented argument name mismatch in wallet/test/psbt_wallet_tests.cpp), treated as warnings-as-errors.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  6. nervana21 force-pushed on Jul 20, 2026
  7. DrahtBot removed the label CI failed on Jul 20, 2026
  8. in src/wallet/scriptpubkeyman.cpp:1376 in 0a13744b73 outdated
    1368 | @@ -1369,18 +1369,15 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
    1369 |          }
    1370 |  
    1371 |          // Get the scriptPubKey to know which SigningProvider to use
    1372 | -        CScript script;
    1373 | -        if (!input.witness_utxo.IsNull()) {
    1374 | -            script = input.witness_utxo.scriptPubKey;
    1375 | -        } else if (input.non_witness_utxo) {
    1376 | -            if (input.prev_out >= input.non_witness_utxo->vout.size()) {
    


    vicjuma commented at 10:15 AM on August 3, 2026:

    Why do I feel as if these checks are enough? Is there any motivation for someone intending to disagree non_witness_utxo and witness_utxo? Any risk involved? I have explored some possible paths and saw that at no particular point can this be internal.


    nervana21 commented at 3:28 PM on August 3, 2026:

    Hi vicjuma, thanks for your review!

    Why do I feel as if these checks are enough?

    For SignPSBTInput they are but FillPSBT wasn't using that same rule for key selection though, which is the gap this PR closes.

    Is there any motivation for someone intending to disagree non_witness_utxo and witness_utxo?

    IIUC, a malicious or otherwise buggy PSBT coordinator could add a faulty witness_utxo to an honest peer's already-existing non_witness_utxo. That would cause FillPSBT to choose the faulty witness_utxo for key selection instead of the desired non_witness_utxo, so signing could fail even though we owned the right input.

    Any risk involved?

    As I see it, the risk with the old FillPSBT behavior is just that signing fails or stays incomplete. We'd never sign the wrong utxo.

    I have explored some possible paths and saw that at no particular point can this be internal.

    That's my understanding as well. I think this only corrects the external/mutated PSBT case.

  9. DrahtBot added the label Needs rebase on Aug 15, 2026
  10. test, doc: Pin PSBTInput::GetUTXO prefer rules
    Document and test prefer-non_witness, reject OOB/txid mismatch, and no
    witness fallthrough when fields disagree, before migrating callers.
    775de6e4ad
  11. wallet: Select FillPSBT SigningProvider via GetUTXO
    Match SignPSBTInput by preferring a verified non_witness_utxo and
    rejecting a bad non_witness_utxo instead of falling back to a
    witness_utxo.
    
    Add a regression test.
    2c361ed4fa
  12. nervana21 force-pushed on Aug 15, 2026
  13. DrahtBot removed the label Needs rebase on Aug 15, 2026
  14. DrahtBot added the label Needs rebase on Aug 18, 2026
  15. DrahtBot commented at 12:12 PM on August 18, 2026: contributor

    <!--cf906140f33d8803c4a75a2196329ecb-->

    🐙 This pull request conflicts with the target branch and needs rebase.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-08-21 17:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me