rpc: flag invalid taproot leaf control blocks in decodepsbt #36382

pull fametrano wants to merge 3 commits into bitcoin:master from fametrano:taproot-input-leaf-diagnostics changing 6 files +241 −11
  1. fametrano commented at 4:01 PM on September 29, 2026: contributor

    decodepsbt shows each PSBT_IN_TAP_LEAF_SCRIPT record as-is. It does not say whether its control block could be a genuine leaf of the input's taproot tree. InferTaprootTree() already checks this, but silently skips whatever fails. That is correct there: one bad candidate among several script paths is not an error. But it means a PSBT with an unusable leaf shows no sign of it.

    This PR adds a diagnostic only; signing does not change. A new optional control_block_warnings array, next to control_blocks, gives the first check a control block fails. There are four checks:

    • leaf version (must be even);
    • control block size;
    • leaf-version byte (must match leaf_ver);
    • Merkle root (must match PSBT_IN_TAP_MERKLE_ROOT).

    The Merkle-root check needs PSBT_IN_TAP_MERKLE_ROOT, which is optional per BIP371. When it is absent, only the other three checks run, and the help text says so.

    Similar in spirit to #25513, which added the same kind of check for PSBT_OUT_TAP_TREE.

    Commits

    1. Unit test for the four checks in InferTaprootTree() (none existed).
    2. Extract the checks into CheckTapLeafCandidate(), shared by InferTaprootTree() and decodepsbt.
    3. decodepsbt's new control_block_warnings field, with its functional test.

    Written with LLM, a computer and the Internet; the mistakes, as usual, are all mine.

  2. DrahtBot added the label RPC/REST/ZMQ on Sep 29, 2026
  3. DrahtBot commented at 4:01 PM on September 29, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

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

    • #36224 (test: Add test coverage for PartiallySignedTransaction::Merge() by nebula-21)
    • #36122 (BIP460: CISA for Taproot key path spends by fjahr)
    • #35747 (wallet: Fix FillPSBT failing to sign owned inputs when UTXOs disagree by nervana21)

    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. fametrano force-pushed on Sep 29, 2026
  5. DrahtBot added the label CI failed on Sep 29, 2026
  6. DrahtBot commented at 4:34 PM on September 29, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task previous releases: https://github.com/bitcoin/bitcoin/actions/runs/36594607538/job/109496162271</sub> <sub>LLM reason (✨ experimental): CI failed due to a C++ compilation error in src/test/psbt_tests.cpp (lambda uses leaf_ver illegally: “local variable ‘leaf_ver’ may not appear in this context”).</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>

  7. fametrano force-pushed on Sep 29, 2026
  8. fametrano force-pushed on Sep 29, 2026
  9. DrahtBot removed the label CI failed on Sep 29, 2026
  10. fametrano force-pushed on Sep 29, 2026
  11. fametrano force-pushed on Sep 30, 2026
  12. test: cover InferTaprootTree's leaf checks
    InferTaprootTree() rejects a candidate leaf that fails any of four
    structural checks (leaf version, control block size, leaf-version
    byte, Merkle root), but none of them had a test.
    
    Add a two-leaf taproot tree fixture and, for each check, corrupt
    exactly what it looks at and assert the whole input is rejected.
    
    Added before the next commit's refactor, so that one needs no test
    changes.
    1ea8e00a7e
  13. refactor: extract CheckTapLeafCandidate
    InferTaprootTree()'s four inline "Skip script records that ..."
    checks have no way to report which one a candidate failed, which the
    next commit needs for decodepsbt's diagnostic.
    
    Extract them into CheckTapLeafCandidate(), returning a
    TapLeafCandidateError enum (or NONE). InferTaprootTree() now calls it
    and continues on anything but NONE; its behavior is unchanged.
    
    Declared in script/interpreter.h/.cpp, next to the Taproot constants
    and hash helpers it's built from -- rpc/rawtransaction.cpp already
    includes that header, so decodepsbt needs no new dependency.
    
    merkle_root is std::optional<uint256>, not a plain uint256: decodepsbt
    can lack PSBT_IN_TAP_MERKLE_ROOT and still run the other three checks.
    InferTaprootTree() always has one, so this is not a behavior change
    for it.
    9fc886b109
  14. rpc: flag invalid taproot leaf control blocks
    decodepsbt echoes each PSBT_IN_TAP_LEAF_SCRIPT record as-is.
    InferTaprootTree() already checks whether a control block could be a
    genuine leaf, but silently skips whatever fails -- correctly, since
    one bad candidate among several script paths isn't an error. So a
    PSBT with an unusable leaf gives no visible sign of it.
    
    Add an optional "control_block_warnings" array next to
    "control_blocks", naming why a control block fails
    CheckTapLeafCandidate(). A passing control block gets no entry, and
    the array is omitted entirely when every one passes; "control_blocks"
    itself is untouched.
    
    The Merkle-root check needs PSBT_IN_TAP_MERKLE_ROOT (optional per
    BIP371); when absent, only the other three checks run, and the help
    text says so, so an unflagged block is never overstated as verified.
    
    No change to signing: this is decodepsbt-only.
    
    The functional test builds a real two-leaf taproot tree, so that there
    is a real Merkle branch to corrupt. It covers all four checks, one
    passing control block, and an input without PSBT_IN_TAP_MERKLE_ROOT.
    
    The size check uses an oversized control block. PSBT deserialization
    already rejects an undersized or misaligned one before decodepsbt runs,
    so the undersized case is tested at the InferTaprootTree() level.
    6bb4253ccb
  15. fametrano force-pushed on Oct 2, 2026
  16. DrahtBot added the label CI failed on Oct 2, 2026

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-10-04 22:51 UTC

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