]> Untitled Git - bdk/commitdiff
Update bitcoin, miniscript, electrum-client
authorAlekos Filini <alekos.filini@gmail.com>
Wed, 3 Feb 2021 01:06:40 +0000 (20:06 -0500)
committerAlekos Filini <alekos.filini@gmail.com>
Fri, 5 Feb 2021 14:11:27 +0000 (09:11 -0500)
17 files changed:
Cargo.toml
examples/compiler.rs
examples/parse_descriptor.rs [deleted file]
src/descriptor/derived.rs [new file with mode: 0644]
src/descriptor/dsl.rs
src/descriptor/mod.rs
src/descriptor/policy.rs
src/descriptor/template.rs
src/keys/bip39.rs
src/keys/mod.rs
src/wallet/export.rs
src/wallet/mod.rs
src/wallet/signer.rs
src/wallet/tx_builder.rs
src/wallet/utils.rs
testutils/Cargo.toml
testutils/src/lib.rs

index 12b0fd1a8f4d9fb89dd83f10b525687350e5c857..814e2c21c66ebb94b696ecfc38ece8036c765ad0 100644 (file)
@@ -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"
 
index 0f1ac4810188c91ce19dca9cb64454edddc98e96..eb2e52d5fd819b6b223d54313f10ed6eb572fed5 100644 (file)
@@ -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<dyn Error>> {
     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::<String>::from_str(&policy_str).unwrap();
+    let policy = Concrete::<String>::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 (file)
index c961d4d..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-// Magical Bitcoin Library
-// Written in 2020 by
-//     Alekos Filini <alekos.filini@gmail.com>
-//
-// 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 (file)
index 0000000..bf88f0f
--- /dev/null
@@ -0,0 +1,163 @@
+// Magical Bitcoin Library
+// Written in 2020 by
+//     Alekos Filini <alekos.filini@gmail.com>
+//
+// 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<Ordering> {
+        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<H: Hasher>(&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<DerivedDescriptorKey<'s>>;
+
+    // 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<DerivedDescriptorKey<'s>>;
+}
+
+impl AsDerived for Descriptor<DescriptorPublicKey> {
+    fn as_derived<'s>(
+        &self,
+        index: u32,
+        secp: &'s SecpCtx,
+    ) -> Descriptor<DerivedDescriptorKey<'s>> {
+        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<DerivedDescriptorKey<'s>> {
+        assert!(!self.is_deriveable());
+
+        self.as_derived(0, secp)
+    }
+}
index caf2eaa0a8c00ed1a24bd0373ac9b02b6f029f89..41c4ec563af71768f05b4741869c8b584fc3a065 100644 (file)
 #[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::<DescriptorPublicKey>::$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::<DescriptorPublicKey>::$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::<DescriptorPublicKey>::$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::<Result<Vec<_>, _>>()
             .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::<DescriptorPublicKey>::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::<DescriptorPublicKey>::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::<DescriptorPublicKey>::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<Legacy> = (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<Segwitv0> = (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;
-
-    //     descriptor!(wsh(v:pk(uncompressed_pk))).unwrap();
-    // }
+    #[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();
+    }
 }
index cffb1cb476dd09c8d79f61a39427136bbce888fd..1932202992b6fbe740305b64cf9cf1aec7ce1bc2 100644 (file)
 //! 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<DescriptorPublicKey>;
 
+/// Alias for a [`Descriptor`] that contains extended **derived** keys
+pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
+
 /// 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<miniscript::Segwitv0> =
@@ -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<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
+// 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<ExtendedPubKey> {
     fn full_path(&self, append: &[ChildNumber]) -> DerivationPath {
         let full_path = match self.origin {
             Some((_, ref path)) => path
@@ -230,7 +244,36 @@ impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
             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<ExtendedPrivKey> {
+    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<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
     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<HDKeyPaths, DescriptorError>;
+}
+
+pub(crate) trait DescriptorMeta {
     fn is_witness(&self) -> bool;
-    fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError>;
     fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
-    fn is_fixed(&self) -> bool;
-    fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self>;
-    fn derive_from_psbt_input(
+    fn derive_from_hd_keypaths<'s>(
+        &self,
+        hd_keypaths: &HDKeyPaths,
+        secp: &'s SecpCtx,
+    ) -> Option<DerivedDescriptor<'s>>;
+    fn derive_from_psbt_input<'s>(
         &self,
         psbt_input: &psbt::Input,
         utxo: Option<TxOut>,
-        secp: &SecpCtx,
-    ) -> Option<Self>;
+        secp: &'s SecpCtx,
+    ) -> Option<DerivedDescriptor<'s>>;
 }
 
 pub(crate) trait DescriptorScripts {
-    fn psbt_redeem_script(&self, secp: &SecpCtx) -> Option<Script>;
-    fn psbt_witness_script(&self, secp: &SecpCtx) -> Option<Script>;
+    fn psbt_redeem_script(&self) -> Option<Script>;
+    fn psbt_witness_script(&self) -> Option<Script>;
 }
 
-impl DescriptorScripts for Descriptor<DescriptorPublicKey> {
-    fn psbt_redeem_script(&self, secp: &SecpCtx) -> Option<Script> {
-        let deriv_ctx = descriptor_to_pk_ctx(secp);
-
-        match self {
-            Descriptor::ShWpkh(_) => Some(self.witness_script(deriv_ctx)),
-            Descriptor::ShWsh(ref script) => Some(script.encode(deriv_ctx).to_v0_p2wsh()),
-            Descriptor::Sh(ref script) => Some(script.encode(deriv_ctx)),
-            Descriptor::Bare(ref script) => Some(script.encode(deriv_ctx)),
-            Descriptor::ShSortedMulti(ref keys) => Some(keys.encode(deriv_ctx)),
+impl<'s> DescriptorScripts for DerivedDescriptor<'s> {
+    fn psbt_redeem_script(&self) -> Option<Script> {
+        match self.desc_type() {
+            DescriptorType::ShWpkh => Some(self.explicit_script()),
+            DescriptorType::ShWsh => Some(self.explicit_script().to_v0_p2wsh()),
+            DescriptorType::Sh => Some(self.explicit_script()),
+            DescriptorType::Bare => Some(self.explicit_script()),
+            DescriptorType::ShSortedMulti => Some(self.explicit_script()),
             _ => None,
         }
     }
 
-    fn psbt_witness_script(&self, secp: &SecpCtx) -> Option<Script> {
-        let deriv_ctx = descriptor_to_pk_ctx(secp);
-
-        match self {
-            Descriptor::Wsh(ref script) => Some(script.encode(deriv_ctx)),
-            Descriptor::ShWsh(ref script) => Some(script.encode(deriv_ctx)),
-            Descriptor::WshSortedMulti(ref keys) | Descriptor::ShWshSortedMulti(ref keys) => {
-                Some(keys.encode(deriv_ctx))
+    fn psbt_witness_script(&self) -> Option<Script> {
+        match self.desc_type() {
+            DescriptorType::Wsh => Some(self.explicit_script()),
+            DescriptorType::ShWsh => Some(self.explicit_script()),
+            DescriptorType::WshSortedMulti | DescriptorType::ShWshSortedMulti => {
+                Some(self.explicit_script())
             }
             _ => None,
         }
     }
 }
 
-impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
+impl DescriptorMeta for ExtendedDescriptor {
     fn is_witness(&self) -> bool {
-        match self {
-            Descriptor::Bare(_)
-            | Descriptor::Pk(_)
-            | Descriptor::Pkh(_)
-            | Descriptor::Sh(_)
-            | Descriptor::ShSortedMulti(_) => false,
-            Descriptor::Wpkh(_)
-            | Descriptor::ShWpkh(_)
-            | Descriptor::Wsh(_)
-            | Descriptor::ShWsh(_)
-            | Descriptor::ShWshSortedMulti(_)
-            | Descriptor::WshSortedMulti(_) => true,
-        }
-    }
-
-    fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError> {
-        let translate_key = |key: &DescriptorPublicKey,
-                             index: u32,
-                             paths: &mut HDKeyPaths|
-         -> Result<DummyKey, DescriptorError> {
-            match key {
-                DescriptorPublicKey::SinglePub(_) => {}
-                DescriptorPublicKey::XPub(xpub) => {
-                    let derive_path = if xpub.is_wildcard {
-                        xpub.derivation_path
-                            .into_iter()
-                            .chain([ChildNumber::from_normal_idx(index)?].iter())
-                            .cloned()
-                            .collect()
-                    } else {
-                        xpub.derivation_path.clone()
-                    };
-                    let derived_pubkey = xpub
-                        .xkey
-                        .derive_pub(&Secp256k1::verification_only(), &derive_path)?;
-
-                    paths.insert(
-                        derived_pubkey.public_key,
-                        (
-                            xpub.root_fingerprint(secp),
-                            xpub.full_path(&[ChildNumber::from_normal_idx(index)?]),
-                        ),
-                    );
-                }
-            }
-
-            Ok(DummyKey::default())
-        };
-
-        let mut answer_pk = BTreeMap::new();
-        let mut answer_pkh = BTreeMap::new();
-
-        self.translate_pk(
-            |pk| translate_key(pk, index, &mut answer_pk),
-            |pkh| translate_key(pkh, index, &mut answer_pkh),
-        )?;
-
-        answer_pk.append(&mut answer_pkh);
-
-        Ok(answer_pk)
+        matches!(
+            self.desc_type(),
+            DescriptorType::Wpkh
+                | DescriptorType::ShWpkh
+                | DescriptorType::Wsh
+                | DescriptorType::ShWsh
+                | DescriptorType::ShWshSortedMulti
+                | DescriptorType::WshSortedMulti
+        )
     }
 
     fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
-        let get_key = |key: &DescriptorPublicKey,
-                       keys: &mut Vec<DescriptorXKey<ExtendedPubKey>>|
-         -> Result<DummyKey, DescriptorError> {
-            if let DescriptorPublicKey::XPub(xpub) = key {
-                keys.push(xpub.clone())
-            }
-
-            Ok(DummyKey::default())
-        };
-
-        let mut answer_pk = Vec::new();
-        let mut answer_pkh = Vec::new();
-
-        self.translate_pk(
-            |pk| get_key(pk, &mut answer_pk),
-            |pkh| get_key(pkh, &mut answer_pkh),
-        )?;
+        let mut answer = Vec::new();
 
-        answer_pk.append(&mut answer_pkh);
-
-        Ok(answer_pk)
-    }
-
-    fn is_fixed(&self) -> bool {
-        fn check_key(
-            key: &DescriptorPublicKey,
-            flag: &mut bool,
-        ) -> Result<DummyKey, DescriptorError> {
-            match key {
-                DescriptorPublicKey::SinglePub(_) => {}
-                DescriptorPublicKey::XPub(xpub) => {
-                    if xpub.is_wildcard {
-                        *flag = true;
-                    }
-                }
+        self.for_each_key(|pk| {
+            if let DescriptorPublicKey::XPub(xpub) = pk.as_key() {
+                answer.push(xpub.clone());
             }
 
-            Ok(DummyKey::default())
-        }
-
-        let mut found_wildcard_pk = false;
-        let mut found_wildcard_pkh = false;
+            true
+        });
 
-        self.translate_pk(
-            |pk| check_key(pk, &mut found_wildcard_pk),
-            |pkh| check_key(pkh, &mut found_wildcard_pkh),
-        )
-        .unwrap();
-
-        !found_wildcard_pk && !found_wildcard_pkh
+        Ok(answer)
     }
 
-    fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self> {
-        let try_key = |key: &DescriptorPublicKey,
-                       index: &HashMap<Fingerprint, DerivationPath>,
-                       found_path: &mut Option<ChildNumber>|
-         -> Result<DummyKey, DescriptorError> {
-            if found_path.is_some() {
+    fn derive_from_hd_keypaths<'s>(
+        &self,
+        hd_keypaths: &HDKeyPaths,
+        secp: &'s SecpCtx,
+    ) -> Option<DerivedDescriptor<'s>> {
+        let index: HashMap<_, _> = hd_keypaths.values().map(|(a, b)| (a, b)).collect();
+
+        let mut path_found = None;
+        self.for_each_key(|key| {
+            if path_found.is_some() {
                 // already found a matching path, we are done
-                return Ok(DummyKey::default());
+                return true;
             }
 
-            if let DescriptorPublicKey::XPub(xpub) = key {
+            if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
                 // Check if the key matches one entry in our `index`. If it does, `matches()` will
                 // return the "prefix" that matched, so we remove that prefix from the full path
                 // found in `index` and save it in `derive_path`. We expect this to be a derivation
-                // path of length 1 if the key `is_wildcard` and an empty path otherwise.
+                // path of length 1 if the key is `wildcard` and an empty path otherwise.
                 let root_fingerprint = xpub.root_fingerprint(secp);
                 let derivation_path: Option<Vec<ChildNumber>> = index
                     .get_key_value(&root_fingerprint)
                     .and_then(|(fingerprint, path)| {
-                        xpub.matches(&(*fingerprint, path.clone()), secp)
+                        xpub.matches(&(**fingerprint, (*path).clone()), secp)
                     })
                     .map(|prefix| {
                         index
@@ -445,128 +404,90 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
                     });
 
                 match derivation_path {
-                    Some(path) if xpub.is_wildcard && path.len() == 1 => {
-                        *found_path = Some(path[0])
+                    Some(path) if xpub.wildcard != Wildcard::None && path.len() == 1 => {
+                        // Ignore hardened wildcards
+                        if let ChildNumber::Normal { index } = path[0] {
+                            path_found = Some(index)
+                        }
                     }
-                    Some(path) if !xpub.is_wildcard && path.is_empty() => {
-                        *found_path = Some(ChildNumber::Normal { index: 0 })
+                    Some(path) if xpub.wildcard == Wildcard::None && path.is_empty() => {
+                        path_found = Some(0)
                     }
-                    Some(_) => return Err(DescriptorError::InvalidHDKeyPath),
                     _ => {}
                 }
             }
 
-            Ok(DummyKey::default())
-        };
-
-        let index: HashMap<_, _> = hd_keypaths.values().cloned().collect();
-
-        let mut found_path_pk = None;
-        let mut found_path_pkh = None;
-
-        if self
-            .translate_pk(
-                |pk| try_key(pk, &index, &mut found_path_pk),
-                |pkh| try_key(pkh, &index, &mut found_path_pkh),
-            )
-            .is_err()
-        {
-            return None;
-        }
-
-        // if we have found a path for both `found_path_pk` and `found_path_pkh` but they are
-        // different we consider this an error and return None. we only return a path either if
-        // they are equal or if only one of them is Some(_)
-        let merged_path = match (found_path_pk, found_path_pkh) {
-            (Some(a), Some(b)) if a != b => return None,
-            (a, b) => a.or(b),
-        };
+            true
+        });
 
-        merged_path.map(|path| self.derive(path))
+        path_found.map(|path| self.as_derived(path, secp))
     }
 
-    fn derive_from_psbt_input(
+    fn derive_from_psbt_input<'s>(
         &self,
         psbt_input: &psbt::Input,
         utxo: Option<TxOut>,
-        secp: &SecpCtx,
-    ) -> Option<Self> {
-        if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.hd_keypaths, secp) {
+        secp: &'s SecpCtx,
+    ) -> Option<DerivedDescriptor<'s>> {
+        if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.bip32_derivation, secp) {
             return Some(derived);
-        } else if !self.is_fixed() {
-            // If the descriptor is not fixed we can't brute-force the derivation address, so just
-            // exit here
+        }
+        if self.is_deriveable() {
+            // We can't try to bruteforce the derivation index, exit here
             return None;
         }
 
-        let deriv_ctx = descriptor_to_pk_ctx(secp);
-        match self {
-            Descriptor::Pk(_)
-            | Descriptor::Pkh(_)
-            | Descriptor::Wpkh(_)
-            | Descriptor::ShWpkh(_)
+        let descriptor = self.as_derived_fixed(secp);
+        match descriptor.desc_type() {
+            // TODO: add pk() here
+            DescriptorType::Pkh | DescriptorType::Wpkh | DescriptorType::ShWpkh
                 if utxo.is_some()
-                    && self.script_pubkey(deriv_ctx) == utxo.as_ref().unwrap().script_pubkey =>
-            {
-                Some(self.clone())
-            }
-            Descriptor::Bare(ms)
-                if psbt_input.redeem_script.is_some()
-                    && &ms.encode(deriv_ctx) == psbt_input.redeem_script.as_ref().unwrap() =>
-            {
-                Some(self.clone())
-            }
-            Descriptor::Sh(ms)
-                if psbt_input.redeem_script.is_some()
-                    && &ms.encode(deriv_ctx) == psbt_input.redeem_script.as_ref().unwrap() =>
-            {
-                Some(self.clone())
-            }
-            Descriptor::Wsh(ms) | Descriptor::ShWsh(ms)
-                if psbt_input.witness_script.is_some()
-                    && &ms.encode(deriv_ctx) == psbt_input.witness_script.as_ref().unwrap() =>
+                    && descriptor.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
             {
-                Some(self.clone())
+                Some(descriptor)
             }
-            Descriptor::ShSortedMulti(keys)
+            DescriptorType::Bare | DescriptorType::Sh | DescriptorType::ShSortedMulti
                 if psbt_input.redeem_script.is_some()
-                    && &keys.encode(deriv_ctx) == psbt_input.redeem_script.as_ref().unwrap() =>
+                    && &descriptor.explicit_script()
+                        == psbt_input.redeem_script.as_ref().unwrap() =>
             {
-                Some(self.clone())
+                Some(descriptor)
             }
-            Descriptor::WshSortedMulti(keys) | Descriptor::ShWshSortedMulti(keys)
+            DescriptorType::Wsh
+            | DescriptorType::ShWsh
+            | DescriptorType::ShWshSortedMulti
+            | DescriptorType::WshSortedMulti
                 if psbt_input.witness_script.is_some()
-                    && &keys.encode(deriv_ctx) == psbt_input.witness_script.as_ref().unwrap() =>
+                    && &descriptor.explicit_script()
+                        == psbt_input.witness_script.as_ref().unwrap() =>
             {
-                Some(self.clone())
+                Some(descriptor)
             }
             _ => None,
         }
     }
 }
 
-#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
-struct DummyKey();
-
-impl fmt::Display for DummyKey {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "DummyKey")
-    }
-}
-
-impl std::str::FromStr for DummyKey {
-    type Err = ();
-
-    fn from_str(_: &str) -> Result<Self, Self::Err> {
-        Ok(DummyKey::default())
-    }
-}
+impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> {
+    fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError> {
+        let mut answer = BTreeMap::new();
+        self.for_each_key(|key| {
+            if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
+                let derived_pubkey = xpub
+                    .xkey
+                    .derive_pub(secp, &xpub.derivation_path)
+                    .expect("Derivation can't fail");
+
+                answer.insert(
+                    derived_pubkey.public_key,
+                    (xpub.root_fingerprint(secp), xpub.full_path(&[])),
+                );
+            }
 
-impl miniscript::MiniscriptKey for DummyKey {
-    type Hash = DummyKey;
+            true
+        });
 
-    fn to_pubkeyhash(&self) -> DummyKey {
-        DummyKey::default()
+        Ok(answer)
     }
 }
 
@@ -694,6 +615,8 @@ mod test {
     fn test_to_wallet_descriptor_fixup_networks() {
         use crate::keys::{any_network, ToDescriptorKey};
 
+        let secp = Secp256k1::new();
+
         let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap();
         let path = bip32::DerivationPath::from_str("m/0").unwrap();
 
@@ -706,39 +629,41 @@ mod test {
         // make a descriptor out of it
         let desc = crate::descriptor!(wpkh(key)).unwrap();
         // this should conver the key that supports "any_network" to the right network (testnet)
-        let (wallet_desc, _) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
+        let (wallet_desc, _) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
 
-        assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
+        assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)#y8p7e8kk");
     }
 
     // test ToWalletDescriptor trait from &str with and without checksum appended
     #[test]
     fn test_descriptor_from_str_with_checksum() {
+        let secp = Secp256k1::new();
+
         let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(desc.is_ok());
 
         let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(desc.is_ok());
 
         let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(desc.is_ok());
 
         let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(desc.is_ok());
 
         let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(matches!(
             desc.err(),
             Some(DescriptorError::InvalidDescriptorChecksum)
         ));
 
         let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(matches!(
             desc.err(),
             Some(DescriptorError::InvalidDescriptorChecksum)
@@ -748,39 +673,41 @@ mod test {
     // test ToWalletDescriptor trait from &str with keys from right and wrong network
     #[test]
     fn test_descriptor_from_str_with_keys_network() {
+        let secp = Secp256k1::new();
+
         let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(desc.is_ok());
 
         let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
-            .to_wallet_descriptor(Network::Regtest);
+            .to_wallet_descriptor(&secp, Network::Regtest);
         assert!(desc.is_ok());
 
         let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(desc.is_ok());
 
         let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
-            .to_wallet_descriptor(Network::Regtest);
+            .to_wallet_descriptor(&secp, Network::Regtest);
         assert!(desc.is_ok());
 
         let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))"
-            .to_wallet_descriptor(Network::Testnet);
+            .to_wallet_descriptor(&secp, Network::Testnet);
         assert!(desc.is_ok());
 
         let desc = "sh(wpkh(02864bb4ad00cefa806098a69e192bbda937494e69eb452b87bb3f20f6283baedb))"
-            .to_wallet_descriptor(Network::Bitcoin);
+            .to_wallet_descriptor(&secp, Network::Bitcoin);
         assert!(desc.is_ok());
 
         let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
-            .to_wallet_descriptor(Network::Bitcoin);
+            .to_wallet_descriptor(&secp, Network::Bitcoin);
         assert!(matches!(
             desc.err(),
             Some(DescriptorError::Key(KeyError::InvalidNetwork))
         ));
 
         let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
-            .to_wallet_descriptor(Network::Bitcoin);
+            .to_wallet_descriptor(&secp, Network::Bitcoin);
         assert!(matches!(
             desc.err(),
             Some(DescriptorError::Key(KeyError::InvalidNetwork))
@@ -790,6 +717,8 @@ mod test {
     // test ToWalletDescriptor trait from the output of the descriptor!() macro
     #[test]
     fn test_descriptor_from_str_from_output_of_macro() {
+        let secp = Secp256k1::new();
+
         let tpub = bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK").unwrap();
         let path = bip32::DerivationPath::from_str("m/1/2").unwrap();
         let key = (tpub, path).to_descriptor_key().unwrap();
@@ -797,12 +726,12 @@ mod test {
         // make a descriptor out of it
         let desc = crate::descriptor!(wpkh(key)).unwrap();
 
-        let (wallet_desc, _) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
+        let (wallet_desc, _) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
         let wallet_desc_str = wallet_desc.to_string();
-        assert_eq!(wallet_desc_str, "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)");
+        assert_eq!(wallet_desc_str, "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)#67ju93jw");
 
         let (wallet_desc2, _) = wallet_desc_str
-            .to_wallet_descriptor(Network::Testnet)
+            .to_wallet_descriptor(&secp, Network::Testnet)
             .unwrap();
         assert_eq!(wallet_desc, wallet_desc2)
     }
index d7380907d9159f88eeeb316eebadba24a61bed7c..3550c49c1ab84011726d1462e5bb603af3f729d9 100644 (file)
@@ -38,7 +38,7 @@
 //! let secp = Secp256k1::new();
 //! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
 //!
-//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(desc)?;
+//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?;
 //! println!("{:?}", extended_desc);
 //!
 //! let signers = Arc::new(key_map.into());
@@ -58,15 +58,15 @@ use bitcoin::hashes::*;
 use bitcoin::util::bip32::Fingerprint;
 use bitcoin::PublicKey;
 
-use miniscript::descriptor::{DescriptorPublicKey, SortedMultiVec};
+use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
 use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey};
 
 #[allow(unused_imports)]
 use log::{debug, error, info, trace};
 
-use crate::descriptor::ExtractPolicy;
+use crate::descriptor::{DerivedDescriptorKey, ExtractPolicy};
 use crate::wallet::signer::{SignerId, SignersContainer};
-use crate::wallet::utils::{self, descriptor_to_pk_ctx, SecpCtx};
+use crate::wallet::utils::{self, SecpCtx};
 
 use super::checksum::get_checksum;
 use super::error::Error;
@@ -738,8 +738,9 @@ fn signature_key(
     signers: &SignersContainer,
     secp: &SecpCtx,
 ) -> Policy {
-    let deriv_ctx = descriptor_to_pk_ctx(secp);
-    let key_hash = key.to_public_key(deriv_ctx).to_pubkeyhash();
+    let key_hash = DerivedDescriptorKey::new(key.clone(), secp)
+        .to_public_key()
+        .to_pubkeyhash();
     let mut policy: Policy = SatisfiableItem::Signature(PKOrF::from_key_hash(key_hash)).into();
 
     if signers.find(SignerId::PkHash(key_hash)).is_some() {
@@ -866,28 +867,28 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
         }
 
         match self {
-            Descriptor::Pk(pubkey)
-            | Descriptor::Pkh(pubkey)
-            | Descriptor::Wpkh(pubkey)
-            | Descriptor::ShWpkh(pubkey) => Ok(Some(signature(pubkey, signers, secp))),
-            Descriptor::Bare(inner) => Ok(inner.extract_policy(signers, secp)?),
-            Descriptor::Sh(inner) => Ok(inner.extract_policy(signers, secp)?),
-            Descriptor::Wsh(inner) | Descriptor::ShWsh(inner) => {
-                Ok(inner.extract_policy(signers, secp)?)
-            }
-
-            // `sortedmulti()` is handled separately
-            Descriptor::ShSortedMulti(keys) => make_sortedmulti(&keys, signers, secp),
-            Descriptor::ShWshSortedMulti(keys) | Descriptor::WshSortedMulti(keys) => {
-                make_sortedmulti(&keys, signers, secp)
-            }
+            Descriptor::Pkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
+            Descriptor::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
+            Descriptor::Sh(sh) => match sh.as_inner() {
+                ShInner::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))),
+                ShInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
+                ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
+                ShInner::Wsh(wsh) => match wsh.as_inner() {
+                    WshInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
+                    WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
+                },
+            },
+            Descriptor::Wsh(wsh) => match wsh.as_inner() {
+                WshInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?),
+                WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp),
+            },
+            Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, secp)?),
         }
     }
 }
 
 #[cfg(test)]
 mod test {
-
     use crate::descriptor;
     use crate::descriptor::{ExtractPolicy, ToWalletDescriptor};
 
@@ -897,7 +898,6 @@ mod test {
     use crate::wallet::signer::SignersContainer;
     use bitcoin::secp256k1::{All, Secp256k1};
     use bitcoin::util::bip32;
-    use bitcoin::util::bip32::ChildNumber;
     use bitcoin::Network;
     use std::str::FromStr;
     use std::sync::Arc;
@@ -925,9 +925,11 @@ mod test {
 
     #[test]
     fn test_extract_policy_for_wpkh() {
+        let secp = Secp256k1::new();
+
         let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR);
         let desc = descriptor!(wpkh(pubkey)).unwrap();
-        let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
+        let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
         let signers_container = Arc::new(SignersContainer::from(keymap));
         let policy = wallet_desc
             .extract_policy(&signers_container, &Secp256k1::new())
@@ -940,7 +942,7 @@ mod test {
         assert!(matches!(&policy.contribution, Satisfaction::None));
 
         let desc = descriptor!(wpkh(prvkey)).unwrap();
-        let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
+        let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
         let signers_container = Arc::new(SignersContainer::from(keymap));
         let policy = wallet_desc
             .extract_policy(&signers_container, &Secp256k1::new())
@@ -1018,10 +1020,12 @@ mod test {
     #[test]
     #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
     fn test_extract_policy_for_sh_multi_complete_1of2() {
+        let secp = Secp256k1::new();
+
         let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
         let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
         let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
-        let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
+        let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
         let signers_container = Arc::new(SignersContainer::from(keymap));
         let policy = wallet_desc
             .extract_policy(&signers_container, &Secp256k1::new())
@@ -1046,10 +1050,12 @@ mod test {
     // 2 prv keys descriptor, required 2 prv keys
     #[test]
     fn test_extract_policy_for_sh_multi_complete_2of2() {
+        let secp = Secp256k1::new();
+
         let (prvkey0, _pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
         let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
         let desc = descriptor!(sh(multi(2, prvkey0, prvkey1))).unwrap();
-        let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
+        let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
         let signers_container = Arc::new(SignersContainer::from(keymap));
         let policy = wallet_desc
             .extract_policy(&signers_container, &Secp256k1::new())
@@ -1075,10 +1081,12 @@ mod test {
 
     #[test]
     fn test_extract_policy_for_single_wpkh() {
+        let secp = Secp256k1::new();
+
         let (prvkey, pubkey, fingerprint) = setup_keys(TPRV0_STR);
         let desc = descriptor!(wpkh(pubkey)).unwrap();
-        let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
-        let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap());
+        let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
+        let single_key = wallet_desc.derive(0);
         let signers_container = Arc::new(SignersContainer::from(keymap));
         let policy = single_key
             .extract_policy(&signers_container, &Secp256k1::new())
@@ -1091,8 +1099,8 @@ mod test {
         assert!(matches!(&policy.contribution, Satisfaction::None));
 
         let desc = descriptor!(wpkh(prvkey)).unwrap();
-        let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
-        let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap());
+        let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
+        let single_key = wallet_desc.derive(0);
         let signers_container = Arc::new(SignersContainer::from(keymap));
         let policy = single_key
             .extract_policy(&signers_container, &Secp256k1::new())
@@ -1111,11 +1119,13 @@ mod test {
     #[test]
     #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
     fn test_extract_policy_for_single_wsh_multi_complete_1of2() {
+        let secp = Secp256k1::new();
+
         let (_prvkey0, pubkey0, fingerprint0) = setup_keys(TPRV0_STR);
         let (prvkey1, _pubkey1, fingerprint1) = setup_keys(TPRV1_STR);
         let desc = descriptor!(sh(multi(1, pubkey0, prvkey1))).unwrap();
-        let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
-        let single_key = wallet_desc.derive(ChildNumber::from_normal_idx(0).unwrap());
+        let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
+        let single_key = wallet_desc.derive(0);
         let signers_container = Arc::new(SignersContainer::from(keymap));
         let policy = single_key
             .extract_policy(&signers_container, &Secp256k1::new())
@@ -1142,6 +1152,8 @@ mod test {
     #[test]
     #[ignore] // see https://github.com/bitcoindevkit/bdk/issues/225
     fn test_extract_policy_for_wsh_multi_timelock() {
+        let secp = Secp256k1::new();
+
         let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR);
         let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR);
         let sequence = 50;
@@ -1154,7 +1166,7 @@ mod test {
         )))
         .unwrap();
 
-        let (wallet_desc, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
+        let (wallet_desc, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
         let signers_container = Arc::new(SignersContainer::from(keymap));
         let policy = wallet_desc
             .extract_policy(&signers_container, &Secp256k1::new())
index bc34e7075129fa18957d93aec9a055538fb4651b..f269c7a9767273e2a9c8e3e846d78df2f98dc7a0 100644 (file)
@@ -35,6 +35,7 @@ use miniscript::{Legacy, Segwitv0};
 use super::{ExtendedDescriptor, KeyMap, ToWalletDescriptor};
 use crate::descriptor::DescriptorError;
 use crate::keys::{DerivableKey, ToDescriptorKey, ValidNetworks};
+use crate::wallet::utils::SecpCtx;
 use crate::{descriptor, KeychainKind};
 
 /// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others
@@ -71,9 +72,10 @@ pub trait DescriptorTemplate {
 impl<T: DescriptorTemplate> ToWalletDescriptor for T {
     fn to_wallet_descriptor(
         self,
+        secp: &SecpCtx,
         network: Network,
     ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
-        Ok(self.build()?.to_wallet_descriptor(network)?)
+        Ok(self.build()?.to_wallet_descriptor(secp, network)?)
     }
 }
 
@@ -201,7 +203,7 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
 /// )?;
 ///
 /// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
@@ -240,7 +242,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
 /// )?;
 ///
 /// assert_eq!(wallet.get_new_address()?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct BIP44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
@@ -275,7 +277,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
 /// )?;
 ///
 /// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
@@ -314,7 +316,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
 /// )?;
 ///
 /// assert_eq!(wallet.get_new_address()?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49\'/0\'/0\']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct BIP49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
@@ -349,7 +351,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
 /// )?;
 ///
 /// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
@@ -388,7 +390,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
 /// )?;
 ///
 /// assert_eq!(wallet.get_new_address()?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
-/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)");
+/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub struct BIP84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
@@ -458,13 +460,13 @@ mod test {
     // test existing descriptor templates, make sure they are expanded to the right descriptors
 
     use super::*;
+    use crate::descriptor::derived::AsDerived;
     use crate::descriptor::{DescriptorError, DescriptorMeta};
     use crate::keys::ValidNetworks;
     use bitcoin::hashes::core::str::FromStr;
     use bitcoin::network::constants::Network::Regtest;
     use bitcoin::secp256k1::Secp256k1;
-    use bitcoin::util::bip32::ChildNumber;
-    use miniscript::descriptor::{DescriptorPublicKey, DescriptorPublicKeyCtx, KeyMap};
+    use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};
     use miniscript::Descriptor;
 
     // verify template descriptor generates expected address(es)
@@ -475,20 +477,18 @@ mod test {
         expected: &[&str],
     ) {
         let secp = Secp256k1::new();
-        let deriv_ctx =
-            DescriptorPublicKeyCtx::new(&secp, ChildNumber::from_normal_idx(0).unwrap());
 
         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).unwrap();
+            let address = child_desc.address(Regtest).unwrap();
             assert_eq!(address.to_string(), *expected.get(i).unwrap());
         }
     }
index 8ea2aeb89739119f975823f8a805236cdd33888e..4f121165527091056527054f24f12ef55fa3da36 100644 (file)
@@ -147,7 +147,7 @@ mod test {
 
         let key = (mnemonic, path);
         let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
-        assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)");
+        assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)#0r8v4nkv");
         assert_eq!(keys.len(), 1);
         assert_eq!(networks.len(), 3);
     }
@@ -161,7 +161,7 @@ mod test {
 
         let key = ((mnemonic, Some("passphrase".into())), path);
         let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
-        assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)");
+        assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)#h0j0tg5m");
         assert_eq!(keys.len(), 1);
         assert_eq!(networks.len(), 3);
     }
index 1df214cf7df5aae8762311ffda6bb9358c38fc9b..8c08d03aedfb3830ef2eaa417486f8200c63714d 100644 (file)
@@ -35,11 +35,11 @@ use bitcoin::secp256k1::{self, Secp256k1, Signing};
 use bitcoin::util::bip32;
 use bitcoin::{Network, PrivateKey, PublicKey};
 
+use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
 pub use miniscript::descriptor::{
-    DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub,
+    DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, KeyMap,
     SortedMultiVec,
 };
-use miniscript::descriptor::{DescriptorXKey, KeyMap};
 pub use miniscript::ScriptContext;
 use miniscript::{Miniscript, Terminal};
 
@@ -492,14 +492,14 @@ let xprv = xkey.into_xprv(Network::Bitcoin).unwrap();
                 origin,
                 xkey: xprv,
                 derivation_path,
-                is_wildcard: true,
+                wildcard: Wildcard::Unhardened,
             })
             .to_descriptor_key(),
             ExtendedKey::Public((xpub, _)) => DescriptorPublicKey::XPub(DescriptorXKey {
                 origin,
                 xkey: xpub,
                 derivation_path,
-                is_wildcard: true,
+                wildcard: Wildcard::Unhardened,
             })
             .to_descriptor_key(),
         }
@@ -776,24 +776,24 @@ pub fn make_multi<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
 
 // Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments
 #[doc(hidden)]
-pub fn make_sortedmulti_inner<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
+pub fn make_sortedmulti<Pk, Ctx, F>(
     thresh: usize,
     pks: Vec<Pk>,
+    build_desc: F,
     secp: &SecpCtx,
-) -> Result<
-    (
-        SortedMultiVec<DescriptorPublicKey, Ctx>,
-        KeyMap,
-        ValidNetworks,
-    ),
-    DescriptorError,
-> {
+) -> Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>
+where
+    Pk: ToDescriptorKey<Ctx>,
+    Ctx: ScriptContext,
+    F: Fn(
+        usize,
+        Vec<DescriptorPublicKey>,
+    ) -> Result<(Descriptor<DescriptorPublicKey>, PhantomData<Ctx>), DescriptorError>,
+{
     let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
-    let minisc = SortedMultiVec::new(thresh, pks)?;
+    let descriptor = build_desc(thresh, pks)?.0;
 
-    // TODO: should we apply the checks here as well?
-
-    Ok((minisc, key_map, valid_networks))
+    Ok((descriptor, key_map, valid_networks))
 }
 
 /// The "identity" conversion is used internally by some `bdk::fragment`s
index cb367b35517e78ae61478b95a6a8b0c56b67dd78..000eb32a9dddc683541cc9f7f5192caac3dab2f3 100644 (file)
@@ -76,6 +76,7 @@ use std::str::FromStr;
 
 use serde::{Deserialize, Serialize};
 
+use miniscript::descriptor::{ShInner, WshInner};
 use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal};
 
 use crate::database::BatchDatabase;
@@ -107,6 +108,10 @@ impl FromStr for WalletExport {
     }
 }
 
+fn remove_checksum(s: String) -> String {
+    s.splitn(2, '#').next().map(String::from).unwrap()
+}
+
 impl WalletExport {
     /// Export a wallet
     ///
@@ -127,6 +132,7 @@ impl WalletExport {
         let descriptor = wallet
             .descriptor
             .to_string_with_secret(&wallet.signers.as_key_map(wallet.secp_ctx()));
+        let descriptor = remove_checksum(descriptor);
         Self::is_compatible_with_core(&descriptor)?;
 
         let blockheight = match wallet.database.borrow().iter_txs(false) {
@@ -150,7 +156,9 @@ impl WalletExport {
         };
 
         let desc_to_string = |d: &Descriptor<DescriptorPublicKey>| {
-            d.to_string_with_secret(&wallet.change_signers.as_key_map(wallet.secp_ctx()))
+            let descriptor =
+                d.to_string_with_secret(&wallet.change_signers.as_key_map(wallet.secp_ctx()));
+            remove_checksum(descriptor)
         };
         if export.change_descriptor() != wallet.change_descriptor.as_ref().map(desc_to_string) {
             return Err("Incompatible change descriptor");
@@ -161,7 +169,7 @@ impl WalletExport {
 
     fn is_compatible_with_core(descriptor: &str) -> Result<(), &'static str> {
         fn check_ms<Ctx: ScriptContext>(
-            terminal: Terminal<String, Ctx>,
+            terminal: &Terminal<String, Ctx>,
         ) -> Result<(), &'static str> {
             if let Terminal::Multi(_, _) = terminal {
                 Ok(())
@@ -170,13 +178,22 @@ impl WalletExport {
             }
         }
 
+        // pkh(), wpkh(), sh(wpkh()) are always fine, as well as multi() and sortedmulti()
         match Descriptor::<String>::from_str(descriptor).map_err(|_| "Invalid descriptor")? {
-            Descriptor::Pk(_)
-            | Descriptor::Pkh(_)
-            | Descriptor::Wpkh(_)
-            | Descriptor::ShWpkh(_) => Ok(()),
-            Descriptor::Sh(ms) => check_ms(ms.node),
-            Descriptor::Wsh(ms) | Descriptor::ShWsh(ms) => check_ms(ms.node),
+            Descriptor::Pkh(_) | Descriptor::Wpkh(_) => Ok(()),
+            Descriptor::Sh(sh) => match sh.as_inner() {
+                ShInner::Wpkh(_) => Ok(()),
+                ShInner::SortedMulti(_) => Ok(()),
+                ShInner::Wsh(wsh) => match wsh.as_inner() {
+                    WshInner::SortedMulti(_) => Ok(()),
+                    WshInner::Ms(ms) => check_ms(&ms.node),
+                },
+                ShInner::Ms(ms) => check_ms(&ms.node),
+            },
+            Descriptor::Wsh(wsh) => match wsh.as_inner() {
+                WshInner::SortedMulti(_) => Ok(()),
+                WshInner::Ms(ms) => check_ms(&ms.node),
+            },
             _ => Err("The descriptor is not compatible with Bitcoin Core"),
         }
     }
index 19dfff3cb9f7ab59fcb0e18338bfd2be6f3f791d..69e578bf59207c18bcc7a456d2b8a328a1d38853 100644 (file)
@@ -36,11 +36,11 @@ use bitcoin::secp256k1::Secp256k1;
 
 use bitcoin::consensus::encode::serialize;
 use bitcoin::util::base58;
-use bitcoin::util::bip32::ChildNumber;
 use bitcoin::util::psbt::raw::Key as PSBTKey;
 use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
 use bitcoin::{Address, Network, OutPoint, Script, Transaction, TxOut, Txid};
 
+use miniscript::descriptor::DescriptorTrait;
 use miniscript::psbt::PsbtInputSatisfier;
 
 #[allow(unused_imports)]
@@ -60,16 +60,14 @@ use address_validator::AddressValidator;
 use coin_selection::DefaultCoinSelectionAlgorithm;
 use signer::{Signer, SignerOrdering, SignersContainer};
 use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
-use utils::{
-    check_nlocktime, check_nsequence_rbf, descriptor_to_pk_ctx, After, Older, SecpCtx,
-    DUST_LIMIT_SATOSHI,
-};
+use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LIMIT_SATOSHI};
 
 use crate::blockchain::{Blockchain, Progress};
 use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
+use crate::descriptor::derived::AsDerived;
 use crate::descriptor::{
-    get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
-    ToWalletDescriptor, XKeyUtils,
+    get_checksum, DerivedDescriptor, DerivedDescriptorMeta, DescriptorMeta, DescriptorScripts,
+    ExtendedDescriptor, ExtractPolicy, Policy, ToWalletDescriptor, XKeyUtils,
 };
 use crate::error::Error;
 use crate::psbt::PSBTUtils;
@@ -134,7 +132,9 @@ where
         client: B,
         current_height: Option<u32>,
     ) -> Result<Self, Error> {
-        let (descriptor, keymap) = descriptor.to_wallet_descriptor(network)?;
+        let secp = Secp256k1::new();
+
+        let (descriptor, keymap) = descriptor.to_wallet_descriptor(&secp, network)?;
         database.check_descriptor_checksum(
             KeychainKind::External,
             get_checksum(&descriptor.to_string())?.as_bytes(),
@@ -142,7 +142,8 @@ where
         let signers = Arc::new(SignersContainer::from(keymap));
         let (change_descriptor, change_signers) = match change_descriptor {
             Some(desc) => {
-                let (change_descriptor, change_keymap) = desc.to_wallet_descriptor(network)?;
+                let (change_descriptor, change_keymap) =
+                    desc.to_wallet_descriptor(&secp, network)?;
                 database.check_descriptor_checksum(
                     KeychainKind::Internal,
                     get_checksum(&change_descriptor.to_string())?.as_bytes(),
@@ -168,7 +169,7 @@ where
             current_height,
             client,
             database: RefCell::new(database),
-            secp: Secp256k1::new(),
+            secp,
         })
     }
 }
@@ -181,12 +182,11 @@ where
     /// Return a newly generated address using the external descriptor
     pub fn get_new_address(&self) -> Result<Address, Error> {
         let index = self.fetch_and_increment_index(KeychainKind::External)?;
-        let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
 
         self.descriptor
-            .derive(ChildNumber::from_normal_idx(index)?)
-            .address(self.network, deriv_ctx)
-            .ok_or(Error::ScriptDoesntHaveAddressForm)
+            .as_derived(index, &self.secp)
+            .address(self.network)
+            .map_err(|_| Error::ScriptDoesntHaveAddressForm)
     }
 
     /// Return whether or not a `script` is part of this wallet (either internal or external)
@@ -666,7 +666,6 @@ where
         let vbytes = tx.get_weight() as f32 / 4.0;
         let feerate = details.fees as f32 / vbytes;
 
-        let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
         // remove the inputs from the tx and process them
         let original_txin = tx.input.drain(..).collect::<Vec<_>>();
         let original_utxos = original_txin
@@ -686,7 +685,7 @@ where
                     Some((keychain, _)) => (
                         self._get_descriptor_for_keychain(keychain)
                             .0
-                            .max_satisfaction_weight(deriv_ctx)
+                            .max_satisfaction_weight()
                             .unwrap(),
                         keychain,
                     ),
@@ -855,7 +854,7 @@ where
             // - Try to derive the descriptor by looking at the txout. If it's in our database, we
             //   know exactly which `keychain` to use, and which derivation index it is
             // - If that fails, try to derive it by looking at the psbt input: the complete logic
-            //   is in `src/descriptor/mod.rs`, but it will basically look at `hd_keypaths`,
+            //   is in `src/descriptor/mod.rs`, but it will basically look at `bip32_derivation`,
             //   `redeem_script` and `witness_script` to determine the right derivation
             // - If that also fails, it will try it on the internal descriptor, if present
             let desc = psbt
@@ -879,7 +878,6 @@ where
             match desc {
                 Some(desc) => {
                     let mut tmp_input = bitcoin::TxIn::default();
-                    let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
                     match desc.satisfy(
                         &mut tmp_input,
                         (
@@ -887,7 +885,6 @@ where
                             After::new(current_height, false),
                             Older::new(current_height, create_height, false),
                         ),
-                        deriv_ctx,
                     ) {
                         Ok(_) => {
                             let psbt_input = &mut psbt.inputs[n];
@@ -933,31 +930,30 @@ where
         }
     }
 
-    fn get_descriptor_for_txout(&self, txout: &TxOut) -> Result<Option<ExtendedDescriptor>, Error> {
+    fn get_descriptor_for_txout(
+        &self,
+        txout: &TxOut,
+    ) -> Result<Option<DerivedDescriptor<'_>>, Error> {
         Ok(self
             .database
             .borrow()
             .get_path_from_script_pubkey(&txout.script_pubkey)?
             .map(|(keychain, child)| (self.get_descriptor_for_keychain(keychain), child))
-            .map(|(desc, child)| desc.derive(ChildNumber::from_normal_idx(child).unwrap())))
+            .map(|(desc, child)| desc.as_derived(child, &self.secp)))
     }
 
     fn get_change_address(&self) -> Result<Script, Error> {
-        let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
-
         let (desc, keychain) = self._get_descriptor_for_keychain(KeychainKind::Internal);
         let index = self.fetch_and_increment_index(keychain)?;
 
-        Ok(desc
-            .derive(ChildNumber::from_normal_idx(index)?)
-            .script_pubkey(deriv_ctx))
+        Ok(desc.as_derived(index, &self.secp).script_pubkey())
     }
 
     fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
         let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
-        let index = match descriptor.is_fixed() {
-            true => 0,
-            false => self.database.borrow_mut().increment_last_index(keychain)?,
+        let index = match descriptor.is_deriveable() {
+            false => 0,
+            true => self.database.borrow_mut().increment_last_index(keychain)?,
         };
 
         if self
@@ -969,12 +965,11 @@ where
             self.cache_addresses(keychain, index, CACHE_ADDR_BATCH_SIZE)?;
         }
 
-        let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
+        let derived_descriptor = descriptor.as_derived(index, &self.secp);
+
+        let hd_keypaths = derived_descriptor.get_hd_keypaths(&self.secp)?;
+        let script = derived_descriptor.script_pubkey();
 
-        let hd_keypaths = descriptor.get_hd_keypaths(index, &self.secp)?;
-        let script = descriptor
-            .derive(ChildNumber::from_normal_idx(index)?)
-            .script_pubkey(deriv_ctx);
         for validator in &self.address_validators {
             validator.validate(keychain, &hd_keypaths, &script)?;
         }
@@ -989,7 +984,7 @@ where
         mut count: u32,
     ) -> Result<(), Error> {
         let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
-        if descriptor.is_fixed() {
+        if !descriptor.is_deriveable() {
             if from > 0 {
                 return Ok(());
             }
@@ -997,16 +992,12 @@ where
             count = 1;
         }
 
-        let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
-
         let mut address_batch = self.database.borrow().begin_batch();
 
         let start_time = time::Instant::new();
         for i in from..(from + count) {
             address_batch.set_script_pubkey(
-                &descriptor
-                    .derive(ChildNumber::from_normal_idx(i)?)
-                    .script_pubkey(deriv_ctx),
+                &descriptor.as_derived(i, &self.secp).script_pubkey(),
                 keychain,
                 i,
             )?;
@@ -1025,7 +1016,6 @@ where
     }
 
     fn get_available_utxos(&self) -> Result<Vec<(UTXO, usize)>, Error> {
-        let deriv_ctx = descriptor_to_pk_ctx(&self.secp);
         Ok(self
             .list_unspent()?
             .into_iter()
@@ -1034,7 +1024,7 @@ where
                 (
                     utxo,
                     self.get_descriptor_for_keychain(keychain)
-                        .max_satisfaction_weight(deriv_ctx)
+                        .max_satisfaction_weight()
                         .unwrap(),
                 )
             })
@@ -1174,19 +1164,19 @@ where
             };
 
             let (desc, _) = self._get_descriptor_for_keychain(keychain);
-            psbt_input.hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?;
-            let derived_descriptor = desc.derive(ChildNumber::from_normal_idx(child)?);
+            let derived_descriptor = desc.as_derived(child, &self.secp);
+            psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?;
 
-            psbt_input.redeem_script = derived_descriptor.psbt_redeem_script(&self.secp);
-            psbt_input.witness_script = derived_descriptor.psbt_witness_script(&self.secp);
+            psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
+            psbt_input.witness_script = derived_descriptor.psbt_witness_script();
 
             let prev_output = input.previous_output;
             if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? {
-                if derived_descriptor.is_witness() {
+                if desc.is_witness() {
                     psbt_input.witness_utxo =
                         Some(prev_tx.output[prev_output.vout as usize].clone());
                 }
-                if !derived_descriptor.is_witness() || params.force_non_witness_utxo {
+                if !desc.is_witness() || params.force_non_witness_utxo {
                     psbt_input.non_witness_utxo = Some(prev_tx);
                 }
             }
@@ -1207,11 +1197,12 @@ where
                 .get_path_from_script_pubkey(&tx_output.script_pubkey)?
             {
                 let (desc, _) = self._get_descriptor_for_keychain(keychain);
-                psbt_output.hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?;
+                let derived_descriptor = desc.as_derived(child, &self.secp);
+
+                psbt_output.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?;
                 if params.include_output_redeem_witness_script {
-                    let derived_descriptor = desc.derive(ChildNumber::from_normal_idx(child)?);
-                    psbt_output.witness_script = derived_descriptor.psbt_witness_script(&self.secp);
-                    psbt_output.redeem_script = derived_descriptor.psbt_redeem_script(&self.secp);
+                    psbt_output.witness_script = derived_descriptor.psbt_witness_script();
+                    psbt_output.redeem_script = derived_descriptor.psbt_redeem_script();
                 };
             }
         }
@@ -1237,8 +1228,10 @@ where
 
                     // merge hd_keypaths
                     let desc = self.get_descriptor_for_keychain(keychain);
-                    let mut hd_keypaths = desc.get_hd_keypaths(child, &self.secp)?;
-                    psbt_input.hd_keypaths.append(&mut hd_keypaths);
+                    let mut hd_keypaths = desc
+                        .as_derived(child, &self.secp)
+                        .get_hd_keypaths(&self.secp)?;
+                    psbt_input.bip32_derivation.append(&mut hd_keypaths);
                 }
             }
         }
@@ -1283,9 +1276,9 @@ where
 
         let mut run_setup = false;
 
-        let max_address = match self.descriptor.is_fixed() {
-            true => 0,
-            false => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
+        let max_address = match self.descriptor.is_deriveable() {
+            false => 0,
+            true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
         };
         if self
             .database
@@ -1298,9 +1291,9 @@ where
         }
 
         if let Some(change_descriptor) = &self.change_descriptor {
-            let max_address = match change_descriptor.is_fixed() {
-                true => 0,
-                false => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
+            let max_address = match change_descriptor.is_deriveable() {
+                false => 0,
+                true => max_address_param.unwrap_or(CACHE_ADDR_BATCH_SIZE),
             };
 
             if self
@@ -1946,9 +1939,9 @@ mod test {
             .drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
 
-        assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
+        assert_eq!(psbt.inputs[0].bip32_derivation.len(), 1);
         assert_eq!(
-            psbt.inputs[0].hd_keypaths.values().nth(0).unwrap(),
+            psbt.inputs[0].bip32_derivation.values().nth(0).unwrap(),
             &(
                 Fingerprint::from_str("d34db33f").unwrap(),
                 DerivationPath::from_str("m/44'/0'/0'/0/0").unwrap()
@@ -1972,9 +1965,9 @@ mod test {
             .drain_wallet();
         let (psbt, _) = builder.finish().unwrap();
 
-        assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
+        assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1);
         assert_eq!(
-            psbt.outputs[0].hd_keypaths.values().nth(0).unwrap(),
+            psbt.outputs[0].bip32_derivation.values().nth(0).unwrap(),
             &(
                 Fingerprint::from_str("d34db33f").unwrap(),
                 DerivationPath::from_str("m/44'/0'/0'/0/5").unwrap()
@@ -3188,8 +3181,8 @@ mod test {
             .drain_wallet();
         let (mut psbt, _) = builder.finish().unwrap();
 
-        psbt.inputs[0].hd_keypaths.clear();
-        assert_eq!(psbt.inputs[0].hd_keypaths.len(), 0);
+        psbt.inputs[0].bip32_derivation.clear();
+        assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0);
 
         let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
         assert_eq!(finalized, true);
@@ -3233,7 +3226,7 @@ mod test {
                 "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)",
             )
             .unwrap()
-            .script_pubkey(miniscript::NullCtx),
+            .script_pubkey(),
         });
         psbt.inputs.push(dud_input);
         psbt.global.unsigned_tx.input.push(bitcoin::TxIn::default());
index 8d0a70d8266271047ee9bd8f1ba4e07cd05ae03d..bffc469f30cb9321fb576ca8672e6373551af781 100644 (file)
@@ -220,7 +220,7 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
         }
 
         let (public_key, deriv_path) = match psbt.inputs[input_index]
-            .hd_keypaths
+            .bip32_derivation
             .iter()
             .filter_map(|(pk, &(fingerprint, ref path))| {
                 if self.matches(&(fingerprint, path.clone()), &secp).is_some() {
@@ -562,7 +562,7 @@ mod signers_container_tests {
     use crate::descriptor;
     use crate::descriptor::ToWalletDescriptor;
     use crate::keys::{DescriptorKey, ToDescriptorKey};
-    use bitcoin::secp256k1::All;
+    use bitcoin::secp256k1::{All, Secp256k1};
     use bitcoin::util::bip32;
     use bitcoin::util::psbt::PartiallySignedTransaction;
     use bitcoin::Network;
@@ -574,10 +574,12 @@ mod signers_container_tests {
     // 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 (_, keymap) = desc.to_wallet_descriptor(Network::Testnet).unwrap();
+        let (_, keymap) = desc.to_wallet_descriptor(&secp, Network::Testnet).unwrap();
 
         let signers = SignersContainer::from(keymap);
         assert_eq!(signers.ids().len(), 2);
index a4848096ba7b8f34e9e3b8412121f4d5405d2029..86dca5637da22ef04ee36d7e764f848117f209ab 100644 (file)
@@ -57,6 +57,8 @@ use std::marker::PhantomData;
 use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
 use bitcoin::{OutPoint, Script, SigHashType, Transaction};
 
+use miniscript::descriptor::DescriptorTrait;
+
 use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
 use crate::{database::BatchDatabase, Error, Wallet};
 use crate::{
@@ -276,7 +278,6 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
     /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
     /// the "utxos" and the "unspendable" list, it will be spent.
     pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, Error> {
-        let deriv_ctx = crate::wallet::descriptor_to_pk_ctx(self.wallet.secp_ctx());
         let utxos = outpoints
             .iter()
             .map(|outpoint| self.wallet.get_utxo(*outpoint)?.ok_or(Error::UnknownUTXO))
@@ -284,7 +285,7 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
 
         for utxo in utxos {
             let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain);
-            let satisfaction_weight = descriptor.max_satisfaction_weight(deriv_ctx).unwrap();
+            let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap();
             self.params.utxos.push((utxo, satisfaction_weight));
         }
 
index 5f2b8004649be5f00a81b1a8417d6ffa135698c6..a71d09c14ac77dd9335f9138ca06c94588bcf236 100644 (file)
@@ -23,9 +23,7 @@
 // SOFTWARE.
 
 use bitcoin::secp256k1::{All, Secp256k1};
-use bitcoin::util::bip32;
 
-use miniscript::descriptor::DescriptorPublicKeyCtx;
 use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
 
 // De-facto standard "dust limit" (even though it should change based on the output type)
@@ -110,7 +108,7 @@ pub(crate) fn check_nlocktime(nlocktime: u32, required: u32) -> bool {
     true
 }
 
-impl<ToPkCtx: Copy, Pk: MiniscriptKey + ToPublicKey<ToPkCtx>> Satisfier<ToPkCtx, Pk> for After {
+impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
     fn check_after(&self, n: u32) -> bool {
         if let Some(current_height) = self.current_height {
             current_height >= n
@@ -140,7 +138,7 @@ impl Older {
     }
 }
 
-impl<ToPkCtx: Copy, Pk: MiniscriptKey + ToPublicKey<ToPkCtx>> Satisfier<ToPkCtx, Pk> for Older {
+impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
     fn check_older(&self, n: u32) -> bool {
         if let Some(current_height) = self.current_height {
             // TODO: test >= / >
@@ -152,12 +150,6 @@ impl<ToPkCtx: Copy, Pk: MiniscriptKey + ToPublicKey<ToPkCtx>> Satisfier<ToPkCtx,
 }
 
 pub(crate) type SecpCtx = Secp256k1<All>;
-pub(crate) fn descriptor_to_pk_ctx(secp: &SecpCtx) -> DescriptorPublicKeyCtx<'_, All> {
-    // Create a `to_pk_ctx` with a dummy derivation index, since we always use this on descriptor
-    // that have already been derived with `Descriptor::derive()`, so the child number added here
-    // is ignored.
-    DescriptorPublicKeyCtx::new(secp, bip32::ChildNumber::Normal { index: 0 })
-}
 
 pub struct ChunksIterator<I: Iterator> {
     iter: I,
index 0800fe4d9098085d23de33d191f01daebb379f91..8ce0035d3b5f1fc2f89e67c358a3b1350f151f64 100644 (file)
@@ -20,6 +20,7 @@ log = "0.4.8"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
 serial_test = "0.4"
-bitcoin = "0.25"
-bitcoincore-rpc = "0.12"
-electrum-client = "0.4.0-beta.1"
+bitcoin = "0.26"
+bitcoincore-rpc = "0.13"
+miniscript = "5.1"
+electrum-client = "0.6.0"
index 12de77cf17db137b288e93a9b5ecf15f3b00313d..3aaf9f1982df8dab23bba498b0b6672d4651b874 100644 (file)
@@ -40,7 +40,11 @@ use log::{debug, error, info, trace};
 use bitcoin::consensus::encode::{deserialize, serialize};
 use bitcoin::hashes::hex::{FromHex, ToHex};
 use bitcoin::hashes::sha256d;
-use bitcoin::{Address, Amount, Script, Transaction, Txid};
+use bitcoin::secp256k1::{Secp256k1, Verification};
+use bitcoin::{Address, Amount, PublicKey, Script, Transaction, Txid};
+
+use miniscript::descriptor::DescriptorPublicKey;
+use miniscript::{Descriptor, MiniscriptKey, TranslatePk};
 
 pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
 pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
@@ -113,23 +117,62 @@ impl TestIncomingTx {
     }
 }
 
+#[doc(hidden)]
+pub trait TranslateDescriptor {
+    // derive and translate a `Descriptor<DescriptorPublicKey>` into a `Descriptor<PublicKey>`
+    fn derive_translated<C: Verification>(
+        &self,
+        secp: &Secp256k1<C>,
+        index: u32,
+    ) -> Descriptor<PublicKey>;
+}
+
+impl TranslateDescriptor for Descriptor<DescriptorPublicKey> {
+    fn derive_translated<C: Verification>(
+        &self,
+        secp: &Secp256k1<C>,
+        index: u32,
+    ) -> Descriptor<PublicKey> {
+        let translate = |key: &DescriptorPublicKey| -> PublicKey {
+            match key {
+                DescriptorPublicKey::XPub(xpub) => {
+                    xpub.xkey
+                        .derive_pub(secp, &xpub.derivation_path)
+                        .expect("hardened derivation steps")
+                        .public_key
+                }
+                DescriptorPublicKey::SinglePub(key) => key.key,
+            }
+        };
+
+        self.derive(index)
+            .translate_pk_infallible(|pk| translate(pk), |pkh| translate(pkh).to_pubkeyhash())
+    }
+}
+
 #[macro_export]
 macro_rules! testutils {
     ( @external $descriptors:expr, $child:expr ) => ({
         use bitcoin::secp256k1::Secp256k1;
-        use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorPublicKeyCtx};
+        use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
+
+        use $crate::TranslateDescriptor;
 
         let secp = Secp256k1::new();
-        let deriv_ctx = DescriptorPublicKeyCtx::new(&secp, bitcoin::util::bip32::ChildNumber::from_normal_idx(0).unwrap());
 
-        let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
-        parsed.derive(bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()).address(bitcoin::Network::Regtest, deriv_ctx).expect("No address form")
+        let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
+        parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
     });
     ( @internal $descriptors:expr, $child:expr ) => ({
-        use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+        use bitcoin::secp256k1::Secp256k1;
+        use miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
 
-        let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
-        parsed.derive(bitcoin::util::bip32::ChildNumber::from_normal_idx($child).unwrap()).address(bitcoin::Network::Regtest).expect("No address form")
+        use $crate::TranslateDescriptor;
+
+        let secp = Secp256k1::new();
+
+        let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
+        parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
     });
     ( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
     ( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
@@ -202,6 +245,7 @@ macro_rules! testutils {
         use std::convert::TryInto;
 
         use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
+        use miniscript::TranslatePk;
 
         let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
         $(
@@ -209,40 +253,39 @@ macro_rules! testutils {
         )*
 
         let external: Descriptor<String> = FromStr::from_str($external_descriptor).unwrap();
-        let external: Descriptor<String> = external.translate_pk::<_, _, _, &'static str>(|k| {
+        let external: Descriptor<String> = external.translate_pk_infallible::<_, _>(|k| {
             if let Some((key, ext_path, _)) = keys.get(&k.as_str()) {
-                Ok(format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into())))
+                format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
             } else {
-                Ok(k.clone())
+                k.clone()
             }
         }, |kh| {
             if let Some((key, ext_path, _)) = keys.get(&kh.as_str()) {
-                Ok(format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into())))
+                format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
             } else {
-                Ok(kh.clone())
+                kh.clone()
             }
 
-        }).unwrap();
+        });
         let external = external.to_string();
 
         let mut internal = None::<String>;
         $(
             let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
 
-            let string_internal: Descriptor<String> = string_internal.translate_pk::<_, _, _, &'static str>(|k| {
+            let string_internal: Descriptor<String> = string_internal.translate_pk_infallible::<_, _>(|k| {
                 if let Some((key, _, int_path)) = keys.get(&k.as_str()) {
-                    Ok(format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into())))
+                    format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
                 } else {
-                    Ok(k.clone())
+                    k.clone()
                 }
             }, |kh| {
                 if let Some((key, _, int_path)) = keys.get(&kh.as_str()) {
-                    Ok(format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into())))
+                    format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
                 } else {
-                    Ok(kh.clone())
+                    kh.clone()
                 }
-
-            }).unwrap();
+            });
             internal = Some(string_internal.to_string());
         )*