From 0ab57bb0785346a5aea37ae63a51df83dcd3bfc5 Mon Sep 17 00:00:00 2001 From: Gaurav Chaudhary Date: Fri, 10 Jul 2026 13:47:30 +0530 Subject: [PATCH] docs(wallet): remove outdated xpub-only multipath restriction Update two-path descriptor docs to reflect that both public and private extended keys are supported. Add an ignored test for private multipath wallet creation to enable when miniscript 13.1.0 ships with 4.0. Closes #511 --- CHANGELOG.md | 6 ++++++ src/wallet/mod.rs | 33 ++++++++++++++++++++++++++------- src/wallet/params.rs | 6 ++++-- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a27fa13..83dc6358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ Contributors do not need to change this file but do need to add changelog detail The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- docs(wallet): remove outdated xpub-only restriction for two-path multipath descriptors ([#511](https://github.com/bitcoindevkit/bdk_wallet/issues/511)) + ## [v3.1.0] ### Added diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 5bd42ba7..3beaf072 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -270,8 +270,10 @@ impl Wallet { /// Build a new [`Wallet`] from a two-path descriptor. /// /// This function parses a multipath descriptor with exactly 2 paths and creates a wallet - /// using the existing receive and change wallet creation logic. Note that you can only use this - /// method with public extended keys (`xpub` prefix) to create watch-only wallets. + /// using the existing receive and change wallet creation logic. + /// + /// Both public (`xpub`/`tpub`) and private (`xprv`/`tprv`) extended keys are supported. + /// An `xpub` descriptor creates a watch-only wallet; an `xprv` descriptor enables signing. /// /// Multipath descriptors follow [BIP 389] and allow defining both receive and change /// derivation paths in a single descriptor using the `<0;1>` syntax. @@ -279,8 +281,7 @@ impl Wallet { /// If you have previously created a wallet, use [`load`](Self::load) instead. /// /// # Errors - /// Returns an error if the descriptor is invalid, not a 2-path multipath descriptor, or if - /// the descriptor provided contains an extended private key (`xprv` prefix). + /// Returns an error if the descriptor is invalid or not a 2-path multipath descriptor. /// /// # Synopsis /// @@ -3164,9 +3165,8 @@ mod test { let wallet = params.network(Network::Testnet).create_wallet_no_persist(); assert!(matches!(wallet, Err(DescriptorError::MultiPath))); - // Test with a private descriptor - // You get a Miniscript(Unexpected("Can't make an extended private key with multiple paths - // into a public key.")) error. + // With miniscript 12.x, private multipath descriptors fail at parse time. This assertion + // documents current behavior; remove when miniscript 13.1.0+ is available (see #511). let private_multipath_descriptor = "wpkh(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/84'/1'/0'/<0;1>/*)"; let params = Wallet::create_from_two_path_descriptor(private_multipath_descriptor); let wallet = params.network(Network::Testnet).create_wallet_no_persist(); @@ -3187,6 +3187,25 @@ mod test { let wallet = params.network(Network::Testnet).create_wallet_no_persist(); assert!(wallet.is_err()); } + + #[test] + #[ignore = "requires miniscript 13.1.0; enable when bdk_chain 0.24.0 is released (#511)"] + fn test_create_two_path_wallet_private_multipath() { + let desc_private = "tr(tprv8ZgxMBicQKsPdfqH2fGKQkBAMXpqCpC6v6WhYnEZC7TbpcEavC1N27tHbFP16eLm9XdFDW6cqnGChit8gWXyyT1zQ3xFqUWgHTS9XBQw3j5/1'/2'/0/<0;1>/*)"; + + let wallet = Wallet::create_from_two_path_descriptor(desc_private) + .network(Network::Testnet) + .create_wallet_no_persist() + .expect("private multipath descriptor should create wallet"); + + let keychains: Vec<_> = wallet.keychains().collect(); + assert_eq!(keychains.len(), 2); + + let external_addr = wallet.peek_address(KeychainKind::External, 0); + let internal_addr = wallet.peek_address(KeychainKind::Internal, 0); + assert_ne!(external_addr.address, internal_addr.address); + } + #[test] fn test_wallet_name_from_descriptor_public_key_check() { let secp = SecpCtx::new(); diff --git a/src/wallet/params.rs b/src/wallet/params.rs index f4ed438a..dbc326d8 100644 --- a/src/wallet/params.rs +++ b/src/wallet/params.rs @@ -121,6 +121,8 @@ impl CreateParams { /// This function parses a two-path descriptor (receive and change) and creates parameters /// using the existing receive and change wallet creation logic. /// + /// Both public (`xpub`/`tpub`) and private (`xprv`/`tprv`) extended keys are supported. + /// /// Default values: /// * `network` = [`Network::Bitcoin`] /// * `genesis_hash` = `None` @@ -275,8 +277,8 @@ impl LoadParams { /// /// # Note /// - /// The provided descriptor may only contain extended public keys (`xpub`) with exactly 2 paths, - /// or an error will occur at load time. + /// The descriptor must contain exactly 2 paths (receive and change). Both public (`xpub`) + /// and private (`xprv`) extended keys are supported. pub fn two_path_descriptor(mut self, expected_descriptor: D) -> Self where D: IntoWalletDescriptor + Send + Clone + 'static,