]> Untitled Git - bdk/commitdiff
[keys] Add a way to restrict the networks in which keys are valid
authorAlekos Filini <alekos.filini@gmail.com>
Mon, 21 Sep 2020 13:44:07 +0000 (15:44 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Thu, 24 Sep 2020 07:53:51 +0000 (09:53 +0200)
Thanks to the `ToWalletDescriptor` trait we can also very easily validate the checksum
for descriptors that are loaded from strings, if they contain one. Fixes #20.

src/descriptor/dsl.rs
src/descriptor/error.rs
src/descriptor/mod.rs
src/error.rs
src/keys/bip39.rs
src/keys/mod.rs
src/wallet/export.rs
src/wallet/mod.rs

index 0b9853b3386cbebcfd951645425df4ab05ecaabe..7fb28007d88552c43c98a1b8a6f7c623e96dc3d1 100644 (file)
@@ -29,7 +29,7 @@
 macro_rules! impl_top_level_sh {
     ( $descriptor_variant:ident, $( $minisc:tt )* ) => {
         $crate::fragment!($( $minisc )*)
-            .map(|(minisc, keymap)|($crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::$descriptor_variant(minisc), keymap))
+            .map(|(minisc, keymap, networks)|($crate::miniscript::Descriptor::<$crate::miniscript::descriptor::DescriptorPublicKey>::$descriptor_variant(minisc), keymap, networks))
     };
 }
 
@@ -40,13 +40,14 @@ macro_rules! impl_top_level_pk {
         use $crate::keys::{DescriptorKey, ToDescriptorKey};
 
         $key.to_descriptor_key()
-            .and_then(|key: DescriptorKey<$ctx>| key.into_key_and_secret())
-            .map(|(pk, key_map)| {
+            .and_then(|key: DescriptorKey<$ctx>| key.extract())
+            .map(|(pk, key_map, valid_networks)| {
                 (
                     $crate::miniscript::Descriptor::<
                         $crate::miniscript::descriptor::DescriptorPublicKey,
                     >::$descriptor_variant(pk),
                     key_map,
+                    valid_networks,
                 )
             })
     }};
@@ -57,7 +58,8 @@ macro_rules! impl_top_level_pk {
 macro_rules! impl_modifier {
     ( $terminal_variant:ident, $( $inner:tt )* ) => {
         $crate::fragment!($( $inner )*)
-            .and_then(|(minisc, keymap)| Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(std::sync::Arc::new(minisc)))?, keymap)))
+            .map_err(|e| -> $crate::Error { e.into() })
+            .and_then(|(minisc, keymap, networks)| Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(std::sync::Arc::new(minisc)))?, keymap, networks)))
     };
 }
 
@@ -69,7 +71,13 @@ macro_rules! impl_leaf_opcode {
             $crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
         )
         .map_err($crate::Error::Miniscript)
-        .map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
+        .map(|minisc| {
+            (
+                minisc,
+                $crate::miniscript::descriptor::KeyMap::default(),
+                $crate::keys::any_network(),
+            )
+        })
     };
 }
 
@@ -81,7 +89,13 @@ macro_rules! impl_leaf_opcode_value {
             $crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
         )
         .map_err($crate::Error::Miniscript)
-        .map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
+        .map(|minisc| {
+            (
+                minisc,
+                $crate::miniscript::descriptor::KeyMap::default(),
+                $crate::keys::any_network(),
+            )
+        })
     };
 }
 
@@ -93,7 +107,13 @@ macro_rules! impl_leaf_opcode_value_two {
             $crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
         )
         .map_err($crate::Error::Miniscript)
-        .map(|minisc| (minisc, $crate::miniscript::descriptor::KeyMap::default()))
+        .map(|minisc| {
+            (
+                minisc,
+                $crate::miniscript::descriptor::KeyMap::default(),
+                $crate::keys::any_network(),
+            )
+        })
     };
 }
 
@@ -103,14 +123,14 @@ macro_rules! impl_node_opcode_two {
     ( $terminal_variant:ident, ( $( $a:tt )* ), ( $( $b:tt )* ) ) => {
         $crate::fragment!($( $a )*)
             .and_then(|a| Ok((a, $crate::fragment!($( $b )*)?)))
-            .and_then(|((a_minisc, mut a_keymap), (b_minisc, b_keymap))| {
+            .and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks))| {
                 // join key_maps
                 a_keymap.extend(b_keymap.into_iter());
 
                 Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
                     std::sync::Arc::new(a_minisc),
                     std::sync::Arc::new(b_minisc),
-                ))?, a_keymap))
+                ))?, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
             })
     };
 }
@@ -121,23 +141,26 @@ macro_rules! impl_node_opcode_three {
     ( $terminal_variant:ident, ( $( $a:tt )* ), ( $( $b:tt )* ), ( $( $c:tt )* ) ) => {
         $crate::fragment!($( $a )*)
             .and_then(|a| Ok((a, $crate::fragment!($( $b )*)?, $crate::fragment!($( $c )*)?)))
-            .and_then(|((a_minisc, mut a_keymap), (b_minisc, b_keymap), (c_minisc, c_keymap))| {
+            .and_then(|((a_minisc, mut a_keymap, a_networks), (b_minisc, b_keymap, b_networks), (c_minisc, c_keymap, c_networks))| {
                 // join key_maps
                 a_keymap.extend(b_keymap.into_iter());
                 a_keymap.extend(c_keymap.into_iter());
 
+                let networks = $crate::keys::merge_networks(&a_networks, &b_networks);
+                let networks = $crate::keys::merge_networks(&networks, &c_networks);
+
                 Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
                     std::sync::Arc::new(a_minisc),
                     std::sync::Arc::new(b_minisc),
                     std::sync::Arc::new(c_minisc),
-                ))?, a_keymap))
+                ))?, a_keymap, networks))
             })
     };
 }
 
 /// Macro to write full descriptors with code
 ///
-/// This macro expands to an object of type `Result<(Descriptor<DescriptorPublicKey>, KeyMap), Error>`.
+/// This macro expands to an object of type `Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), Error>`.
 ///
 /// ## Example
 ///
@@ -147,7 +170,7 @@ macro_rules! impl_node_opcode_three {
 /// # use std::str::FromStr;
 /// let my_key = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
 /// let my_timelock = 50;
-/// let (my_descriptor, my_keys_map) = bdk::descriptor!(sh ( wsh ( and_v (+v pk my_key), ( older my_timelock ))))?;
+/// let (my_descriptor, my_keys_map, networks) = bdk::descriptor!(sh ( wsh ( and_v (+v pk my_key), ( older my_timelock ))))?;
 /// # Ok::<(), Box<dyn std::error::Error>>(())
 /// ```
 ///
@@ -165,7 +188,7 @@ macro_rules! impl_node_opcode_three {
 /// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
 /// let my_timelock = 50;
 ///
-/// let (descriptor_a, key_map_a) = bdk::descriptor! {
+/// let (descriptor_a, key_map_a, networks) = bdk::descriptor! {
 ///     wsh (
 ///         thresh 2, (pk my_key_1), (+s pk my_key_2), (+s+d+v older my_timelock)
 ///     )
@@ -176,7 +199,7 @@ macro_rules! impl_node_opcode_three {
 ///     bdk::fragment!(+s pk my_key_2)?,
 ///     bdk::fragment!(+s+d+v older my_timelock)?,
 /// ];
-/// let (descriptor_b, mut key_map_b) = bdk::descriptor!( wsh ( thresh_vec 2, b_items ) )?;
+/// let (descriptor_b, mut key_map_b, networks) = bdk::descriptor!( wsh ( thresh_vec 2, b_items ) )?;
 ///
 /// assert_eq!(descriptor_a, descriptor_b);
 /// assert_eq!(key_map_a.len(), key_map_b.len());
@@ -192,7 +215,7 @@ macro_rules! impl_node_opcode_three {
 /// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
 /// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
 ///
-/// let (descriptor, key_map) = bdk::descriptor! {
+/// let (descriptor, key_map, networks) = bdk::descriptor! {
 ///     wsh (
 ///         multi 2, my_key_1, my_key_2
 ///     )
@@ -205,10 +228,9 @@ macro_rules! impl_node_opcode_three {
 /// Native-Segwit single-sig, equivalent to: `wpkh(...)`
 ///
 /// ```
-/// # use std::str::FromStr;
 /// let my_key = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
 ///
-/// let (descriptor, key_map) = bdk::descriptor!(wpkh ( my_key ) )?;
+/// let (descriptor, key_map, networks) = bdk::descriptor!(wpkh ( my_key ) )?;
 /// # Ok::<(), Box<dyn std::error::Error>>(())
 /// ```
 #[macro_export]
@@ -247,7 +269,7 @@ macro_rules! descriptor {
 
 /// Macro to write descriptor fragments with code
 ///
-/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap), Error>`. It allows writing
+/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap, ValidNetworks), Error>`. It allows writing
 /// fragments of larger descriptors that can be pieced together using `fragment!(thresh_vec ...)`.
 #[macro_export]
 macro_rules! fragment {
@@ -291,8 +313,7 @@ macro_rules! fragment {
         $crate::impl_leaf_opcode!(False)
     });
     ( pk_k $key:expr ) => ({
-        use $crate::keys::ToDescriptorKey;
-        $key.into_miniscript_and_secret()
+        $crate::keys::make_pk($key)
     });
     ( pk $key:expr ) => ({
         $crate::fragment!(+c pk_k $key)
@@ -342,15 +363,18 @@ macro_rules! fragment {
     ( thresh_vec $thresh:expr, $items:expr ) => ({
         use $crate::miniscript::descriptor::KeyMap;
 
-        let (items, key_maps): (Vec<_>, Vec<_>) = $items.into_iter().unzip();
+        let (items, key_maps_networks): (Vec<_>, Vec<_>) = $items.into_iter().map(|(a, b, c)| (a, (b, c))).unzip();
         let items = items.into_iter().map(std::sync::Arc::new).collect();
-        let key_maps = key_maps.into_iter().fold(KeyMap::default(), |mut acc, map| {
-            acc.extend(map.into_iter());
-            acc
+
+        let (key_maps, valid_networks) = key_maps_networks.into_iter().fold((KeyMap::default(), $crate::keys::any_network()), |(mut keys_acc, net_acc), (key, net)| {
+            keys_acc.extend(key.into_iter());
+            let net_acc = $crate::keys::merge_networks(&net_acc, &net);
+
+            (keys_acc, net_acc)
         });
 
         $crate::impl_leaf_opcode_value_two!(Thresh, $thresh, items)
-            .map(|(minisc, _)| (minisc, key_maps))
+            .map(|(minisc, _, _)| (minisc, key_maps, valid_networks))
     });
     ( thresh $thresh:expr $(, ( $( $item:tt )* ) )+ ) => ({
         let mut items = vec![];
index 47a2473d80d6abd9cb94cd4818200f87bc622a72..2bd0629f4a3aea34ee65c5428b662f3c65964fcc 100644 (file)
@@ -31,7 +31,9 @@ pub enum Error {
     InvalidPrefix(Vec<u8>),
     HardenedDerivationOnXpub,
     MalformedInput,
+
     KeyParsingError(String),
+    Key(crate::keys::KeyError),
 
     Policy(crate::descriptor::policy::PolicyError),
 
@@ -50,6 +52,16 @@ pub enum Error {
     Hex(bitcoin::hashes::hex::Error),
 }
 
+impl From<crate::keys::KeyError> for Error {
+    fn from(key_error: crate::keys::KeyError) -> Error {
+        match key_error {
+            crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
+            crate::keys::KeyError::BIP32(inner) => Error::BIP32(inner),
+            e @ _ => Error::Key(e),
+        }
+    }
+}
+
 impl std::fmt::Display for Error {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{:?}", self)
index 10283703996c3680638645abba10b919794b9404..cd76aa38ff1316721ad5d9ffcb10d686473f3520 100644 (file)
@@ -35,7 +35,7 @@ use bitcoin::hashes::hash160;
 use bitcoin::secp256k1::Secp256k1;
 use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
 use bitcoin::util::psbt;
-use bitcoin::{PublicKey, Script, TxOut};
+use bitcoin::{Network, PublicKey, Script, TxOut};
 
 use miniscript::descriptor::{DescriptorPublicKey, DescriptorXKey, InnerXKey, KeyMap};
 pub use miniscript::{
@@ -50,6 +50,7 @@ pub mod policy;
 pub use self::checksum::get_checksum;
 use self::error::Error;
 pub use self::policy::Policy;
+use crate::keys::{KeyError, ToDescriptorKey, ValidNetworks};
 use crate::wallet::signer::SignersContainer;
 
 /// Alias for a [`Descriptor`] that can contain extended keys using [`DescriptorPublicKey`]
@@ -62,32 +63,124 @@ pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
 /// [`psbt::Output`]: bitcoin::util::psbt::Output
 pub type HDKeyPaths = BTreeMap<PublicKey, (Fingerprint, DerivationPath)>;
 
-/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet
+/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
 pub trait ToWalletDescriptor {
-    fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error>;
+    fn to_wallet_descriptor(
+        self,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), KeyError>;
 }
 
 impl ToWalletDescriptor for &str {
-    fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
-        Ok(ExtendedDescriptor::parse_secret(self)?)
+    fn to_wallet_descriptor(
+        self,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
+        let descriptor = if self.contains("#") {
+            let parts: Vec<&str> = self.splitn(2, "#").collect();
+            if !get_checksum(parts[0])
+                .ok()
+                .map(|computed| computed == parts[1])
+                .unwrap_or(false)
+            {
+                return Err(KeyError::InvalidChecksum);
+            }
+
+            parts[0]
+        } else {
+            self
+        };
+
+        ExtendedDescriptor::parse_secret(descriptor)?.to_wallet_descriptor(network)
     }
 }
 
 impl ToWalletDescriptor for &String {
-    fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
-        self.as_str().to_wallet_descriptor()
+    fn to_wallet_descriptor(
+        self,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
+        self.as_str().to_wallet_descriptor(network)
+    }
+}
+
+impl ToWalletDescriptor for ExtendedDescriptor {
+    fn to_wallet_descriptor(
+        self,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
+        (self, KeyMap::default()).to_wallet_descriptor(network)
     }
 }
 
 impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
-    fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
-        Ok(self)
+    fn to_wallet_descriptor(
+        self,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
+        use crate::keys::DescriptorKey;
+
+        // check the network for the keys
+        let translated = self.0.translate_pk(
+            |pk| {
+                let (pk, _, networks) = if self.0.is_witness() {
+                    let desciptor_key: DescriptorKey<miniscript::Segwitv0> =
+                        pk.clone().to_descriptor_key()?;
+                    desciptor_key.extract()?
+                } else {
+                    let desciptor_key: DescriptorKey<miniscript::Legacy> =
+                        pk.clone().to_descriptor_key()?;
+                    desciptor_key.extract()?
+                };
+
+                if networks.contains(&network) {
+                    Ok(pk)
+                } else {
+                    Err(KeyError::InvalidNetwork)
+                }
+            },
+            |pkh| Ok::<_, KeyError>(*pkh),
+        )?;
+
+        Ok((translated, self.1))
     }
 }
 
-impl ToWalletDescriptor for ExtendedDescriptor {
-    fn to_wallet_descriptor(self) -> Result<(ExtendedDescriptor, KeyMap), Error> {
-        (self, KeyMap::default()).to_wallet_descriptor()
+impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap, ValidNetworks) {
+    fn to_wallet_descriptor(
+        self,
+        network: Network,
+    ) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
+        let valid_networks = &self.2;
+
+        // fixup the network for keys that need it
+        let translated = self.0.translate_pk(
+            |pk| {
+                if valid_networks.contains(&network) {
+                    // workaround for xpubs generated by other key types, like bip39: since when the
+                    // conversion is made one network has to be chosen, what we generally choose
+                    // "mainnet", but then override the set of valid networks to specify that all of
+                    // them are valid. here we reset the network to make sure the wallet struct gets a
+                    // descriptor with the right network everywhere.
+                    let pk = match pk {
+                        DescriptorPublicKey::XPub(ref xpub) => {
+                            let mut xpub = xpub.clone();
+                            xpub.xkey.network = network;
+
+                            DescriptorPublicKey::XPub(xpub)
+                        }
+                        other @ _ => other.clone(),
+                    };
+
+                    Ok(pk)
+                } else {
+                    Err(KeyError::InvalidNetwork)
+                }
+            },
+            |pkh| Ok::<_, KeyError>(*pkh),
+        )?;
+
+        Ok((translated, self.1))
     }
 }
 
@@ -355,7 +448,7 @@ mod test {
 
     use bitcoin::consensus::encode::deserialize;
     use bitcoin::hashes::hex::FromHex;
-    use bitcoin::util::psbt;
+    use bitcoin::util::{bip32, psbt};
 
     use super::*;
     use crate::psbt::PSBTUtils;
@@ -467,4 +560,25 @@ mod test {
             .derive_from_psbt_input(&psbt.inputs[0], psbt.get_utxo_for(0))
             .is_some());
     }
+
+    #[test]
+    fn test_to_wallet_descriptor_fixup_networks() {
+        use crate::keys::{any_network, ToDescriptorKey};
+
+        let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap();
+        let path = bip32::DerivationPath::from_str("m/0").unwrap();
+
+        // here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
+        // we are using an "xpub"
+        let key = (xpub, path).to_descriptor_key().unwrap();
+        // override it with any. this happens in some key conversions, like bip39
+        let key = key.override_valid_networks(any_network());
+
+        // 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();
+
+        assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
+    }
 }
index b773f5f9e99301bfac45515f601c0326c30016a9..40f5820b8856894f66d16f14bace673dba6b866e 100644 (file)
@@ -48,6 +48,8 @@ pub enum Error {
         required: crate::types::FeeRate,
     },
 
+    Key(crate::keys::KeyError),
+
     ChecksumMismatch,
     DifferentDescriptorStructure,
 
@@ -114,6 +116,17 @@ impl_error!(
 );
 impl_error!(crate::wallet::signer::SignerError, Signer);
 
+impl From<crate::keys::KeyError> for Error {
+    fn from(key_error: crate::keys::KeyError) -> Error {
+        match key_error {
+            crate::keys::KeyError::Miniscript(inner) => Error::Miniscript(inner),
+            crate::keys::KeyError::BIP32(inner) => Error::BIP32(inner),
+            crate::keys::KeyError::InvalidChecksum => Error::ChecksumMismatch,
+            e @ _ => Error::Key(e),
+        }
+    }
+}
+
 impl_error!(bitcoin::consensus::encode::Error, Encode);
 impl_error!(miniscript::Error, Miniscript);
 impl_error!(bitcoin::util::bip32::Error, BIP32);
index 005d0f99f0db42fb7682ab3e0bff8dfd86b1af13..d6e787675116e34705b25075d5a81ec6659cbf44 100644 (file)
 use bitcoin::util::bip32;
 use bitcoin::Network;
 
+use miniscript::ScriptContext;
+
 use bip39::{Mnemonic, Seed};
 
-use super::{DescriptorKey, ToDescriptorKey};
-use crate::Error;
+use super::{any_network, DescriptorKey, KeyError, ToDescriptorKey};
 
 pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
 
-impl ToDescriptorKey for (Seed, bip32::DerivationPath) {
-    fn to_descriptor_key(self) -> Result<DescriptorKeyError> {
+impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (Seed, bip32::DerivationPath) {
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
         let xprv = bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self.0.as_bytes())?;
-        (xprv, self.1).to_descriptor_key()
+        let descriptor_key = (xprv, self.1).to_descriptor_key()?;
+
+        // here we must choose one network to build the xpub, but since the bip39 standard doesn't
+        // encode the network the xpub we create is actually valid everywhere. so we override the
+        // valid networks with `any_network()`.
+        Ok(descriptor_key.override_valid_networks(any_network()))
     }
 }
 
-impl ToDescriptorKey for (MnemonicWithPassphrase, bip32::DerivationPath) {
-    fn to_descriptor_key(self) -> Result<DescriptorKeyError> {
+impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (MnemonicWithPassphrase, bip32::DerivationPath) {
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
         let (mnemonic, passphrase) = self.0;
         let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or(""));
         (seed, self.1).to_descriptor_key()
     }
 }
 
-impl ToDescriptorKey for (Mnemonic, bip32::DerivationPath) {
-    fn to_descriptor_key(self) -> Result<DescriptorKeyError> {
+impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (Mnemonic, bip32::DerivationPath) {
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
         ((self.0, None), self.1).to_descriptor_key()
     }
 }
@@ -74,9 +80,10 @@ mod test {
         let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
 
         let key = (mnemonic, path);
-        let (desc, keys) = crate::descriptor!(wpkh(key)).unwrap();
+        let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
         assert_eq!(desc.to_string(), "wpkh([be83839f/44'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)");
         assert_eq!(keys.len(), 1);
+        assert_eq!(networks.len(), 3);
     }
 
     #[test]
@@ -87,8 +94,9 @@ mod test {
         let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
 
         let key = ((mnemonic, Some("passphrase".into())), path);
-        let (desc, keys) = crate::descriptor!(wpkh(key)).unwrap();
+        let (desc, keys, networks) = crate::descriptor!(wpkh(key)).unwrap();
         assert_eq!(desc.to_string(), "wpkh([8f6cb80c/44'/0'/0']xpub6DWYS8bbihFevy29M4cbw4ZR3P5E12jB8R88gBDWCTCNpYiDHhYWNywrCF9VZQYagzPmsZpxXpytzSoxynyeFr4ZyzheVjnpLKuse4fiwZw/0/*)");
         assert_eq!(keys.len(), 1);
+        assert_eq!(networks.len(), 3);
     }
 }
index a8148aafd5ac5eaa944f4818d6e7a828c647ba03..397822e86347fccfe0e0cb95b216028081554ad2 100644 (file)
 //! Key formats
 
 use std::any::TypeId;
+use std::collections::HashSet;
 use std::marker::PhantomData;
 
 use bitcoin::util::bip32;
-use bitcoin::{PrivateKey, PublicKey};
+use bitcoin::{Network, PrivateKey, PublicKey};
 
 use miniscript::descriptor::{DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap};
 pub use miniscript::ScriptContext;
 use miniscript::{Miniscript, Terminal};
 
-use crate::Error;
-
 #[cfg(feature = "keys-bip39")]
 #[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
 pub mod bip39;
 
+/// Set of valid networks for a key
+pub type ValidNetworks = HashSet<Network>;
+
+/// Create a set containing mainnet, testnet and regtest
+pub fn any_network() -> ValidNetworks {
+    vec![Network::Bitcoin, Network::Testnet, Network::Regtest]
+        .into_iter()
+        .collect()
+}
+/// Create a set only containing mainnet
+pub fn mainnet_network() -> ValidNetworks {
+    vec![Network::Bitcoin].into_iter().collect()
+}
+/// Create a set containing testnet and regtest
+pub fn test_networks() -> ValidNetworks {
+    vec![Network::Testnet, Network::Regtest]
+        .into_iter()
+        .collect()
+}
+/// Compute the intersection of two sets
+pub fn merge_networks(a: &ValidNetworks, b: &ValidNetworks) -> ValidNetworks {
+    a.intersection(b).cloned().collect()
+}
+
 /// Container for public or secret keys
 pub enum DescriptorKey<Ctx: ScriptContext> {
-    Public(DescriptorPublicKey, PhantomData<Ctx>),
-    Secret(DescriptorSecretKey, PhantomData<Ctx>),
+    #[doc(hidden)]
+    Public(DescriptorPublicKey, ValidNetworks, PhantomData<Ctx>),
+    #[doc(hidden)]
+    Secret(DescriptorSecretKey, ValidNetworks, PhantomData<Ctx>),
 }
 
 impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
+    /// Create an instance given a public key and a set of valid networks
+    pub fn from_public(public: DescriptorPublicKey, networks: ValidNetworks) -> Self {
+        DescriptorKey::Public(public, networks, PhantomData)
+    }
+
+    /// Create an instance given a secret key and a set of valid networks
+    pub fn from_secret(secret: DescriptorSecretKey, networks: ValidNetworks) -> Self {
+        DescriptorKey::Secret(secret, networks, PhantomData)
+    }
+
+    /// Override the computed set of valid networks
+    pub fn override_valid_networks(self, networks: ValidNetworks) -> Self {
+        match self {
+            DescriptorKey::Public(key, _, _) => DescriptorKey::Public(key, networks, PhantomData),
+            DescriptorKey::Secret(key, _, _) => DescriptorKey::Secret(key, networks, PhantomData),
+        }
+    }
+
     // This method is used internally by `bdk::fragment!` and `bdk::descriptor!`. It has to be
     // public because it is effectively called by external crates, once the macros are expanded,
     // but since it is not meant to be part of the public api we hide it from the docs.
     #[doc(hidden)]
-    pub fn into_key_and_secret(self) -> Result<(DescriptorPublicKey, KeyMap), Error> {
+    pub fn extract(self) -> Result<(DescriptorPublicKey, KeyMap, ValidNetworks), KeyError> {
         match self {
-            DescriptorKey::Public(public, _) => Ok((public, KeyMap::default())),
-            DescriptorKey::Secret(secret, _) => {
+            DescriptorKey::Public(public, valid_networks, _) => {
+                Ok((public, KeyMap::default(), valid_networks))
+            }
+            DescriptorKey::Secret(secret, valid_networks, _) => {
                 let mut key_map = KeyMap::with_capacity(1);
 
                 let public = secret
@@ -62,12 +107,13 @@ impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
                     .map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
                 key_map.insert(public.clone(), secret);
 
-                Ok((public, key_map))
+                Ok((public, key_map, valid_networks))
             }
         }
     }
 }
 
+/// Enum representation of the known valid [`ScriptContext`]s
 #[derive(Debug, Eq, PartialEq, Copy, Clone)]
 pub enum ScriptContextEnum {
     Legacy,
@@ -84,6 +130,7 @@ impl ScriptContextEnum {
     }
 }
 
+/// Trait that adds extra useful methods to [`ScriptContext`]s
 pub trait ExtScriptContext: ScriptContext {
     fn as_enum() -> ScriptContextEnum;
 
@@ -116,7 +163,7 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
 /// For key types that do care about this, the [`ExtScriptContext`] trait provides some useful
 /// methods that can be used to check at runtime which `Ctx` is being used.
 ///
-/// For key types that that do not need to check this at runtime (because they can only work within a
+/// For key types that can do this check statically (because they can only work within a
 /// single `Ctx`), the "specialized" trait can be implemented to make the compiler handle the type
 /// checking.
 ///
@@ -127,15 +174,14 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
 /// ```
 /// use bdk::bitcoin::PublicKey;
 ///
-/// use bdk::keys::{ScriptContext, ToDescriptorKey, DescriptorKey};
-/// use bdk::Error;
+/// use bdk::keys::{ScriptContext, ToDescriptorKey, DescriptorKey, KeyError};
 ///
 /// pub struct MyKeyType {
 ///     pubkey: PublicKey,
 /// }
 ///
 /// impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for MyKeyType {
-///     fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
+///     fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
 ///         self.pubkey.to_descriptor_key()
 ///     }
 /// }
@@ -146,8 +192,7 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
 /// ```
 /// use bdk::bitcoin::PublicKey;
 ///
-/// use bdk::keys::{ExtScriptContext, ScriptContext, ToDescriptorKey, DescriptorKey};
-/// use bdk::Error;
+/// use bdk::keys::{ExtScriptContext, ScriptContext, ToDescriptorKey, DescriptorKey, KeyError};
 ///
 /// pub struct MyKeyType {
 ///     is_legacy: bool,
@@ -155,11 +200,11 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
 /// }
 ///
 /// impl<Ctx: ScriptContext + 'static> ToDescriptorKey<Ctx> for MyKeyType {
-///     fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
+///     fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
 ///         if Ctx::is_legacy() == self.is_legacy {
 ///             self.pubkey.to_descriptor_key()
 ///         } else {
-///             Err(Error::Generic("Invalid key context".into()))
+///             Err(KeyError::InvalidScriptContext)
 ///         }
 ///     }
 /// }
@@ -176,15 +221,14 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
 /// use std::str::FromStr;
 /// use bdk::bitcoin::PublicKey;
 ///
-/// use bdk::keys::{ToDescriptorKey, DescriptorKey};
-/// use bdk::Error;
+/// use bdk::keys::{ToDescriptorKey, DescriptorKey, KeyError};
 ///
 /// pub struct MySegwitOnlyKeyType {
 ///     pubkey: PublicKey,
 /// }
 ///
 /// impl ToDescriptorKey<bdk::miniscript::Segwitv0> for MySegwitOnlyKeyType {
-///     fn to_descriptor_key(self) -> Result<DescriptorKey<bdk::miniscript::Segwitv0>, Error> {
+///     fn to_descriptor_key(self) -> Result<DescriptorKey<bdk::miniscript::Segwitv0>, KeyError> {
 ///         self.pubkey.to_descriptor_key()
 ///     }
 /// }
@@ -192,25 +236,28 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
 /// let key = MySegwitOnlyKeyType {
 ///     pubkey: PublicKey::from_str("...")?,
 /// };
-/// let (descriptor, _) = bdk::descriptor!(pkh ( key ) )?;
-/// //                                    ^^^^^ changing this to `wpkh` would make it compile
+/// let (descriptor, _, _) = bdk::descriptor!(pkh ( key ) )?;
+/// //                                       ^^^^^ changing this to `wpkh` would make it compile
 ///
 /// # Ok::<_, Box<dyn std::error::Error>>(())
 /// ```
 pub trait ToDescriptorKey<Ctx: ScriptContext>: Sized {
     /// Turn the key into a [`DescriptorKey`] within the requested [`ScriptContext`]
-    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error>;
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError>;
+}
 
-    // Used internally by `bdk::fragment!` to build `pk_k()` fragments
-    #[doc(hidden)]
-    fn into_miniscript_and_secret(
-        self,
-    ) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap), Error> {
-        let descriptor_key = self.to_descriptor_key()?;
-        let (key, key_map) = descriptor_key.into_key_and_secret()?;
+// Used internally by `bdk::fragment!` to build `pk_k()` fragments
+#[doc(hidden)]
+pub fn make_pk<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
+    descriptor_key: Pk,
+) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), KeyError> {
+    let (key, key_map, valid_networks) = descriptor_key.to_descriptor_key()?.extract()?;
 
-        Ok((Miniscript::from_ast(Terminal::PkK(key))?, key_map))
-    }
+    Ok((
+        Miniscript::from_ast(Terminal::PkK(key))?,
+        key_map,
+        valid_networks,
+    ))
 }
 
 // Used internally by `bdk::fragment!` to build `multi()` fragments
@@ -218,90 +265,136 @@ pub trait ToDescriptorKey<Ctx: ScriptContext>: Sized {
 pub fn make_multi<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
     thresh: usize,
     pks: Vec<Pk>,
-) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap), Error> {
-    let (pks, key_maps): (Vec<_>, Vec<_>) = pks
+) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), KeyError> {
+    let (pks, key_maps_networks): (Vec<_>, Vec<_>) = pks
         .into_iter()
-        .map(|key| {
-            key.to_descriptor_key()
-                .and_then(DescriptorKey::into_key_and_secret)
-        })
+        .map(|key| Ok::<_, KeyError>(key.to_descriptor_key()?.extract()?))
         .collect::<Result<Vec<_>, _>>()?
         .into_iter()
+        .map(|(a, b, c)| (a, (b, c)))
         .unzip();
 
-    let key_map = key_maps
-        .into_iter()
-        .fold(KeyMap::default(), |mut acc, map| {
-            acc.extend(map.into_iter());
-            acc
-        });
+    let (key_map, valid_networks) = key_maps_networks.into_iter().fold(
+        (KeyMap::default(), any_network()),
+        |(mut keys_acc, net_acc), (key, net)| {
+            keys_acc.extend(key.into_iter());
+            let net_acc = merge_networks(&net_acc, &net);
 
-    Ok((Miniscript::from_ast(Terminal::Multi(thresh, pks))?, key_map))
+            (keys_acc, net_acc)
+        },
+    );
+
+    Ok((
+        Miniscript::from_ast(Terminal::Multi(thresh, pks))?,
+        key_map,
+        valid_networks,
+    ))
 }
 
 /// The "identity" conversion is used internally by some `bdk::fragment`s
 impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorKey<Ctx> {
-    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
         Ok(self)
     }
 }
 
 impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorPublicKey {
-    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
-        Ok(DescriptorKey::Public(self, PhantomData))
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let networks = match self {
+            DescriptorPublicKey::PubKey(_) => any_network(),
+            DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. })
+                if xkey.network == Network::Bitcoin =>
+            {
+                mainnet_network()
+            }
+            _ => test_networks(),
+        };
+
+        Ok(DescriptorKey::from_public(self, networks))
     }
 }
 
 impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for PublicKey {
-    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
-        Ok(DescriptorKey::Public(
-            DescriptorPublicKey::PubKey(self),
-            PhantomData,
-        ))
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        DescriptorPublicKey::PubKey(self).to_descriptor_key()
     }
 }
 
 /// This assumes that "is_wildcard" is true, since this is generally the way extended keys are used
 impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (bip32::ExtendedPubKey, bip32::DerivationPath) {
-    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
-        Ok(DescriptorKey::Public(
-            DescriptorPublicKey::XPub(DescriptorXKey {
-                source: None,
-                xkey: self.0,
-                derivation_path: self.1,
-                is_wildcard: true,
-            }),
-            PhantomData,
-        ))
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        DescriptorPublicKey::XPub(DescriptorXKey {
+            source: None,
+            xkey: self.0,
+            derivation_path: self.1,
+            is_wildcard: true,
+        })
+        .to_descriptor_key()
     }
 }
 
 impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for DescriptorSecretKey {
-    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
-        Ok(DescriptorKey::Secret(self, PhantomData))
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        let networks = match self {
+            DescriptorSecretKey::PrivKey(sk) if sk.network == Network::Bitcoin => mainnet_network(),
+            DescriptorSecretKey::XPrv(DescriptorXKey { xkey, .. })
+                if xkey.network == Network::Bitcoin =>
+            {
+                mainnet_network()
+            }
+            _ => test_networks(),
+        };
+
+        Ok(DescriptorKey::from_secret(self, networks))
     }
 }
 
 impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for PrivateKey {
-    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
-        Ok(DescriptorKey::Secret(
-            DescriptorSecretKey::PrivKey(self),
-            PhantomData,
-        ))
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        DescriptorSecretKey::PrivKey(self).to_descriptor_key()
     }
 }
 
 /// This assumes that "is_wildcard" is true, since this is generally the way extended keys are used
 impl<Ctx: ScriptContext> ToDescriptorKey<Ctx> for (bip32::ExtendedPrivKey, bip32::DerivationPath) {
-    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, Error> {
-        Ok(DescriptorKey::Secret(
-            DescriptorSecretKey::XPrv(DescriptorXKey {
-                source: None,
-                xkey: self.0,
-                derivation_path: self.1,
-                is_wildcard: true,
-            }),
-            PhantomData,
-        ))
+    fn to_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
+        DescriptorSecretKey::XPrv(DescriptorXKey {
+            source: None,
+            xkey: self.0,
+            derivation_path: self.1,
+            is_wildcard: true,
+        })
+        .to_descriptor_key()
     }
 }
+
+#[derive(Debug)]
+pub enum KeyError {
+    InvalidScriptContext,
+    InvalidNetwork,
+    InvalidChecksum,
+    Message(String),
+
+    BIP32(bitcoin::util::bip32::Error),
+    Miniscript(miniscript::Error),
+}
+
+impl From<miniscript::Error> for KeyError {
+    fn from(inner: miniscript::Error) -> Self {
+        KeyError::Miniscript(inner)
+    }
+}
+
+impl From<bitcoin::util::bip32::Error> for KeyError {
+    fn from(inner: bitcoin::util::bip32::Error) -> Self {
+        KeyError::BIP32(inner)
+    }
+}
+
+impl std::fmt::Display for KeyError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+impl std::error::Error for KeyError {}
index 9f4c3f97655cb25c17d1e6f1daa1d38ef7d3af5a..ecb05af3b60da96ef1eaac2fa16445abf37618ae 100644 (file)
@@ -234,7 +234,7 @@ mod test {
         let wallet: OfflineWallet<_> = Wallet::new_offline(
             descriptor,
             Some(change_descriptor),
-            Network::Testnet,
+            Network::Bitcoin,
             get_test_db(),
         )
         .unwrap();
@@ -256,7 +256,7 @@ mod test {
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
 
         let wallet: OfflineWallet<_> =
-            Wallet::new_offline(descriptor, None, Network::Testnet, get_test_db()).unwrap();
+            Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
         WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
     }
 
@@ -272,7 +272,7 @@ mod test {
         let wallet: OfflineWallet<_> = Wallet::new_offline(
             descriptor,
             Some(change_descriptor),
-            Network::Testnet,
+            Network::Bitcoin,
             get_test_db(),
         )
         .unwrap();
@@ -315,7 +315,7 @@ mod test {
         let wallet: OfflineWallet<_> = Wallet::new_offline(
             descriptor,
             Some(change_descriptor),
-            Network::Testnet,
+            Network::Bitcoin,
             get_test_db(),
         )
         .unwrap();
index 91f8ad8a380b45914e210ffbe21385d43dac3662..55d77642105a168258c07f53242b37c5fb60c0d2 100644 (file)
@@ -113,7 +113,7 @@ where
         network: Network,
         mut database: D,
     ) -> Result<Self, Error> {
-        let (descriptor, keymap) = descriptor.to_wallet_descriptor()?;
+        let (descriptor, keymap) = descriptor.to_wallet_descriptor(network)?;
         database.check_descriptor_checksum(
             ScriptType::External,
             get_checksum(&descriptor.to_string())?.as_bytes(),
@@ -121,7 +121,7 @@ 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()?;
+                let (change_descriptor, change_keymap) = desc.to_wallet_descriptor(network)?;
                 database.check_descriptor_checksum(
                     ScriptType::Internal,
                     get_checksum(&change_descriptor.to_string())?.as_bytes(),
@@ -1737,7 +1737,7 @@ mod test {
         use bitcoin::util::bip32::{DerivationPath, Fingerprint};
         use std::str::FromStr;
 
-        let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
+        let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
             .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
@@ -1758,7 +1758,7 @@ mod test {
         use bitcoin::util::bip32::{DerivationPath, Fingerprint};
         use std::str::FromStr;
 
-        let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
+        let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
         // cache some addresses
         wallet.get_new_address().unwrap();