[signer] Add an option to explicitly allow using non-ALL sighashes

Instead of blindly using the `sighash_type` set in a psbt input, we
now only sign `SIGHASH_ALL` inputs by default, and require the user to
explicitly opt-in to using other sighashes if they desire to do so.

Fixes #350
This commit is contained in:
Alekos Filini 2021-05-26 10:34:25 +02:00
parent 5633475ce8
commit 881ca8d1e3
No known key found for this signature in database
GPG Key ID: 431401E4A4530061
4 changed files with 79 additions and 3 deletions

View File

@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Wallet
- Added an option that must be explicitly enabled to allow signing using non-`SIGHASH_ALL` sighashes (#350)
## [v0.7.0] - [v0.6.0]
### Policy

View File

@ -63,7 +63,7 @@ mod test {
psbt.inputs.push(psbt_bip.inputs[0].clone());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
..Default::default()
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}
@ -80,7 +80,7 @@ mod test {
psbt.inputs.push(psbt_bip.inputs[1].clone());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
..Default::default()
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}
@ -96,7 +96,7 @@ mod test {
psbt.global.unsigned_tx.input.push(TxIn::default());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
..Default::default()
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}

View File

@ -872,6 +872,17 @@ where
return Err(Error::Signer(signer::SignerError::MissingNonWitnessUtxo));
}
// If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input
// is using `SIGHASH_ALL`
if !sign_options.allow_all_sighashes
&& !psbt
.inputs
.iter()
.all(|i| i.sighash_type.is_none() || i.sighash_type == Some(SigHashType::All))
{
return Err(Error::Signer(signer::SignerError::NonStandardSighash));
}
for signer in self
.signers
.signers()
@ -1514,6 +1525,7 @@ pub(crate) mod test {
use crate::types::KeychainKind;
use super::*;
use crate::signer::{SignOptions, SignerError};
use crate::testutils;
use crate::wallet::AddressIndex::{LastUnused, New, Peek, Reset};
@ -3670,6 +3682,54 @@ pub(crate) mod test {
)
}
#[test]
fn test_sign_nonstandard_sighash() {
let sighash = SigHashType::NonePlusAnyoneCanPay;
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
.set_single_recipient(addr.script_pubkey())
.sighash(sighash)
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let result = wallet.sign(&mut psbt, Default::default());
assert!(
result.is_err(),
"Signing should have failed because the TX uses non-standard sighashes"
);
assert!(
matches!(
result.unwrap_err(),
Error::Signer(SignerError::NonStandardSighash)
),
"Signing failed with the wrong error type"
);
// try again after opting-in
let result = wallet.sign(
&mut psbt,
SignOptions {
allow_all_sighashes: true,
..Default::default()
},
);
assert!(result.is_ok(), "Signing should have worked");
assert!(
result.unwrap(),
"Should finalize the input since we can produce signatures"
);
let extracted = psbt.extract_tx();
assert_eq!(
*extracted.input[0].witness[0].last().unwrap(),
sighash.as_u32() as u8,
"The signature should have been made with the right sighash"
);
}
#[test]
fn test_unused_address() {
let db = MemoryDatabase::new();

View File

@ -147,6 +147,12 @@ pub enum SignerError {
MissingWitnessScript,
/// The fingerprint and derivation path are missing from the psbt input
MissingHdKeypath,
/// The psbt contains a non-`SIGHASH_ALL` sighash in one of its input and the user hasn't
/// explicitly allowed them
///
/// To enable signing transactions with non-standard sighashes set
/// [`SignOptions::allow_all_sighashes`] to `true`.
NonStandardSighash,
}
impl fmt::Display for SignerError {
@ -465,6 +471,12 @@ pub struct SignOptions {
/// timelock height has already been reached. This option allows overriding the "current height" to let the
/// wallet use timelocks in the future to spend a coin.
pub assume_height: Option<u32>,
/// Whether the signer should use the `sighash_type` set in the PSBT when signing, no matter
/// what its value is
///
/// Defaults to `false` which will only allow signing using `SIGHASH_ALL`.
pub allow_all_sighashes: bool,
}
impl Default for SignOptions {
@ -472,6 +484,7 @@ impl Default for SignOptions {
SignOptions {
trust_witness_utxo: false,
assume_height: None,
allow_all_sighashes: false,
}
}
}