Description
SignOptions::allow_all_sighashes is documented as controlling whether the signer will accept non-SIGHASH_ALL sighash types. However, this flag is only checked inside Wallet::sign as a PSBT-wide pre-flight guard. The underlying InputSigner::sign_input never consults it although it's a public trait method. Callers that use InputSigner or TransactionSigner directly bypass the check entirely.
Affected code
SignerWrapper<PrivateKey>::sign_input — the leaf implementation for all BDK software signers.
DescriptorXKey and DescriptorMultiXKey both derive a key and then delegate here, so all three are affected through the same code path.
- All signing contexts (Legacy, Segwitv0, Taproot) are affected.
The ECDSA path calls psbt.sighash_ecdsa() which returns whatever type the PSBT declares and proceeds. The Taproot path calls compute_tap_sighash() which similarly reads the PSBT sighash type without checking allow_all_sighashes.
Steps to reproduce
- Obtain a signer directly (e.g. from wallet internals or a custom
SignersContainer)
- Set SIGHASH_NONE on a PSBT input
- Call
signer.sign_input(&mut psbt, 0, &SignOptions::default(), &secp) (signs successfully despite allow_all_sighashes being false)
Expected behavior
sign_input should return Err(SignerError::NonStandardSighash) when sign_options.allow_all_sighashes is false and the PSBT input declares a sighash type other than SIGHASH_ALL / SIGHASH_DEFAULT.
Proposed fix
Add the sighash guard in SignerWrapper<PrivateKey>::sign_input, after the "already finalized" early return but before the match self.ctx dispatch. This ensures the check covers all signing contexts and all delegating implementations in a single place:
if !sign_options.allow_all_sighashes {
let sighash_type = psbt.inputs[input_index].sighash_type;
if sighash_type.is_some()
&& sighash_type != Some(EcdsaSighashType::All.into())
&& sighash_type != Some(TapSighashType::All.into())
&& sighash_type != Some(TapSighashType::Default.into())
{
return Err(SignerError::NonStandardSighash);
}
}
This mirrors the existing logic in Wallet::sign. Through the Wallet::sign path, behavior is unchanged since the PSBT-wide check still fires first.
A test should be added covering:
- Taproot input with
SIGHASH_NONE and allow_all_sighashes: false returns NonStandardSighash
- The same with
allow_all_sighashes: true proceeds normally.
Description
SignOptions::allow_all_sighashesis documented as controlling whether the signer will accept non-SIGHASH_ALLsighash types. However, this flag is only checked insideWallet::signas a PSBT-wide pre-flight guard. The underlyingInputSigner::sign_inputnever consults it although it's a public trait method. Callers that useInputSignerorTransactionSignerdirectly bypass the check entirely.Affected code
SignerWrapper<PrivateKey>::sign_input— the leaf implementation for all BDK software signers.DescriptorXKeyandDescriptorMultiXKeyboth derive a key and then delegate here, so all three are affected through the same code path.The ECDSA path calls
psbt.sighash_ecdsa()which returns whatever type the PSBT declares and proceeds. The Taproot path callscompute_tap_sighash()which similarly reads the PSBT sighash type without checkingallow_all_sighashes.Steps to reproduce
SignersContainer)signer.sign_input(&mut psbt, 0, &SignOptions::default(), &secp)(signs successfully despiteallow_all_sighashesbeing false)Expected behavior
sign_inputshould returnErr(SignerError::NonStandardSighash)whensign_options.allow_all_sighashesisfalseand the PSBT input declares a sighash type other thanSIGHASH_ALL/SIGHASH_DEFAULT.Proposed fix
Add the sighash guard in
SignerWrapper<PrivateKey>::sign_input, after the "already finalized" early return but before thematch self.ctxdispatch. This ensures the check covers all signing contexts and all delegating implementations in a single place:This mirrors the existing logic in
Wallet::sign. Through theWallet::signpath, behavior is unchanged since the PSBT-wide check still fires first.A test should be added covering:
SIGHASH_NONEandallow_all_sighashes: falsereturnsNonStandardSighashallow_all_sighashes: trueproceeds normally.