diff --git a/Cargo.toml b/Cargo.toml index 5f5421ae..43d761fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ authors = ["Alekos Filini ", "Riccardo Casatta { + // disallow `sortedmulti` in `bare()` + ( Bare, Bare, sortedmulti $( $inner:tt )* ) => { + compile_error!("`bare()` descriptors can't contain any `sortedmulti` operands"); + }; + ( Bare, Bare, sortedmulti_vec $( $inner:tt )* ) => { + compile_error!("`bare()` descriptors can't contain any `sortedmulti_vec` operands"); + }; + + ( $descriptor_variant:ident, $sortedmulti_variant:ident, sortedmulti $( $inner:tt )* ) => { + $crate::impl_sortedmulti!(sortedmulti $( $inner )*) + .and_then(|(inner, key_map, valid_networks)| Ok(($crate::miniscript::Descriptor::$sortedmulti_variant(inner), key_map, valid_networks))) + }; + ( $descriptor_variant:ident, $sortedmulti_variant:ident, sortedmulti_vec $( $inner:tt )* ) => { + $crate::impl_sortedmulti!(sortedmulti_vec $( $inner )*) + .and_then(|(inner, key_map, valid_networks)| Ok(($crate::miniscript::Descriptor::$sortedmulti_variant(inner), key_map, valid_networks))) + }; + + ( $descriptor_variant:ident, $sortedmulti_variant:ident, $( $minisc:tt )* ) => { $crate::fragment!($( $minisc )*) .map(|(minisc, keymap, networks)|($crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::$descriptor_variant(minisc), keymap, networks)) }; @@ -39,9 +56,10 @@ macro_rules! impl_top_level_pk { ( $descriptor_variant:ident, $ctx:ty, $key:expr ) => {{ #[allow(unused_imports)] use $crate::keys::{DescriptorKey, ToDescriptorKey}; + let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); $key.to_descriptor_key() - .and_then(|key: DescriptorKey<$ctx>| key.extract()) + .and_then(|key: DescriptorKey<$ctx>| key.extract(&secp)) .map(|(pk, key_map, valid_networks)| { ( $crate::miniscript::Descriptor::< @@ -159,6 +177,28 @@ macro_rules! impl_node_opcode_three { }; } +#[doc(hidden)] +#[macro_export] +macro_rules! impl_sortedmulti { + ( sortedmulti_vec $thresh:expr, $keys:expr ) => ({ + let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); + $crate::keys::make_sortedmulti_inner($thresh, $keys, &secp) + }); + ( sortedmulti $thresh:expr $(, $key:expr )+ ) => ({ + use $crate::keys::ToDescriptorKey; + let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); + + let mut keys = vec![]; + $( + keys.push($key.to_descriptor_key()); + )* + + keys.into_iter().collect::, _>>() + .and_then(|keys| $crate::keys::make_sortedmulti_inner($thresh, keys, &secp)) + }); + +} + /// Macro to write full descriptors with code /// /// This macro expands to an object of type `Result<(Descriptor, KeyMap, ValidNetworks), Error>`. @@ -237,13 +277,13 @@ macro_rules! impl_node_opcode_three { #[macro_export] macro_rules! descriptor { ( bare ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Bare, $( $minisc )*) + $crate::impl_top_level_sh!(Bare, Bare, $( $minisc )*) }); ( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({ $crate::descriptor!(shwsh ($( $minisc )*)) }); ( shwsh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(ShWsh, $( $minisc )*) + $crate::impl_top_level_sh!(ShWsh, ShWshSortedMulti, $( $minisc )*) }); ( pk $key:expr ) => ({ $crate::impl_top_level_pk!(Pk, $crate::miniscript::Legacy, $key) @@ -261,10 +301,10 @@ macro_rules! descriptor { $crate::impl_top_level_pk!(ShWpkh, $crate::miniscript::Segwitv0, $key) }); ( sh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Sh, $( $minisc )*) + $crate::impl_top_level_sh!(Sh, ShSortedMulti, $( $minisc )*) }); ( wsh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Wsh, $( $minisc )*) + $crate::impl_top_level_sh!(Wsh, WshSortedMulti, $( $minisc )*) }); } @@ -314,7 +354,8 @@ macro_rules! fragment { $crate::impl_leaf_opcode!(False) }); ( pk_k $key:expr ) => ({ - $crate::keys::make_pk($key) + let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); + $crate::keys::make_pk($key, &secp) }); ( pk $key:expr ) => ({ $crate::fragment!(+c pk_k $key) @@ -391,6 +432,7 @@ macro_rules! fragment { }); ( multi $thresh:expr $(, $key:expr )+ ) => ({ use $crate::keys::ToDescriptorKey; + let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); let mut keys = vec![]; $( @@ -398,15 +440,23 @@ macro_rules! fragment { )* keys.into_iter().collect::, _>>() - .and_then(|keys| $crate::keys::make_multi($thresh, keys)) + .and_then(|keys| $crate::keys::make_multi($thresh, keys, &secp)) }); + // `sortedmulti()` is handled separately + ( sortedmulti $( $inner:tt )* ) => ({ + compile_error!("`sortedmulti` can only be used as the root operand of a descriptor"); + }); + ( sortedmulti_vec $( $inner:tt )* ) => ({ + compile_error!("`sortedmulti_vec` can only be used as the root operand of a descriptor"); + }); } #[cfg(test)] mod test { use bitcoin::hashes::hex::ToHex; - use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; + use bitcoin::secp256k1::Secp256k1; + use miniscript::descriptor::{DescriptorPublicKey, DescriptorPublicKeyCtx, KeyMap}; use miniscript::{Descriptor, Legacy, Segwitv0}; use std::str::FromStr; @@ -426,6 +476,9 @@ mod test { is_fixed: bool, expected: &[&str], ) { + let secp = Secp256k1::new(); + let deriv_ctx = DescriptorPublicKeyCtx::new(&secp, ChildNumber::Normal { index: 0 }); + let (desc, _key_map, _networks) = desc.unwrap(); assert_eq!(desc.is_witness(), is_witness); assert_eq!(desc.is_fixed(), is_fixed); @@ -436,11 +489,11 @@ mod test { } else { desc.derive(ChildNumber::from_normal_idx(index).unwrap()) }; - let address = child_desc.address(Regtest); - if address.is_some() { - assert_eq!(address.unwrap().to_string(), *expected.get(i).unwrap()); + let address = child_desc.address(Regtest, deriv_ctx); + if let Some(address) = address { + assert_eq!(address.to_string(), *expected.get(i).unwrap()); } else { - let script = child_desc.script_pubkey(); + let script = child_desc.script_pubkey(deriv_ctx); assert_eq!(script.to_hex().as_str(), *expected.get(i).unwrap()); } } @@ -628,6 +681,60 @@ mod test { ); } + #[test] + fn test_dsl_sortedmulti() { + let key_1 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let path_1 = bip32::DerivationPath::from_str("m/0").unwrap(); + + let key_2 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF").unwrap(); + let path_2 = bip32::DerivationPath::from_str("m/1").unwrap(); + + let desc_key1 = (key_1, path_1); + let desc_key2 = (key_2, path_2); + + check( + descriptor!(sh(sortedmulti 1, desc_key1.clone(), desc_key2.clone())), + false, + false, + &[ + "2MsxzPEJDBzpGffJXPaDpfXZAUNnZhaMh2N", + "2My3x3DLPK3UbGWGpxrXr1RnbD8MNC4FpgS", + "2NByEuiQT7YLqHCTNxL5KwYjvtuCYcXNBSC", + "2N1TGbP81kj2VUKTSWgrwxoMfuWjvfUdyu7", + "2N3Bomq2fpAcLRNfZnD3bCWK9quan28CxCR", + "2N9nrZaEzEFDqEAU9RPvDnXGT6AVwBDKAQb", + ], + ); + + check( + descriptor!(sh(wsh(sortedmulti 1, desc_key1.clone(), desc_key2.clone()))), + true, + false, + &[ + "2NCogc5YyM4N6ruv1hUa7WLMW1BPeCK7N9B", + "2N6mkSAKi1V2oaBXby7XHdvBMKEDRQcFpNe", + "2NFmTSttm9v6bXeoWaBvpMcgfPQcZhNn3Eh", + "2Mvib87RBPUHXNEpX5S5Kv1qqrhBfgBGsJM", + "2MtMv5mcK2EjcLsH8Txpx2JxLLzHr4ttczL", + "2MsWCB56rb4T6yPv8QudZGHERTwNgesE4f6", + ], + ); + + check( + descriptor!(wsh(sortedmulti_vec 1, vec![desc_key1, desc_key2])), + true, + false, + &[ + "bcrt1qcvq0lg8q7a47ytrd7zk5y7uls7mulrenjgvflwylpppgwf8029es4vhpnj", + "bcrt1q80yn8sdt6l7pjvkz25lglyaqctlmsq9ugk80rmxt8yu0npdsj97sc7l4de", + "bcrt1qrvf6024v9s50qhffe3t2fr2q9ckdhx2g6jz32chm2pp24ymgtr5qfrdmct", + "bcrt1q6srfmra0ynypym35c7jvsxt2u4yrugeajq95kg2ps7lk6h2gaunsq9lzxn", + "bcrt1qhl8rrzzcdpu7tcup3lcg7tge52sqvwy5fcv4k78v6kxtwmqf3v6qpvyjza", + "bcrt1ql2elz9mhm9ll27ddpewhxs732xyl2fk2kpkqz9gdyh33wgcun4vstrd49k", + ], + ); + } + // - verify the valid_networks returned is correctly computed based on the keys present in the descriptor #[test] fn test_valid_networks() { @@ -649,6 +756,8 @@ mod test { // - verify the key_maps are correctly merged together #[test] fn test_key_maps_merged() { + let secp = Secp256k1::new(); + let xprv1 = bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let path1 = bip32::DerivationPath::from_str("m/0").unwrap(); let desc_key1 = (xprv1, path1.clone()).to_descriptor_key().unwrap(); @@ -672,9 +781,9 @@ mod test { let desc_key3: DescriptorKey = (xprv3, path3.clone()).to_descriptor_key().unwrap(); - let (key1, _key_map, _valid_networks) = desc_key1.extract().unwrap(); - let (key2, _key_map, _valid_networks) = desc_key2.extract().unwrap(); - let (key3, _key_map, _valid_networks) = desc_key3.extract().unwrap(); + let (key1, _key_map, _valid_networks) = desc_key1.extract(&secp).unwrap(); + let (key2, _key_map, _valid_networks) = desc_key2.extract(&secp).unwrap(); + let (key3, _key_map, _valid_networks) = desc_key3.extract(&secp).unwrap(); assert_eq!(key_map.get(&key1).unwrap().to_string(), "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/0/*"); assert_eq!(key_map.get(&key2).unwrap().to_string(), "tprv8ZgxMBicQKsPegBHHnq7YEgM815dG24M2Jk5RVqipgDxF1HJ1tsnT815X5Fd5FRfMVUs8NZs9XCb6y9an8hRPThnhfwfXJ36intaekySHGF/2147483647'/0/*"); assert_eq!(key_map.get(&key3).unwrap().to_string(), "tprv8ZgxMBicQKsPdZXrcHNLf5JAJWFAoJ2TrstMRdSKtEggz6PddbuSkvHKM9oKJyFgZV1B7rw8oChspxyYbtmEXYyg1AjfWbL3ho3XHDpHRZf/10/20/30/40/*"); diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 430b3877..b87124bd 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -53,6 +53,7 @@ use self::error::Error; pub use self::policy::Policy; use crate::keys::{KeyError, ToDescriptorKey, ValidNetworks}; use crate::wallet::signer::SignersContainer; +use crate::wallet::utils::{descriptor_to_pk_ctx, SecpCtx}; /// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`] pub type ExtendedDescriptor = Descriptor; @@ -92,7 +93,7 @@ impl ToWalletDescriptor for &str { self }; - ExtendedDescriptor::parse_secret(descriptor)?.to_wallet_descriptor(network) + ExtendedDescriptor::parse_descriptor(descriptor)?.to_wallet_descriptor(network) } } @@ -121,15 +122,17 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) { ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> { use crate::keys::DescriptorKey; + let secp = Secp256k1::new(); + let check_key = |pk: &DescriptorPublicKey| { let (pk, _, networks) = if self.0.is_witness() { let desciptor_key: DescriptorKey = pk.clone().to_descriptor_key()?; - desciptor_key.extract()? + desciptor_key.extract(&secp)? } else { let desciptor_key: DescriptorKey = pk.clone().to_descriptor_key()?; - desciptor_key.extract()? + desciptor_key.extract(&secp)? }; if networks.contains(&network) { @@ -185,12 +188,16 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap, ValidNetworks) { /// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`] pub trait ExtractPolicy { - fn extract_policy(&self, signers: Arc) -> Result, Error>; + fn extract_policy( + &self, + signers: Arc, + secp: &SecpCtx, + ) -> Result, Error>; } pub(crate) trait XKeyUtils { fn full_path(&self, append: &[ChildNumber]) -> DerivationPath; - fn root_fingerprint(&self) -> Fingerprint; + fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint; } impl XKeyUtils for DescriptorXKey { @@ -215,46 +222,55 @@ impl XKeyUtils for DescriptorXKey { } } - fn root_fingerprint(&self) -> Fingerprint { + fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint { match self.origin { Some((fingerprint, _)) => fingerprint, - None => self.xkey.xkey_fingerprint(), + None => self.xkey.xkey_fingerprint(secp), } } } pub(crate) trait DescriptorMeta: Sized { fn is_witness(&self) -> bool; - fn get_hd_keypaths(&self, index: u32) -> Result; + fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result; fn is_fixed(&self) -> bool; - fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths) -> Option; - fn derive_from_psbt_input(&self, psbt_input: &psbt::Input, utxo: Option) - -> Option; + fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option; + fn derive_from_psbt_input( + &self, + psbt_input: &psbt::Input, + utxo: Option, + secp: &SecpCtx, + ) -> Option; } pub(crate) trait DescriptorScripts { - fn psbt_redeem_script(&self) -> Option