From 439ecf6f0be3c24ef5371e9fb705d282b9558ffa Mon Sep 17 00:00:00 2001 From: Noah Joeris Date: Wed, 1 Jul 2026 10:52:25 +0300 Subject: [PATCH 1/2] refactor(wallet): migrate signing to caller-owned keys Prefer bitcoin::Psbt::sign or Wallet::sign_with_signers in docs, examples, and tests instead of relying on wallet-owned signer state. Add FullyNodedExport::export_wallet_with_keymaps so private descriptor material can be supplied explicitly during export. --- README.md | 1 - examples/bitcoind_rpc.rs | 1 - examples/electrum.rs | 26 +++- examples/esplora_async.rs | 27 +++- examples/esplora_blocking.rs | 27 +++- src/wallet/export.rs | 152 ++++++++++++++++----- src/wallet/mod.rs | 37 ++--- src/wallet/params.rs | 10 -- src/wallet/signer.rs | 20 ++- tests/add_foreign_utxo.rs | 17 ++- tests/common.rs | 20 +++ tests/persisted_wallet.rs | 23 +--- tests/psbt.rs | 162 ++++++++++++++++++---- tests/wallet.rs | 258 +++++++++++++++++++++++------------ 14 files changed, 555 insertions(+), 226 deletions(-) diff --git a/README.md b/README.md index 2176a4734..71b0265d8 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,6 @@ let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfS let mut wallet = match Wallet::load() .descriptor(KeychainKind::External, Some(descriptor)) .descriptor(KeychainKind::Internal, Some(change_descriptor)) - .extract_keys() .check_network(network) .load_wallet(&mut conn)? { diff --git a/examples/bitcoind_rpc.rs b/examples/bitcoind_rpc.rs index f0bbd7290..5ea01c27e 100644 --- a/examples/bitcoind_rpc.rs +++ b/examples/bitcoind_rpc.rs @@ -97,7 +97,6 @@ fn main() -> anyhow::Result<()> { let wallet_opt = Wallet::load() .descriptor(KeychainKind::External, Some(args.descriptor.clone())) .descriptor(KeychainKind::Internal, args.change_descriptor.clone()) - .extract_keys() .check_network(args.network) .load_wallet(&mut db)?; let mut wallet = match wallet_opt { diff --git a/examples/electrum.rs b/examples/electrum.rs index 8ccbe130a..7a3dc2ae9 100644 --- a/examples/electrum.rs +++ b/examples/electrum.rs @@ -1,9 +1,12 @@ use bdk_electrum::electrum_client; use bdk_electrum::BdkElectrumClient; +use bdk_wallet::bitcoin::secp256k1::Secp256k1; use bdk_wallet::bitcoin::Amount; use bdk_wallet::bitcoin::FeeRate; use bdk_wallet::bitcoin::Network; use bdk_wallet::chain::collections::HashSet; +use bdk_wallet::descriptor::IntoWalletDescriptor; +use bdk_wallet::miniscript::descriptor::KeyMapWrapper; use bdk_wallet::psbt::PsbtUtils; use bdk_wallet::rusqlite::Connection; use bdk_wallet::Wallet; @@ -23,16 +26,22 @@ const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7 const ELECTRUM_URL: &str = "ssl://mempool.space:40002"; fn main() -> Result<(), anyhow::Error> { + let secp = Secp256k1::new(); + let (external_descriptor, mut external_keymap) = + EXTERNAL_DESC.into_wallet_descriptor(&secp, NETWORK.into())?; + let (internal_descriptor, internal_keymap) = + INTERNAL_DESC.into_wallet_descriptor(&secp, NETWORK.into())?; + external_keymap.extend(internal_keymap); + let signer = KeyMapWrapper::from(external_keymap); let mut db = Connection::open(DB_PATH)?; let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() + .descriptor(KeychainKind::External, Some(external_descriptor.clone())) + .descriptor(KeychainKind::Internal, Some(internal_descriptor.clone())) .check_network(NETWORK) .load_wallet(&mut db)?; let mut wallet = match wallet_opt { Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + None => Wallet::create(external_descriptor, internal_descriptor) .network(NETWORK) .create_wallet(&mut db)?, }; @@ -89,7 +98,9 @@ fn main() -> Result<(), anyhow::Error> { tx_builder.fee_rate(target_fee_rate); let mut psbt = tx_builder.finish()?; - let finalized = wallet.sign(&mut psbt, SignOptions::default())?; + psbt.sign(&signer, wallet.secp_ctx()) + .map_err(|(_, e)| anyhow::anyhow!("{e:?}"))?; + let finalized = wallet.finalize_psbt(&mut psbt, SignOptions::default())?; assert!(finalized); let original_fee = psbt.fee_amount().unwrap(); let tx_feerate = psbt.fee_rate().unwrap(); @@ -124,7 +135,10 @@ fn main() -> Result<(), anyhow::Error> { let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx"); builder.fee_rate(feerate); let mut bumped_psbt = builder.finish().unwrap(); - let finalize_btx = wallet.sign(&mut bumped_psbt, SignOptions::default())?; + bumped_psbt + .sign(&signer, wallet.secp_ctx()) + .map_err(|(_, e)| anyhow::anyhow!("{e:?}"))?; + let finalize_btx = wallet.finalize_psbt(&mut bumped_psbt, SignOptions::default())?; assert!(finalize_btx); let new_fee = bumped_psbt.fee_amount().unwrap(); let bumped_tx = bumped_psbt.extract_tx()?; diff --git a/examples/esplora_async.rs b/examples/esplora_async.rs index 6e19069c4..7a979cb93 100644 --- a/examples/esplora_async.rs +++ b/examples/esplora_async.rs @@ -1,6 +1,8 @@ use bdk_esplora::{esplora_client, EsploraAsyncExt}; use bdk_wallet::{ - bitcoin::{Amount, FeeRate, Network}, + bitcoin::{secp256k1::Secp256k1, Amount, FeeRate, Network}, + descriptor::IntoWalletDescriptor, + miniscript::descriptor::KeyMapWrapper, psbt::PsbtUtils, rusqlite::Connection, KeychainKind, SignOptions, Wallet, @@ -20,16 +22,22 @@ const ESPLORA_URL: &str = "https://mempool.space/testnet4/api"; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { + let secp = Secp256k1::new(); + let (external_descriptor, mut external_keymap) = + EXTERNAL_DESC.into_wallet_descriptor(&secp, NETWORK.into())?; + let (internal_descriptor, internal_keymap) = + INTERNAL_DESC.into_wallet_descriptor(&secp, NETWORK.into())?; + external_keymap.extend(internal_keymap); + let signer = KeyMapWrapper::from(external_keymap); let mut db = Connection::open(DB_PATH)?; let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() + .descriptor(KeychainKind::External, Some(external_descriptor.clone())) + .descriptor(KeychainKind::Internal, Some(internal_descriptor.clone())) .check_network(NETWORK) .load_wallet(&mut db)?; let mut wallet = match wallet_opt { Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + None => Wallet::create(external_descriptor, internal_descriptor) .network(NETWORK) .create_wallet(&mut db)?, }; @@ -83,7 +91,9 @@ async fn main() -> Result<(), anyhow::Error> { tx_builder.fee_rate(target_fee_rate); let mut psbt = tx_builder.finish()?; - let finalized = wallet.sign(&mut psbt, SignOptions::default())?; + psbt.sign(&signer, wallet.secp_ctx()) + .map_err(|(_, e)| anyhow::anyhow!("{e:?}"))?; + let finalized = wallet.finalize_psbt(&mut psbt, SignOptions::default())?; assert!(finalized); let original_fee = psbt.fee_amount().unwrap(); let tx_feerate = psbt.fee_rate().unwrap(); @@ -117,7 +127,10 @@ async fn main() -> Result<(), anyhow::Error> { let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx"); builder.fee_rate(feerate); let mut bumped_psbt = builder.finish().unwrap(); - let finalize_btx = wallet.sign(&mut bumped_psbt, SignOptions::default())?; + bumped_psbt + .sign(&signer, wallet.secp_ctx()) + .map_err(|(_, e)| anyhow::anyhow!("{e:?}"))?; + let finalize_btx = wallet.finalize_psbt(&mut bumped_psbt, SignOptions::default())?; assert!(finalize_btx); let new_fee = bumped_psbt.fee_amount().unwrap(); let bumped_tx = bumped_psbt.extract_tx()?; diff --git a/examples/esplora_blocking.rs b/examples/esplora_blocking.rs index 3131bec07..7ebc8155e 100644 --- a/examples/esplora_blocking.rs +++ b/examples/esplora_blocking.rs @@ -1,7 +1,9 @@ use bdk_esplora::{esplora_client, EsploraExt}; use bdk_wallet::rusqlite::Connection; use bdk_wallet::{ - bitcoin::{Amount, FeeRate, Network}, + bitcoin::{secp256k1::Secp256k1, Amount, FeeRate, Network}, + descriptor::IntoWalletDescriptor, + miniscript::descriptor::KeyMapWrapper, psbt::PsbtUtils, KeychainKind, SignOptions, Wallet, }; @@ -20,16 +22,22 @@ const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7 const ESPLORA_URL: &str = "https://mempool.space/testnet4/api"; fn main() -> Result<(), anyhow::Error> { + let secp = Secp256k1::new(); + let (external_descriptor, mut external_keymap) = + EXTERNAL_DESC.into_wallet_descriptor(&secp, NETWORK.into())?; + let (internal_descriptor, internal_keymap) = + INTERNAL_DESC.into_wallet_descriptor(&secp, NETWORK.into())?; + external_keymap.extend(internal_keymap); + let signer = KeyMapWrapper::from(external_keymap); let mut db = Connection::open(DB_PATH)?; let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() + .descriptor(KeychainKind::External, Some(external_descriptor.clone())) + .descriptor(KeychainKind::Internal, Some(internal_descriptor.clone())) .check_network(NETWORK) .load_wallet(&mut db)?; let mut wallet = match wallet_opt { Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + None => Wallet::create(external_descriptor, internal_descriptor) .network(NETWORK) .create_wallet(&mut db)?, }; @@ -78,7 +86,9 @@ fn main() -> Result<(), anyhow::Error> { tx_builder.fee_rate(target_fee_rate); let mut psbt = tx_builder.finish()?; - let finalized = wallet.sign(&mut psbt, SignOptions::default())?; + psbt.sign(&signer, wallet.secp_ctx()) + .map_err(|(_, e)| anyhow::anyhow!("{e:?}"))?; + let finalized = wallet.finalize_psbt(&mut psbt, SignOptions::default())?; assert!(finalized); let original_fee = psbt.fee_amount().unwrap(); let tx_feerate = psbt.fee_rate().unwrap(); @@ -113,7 +123,10 @@ fn main() -> Result<(), anyhow::Error> { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.fee_rate(feerate); let mut new_psbt = builder.finish().unwrap(); - let finalize_tx = wallet.sign(&mut new_psbt, SignOptions::default())?; + new_psbt + .sign(&signer, wallet.secp_ctx()) + .map_err(|(_, e)| anyhow::anyhow!("{e:?}"))?; + let finalize_tx = wallet.finalize_psbt(&mut new_psbt, SignOptions::default())?; assert!(finalize_tx); let new_fee = new_psbt.fee_amount().unwrap(); let bumped_tx = new_psbt.extract_tx()?; diff --git a/src/wallet/export.rs b/src/wallet/export.rs index b169bba7d..1f90ed6f2 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -46,13 +46,23 @@ //! # use bitcoin::*; //! # use bdk_wallet::export::*; //! # use bdk_wallet::*; -//! let wallet = Wallet::create( -//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", -//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)", -//! ) -//! .network(Network::Testnet) -//! .create_wallet_no_persist()?; -//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap(); +//! # use miniscript::{Descriptor, descriptor::DescriptorPublicKey}; +//! # use bitcoin::secp256k1::Secp256k1; +//! let external = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; +//! let internal = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; +//! let secp = Secp256k1::new(); +//! let (external_descriptor, external_keymap) = Descriptor::::parse_descriptor(&secp, external)?; +//! let (internal_descriptor, internal_keymap) = Descriptor::::parse_descriptor(&secp, internal)?; +//! let wallet = Wallet::create(external_descriptor, internal_descriptor) +//! .network(Network::Testnet) +//! .create_wallet_no_persist()?; +//! let export = FullyNodedExport::export_wallet_with_keymaps( +//! &wallet, +//! &external_keymap, +//! &internal_keymap, +//! "exported wallet", +//! true, +//! )?; //! //! println!("Exported: {}", export.to_string()); //! # Ok::<_, Box>(()) @@ -142,7 +152,7 @@ use core::fmt; use core::str::FromStr; use serde::{Deserialize, Serialize}; -use miniscript::descriptor::{DescriptorPublicKey, ShInner, WshInner}; +use miniscript::descriptor::{DescriptorPublicKey, KeyMap, ShInner, WshInner}; use miniscript::{Descriptor, ScriptContext, Terminal}; use crate::types::KeychainKind; @@ -194,19 +204,18 @@ impl FullyNodedExport { /// /// If the database is empty or `include_blockheight` is false, the `blockheight` field /// returned will be `0`. - pub fn export_wallet( + pub fn export_wallet_with_keymaps( wallet: &Wallet, + external_keymap: &KeyMap, + internal_keymap: &KeyMap, label: &str, include_blockheight: bool, ) -> Result { - let descriptor = wallet - .public_descriptor(KeychainKind::External) - .to_string_with_secret( - &wallet - .get_signers(KeychainKind::External) - .as_key_map(wallet.secp_ctx()), - ); - let descriptor = remove_checksum(descriptor); + let descriptor = remove_checksum( + wallet + .public_descriptor(KeychainKind::External) + .to_string_with_secret(external_keymap), + ); Self::is_compatible_with_core(&descriptor)?; let blockheight = if include_blockheight { @@ -226,16 +235,11 @@ impl FullyNodedExport { blockheight, }; - let change_descriptor = { - let descriptor = wallet + let change_descriptor = Some(remove_checksum( + wallet .public_descriptor(KeychainKind::Internal) - .to_string_with_secret( - &wallet - .get_signers(KeychainKind::Internal) - .as_key_map(wallet.secp_ctx()), - ); - Some(remove_checksum(descriptor)) - }; + .to_string_with_secret(internal_keymap), + )); if export.change_descriptor() != change_descriptor { return Err("Incompatible change descriptor"); @@ -244,6 +248,25 @@ impl FullyNodedExport { Ok(export) } + /// Export a wallet to FullyNoded format using keys stored in the wallet. + pub fn export_wallet( + wallet: &Wallet, + label: &str, + include_blockheight: bool, + ) -> Result { + Self::export_wallet_with_keymaps( + wallet, + &wallet + .get_signers(KeychainKind::External) + .as_key_map(wallet.secp_ctx()), + &wallet + .get_signers(KeychainKind::Internal) + .as_key_map(wallet.secp_ctx()), + label, + include_blockheight, + ) + } + fn is_compatible_with_core(descriptor: &str) -> Result<(), &'static str> { fn check_ms( terminal: &Terminal, @@ -716,7 +739,7 @@ mod test { use core::str::FromStr; use bdk_chain::BlockId; - use bitcoin::{hashes::Hash, BlockHash, Network}; + use bitcoin::{hashes::Hash, secp256k1::Secp256k1, BlockHash, Network}; use super::*; use crate::test_utils::*; @@ -737,13 +760,31 @@ mod test { wallet } + fn keymaps_from_descriptors(external: &str, internal: &str) -> (KeyMap, KeyMap) { + let secp = Secp256k1::new(); + let (_, external_keymap) = + Descriptor::::parse_descriptor(&secp, external).unwrap(); + let (_, internal_keymap) = + Descriptor::::parse_descriptor(&secp, internal).unwrap(); + (external_keymap, internal_keymap) + } + #[test] fn test_export_bip44() { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin); - let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + let (external_keymap, internal_keymap) = + keymaps_from_descriptors(descriptor, change_descriptor); + let export = FullyNodedExport::export_wallet_with_keymaps( + &wallet, + &external_keymap, + &internal_keymap, + "Test Label", + true, + ) + .unwrap(); assert_eq!(export.descriptor(), descriptor); assert_eq!(export.change_descriptor(), Some(change_descriptor.into())); @@ -762,7 +803,16 @@ mod test { let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/0)"; let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin); - FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + let (external_keymap, internal_keymap) = + keymaps_from_descriptors(descriptor, change_descriptor); + FullyNodedExport::export_wallet_with_keymaps( + &wallet, + &external_keymap, + &internal_keymap, + "Test Label", + true, + ) + .unwrap(); } #[test] @@ -775,7 +825,16 @@ mod test { let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)"; let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin); - FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + let (external_keymap, internal_keymap) = + keymaps_from_descriptors(descriptor, change_descriptor); + FullyNodedExport::export_wallet_with_keymaps( + &wallet, + &external_keymap, + &internal_keymap, + "Test Label", + true, + ) + .unwrap(); } #[test] @@ -792,7 +851,16 @@ mod test { ))"; let wallet = get_test_wallet(descriptor, change_descriptor, Network::Testnet); - let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + let (external_keymap, internal_keymap) = + keymaps_from_descriptors(descriptor, change_descriptor); + let export = FullyNodedExport::export_wallet_with_keymaps( + &wallet, + &external_keymap, + &internal_keymap, + "Test Label", + true, + ) + .unwrap(); assert_eq!(export.descriptor(), descriptor); assert_eq!(export.change_descriptor(), Some(change_descriptor.into())); @@ -805,7 +873,16 @@ mod test { 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, change_descriptor, Network::Testnet); - let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + let (external_keymap, internal_keymap) = + keymaps_from_descriptors(descriptor, change_descriptor); + let export = FullyNodedExport::export_wallet_with_keymaps( + &wallet, + &external_keymap, + &internal_keymap, + "Test Label", + true, + ) + .unwrap(); assert_eq!(export.descriptor(), descriptor); assert_eq!(export.change_descriptor(), Some(change_descriptor.into())); assert_eq!(export.blockheight, 5000); @@ -818,7 +895,16 @@ mod test { let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin); - let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + let (external_keymap, internal_keymap) = + keymaps_from_descriptors(descriptor, change_descriptor); + let export = FullyNodedExport::export_wallet_with_keymaps( + &wallet, + &external_keymap, + &internal_keymap, + "Test Label", + true, + ) + .unwrap(); assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}"); } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 60d1cceff..6e28e6001 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -95,8 +95,9 @@ pub use utils::TxDetails; /// The `Wallet` acts as a way of coherently interfacing with output descriptors and related /// transactions. Its main components are: /// -/// 1. output *descriptors* from which it can derive addresses. -/// 2. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors. +/// 1. output *descriptors* from which it derives addresses. +/// 2. local chain state. +/// 3. a transaction graph indexed by those descriptors. /// /// The user is responsible for loading and writing wallet changes which are represented as /// [`ChangeSet`]s (see [`take_staged`]). Also see individual functions and example for instructions @@ -379,11 +380,12 @@ impl Wallet { /// Build [`Wallet`] by loading from persistence or [`ChangeSet`]. /// - /// Note that the descriptor secret keys are not persisted to the db. You can add - /// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. You - /// can also add keys when building the wallet by using [`LoadParams::keymap`]. Finally - /// you can check the wallet's descriptors are what you expect with [`LoadParams::descriptor`] - /// which will try to populate signers if [`LoadParams::extract_keys`] is enabled. + /// Note that descriptor secret keys are not persisted to the db. The recommended signing + /// flow is to keep signing keys outside the wallet and pass them to [`bitcoin::Psbt::sign`] + /// with [`Wallet::secp_ctx`]. + /// [`Wallet::sign_with_signers`] supports caller-owned [`SignersContainer`]s while the + /// [`signer`] module is phased out. + /// You can check the wallet's descriptors are what you expect with [`LoadParams::descriptor`]. /// /// # Synopsis /// @@ -402,18 +404,12 @@ impl Wallet { /// // Load a wallet that is persisted to SQLite database. /// # let temp_dir = tempfile::tempdir().expect("must create tempdir"); /// # let file_path = temp_dir.path().join("store.db"); - /// # let external_keymap = Default::default(); - /// # let internal_keymap = Default::default(); /// # let genesis_hash = BlockHash::all_zeros(); /// let mut conn = bdk_wallet::rusqlite::Connection::open(file_path)?; /// let mut wallet = Wallet::load() - /// // check loaded descriptors matches these values and extract private keys + /// // check loaded descriptors matches these values /// .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) /// .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - /// .extract_keys() - /// // you can also manually add private keys - /// .keymap(KeychainKind::External, external_keymap) - /// .keymap(KeychainKind::Internal, internal_keymap) /// // ensure loaded wallet's genesis hash matches this value /// .check_genesis_hash(genesis_hash) /// // set a lookahead for our indexer @@ -1563,18 +1559,24 @@ impl Wallet { /// # use bitcoin::*; /// # use bdk_wallet::*; /// # use bdk_wallet::ChangeSet; + /// # use bdk_wallet::descriptor::IntoWalletDescriptor; /// # use bdk_wallet::error::CreateTxError; /// # use anyhow::Error; - /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; + /// # use miniscript::descriptor::KeyMapWrapper; + /// # let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)"; /// # let mut wallet = doctest_wallet!(); /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); + /// let (_, keymap) = descriptor + /// .into_wallet_descriptor(wallet.secp_ctx(), wallet.network().into())?; + /// let signer = KeyMapWrapper::from(keymap); /// let mut psbt = { /// let mut builder = wallet.build_tx(); /// builder /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); /// builder.finish()? /// }; - /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; + /// psbt.sign(&signer, wallet.secp_ctx()).map_err(|(_, e)| Error::msg(format!("{e:?}")))?; + /// wallet.finalize_psbt(&mut psbt, SignOptions::default())?; /// let tx = psbt.clone().extract_tx().expect("tx"); /// // broadcast tx but it's taking too long to confirm so we want to bump the fee /// let mut psbt = { @@ -1584,7 +1586,8 @@ impl Wallet { /// builder.finish()? /// }; /// - /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; + /// psbt.sign(&signer, wallet.secp_ctx()).map_err(|(_, e)| Error::msg(format!("{e:?}")))?; + /// wallet.finalize_psbt(&mut psbt, SignOptions::default())?; /// let fee_bumped_tx = psbt.extract_tx(); /// // broadcast fee_bumped_tx to replace original /// # Ok::<(), anyhow::Error>(()) diff --git a/src/wallet/params.rs b/src/wallet/params.rs index 388d67c51..0470d8d83 100644 --- a/src/wallet/params.rs +++ b/src/wallet/params.rs @@ -253,11 +253,6 @@ impl LoadParams { } /// Checks the `expected_descriptor` matches exactly what is loaded for `keychain`. - /// - /// # Note - /// - /// You must also specify [`extract_keys`](Self::extract_keys) if you wish to add a signer - /// for an expected descriptor containing secrets. pub fn descriptor(mut self, keychain: KeychainKind, expected_descriptor: Option) -> Self where D: IntoWalletDescriptor + Send + 'static, @@ -272,11 +267,6 @@ impl LoadParams { /// Checks that the provided two-path descriptor matches exactly what is loaded for both the /// external and internal keychains. - /// - /// # Note - /// - /// You must also specify [`extract_keys`](Self::extract_keys) if you wish to add a signer - /// for an expected descriptor containing secrets. pub fn two_path_descriptor(mut self, expected_descriptor: D) -> Self where D: IntoWalletDescriptor + Send + Clone + 'static, diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index e369911b0..d9eb705ef 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -11,8 +11,12 @@ //! Generalized signers //! -//! This module provides the ability to add customized signers to a [`Wallet`](super::Wallet) -//! through the [`Wallet::add_signer`](super::Wallet::add_signer) function. +//! This module provides the ability to build caller-owned signer containers and use them with +//! [`Wallet::sign_with_signers`](super::Wallet::sign_with_signers). +//! +//! This is an interim bridge for callers still relying on bdk's signer infrastructure. Ideally +//! you should sign PSBTs with [`bitcoin::Psbt::sign`] (software signing), or have your own signer +//! (hardware signing). //! //! ``` //! # use alloc::sync::Arc; @@ -65,17 +69,19 @@ //! } //! } //! -//! let custom_signer = CustomSigner::connect(); +//! let custom_signer = Arc::new(CustomSigner::connect()); //! //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)"; //! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)"; -//! let mut wallet = Wallet::create(descriptor, change_descriptor) +//! let wallet = Wallet::create(descriptor, change_descriptor) //! .network(Network::Testnet) //! .create_wallet_no_persist()?; -//! wallet.add_signer( -//! KeychainKind::External, +//! +//! let mut external_signers = SignersContainer::new(); +//! external_signers.add_external( +//! custom_signer.id(wallet.secp_ctx()), //! SignerOrdering(200), -//! Arc::new(custom_signer) +//! custom_signer, //! ); //! //! # Ok::<_, anyhow::Error>(()) diff --git a/tests/add_foreign_utxo.rs b/tests/add_foreign_utxo.rs index 409d71fd6..430d11f4c 100644 --- a/tests/add_foreign_utxo.rs +++ b/tests/add_foreign_utxo.rs @@ -8,12 +8,14 @@ use bdk_wallet::KeychainKind; use bitcoin::{psbt, Address, Amount}; mod common; +use common::signers_from_descriptor; #[test] fn test_add_foreign_utxo() { - let (mut wallet1, _) = get_funded_wallet_wpkh(); - let (wallet2, _) = - get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); + let (wallet1_descriptor, wallet1_change_descriptor) = get_test_wpkh_and_change_desc(); + let (mut wallet1, _) = get_funded_wallet(wallet1_descriptor, wallet1_change_descriptor); + let wallet2_descriptor = "wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"; + let (wallet2, _) = get_funded_wallet_single(wallet2_descriptor); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() @@ -55,9 +57,12 @@ fn test_add_foreign_utxo() { "foreign_utxo should be in there" ); + let wallet1_external_signers = signers_from_descriptor(&wallet1, wallet1_descriptor); + let wallet1_internal_signers = signers_from_descriptor(&wallet1, wallet1_change_descriptor); let finished = wallet1 - .sign( + .sign_with_signers( &mut psbt, + &[&wallet1_external_signers, &wallet1_internal_signers], SignOptions { trust_witness_utxo: true, ..Default::default() @@ -70,9 +75,11 @@ fn test_add_foreign_utxo() { "only one of the inputs should have been signed so far" ); + let wallet2_signers = signers_from_descriptor(&wallet2, wallet2_descriptor); let finished = wallet2 - .sign( + .sign_with_signers( &mut psbt, + &[&wallet2_signers], SignOptions { trust_witness_utxo: true, ..Default::default() diff --git a/tests/common.rs b/tests/common.rs index c7a9b9c5f..3744a89c4 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,5 +1,8 @@ #![allow(unused)] +use bdk_wallet::descriptor::IntoWalletDescriptor; +use bdk_wallet::signer::SignersContainer; +use bdk_wallet::Wallet; use bitcoin::secp256k1::Secp256k1; use miniscript::{descriptor::KeyMap, Descriptor, DescriptorPublicKey}; @@ -17,6 +20,23 @@ pub fn parse_descriptor(s: &str) -> (Descriptor, KeyMap) { .expect("failed to parse descriptor") } +pub fn signers_from_descriptor( + wallet: &Wallet, + descriptor: impl IntoWalletDescriptor, +) -> SignersContainer { + let (descriptor, keymap) = descriptor + .into_wallet_descriptor(wallet.secp_ctx(), wallet.network().into()) + .expect("failed to parse signing descriptor"); + SignersContainer::build(keymap, &descriptor, wallet.secp_ctx()) +} + +pub fn keymap_from_descriptor(wallet: &Wallet, descriptor: impl IntoWalletDescriptor) -> KeyMap { + let (_, keymap) = descriptor + .into_wallet_descriptor(wallet.secp_ctx(), wallet.network().into()) + .expect("failed to parse signing descriptor"); + keymap +} + /// Validate and return the transaction fee from a PSBT. /// Panics if extraction fails, fee calculation fails, or if calculated fee doesn't match PSBT's /// fee. diff --git a/tests/persisted_wallet.rs b/tests/persisted_wallet.rs index bc1375948..d627e10d2 100644 --- a/tests/persisted_wallet.rs +++ b/tests/persisted_wallet.rs @@ -10,6 +10,10 @@ use bdk_chain::{ use bdk_wallet::coin_selection::InsufficientFunds; use bdk_wallet::descriptor::IntoWalletDescriptor; use bdk_wallet::error::CreateTxError; + +use bdk_wallet::persist_test_utils::{ + persist_keychains, persist_network, persist_single_keychain, persist_wallet_changeset, +}; use bdk_wallet::test_utils::*; use bdk_wallet::{ ChangeSet, KeychainKind, LoadError, LoadMismatch, LoadWithPersistError, Wallet, WalletPersister, @@ -23,10 +27,6 @@ use bitcoin::{ }; use miniscript::{Descriptor, DescriptorPublicKey}; -use bdk_wallet::persist_test_utils::{ - persist_keychains, persist_network, persist_single_keychain, persist_wallet_changeset, -}; - mod common; use common::*; @@ -354,12 +354,8 @@ fn wallet_should_persist_anchors_and_recover() { assert!(wallet.persist(&mut db).unwrap()); // should recover persisted wallet - let secp = wallet.secp_ctx(); - let (_, keymap) = >::parse_descriptor(secp, desc).unwrap(); - assert!(!keymap.is_empty()); let wallet = Wallet::load() .descriptor(KeychainKind::External, Some(desc)) - .extract_keys() .load_wallet(&mut db) .unwrap() .expect("must have loaded changeset"); @@ -380,8 +376,6 @@ fn wallet_should_persist_anchors_and_recover() { #[test] fn single_descriptor_wallet_persist_and_recover() { - use bdk_chain::miniscript::Descriptor; - use bdk_chain::miniscript::DescriptorPublicKey; use bdk_chain::rusqlite; let temp_dir = tempfile::tempdir().unwrap(); let db_path = temp_dir.path().join("wallet.db"); @@ -397,27 +391,18 @@ fn single_descriptor_wallet_persist_and_recover() { // should recover persisted wallet let secp = wallet.secp_ctx(); - let (_, keymap) = >::parse_descriptor(secp, desc).unwrap(); - assert!(!keymap.is_empty()); let wallet = Wallet::load() .descriptor(KeychainKind::External, Some(desc)) - .extract_keys() .load_wallet(&mut db) .unwrap() .expect("must have loaded changeset"); assert_eq!(wallet.derivation_index(KeychainKind::External), Some(2)); - // should have private key - assert_eq!( - wallet.get_signers(KeychainKind::External).as_key_map(secp), - keymap, - ); // should error on wrong internal params let desc = get_test_wpkh(); let (exp_desc, _) = >::parse_descriptor(secp, desc).unwrap(); let err = Wallet::load() .descriptor(KeychainKind::Internal, Some(desc)) - .extract_keys() .load_wallet(&mut db); assert_matches!( err, diff --git a/tests/psbt.rs b/tests/psbt.rs index 08c4acc9e..4b18b979c 100644 --- a/tests/psbt.rs +++ b/tests/psbt.rs @@ -1,7 +1,13 @@ +use bdk_wallet::bitcoin::bip32::Xpriv; +use bdk_wallet::bitcoin::psbt::SigningKeys; use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, TxIn}; use bdk_wallet::test_utils::*; use bdk_wallet::{psbt, KeychainKind, SignOptions}; use core::str::FromStr; +use miniscript::descriptor::KeyMapWrapper; + +mod common; +use common::{keymap_from_descriptor, signers_from_descriptor}; // from bip 174 const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA"; @@ -10,7 +16,8 @@ const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6 #[should_panic(expected = "InputIndexOutOfRange")] fn test_psbt_malformed_psbt_input_legacy() { let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); - let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); + let desc = get_test_wpkh(); + let (mut wallet, _) = get_funded_wallet_single(desc); let send_to = wallet.peek_address(KeychainKind::External, 0); let mut builder = wallet.build_tx(); builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); @@ -20,14 +27,18 @@ fn test_psbt_malformed_psbt_input_legacy() { trust_witness_utxo: true, ..Default::default() }; - let _ = wallet.sign(&mut psbt, options).unwrap(); + let signers = signers_from_descriptor(&wallet, desc); + let _ = wallet + .sign_with_signers(&mut psbt, &[&signers], options) + .unwrap(); } #[test] #[should_panic(expected = "InputIndexOutOfRange")] fn test_psbt_malformed_psbt_input_segwit() { let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); - let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); + let descriptor = get_test_wpkh(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); let send_to = wallet.peek_address(KeychainKind::External, 0); let mut builder = wallet.build_tx(); builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); @@ -37,13 +48,17 @@ fn test_psbt_malformed_psbt_input_segwit() { trust_witness_utxo: true, ..Default::default() }; - let _ = wallet.sign(&mut psbt, options).unwrap(); + let signers = signers_from_descriptor(&wallet, descriptor); + let _ = wallet + .sign_with_signers(&mut psbt, &[&signers], options) + .unwrap(); } #[test] #[should_panic(expected = "InputIndexOutOfRange")] fn test_psbt_malformed_tx_input() { - let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); + let descriptor = get_test_wpkh(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); let send_to = wallet.peek_address(KeychainKind::External, 0); let mut builder = wallet.build_tx(); builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); @@ -53,13 +68,17 @@ fn test_psbt_malformed_tx_input() { trust_witness_utxo: true, ..Default::default() }; - let _ = wallet.sign(&mut psbt, options).unwrap(); + let signers = signers_from_descriptor(&wallet, descriptor); + let _ = wallet + .sign_with_signers(&mut psbt, &[&signers], options) + .unwrap(); } #[test] fn test_psbt_sign_with_finalized() { let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); - let (mut wallet, _) = get_funded_wallet_wpkh(); + let (descriptor, change_descriptor) = get_test_wpkh_and_change_desc(); + let (mut wallet, _) = get_funded_wallet(descriptor, change_descriptor); let send_to = wallet.peek_address(KeychainKind::External, 0); let mut builder = wallet.build_tx(); builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); @@ -71,7 +90,15 @@ fn test_psbt_sign_with_finalized() { .input .push(psbt_bip.unsigned_tx.input[0].clone()); - let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); + let external_signers = signers_from_descriptor(&wallet, descriptor); + let internal_signers = signers_from_descriptor(&wallet, change_descriptor); + let _ = wallet + .sign_with_signers( + &mut psbt, + &[&external_signers, &internal_signers], + SignOptions::default(), + ) + .unwrap(); } #[test] @@ -80,7 +107,8 @@ fn test_psbt_fee_rate_with_witness_utxo() { let expected_fee_rate = FeeRate::from_sat_per_kwu(310); - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.peek_address(KeychainKind::External, 0); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); @@ -91,7 +119,9 @@ fn test_psbt_fee_rate_with_witness_utxo() { let unfinalized_fee_rate = psbt.fee_rate().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); + let finalized = wallet.finalize_psbt(&mut psbt, Default::default()).unwrap(); assert!(finalized); let finalized_fee_rate = psbt.fee_rate().unwrap(); @@ -105,7 +135,8 @@ fn test_psbt_fee_rate_with_nonwitness_utxo() { let expected_fee_rate = FeeRate::from_sat_per_kwu(310); - let (mut wallet, _) = get_funded_wallet_single("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.peek_address(KeychainKind::External, 0); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); @@ -115,7 +146,9 @@ fn test_psbt_fee_rate_with_nonwitness_utxo() { assert!(fee_amount.is_some()); let unfinalized_fee_rate = psbt.fee_rate().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); + let finalized = wallet.finalize_psbt(&mut psbt, Default::default()).unwrap(); assert!(finalized); let finalized_fee_rate = psbt.fee_rate().unwrap(); @@ -157,7 +190,7 @@ fn test_psbt_fee_rate_with_missing_txout() { #[test] fn test_psbt_multiple_internalkey_signers() { - use bdk_wallet::signer::{SignerContext, SignerOrdering, SignerWrapper}; + use bdk_wallet::signer::{SignerContext, SignerOrdering, SignerWrapper, TransactionSigner}; use bdk_wallet::KeychainKind; use bitcoin::key::TapTweak; use bitcoin::secp256k1::{schnorr, Keypair, Message, Secp256k1, XOnlyPublicKey}; @@ -179,20 +212,24 @@ fn test_psbt_multiple_internalkey_signers() { builder.drain_to(send_to.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); let unsigned_tx = psbt.unsigned_tx.clone(); + let mut signers = signers_from_descriptor(&wallet, desc.as_str()); // Adds a signer for the wrong internal key, bdk should not use this key to sign - wallet.add_signer( - KeychainKind::External, - // A signerordering lower than 100, bdk will use this signer first + let wrong_signer: Arc = Arc::new(SignerWrapper::new( + PrivateKey::from_wif("5J5PZqvCe1uThJ3FZeUUFLCh2FuK9pZhtEK4MzhNmugqTmxCdwE").unwrap(), + SignerContext::Tap { + is_internal_key: true, + }, + )); + signers.add_external( + wrong_signer.id(wallet.secp_ctx()), + // A signer ordering lower than 100, bdk will use this signer first SignerOrdering(0), - Arc::new(SignerWrapper::new( - PrivateKey::from_wif("5J5PZqvCe1uThJ3FZeUUFLCh2FuK9pZhtEK4MzhNmugqTmxCdwE").unwrap(), - SignerContext::Tap { - is_internal_key: true, - }, - )), + wrong_signer, ); - let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); + let finalized = wallet + .sign_with_signers(&mut psbt, &[&signers], SignOptions::default()) + .unwrap(); assert!(finalized); // To verify, we need the signature, message, and pubkey @@ -221,3 +258,82 @@ fn test_psbt_multiple_internalkey_signers() { let verify_res = secp.verify_schnorr(&signature, &message, &xonlykey); assert!(verify_res.is_ok(), "The wrong internal key was used"); } + +// wpkh PSBT with bip32_derivation populated, derived from +// tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L +const WPKH_PSBT_WITH_DERIVATION: &str = "cHNidP8BAHECAAAAAbOlV/kRKdNVk6Wn2cay5JUvpFw4tEsKWylqu+HfPKDyAAAAAAD9////ArObAAAAAAAAFgAU2+Ijq+8PDcPUGgHQqOPg9+6n9h8QJwAAAAAAABYAFKENkldInmhd2gMGYjkNwXeFL68T0AcAAAABAHEBAAAAATCzgdcz18YZK+8oNpJzqjM8ErFYW3hJLi+bO4bjmQrRAAAAAAD/////AlDDAAAAAAAAFgAUoQ2SV0ieaF3aAwZiOQ3Bd4UvrxOoYQAAAAAAABYAFIgWLNSQrRaGsRyWtCHaqeauCPYsAAAAAAEBH1DDAAAAAAAAFgAUoQ2SV0ieaF3aAwZiOQ3Bd4UvrxMiBgLOtp4iMz+DVWxdHvunWgM0a/PVLPvTn8XSTe0DTvfZ9Bjic/5CVAAAgAEAAIAAAACAAAAAAAAAAAAAIgIDxWYngmqPgL3saGZ4NTgcy5W/XINU8lkqnqjKC+oJuRwY4nP+QlQAAIABAACAAAAAgAEAAAAAAAAAACICAs62niIzP4NVbF0e+6daAzRr89Us+9OfxdJN7QNO99n0GOJz/kJUAACAAQAAgAAAAIAAAAAAAAAAAAA="; + +#[test] +fn test_psbt_sign_with_xpriv() { + let mut psbt = Psbt::from_str(WPKH_PSBT_WITH_DERIVATION).unwrap(); + let (desc, change_desc) = get_test_wpkh_and_change_desc(); + let (wallet, _) = get_funded_wallet(desc, change_desc); + + let xpriv = Xpriv::from_str( + "tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L", + ) + .unwrap(); + + let signing_keys = psbt + .sign(&xpriv, wallet.secp_ctx()) + .expect("PSBT signing should succeed with the correct xpriv"); + + assert!( + !signing_keys.is_empty(), + "expected at least one input to be signed" + ); +} + +#[test] +fn test_psbt_sign_with_wrong_key_signs_nothing() { + let mut psbt = Psbt::from_str(WPKH_PSBT_WITH_DERIVATION).unwrap(); + let (desc, change_desc) = get_test_wpkh_and_change_desc(); + let (wallet, _) = get_funded_wallet(desc, change_desc); + + let wrong_xpriv = Xpriv::from_str( + "tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS", + ) + .unwrap(); + + let signing_keys = psbt + .sign(&wrong_xpriv, wallet.secp_ctx()) + .expect("PSBT signing returns Ok even when no keys matched"); + + let keys_used: usize = signing_keys + .values() + .map(|keys| match keys { + SigningKeys::Ecdsa(v) => v.len(), + SigningKeys::Schnorr(v) => v.len(), + }) + .sum(); + assert_eq!( + keys_used, 0, + "expected zero keys used when signing with an unrelated xpriv" + ); + for input in &psbt.inputs { + assert!( + input.partial_sigs.is_empty(), + "expected no partial signatures when signing with an unrelated key" + ); + } +} + +#[test] +fn test_psbt_sign_with_descriptor_keymap() { + let mut psbt = Psbt::from_str(WPKH_PSBT_WITH_DERIVATION).unwrap(); + let (desc, change_desc) = get_test_wpkh_and_change_desc(); + let (wallet, _) = get_funded_wallet(desc, change_desc); + + let mut keymap = keymap_from_descriptor(&wallet, desc); + keymap.extend(keymap_from_descriptor(&wallet, change_desc)); + let wrapper = KeyMapWrapper::from(keymap); + + let signing_keys = psbt + .sign(&wrapper, wallet.secp_ctx()) + .expect("PSBT signing should succeed with descriptor keymap"); + + assert!( + !signing_keys.is_empty(), + "expected at least one input to be signed using descriptor keymap" + ); +} diff --git a/tests/wallet.rs b/tests/wallet.rs index 47afa502d..e36f48ec5 100644 --- a/tests/wallet.rs +++ b/tests/wallet.rs @@ -4,10 +4,10 @@ use std::sync::Arc; use assert_matches::assert_matches; use bdk_chain::{BlockId, CanonicalizationParams, ConfirmationBlockTime}; use bdk_wallet::coin_selection; -use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor}; +use bdk_wallet::descriptor::{calc_checksum, DescriptorError}; use bdk_wallet::error::CreateTxError; use bdk_wallet::psbt::PsbtUtils; -use bdk_wallet::signer::{SignOptions, SignerError, SignersContainer}; +use bdk_wallet::signer::{SignOptions, SignerError}; use bdk_wallet::test_utils::*; use bdk_wallet::KeychainKind; use bdk_wallet::{AddressInfo, Balance, PersistedWallet, Update, Wallet, WalletTx}; @@ -17,13 +17,15 @@ use bitcoin::script::PushBytesBuf; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::taproot::TapNodeHash; use bitcoin::{ - absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, NetworkKind, OutPoint, - ScriptBuf, Sequence, SignedAmount, Transaction, TxIn, TxOut, Txid, + absolute, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, ScriptBuf, + Sequence, SignedAmount, Transaction, TxIn, TxOut, Txid, }; +use miniscript::descriptor::KeyMapWrapper; use rand::rngs::StdRng; use rand::SeedableRng; mod common; +use common::{keymap_from_descriptor, signers_from_descriptor}; #[test] fn test_error_external_and_internal_are_the_same() { @@ -272,7 +274,8 @@ fn test_create_tx_default_locktime_cltv() { #[test] fn test_create_tx_locktime_cltv_timestamp() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv_timestamp()); + let descriptor = get_test_single_sig_cltv_timestamp(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -280,7 +283,11 @@ fn test_create_tx_locktime_cltv_timestamp() { assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 1_734_230_218); - let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); + let finalized = wallet + .finalize_psbt(&mut psbt, SignOptions::default()) + .unwrap(); assert!(finalized); } @@ -1355,13 +1362,17 @@ fn test_fee_amount_negative_drain_val() { #[test] fn test_sign_single_xprv() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = + "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); + let finalized = wallet.finalize_psbt(&mut psbt, Default::default()).unwrap(); assert!(finalized); let extracted = psbt.extract_tx().expect("failed to extract tx"); @@ -1372,22 +1383,8 @@ fn test_sign_single_xprv() { fn test_sign_with_signers() { let (descriptor, change_descriptor) = get_test_wpkh_and_change_desc(); let (mut wallet, _) = get_funded_wallet(descriptor, change_descriptor); - let (_, external_keymap) = descriptor - .into_wallet_descriptor(wallet.secp_ctx(), NetworkKind::Test) - .unwrap(); - let external_signers = SignersContainer::build( - external_keymap, - wallet.public_descriptor(KeychainKind::External), - wallet.secp_ctx(), - ); - let (_, internal_keymap) = change_descriptor - .into_wallet_descriptor(wallet.secp_ctx(), NetworkKind::Test) - .unwrap(); - let internal_signers = SignersContainer::build( - internal_keymap, - wallet.public_descriptor(KeychainKind::Internal), - wallet.secp_ctx(), - ); + let external_signers = signers_from_descriptor(&wallet, descriptor); + let internal_signers = signers_from_descriptor(&wallet, change_descriptor); let latest_block = wallet.latest_checkpoint().block_id(); let internal_addr = wallet.next_unused_address(KeychainKind::Internal); @@ -1422,13 +1419,18 @@ fn test_sign_with_signers() { #[test] fn test_sign_single_xprv_with_master_fingerprint_and_path() { - let (mut wallet, _) = get_funded_wallet_single("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = + "wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let signers = signers_from_descriptor(&wallet, descriptor); + let finalized = wallet + .sign_with_signers(&mut psbt, &[&signers], Default::default()) + .unwrap(); assert!(finalized); let extracted = psbt.extract_tx().expect("failed to extract tx"); @@ -1437,13 +1439,17 @@ fn test_sign_single_xprv_with_master_fingerprint_and_path() { #[test] fn test_sign_single_xprv_bip44_path() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)"); + let descriptor = + "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); + let finalized = wallet.finalize_psbt(&mut psbt, Default::default()).unwrap(); assert!(finalized); let extracted = psbt.extract_tx().expect("failed to extract tx"); @@ -1452,13 +1458,17 @@ fn test_sign_single_xprv_bip44_path() { #[test] fn test_sign_single_xprv_sh_wpkh() { - let (mut wallet, _) = get_funded_wallet_single("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))"); + let descriptor = + "sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); + let finalized = wallet.finalize_psbt(&mut psbt, Default::default()).unwrap(); assert!(finalized); let extracted = psbt.extract_tx().expect("failed to extract tx"); @@ -1467,14 +1477,16 @@ fn test_sign_single_xprv_sh_wpkh() { #[test] fn test_sign_single_wif() { - let (mut wallet, _) = - get_funded_wallet_single("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"); + let descriptor = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); + let finalized = wallet.finalize_psbt(&mut psbt, Default::default()).unwrap(); assert!(finalized); let extracted = psbt.extract_tx().expect("failed to extract tx"); @@ -1483,7 +1495,9 @@ fn test_sign_single_wif() { #[test] fn test_sign_single_xprv_no_hd_keypaths() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = + "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); @@ -1492,7 +1506,10 @@ fn test_sign_single_xprv_no_hd_keypaths() { psbt.inputs[0].bip32_derivation.clear(); assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0); - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + let signers = signers_from_descriptor(&wallet, descriptor); + let finalized = wallet + .sign_with_signers(&mut psbt, &[&signers], Default::default()) + .unwrap(); assert!(finalized); let extracted = psbt.extract_tx().expect("failed to extract tx"); @@ -1530,7 +1547,8 @@ fn test_output_redeem_witness_script_populated_automatically() { #[test] fn test_signing_only_one_of_multiple_inputs() { - let (mut wallet, _) = get_funded_wallet_wpkh(); + let (descriptor, change_descriptor) = get_test_wpkh_and_change_desc(); + let (mut wallet, _) = get_funded_wallet(descriptor, change_descriptor); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .assume_checked(); @@ -1553,14 +1571,12 @@ fn test_signing_only_one_of_multiple_inputs() { psbt.inputs.push(dud_input); psbt.unsigned_tx.input.push(bitcoin::TxIn::default()); + let mut keymap = keymap_from_descriptor(&wallet, descriptor); + keymap.extend(keymap_from_descriptor(&wallet, change_descriptor)); + let signer = KeyMapWrapper::from(keymap); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); let is_final = wallet - .sign( - &mut psbt, - SignOptions { - trust_witness_utxo: true, - ..Default::default() - }, - ) + .finalize_psbt(&mut psbt, SignOptions::default()) .unwrap(); assert!( !is_final, @@ -1574,7 +1590,10 @@ fn test_signing_only_one_of_multiple_inputs() { #[test] fn test_try_finalize_sign_option() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = + "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); for try_finalize in &[true, false] { let addr = wallet.next_unused_address(KeychainKind::External); @@ -1583,8 +1602,9 @@ fn test_try_finalize_sign_option() { let mut psbt = builder.finish().unwrap(); let finalized = wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { try_finalize: *try_finalize, ..Default::default() @@ -1608,7 +1628,9 @@ fn test_try_finalize_sign_option() { #[test] fn test_taproot_try_finalize_sign_option() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); + let descriptor = get_test_tr_with_taptree(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); for try_finalize in &[true, false] { let addr = wallet.next_unused_address(KeychainKind::External); @@ -1617,8 +1639,9 @@ fn test_taproot_try_finalize_sign_option() { let mut psbt = builder.finish().unwrap(); let finalized = wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { try_finalize: *try_finalize, ..Default::default() @@ -1659,7 +1682,10 @@ fn test_taproot_try_finalize_sign_option() { fn test_sign_nonstandard_sighash() { let sighash = EcdsaSighashType::NonePlusAnyoneCanPay; - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = + "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder @@ -1668,7 +1694,7 @@ fn test_sign_nonstandard_sighash() { .drain_wallet(); let mut psbt = builder.finish().unwrap(); - let result = wallet.sign(&mut psbt, Default::default()); + let result = wallet.sign_with_signers(&mut psbt, &[&signers], Default::default()); assert!( result.is_err(), "Signing should have failed because the TX uses non-standard sighashes" @@ -1680,8 +1706,9 @@ fn test_sign_nonstandard_sighash() { ); // try again after opting-in - let result = wallet.sign( + let result = wallet.sign_with_signers( &mut psbt, + &[&signers], SignOptions { allow_all_sighashes: true, ..Default::default() @@ -2144,15 +2171,18 @@ fn test_taproot_psbt_input_tap_tree() { #[test] fn test_taproot_sign_missing_witness_utxo() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); + let descriptor = get_test_tr_single_sig(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); let mut psbt = builder.finish().unwrap(); let witness_utxo = psbt.inputs[0].witness_utxo.take(); - let result = wallet.sign( + let result = wallet.sign_with_signers( &mut psbt, + &[&signers], SignOptions { allow_all_sighashes: true, ..Default::default() @@ -2167,8 +2197,9 @@ fn test_taproot_sign_missing_witness_utxo() { // restore the witness_utxo psbt.inputs[0].witness_utxo = witness_utxo; - let result = wallet.sign( + let result = wallet.sign_with_signers( &mut psbt, + &[&signers], SignOptions { allow_all_sighashes: true, ..Default::default() @@ -2184,7 +2215,8 @@ fn test_taproot_sign_missing_witness_utxo() { #[test] fn test_taproot_sign_using_non_witness_utxo() { - let (mut wallet, prev_txid) = get_funded_wallet_single(get_test_tr_single_sig()); + let descriptor = get_test_tr_single_sig(); + let (mut wallet, prev_txid) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); @@ -2198,15 +2230,16 @@ fn test_taproot_sign_using_non_witness_utxo() { "Previous tx should be present in the database" ); - let result = wallet.sign(&mut psbt, Default::default()); - assert!(result.is_ok(), "Signing should have worked"); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()) + .expect("signing should have worked"); assert!( - result.unwrap(), + wallet.finalize_psbt(&mut psbt, Default::default()).unwrap(), "Should finalize the input since we can produce signatures" ); } -fn test_spend_from_wallet(mut wallet: Wallet) { +fn test_spend_from_wallet(mut wallet: Wallet, descriptor: &str) { let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); @@ -2214,8 +2247,10 @@ fn test_spend_from_wallet(mut wallet: Wallet) { let mut psbt = builder.finish().unwrap(); assert_eq!(psbt.unsigned_tx.version.0, 2); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); assert!( - wallet.sign(&mut psbt, Default::default()).unwrap(), + wallet.finalize_psbt(&mut psbt, Default::default()).unwrap(), "Unable to finalize tx" ); } @@ -2231,7 +2266,9 @@ fn test_spend_from_wallet(mut wallet: Wallet) { #[test] fn test_taproot_no_key_spend() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); + let descriptor = get_test_tr_with_taptree_both_priv(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); @@ -2240,8 +2277,9 @@ fn test_taproot_no_key_spend() { assert!( wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { sign_with_tap_internal_key: false, ..Default::default() @@ -2256,17 +2294,21 @@ fn test_taproot_no_key_spend() { #[test] fn test_taproot_script_spend() { - let (wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); - test_spend_from_wallet(wallet); + let descriptor = get_test_tr_with_taptree(); + let (wallet, _) = get_funded_wallet_single(descriptor); + test_spend_from_wallet(wallet, descriptor); - let (wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_xprv()); - test_spend_from_wallet(wallet); + let descriptor = get_test_tr_with_taptree_xprv(); + let (wallet, _) = get_funded_wallet_single(descriptor); + test_spend_from_wallet(wallet, descriptor); } #[test] fn test_taproot_script_spend_sign_all_leaves() { use bdk_wallet::signer::TapLeavesOptions; - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); + let descriptor = get_test_tr_with_taptree_both_priv(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); @@ -2275,8 +2317,9 @@ fn test_taproot_script_spend_sign_all_leaves() { assert!( wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { tap_leaves_options: TapLeavesOptions::All, ..Default::default() @@ -2297,7 +2340,9 @@ fn test_taproot_script_spend_sign_include_some_leaves() { use bdk_wallet::signer::TapLeavesOptions; use bitcoin::taproot::TapLeafHash; - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); + let descriptor = get_test_tr_with_taptree_both_priv(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); @@ -2314,8 +2359,9 @@ fn test_taproot_script_spend_sign_include_some_leaves() { assert!( wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { tap_leaves_options: TapLeavesOptions::Include(included_script_leaves.clone()), ..Default::default() @@ -2337,7 +2383,9 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() { use bdk_wallet::signer::TapLeavesOptions; use bitcoin::taproot::TapLeafHash; - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); + let descriptor = get_test_tr_with_taptree_both_priv(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); @@ -2354,8 +2402,9 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() { assert!( wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { tap_leaves_options: TapLeavesOptions::Exclude(excluded_script_leaves.clone()), ..Default::default() @@ -2375,7 +2424,9 @@ fn test_taproot_script_spend_sign_exclude_some_leaves() { #[test] fn test_taproot_script_spend_sign_no_leaves() { use bdk_wallet::signer::TapLeavesOptions; - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); + let descriptor = get_test_tr_with_taptree_both_priv(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); @@ -2383,8 +2434,9 @@ fn test_taproot_script_spend_sign_no_leaves() { let mut psbt = builder.finish().unwrap(); wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { tap_leaves_options: TapLeavesOptions::None, ..Default::default() @@ -2397,7 +2449,9 @@ fn test_taproot_script_spend_sign_no_leaves() { #[test] fn test_taproot_sign_derive_index_from_psbt() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig_xprv()); + let descriptor = get_test_tr_single_sig_xprv(); + let change_descriptor = get_test_tr_single_sig(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); @@ -2406,22 +2460,27 @@ 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::create(get_test_tr_single_sig_xprv(), get_test_tr_single_sig()) + let wallet_empty = Wallet::create(descriptor, change_descriptor) .network(Network::Regtest) .create_wallet_no_persist() .unwrap(); // signing with an empty db means that we will only look at the psbt to infer the // derivation index + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet_empty, descriptor)); + psbt.sign(&signer, wallet_empty.secp_ctx()).unwrap(); assert!( - wallet_empty.sign(&mut psbt, Default::default()).unwrap(), + wallet_empty + .finalize_psbt(&mut psbt, Default::default()) + .unwrap(), "Unable to finalize tx" ); } #[test] fn test_taproot_sign_explicit_sighash_all() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); + let descriptor = get_test_tr_single_sig(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder @@ -2430,18 +2489,21 @@ fn test_taproot_sign_explicit_sighash_all() { .drain_wallet(); let mut psbt = builder.finish().unwrap(); - let result = wallet.sign(&mut psbt, Default::default()); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + let result = psbt.sign(&signer, wallet.secp_ctx()); assert!( result.is_ok(), "Signing should work because SIGHASH_ALL is safe" - ) + ); } #[test] fn test_taproot_sign_non_default_sighash() { let sighash = TapSighashType::NonePlusAnyoneCanPay; - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); + let descriptor = get_test_tr_single_sig(); + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let mut builder = wallet.build_tx(); builder @@ -2452,7 +2514,7 @@ fn test_taproot_sign_non_default_sighash() { let witness_utxo = psbt.inputs[0].witness_utxo.take(); - let result = wallet.sign(&mut psbt, Default::default()); + let result = wallet.sign_with_signers(&mut psbt, &[&signers], Default::default()); assert!( result.is_err(), "Signing should have failed because the TX uses non-standard sighashes" @@ -2464,8 +2526,9 @@ fn test_taproot_sign_non_default_sighash() { ); // try again after opting-in - let result = wallet.sign( + let result = wallet.sign_with_signers( &mut psbt, + &[&signers], SignOptions { allow_all_sighashes: true, ..Default::default() @@ -2484,8 +2547,9 @@ fn test_taproot_sign_non_default_sighash() { // restore the witness_utxo psbt.inputs[0].witness_utxo = witness_utxo; - let result = wallet.sign( + let result = wallet.sign_with_signers( &mut psbt, + &[&signers], SignOptions { allow_all_sighashes: true, ..Default::default() @@ -2662,7 +2726,10 @@ fn test_fee_rate_sign_no_grinding_high_r() { // Our goal is to obtain a transaction with a signature with high-R (71 bytes // instead of 70). We then check that our fee rate and fee calculation is // alright. - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = + "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let fee_rate = FeeRate::from_sat_per_vb_u32(1); let mut builder = wallet.build_tx(); @@ -2693,8 +2760,9 @@ fn test_fee_rate_sign_no_grinding_high_r() { psbt.inputs[0].partial_sigs.clear(); // Signing wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { try_finalize: false, allow_grinding: false, @@ -2711,8 +2779,9 @@ fn test_fee_rate_sign_no_grinding_high_r() { } // Actually finalizing the transaction... wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { allow_grinding: false, ..Default::default() @@ -2729,7 +2798,10 @@ fn test_fee_rate_sign_grinding_low_r() { // by setting the `allow_grinding` signing option as true. // We then check that our fee rate and fee calculation is alright and that our // signature is 70 bytes. - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); + let descriptor = + "wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"; + let (mut wallet, _) = get_funded_wallet_single(descriptor); + let signers = signers_from_descriptor(&wallet, descriptor); let addr = wallet.next_unused_address(KeychainKind::External); let fee_rate = FeeRate::from_sat_per_vb_u32(1); let mut builder = wallet.build_tx(); @@ -2741,8 +2813,9 @@ fn test_fee_rate_sign_grinding_low_r() { let fee = check_fee!(wallet, psbt); wallet - .sign( + .sign_with_signers( &mut psbt, + &[&signers], SignOptions { try_finalize: false, allow_grinding: true, @@ -2824,7 +2897,8 @@ fn test_thread_safety() { #[test] fn single_descriptor_wallet_can_create_tx_and_receive_change() { // create single descriptor wallet and fund it - let mut wallet = Wallet::create_single(get_test_tr_single_sig_xprv()) + let descriptor = get_test_tr_single_sig_xprv(); + let mut wallet = Wallet::create_single(descriptor) .network(Network::Testnet) .create_wallet_no_persist() .unwrap(); @@ -2838,7 +2912,11 @@ fn single_descriptor_wallet_can_create_tx_and_receive_change() { let mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), amount); let mut psbt = builder.finish().unwrap(); - assert!(wallet.sign(&mut psbt, SignOptions::default()).unwrap()); + let signer = KeyMapWrapper::from(keymap_from_descriptor(&wallet, descriptor)); + psbt.sign(&signer, wallet.secp_ctx()).unwrap(); + assert!(wallet + .finalize_psbt(&mut psbt, SignOptions::default()) + .unwrap()); let tx = psbt.extract_tx().unwrap(); let _txid = tx.compute_txid(); insert_tx(&mut wallet, tx); From c8109380fb6278dd35039e8b1de54ca59e9c9beb Mon Sep 17 00:00:00 2001 From: Noah Joeris Date: Wed, 1 Jul 2026 10:52:40 +0300 Subject: [PATCH 2/2] deprecate(wallet): wallet-owned signer APIs Mark Wallet::sign, signer/keymap accessors, keymap load/create helpers, and FullyNodedExport::export_wallet as deprecated. The replacement path is to keep KeyMaps/Xprivs outside Wallet, use bitcoin::Psbt::sign with wallet.secp_ctx(), or pass caller-owned SignersContainers to Wallet::sign_with_signers when SignOptions are needed. --- src/wallet/export.rs | 40 +++++++++++++++ src/wallet/mod.rs | 103 ++++++++++++++++++++++++++++++++++++++ src/wallet/params.rs | 12 +++++ tests/persisted_wallet.rs | 1 + 4 files changed, 156 insertions(+) diff --git a/src/wallet/export.rs b/src/wallet/export.rs index 1f90ed6f2..33e9e6ec2 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -249,6 +249,11 @@ impl FullyNodedExport { } /// Export a wallet to FullyNoded format using keys stored in the wallet. + #[deprecated( + since = "3.2.0", + note = "pass external and internal KeyMaps explicitly; use FullyNodedExport::export_wallet_with_keymaps instead." + )] + #[allow(deprecated)] pub fn export_wallet( wallet: &Wallet, label: &str, @@ -792,6 +797,41 @@ mod test { assert_eq!(export.label, "Test Label"); } + #[test] + #[allow(deprecated)] + fn test_deprecated_export_wallet_matches_export_with_keymaps() { + let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; + let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; + let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin); + let (external_keymap, internal_keymap) = + keymaps_from_descriptors(descriptor, change_descriptor); + + let deprecated_export = + FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + let export_with_keymaps = FullyNodedExport::export_wallet_with_keymaps( + &wallet, + &external_keymap, + &internal_keymap, + "Test Label", + true, + ) + .unwrap(); + + assert_eq!( + deprecated_export.descriptor(), + export_with_keymaps.descriptor() + ); + assert_eq!( + deprecated_export.change_descriptor(), + export_with_keymaps.change_descriptor() + ); + assert_eq!( + deprecated_export.blockheight, + export_with_keymaps.blockheight + ); + assert_eq!(deprecated_export.label, export_with_keymaps.label); + } + #[test] #[should_panic(expected = "Incompatible change descriptor")] fn test_export_no_change() { diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 6e28e6001..8a6795f8b 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -1123,6 +1123,10 @@ impl Wallet { /// Add an external signer /// /// See [the `signer` module](signer) for an example. + #[deprecated( + since = "3.2.0", + note = "use your KeyMap or Xpriv with bitcoin::Psbt::sign and wallet.secp_ctx() for software signing, or use your own signer for hardware signing. If you rely on SignOptions, use Wallet::sign_with_signers with caller-owned SignersContainers." + )] pub fn add_signer( &mut self, keychain: KeychainKind, @@ -1141,6 +1145,10 @@ impl Wallet { /// /// Note this does nothing if the given keychain has no descriptor because we won't /// know the context (segwit, taproot, etc) in which to create signatures. + #[deprecated( + since = "3.2.0", + note = "use your KeyMap or Xpriv with bitcoin::Psbt::sign and wallet.secp_ctx() for software signing, or use your own signer for hardware signing. If you rely on SignOptions, use Wallet::sign_with_signers with caller-owned SignersContainers." + )] pub fn set_keymap(&mut self, keychain: KeychainKind, keymap: KeyMap) { let wallet_signers = match keychain { KeychainKind::External => Arc::make_mut(&mut self.signers), @@ -1152,6 +1160,11 @@ impl Wallet { } /// Set the keymap for each keychain. + #[deprecated( + since = "3.2.0", + note = "use your KeyMap or Xpriv with bitcoin::Psbt::sign and wallet.secp_ctx() for software signing, or use your own signer for hardware signing. If you rely on SignOptions, use Wallet::sign_with_signers with caller-owned SignersContainers." + )] + #[allow(deprecated)] pub fn set_keymaps(&mut self, keymaps: impl IntoIterator) { for (keychain, keymap) in keymaps { self.set_keymap(keychain, keymap); @@ -1170,6 +1183,7 @@ impl Wallet { /// let wallet = Wallet::create(descriptor, change_descriptor) /// .network(Network::Testnet) /// .create_wallet_no_persist()?; + /// #[allow(deprecated)] /// 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); @@ -1177,6 +1191,10 @@ impl Wallet { /// /// Ok::<(), Box>(()) /// ``` + #[deprecated( + since = "3.2.0", + note = "the Wallet is being phased out as a key store; keep your own KeyMap or Xpriv instead. If you rely on SignOptions, use Wallet::sign_with_signers with caller-owned SignersContainers." + )] pub fn get_signers(&self, keychain: KeychainKind) -> Arc { match keychain { KeychainKind::External => Arc::clone(&self.signers), @@ -1748,10 +1766,15 @@ impl Wallet { /// builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); /// builder.finish()? /// }; + /// #[allow(deprecated)] /// let finalized = wallet.sign(&mut psbt, SignOptions::default())?; /// assert!(finalized, "we should have signed all the inputs"); /// # Ok::<(),anyhow::Error>(()) /// ``` + #[deprecated( + since = "3.2.0", + note = "use your KeyMap or Xpriv with bitcoin::Psbt::sign and wallet.secp_ctx() for software signing, or use your own signer for hardware signing. If you rely on SignOptions, use Wallet::sign_with_signers with caller-owned SignersContainers." + )] pub fn sign(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result { self.sign_with_signers( psbt, @@ -3156,4 +3179,84 @@ mod test { let wallet_name = result_with_change.unwrap(); assert_eq!(wallet_name, "vn4aqs37jgrerlc3"); } + + #[test] + #[allow(deprecated)] + fn test_deprecated_keymap_methods() { + use crate::test_utils::get_test_wpkh_and_change_desc; + + let (external_desc, internal_desc) = get_test_wpkh_and_change_desc(); + let secp = SecpCtx::new(); + let (external_descriptor, external_keymap) = external_desc + .into_wallet_descriptor(&secp, NetworkKind::Test) + .unwrap(); + let (internal_descriptor, internal_keymap) = internal_desc + .into_wallet_descriptor(&secp, NetworkKind::Test) + .unwrap(); + + let assert_keymaps = |wallet: &Wallet| { + assert_eq!( + wallet + .get_signers(KeychainKind::External) + .as_key_map(wallet.secp_ctx()), + external_keymap + ); + assert_eq!( + wallet + .get_signers(KeychainKind::Internal) + .as_key_map(wallet.secp_ctx()), + internal_keymap + ); + }; + + let wallet = Wallet::create(external_descriptor.clone(), internal_descriptor.clone()) + .network(Network::Testnet) + .keymap(KeychainKind::External, external_keymap.clone()) + .keymap(KeychainKind::Internal, internal_keymap.clone()) + .create_wallet_no_persist() + .unwrap(); + assert_keymaps(&wallet); + + let mut wallet = Wallet::create(external_descriptor, internal_descriptor) + .network(Network::Testnet) + .create_wallet_no_persist() + .unwrap(); + wallet.set_keymaps([ + (KeychainKind::External, external_keymap.clone()), + (KeychainKind::Internal, internal_keymap.clone()), + ]); + assert_keymaps(&wallet); + } + + #[test] + #[allow(deprecated)] + fn test_deprecated_sign_matches_sign_with_signers() { + use crate::test_utils::get_funded_wallet_wpkh; + + let (mut wallet, _) = get_funded_wallet_wpkh(); + let addr = wallet.next_unused_address(KeychainKind::External); + let mut builder = wallet.build_tx(); + builder.drain_to(addr.script_pubkey()).drain_wallet(); + let psbt = builder.finish().unwrap(); + + let mut deprecated_psbt = psbt.clone(); + let mut signers_psbt = psbt; + + let deprecated_finalized = wallet + .sign(&mut deprecated_psbt, SignOptions::default()) + .unwrap(); + let external_signers = wallet.get_signers(KeychainKind::External); + let internal_signers = wallet.get_signers(KeychainKind::Internal); + let signers_finalized = wallet + .sign_with_signers( + &mut signers_psbt, + &[external_signers.as_ref(), internal_signers.as_ref()], + SignOptions::default(), + ) + .unwrap(); + + assert!(deprecated_finalized); + assert_eq!(deprecated_finalized, signers_finalized); + assert_eq!(deprecated_psbt, signers_psbt); + } } diff --git a/src/wallet/params.rs b/src/wallet/params.rs index 0470d8d83..fdb1a13cc 100644 --- a/src/wallet/params.rs +++ b/src/wallet/params.rs @@ -141,6 +141,10 @@ impl CreateParams { } /// Extend the given `keychain`'s `keymap`. + #[deprecated( + since = "3.2.0", + note = "use your KeyMap or Xpriv with bitcoin::Psbt::sign and wallet.secp_ctx() for software signing, or use your own signer for hardware signing. If you rely on SignOptions, use Wallet::sign_with_signers with caller-owned SignersContainers." + )] pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self { match keychain { KeychainKind::External => &mut self.descriptor_keymap, @@ -243,6 +247,10 @@ impl LoadParams { } /// Extend the given `keychain`'s `keymap`. + #[deprecated( + since = "3.2.0", + note = "use your KeyMap or Xpriv with bitcoin::Psbt::sign and wallet.secp_ctx() for software signing, or use your own signer for hardware signing. If you rely on SignOptions, use Wallet::sign_with_signers with caller-owned SignersContainers." + )] pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self { match keychain { KeychainKind::External => &mut self.descriptor_keymap, @@ -307,6 +315,10 @@ impl LoadParams { /// Whether to try extracting private keys from the *provided descriptors* upon loading. /// See also [`LoadParams::descriptor`]. + #[deprecated( + since = "3.2.0", + note = "use your KeyMap or Xpriv with bitcoin::Psbt::sign and wallet.secp_ctx() for software signing, or use your own signer for hardware signing. If you rely on SignOptions, use Wallet::sign_with_signers with caller-owned SignersContainers." + )] pub fn extract_keys(mut self) -> Self { self.extract_keys = true; self diff --git a/tests/persisted_wallet.rs b/tests/persisted_wallet.rs index d627e10d2..6d1f8d320 100644 --- a/tests/persisted_wallet.rs +++ b/tests/persisted_wallet.rs @@ -230,6 +230,7 @@ fn wallet_is_persisted() -> anyhow::Result<()> { } #[test] +#[allow(deprecated)] fn wallet_load_checks() -> anyhow::Result<()> { fn run( filename: &str,