Repository navigation
Carry the PSBT finalization-clearing findings upstream: one PR to Core, one to the BIPs #696
Description
Split off from #446, which found that BIP174, BIP371 and Bitcoin Core
disagree about which fields finalizing a PSBT input clears, and settled
what btclib does. This is the delivery half: the two pull requests, and
what each needs checked before it is proposed.
No btclib code changes here. #446 concluded "btclib stays as it is",
and the position is not only implemented but pinned:
_FINALIZED_KEEPS in btclib/psbt/psbt.py names the ten fields that
survive, and
tests/psbt/psbt_test.py::test_a_finalized_input_keeps_the_utxo_and_the_unknown_fields_only
walks every other field of PsbtIn for both a p2wsh and a p2tr
input, so a field added to the dataclass and left out of both lists fails
the suite. That test spells the list out rather than importing it, on the
stated grounds that a test importing _FINALIZED_KEEPS would agree with a
wrong list as readily as with a right one.
Re-verified today, before proposing anything
#446 pinned Core at c36ffd87 and bips at 01d2c841. Both claims still
hold at the current tips:
- Core
4c045f1a0323(2026-08-12),src/psbt.cpp:361: the
sigdata.completebranch ofPSBTInput::FromSignatureDataclears
partial_sigs,hd_keypaths,redeem_scriptandwitness_script,
and nothing else. Grepping the whole file for aclear()on any
taproot, MuSig2 or preimage field, or a reset of the sighash type,
returns nothing. - bips
c38071c8c45a(2026-08-10),bip-0371.mediawiki: the
description ofPSBT_OUT_TAP_BIP32_DERIVATION = 0x07still ends
"Finalizers should remove this field after
PSBT_IN_FINAL_SCRIPTWITNESSis constructed" — a finalizer clause on
an output field, referring to an input field, with no indication of
which input. Its two neighbours in the same table carry no such clause.
The pull request to Core
Clear the six BIP371 input fields (PSBT_IN_TAP_KEY_SIG through
PSBT_IN_TAP_MERKLE_ROOT, 0x13 to 0x18) in the sigdata.complete
branch, beside the four already there, with a functional test showing a
finalized taproot input carrying no derivation path.
Scope it to those six and no further: each is an explicit per-field
"should" in BIP371, which makes the change reviewable on its own, whereas
the sighash type and the four preimage maps Core also keeps are covered
only by BIP174's general sentence and belong to the BIP discussion below.
What to check before proposing, and to state in the PR as having been
checked: that nothing downstream of FinalizePSBT reads a taproot field,
FinalizeAndExtractPSBT and the wallet's own paths included.
The privacy asymmetry this closes is the one worth leading with: Core
clears hd_keypaths and leaves m_tap_bip32_paths, so a finalized
taproot input goes on publishing the master fingerprint and derivation
path of every key where a finalized ECDSA input publishes none. Core
#30294 asks for this
metadata to be strippable and
#35370 is building an
RPC eraser for it — both worth re-reading for their current state before
citing them, since either may have moved.
The pull request to the BIPs
Two changes, and the first is the one that matters:
- Amend BIP174's sentence so a version 2 psbt survives it. "All
other data except the UTXO and unknown fields ... should be cleared"
was written when the unsigned transaction was a global field. BIP370
puts the transaction's own data in the input map, where
PSBT_IN_PREVIOUS_TXIDandPSBT_IN_OUTPUT_INDEXare required and a
psbt missing either is invalid; BIP370 never mentions the Finalizer, so
nothing amends the sentence for the version it defines. A finalizer
following BIP174 literally on a v2 psbt produces one that cannot be
parsed back._FINALIZED_KEEPSkeeps those five fields precisely
because of this, so btclib is already the worked example. - Drop the finalizer clause from
PSBT_OUT_TAP_BIP32_DERIVATION, or
say which input it means. It reads as a copy-paste from the input
table, and no implementation clears output fields on finalization.
The precedent to follow is
bips#1396, which kept the
input UTXO through finalization to fix a conflict with BIP78: a conflict
between the sentence and a later document, fixed in the sentence. The
other two merged PRs that touched it —
#771 naming the finalized
fields and #2183 adding the
proprietary clause — went nowhere near these points.
Whether the sighash type and the four preimage maps should also survive
is the open question Core's behaviour raises. It belongs in this PR's
discussion rather than being decided here, and btclib clears them today
on the grounds that what they buy after finalization is nothing: the
preimage a hash-locked script needs is in the witness by then.
How this gets filed
Both drafts go on this issue before they go upstream, and every citation
is re-derived against the tip at the moment of submission rather than
against the shas above: a report has already been closed upstream for
citing a blob id as though it were a commit.
Activity
fametrano commentedon Aug 14, 2026
Re-verified 2026-08-14, at today's tips
Both claims still hold, and the two drafts below are ready to send.
11090c8b(2026-08-13),src/psbt.cpp:361PSBTInput::FromSignatureData: thesigdata.completebranch (363-376)clears
partial_sigs,hd_keypaths,redeem_script,witness_scriptand nothing else. The only other
clear()calls in the file are at 182and 183 (a different path) and 773/778/785 (a local
to_drop).60f5b33b(2026-08-13),bip-0371.mediawiki: the six inputfields carry "Finalizers should remove this field after
PSBT_IN_FINAL_SCRIPTWITNESSis constructed" (L51, 61, 71, 81, 91,101), and so does the output field
PSBT_OUT_TAP_BIP32_DERIVATION(L145). Its two neighbours in the output table,
PSBT_OUT_TAP_INTERNAL_KEYandPSBT_OUT_TAP_TREE, do not.bip-0174.mediawiki:523is the sentence, unchanged.issue #30294 (about
walletprocesspsbtemitting outputbip32_derivseven withbip32derivs=false) andPR #35370 ("rpc: add
key-origin modes to PSBT processing RPCs", last touched 2026-07-11). No
open Core PR clears taproot fields at finalization, so this does not
duplicate anything.
#1396 merged 2025-01-14,
#771 merged 2020-06-12,
#2183 merged 2026-05-30.
bitcoin/bipshas issues disabled, so the BIP half can only be apull request. Noting it because it removes the "file an issue first to
ask" option that the Core half still has.
PR 1, to
bitcoin/bitcoinTitle:
psbt: clear BIP371 taproot fields when an input is finalizedBody:
Implementation: in the
sigdata.completebranch ofPSBTInput::FromSignatureData(src/psbt.cpp:363), beside the fourexisting
clear()calls, resetm_tap_key_sig,m_tap_script_sigs,m_tap_scripts,m_tap_bip32_paths,m_tap_internal_keyandm_tap_merkle_root(the last two areXOnlyPubKeyanduint256, soassignment of a default rather than
clear()). Names read fromsrc/psbt.h:306-311.What to check before proposing, and to state in the PR as checked: that
nothing downstream of
FinalizePSBTreads a taproot field —FinalizeAndExtractPSBT, the wallet's signing paths, andPSBTInput::FillSignatureData, which returns early onsigdata.complete(src/psbt.cpp:307) and therefore cannot be theconsumer, but the RPC layer that serializes a finalized psbt back to the
user can be.
Test:
test/functional/rpc_psbt.pyorwallet_taproot.py— finalize ataproot input and assert the decoded psbt input has no
taproot_bip32_derivs,taproot_internal_key,taproot_key_path_sig.PR 2, to
bitcoin/bipsTitle:
bip-0174, bip-0371: fix the finalizer clearing rulesTwo independent commits, so a reviewer can take one and refuse the other.
Commit 1 — BIP174: a v2 psbt must survive finalization. L523's "All
other data except the UTXO and unknown fields ... should be cleared" was
written when the unsigned transaction was a global field. BIP370 moves the
transaction's own data into the input map, where
PSBT_IN_PREVIOUS_TXIDand
PSBT_IN_OUTPUT_INDEXare required and a psbt missing either isinvalid; BIP370 never mentions the Finalizer, so nothing amends the
sentence for the version it defines. A finalizer following BIP174 literally
on a v2 psbt produces one that cannot be parsed back. The edit adds the
version 2 required fields to the exception list, in the shape
#1396 used for the UTXO.
btclib's
_FINALIZED_KEEPSis the worked example and can be cited.Commit 2 — BIP371: drop the finalizer clause from
PSBT_OUT_TAP_BIP32_DERIVATION(L145), or say which input it means. Itrefers to
PSBT_IN_FINAL_SCRIPTWITNESSfrom an output field, names noinput, and no implementation clears output fields on finalization.
Open question to raise in the PR discussion rather than decide in the
diff: whether the sighash type and the four preimage maps should also
survive, since Core keeps them today. btclib clears them, on the grounds
that a hash-locked script's preimage is in the witness by the time the
input is final.
Order: Core first. If it merges, the BIP PR can say "implemented in
Core as of
<sha>", which is the argument the BIP editors respond to.Verified against btclib
d7c4b3d5.fametrano commentedon Sep 7, 2026
2026-09-07: the Core half is withdrawn, measured
The premise of "PR 1, to bitcoin/bitcoin" is wrong.
PSBTInput::Serialize(src/psbt.h:371at master19b0ff2fa0) writes partial signatures, sighash, scripts, keypaths, preimages, the six taproot fields and the MuSig2 fields only when the input has no final scriptSig and no final scriptWitness; the block runs to line 506. A finalized input leaves Core with the UTXO, the final scripts, the PSBTv2 input fields, proprietary and unknown fields, and nothing else. The gate dates fromc6c3d42a7d(2019-02-11).Verified in three ways on a worktree at
19b0ff2fa0:sigdata.completebranch ofFromSignatureData, plus awallet_taproot.pycheck that a finalized taproot input has no taproot field: green;psbt.cpp: also green, so the change is unobservable;walletprocesspsbt(finalize=False)→finalizepsbt(extract=False)→decodepsbt) on a taproot key path input: after finalization the input hasfinal_scriptwitness,previous_txid,previous_vout,sequence,witness_utxoonly.So there is no privacy asymmetry on the wire, Core already drops the sighash type and the preimages too, and Core and btclib keep the same set of fields. No pull request to Core. The three btclib passages that claim the opposite are #1841.
Two smaller corrections to the drafts above: bips#1396 touches only bip-0078, not the BIP174 sentence, which is unchanged since
f515da0(2018-07-05) apart from the proprietary clause added ina4668bb(#2183); and BIP370, not BIP174, is where the v2 rule belongs, since BIP370 already amends every other role for v2 and has no Input Finalizer section."PR 2, to bitcoin/bips" stands, reshaped: a commit adding an Input Finalizer section to BIP370 that keeps the five v2 input fields, citing Core's serializer as the deployed behaviour, and the BIP371 commit dropping the clause from
PSBT_OUT_TAP_BIP32_DERIVATION. Branch under local review now; link to follow.fametrano commentedon Sep 7, 2026
Filed: bitcoin/bips#2277
bitcoin/bips#2277 — branch
fametrano:psbt-finalizer-clearing, two commits over master09e2103:4901287adds an Input Finalizer section to BIP 370 keeping the five PSBTv2 input fields,f5034acdrops the finalizer clause fromPSBT_OUT_TAP_BIP32_DERIVATIONin BIP 371. +6/−1. Local review, two rounds, cleared the content;typos,link-format-chk.shanddiffcheck.shpass.Closing: the Core half is withdrawn (comment above), and nothing in btclib depends on the BIP outcome.
_FINALIZED_KEEPSalready keeps the five fields, andtest_a_finalized_input_keeps_the_utxo_and_the_unknown_fields_onlypins it. The three passages that misstate Core's behaviour are #1841.