[signer] Adjust signing behavior with SignOptions

This commit is contained in:
Alekos Filini
2021-04-19 14:16:39 +02:00
parent 1d628d84b5
commit b5e9589803
6 changed files with 110 additions and 41 deletions

View File

@@ -256,6 +256,7 @@ pub use error::Error;
pub use types::*;
pub use wallet::address_validator;
pub use wallet::signer;
pub use wallet::signer::SignOptions;
pub use wallet::tx_builder::TxBuilder;
pub use wallet::Wallet;

View File

@@ -46,7 +46,7 @@ pub use utils::IsDust;
use address_validator::AddressValidator;
use coin_selection::DefaultCoinSelectionAlgorithm;
use signer::{Signer, SignerOrdering, SignersContainer};
use signer::{SignOptions, Signer, SignerOrdering, SignersContainer};
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LIMIT_SATOSHI};
@@ -706,7 +706,7 @@ where
/// .enable_rbf();
/// builder.finish()?
/// };
/// let _ = wallet.sign(&mut psbt, None)?;
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
/// let tx = psbt.extract_tx();
/// // broadcast tx but it's taking too long to confirm so we want to bump the fee
/// let (mut psbt, _) = {
@@ -716,7 +716,7 @@ where
/// builder.finish()?
/// };
///
/// let _ = wallet.sign(&mut psbt, None)?;
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
/// let fee_bumped_tx = psbt.extract_tx();
/// // broadcast fee_bumped_tx to replace original
/// # Ok::<(), bdk::Error>(())
@@ -833,6 +833,11 @@ where
/// Sign a transaction with all the wallet's signers, in the order specified by every signer's
/// [`SignerOrdering`]
///
/// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
/// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
/// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined
/// in this library will.
///
/// ## Example
///
/// ```
@@ -848,13 +853,23 @@ where
/// builder.add_recipient(to_address.script_pubkey(), 50_000);
/// builder.finish()?
/// };
/// let finalized = wallet.sign(&mut psbt, None)?;
/// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
/// assert!(finalized, "we should have signed all the inputs");
/// # Ok::<(), bdk::Error>(())
pub fn sign(&self, psbt: &mut PSBT, assume_height: Option<u32>) -> Result<bool, Error> {
pub fn sign(&self, psbt: &mut PSBT, sign_options: SignOptions) -> Result<bool, Error> {
// this helps us doing our job later
self.add_input_hd_keypaths(psbt)?;
// If we aren't allowed to use `witness_utxo`, ensure that every input has the
// `non_witness_utxo`
if !sign_options.trust_witness_utxo {
for input in &psbt.inputs {
if input.non_witness_utxo.is_none() {
return Err(Error::Signer(signer::SignerError::MissingNonWitnessUtxo));
}
}
}
for signer in self
.signers
.signers()
@@ -871,7 +886,7 @@ where
}
// attempt to finalize
self.finalize_psbt(psbt, assume_height)
self.finalize_psbt(psbt, sign_options)
}
/// Return the spending policies for the wallet's descriptor
@@ -907,11 +922,9 @@ where
}
/// Try to finalize a PSBT
pub fn finalize_psbt(
&self,
psbt: &mut PSBT,
assume_height: Option<u32>,
) -> Result<bool, Error> {
///
/// The [`SignOptions`] can be used to tweak the behavior of the finalizer.
pub fn finalize_psbt(&self, psbt: &mut PSBT, sign_options: SignOptions) -> Result<bool, Error> {
let tx = &psbt.global.unsigned_tx;
let mut finished = true;
@@ -927,7 +940,7 @@ where
.borrow()
.get_tx(&input.previous_output.txid, false)?
.map(|tx| tx.height.unwrap_or(std::u32::MAX));
let current_height = assume_height.or(self.current_height);
let current_height = sign_options.assume_height.or(self.current_height);
debug!(
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
@@ -2440,14 +2453,14 @@ mod test {
"foreign_utxo should be in there"
);
let finished = wallet1.sign(&mut psbt, None).unwrap();
let finished = wallet1.sign(&mut psbt, Default::default()).unwrap();
assert!(
!finished,
"only one of the inputs should have been signed so far"
);
let finished = wallet2.sign(&mut psbt, None).unwrap();
let finished = wallet2.sign(&mut psbt, Default::default()).unwrap();
assert!(finished, "all the inputs should have been signed now");
}
@@ -3468,7 +3481,7 @@ mod test {
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, None).unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true);
let extracted = psbt.extract_tx();
@@ -3485,7 +3498,7 @@ mod test {
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, None).unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true);
let extracted = psbt.extract_tx();
@@ -3502,7 +3515,7 @@ mod test {
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, None).unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true);
let extracted = psbt.extract_tx();
@@ -3519,7 +3532,7 @@ mod test {
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, None).unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true);
let extracted = psbt.extract_tx();
@@ -3537,7 +3550,7 @@ mod test {
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, None).unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true);
let extracted = psbt.extract_tx();
@@ -3557,7 +3570,7 @@ mod test {
psbt.inputs[0].bip32_derivation.clear();
assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0);
let finalized = wallet.sign(&mut psbt, None).unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true);
let extracted = psbt.extract_tx();
@@ -3606,7 +3619,7 @@ mod test {
psbt.inputs.push(dud_input);
psbt.global.unsigned_tx.input.push(bitcoin::TxIn::default());
let is_final = wallet.sign(&mut psbt, None).unwrap();
let is_final = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(
!is_final,
"shouldn't be final since we can't sign one of the inputs"

View File

@@ -427,6 +427,43 @@ impl SignersContainer {
}
}
/// Options for a software signer
///
/// Adjust the behavior of our software signers and the way a transaction is finalized
#[derive(Debug, Clone)]
pub struct SignOptions {
/// Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been
/// provided
///
/// Defaults to `false` to mitigate the "SegWit bug" which chould trick the wallet into
/// paying a fee larger than expected.
///
/// Some wallets, especially if relatively old, might not provide the `non_witness_utxo` for
/// SegWit transactions in the PSBT they generate: in those cases setting this to `true`
/// should correctly produce a signature, at the expense of an increased trust in the creator
/// of the PSBT.
///
/// For more details see: <https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd>
pub trust_witness_utxo: bool,
/// Whether the wallet should assume a specific height has been reached when trying to finalize
/// a transaction
///
/// The wallet will only "use" a timelock to satisfy the spending policy of an input if the
/// 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>,
}
impl Default for SignOptions {
fn default() -> Self {
SignOptions {
trust_witness_utxo: false,
assume_height: None,
}
}
}
pub(crate) trait ComputeSighash {
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
@@ -492,20 +529,37 @@ impl ComputeSighash for Segwitv0 {
}
let psbt_input = &psbt.inputs[input_index];
let tx_input = &psbt.global.unsigned_tx.input[input_index];
let sighash = psbt_input.sighash_type.unwrap_or(SigHashType::All);
let witness_utxo = psbt_input
.witness_utxo
.as_ref()
.ok_or(SignerError::MissingWitnessUtxo)?;
let value = witness_utxo.value;
// Always try first with the non-witness utxo
let utxo = if let Some(prev_tx) = &psbt_input.non_witness_utxo {
// Check the provided prev-tx
if prev_tx.txid() != tx_input.previous_output.txid {
return Err(SignerError::InvalidNonWitnessUtxo);
}
// The output should be present, if it's missing the `non_witness_utxo` is invalid
prev_tx
.output
.get(tx_input.previous_output.vout as usize)
.ok_or(SignerError::InvalidNonWitnessUtxo)?
} else if let Some(witness_utxo) = &psbt_input.witness_utxo {
// Fallback to the witness_utxo. If we aren't allowed to use it, signing should fail
// before we get to this point
witness_utxo
} else {
// Nothing has been provided
return Err(SignerError::MissingNonWitnessUtxo);
};
let value = utxo.value;
let script = match psbt_input.witness_script {
Some(ref witness_script) => witness_script.clone(),
None => {
if witness_utxo.script_pubkey.is_v0_p2wpkh() {
p2wpkh_script_code(&witness_utxo.script_pubkey)
if utxo.script_pubkey.is_v0_p2wpkh() {
p2wpkh_script_code(&utxo.script_pubkey)
} else if psbt_input
.redeem_script
.as_ref()