Description
The tx-level PsbtParams::sighash_type field is dead weight: its default of None covers the SIGHASH_ALL case (the 99% common one), and the rare callers who'd want to override usually want per-input control (atomic swaps, coordinated txs, ANYONECANPAY workflows). It also has an awkward asymmetry: it only applies to Plan-variant inputs, while PsbtInput-variant inputs already carry their own sighash in psbt::Input::sighash_type.
Just removing the field is most of the fix. The trickier question is what create_psbt should do for Plan-variant inputs by default, because Taproot signature size depends on sighash (64 bytes for SIGHASH_DEFAULT, 65 for anything else). Miniscript's Plan bakes this assumption in via TaprootCanSign::sighash_default, and Plan::satisfaction_weight() is only accurate if the signer actually uses the assumed sighash. If create_psbt leaves sighash entirely up to the caller, the catastrophic case is: Plan was built with sighash_default = true (64-byte sigs assumed), signer produces a 65-byte signature, transaction is silently under-funded.
Proposed Changes
-
Remove PsbtParams::sighash_type.
-
In create_psbt, auto-write TapSighashType::Default only when the Plan's Schnorr placeholders are uniformly 64 bytes (all-Default-Taproot). This is the one case where the Plan's weight assumption forces a specific sighash, and where a misbehaving (or BIP-defaulted) signer producing 65-byte sigs would silently under-fund the tx. Every other case is left unset:
| Plan's Taproot placeholder sizes |
Behavior |
| All 64B (uniform Default-Taproot) |
Auto-write TapSighashType::Default. |
| All 65B (uniform non-Default-Taproot) |
Leave unset. Signer defaults to SIGHASH_ALL (65B — matches budget). |
| Mixed 64B + 65B |
Leave unset. Plan weight is accurate only if signers coordinate externally to use the per-key declared sighashes; PSBT cannot represent per-key sighash intent. Documented caveat, not an error — the Plan is consensus- and miniscript-valid. |
| No Schnorr placeholders (ECDSA) |
Leave unset. Sighash-invariant for weight. |
Sizes are read from Plan::witness_template() (the trailing usize on each SchnorrSigPk/SchnorrSigPkHash placeholder).
-
Callers needing non-ALL semantics (SINGLE, NONE, *_ANYONECANPAY) set psbt.inputs[i].sighash_type directly after create_psbt returns. psbt::Input.sighash_type becomes the single source of truth for per-input sighash on the constructed PSBT — uniform across Plan-derived and PSBT-derived inputs.
The auto-write of Default for uniform-64B Plans guards the catastrophic direction (64B-budget, 65B-signature) by default. Overriding it post-construction is explicit caller intent — to be documented on create_psbt as the one remaining footgun (mismatched byte length silently mis-funds the tx).
Design discussion (originally on the closed #69): #69 (comment).
Description
The tx-level
PsbtParams::sighash_typefield is dead weight: its default ofNonecovers theSIGHASH_ALLcase (the 99% common one), and the rare callers who'd want to override usually want per-input control (atomic swaps, coordinated txs, ANYONECANPAY workflows). It also has an awkward asymmetry: it only applies to Plan-variant inputs, whilePsbtInput-variant inputs already carry their own sighash inpsbt::Input::sighash_type.Just removing the field is most of the fix. The trickier question is what
create_psbtshould do for Plan-variant inputs by default, because Taproot signature size depends on sighash (64 bytes forSIGHASH_DEFAULT, 65 for anything else). Miniscript'sPlanbakes this assumption in viaTaprootCanSign::sighash_default, andPlan::satisfaction_weight()is only accurate if the signer actually uses the assumed sighash. Ifcreate_psbtleaves sighash entirely up to the caller, the catastrophic case is: Plan was built withsighash_default = true(64-byte sigs assumed), signer produces a 65-byte signature, transaction is silently under-funded.Proposed Changes
Remove
PsbtParams::sighash_type.In
create_psbt, auto-writeTapSighashType::Defaultonly when the Plan's Schnorr placeholders are uniformly 64 bytes (all-Default-Taproot). This is the one case where the Plan's weight assumption forces a specific sighash, and where a misbehaving (or BIP-defaulted) signer producing 65-byte sigs would silently under-fund the tx. Every other case is left unset:TapSighashType::Default.SIGHASH_ALL(65B — matches budget).Sizes are read from
Plan::witness_template()(the trailingusizeon eachSchnorrSigPk/SchnorrSigPkHashplaceholder).Callers needing non-
ALLsemantics (SINGLE,NONE,*_ANYONECANPAY) setpsbt.inputs[i].sighash_typedirectly aftercreate_psbtreturns.psbt::Input.sighash_typebecomes the single source of truth for per-input sighash on the constructed PSBT — uniform across Plan-derived and PSBT-derived inputs.The auto-write of
Defaultfor uniform-64B Plans guards the catastrophic direction (64B-budget, 65B-signature) by default. Overriding it post-construction is explicit caller intent — to be documented oncreate_psbtas the one remaining footgun (mismatched byte length silently mis-funds the tx).Design discussion (originally on the closed #69): #69 (comment).