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
Unit test for the four checks in InferTaprootTree() (none existed).
Extract the checks into CheckTapLeafCandidate(), shared by
InferTaprootTree() and decodepsbt.
decodepsbt's new control_block_warnings field, with its
functional test.
Made with my usual tools: a computer, the Internet and an LLM. The mistakes, as usual, are all mine.
DrahtBot added the label RPC/REST/ZMQ on Sep 29, 2026
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.
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:
#36577 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36577.svg"></sub> (psbt: accept taproot ALL sigs with sighash unset by fametrano)
#36224 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36224.svg"></sub> (test: Add test coverage for PartiallySignedTransaction::Merge() by nebula-21)
#36122 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36122.svg"></sub> (BIP460: CISA for Taproot key path spends by fjahr)
#35747 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/35747.svg"></sub> (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-->
fametrano force-pushed on Sep 29, 2026
DrahtBot added the label CI failed on Sep 29, 2026
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>
fametrano force-pushed on Sep 29, 2026
fametrano force-pushed on Sep 29, 2026
DrahtBot removed the label CI failed on Sep 29, 2026
fametrano force-pushed on Sep 29, 2026
fametrano force-pushed on Sep 30, 2026
fametrano force-pushed on Oct 2, 2026
DrahtBot added the label CI failed on Oct 2, 2026
fametrano force-pushed on Oct 6, 2026
DrahtBot removed the label CI failed on Oct 6, 2026
fametrano force-pushed on Oct 7, 2026
fametrano force-pushed on Oct 10, 2026
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.
bd3c7c7034
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.
4b26227ed0
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.
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-30 14:51 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me