psbt: support output metadata updates before inputs are added #35797

pull l0rinc wants to merge 3 commits into bitcoin:master from l0rinc:l0rinc/psbt-zero-input-output-update changing 3 files +108 −3
  1. l0rinc commented at 4:33 AM on July 25, 2026: contributor

    Problem: PSBTv2 permits outputs to be added before inputs. An authenticated descriptorprocesspsbt request can abort the node while updating metadata for one of those outputs because UpdatePSBTOutput() traverses the output script with a signature creator for input index 0. ECDSA signing or a miniscript timelock check can then access the missing input.

    Fix: Make UpdatePSBTOutput() traverse output scripts with a temporary one-input transaction while continuing to take the output from the PSBT's unsigned transaction. MutableTransactionSignatureCreator continues to require a valid input index. Output metadata traversal still records scripts and key origins, allowing outputs to be updated before inputs are added.

  2. DrahtBot added the label PSBT on Jul 25, 2026
  3. DrahtBot commented at 4:33 AM on July 25, 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/35797.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK jeanpablojp
    Approach ACK vicjuma
    Stale ACK Bicaru20, w0xlt

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35848 (test: Cover IsNull() for PSBT, PSBTInput, PSBTOutput by nebula-21)
    • #35747 (wallet: Fix FillPSBT failing to sign owned inputs when UTXOs disagree by nervana21)
    • #35713 (Remove boost as a unit test runner by rustaceanrob)

    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-->

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • UpdatePSBTOutput(provider, psbt, 0) in src/test/psbt_tests.cpp

    <sup>2026-08-12 21:35:08</sup>

  4. in src/script/sign.h:59 in 0fa3581395
      55 | @@ -59,12 +56,14 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator
      56 |      const MutableTransactionSignatureChecker checker;
      57 |      const PrecomputedTransactionData* m_txdata;
      58 |  
      59 | +    bool HasInput() const { return nIn < m_txto.vin.size(); }
    


    vicjuma commented at 4:05 PM on July 29, 2026:

    This appears to be for ECDSA and/or Schnorr inputs check

  5. in src/script/sign.h:66 in 0fa3581395
      62 |  public:
      63 |      MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, const SignOptions& options);
      64 |      MutableTransactionSignatureCreator(const CMutableTransaction& tx LIFETIMEBOUND, unsigned int input_idx, const CAmount& amount, const PrecomputedTransactionData* txdata, const SignOptions& options);
      65 | -    const BaseSignatureChecker& Checker() const override { return checker; }
      66 | +    /** Returns the transaction checker, or a rejecting checker when input_idx has no corresponding input. */
      67 | +    const BaseSignatureChecker& Checker() const override;
    


    vicjuma commented at 4:12 PM on July 29, 2026:

    This appears to be for the miniscript path checks

  6. in src/script/sign.cpp:53 in 0fa3581395
      49 | @@ -50,10 +50,18 @@ MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMu
      50 |  {
      51 |  }
      52 |  
      53 | +const BaseSignatureChecker& MutableTransactionSignatureCreator::Checker() const
    


    vicjuma commented at 5:58 PM on July 29, 2026:

    Implementation has fixed the miniscript path as indicated in the RPC testing process

    Before change

    ratedg@0xratedg:~/projects/contributions/bitcoin/build/bin$ ./bitcoind
    Bitcoin Core starting
    ratedg@0xratedg:~/projects/contributions/bitcoin/build/bin$ ./bitcoin-cli descriptorprocesspsbt "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABAwhAQg8AAAAAAAEEIgAgVLmFfPxqejfeM2UcPknlg+zd1E47Aj/mtr5ZMMYDfN4A" "[\"wsh(and_v(v:pk([150541d1/44h/1h/0h/0/0]03c5bf15b5baf73ebd5285746ece7c95d502916f189145a7005248bb755e2c3c5e),older(10)))#qlhqad4l\"]"
    error: Error while attempting to communicate with server 127.0.0.1:18443 (EOF)
    
    Make sure the bitcoind server is running and that you are connecting to the correct RPC port.
    Use "bitcoin-cli -help" for more info.
    ratedg@0xratedg:~/projects/contributions/bitcoin/build/bin$  
    

    After change

    ratedg@0xratedg:~/projects/contributions/bitcoin$ cd build/bin/ && ./bitcoin-cli stop && ./bitcoind
    Bitcoin Core stopping
    Bitcoin Core starting
    ratedg@0xratedg:~/projects/contributions/bitcoin/build/bin$ ./bitcoin-cli descriptorprocesspsbt "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABAwhAQg8AAAAAAAEEIgAgVLmFfPxqejfeM2UcPknlg+zd1E47Aj/mtr5ZMMYDfN4A" "[\"wsh(and_v(v:pk([150541d1/44h/1h/0h/0/0]03c5bf15b5baf73ebd5285746ece7c95d502916f189145a7005248bb755e2c3c5e),older(10)))#qlhqad4l\"]"
    {
      "psbt": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABASUhA8W/FbW69z69UoV0bs58ldUCkW8YkUWnAFJIu3VeLDxerVqyIgIDxb8Vtbr3Pr1ShXRuznyV1QKRbxiRRacAUki7dV4sPF4YFQVB0SwAAIABAACAAAAAgAAAAAAAAAAAAQMIQEIPAAAAAAABBCIAIFS5hXz8ano33jNlHD5J5YPs3dROOwI/5ra+WTDGA3zeAA==",
      "complete": true,
      "hex": "02000000000140420f000000000022002054b9857cfc6a7a37de33651c3e49e583ecddd44e3b023fe6b6be5930c6037cde00000000"
    }
    ratedg@0xratedg:~/projects/contributions/bitcoin/build/bin$ 
    
    
  7. in src/script/sign.cpp:63 in 0fa3581395
      58 | +
      59 |  bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
      60 |  {
      61 |      assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0);
      62 |  
      63 | +    if (!HasInput()) return false;
    


    vicjuma commented at 6:03 PM on July 29, 2026:

    Reasonable to me for ECDSA path

  8. in src/script/sign.cpp:90 in 0fa3581395
      86 | @@ -79,6 +87,8 @@ std::optional<uint256> MutableTransactionSignatureCreator::ComputeSchnorrSignatu
      87 |  {
      88 |      assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT);
      89 |  
      90 | +    if (!HasInput()) return std::nullopt;
    


    vicjuma commented at 6:04 PM on July 29, 2026:

    Reasonable to me for Schnorr path

  9. vicjuma commented at 6:33 PM on July 29, 2026: contributor

    Tested the miniscript path and the change seems to fix the initial crush with empty inputs created from the createpsbt RPC

    ./bitcoin-cli createpsbt "[]" "[{\"bcrt1q2juc2l8udfar0h3nv5wruj09s0kdm4zw8vprle4khevnp3sr0n0q6gkweu\":0.01}]"
    
    cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABAwhAQg8AAAAAAAEEIgAgVLmFfPxqejfeM2UcPknlg+zd1E47Aj/mtr5ZMMYDfN4A
    

    N/B: To reproduce the crush, the address used was derived from the miniscript.
    "wsh(and_v(v:pk([150541d1/44h/1h/0h/0/0]03c5bf15b5baf73ebd5285746ece7c95d502916f189145a7005248bb755e2c3c5e),older(10)))#qlhqad4l" [ "bcrt1q2juc2l8udfar0h3nv5wruj09s0kdm4zw8vprle4khevnp3sr0n0q6gkweu" ]

  10. l0rinc commented at 7:12 PM on July 29, 2026: contributor

    Thanks for the excellent before-and-after reproducers @vicjuma. It sounds as though you agree with the approach - if you are comfortable taking responsibility for the review, consider ACK-ing the PR as described in the code review guidelines?

  11. vicjuma commented at 7:14 PM on July 29, 2026: contributor

    Thanks for the excellent before-and-after reproducers @vicjuma. It sounds as though you agree with the approach - if you are comfortable taking responsibility for the review, consider ACK-ing the PR as described in the code review guidelines?

    Approach ACK

  12. Bicaru20 commented at 8:01 PM on August 10, 2026: contributor

    ACK 0fa358139527a998c69139e4e20366be213738d2 I tested the code. I reproduced the bugs for ECDSA signing or a miniscript timelock paths:

    <details> <summary>ECDSA signing: </summary>

    Before the fix in 97e0758ccb44de9a0ca75bd412e26b6f9cf1ee01:

    build/bin/bitcoin-cli -regtest descriptorprocesspsbt "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABAwhAQg8AAAAAAAEEFgAU7ioYOut1QHrhUA+gdH3xtZ0de4oA" "[\"wpkh(tprv8ZgxMBicQKsPcsyWWaGZQB9Q8wSNowQASxz6EjZQj73DQm2xcvt6VAhYifn4L7P2TjF26tiFuKzQCc19oBNqgKvEXRKi4r8ZMc1shSvmiAn/84h/1h/0h/0/*)#hrzqd8as\"]"
    error: Error while attempting to communicate with server 127.0.0.1:18443 (EOF)
    
    Make sure the bitcoind server is running and that you are connecting to the correct RPC port.
    Use "bitcoin-cli -help" for more info.
    

    And after the fix

    build/bin/bitcoin-cli -regtest descriptorprocesspsbt "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABAwhAQg8AAAAAAAEEFgAU7ioYOut1QHrhUA+gdH3xtZ0de4oA" "[\"wpkh(tprv8ZgxMBicQKsPcsyWWaGZQB9Q8wSNowQASxz6EjZQj73DQm2xcvt6VAhYifn4L7P2TjF26tiFuKzQCc19oBNqgKvEXRKi4r8ZMc1shSvmiAn/84h/1h/0h/0/*)#hrzqd8as\"]"
    {
      "psbt": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAAiAgKR3Fqk68sTPwDohGzcmFc6jPwIf5PVxJ2mb+h8ZJxRkRjymJqrVAAAgAEAAIAAAACAAAAAAAAAAAABAwhAQg8AAAAAAAEEFgAU7ioYOut1QHrhUA+gdH3xtZ0de4oA",
      "complete": true,
      "hex": "02000000000140420f0000000000160014ee2a183aeb75407ae1500fa0747df1b59d1d7b8a00000000"
    }
    

    </details>

    <details> <summary>Miniscript timelock: </summary>

    Before the fix in 9f0afec37e458f3715c1aba2aec857156b7101b5:

    build/bin/bitcoin-cli -regtest descriptorprocesspsbt "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABAwhAQg8AAAAAAAEEIgAgmp0/U1lO3UtHpQn8bVMjSE8rdGWgiR2Ab5FzATmUmIMA" "[\"wsh(and_v(v:pk(tprv8ZgxMBicQKsPcsyWWaGZQB9Q8wSNowQASxz6EjZQj73DQm2xcvt6VAhYifn4L7P2TjF26tiFuKzQCc19oBNqgKvEXRKi4r8ZMc1shSvmiAn/84h/1h/0h/0/*),older(144)))#vtx4097p\"]"
    error: Error while attempting to communicate with server 127.0.0.1:18443 (EOF)
    
    Make sure the bitcoind server is running and that you are connecting to the correct RPC port.
    Use "bitcoin-cli -help" for more info.
    
    

    And after the fix

    build/bin/bitcoin-cli -regtest descriptorprocesspsbt "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABAwhAQg8AAAAAAAEEIgAgmp0/U1lO3UtHpQn8bVMjSE8rdGWgiR2Ab5FzATmUmIMA" "[\"wsh(and_v(v:pk(tprv8ZgxMBicQKsPcsyWWaGZQB9Q8wSNowQASxz6EjZQj73DQm2xcvt6VAhYifn4L7P2TjF26tiFuKzQCc19oBNqgKvEXRKi4r8ZMc1shSvmiAn/84h/1h/0h/0/*),older(144)))#vtx4097p\"]"
    {
      "psbt": "cHNidP8BAgQCAAAAAQMEAAAAAAEEAQABBQEBAfsEAgAAAAABASchApHcWqTryxM/AOiEbNyYVzqM/Ah/k9XEnaZv6HxknFGRrQKQALIiAgKR3Fqk68sTPwDohGzcmFc6jPwIf5PVxJ2mb+h8ZJxRkRjymJqrVAAAgAEAAIAAAACAAAAAAAAAAAABAwhAQg8AAAAAAAEEIgAgmp0/U1lO3UtHpQn8bVMjSE8rdGWgiR2Ab5FzATmUmIMA",
      "complete": true,
      "hex": "02000000000140420f00000000002200209a9d3f53594edd4b47a509fc6d5323484f2b7465a0891d806f9173013994988300000000"
    }
    

    </details>

    Finally I took a closer look at the fix in 0fa358139527a998c69139e4e20366be213738d2. From what I understood, before the fix we could create transaction with one input spending from another one that payed to a rawtr(<KEY>) output. The problem came when trying to sing the transaction, since CreateSchnorrSig didn't check if it has an input to sign. Before this there was just this assert to control this case. https://github.com/bitcoin/bitcoin/blob/1d386c250f222ad12b61a530af1ad673b3621ad5/src/script/interpreter.cpp#L1512

  13. DrahtBot requested review from vicjuma on Aug 10, 2026
  14. w0xlt commented at 12:01 AM on August 12, 2026: contributor

    LGTM. ACK 0fa358139527a998c69139e4e20366be213738d2

  15. in src/script/sign.cpp:57 in 97e0758ccb
      53 | @@ -54,6 +54,8 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid
      54 |  {
      55 |      assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0);
      56 |  
      57 | +    if (!HasInput()) return false;
    


    achow101 commented at 5:58 PM on August 12, 2026:

    In 97e0758ccb44de9a0ca75bd412e26b6f9cf1ee01 "sign: reject ECDSA signing without input"

    I don't think this is the right place to fix this bug. This is a problem with how PSBTs call into signing, not a bug in signing itself. I think this should be an Assume, or even an Assert if added at all.


    l0rinc commented at 9:39 PM on August 12, 2026:

    It only uses the PSBT's unsigned tx because it was convenient and (originally) did not require constructing another CMutableTransaction.

    That's very useful context, thanks. Making the calls valid makes the change a lot simpler, rebased, pushed, added you as coauthor. Skipped the asserts, the code will already fail without them.

  16. achow101 commented at 6:02 PM on August 12, 2026: member

    This fixes the bug in the wrong place. This is not an issue with MutableTransactionSignatureCreator, but rather with how UpdatePSBTOutput is calling it.

    The correct fix would be to change UpdatePSBTOutput to make a transaction that MutableTransactionSignatureCreator can always sign, which should be trivially doable. It only uses the PSBT's unsigned tx because it was convenient and (originally) did not require constructing another CMutableTransaction. There's no requirement that it needs to use that transaction.

  17. test: characterize PSBT output metadata
    Document the scripts and key origins collected for common output descriptors before changing zero-input handling.
    Use a valid input for ECDSA-backed outputs because their zero-input paths currently abort.
    e24e8fa2a6
  18. test: characterize P2WSH miniscript output
    Add a relative-timelock case that consults the transaction checker during output metadata traversal.
    Use a valid input because the pre-fix missing-input path is only reliably diagnosed under a sanitizer.
    4f5712476a
  19. psbt: update output metadata without inputs
    PSBTv2 permits outputs to be added before inputs.
    `UpdatePSBTOutput()` traverses output scripts with a signature creator for input index 0, so ECDSA signing or a miniscript relative-timelock check can access a missing input and abort.
    
    Construct a standalone transaction with one input for metadata traversal.
    Continue taking the output from the unsigned PSBT transaction so scripts and key origins remain associated with the correct output while traversal no longer depends on the PSBT input list.
    
    `MutableTransactionSignatureCreator` continues to require callers to provide a valid input index.
    
    Co-authored-by: Ava Chow <github@achow101.com>
    c079288967
  20. l0rinc force-pushed on Aug 12, 2026
  21. jeanpablojp commented at 1:20 AM on August 13, 2026: none

    tACK c0792889673da175661f29138a318614b33636cd

    I reproduced the crash on the code without the fix (the RPC returns EOF and the node dies). On this head both paths, ECDSA and miniscript, return complete.

    And I think this deserves a release note, right? The crash reproduces on v31.1 with a plain v0 PSBT, so it affects the current release: doc/release-notes-35797.md.

  22. DrahtBot requested review from w0xlt on Aug 13, 2026
  23. achow101 commented at 1:49 AM on August 13, 2026: member

    And I think this deserves a release note, right?

    Bug fixes generally don't get release notes. This is pretty minor too.


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-14 17:51 UTC

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