Introduce per-input sighash type via Input::set_sighash_type#69
Introduce per-input sighash type via Input::set_sighash_type#69evanlinjin wants to merge 1 commit into
Input::set_sighash_type#69Conversation
556f0e1 to
9ffb42c
Compare
noahjoeris
left a comment
There was a problem hiding this comment.
Nice one!
ACK 9ffb42c
Replace `PsbtParams::sighash_type` with a per-input setter/getter on `Input`. `Selection::create_psbt` propagates each input's sighash type into the resulting PSBT input. `Input::set_sighash_type` accepts anything convertible into `PsbtSighashType`, including the standard `EcdsaSighashType` and `TapSighashType` from rust-bitcoin. BREAKING CHANGE: `PsbtParams::sighash_type` is removed. Callers should call `Input::set_sighash_type` on each input before selection.
9ffb42c to
95d4b35
Compare
This comment was marked as outdated.
This comment was marked as outdated.
|
Updated design for this PR. After working through it, the per-Input sighash API turns out to be the source of the problem rather than a feature. Removing it and letting The problemFor Taproot, signature size depends on sighash: 64 bytes for
Why sighash belongs at the PSBT layer, not on
|
tap_sighash_default(plan) |
Behavior in create_psbt |
|---|---|
Some(true) (Default-Taproot Plan) |
Auto-write TapSighashType::Default. This is the one case where the Plan's 64-byte weight assumption locks the sighash to exactly one value; any other choice silently under-funds the transaction. |
Some(false) (non-Default-Taproot Plan) |
Leave the field unset. Signer defaults to SIGHASH_ALL (65 bytes — matches the Plan's weight assumption). Callers needing non-ALL semantics (SINGLE, NONE, *_ANYONECANPAY) set psbt.inputs[i].sighash_type post-construction. |
None (ECDSA — sighash-invariant for weight) |
Leave the field unset. |
PSBT-based Inputs (built via Input::from_psbt_input) are unaffected — their embedded psbt::Input already owns its sighash_type field. The auto-write applies only to Plan-based inputs.
3. Doc the one remaining footgun on create_psbt: mutating psbt.inputs[i].sighash_type after construction on a Taproot input is allowed but the caller is responsible for keeping the byte length consistent with the Plan's witness-size assumption (64B for Default, 65B for any other Taproot sighash). Mismatch silently mis-funds the transaction. The auto-write of Default for Default-Taproot Plans guards the catastrophic direction (64B-budget, 65B-signature) by default; overriding it is explicit caller intent.
Net effect
Inputis purely a spend description, cannot carry inconsistent sighash state.- Single source of truth for per-input sighash on the constructed PSBT:
psbt.inputs[i].sighash_type. No parallel override API. - Default-Taproot Plans get safe auto-lock with zero caller ceremony — prevents the catastrophic 64B→65B underpayment.
- Non-Default-Taproot Plans match BIP-174's implicit default (
SIGHASH_ALL, 65 bytes) and stay weight-accurate; callers needing non-ALLsemantics make a one-line post-construction edit. - ECDSA Plans are unconstrained, matching reality.
nymius
left a comment
There was a problem hiding this comment.
Is this PR complete? I agree with the code here, but I wouldn't relay on a witness guessing scheme for extracting the SIGHASH type in the Plan variant.
It is not complete, waiting on upstream changes. Where did you think we were guessing the sighash type? Sometimes plan is based on Sighash DEFAULT - if the resultant tx uses anything else, the feerate/fee will be different to what CS assumed. The idea is to error on inconsistency. |
|
I was thinking in the |
I realized that we can actually use |
|
Closing. Rationale: #69 (comment) |
Description
Closes #60
Replace
PsbtParams::sighash_typewith a per-input setter/getter onInput.Selection::create_psbtpropagates each input's sighash type into the resulting PSBT input.Changelog notice
Before submitting