Skip to content

PsbtParams::sighash_type does not make sense #60

@evanlinjin

Description

@evanlinjin

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

  1. Remove PsbtParams::sighash_type.

  2. 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).

  3. 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).

Metadata

Metadata

Assignees

Labels

apiChanges the public API

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions