// Bitcoin Dev Kit // Written in 2020 by Alekos Filini // // Copyright (c) 2020-2021 Bitcoin Dev Kit Developers // // This file is licensed under the Apache License, Version 2.0 or the MIT license // , at your option. // You may not use this file except in accordance with one or both of these // licenses. //! Generalized signers //! //! This module provides the ability to add customized signers to a [`Wallet`](super::Wallet) //! through the [`Wallet::add_signer`](super::Wallet::add_signer) function. //! //! ``` //! # use std::sync::Arc; //! # use std::str::FromStr; //! # use bitcoin::secp256k1::{Secp256k1, All}; //! # use bitcoin::*; //! # use bitcoin::util::psbt; //! # use bdk::signer::*; //! # use bdk::database::*; //! # use bdk::*; //! # #[derive(Debug)] //! # struct CustomHSM; //! # impl CustomHSM { //! # fn hsm_sign_input(&self, _psbt: &mut psbt::PartiallySignedTransaction, _input: usize) -> Result<(), SignerError> { //! # Ok(()) //! # } //! # fn connect() -> Self { //! # CustomHSM //! # } //! # fn get_id(&self) -> SignerId { //! # SignerId::Dummy(0) //! # } //! # } //! #[derive(Debug)] //! struct CustomSigner { //! device: CustomHSM, //! } //! //! impl CustomSigner { //! fn connect() -> Self { //! CustomSigner { device: CustomHSM::connect() } //! } //! } //! //! impl SignerCommon for CustomSigner { //! fn id(&self, _secp: &Secp256k1) -> SignerId { //! self.device.get_id() //! } //! } //! //! impl InputSigner for CustomSigner { //! fn sign_input( //! &self, //! psbt: &mut psbt::PartiallySignedTransaction, //! input_index: usize, //! _secp: &Secp256k1, //! ) -> Result<(), SignerError> { //! self.device.hsm_sign_input(psbt, input_index)?; //! //! Ok(()) //! } //! } //! //! let custom_signer = CustomSigner::connect(); //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; //! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?; //! wallet.add_signer( //! KeychainKind::External, //! SignerOrdering(200), //! Arc::new(custom_signer) //! ); //! //! # Ok::<_, bdk::Error>(()) //! ``` use std::cmp::Ordering; use std::collections::BTreeMap; use std::fmt; use std::ops::{Bound::Included, Deref}; use std::sync::Arc; use bitcoin::blockdata::opcodes; use bitcoin::blockdata::script::Builder as ScriptBuilder; use bitcoin::hashes::{hash160, Hash}; use bitcoin::secp256k1::Message; use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint}; use bitcoin::util::{ecdsa, psbt, schnorr, sighash, taproot}; use bitcoin::{secp256k1, XOnlyPublicKey}; use bitcoin::{EcdsaSighashType, PrivateKey, PublicKey, SchnorrSighashType, Script}; use miniscript::descriptor::{ Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey, KeyMap, SinglePubKey, }; use miniscript::{Legacy, MiniscriptKey, Segwitv0, Tap}; use super::utils::SecpCtx; use crate::descriptor::{DescriptorMeta, XKeyUtils}; /// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among /// multiple of them #[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq, Hash)] pub enum SignerId { /// Bitcoin HASH160 (RIPEMD160 after SHA256) hash of an ECDSA public key PkHash(hash160::Hash), /// The fingerprint of a BIP32 extended key Fingerprint(Fingerprint), /// Dummy identifier Dummy(u64), } impl From for SignerId { fn from(hash: hash160::Hash) -> SignerId { SignerId::PkHash(hash) } } impl From for SignerId { fn from(fing: Fingerprint) -> SignerId { SignerId::Fingerprint(fing) } } /// Signing error #[derive(Debug, PartialEq, Eq, Clone)] pub enum SignerError { /// The private key is missing for the required public key MissingKey, /// The private key in use has the right fingerprint but derives differently than expected InvalidKey, /// The user canceled the operation UserCanceled, /// Input index is out of range InputIndexOutOfRange, /// The `non_witness_utxo` field of the transaction is required to sign this input MissingNonWitnessUtxo, /// The `non_witness_utxo` specified is invalid InvalidNonWitnessUtxo, /// The `witness_utxo` field of the transaction is required to sign this input MissingWitnessUtxo, /// The `witness_script` field of the transaction is required to sign this input 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, /// Invalid SIGHASH for the signing context in use InvalidSighash, /// Error while computing the hash to sign SighashError(sighash::Error), } impl From for SignerError { fn from(e: sighash::Error) -> Self { SignerError::SighashError(e) } } impl fmt::Display for SignerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) } } impl std::error::Error for SignerError {} /// Signing context /// /// Used by our software signers to determine the type of signatures to make #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SignerContext { /// Legacy context Legacy, /// Segwit v0 context (BIP 143) Segwitv0, /// Taproot context (BIP 340) Tap { /// Whether the signer can sign for the internal key or not is_internal_key: bool, }, } /// Wrapper structure to pair a signer with its context #[derive(Debug, Clone)] pub struct SignerWrapper { signer: S, ctx: SignerContext, } impl SignerWrapper { /// Create a wrapped signer from a signer and a context pub fn new(signer: S, ctx: SignerContext) -> Self { SignerWrapper { signer, ctx } } } impl Deref for SignerWrapper { type Target = S; fn deref(&self) -> &Self::Target { &self.signer } } /// Common signer methods pub trait SignerCommon: fmt::Debug + Send + Sync { /// Return the [`SignerId`] for this signer /// /// The [`SignerId`] can be used to lookup a signer in the [`Wallet`](crate::Wallet)'s signers map or to /// compare two signers. fn id(&self, secp: &SecpCtx) -> SignerId; /// Return the secret key for the signer /// /// This is used internally to reconstruct the original descriptor that may contain secrets. /// External signers that are meant to keep key isolated should just return `None` here (which /// is the default for this method, if not overridden). fn descriptor_secret_key(&self) -> Option { None } } /// PSBT Input signer /// /// This trait can be implemented to provide custom signers to the wallet. If the signer supports signing /// individual inputs, this trait should be implemented and BDK will provide automatically an implementation /// for [`TransactionSigner`]. pub trait InputSigner: SignerCommon { /// Sign a single psbt input fn sign_input( &self, psbt: &mut psbt::PartiallySignedTransaction, input_index: usize, secp: &SecpCtx, ) -> Result<(), SignerError>; } /// PSBT signer /// /// This trait can be implemented when the signer can't sign inputs individually, but signs the whole transaction /// at once. pub trait TransactionSigner: SignerCommon { /// Sign all the inputs of the psbt fn sign_transaction( &self, psbt: &mut psbt::PartiallySignedTransaction, secp: &SecpCtx, ) -> Result<(), SignerError>; } impl TransactionSigner for T { fn sign_transaction( &self, psbt: &mut psbt::PartiallySignedTransaction, secp: &SecpCtx, ) -> Result<(), SignerError> { for input_index in 0..psbt.inputs.len() { self.sign_input(psbt, input_index, secp)?; } Ok(()) } } impl SignerCommon for SignerWrapper> { fn id(&self, secp: &SecpCtx) -> SignerId { SignerId::from(self.root_fingerprint(secp)) } fn descriptor_secret_key(&self) -> Option { Some(DescriptorSecretKey::XPrv(self.signer.clone())) } } impl InputSigner for SignerWrapper> { fn sign_input( &self, psbt: &mut psbt::PartiallySignedTransaction, input_index: usize, secp: &SecpCtx, ) -> Result<(), SignerError> { if input_index >= psbt.inputs.len() { return Err(SignerError::InputIndexOutOfRange); } if psbt.inputs[input_index].final_script_sig.is_some() || psbt.inputs[input_index].final_script_witness.is_some() { return Ok(()); } let tap_key_origins = psbt.inputs[input_index] .tap_key_origins .iter() .map(|(pk, (_, keysource))| (SinglePubKey::XOnly(*pk), keysource)); let (public_key, full_path) = match psbt.inputs[input_index] .bip32_derivation .iter() .map(|(pk, keysource)| (SinglePubKey::FullKey(PublicKey::new(*pk)), keysource)) .chain(tap_key_origins) .find_map(|(pk, keysource)| { if self.matches(keysource, secp).is_some() { Some((pk, keysource.1.clone())) } else { None } }) { Some((pk, full_path)) => (pk, full_path), None => return Ok(()), }; let derived_key = match self.origin.clone() { Some((_fingerprint, origin_path)) => { let deriv_path = DerivationPath::from( &full_path.into_iter().cloned().collect::>() [origin_path.len()..], ); self.xkey.derive_priv(secp, &deriv_path).unwrap() } None => self.xkey.derive_priv(secp, &full_path).unwrap(), }; let computed_pk = secp256k1::PublicKey::from_secret_key(secp, &derived_key.private_key); let valid_key = match public_key { SinglePubKey::FullKey(pk) if pk.inner == computed_pk => true, SinglePubKey::XOnly(x_only) if XOnlyPublicKey::from(computed_pk) == x_only => true, _ => false, }; if !valid_key { Err(SignerError::InvalidKey) } else { // HD wallets imply compressed keys let priv_key = PrivateKey { compressed: true, network: self.xkey.network, inner: derived_key.private_key, }; SignerWrapper::new(priv_key, self.ctx).sign_input(psbt, input_index, secp) } } } impl SignerCommon for SignerWrapper { fn id(&self, secp: &SecpCtx) -> SignerId { SignerId::from(self.public_key(secp).to_pubkeyhash()) } fn descriptor_secret_key(&self) -> Option { Some(DescriptorSecretKey::SinglePriv(DescriptorSinglePriv { key: self.signer, origin: None, })) } } impl InputSigner for SignerWrapper { fn sign_input( &self, psbt: &mut psbt::PartiallySignedTransaction, input_index: usize, secp: &SecpCtx, ) -> Result<(), SignerError> { if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() { return Err(SignerError::InputIndexOutOfRange); } if psbt.inputs[input_index].final_script_sig.is_some() || psbt.inputs[input_index].final_script_witness.is_some() { return Ok(()); } let pubkey = PublicKey::from_private_key(secp, self); let x_only_pubkey = XOnlyPublicKey::from(pubkey.inner); if let SignerContext::Tap { is_internal_key } = self.ctx { if is_internal_key && psbt.inputs[input_index].tap_key_sig.is_none() { let (hash, hash_ty) = Tap::sighash(psbt, input_index, None)?; sign_psbt_schnorr( &self.inner, x_only_pubkey, None, &mut psbt.inputs[input_index], hash, hash_ty, secp, ); } if let Some((leaf_hashes, _)) = psbt.inputs[input_index].tap_key_origins.get(&x_only_pubkey) { let leaf_hashes = leaf_hashes .iter() .filter(|lh| { !psbt.inputs[input_index] .tap_script_sigs .contains_key(&(x_only_pubkey, **lh)) }) .cloned() .collect::>(); for lh in leaf_hashes { let (hash, hash_ty) = Tap::sighash(psbt, input_index, Some(lh))?; sign_psbt_schnorr( &self.inner, x_only_pubkey, Some(lh), &mut psbt.inputs[input_index], hash, hash_ty, secp, ); } } return Ok(()); } if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) { return Ok(()); } let (hash, hash_ty) = match self.ctx { SignerContext::Segwitv0 => Segwitv0::sighash(psbt, input_index, ())?, SignerContext::Legacy => Legacy::sighash(psbt, input_index, ())?, _ => return Ok(()), // handled above }; sign_psbt_ecdsa( &self.inner, pubkey, &mut psbt.inputs[input_index], hash, hash_ty, secp, ); Ok(()) } } fn sign_psbt_ecdsa( secret_key: &secp256k1::SecretKey, pubkey: PublicKey, psbt_input: &mut psbt::Input, hash: bitcoin::Sighash, hash_ty: EcdsaSighashType, secp: &SecpCtx, ) { let sig = secp.sign_ecdsa( &Message::from_slice(&hash.into_inner()[..]).unwrap(), secret_key, ); let final_signature = ecdsa::EcdsaSig { sig, hash_ty }; psbt_input.partial_sigs.insert(pubkey, final_signature); } // Calling this with `leaf_hash` = `None` will sign for key-spend fn sign_psbt_schnorr( secret_key: &secp256k1::SecretKey, pubkey: XOnlyPublicKey, leaf_hash: Option, psbt_input: &mut psbt::Input, hash: taproot::TapSighashHash, hash_ty: SchnorrSighashType, secp: &SecpCtx, ) { use schnorr::TapTweak; let keypair = secp256k1::KeyPair::from_seckey_slice(secp, secret_key.as_ref()).unwrap(); let keypair = match leaf_hash { None => keypair .tap_tweak(secp, psbt_input.tap_merkle_root) .into_inner(), Some(_) => keypair, // no tweak for script spend }; let sig = secp.sign_schnorr( &Message::from_slice(&hash.into_inner()[..]).unwrap(), &keypair, ); let final_signature = schnorr::SchnorrSig { sig, hash_ty }; if let Some(lh) = leaf_hash { psbt_input .tap_script_sigs .insert((pubkey, lh), final_signature); } else { psbt_input.tap_key_sig = Some(final_signature); } } /// Defines the order in which signers are called /// /// The default value is `100`. Signers with an ordering above that will be called later, /// and they will thus see the partial signatures added to the transaction once they get to sign /// themselves. #[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)] pub struct SignerOrdering(pub usize); impl std::default::Default for SignerOrdering { fn default() -> Self { SignerOrdering(100) } } #[derive(Debug, Clone)] struct SignersContainerKey { id: SignerId, ordering: SignerOrdering, } impl From<(SignerId, SignerOrdering)> for SignersContainerKey { fn from(tuple: (SignerId, SignerOrdering)) -> Self { SignersContainerKey { id: tuple.0, ordering: tuple.1, } } } /// Container for multiple signers #[derive(Debug, Default, Clone)] pub struct SignersContainer(BTreeMap>); impl SignersContainer { /// Create a map of public keys to secret keys pub fn as_key_map(&self, secp: &SecpCtx) -> KeyMap { self.0 .values() .filter_map(|signer| signer.descriptor_secret_key()) .filter_map(|secret| secret.as_public(secp).ok().map(|public| (public, secret))) .collect() } /// Build a new signer container from a [`KeyMap`] /// /// Also looks at the corresponding descriptor to determine the [`SignerContext`] to attach to /// the signers pub fn build( keymap: KeyMap, descriptor: &Descriptor, secp: &SecpCtx, ) -> SignersContainer { let mut container = SignersContainer::new(); for (pubkey, secret) in keymap { let ctx = match descriptor { Descriptor::Tr(tr) => SignerContext::Tap { is_internal_key: tr.internal_key() == &pubkey, }, _ if descriptor.is_witness() => SignerContext::Segwitv0, _ => SignerContext::Legacy, }; match secret { DescriptorSecretKey::SinglePriv(private_key) => container.add_external( SignerId::from(private_key.key.public_key(secp).to_pubkeyhash()), SignerOrdering::default(), Arc::new(SignerWrapper::new(private_key.key, ctx)), ), DescriptorSecretKey::XPrv(xprv) => container.add_external( SignerId::from(xprv.root_fingerprint(secp)), SignerOrdering::default(), Arc::new(SignerWrapper::new(xprv, ctx)), ), }; } container } } impl SignersContainer { /// Default constructor pub fn new() -> Self { SignersContainer(Default::default()) } /// Adds an external signer to the container for the specified id. Optionally returns the /// signer that was previously in the container, if any pub fn add_external( &mut self, id: SignerId, ordering: SignerOrdering, signer: Arc, ) -> Option> { self.0.insert((id, ordering).into(), signer) } /// Removes a signer from the container and returns it pub fn remove( &mut self, id: SignerId, ordering: SignerOrdering, ) -> Option> { self.0.remove(&(id, ordering).into()) } /// Returns the list of identifiers of all the signers in the container pub fn ids(&self) -> Vec<&SignerId> { self.0 .keys() .map(|SignersContainerKey { id, .. }| id) .collect() } /// Returns the list of signers in the container, sorted by lowest to highest `ordering` pub fn signers(&self) -> Vec<&Arc> { self.0.values().collect() } /// Finds the signer with lowest ordering for a given id in the container. pub fn find(&self, id: SignerId) -> Option<&Arc> { self.0 .range(( Included(&(id.clone(), SignerOrdering(0)).into()), Included(&(id.clone(), SignerOrdering(usize::MAX)).into()), )) .filter(|(k, _)| k.id == id) .map(|(_, v)| v) .next() } } /// 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: 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, /// 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, } #[allow(clippy::derivable_impls)] impl Default for SignOptions { fn default() -> Self { SignOptions { trust_witness_utxo: false, assume_height: None, allow_all_sighashes: false, } } } pub(crate) trait ComputeSighash { type Extra; type Sighash; type SighashType; fn sighash( psbt: &psbt::PartiallySignedTransaction, input_index: usize, extra: Self::Extra, ) -> Result<(Self::Sighash, Self::SighashType), SignerError>; } impl ComputeSighash for Legacy { type Extra = (); type Sighash = bitcoin::Sighash; type SighashType = EcdsaSighashType; fn sighash( psbt: &psbt::PartiallySignedTransaction, input_index: usize, _extra: (), ) -> Result<(Self::Sighash, Self::SighashType), SignerError> { if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() { return Err(SignerError::InputIndexOutOfRange); } let psbt_input = &psbt.inputs[input_index]; let tx_input = &psbt.unsigned_tx.input[input_index]; let sighash = psbt_input .sighash_type .unwrap_or_else(|| EcdsaSighashType::All.into()) .ecdsa_hash_ty() .map_err(|_| SignerError::InvalidSighash)?; let script = match psbt_input.redeem_script { Some(ref redeem_script) => redeem_script.clone(), None => { let non_witness_utxo = psbt_input .non_witness_utxo .as_ref() .ok_or(SignerError::MissingNonWitnessUtxo)?; let prev_out = non_witness_utxo .output .get(tx_input.previous_output.vout as usize) .ok_or(SignerError::InvalidNonWitnessUtxo)?; prev_out.script_pubkey.clone() } }; Ok(( sighash::SighashCache::new(&psbt.unsigned_tx).legacy_signature_hash( input_index, &script, sighash.to_u32(), )?, sighash, )) } } fn p2wpkh_script_code(script: &Script) -> Script { ScriptBuilder::new() .push_opcode(opcodes::all::OP_DUP) .push_opcode(opcodes::all::OP_HASH160) .push_slice(&script[2..]) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG) .into_script() } impl ComputeSighash for Segwitv0 { type Extra = (); type Sighash = bitcoin::Sighash; type SighashType = EcdsaSighashType; fn sighash( psbt: &psbt::PartiallySignedTransaction, input_index: usize, _extra: (), ) -> Result<(Self::Sighash, Self::SighashType), SignerError> { if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() { return Err(SignerError::InputIndexOutOfRange); } let psbt_input = &psbt.inputs[input_index]; let tx_input = &psbt.unsigned_tx.input[input_index]; let sighash = psbt_input .sighash_type .unwrap_or_else(|| EcdsaSighashType::All.into()) .ecdsa_hash_ty() .map_err(|_| SignerError::InvalidSighash)?; // 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 utxo.script_pubkey.is_v0_p2wpkh() { p2wpkh_script_code(&utxo.script_pubkey) } else if psbt_input .redeem_script .as_ref() .map(Script::is_v0_p2wpkh) .unwrap_or(false) { p2wpkh_script_code(psbt_input.redeem_script.as_ref().unwrap()) } else { return Err(SignerError::MissingWitnessScript); } } }; Ok(( sighash::SighashCache::new(&psbt.unsigned_tx).segwit_signature_hash( input_index, &script, value, sighash, )?, sighash, )) } } impl ComputeSighash for Tap { type Extra = Option; type Sighash = taproot::TapSighashHash; type SighashType = SchnorrSighashType; fn sighash( psbt: &psbt::PartiallySignedTransaction, input_index: usize, extra: Self::Extra, ) -> Result<(Self::Sighash, SchnorrSighashType), SignerError> { if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() { return Err(SignerError::InputIndexOutOfRange); } let psbt_input = &psbt.inputs[input_index]; let sighash_type = psbt_input .sighash_type .unwrap_or_else(|| SchnorrSighashType::Default.into()) .schnorr_hash_ty() .map_err(|_| SignerError::InvalidSighash)?; let witness_utxos = psbt .inputs .iter() .cloned() .map(|i| i.witness_utxo) .collect::>(); let mut all_witness_utxos = vec![]; let mut cache = sighash::SighashCache::new(&psbt.unsigned_tx); let is_anyone_can_pay = psbt::PsbtSighashType::from(sighash_type).to_u32() & 0x80 != 0; let prevouts = if is_anyone_can_pay { sighash::Prevouts::One( input_index, witness_utxos[input_index] .as_ref() .ok_or(SignerError::MissingWitnessUtxo)?, ) } else if witness_utxos.iter().all(Option::is_some) { all_witness_utxos.extend(witness_utxos.iter().filter_map(|x| x.as_ref())); sighash::Prevouts::All(&all_witness_utxos) } else { return Err(SignerError::MissingWitnessUtxo); }; // Assume no OP_CODESEPARATOR let extra = extra.map(|leaf_hash| (leaf_hash, 0xFFFFFFFF)); Ok(( cache.taproot_signature_hash(input_index, &prevouts, None, extra, sighash_type)?, sighash_type, )) } } impl PartialOrd for SignersContainerKey { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for SignersContainerKey { fn cmp(&self, other: &Self) -> Ordering { self.ordering .cmp(&other.ordering) .then(self.id.cmp(&other.id)) } } impl PartialEq for SignersContainerKey { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.ordering == other.ordering } } impl Eq for SignersContainerKey {} #[cfg(test)] mod signers_container_tests { use super::*; use crate::descriptor; use crate::descriptor::IntoWalletDescriptor; use crate::keys::{DescriptorKey, IntoDescriptorKey}; use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::util::bip32; use bitcoin::Network; use miniscript::ScriptContext; use std::str::FromStr; fn is_equal(this: &Arc, that: &Arc) -> bool { let secp = Secp256k1::new(); this.id(&secp) == that.id(&secp) } // Signers added with the same ordering (like `Ordering::default`) created from `KeyMap` // should be preserved and not overwritten. // This happens usually when a set of signers is created from a descriptor with private keys. #[test] fn signers_with_same_ordering() { let secp = Secp256k1::new(); let (prvkey1, _, _) = setup_keys(TPRV0_STR); let (prvkey2, _, _) = setup_keys(TPRV1_STR); let desc = descriptor!(sh(multi(2, prvkey1, prvkey2))).unwrap(); let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); let signers = SignersContainer::build(keymap, &wallet_desc, &secp); assert_eq!(signers.ids().len(), 2); let signers = signers.signers(); assert_eq!(signers.len(), 2); } #[test] fn signers_sorted_by_ordering() { let mut signers = SignersContainer::new(); let signer1 = Arc::new(DummySigner { number: 1 }); let signer2 = Arc::new(DummySigner { number: 2 }); let signer3 = Arc::new(DummySigner { number: 3 }); // Mixed order insertions verifies we are not inserting at head or tail. signers.add_external(SignerId::Dummy(2), SignerOrdering(2), signer2.clone()); signers.add_external(SignerId::Dummy(1), SignerOrdering(1), signer1.clone()); signers.add_external(SignerId::Dummy(3), SignerOrdering(3), signer3.clone()); // Check that signers are sorted from lowest to highest ordering let signers = signers.signers(); assert!(is_equal(signers[0], &signer1)); assert!(is_equal(signers[1], &signer2)); assert!(is_equal(signers[2], &signer3)); } #[test] fn find_signer_by_id() { let mut signers = SignersContainer::new(); let signer1 = Arc::new(DummySigner { number: 1 }); let signer2 = Arc::new(DummySigner { number: 2 }); let signer3 = Arc::new(DummySigner { number: 3 }); let signer4 = Arc::new(DummySigner { number: 3 }); // Same ID as `signer3` but will use lower ordering. let id1 = SignerId::Dummy(1); let id2 = SignerId::Dummy(2); let id3 = SignerId::Dummy(3); let id_nonexistent = SignerId::Dummy(999); signers.add_external(id1.clone(), SignerOrdering(1), signer1.clone()); signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone()); signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone()); assert!(matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1))); assert!(matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2))); assert!(matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3))); // The `signer4` has the same ID as `signer3` but lower ordering. // It should be found by `id3` instead of `signer3`. signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone()); assert!(matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4))); // Can't find anything with ID that doesn't exist assert!(matches!(signers.find(id_nonexistent), None)); } #[derive(Debug, Clone, Copy)] struct DummySigner { number: u64, } impl SignerCommon for DummySigner { fn id(&self, _secp: &SecpCtx) -> SignerId { SignerId::Dummy(self.number) } } impl TransactionSigner for DummySigner { fn sign_transaction( &self, _psbt: &mut psbt::PartiallySignedTransaction, _secp: &SecpCtx, ) -> Result<(), SignerError> { Ok(()) } } const TPRV0_STR:&str = "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf"; const TPRV1_STR:&str = "tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N"; const PATH: &str = "m/44'/1'/0'/0"; fn setup_keys( tprv: &str, ) -> (DescriptorKey, DescriptorKey, Fingerprint) { let secp: Secp256k1 = Secp256k1::new(); let path = bip32::DerivationPath::from_str(PATH).unwrap(); let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap(); let tpub = bip32::ExtendedPubKey::from_priv(&secp, &tprv); let fingerprint = tprv.fingerprint(&secp); let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap(); let pubkey = (tpub, path).into_descriptor_key().unwrap(); (prvkey, pubkey, fingerprint) } }