From 1312184ed7a9c5c7a6f99cf8bb08d43775ba5083 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Thu, 12 May 2022 17:28:41 +0200 Subject: [PATCH] Attach a context to our software signers This allows the signer to know the signing context precisely without relying on heuristics on the psbt fields. Due to the context being static, we still have to look at the PSBT when producing taproot signatures to determine the set of leaf hashes that the key can sign for. --- src/descriptor/policy.rs | 39 ++++---- src/wallet/mod.rs | 8 +- src/wallet/signer.rs | 197 ++++++++++++++++++++++++--------------- 3 files changed, 150 insertions(+), 94 deletions(-) diff --git a/src/descriptor/policy.rs b/src/descriptor/policy.rs index 98b26573..c4b94a52 100644 --- a/src/descriptor/policy.rs +++ b/src/descriptor/policy.rs @@ -21,6 +21,7 @@ //! ``` //! # use std::sync::Arc; //! # use bdk::descriptor::*; +//! # use bdk::wallet::signer::*; //! # use bdk::bitcoin::secp256k1::Secp256k1; //! use bdk::descriptor::policy::BuildSatisfaction; //! let secp = Secp256k1::new(); @@ -29,7 +30,7 @@ //! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?; //! println!("{:?}", extended_desc); //! -//! let signers = Arc::new(key_map.into()); +//! let signers = Arc::new(SignersContainer::build(key_map, &extended_desc, &secp)); //! let policy = extended_desc.extract_policy(&signers, BuildSatisfaction::None, &secp)?; //! println!("policy: {}", serde_json::to_string(&policy)?); //! # Ok::<(), bdk::Error>(()) @@ -1109,7 +1110,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1124,7 +1125,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1148,7 +1149,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1180,7 +1181,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1212,7 +1213,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1244,7 +1245,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1277,7 +1278,7 @@ mod test { .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); let single_key = wallet_desc.derive(0); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = single_key .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1293,7 +1294,7 @@ mod test { .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); let single_key = wallet_desc.derive(0); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = single_key .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1320,7 +1321,7 @@ mod test { .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); let single_key = wallet_desc.derive(0); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = single_key .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1363,7 +1364,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1402,7 +1403,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1427,7 +1428,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1445,7 +1446,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() @@ -1467,10 +1468,10 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers = keymap.into(); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc - .extract_policy(&signers, BuildSatisfaction::None, &secp) + .extract_policy(&signers_container, BuildSatisfaction::None, &secp) .unwrap() .unwrap(); @@ -1533,7 +1534,7 @@ mod test { addr.to_string() ); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let psbt = Psbt::from_str(ALICE_SIGNED_PSBT).unwrap(); @@ -1594,7 +1595,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let addr = wallet_desc .as_derived(0, &secp) @@ -1682,7 +1683,7 @@ mod test { let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers_container = Arc::new(SignersContainer::from(keymap)); + let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp)); let policy = wallet_desc.extract_policy(&signers_container, BuildSatisfaction::None, &secp); assert!(policy.is_ok()); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index b04921bd..ebbf5d4f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -197,7 +197,7 @@ where KeychainKind::External, get_checksum(&descriptor.to_string())?.as_bytes(), )?; - let signers = Arc::new(SignersContainer::from(keymap)); + let signers = Arc::new(SignersContainer::build(keymap, &descriptor, &secp)); let (change_descriptor, change_signers) = match change_descriptor { Some(desc) => { let (change_descriptor, change_keymap) = @@ -207,7 +207,11 @@ where get_checksum(&change_descriptor.to_string())?.as_bytes(), )?; - let change_signers = Arc::new(SignersContainer::from(change_keymap)); + let change_signers = Arc::new(SignersContainer::build( + change_keymap, + &change_descriptor, + &secp, + )); // if !parsed.same_structure(descriptor.as_ref()) { // return Err(Error::DifferentDescriptorStructure); // } diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index b6371035..cf6c882e 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -82,23 +82,26 @@ use std::cmp::Ordering; use std::collections::BTreeMap; use std::fmt; -use std::ops::Bound::Included; +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, Secp256k1}; +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::{DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey, KeyMap}; +use miniscript::descriptor::{ + Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey, + KeyMap, +}; use miniscript::{Legacy, MiniscriptKey, Segwitv0, Tap}; use super::utils::SecpCtx; -use crate::descriptor::XKeyUtils; +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 @@ -171,6 +174,44 @@ impl fmt::Display for SignerError { 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 @@ -231,17 +272,17 @@ impl TransactionSigner for T { } } -impl SignerCommon for DescriptorXKey { +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.clone())) + Some(DescriptorSecretKey::XPrv(self.signer.clone())) } } -impl InputSigner for DescriptorXKey { +impl InputSigner for SignerWrapper> { fn sign_input( &self, psbt: &mut psbt::PartiallySignedTransaction, @@ -289,30 +330,31 @@ impl InputSigner for DescriptorXKey { Err(SignerError::InvalidKey) } else { // HD wallets imply compressed keys - PrivateKey { + let priv_key = PrivateKey { compressed: true, network: self.xkey.network, inner: derived_key.private_key, - } - .sign_input(psbt, input_index, secp) + }; + + SignerWrapper::new(priv_key, self.ctx).sign_input(psbt, input_index, secp) } } } -impl SignerCommon for PrivateKey { +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, + key: self.signer, origin: None, })) } } -impl InputSigner for PrivateKey { +impl InputSigner for SignerWrapper { fn sign_input( &self, psbt: &mut psbt::PartiallySignedTransaction, @@ -331,11 +373,9 @@ impl InputSigner for PrivateKey { let pubkey = PublicKey::from_private_key(secp, self); let x_only_pubkey = XOnlyPublicKey::from(pubkey.inner); - let is_taproot = psbt.inputs[input_index].tap_internal_key.is_some() - || psbt.inputs[input_index].tap_merkle_root.is_some(); - match psbt.inputs[input_index].tap_internal_key { - Some(k) if k == x_only_pubkey && psbt.inputs[input_index].tap_key_sig.is_none() => { + 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, @@ -347,56 +387,54 @@ impl InputSigner for PrivateKey { 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, - ); - } - } - if !is_taproot { - if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) { - return Ok(()); + 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, + ); + } } - // FIXME: use the presence of `witness_utxo` as an indication that we should make a bip143 - // sig. Does this make sense? Should we add an extra argument to explicitly switch between - // these? The original idea was to declare sign() as sign() and use Ctx, - // but that violates the rules for trait-objects, so we can't do it. - let (hash, hash_ty) = match psbt.inputs[input_index].witness_utxo { - Some(_) => Segwitv0::sighash(psbt, input_index, ())?, - None => Legacy::sighash(psbt, input_index, ())?, - }; - sign_psbt_ecdsa( - &self.inner, - pubkey, - &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(()) } } @@ -496,24 +534,37 @@ impl SignersContainer { .filter_map(|secret| secret.as_public(secp).ok().map(|public| (public, secret))) .collect() } -} -impl From for SignersContainer { - fn from(keymap: KeyMap) -> SignersContainer { - let secp = Secp256k1::new(); + /// 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 (_, secret) in keymap { + 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()), + SignerId::from(private_key.key.public_key(secp).to_pubkeyhash()), SignerOrdering::default(), - Arc::new(private_key.key), + Arc::new(SignerWrapper::new(private_key.key, ctx)), ), DescriptorSecretKey::XPrv(xprv) => container.add_external( - SignerId::from(xprv.root_fingerprint(&secp)), + SignerId::from(xprv.root_fingerprint(secp)), SignerOrdering::default(), - Arc::new(xprv), + Arc::new(SignerWrapper::new(xprv, ctx)), ), }; } @@ -869,11 +920,11 @@ mod signers_container_tests { let (prvkey1, _, _) = setup_keys(TPRV0_STR); let (prvkey2, _, _) = setup_keys(TPRV1_STR); let desc = descriptor!(sh(multi(2, prvkey1, prvkey2))).unwrap(); - let (_, keymap) = desc + let (wallet_desc, keymap) = desc .into_wallet_descriptor(&secp, Network::Testnet) .unwrap(); - let signers = SignersContainer::from(keymap); + let signers = SignersContainer::build(keymap, &wallet_desc, &secp); assert_eq!(signers.ids().len(), 2); let signers = signers.signers();