]> Untitled Git - bdk/commitdiff
refactor(wallet)!: Make Wallet require a change descriptor
authorvalued mammal <valuedmammal@protonmail.com>
Wed, 27 Mar 2024 01:57:10 +0000 (21:57 -0400)
committervalued mammal <valuedmammal@protonmail.com>
Wed, 5 Jun 2024 10:29:52 +0000 (06:29 -0400)
All `Wallet` constructors are modified to require a change
descriptor, where previously it was optional. Additionally
we enforce uniqueness of the change descriptor to avoid
ambiguity when deriving scripts and ensure the wallet will
always have two distinct keystores.

Notable changes

* Add error DescriptorError::ExternalAndInternalAreTheSame
* Remove error CreateTxError::ChangePolicyDescriptor
* No longer rely on `map_keychain`

16 files changed:
crates/hwi/src/lib.rs
crates/wallet/README.md
crates/wallet/examples/compiler.rs
crates/wallet/src/descriptor/error.rs
crates/wallet/src/descriptor/template.rs
crates/wallet/src/wallet/error.rs
crates/wallet/src/wallet/export.rs
crates/wallet/src/wallet/mod.rs
crates/wallet/src/wallet/signer.rs
crates/wallet/tests/common.rs
crates/wallet/tests/psbt.rs
crates/wallet/tests/wallet.rs
example-crates/wallet_electrum/src/main.rs
example-crates/wallet_esplora_async/src/main.rs
example-crates/wallet_esplora_blocking/src/main.rs
example-crates/wallet_rpc/src/main.rs

index 129ceac24831e30bca6cc6ab256576cc312b952d..762af8ff11eb56ba6161fcf8a597ddbbf430baf5 100644 (file)
@@ -20,7 +20,7 @@
 //!
 //! # let mut wallet = Wallet::new_no_persist(
 //! #     "",
-//! #     None,
+//! #     "",
 //! #     Network::Testnet,
 //! # )?;
 //! #
index 37afee3873b460fca568370a633eb5093ab0ac37..73ff4b58dae8619f7e7117fc1ba7779fb0fe7fd6 100644 (file)
@@ -74,7 +74,8 @@ fn main() {
     let db = bdk_file_store::Store::<ChangeSet>::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store");
 
     let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
-    let mut wallet = Wallet::new_or_load(descriptor, None, db, Network::Testnet).expect("create or load wallet");
+    let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)";
+    let mut wallet = Wallet::new_or_load(descriptor, change_descriptor, db, Network::Testnet).expect("create or load wallet");
 
     // Insert a single `TxOut` at `OutPoint` into the wallet.
     let _ = wallet.insert_txout(outpoint, txout);
index 116df4733a6857522834099b3d2580bf88514fcf..a11899f4751d7fe769fb5b975a84c0d73f10c648 100644 (file)
@@ -32,21 +32,52 @@ use bdk_wallet::{KeychainKind, Wallet};
 /// This example demonstrates the interaction between a bdk wallet and miniscript policy.
 
 fn main() -> Result<(), Box<dyn Error>> {
-    // We start with a generic miniscript policy string
-    let policy_str = "or(10@thresh(4,pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2)),1@and(older(4209713),thresh(2,pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),pk(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),pk(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068))))";
+    // We start with a miniscript policy string
+    let policy_str = "or(
+        10@thresh(4,
+            pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2)
+        ),1@and(
+            older(4209713),
+            thresh(2,
+                pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),pk(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),pk(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068)
+            )
+        )
+    )"
+    .replace(&[' ', '\n', '\t'][..], "");
+
     println!("Compiling policy: \n{}", policy_str);
 
     // Parse the string as a [`Concrete`] type miniscript policy.
-    let policy = Concrete::<String>::from_str(policy_str)?;
+    let policy = Concrete::<String>::from_str(&policy_str)?;
 
     // Create a `wsh` type descriptor from the policy.
     // `policy.compile()` returns the resulting miniscript from the policy.
-    let descriptor = Descriptor::new_wsh(policy.compile()?)?;
+    let descriptor = Descriptor::new_wsh(policy.compile()?)?.to_string();
+
+    println!("Compiled into Descriptor: \n{}", descriptor);
+
+    // Do the same for another (internal) keychain
+    let policy_str = "or(
+        10@thresh(2,
+            pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec)
+        ),1@and(
+            pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),
+            older(12960)
+        )
+    )"
+    .replace(&[' ', '\n', '\t'][..], "");
 
-    println!("Compiled into following Descriptor: \n{}", descriptor);
+    println!("Compiling internal policy: \n{}", policy_str);
+
+    let policy = Concrete::<String>::from_str(&policy_str)?;
+    let internal_descriptor = Descriptor::new_wsh(policy.compile()?)?.to_string();
+    println!(
+        "Compiled into internal Descriptor: \n{}",
+        internal_descriptor
+    );
 
-    // Create a new wallet from this descriptor
-    let mut wallet = Wallet::new_no_persist(&format!("{}", descriptor), None, Network::Regtest)?;
+    // Create a new wallet from descriptors
+    let mut wallet = Wallet::new_no_persist(&descriptor, &internal_descriptor, Network::Regtest)?;
 
     println!(
         "First derived address from the descriptor: \n{}",
index b2809f2107a8bcdb69dd51169f39236e07f72b92..4d15e3602148d0ab4aa52cb36bbe2fa77c2bd3b1 100644 (file)
@@ -42,6 +42,8 @@ pub enum Error {
     Miniscript(miniscript::Error),
     /// Hex decoding error
     Hex(bitcoin::hex::HexToBytesError),
+    /// The provided wallet descriptors are identical
+    ExternalAndInternalAreTheSame,
 }
 
 impl From<crate::keys::KeyError> for Error {
@@ -79,6 +81,9 @@ impl fmt::Display for Error {
             Self::Pk(err) => write!(f, "Key-related error: {}", err),
             Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
             Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
+            Self::ExternalAndInternalAreTheSame => {
+                write!(f, "External and internal descriptors are the same")
+            }
         }
     }
 }
index 528be1f3b6296c6a4fb3a6c17d15318b756b3577..528f227fb4dad03e458195bacecfae0893d9fa97 100644 (file)
@@ -77,9 +77,12 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
 /// # use bdk_wallet::KeychainKind;
 /// use bdk_wallet::template::P2Pkh;
 ///
-/// let key =
+/// let key_external =
 ///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let mut wallet = Wallet::new_no_persist(P2Pkh(key), None, Network::Testnet)?;
+/// let key_internal =
+///     bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
+/// let mut wallet =
+///     Wallet::new_no_persist(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
 ///
 /// assert_eq!(
 ///     wallet
@@ -107,9 +110,15 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
 /// # use bdk_wallet::KeychainKind;
 /// use bdk_wallet::template::P2Wpkh_P2Sh;
 ///
-/// let key =
+/// let key_external =
 ///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let mut wallet = Wallet::new_no_persist(P2Wpkh_P2Sh(key), None, Network::Testnet)?;
+/// let key_internal =
+///     bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
+/// let mut wallet = Wallet::new_no_persist(
+///     P2Wpkh_P2Sh(key_external),
+///     P2Wpkh_P2Sh(key_internal),
+///     Network::Testnet,
+/// )?;
 ///
 /// assert_eq!(
 ///     wallet
@@ -138,9 +147,12 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
 /// # use bdk_wallet::KeychainKind;
 /// use bdk_wallet::template::P2Wpkh;
 ///
-/// let key =
+/// let key_external =
 ///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let mut wallet = Wallet::new_no_persist(P2Wpkh(key), None, Network::Testnet)?;
+/// let key_internal =
+///     bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
+/// let mut wallet =
+///     Wallet::new_no_persist(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
 ///
 /// assert_eq!(
 ///     wallet
@@ -168,9 +180,12 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
 /// # use bdk_wallet::KeychainKind;
 /// use bdk_wallet::template::P2TR;
 ///
-/// let key =
+/// let key_external =
 ///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let mut wallet = Wallet::new_no_persist(P2TR(key), None, Network::Testnet)?;
+/// let key_internal =
+///     bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
+/// let mut wallet =
+///     Wallet::new_no_persist(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
 ///
 /// assert_eq!(
 ///     wallet
@@ -205,7 +220,7 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
 /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
 /// let mut wallet = Wallet::new_no_persist(
 ///     Bip44(key.clone(), KeychainKind::External),
-///     Some(Bip44(key, KeychainKind::Internal)),
+///     Bip44(key, KeychainKind::Internal),
 ///     Network::Testnet,
 /// )?;
 ///
@@ -242,7 +257,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
 /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
 /// let mut wallet = Wallet::new_no_persist(
 ///     Bip44Public(key.clone(), fingerprint, KeychainKind::External),
-///     Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
+///     Bip44Public(key, fingerprint, KeychainKind::Internal),
 ///     Network::Testnet,
 /// )?;
 ///
@@ -278,7 +293,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
 /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
 /// let mut wallet = Wallet::new_no_persist(
 ///     Bip49(key.clone(), KeychainKind::External),
-///     Some(Bip49(key, KeychainKind::Internal)),
+///     Bip49(key, KeychainKind::Internal),
 ///     Network::Testnet,
 /// )?;
 ///
@@ -315,7 +330,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
 /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
 /// let mut wallet = Wallet::new_no_persist(
 ///     Bip49Public(key.clone(), fingerprint, KeychainKind::External),
-///     Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
+///     Bip49Public(key, fingerprint, KeychainKind::Internal),
 ///     Network::Testnet,
 /// )?;
 ///
@@ -351,7 +366,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
 /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
 /// let mut wallet = Wallet::new_no_persist(
 ///     Bip84(key.clone(), KeychainKind::External),
-///     Some(Bip84(key, KeychainKind::Internal)),
+///     Bip84(key, KeychainKind::Internal),
 ///     Network::Testnet,
 /// )?;
 ///
@@ -388,7 +403,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
 /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
 /// let mut wallet = Wallet::new_no_persist(
 ///     Bip84Public(key.clone(), fingerprint, KeychainKind::External),
-///     Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
+///     Bip84Public(key, fingerprint, KeychainKind::Internal),
 ///     Network::Testnet,
 /// )?;
 ///
@@ -424,7 +439,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
 /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
 /// let mut wallet = Wallet::new_no_persist(
 ///     Bip86(key.clone(), KeychainKind::External),
-///     Some(Bip86(key, KeychainKind::Internal)),
+///     Bip86(key, KeychainKind::Internal),
 ///     Network::Testnet,
 /// )?;
 ///
@@ -461,7 +476,7 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
 /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
 /// let mut wallet = Wallet::new_no_persist(
 ///     Bip86Public(key.clone(), fingerprint, KeychainKind::External),
-///     Some(Bip86Public(key, fingerprint, KeychainKind::Internal)),
+///     Bip86Public(key, fingerprint, KeychainKind::Internal),
 ///     Network::Testnet,
 /// )?;
 ///
index 56612c54e046d0303f9e6386d4afc51de4222874..3504b7d2f2a53700bca0f0722189cd382664dc68 100644 (file)
@@ -90,8 +90,6 @@ pub enum CreateTxError {
     NoUtxosSelected,
     /// Output created is under the dust limit, 546 satoshis
     OutputBelowDustLimit(usize),
-    /// The `change_policy` was set but the wallet does not have a change_descriptor
-    ChangePolicyDescriptor,
     /// There was an error with coin selection
     CoinSelection(coin_selection::Error),
     /// Wallet's UTXO set is not enough to cover recipient's requested plus fee
@@ -177,12 +175,6 @@ impl fmt::Display for CreateTxError {
             CreateTxError::OutputBelowDustLimit(limit) => {
                 write!(f, "Output below the dust limit: {}", limit)
             }
-            CreateTxError::ChangePolicyDescriptor => {
-                write!(
-                    f,
-                    "The `change_policy` can be set only if the wallet has a change_descriptor"
-                )
-            }
             CreateTxError::CoinSelection(e) => e.fmt(f),
             CreateTxError::InsufficientFunds { needed, available } => {
                 write!(
index 9eea7bd6a9fc5b1ca93d3a8a78d906d53af90b2d..3697f91ddd068911f048bf83cb66f4c0eb9b70c8 100644 (file)
@@ -31,7 +31,7 @@
 //! let import = FullyNodedExport::from_str(import)?;
 //! let wallet = Wallet::new_no_persist(
 //!     &import.descriptor(),
-//!     import.change_descriptor().as_ref(),
+//!     &import.change_descriptor().expect("change descriptor"),
 //!     Network::Testnet,
 //! )?;
 //! # Ok::<_, Box<dyn std::error::Error>>(())
@@ -44,7 +44,7 @@
 //! # use bdk_wallet::*;
 //! let wallet = Wallet::new_no_persist(
 //!     "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
-//!     Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
+//!     "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
 //!     Network::Testnet,
 //! )?;
 //! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap();
@@ -142,19 +142,17 @@ impl FullyNodedExport {
             blockheight,
         };
 
-        let change_descriptor = match wallet.public_descriptor(KeychainKind::Internal).is_some() {
-            false => None,
-            true => {
-                let descriptor = wallet
-                    .get_descriptor_for_keychain(KeychainKind::Internal)
-                    .to_string_with_secret(
-                        &wallet
-                            .get_signers(KeychainKind::Internal)
-                            .as_key_map(wallet.secp_ctx()),
-                    );
-                Some(remove_checksum(descriptor))
-            }
+        let change_descriptor = {
+            let descriptor = wallet
+                .get_descriptor_for_keychain(KeychainKind::Internal)
+                .to_string_with_secret(
+                    &wallet
+                        .get_signers(KeychainKind::Internal)
+                        .as_key_map(wallet.secp_ctx()),
+                );
+            Some(remove_checksum(descriptor))
         };
+
         if export.change_descriptor() != change_descriptor {
             return Err("Incompatible change descriptor");
         }
@@ -223,11 +221,7 @@ mod test {
     use super::*;
     use crate::wallet::Wallet;
 
-    fn get_test_wallet(
-        descriptor: &str,
-        change_descriptor: Option<&str>,
-        network: Network,
-    ) -> Wallet {
+    fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
         let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap();
         let transaction = Transaction {
             input: vec![],
@@ -258,7 +252,7 @@ mod test {
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
         let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
 
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
+        let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
         let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
 
         assert_eq!(export.descriptor(), descriptor);
@@ -270,13 +264,14 @@ mod test {
     #[test]
     #[should_panic(expected = "Incompatible change descriptor")]
     fn test_export_no_change() {
-        // This wallet explicitly doesn't have a change descriptor. It should be impossible to
+        // The wallet's change descriptor has no wildcard. It should be impossible to
         // export, because exporting this kind of external descriptor normally implies the
-        // existence of an internal descriptor
+        // existence of a compatible internal descriptor
 
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
+        let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/0)";
 
-        let wallet = get_test_wallet(descriptor, None, Network::Bitcoin);
+        let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
         FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
     }
 
@@ -289,7 +284,7 @@ mod test {
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
         let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
 
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
+        let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
         FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
     }
 
@@ -306,7 +301,7 @@ mod test {
                                        [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
                                  ))";
 
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet);
+        let wallet = get_test_wallet(descriptor, change_descriptor, Network::Testnet);
         let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
 
         assert_eq!(export.descriptor(), descriptor);
@@ -319,7 +314,7 @@ mod test {
     fn test_export_tr() {
         let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
         let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet);
+        let wallet = get_test_wallet(descriptor, change_descriptor, Network::Testnet);
         let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
         assert_eq!(export.descriptor(), descriptor);
         assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
@@ -332,7 +327,7 @@ mod test {
         let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
         let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
 
-        let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
+        let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
         let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
 
         assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}");
index 73fcaa218d15597a635fcb8cb850bc27aa2b7aab..af5f8244a0446e56cf0e64c6886c1d18f6f8718c 100644 (file)
@@ -166,7 +166,7 @@ impl Wallet {
     /// Creates a wallet that does not persist data.
     pub fn new_no_persist<E: IntoWalletDescriptor>(
         descriptor: E,
-        change_descriptor: Option<E>,
+        change_descriptor: E,
         network: Network,
     ) -> Result<Self, DescriptorError> {
         Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
@@ -179,7 +179,7 @@ impl Wallet {
     /// Creates a wallet that does not persist data, with a custom genesis hash.
     pub fn new_no_persist_with_genesis_hash<E: IntoWalletDescriptor>(
         descriptor: E,
-        change_descriptor: Option<E>,
+        change_descriptor: E,
         network: Network,
         genesis_hash: BlockHash,
     ) -> Result<Self, crate::descriptor::DescriptorError> {
@@ -398,7 +398,7 @@ impl Wallet {
     /// Initialize an empty [`Wallet`].
     pub fn new<E: IntoWalletDescriptor>(
         descriptor: E,
-        change_descriptor: Option<E>,
+        change_descriptor: E,
         db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
         network: Network,
     ) -> Result<Self, NewError> {
@@ -412,7 +412,7 @@ impl Wallet {
     /// for syncing from alternative networks.
     pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
         descriptor: E,
-        change_descriptor: Option<E>,
+        change_descriptor: E,
         mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
         network: Network,
         genesis_hash: BlockHash,
@@ -524,7 +524,8 @@ impl Wallet {
             .indexer
             .keychains_added
             .get(&KeychainKind::Internal)
-            .cloned();
+            .ok_or(LoadError::MissingDescriptor)?
+            .clone();
 
         let (signers, change_signers) =
             create_signers(&mut index, &secp, descriptor, change_descriptor, network)
@@ -551,7 +552,7 @@ impl Wallet {
     /// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
     pub fn new_or_load<E: IntoWalletDescriptor>(
         descriptor: E,
-        change_descriptor: Option<E>,
+        change_descriptor: E,
         db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
         network: Network,
     ) -> Result<Self, NewOrLoadError> {
@@ -573,7 +574,7 @@ impl Wallet {
     /// useful for syncing from alternative networks.
     pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
         descriptor: E,
-        change_descriptor: Option<E>,
+        change_descriptor: E,
         mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
         network: Network,
         genesis_hash: BlockHash,
@@ -639,44 +640,32 @@ impl Wallet {
                     });
                 }
 
-                let expected_change_descriptor = if let Some(c) = change_descriptor {
-                    Some(
-                        c.into_wallet_descriptor(&wallet.secp, network)
-                            .map_err(NewOrLoadError::Descriptor)?,
-                    )
-                } else {
-                    None
-                };
+                let (expected_change_descriptor, expected_change_descriptor_keymap) =
+                    change_descriptor
+                        .into_wallet_descriptor(&wallet.secp, network)
+                        .map_err(NewOrLoadError::Descriptor)?;
                 let wallet_change_descriptor =
                     wallet.public_descriptor(KeychainKind::Internal).cloned();
-
-                match (expected_change_descriptor, wallet_change_descriptor) {
-                    (Some((expected_descriptor, expected_keymap)), Some(wallet_descriptor))
-                        if wallet_descriptor == expected_descriptor =>
-                    {
-                        // if expected change descriptor has private keys add them as new signers
-                        if !expected_keymap.is_empty() {
-                            let signer_container = SignersContainer::build(
-                                expected_keymap,
-                                &expected_descriptor,
-                                &wallet.secp,
-                            );
-                            signer_container.signers().into_iter().for_each(|signer| {
-                                wallet.add_signer(
-                                    KeychainKind::Internal,
-                                    SignerOrdering::default(),
-                                    signer.clone(),
-                                )
-                            });
-                        }
-                    }
-                    (None, None) => (),
-                    (_, wallet_descriptor) => {
-                        return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
-                            got: wallet_descriptor,
-                            keychain: KeychainKind::Internal,
-                        });
-                    }
+                if wallet_change_descriptor != Some(expected_change_descriptor.clone()) {
+                    return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
+                        got: wallet_change_descriptor,
+                        keychain: KeychainKind::Internal,
+                    });
+                }
+                // if expected change descriptor has private keys add them as new signers
+                if !expected_change_descriptor_keymap.is_empty() {
+                    let signer_container = SignersContainer::build(
+                        expected_change_descriptor_keymap,
+                        &expected_change_descriptor,
+                        &wallet.secp,
+                    );
+                    signer_container.signers().into_iter().for_each(|signer| {
+                        wallet.add_signer(
+                            KeychainKind::Internal,
+                            SignerOrdering::default(),
+                            signer.clone(),
+                        )
+                    });
                 }
 
                 Ok(wallet)
@@ -717,12 +706,11 @@ impl Wallet {
     /// This panics when the caller requests for an address of derivation index greater than the
     /// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index.
     pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo {
-        let keychain = self.map_keychain(keychain);
         let mut spk_iter = self
             .indexed_graph
             .index
             .unbounded_spk_iter(&keychain)
-            .expect("Must exist (we called map_keychain)");
+            .expect("keychain must exist");
         if !spk_iter.descriptor().has_wildcard() {
             index = 0;
         }
@@ -748,12 +736,11 @@ impl Wallet {
     ///
     /// If writing to persistent storage fails.
     pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
-        let keychain = self.map_keychain(keychain);
         let ((index, spk), index_changeset) = self
             .indexed_graph
             .index
             .reveal_next_spk(&keychain)
-            .expect("Must exist (we called map_keychain)");
+            .expect("keychain must exist");
 
         self.persist
             .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
@@ -780,12 +767,11 @@ impl Wallet {
         keychain: KeychainKind,
         index: u32,
     ) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
-        let keychain = self.map_keychain(keychain);
         let (spk_iter, index_changeset) = self
             .indexed_graph
             .index
             .reveal_to_target(&keychain, index)
-            .expect("must exist (we called map_keychain)");
+            .expect("keychain must exist");
 
         self.persist
             .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
@@ -807,12 +793,11 @@ impl Wallet {
     ///
     /// If writing to persistent storage fails.
     pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
-        let keychain = self.map_keychain(keychain);
         let ((index, spk), index_changeset) = self
             .indexed_graph
             .index
             .next_unused_spk(&keychain)
-            .expect("must exist (we called map_keychain)");
+            .expect("keychain must exist");
 
         self.persist
             .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
@@ -852,7 +837,6 @@ impl Wallet {
         &self,
         keychain: KeychainKind,
     ) -> impl DoubleEndedIterator<Item = AddressInfo> + '_ {
-        let keychain = self.map_keychain(keychain);
         self.indexed_graph
             .index
             .unused_keychain_spks(&keychain)
@@ -934,11 +918,10 @@ impl Wallet {
         &self,
         keychain: KeychainKind,
     ) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
-        let keychain = self.map_keychain(keychain);
         self.indexed_graph
             .index
             .unbounded_spk_iter(&keychain)
-            .expect("Must exist (we called map_keychain)")
+            .expect("keychain must exist")
     }
 
     /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
@@ -1248,7 +1231,9 @@ impl Wallet {
     /// ```
     /// # use bdk_wallet::{Wallet, KeychainKind};
     /// # use bdk_wallet::bitcoin::Network;
-    /// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet)?;
+    /// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
+    /// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)";
+    /// let wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?;
     /// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
     ///     // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
     ///     println!("secret_key: {}", secret_key);
@@ -1307,20 +1292,14 @@ impl Wallet {
     ) -> Result<Psbt, CreateTxError> {
         let keychains: BTreeMap<_, _> = self.indexed_graph.index.keychains().collect();
         let external_descriptor = keychains.get(&KeychainKind::External).expect("must exist");
-        let internal_descriptor = keychains.get(&KeychainKind::Internal);
+        let internal_descriptor = keychains.get(&KeychainKind::Internal).expect("must exist");
 
         let external_policy = external_descriptor
             .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
             .unwrap();
         let internal_policy = internal_descriptor
-            .as_ref()
-            .map(|desc| {
-                Ok::<_, CreateTxError>(
-                    desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
-                        .unwrap(),
-                )
-            })
-            .transpose()?;
+            .extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
+            .unwrap();
 
         // The policy allows spending external outputs, but it requires a policy path that hasn't been
         // provided
@@ -1332,17 +1311,15 @@ impl Wallet {
                 KeychainKind::External,
             ));
         };
-        // Same for the internal_policy path, if present
-        if let Some(internal_policy) = &internal_policy {
-            if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
-                && internal_policy.requires_path()
-                && params.internal_policy_path.is_none()
-            {
-                return Err(CreateTxError::SpendingPolicyRequired(
-                    KeychainKind::Internal,
-                ));
-            };
-        }
+        // Same for the internal_policy path
+        if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
+            && internal_policy.requires_path()
+            && params.internal_policy_path.is_none()
+        {
+            return Err(CreateTxError::SpendingPolicyRequired(
+                KeychainKind::Internal,
+            ));
+        };
 
         let external_requirements = external_policy.get_condition(
             params
@@ -1350,21 +1327,14 @@ impl Wallet {
                 .as_ref()
                 .unwrap_or(&BTreeMap::new()),
         )?;
-        let internal_requirements = internal_policy
-            .map(|policy| {
-                Ok::<_, CreateTxError>(
-                    policy.get_condition(
-                        params
-                            .internal_policy_path
-                            .as_ref()
-                            .unwrap_or(&BTreeMap::new()),
-                    )?,
-                )
-            })
-            .transpose()?;
+        let internal_requirements = internal_policy.get_condition(
+            params
+                .internal_policy_path
+                .as_ref()
+                .unwrap_or(&BTreeMap::new()),
+        )?;
 
-        let requirements =
-            external_requirements.merge(&internal_requirements.unwrap_or_default())?;
+        let requirements = external_requirements.merge(&internal_requirements)?;
 
         let version = match params.version {
             Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0),
@@ -1526,12 +1496,6 @@ impl Wallet {
 
         fee_amount += (fee_rate * tx.weight()).to_sat();
 
-        if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
-            && internal_descriptor.is_none()
-        {
-            return Err(CreateTxError::ChangePolicyDescriptor);
-        }
-
         let (required_utxos, optional_utxos) =
             self.preselect_utxos(&params, Some(current_height.to_consensus_u32()));
 
@@ -1539,12 +1503,12 @@ impl Wallet {
         let drain_script = match params.drain_to {
             Some(ref drain_recipient) => drain_recipient.clone(),
             None => {
-                let change_keychain = self.map_keychain(KeychainKind::Internal);
+                let change_keychain = KeychainKind::Internal;
                 let ((index, spk), index_changeset) = self
                     .indexed_graph
                     .index
                     .next_unused_spk(&change_keychain)
-                    .expect("Keychain exists (we called map_keychain)");
+                    .expect("keychain must exist");
                 let spk = spk.into();
                 self.indexed_graph.index.mark_used(change_keychain, index);
                 self.persist
@@ -1773,9 +1737,11 @@ impl Wallet {
         if tx.output.len() > 1 {
             let mut change_index = None;
             for (index, txout) in tx.output.iter().enumerate() {
-                let change_type = self.map_keychain(KeychainKind::Internal);
+                let change_keychain = KeychainKind::Internal;
                 match txout_index.index_of_spk(&txout.script_pubkey) {
-                    Some((keychain, _)) if keychain == change_type => change_index = Some(index),
+                    Some((keychain, _)) if keychain == change_keychain => {
+                        change_index = Some(index)
+                    }
                     _ => {}
                 }
             }
@@ -2014,8 +1980,7 @@ impl Wallet {
 
     /// Returns the descriptor used to create addresses for a particular `keychain`.
     pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
-        self.public_descriptor(self.map_keychain(keychain))
-            .expect("we mapped it to external if it doesn't exist")
+        self.public_descriptor(keychain).expect("keychain exists")
     }
 
     /// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
@@ -2026,11 +1991,10 @@ impl Wallet {
 
     /// The index of the next address that you would get if you were to ask the wallet for a new address
     pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
-        let keychain = self.map_keychain(keychain);
         self.indexed_graph
             .index
             .next_index(&keychain)
-            .expect("Keychain must exist (we called map_keychain)")
+            .expect("keychain must exist")
             .0
     }
 
@@ -2049,16 +2013,6 @@ impl Wallet {
         }
     }
 
-    fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind {
-        if keychain == KeychainKind::Internal
-            && self.public_descriptor(KeychainKind::Internal).is_none()
-        {
-            KeychainKind::External
-        } else {
-            keychain
-        }
-    }
-
     fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
         let (keychain, child) = self
             .indexed_graph
@@ -2569,22 +2523,22 @@ fn create_signers<E: IntoWalletDescriptor>(
     index: &mut KeychainTxOutIndex<KeychainKind>,
     secp: &Secp256k1<All>,
     descriptor: E,
-    change_descriptor: Option<E>,
+    change_descriptor: E,
     network: Network,
-) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), crate::descriptor::error::Error> {
-    let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
+) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), DescriptorError> {
+    let descriptor = into_wallet_descriptor_checked(descriptor, secp, network)?;
+    let change_descriptor = into_wallet_descriptor_checked(change_descriptor, secp, network)?;
+    if descriptor.0 == change_descriptor.0 {
+        return Err(DescriptorError::ExternalAndInternalAreTheSame);
+    }
+
+    let (descriptor, keymap) = descriptor;
     let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
     let _ = index.insert_descriptor(KeychainKind::External, descriptor);
 
-    let change_signers = match change_descriptor {
-        Some(descriptor) => {
-            let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
-            let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
-            let _ = index.insert_descriptor(KeychainKind::Internal, descriptor);
-            signers
-        }
-        None => Arc::new(SignersContainer::new()),
-    };
+    let (descriptor, keymap) = change_descriptor;
+    let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
+    let _ = index.insert_descriptor(KeychainKind::Internal, descriptor);
 
     Ok((signers, change_signers))
 }
@@ -2613,7 +2567,7 @@ macro_rules! doctest_wallet {
 
         let mut wallet = Wallet::new_no_persist(
             descriptor,
-            Some(change_descriptor),
+            change_descriptor,
             Network::Regtest,
         )
         .unwrap();
index 51420caa3e31df79ee7f0007ef62fa46d2406c31..280e7066f4bb2070212e11db5a8dde4458617d8e 100644 (file)
@@ -67,8 +67,9 @@
 //!
 //! let custom_signer = CustomSigner::connect();
 //!
-//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
-//! let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet)?;
+//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
+//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)";
+//! let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?;
 //! wallet.add_signer(
 //!     KeychainKind::External,
 //!     SignerOrdering(200),
index a51dcafb3afa50fc0e2d51bd6687aba04fdf3bc4..34ffda41d6e42063e801d7a8209952ae4233530d 100644 (file)
@@ -15,12 +15,9 @@ use std::str::FromStr;
 // The funded wallet containing a tx with a 76_000 sats input and two outputs, one spending 25_000
 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
 // sats are the transaction fee.
-pub fn get_funded_wallet_with_change(
-    descriptor: &str,
-    change: Option<&str>,
-) -> (Wallet, bitcoin::Txid) {
+pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet, bitcoin::Txid) {
     let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap();
-    let change_address = wallet.peek_address(KeychainKind::External, 0).address;
+    let receive_address = wallet.peek_address(KeychainKind::External, 0).address;
     let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
         .expect("address")
         .require_network(Network::Regtest)
@@ -40,7 +37,7 @@ pub fn get_funded_wallet_with_change(
         }],
         output: vec![TxOut {
             value: Amount::from_sat(76_000),
-            script_pubkey: change_address.script_pubkey(),
+            script_pubkey: receive_address.script_pubkey(),
         }],
     };
 
@@ -59,7 +56,7 @@ pub fn get_funded_wallet_with_change(
         output: vec![
             TxOut {
                 value: Amount::from_sat(50_000),
-                script_pubkey: change_address.script_pubkey(),
+                script_pubkey: receive_address.script_pubkey(),
             },
             TxOut {
                 value: Amount::from_sat(25_000),
@@ -108,13 +105,29 @@ pub fn get_funded_wallet_with_change(
 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
 // sats are the transaction fee.
 pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) {
-    get_funded_wallet_with_change(descriptor, None)
+    let change = get_test_wpkh_change();
+    get_funded_wallet_with_change(descriptor, change)
+}
+
+pub fn get_funded_wallet_wpkh() -> (Wallet, bitcoin::Txid) {
+    get_funded_wallet_with_change(get_test_wpkh(), get_test_wpkh_change())
 }
 
 pub fn get_test_wpkh() -> &'static str {
     "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
 }
 
+pub fn get_test_wpkh_with_change_desc() -> (&'static str, &'static str) {
+    (
+        "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)",
+        get_test_wpkh_change(),
+    )
+}
+
+fn get_test_wpkh_change() -> &'static str {
+    "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/0)"
+}
+
 pub fn get_test_single_sig_csv() -> &'static str {
     // and(pk(Alice),older(6))
     "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))"
@@ -150,6 +163,11 @@ pub fn get_test_tr_single_sig_xprv() -> &'static str {
     "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"
 }
 
+pub fn get_test_tr_single_sig_xprv_with_change_desc() -> (&'static str, &'static str) {
+    ("tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/0/*)",
+    "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/1/*)")
+}
+
 pub fn get_test_tr_with_taptree_xprv() -> &'static str {
     "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
 }
index a858e8a4aa92759d2027254665e59149f7eeb463..155bb143a95d70d865a36af5202c057a0cfb8889 100644 (file)
@@ -142,7 +142,9 @@ fn test_psbt_fee_rate_with_missing_txout() {
     assert!(wpkh_psbt.fee_amount().is_none());
     assert!(wpkh_psbt.fee_rate().is_none());
 
-    let (mut pkh_wallet,  _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
+    let desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)";
+    let change_desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1)";
+    let (mut pkh_wallet, _) = get_funded_wallet_with_change(desc, change_desc);
     let addr = pkh_wallet.peek_address(KeychainKind::External, 0);
     let mut builder = pkh_wallet.build_tx();
     builder.drain_to(addr.script_pubkey()).drain_wallet();
@@ -170,7 +172,8 @@ fn test_psbt_multiple_internalkey_signers() {
     let prv = PrivateKey::from_wif(wif).unwrap();
     let keypair = Keypair::from_secret_key(&secp, &prv.inner);
 
-    let (mut wallet, _) = get_funded_wallet(&desc);
+    let change_desc = "tr(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
+    let (mut wallet, _) = get_funded_wallet_with_change(&desc, change_desc);
     let to_spend = wallet.balance().total();
     let send_to = wallet.peek_address(KeychainKind::External, 0);
     let mut builder = wallet.build_tx();
index 93baff79635e95160da627ca61470bf2389192d1..dff699a6ff341bf4d1e728bb9949a5a835090a4f 100644 (file)
@@ -7,7 +7,7 @@ use bdk_chain::COINBASE_MATURITY;
 use bdk_chain::{BlockId, ConfirmationTime};
 use bdk_persist::PersistBackend;
 use bdk_sqlite::rusqlite::Connection;
-use bdk_wallet::descriptor::{calc_checksum, IntoWalletDescriptor};
+use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
 use bdk_wallet::psbt::PsbtUtils;
 use bdk_wallet::signer::{SignOptions, SignerError};
 use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection};
@@ -81,12 +81,13 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
     {
         let temp_dir = tempfile::tempdir().expect("must create tempdir");
         let file_path = temp_dir.path().join(filename);
+        let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
 
         // create new wallet
         let wallet_spk_index = {
             let db = create_new(&file_path).expect("must create db");
-            let mut wallet = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet)
-                .expect("must init wallet");
+            let mut wallet =
+                Wallet::new(desc, change_desc, db, Network::Testnet).expect("must init wallet");
 
             wallet.reveal_next_address(KeychainKind::External).unwrap();
             wallet.spk_index().clone()
@@ -108,8 +109,7 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
             let secp = Secp256k1::new();
             assert_eq!(
                 *wallet.get_descriptor_for_keychain(KeychainKind::External),
-                get_test_tr_single_sig_xprv()
-                    .into_wallet_descriptor(&secp, wallet.network())
+                desc.into_wallet_descriptor(&secp, wallet.network())
                     .unwrap()
                     .0
             );
@@ -118,7 +118,7 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
         // `new` can only be called on empty db
         {
             let db = recover(&file_path).expect("must recover db");
-            let result = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet);
+            let result = Wallet::new(desc, change_desc, db, Network::Testnet);
             assert!(matches!(result, Err(NewError::NonEmptyDatabase)));
         }
 
@@ -148,11 +148,12 @@ fn new_or_load() -> anyhow::Result<()> {
     {
         let temp_dir = tempfile::tempdir().expect("must create tempdir");
         let file_path = temp_dir.path().join(filename);
+        let (desc, change_desc) = get_test_wpkh_with_change_desc();
 
         // init wallet when non-existent
         let wallet_keychains: BTreeMap<_, _> = {
             let db = new_or_load(&file_path).expect("must create db");
-            let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet)
+            let wallet = Wallet::new_or_load(desc, change_desc, db, Network::Testnet)
                 .expect("must init wallet");
             wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
         };
@@ -160,7 +161,7 @@ fn new_or_load() -> anyhow::Result<()> {
         // wrong network
         {
             let db = new_or_load(&file_path).expect("must create db");
-            let err = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Bitcoin)
+            let err = Wallet::new_or_load(desc, change_desc, db, Network::Bitcoin)
                 .expect_err("wrong network");
             assert!(
                 matches!(
@@ -183,8 +184,8 @@ fn new_or_load() -> anyhow::Result<()> {
 
             let db = new_or_load(&file_path).expect("must open db");
             let err = Wallet::new_or_load_with_genesis_hash(
-                get_test_wpkh(),
-                None,
+                desc,
+                change_desc,
                 db,
                 Network::Testnet,
                 exp_blockhash,
@@ -204,13 +205,13 @@ fn new_or_load() -> anyhow::Result<()> {
         // wrong external descriptor
         {
             let exp_descriptor = get_test_tr_single_sig();
-            let got_descriptor = get_test_wpkh()
+            let got_descriptor = desc
                 .into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
                 .unwrap()
                 .0;
 
             let db = new_or_load(&file_path).expect("must open db");
-            let err = Wallet::new_or_load(exp_descriptor, None, db, Network::Testnet)
+            let err = Wallet::new_or_load(exp_descriptor, change_desc, db, Network::Testnet)
                 .expect_err("wrong external descriptor");
             assert!(
                 matches!(
@@ -225,17 +226,20 @@ fn new_or_load() -> anyhow::Result<()> {
 
         // wrong internal descriptor
         {
-            let exp_descriptor = Some(get_test_tr_single_sig());
-            let got_descriptor = None;
+            let exp_descriptor = get_test_tr_single_sig();
+            let got_descriptor = change_desc
+                .into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
+                .unwrap()
+                .0;
 
             let db = new_or_load(&file_path).expect("must open db");
-            let err = Wallet::new_or_load(get_test_wpkh(), exp_descriptor, db, Network::Testnet)
+            let err = Wallet::new_or_load(desc, exp_descriptor, db, Network::Testnet)
                 .expect_err("wrong internal descriptor");
             assert!(
                 matches!(
                     err,
                     bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
-                    if got == &got_descriptor && keychain == KeychainKind::Internal
+                    if got == &Some(got_descriptor) && keychain == KeychainKind::Internal
                 ),
                 "err: {}",
                 err,
@@ -245,7 +249,7 @@ fn new_or_load() -> anyhow::Result<()> {
         // all parameters match
         {
             let db = new_or_load(&file_path).expect("must open db");
-            let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet)
+            let wallet = Wallet::new_or_load(desc, change_desc, db, Network::Testnet)
                 .expect("must recover wallet");
             assert_eq!(wallet.network(), Network::Testnet);
             assert!(wallet
@@ -266,9 +270,31 @@ fn new_or_load() -> anyhow::Result<()> {
     Ok(())
 }
 
+#[test]
+fn test_error_external_and_internal_are_the_same() {
+    // identical descriptors should fail to create wallet
+    let desc = get_test_wpkh();
+    let err = Wallet::new_no_persist(desc, desc, Network::Testnet);
+    assert!(
+        matches!(&err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
+        "expected same descriptors error, got {:?}",
+        err,
+    );
+
+    // public + private of same descriptor should fail to create wallet
+    let desc = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
+    let change_desc = "wpkh([3c31d632/84'/1'/0']tpubDCYwFkks2cg78N7eoYbBatsFEGje8vW8arSKW4rLwD1AU1s9KJMDRHE32JkvYERuiFjArrsH7qpWSpJATed5ShZbG9KsskA5Rmi6NSYgYN2/0/*)";
+    let err = Wallet::new_no_persist(desc, change_desc, Network::Testnet);
+    assert!(
+        matches!(err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
+        "expected same descriptors error, got {:?}",
+        err,
+    );
+}
+
 #[test]
 fn test_descriptor_checksum() {
-    let (wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (wallet, _) = get_funded_wallet_wpkh();
     let checksum = wallet.descriptor_checksum(KeychainKind::External);
     assert_eq!(checksum.len(), 8);
 
@@ -287,7 +313,7 @@ fn test_descriptor_checksum() {
 
 #[test]
 fn test_get_funded_wallet_balance() {
-    let (wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (wallet, _) = get_funded_wallet_wpkh();
 
     // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
     // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
@@ -297,7 +323,7 @@ fn test_get_funded_wallet_balance() {
 
 #[test]
 fn test_get_funded_wallet_sent_and_received() {
-    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
+    let (wallet, txid) = get_funded_wallet_wpkh();
 
     let mut tx_amounts: Vec<(Txid, (Amount, Amount))> = wallet
         .transactions()
@@ -317,7 +343,7 @@ fn test_get_funded_wallet_sent_and_received() {
 
 #[test]
 fn test_get_funded_wallet_tx_fees() {
-    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
+    let (wallet, txid) = get_funded_wallet_wpkh();
 
     let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
     let tx_fee = wallet.calculate_fee(&tx).expect("transaction fee");
@@ -330,7 +356,7 @@ fn test_get_funded_wallet_tx_fees() {
 
 #[test]
 fn test_get_funded_wallet_tx_fee_rate() {
-    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
+    let (wallet, txid) = get_funded_wallet_wpkh();
 
     let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
     let tx_fee_rate = wallet
@@ -350,7 +376,7 @@ fn test_get_funded_wallet_tx_fee_rate() {
 
 #[test]
 fn test_list_output() {
-    let (wallet, txid) = get_funded_wallet(get_test_wpkh());
+    let (wallet, txid) = get_funded_wallet_wpkh();
     let txos = wallet
         .list_output()
         .map(|op| (op.outpoint, op))
@@ -428,14 +454,14 @@ macro_rules! from_str {
 #[test]
 #[should_panic(expected = "NoRecipients")]
 fn test_create_tx_empty_recipients() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     wallet.build_tx().finish().unwrap();
 }
 
 #[test]
 #[should_panic(expected = "NoUtxosSelected")]
 fn test_create_tx_manually_selected_empty_utxos() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -446,7 +472,7 @@ fn test_create_tx_manually_selected_empty_utxos() {
 
 #[test]
 fn test_create_tx_version_0() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -468,7 +494,7 @@ fn test_create_tx_version_1_csv() {
 
 #[test]
 fn test_create_tx_custom_version() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -481,7 +507,7 @@ fn test_create_tx_custom_version() {
 
 #[test]
 fn test_create_tx_default_locktime_is_last_sync_height() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
 
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
@@ -495,7 +521,7 @@ fn test_create_tx_default_locktime_is_last_sync_height() {
 
 #[test]
 fn test_create_tx_fee_sniping_locktime_last_sync() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@@ -522,7 +548,7 @@ fn test_create_tx_default_locktime_cltv() {
 
 #[test]
 fn test_create_tx_custom_locktime() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -614,7 +640,7 @@ fn test_create_tx_no_rbf_cltv() {
 
 #[test]
 fn test_create_tx_invalid_rbf_sequence() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -625,7 +651,7 @@ fn test_create_tx_invalid_rbf_sequence() {
 
 #[test]
 fn test_create_tx_custom_rbf_sequence() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -637,30 +663,40 @@ fn test_create_tx_custom_rbf_sequence() {
 }
 
 #[test]
-fn test_create_tx_default_sequence() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+fn test_create_tx_change_policy() {
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
-    let psbt = builder.finish().unwrap();
-
-    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
-}
+    builder
+        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .do_not_spend_change();
+    assert!(builder.finish().is_ok());
 
-#[test]
-fn test_create_tx_change_policy_no_internal() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
-    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    // wallet has no change, so setting `only_spend_change`
+    // should cause tx building to fail
     let mut builder = wallet.build_tx();
     builder
         .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
-        .do_not_spend_change();
+        .only_spend_change();
     assert!(matches!(
         builder.finish(),
-        Err(CreateTxError::ChangePolicyDescriptor)
+        Err(CreateTxError::CoinSelection(
+            coin_selection::Error::InsufficientFunds { .. }
+        )),
     ));
 }
 
+#[test]
+fn test_create_tx_default_sequence() {
+    let (mut wallet, _) = get_funded_wallet_wpkh();
+    let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
+    let mut builder = wallet.build_tx();
+    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    let psbt = builder.finish().unwrap();
+
+    assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
+}
+
 macro_rules! check_fee {
     ($wallet:expr, $psbt: expr) => {{
         let tx = $psbt.clone().extract_tx().expect("failed to extract tx");
@@ -672,7 +708,7 @@ macro_rules! check_fee {
 
 #[test]
 fn test_create_tx_drain_wallet_and_drain_to() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder.drain_to(addr.script_pubkey()).drain_wallet();
@@ -688,7 +724,7 @@ fn test_create_tx_drain_wallet_and_drain_to() {
 
 #[test]
 fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
         .unwrap()
         .assume_checked();
@@ -720,7 +756,7 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
 
 #[test]
 fn test_create_tx_drain_to_and_utxos() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect();
     let mut builder = wallet.build_tx();
@@ -741,7 +777,7 @@ fn test_create_tx_drain_to_and_utxos() {
 #[test]
 #[should_panic(expected = "NoRecipients")]
 fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder.drain_to(drain_addr.script_pubkey());
@@ -750,7 +786,7 @@ fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
 
 #[test]
 fn test_create_tx_default_fee_rate() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@@ -762,7 +798,7 @@ fn test_create_tx_default_fee_rate() {
 
 #[test]
 fn test_create_tx_custom_fee_rate() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -776,7 +812,7 @@ fn test_create_tx_custom_fee_rate() {
 
 #[test]
 fn test_create_tx_absolute_fee() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -796,7 +832,7 @@ fn test_create_tx_absolute_fee() {
 
 #[test]
 fn test_create_tx_absolute_zero_fee() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -817,7 +853,7 @@ fn test_create_tx_absolute_zero_fee() {
 #[test]
 #[should_panic(expected = "InsufficientFunds")]
 fn test_create_tx_absolute_high_fee() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -831,7 +867,7 @@ fn test_create_tx_absolute_high_fee() {
 fn test_create_tx_add_change() {
     use bdk_wallet::wallet::tx_builder::TxOrdering;
 
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -850,7 +886,7 @@ fn test_create_tx_add_change() {
 
 #[test]
 fn test_create_tx_skip_change_dust() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800));
@@ -865,7 +901,7 @@ fn test_create_tx_skip_change_dust() {
 #[test]
 #[should_panic(expected = "InsufficientFunds")]
 fn test_create_tx_drain_to_dust_amount() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     // very high fee rate, so that the only output would be below dust
     let mut builder = wallet.build_tx();
@@ -878,7 +914,7 @@ fn test_create_tx_drain_to_dust_amount() {
 
 #[test]
 fn test_create_tx_ordering_respected() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -899,7 +935,7 @@ fn test_create_tx_ordering_respected() {
 
 #[test]
 fn test_create_tx_default_sighash() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000));
@@ -910,7 +946,7 @@ fn test_create_tx_default_sighash() {
 
 #[test]
 fn test_create_tx_custom_sighash() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -1088,7 +1124,7 @@ fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() {
 
 #[test]
 fn test_create_tx_add_utxo() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let small_output_tx = Transaction {
         input: vec![],
         output: vec![TxOut {
@@ -1138,7 +1174,7 @@ fn test_create_tx_add_utxo() {
 #[test]
 #[should_panic(expected = "InsufficientFunds")]
 fn test_create_tx_manually_selected_insufficient() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let small_output_tx = Transaction {
         input: vec![],
         output: vec![TxOut {
@@ -1189,8 +1225,8 @@ fn test_create_tx_policy_path_required() {
 
 #[test]
 fn test_create_tx_policy_path_no_csv() {
-    let descriptors = get_test_wpkh();
-    let mut wallet = Wallet::new_no_persist(descriptors, None, Network::Regtest).unwrap();
+    let (desc, change_desc) = get_test_wpkh_with_change_desc();
+    let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap();
 
     let tx = Transaction {
         version: transaction::Version::non_standard(0),
@@ -1284,13 +1320,13 @@ fn test_create_tx_global_xpubs_with_origin() {
     let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap();
     let path = bip32::DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
 
-    assert_eq!(psbt.xpub.len(), 1);
+    assert_eq!(psbt.xpub.len(), 2);
     assert_eq!(psbt.xpub.get(&key), Some(&(fingerprint, path)));
 }
 
 #[test]
 fn test_add_foreign_utxo() {
-    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet1, _) = get_funded_wallet_wpkh();
     let (wallet2, _) =
         get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
 
@@ -1366,7 +1402,7 @@ fn test_add_foreign_utxo() {
     expected = "MissingTxOut([OutPoint { txid: 0x21d7fb1bceda00ab4069fc52d06baa13470803e9050edd16f5736e5d8c4925fd, vout: 0 }])"
 )]
 fn test_calculate_fee_with_missing_foreign_utxo() {
-    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet1, _) = get_funded_wallet_wpkh();
     let (wallet2, _) =
         get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
 
@@ -1397,7 +1433,7 @@ fn test_calculate_fee_with_missing_foreign_utxo() {
 
 #[test]
 fn test_add_foreign_utxo_invalid_psbt_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let outpoint = wallet.list_unspent().next().expect("must exist").outpoint;
     let foreign_utxo_satisfaction = wallet
         .get_descriptor_for_keychain(KeychainKind::External)
@@ -1412,7 +1448,7 @@ fn test_add_foreign_utxo_invalid_psbt_input() {
 
 #[test]
 fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
-    let (mut wallet1, txid1) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet1, txid1) = get_funded_wallet_wpkh();
     let (wallet2, txid2) =
         get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
 
@@ -1456,7 +1492,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
 
 #[test]
 fn test_add_foreign_utxo_only_witness_utxo() {
-    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet1, _) = get_funded_wallet_wpkh();
     let (wallet2, txid2) =
         get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
@@ -1523,7 +1559,7 @@ fn test_add_foreign_utxo_only_witness_utxo() {
 #[test]
 fn test_get_psbt_input() {
     // this should grab a known good utxo and set the input
-    let (wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (wallet, _) = get_funded_wallet_wpkh();
     for utxo in wallet.list_unspent() {
         let psbt_input = wallet.get_psbt_input(utxo, None, false).unwrap();
         assert!(psbt_input.witness_utxo.is_some() || psbt_input.non_witness_utxo.is_some());
@@ -1560,7 +1596,7 @@ fn test_create_tx_global_xpubs_master_without_origin() {
     let key = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
     let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap();
 
-    assert_eq!(psbt.xpub.len(), 1);
+    assert_eq!(psbt.xpub.len(), 2);
     assert_eq!(
         psbt.xpub.get(&key),
         Some(&(fingerprint, bip32::DerivationPath::default()))
@@ -1570,7 +1606,7 @@ fn test_create_tx_global_xpubs_master_without_origin() {
 #[test]
 #[should_panic(expected = "IrreplaceableTransaction")]
 fn test_bump_fee_irreplaceable_tx() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@@ -1587,7 +1623,7 @@ fn test_bump_fee_irreplaceable_tx() {
 #[test]
 #[should_panic(expected = "TransactionConfirmed")]
 fn test_bump_fee_confirmed_tx() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@@ -1611,7 +1647,7 @@ fn test_bump_fee_confirmed_tx() {
 
 #[test]
 fn test_bump_fee_low_fee_rate() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -1645,7 +1681,7 @@ fn test_bump_fee_low_fee_rate() {
 #[test]
 #[should_panic(expected = "FeeTooLow")]
 fn test_bump_fee_low_abs() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -1668,7 +1704,7 @@ fn test_bump_fee_low_abs() {
 #[test]
 #[should_panic(expected = "FeeTooLow")]
 fn test_bump_fee_zero_abs() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
     let mut builder = wallet.build_tx();
     builder
@@ -1689,7 +1725,7 @@ fn test_bump_fee_zero_abs() {
 
 #[test]
 fn test_bump_fee_reduce_change() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
         .assume_checked();
@@ -1788,7 +1824,7 @@ fn test_bump_fee_reduce_change() {
 
 #[test]
 fn test_bump_fee_reduce_single_recipient() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
         .assume_checked();
@@ -1836,7 +1872,7 @@ fn test_bump_fee_reduce_single_recipient() {
 
 #[test]
 fn test_bump_fee_absolute_reduce_single_recipient() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
         .assume_checked();
@@ -1882,7 +1918,7 @@ fn test_bump_fee_absolute_reduce_single_recipient() {
 
 #[test]
 fn test_bump_fee_drain_wallet() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     // receive an extra tx so that our wallet has two utxos.
     let tx = Transaction {
         version: transaction::Version::ONE,
@@ -1944,7 +1980,7 @@ fn test_bump_fee_drain_wallet() {
 #[test]
 #[should_panic(expected = "InsufficientFunds")]
 fn test_bump_fee_remove_output_manually_selected_only() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     // receive an extra tx so that our wallet has two utxos. then we manually pick only one of
     // them, and make sure that `bump_fee` doesn't try to add more. This fails because we've
     // told the wallet it's not allowed to add more inputs AND it can't reduce the value of the
@@ -2006,7 +2042,7 @@ fn test_bump_fee_remove_output_manually_selected_only() {
 
 #[test]
 fn test_bump_fee_add_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let init_tx = Transaction {
         version: transaction::Version::ONE,
         lock_time: absolute::LockTime::ZERO,
@@ -2083,7 +2119,7 @@ fn test_bump_fee_add_input() {
 
 #[test]
 fn test_bump_fee_absolute_add_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     receive_output_in_latest_block(&mut wallet, 25_000);
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
@@ -2141,7 +2177,7 @@ fn test_bump_fee_absolute_add_input() {
 
 #[test]
 fn test_bump_fee_no_change_add_input_and_change() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let op = receive_output_in_latest_block(&mut wallet, 25_000);
 
     // initially make a tx without change by using `drain_to`
@@ -2210,7 +2246,7 @@ fn test_bump_fee_no_change_add_input_and_change() {
 
 #[test]
 fn test_bump_fee_add_input_change_dust() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     receive_output_in_latest_block(&mut wallet, 25_000);
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
@@ -2286,7 +2322,7 @@ fn test_bump_fee_add_input_change_dust() {
 
 #[test]
 fn test_bump_fee_force_add_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
 
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
@@ -2352,7 +2388,7 @@ fn test_bump_fee_force_add_input() {
 
 #[test]
 fn test_bump_fee_absolute_force_add_input() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
 
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
@@ -2427,7 +2463,7 @@ fn test_bump_fee_unconfirmed_inputs_only() {
     // So, we fail with "InsufficientFunds", as per RBF rule 2:
     // The replacement transaction may only include an unconfirmed input
     // if that input was included in one of the original transactions.
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
         .assume_checked();
@@ -2464,7 +2500,7 @@ fn test_bump_fee_unconfirmed_input() {
     // (BIP125 rule 2 only apply to newly added unconfirmed input, you can
     // always fee bump with an unconfirmed input if it was included in the
     // original transaction)
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
         .assume_checked();
@@ -2508,7 +2544,7 @@ fn test_fee_amount_negative_drain_val() {
     // This caused a bug in master where we would calculate the wrong fee
     // for a transaction.
     // See https://github.com/bitcoindevkit/bdk/issues/660
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")
         .unwrap()
         .assume_checked();
@@ -2625,7 +2661,9 @@ fn test_sign_single_xprv_no_hd_keypaths() {
 
 #[test]
 fn test_include_output_redeem_witness_script() {
-    let (mut wallet, _) = get_funded_wallet("sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))");
+    let desc = get_test_wpkh();
+    let change_desc = "sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))";
+    let (mut wallet, _) = get_funded_wallet_with_change(desc, change_desc);
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
         .assume_checked();
@@ -2644,7 +2682,7 @@ fn test_include_output_redeem_witness_script() {
 
 #[test]
 fn test_signing_only_one_of_multiple_inputs() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
         .unwrap()
         .assume_checked();
@@ -2800,8 +2838,9 @@ fn test_sign_nonstandard_sighash() {
 
 #[test]
 fn test_unused_address() {
-    let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                 None, Network::Testnet).unwrap();
+    let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
+    let change_desc = get_test_wpkh();
+    let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap();
 
     // `list_unused_addresses` should be empty if we haven't revealed any
     assert!(wallet
@@ -2829,7 +2868,8 @@ fn test_unused_address() {
 #[test]
 fn test_next_unused_address() {
     let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
-    let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet).unwrap();
+    let change = get_test_wpkh();
+    let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Testnet).unwrap();
     assert_eq!(wallet.derivation_index(KeychainKind::External), None);
 
     assert_eq!(
@@ -2877,8 +2917,9 @@ fn test_next_unused_address() {
 
 #[test]
 fn test_peek_address_at_index() {
-    let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                 None, Network::Testnet).unwrap();
+    let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
+    let change_desc = get_test_wpkh();
+    let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap();
 
     assert_eq!(
         wallet.peek_address(KeychainKind::External, 1).to_string(),
@@ -2916,7 +2957,7 @@ fn test_peek_address_at_index() {
 #[test]
 fn test_peek_address_at_index_not_derivable() {
     let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
-                                 None, Network::Testnet).unwrap();
+                                 get_test_wpkh(), Network::Testnet).unwrap();
 
     assert_eq!(
         wallet.peek_address(KeychainKind::External, 1).to_string(),
@@ -2937,7 +2978,7 @@ fn test_peek_address_at_index_not_derivable() {
 #[test]
 fn test_returns_index_and_address() {
     let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
-                                 None, Network::Testnet).unwrap();
+                                 get_test_wpkh(), Network::Testnet).unwrap();
 
     // new index 0
     assert_eq!(
@@ -2990,7 +3031,7 @@ fn test_returns_index_and_address() {
 
 #[test]
 fn test_sending_to_bip350_bech32m_address() {
-    let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet, _) = get_funded_wallet_wpkh();
     let addr = Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
         .unwrap()
         .assume_checked();
@@ -3005,7 +3046,7 @@ fn test_get_address() {
     let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
     let wallet = Wallet::new_no_persist(
         Bip84(key, KeychainKind::External),
-        Some(Bip84(key, KeychainKind::Internal)),
+        Bip84(key, KeychainKind::Internal),
         Network::Regtest,
     )
     .unwrap();
@@ -3031,27 +3072,12 @@ fn test_get_address() {
             keychain: KeychainKind::Internal,
         }
     );
-
-    let wallet =
-        Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
-
-    assert_eq!(
-        wallet.peek_address(KeychainKind::Internal, 0),
-        AddressInfo {
-            index: 0,
-            address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
-                .unwrap()
-                .assume_checked(),
-            keychain: KeychainKind::External,
-        },
-        "when there's no internal descriptor it should just use external"
-    );
 }
 
 #[test]
 fn test_reveal_addresses() {
-    let desc = get_test_tr_single_sig_xprv();
-    let mut wallet = Wallet::new_no_persist(desc, None, Network::Signet).unwrap();
+    let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
+    let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Signet).unwrap();
     let keychain = KeychainKind::External;
 
     let last_revealed_addr = wallet
@@ -3071,13 +3097,17 @@ fn test_reveal_addresses() {
 }
 
 #[test]
-fn test_get_address_no_reuse_single_descriptor() {
+fn test_get_address_no_reuse() {
     use bdk_wallet::descriptor::template::Bip84;
     use std::collections::HashSet;
 
     let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
-    let mut wallet =
-        Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
+    let mut wallet = Wallet::new_no_persist(
+        Bip84(key, KeychainKind::External),
+        Bip84(key, KeychainKind::Internal),
+        Network::Regtest,
+    )
+    .unwrap();
 
     let mut used_set = HashSet::new();
 
@@ -3124,11 +3154,12 @@ fn test_taproot_remove_tapfields_after_finalize_sign_option() {
 
 #[test]
 fn test_taproot_psbt_populate_tap_key_origins() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
+    let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
+    let (mut wallet, _) = get_funded_wallet_with_change(desc, change_desc);
     let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
 
     let mut builder = wallet.build_tx();
-    builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
+    builder.drain_to(addr.script_pubkey()).drain_wallet();
     let psbt = builder.finish().unwrap();
 
     assert_eq!(
@@ -3138,8 +3169,8 @@ fn test_taproot_psbt_populate_tap_key_origins() {
             .into_iter()
             .collect::<Vec<_>>(),
         vec![(
-            from_str!("b96d3a3dc76a4fc74e976511b23aecb78e0754c23c0ed7a6513e18cbbc7178e9"),
-            (vec![], (from_str!("f6a5cb8b"), from_str!("m/0")))
+            from_str!("0841db1dbaf949dbbda893e01a18f2cca9179cf8ea2d8e667857690502b06483"),
+            (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/0")))
         )],
         "Wrong input tap_key_origins"
     );
@@ -3150,8 +3181,8 @@ fn test_taproot_psbt_populate_tap_key_origins() {
             .into_iter()
             .collect::<Vec<_>>(),
         vec![(
-            from_str!("e9b03068cf4a2621d4f81e68f6c4216e6bd260fe6edf6acc55c8d8ae5aeff0a8"),
-            (vec![], (from_str!("f6a5cb8b"), from_str!("m/1")))
+            from_str!("9187c1e80002d19ddde9c5c7f5394e9a063cee8695867b58815af0562695ca21"),
+            (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/1")))
         )],
         "Wrong output tap_key_origins"
     );
@@ -3159,7 +3190,8 @@ fn test_taproot_psbt_populate_tap_key_origins() {
 
 #[test]
 fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
-    let (mut wallet, _) = get_funded_wallet(get_test_tr_repeated_key());
+    let (mut wallet, _) =
+        get_funded_wallet_with_change(get_test_tr_repeated_key(), get_test_tr_single_sig());
     let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
 
     let path = vec![("rn4nre9c".to_string(), vec![0])]
@@ -3168,7 +3200,8 @@ fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
 
     let mut builder = wallet.build_tx();
     builder
-        .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
+        .drain_to(addr.script_pubkey())
+        .drain_wallet()
         .policy_path(path, KeychainKind::External);
     let psbt = builder.finish().unwrap();
 
@@ -3332,7 +3365,7 @@ fn test_taproot_sign_using_non_witness_utxo() {
 
 #[test]
 fn test_taproot_foreign_utxo() {
-    let (mut wallet1, _) = get_funded_wallet(get_test_wpkh());
+    let (mut wallet1, _) = get_funded_wallet_wpkh();
     let (wallet2, _) = get_funded_wallet(get_test_tr_single_sig());
 
     let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
@@ -3575,8 +3608,12 @@ fn test_taproot_sign_derive_index_from_psbt() {
     let mut psbt = builder.finish().unwrap();
 
     // re-create the wallet with an empty db
-    let wallet_empty =
-        Wallet::new_no_persist(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap();
+    let wallet_empty = Wallet::new_no_persist(
+        get_test_tr_single_sig_xprv(),
+        get_test_tr_single_sig(),
+        Network::Regtest,
+    )
+    .unwrap();
 
     // signing with an empty db means that we will only look at the psbt to infer the
     // derivation index
@@ -3675,8 +3712,8 @@ fn test_taproot_sign_non_default_sighash() {
 
 #[test]
 fn test_spend_coinbase() {
-    let descriptor = get_test_wpkh();
-    let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Regtest).unwrap();
+    let (desc, change_desc) = get_test_wpkh_with_change_desc();
+    let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap();
 
     let confirmation_height = 5;
     wallet
@@ -3937,7 +3974,7 @@ fn test_tx_cancellation() {
     }
 
     let (mut wallet, _) =
-        get_funded_wallet_with_change(get_test_wpkh(), Some(get_test_tr_single_sig_xprv()));
+        get_funded_wallet_with_change(get_test_wpkh(), get_test_tr_single_sig_xprv());
 
     let psbt1 = new_tx!(wallet);
     let change_derivation_1 = psbt1
index 36d560e3e705b5c8fb77bc537f1e7303b4421ae9..e6c01c20be15cbec7d0f3ca613bfa9836423de44 100644 (file)
@@ -23,7 +23,7 @@ fn main() -> Result<(), anyhow::Error> {
 
     let mut wallet = Wallet::new_or_load(
         external_descriptor,
-        Some(internal_descriptor),
+        internal_descriptor,
         db,
         Network::Testnet,
     )?;
index 9a9904a54d62d3a9cbd9ee162dee368a668a3970..f46779b3c1ec73564837fe34c8a0e9a1e2c7aa12 100644 (file)
@@ -22,7 +22,7 @@ async fn main() -> Result<(), anyhow::Error> {
 
     let mut wallet = Wallet::new_or_load(
         external_descriptor,
-        Some(internal_descriptor),
+        internal_descriptor,
         db,
         Network::Signet,
     )?;
index 5ecb7122383e1c086d32d1f702322fa77b3fc443..92caca2c9b43890fae56886f6fcda6f6aea5158a 100644 (file)
@@ -21,7 +21,7 @@ fn main() -> Result<(), anyhow::Error> {
 
     let mut wallet = Wallet::new_or_load(
         external_descriptor,
-        Some(internal_descriptor),
+        internal_descriptor,
         db,
         Network::Testnet,
     )?;
index 8c709a02564f6d072d79784a592d2423d69b192f..a64f0539e92a84749706a436dafa0f930ff6676b 100644 (file)
@@ -25,7 +25,7 @@ pub struct Args {
     pub descriptor: String,
     /// Wallet change descriptor
     #[clap(env = "CHANGE_DESCRIPTOR")]
-    pub change_descriptor: Option<String>,
+    pub change_descriptor: String,
     /// Earliest block height to start sync from
     #[clap(env = "START_HEIGHT", long, default_value = "481824")]
     pub start_height: u32,
@@ -88,7 +88,7 @@ fn main() -> anyhow::Result<()> {
     let start_load_wallet = Instant::now();
     let mut wallet = Wallet::new_or_load(
         &args.descriptor,
-        args.change_descriptor.as_ref(),
+        &args.change_descriptor,
         Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
             DB_MAGIC.as_bytes(),
             args.db_path,