From 3d9d6fee0756032359822c667c01f34e1e565a6a Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Tue, 2 Feb 2021 20:06:40 -0500 Subject: [PATCH] Update bitcoin, miniscript, electrum-client --- Cargo.toml | 10 +- examples/compiler.rs | 21 +- examples/parse_descriptor.rs | 60 ----- src/descriptor/derived.rs | 163 ++++++++++++ src/descriptor/dsl.rs | 134 +++++----- src/descriptor/mod.rs | 467 +++++++++++++++-------------------- src/descriptor/policy.rs | 80 +++--- src/descriptor/template.rs | 32 +-- src/keys/bip39.rs | 4 +- src/keys/mod.rs | 34 +-- src/wallet/export.rs | 33 ++- src/wallet/mod.rs | 127 +++++----- src/wallet/signer.rs | 8 +- src/wallet/tx_builder.rs | 5 +- src/wallet/utils.rs | 12 +- testutils/Cargo.toml | 7 +- testutils/src/lib.rs | 85 +++++-- 17 files changed, 701 insertions(+), 581 deletions(-) delete mode 100644 examples/parse_descriptor.rs create mode 100644 src/descriptor/derived.rs diff --git a/Cargo.toml b/Cargo.toml index 12b0fd1a..814e2c21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,15 +14,15 @@ license = "MIT" [dependencies] bdk-macros = { path = "./macros" } log = "^0.4" -miniscript = "4.0" -bitcoin = { version = "^0.25.2", features = ["use-serde"] } +miniscript = "5.1" +bitcoin = { version = "^0.26", features = ["use-serde"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } rand = "^0.7" # Optional dependencies sled = { version = "0.34", optional = true } -electrum-client = { version = "0.5.0-beta.1", optional = true } +electrum-client = { version = "0.6", optional = true } reqwest = { version = "0.11", optional = true, features = ["json"] } futures = { version = "0.3", optional = true } async-trait = { version = "0.1", optional = true } @@ -68,8 +68,8 @@ env_logger = "0.7" base64 = "^0.11" clap = "2.33" -[[example]] -name = "parse_descriptor" +# [[example]] +# name = "parse_descriptor" [[example]] name = "address_validator" diff --git a/examples/compiler.rs b/examples/compiler.rs index 0f1ac481..eb2e52d5 100644 --- a/examples/compiler.rs +++ b/examples/compiler.rs @@ -29,6 +29,7 @@ extern crate log; extern crate miniscript; extern crate serde_json; +use std::error::Error; use std::str::FromStr; use log::info; @@ -42,7 +43,7 @@ use miniscript::Descriptor; use bdk::database::memory::MemoryDatabase; use bdk::{KeychainKind, Wallet}; -fn main() { +fn main() -> Result<(), Box> { env_logger::init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), ); @@ -81,12 +82,12 @@ fn main() { let policy_str = matches.value_of("POLICY").unwrap(); info!("Compiling policy: {}", policy_str); - let policy = Concrete::::from_str(&policy_str).unwrap(); + let policy = Concrete::::from_str(&policy_str)?; let descriptor = match matches.value_of("TYPE").unwrap() { - "sh" => Descriptor::Sh(policy.compile().unwrap()), - "wsh" => Descriptor::Wsh(policy.compile().unwrap()), - "sh-wsh" => Descriptor::ShWsh(policy.compile().unwrap()), + "sh" => Descriptor::new_sh(policy.compile()?)?, + "wsh" => Descriptor::new_wsh(policy.compile()?)?, + "sh-wsh" => Descriptor::new_sh_wsh(policy.compile()?)?, _ => panic!("Invalid type"), }; @@ -98,15 +99,17 @@ fn main() { Some("regtest") => Network::Regtest, Some("testnet") | _ => Network::Testnet, }; - let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database).unwrap(); + let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?; - info!("... First address: {}", wallet.get_new_address().unwrap()); + info!("... First address: {}", wallet.get_new_address()?); if matches.is_present("parsed_policy") { - let spending_policy = wallet.policies(KeychainKind::External).unwrap(); + let spending_policy = wallet.policies(KeychainKind::External)?; info!( "... Spending policy:\n{}", - serde_json::to_string_pretty(&spending_policy).unwrap() + serde_json::to_string_pretty(&spending_policy)? ); } + + Ok(()) } diff --git a/examples/parse_descriptor.rs b/examples/parse_descriptor.rs deleted file mode 100644 index c961d4da..00000000 --- a/examples/parse_descriptor.rs +++ /dev/null @@ -1,60 +0,0 @@ -// Magical Bitcoin Library -// Written in 2020 by -// Alekos Filini -// -// Copyright (c) 2020 Magical Bitcoin -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -extern crate bdk; -extern crate serde_json; - -use std::sync::Arc; - -use bdk::bitcoin::secp256k1::Secp256k1; -use bdk::bitcoin::util::bip32::ChildNumber; -use bdk::bitcoin::*; -use bdk::descriptor::*; -use bdk::miniscript::DescriptorPublicKeyCtx; - -fn main() { - let secp = Secp256k1::new(); - - let desc = "wsh(or_d(\ - multi(\ - 2,[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*,tprv8ZgxMBicQKsPduL5QnGihpprdHyypMGi4DhimjtzYemu7se5YQNcZfAPLqXRuGHb5ZX2eTQj62oNqMnyxJ7B7wz54Uzswqw8fFqMVdcmVF7/1/*\ - ),\ - and_v(vc:pk_h(cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy),older(1000))\ - ))"; - - let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(desc).unwrap(); - println!("{:?}", extended_desc); - - let deriv_ctx = DescriptorPublicKeyCtx::new(&secp, ChildNumber::from_normal_idx(42).unwrap()); - - let signers = Arc::new(key_map.into()); - let policy = extended_desc.extract_policy(&signers, &secp).unwrap(); - println!("policy: {}", serde_json::to_string(&policy).unwrap()); - - let addr = extended_desc.address(Network::Testnet, deriv_ctx).unwrap(); - println!("{}", addr); - - let script = extended_desc.witness_script(deriv_ctx); - println!("{:?}", script); -} diff --git a/src/descriptor/derived.rs b/src/descriptor/derived.rs new file mode 100644 index 00000000..bf88f0f2 --- /dev/null +++ b/src/descriptor/derived.rs @@ -0,0 +1,163 @@ +// Magical Bitcoin Library +// Written in 2020 by +// Alekos Filini +// +// Copyright (c) 2020 Magical Bitcoin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Derived descriptor keys + +use std::cmp::Ordering; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::ops::Deref; + +use bitcoin::hashes::hash160; +use bitcoin::PublicKey; + +pub use miniscript::{ + descriptor::KeyMap, descriptor::Wildcard, Descriptor, DescriptorPublicKey, Legacy, Miniscript, + ScriptContext, Segwitv0, +}; +use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk}; + +use crate::wallet::utils::SecpCtx; + +/// Extended [`DescriptorPublicKey`] that has been derived +/// +/// Derived keys are guaranteed to never contain wildcards of any kind +#[derive(Debug, Clone)] +pub struct DerivedDescriptorKey<'s>(DescriptorPublicKey, &'s SecpCtx); + +impl<'s> DerivedDescriptorKey<'s> { + /// Construct a new derived key + /// + /// Panics if the key is wildcard + pub fn new(key: DescriptorPublicKey, secp: &'s SecpCtx) -> DerivedDescriptorKey<'s> { + if let DescriptorPublicKey::XPub(xpub) = &key { + assert!(xpub.wildcard == Wildcard::None) + } + + DerivedDescriptorKey(key, secp) + } +} + +impl<'s> Deref for DerivedDescriptorKey<'s> { + type Target = DescriptorPublicKey; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'s> PartialEq for DerivedDescriptorKey<'s> { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl<'s> Eq for DerivedDescriptorKey<'s> {} + +impl<'s> PartialOrd for DerivedDescriptorKey<'s> { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl<'s> Ord for DerivedDescriptorKey<'s> { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl<'s> fmt::Display for DerivedDescriptorKey<'s> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'s> Hash for DerivedDescriptorKey<'s> { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl<'s> MiniscriptKey for DerivedDescriptorKey<'s> { + type Hash = Self; + + fn to_pubkeyhash(&self) -> Self::Hash { + DerivedDescriptorKey(self.0.to_pubkeyhash(), self.1) + } + + fn is_uncompressed(&self) -> bool { + self.0.is_uncompressed() + } + fn serialized_len(&self) -> usize { + self.0.serialized_len() + } +} + +impl<'s> ToPublicKey for DerivedDescriptorKey<'s> { + fn to_public_key(&self) -> PublicKey { + match &self.0 { + DescriptorPublicKey::SinglePub(ref spub) => spub.key.to_public_key(), + DescriptorPublicKey::XPub(ref xpub) => { + xpub.xkey + .derive_pub(self.1, &xpub.derivation_path) + .expect("Shouldn't fail, only normal derivations") + .public_key + } + } + } + + fn hash_to_hash160(hash: &Self::Hash) -> hash160::Hash { + hash.to_public_key().to_pubkeyhash() + } +} + +pub(crate) trait AsDerived { + // Derive a descriptor and transform all of its keys to `DerivedDescriptorKey` + fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx) + -> Descriptor>; + + // Transform the keys into `DerivedDescriptorKey`. + // + // Panics if the descriptor is not "fixed", i.e. if it's derivable + fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor>; +} + +impl AsDerived for Descriptor { + fn as_derived<'s>( + &self, + index: u32, + secp: &'s SecpCtx, + ) -> Descriptor> { + self.derive(index).translate_pk_infallible( + |key| DerivedDescriptorKey::new(key.clone(), secp), + |key| DerivedDescriptorKey::new(key.clone(), secp), + ) + } + + fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor> { + assert!(!self.is_deriveable()); + + self.as_derived(0, secp) + } +} diff --git a/src/descriptor/dsl.rs b/src/descriptor/dsl.rs index caf2eaa0..41c4ec56 100644 --- a/src/descriptor/dsl.rs +++ b/src/descriptor/dsl.rs @@ -28,32 +28,53 @@ #[macro_export] macro_rules! impl_top_level_sh { // disallow `sortedmulti` in `bare()` - ( Bare, Bare, sortedmulti $( $inner:tt )* ) => { + ( Bare, new, new, Legacy, sortedmulti $( $inner:tt )* ) => { compile_error!("`bare()` descriptors can't contain any `sortedmulti()` operands"); }; - ( Bare, Bare, sortedmulti_vec $( $inner:tt )* ) => { + ( Bare, new, new, Legacy, 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))) - }; + ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, sortedmulti $( $inner:tt )* ) => {{ + use std::marker::PhantomData; + + use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey}; + use $crate::miniscript::$ctx; + + let build_desc = |k, pks| { + Ok((Descriptor::::$inner_struct($inner_struct::$sortedmulti_constructor(k, pks)?), PhantomData::<$ctx>)) + }; + + $crate::impl_sortedmulti!(build_desc, sortedmulti $( $inner )*) + }}; + ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, sortedmulti_vec $( $inner:tt )* ) => {{ + use std::marker::PhantomData; + + use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey}; + use $crate::miniscript::$ctx; + + let build_desc = |k, pks| { + Ok((Descriptor::::$inner_struct($inner_struct::$sortedmulti_constructor(k, pks)?), PhantomData::<$ctx>)) + }; + + $crate::impl_sortedmulti!(build_desc, sortedmulti_vec $( $inner )*) + }}; + + ( $inner_struct:ident, $constructor:ident, $sortedmulti_constructor:ident, $ctx:ident, $( $minisc:tt )* ) => {{ + use $crate::miniscript::descriptor::{$inner_struct, Descriptor, DescriptorPublicKey}; - ( $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)) - }; + .and_then(|(minisc, keymap, networks)| Ok(($inner_struct::$constructor(minisc)?, keymap, networks))) + .and_then(|(inner, key_map, valid_networks)| Ok((Descriptor::::$inner_struct(inner), key_map, valid_networks))) + }}; } #[doc(hidden)] #[macro_export] macro_rules! impl_top_level_pk { - ( $descriptor_variant:ident, $ctx:ty, $key:expr ) => {{ + ( $inner_type:ident, $ctx:ty, $key:expr ) => {{ + use $crate::miniscript::descriptor::$inner_type; + #[allow(unused_imports)] use $crate::keys::{DescriptorKey, ToDescriptorKey}; let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); @@ -61,15 +82,7 @@ macro_rules! impl_top_level_pk { $key.to_descriptor_key() .and_then(|key: DescriptorKey<$ctx>| key.extract(&secp)) .map_err($crate::descriptor::DescriptorError::Key) - .map(|(pk, key_map, valid_networks)| { - ( - $crate::miniscript::Descriptor::< - $crate::miniscript::descriptor::DescriptorPublicKey, - >::$descriptor_variant(pk), - key_map, - valid_networks, - ) - }) + .map(|(pk, key_map, valid_networks)| ($inner_type::new(pk), key_map, valid_networks)) }}; } @@ -207,11 +220,11 @@ macro_rules! impl_node_opcode_three { #[doc(hidden)] #[macro_export] macro_rules! impl_sortedmulti { - ( sortedmulti_vec ( $thresh:expr, $keys:expr ) ) => ({ + ( $build_desc:expr, sortedmulti_vec ( $thresh:expr, $keys:expr ) ) => ({ let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); - $crate::keys::make_sortedmulti_inner($thresh, $keys, &secp) + $crate::keys::make_sortedmulti($thresh, $keys, $build_desc, &secp) }); - ( sortedmulti ( $thresh:expr $(, $key:expr )+ ) ) => ({ + ( $build_desc:expr, sortedmulti ( $thresh:expr $(, $key:expr )+ ) ) => ({ use $crate::keys::ToDescriptorKey; let secp = $crate::bitcoin::secp256k1::Secp256k1::new(); @@ -222,7 +235,7 @@ macro_rules! impl_sortedmulti { keys.into_iter().collect::, _>>() .map_err($crate::descriptor::DescriptorError::Key) - .and_then(|keys| $crate::keys::make_sortedmulti_inner($thresh, keys, &secp)) + .and_then(|keys| $crate::keys::make_sortedmulti($thresh, keys, $build_desc, &secp)) }); } @@ -399,34 +412,46 @@ macro_rules! apply_modifier { #[macro_export] macro_rules! descriptor { ( bare ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Bare, Bare, $( $minisc )*) + $crate::impl_top_level_sh!(Bare, new, new, Legacy, $( $minisc )*) }); ( sh ( wsh ( $( $minisc:tt )* ) ) ) => ({ $crate::descriptor!(shwsh ($( $minisc )*)) }); ( shwsh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(ShWsh, ShWshSortedMulti, $( $minisc )*) + $crate::impl_top_level_sh!(Sh, new_wsh, new_wsh_sortedmulti, Segwitv0, $( $minisc )*) }); ( pk ( $key:expr ) ) => ({ - $crate::impl_top_level_pk!(Pk, $crate::miniscript::Legacy, $key) + // `pk()` is actually implemented as `bare(pk())` + $crate::descriptor!( bare ( pk ( $key ) ) ) }); ( pkh ( $key:expr ) ) => ({ - $crate::impl_top_level_pk!(Pkh,$crate::miniscript::Legacy, $key) + use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey}; + + $crate::impl_top_level_pk!(Pkh, $crate::miniscript::Legacy, $key) + .map(|(a, b, c)| (Descriptor::::Pkh(a), b, c)) }); ( wpkh ( $key:expr ) ) => ({ + use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey}; + $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key) + .and_then(|(a, b, c)| Ok((a?, b, c))) + .map(|(a, b, c)| (Descriptor::::Wpkh(a), b, c)) }); ( sh ( wpkh ( $key:expr ) ) ) => ({ $crate::descriptor!(shwpkh ( $key )) }); ( shwpkh ( $key:expr ) ) => ({ - $crate::impl_top_level_pk!(ShWpkh, $crate::miniscript::Segwitv0, $key) + use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, Sh}; + + $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key) + .and_then(|(a, b, c)| Ok((a?, b, c))) + .and_then(|(a, b, c)| Ok((Descriptor::::Sh(Sh::new_wpkh(a.into_inner())?), b, c))) }); ( sh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Sh, ShSortedMulti, $( $minisc )*) + $crate::impl_top_level_sh!(Sh, new, new_sortedmulti, Legacy, $( $minisc )*) }); ( wsh ( $( $minisc:tt )* ) ) => ({ - $crate::impl_top_level_sh!(Wsh, WshSortedMulti, $( $minisc )*) + $crate::impl_top_level_sh!(Wsh, new, new_sortedmulti, Segwitv0, $( $minisc )*) }); } @@ -654,7 +679,7 @@ macro_rules! fragment { mod test { use bitcoin::hashes::hex::ToHex; use bitcoin::secp256k1::Secp256k1; - use miniscript::descriptor::{DescriptorPublicKey, DescriptorPublicKeyCtx, KeyMap}; + use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap}; use miniscript::{Descriptor, Legacy, Segwitv0}; use std::str::FromStr; @@ -663,9 +688,10 @@ mod test { use crate::keys::{DescriptorKey, ToDescriptorKey, ValidNetworks}; use bitcoin::network::constants::Network::{Bitcoin, Regtest, Testnet}; use bitcoin::util::bip32; - use bitcoin::util::bip32::ChildNumber; use bitcoin::PrivateKey; + use crate::descriptor::derived::AsDerived; + // test the descriptor!() macro // verify descriptor generates expected script(s) (if bare or pk) or address(es) @@ -676,23 +702,22 @@ mod test { 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); + assert_eq!(!desc.is_deriveable(), is_fixed); for i in 0..expected.len() { let index = i as u32; - let child_desc = if desc.is_fixed() { - desc.clone() + let child_desc = if !desc.is_deriveable() { + desc.as_derived_fixed(&secp) } else { - desc.derive(ChildNumber::from_normal_idx(index).unwrap()) + desc.as_derived(index, &secp) }; - let address = child_desc.address(Regtest, deriv_ctx); - if let Some(address) = address { + let address = child_desc.address(Regtest); + if let Ok(address) = address { assert_eq!(address.to_string(), *expected.get(i).unwrap()); } else { - let script = child_desc.script_pubkey(deriv_ctx); + let script = child_desc.script_pubkey(); assert_eq!(script.to_hex().as_str(), *expected.get(i).unwrap()); } } @@ -1001,7 +1026,7 @@ mod test { let desc_key: DescriptorKey = (xprv, path.clone()).to_descriptor_key().unwrap(); let (desc, _key_map, _valid_networks) = descriptor!(pkh(desc_key)).unwrap(); - assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)"); + assert_eq!(desc.to_string(), "pkh(tpubD6NzVbkrYhZ4WR7a4vY1VT3khMJMeAxVsfq9TBJyJWrNk247zCJtV7AWf6UJP7rAVsn8NNKdJi3gFyKPTmWZS9iukb91xbn2HbFSMQm2igY/0/*)#yrnz9pp2"); // as expected this does not compile due to invalid context //let desc_key:DescriptorKey = (xprv, path.clone()).to_descriptor_key().unwrap(); @@ -1015,17 +1040,16 @@ mod test { let (descriptor, _, _) = descriptor!(wsh(thresh(2,d:v:older(1),s:pk(private_key),s:pk(private_key)))).unwrap(); - assert_eq!(descriptor.to_string(), "wsh(thresh(2,dv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))") + assert_eq!(descriptor.to_string(), "wsh(thresh(2,dv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))#cfdcqs3s") } - // TODO: uncomment once https://github.com/rust-bitcoin/rust-miniscript/pull/221 is released - // - // #[test] - // #[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")] - // fn test_dsl_miniscript_checks() { - // let mut uncompressed_pk = PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap(); - // uncompressed_pk.compressed = false; + #[test] + #[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")] + fn test_dsl_miniscript_checks() { + let mut uncompressed_pk = + PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap(); + uncompressed_pk.compressed = false; - // descriptor!(wsh(v:pk(uncompressed_pk))).unwrap(); - // } + descriptor!(wsh(v: pk(uncompressed_pk))).unwrap(); + } } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index cffb1cb4..19322029 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -28,20 +28,20 @@ //! from [`miniscript`]. use std::collections::{BTreeMap, HashMap}; -use std::fmt; +use std::ops::Deref; -use bitcoin::secp256k1::Secp256k1; -use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource}; +use bitcoin::util::bip32::{ + ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint, KeySource, +}; use bitcoin::util::psbt; use bitcoin::{Network, PublicKey, Script, TxOut}; -use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey}; -pub use miniscript::{ - descriptor::KeyMap, Descriptor, Legacy, Miniscript, MiniscriptKey, ScriptContext, Segwitv0, - Terminal, ToPublicKey, -}; +use miniscript::descriptor::{DescriptorPublicKey, DescriptorType, DescriptorXKey, Wildcard}; +pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0}; +use miniscript::{DescriptorTrait, ForEachKey, TranslatePk}; pub mod checksum; +pub(crate) mod derived; #[doc(hidden)] pub mod dsl; pub mod error; @@ -49,16 +49,21 @@ pub mod policy; pub mod template; pub use self::checksum::get_checksum; +use self::derived::AsDerived; +pub use self::derived::DerivedDescriptorKey; pub use self::error::Error as DescriptorError; pub use self::policy::Policy; use self::template::DescriptorTemplateOut; use crate::keys::{KeyError, ToDescriptorKey}; use crate::wallet::signer::SignersContainer; -use crate::wallet::utils::{descriptor_to_pk_ctx, SecpCtx}; +use crate::wallet::utils::SecpCtx; /// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`] pub type ExtendedDescriptor = Descriptor; +/// Alias for a [`Descriptor`] that contains extended **derived** keys +pub type DerivedDescriptor<'s> = Descriptor>; + /// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or /// [`psbt::Output`] /// @@ -71,6 +76,7 @@ pub trait ToWalletDescriptor { /// Convert to wallet descriptor fn to_wallet_descriptor( self, + secp: &SecpCtx, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>; } @@ -78,6 +84,7 @@ pub trait ToWalletDescriptor { impl ToWalletDescriptor for &str { fn to_wallet_descriptor( self, + secp: &SecpCtx, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { let descriptor = if self.contains('#') { @@ -95,37 +102,38 @@ impl ToWalletDescriptor for &str { self }; - ExtendedDescriptor::parse_descriptor(descriptor)?.to_wallet_descriptor(network) + ExtendedDescriptor::parse_descriptor(secp, descriptor)?.to_wallet_descriptor(secp, network) } } impl ToWalletDescriptor for &String { fn to_wallet_descriptor( self, + secp: &SecpCtx, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { - self.as_str().to_wallet_descriptor(network) + self.as_str().to_wallet_descriptor(secp, network) } } impl ToWalletDescriptor for ExtendedDescriptor { fn to_wallet_descriptor( self, + secp: &SecpCtx, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { - (self, KeyMap::default()).to_wallet_descriptor(network) + (self, KeyMap::default()).to_wallet_descriptor(secp, network) } } impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) { fn to_wallet_descriptor( self, + secp: &SecpCtx, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { 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 = @@ -154,6 +162,7 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) { impl ToWalletDescriptor for DescriptorTemplateOut { fn to_wallet_descriptor( self, + _secp: &SecpCtx, network: Network, ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { let valid_networks = &self.2; @@ -219,7 +228,12 @@ pub(crate) trait XKeyUtils { fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint; } -impl XKeyUtils for DescriptorXKey { +// FIXME: `InnerXKey` was made private in rust-miniscript, so we have to implement this manually on +// both `ExtendedPubKey` and `ExtendedPrivKey`. +// +// Revert back to using the trait once https://github.com/rust-bitcoin/rust-miniscript/pull/230 is +// released +impl XKeyUtils for DescriptorXKey { fn full_path(&self, append: &[ChildNumber]) -> DerivationPath { let full_path = match self.origin { Some((_, ref path)) => path @@ -230,7 +244,36 @@ impl XKeyUtils for DescriptorXKey { None => self.derivation_path.clone(), }; - if self.is_wildcard { + if self.wildcard != Wildcard::None { + full_path + .into_iter() + .chain(append.iter()) + .cloned() + .collect() + } else { + full_path + } + } + + fn root_fingerprint(&self, _: &SecpCtx) -> Fingerprint { + match self.origin { + Some((fingerprint, _)) => fingerprint, + None => self.xkey.fingerprint(), + } + } +} +impl XKeyUtils for DescriptorXKey { + fn full_path(&self, append: &[ChildNumber]) -> DerivationPath { + let full_path = match self.origin { + Some((_, ref path)) => path + .into_iter() + .chain(self.derivation_path.into_iter()) + .cloned() + .collect(), + None => self.derivation_path.clone(), + }; + + if self.wildcard != Wildcard::None { full_path .into_iter() .chain(append.iter()) @@ -244,195 +287,111 @@ impl XKeyUtils for DescriptorXKey { fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint { match self.origin { Some((fingerprint, _)) => fingerprint, - None => self.xkey.xkey_fingerprint(secp), + None => self.xkey.fingerprint(secp), } } } -pub(crate) trait DescriptorMeta: Sized { +pub(crate) trait DerivedDescriptorMeta { + fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result; +} + +pub(crate) trait DescriptorMeta { fn is_witness(&self) -> bool; - fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result; fn get_extended_keys(&self) -> Result>, DescriptorError>; - fn is_fixed(&self) -> bool; - fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option; - fn derive_from_psbt_input( + fn derive_from_hd_keypaths<'s>( + &self, + hd_keypaths: &HDKeyPaths, + secp: &'s SecpCtx, + ) -> Option>; + fn derive_from_psbt_input<'s>( &self, psbt_input: &psbt::Input, utxo: Option, - secp: &SecpCtx, - ) -> Option; + secp: &'s SecpCtx, + ) -> Option>; } pub(crate) trait DescriptorScripts { - fn psbt_redeem_script(&self, secp: &SecpCtx) -> Option