Finalizer::finalize currently strips bip32_derivation, tap_key_origins, and tap_internal_key from every output when the PSBT is fully finalized (finalizer.rs:139-146). Two issues:
-
Not spec-compliant by default. BIP-174's finalizer role is defined over inputs only; output PSBT_OUT_* fields are normally retained until extraction. Bitcoin Core never touches outputs on finalize. Doing it silently diverges from both, and destroys metadata a downstream consumer may legitimately need (verifying change ownership, re-deriving addresses, registering change on a watch-only).
-
Incomplete for policy privacy. The current set removes wallet-linkage (key origins) but leaves redeem_script / witness_script / tap_tree, which expose an output's spending policy (multisig structure, full taproot tree). tap_internal_key sits between the two categories — it's a raw key, not a KeySource — so the split below assigns it to the key-origin function deliberately rather than leaving it ambiguous.
Proposal
- Make
finalize pure BIP-174 (inputs only; outputs untouched). Redaction becomes explicit opt-in.
- Add two standalone, composable functions (no
Finalizer/plan dependency):
redact_output_key_origins → clears bip32_derivation, tap_key_origins, tap_internal_key (unlink output from wallet/xpub)
redact_output_scripts → clears redeem_script, witness_script, tap_tree (hide spending policy)
- Leave
proprietary / unknown untouched (opaque, often load-bearing; mirrors input handling at finalizer.rs:107-109).
Reasoning
- Two threat models, two field sets — callers pick what they need; calling both gives the full strip.
- Separate functions over a
finalize parameter: redaction doesn't depend on the finalizer's plans, so coupling it to finalize is artificial. Distinct names make each function's leak-surface obvious.
- Opt-in default keeps us spec-compliant and Core-aligned; privacy is an explicit, deliberate step.
Acceptance criteria
Finalizer::finalizecurrently stripsbip32_derivation,tap_key_origins, andtap_internal_keyfrom every output when the PSBT is fully finalized (finalizer.rs:139-146). Two issues:Not spec-compliant by default. BIP-174's finalizer role is defined over inputs only; output
PSBT_OUT_*fields are normally retained until extraction. Bitcoin Core never touches outputs on finalize. Doing it silently diverges from both, and destroys metadata a downstream consumer may legitimately need (verifying change ownership, re-deriving addresses, registering change on a watch-only).Incomplete for policy privacy. The current set removes wallet-linkage (key origins) but leaves
redeem_script/witness_script/tap_tree, which expose an output's spending policy (multisig structure, full taproot tree).tap_internal_keysits between the two categories — it's a raw key, not aKeySource— so the split below assigns it to the key-origin function deliberately rather than leaving it ambiguous.Proposal
finalizepure BIP-174 (inputs only; outputs untouched). Redaction becomes explicit opt-in.Finalizer/plan dependency):redact_output_key_origins→ clearsbip32_derivation,tap_key_origins,tap_internal_key(unlink output from wallet/xpub)redact_output_scripts→ clearsredeem_script,witness_script,tap_tree(hide spending policy)proprietary/unknownuntouched (opaque, often load-bearing; mirrors input handling atfinalizer.rs:107-109).Reasoning
finalizeparameter: redaction doesn't depend on the finalizer's plans, so coupling it tofinalizeis artificial. Distinct names make each function's leak-surface obvious.Acceptance criteria
finalizeno longer modifies outputs.redact_output_key_originsandredact_output_scriptsadded.finalizer.rs:20-22updated; the "cleared after finalize" test (finalizer.rs:300-304) moved to exercise the new functions; preserve-by-default tests retained.