You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Merge #275: Multipath descriptor support (BIP 389)
da0b577 Update wallet/src/wallet/params.rs (Andreas Schjønhaug)
9e3adf2 feat(wallet): add multipath descriptor support for two-path descriptors (Andreas Schjønhaug)
Pull request description:
### Description
#### Key Features:
- New API: `Wallet::create_multipath(descriptor)` following the same pattern as `create()` and `create_single()`
- [BIP 389](https://github.com/bitcoin/bips/blob/master/bip-0389.mediawiki) compliance with exactly 2-path validation (receive and change)
- Robust validation with clear error messages
- Pattern consistency with existing wallet creation methods
#### Usage Example:
```rust
let multipath_desc = "wpkh([9a6a2580/84'/1'/0']tpub.../‹0;1›/*)";
let wallet = Wallet::create_multipath(multipath_desc)
.network(Network::Testnet)
.create_wallet_no_persist()?;
// Automatically creates separate receive and change descriptors
let receive_addr = wallet.peek_address(KeychainKind::External, 0); // Uses path /0/*
let change_addr = wallet.peek_address(KeychainKind::Internal, 0); // Uses path /1/*
```
### Notes to the reviewers
#### Design Decisions:
1. Pattern Consistency: Uses `make_multipath_descriptor_to_extract()` helper following the same pattern as existing `make_descriptor_to_extract()` function
2. Lazy Evaluation: Descriptor parsing only happens when needed during wallet creation, not during parameter setup - this maintains performance and follows Rust's lazy evaluation patterns
3. Strict Validation: Only allows exactly 2-path multipath descriptors to ensure proper receive/change separation
4. API Consistency: The `create_multipath()` method returns `CreateParams` just like `create()` and `create_single()`, maintaining the fluent builder pattern
#### Implementation Notes:
- The function calls `make_multipath_descriptor_to_extract()` twice (once for receive, once for change) which is intentional and follows the established pattern of lazy evaluation
- Validation occurs in the descriptor extraction closures, providing clear error messages when descriptors are actually processed
- All existing functionality remains unchanged - this is a purely additive feature
### Changelog notice
Added:
- `Wallet::create_multipath()` method for creating wallets from BIP 389 multipath descriptors
- `CreateParams::new_multipath()` for multipath descriptor parameter creation
- Support for 2-path multipath descriptors with automatic receive/change separation
- Enhanced validation for multipath descriptors with descriptive error messages
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
* [x] I ran `just p` before pushing
#### New Features:
* [x] I've added tests for the new feature
* [x] I've added docs for the new feature
#### Bugfixes:
* [ ] This pull request breaks the existing API
* [ ] I've added tests to reproduce the issue which are now passing
* [x] I'm linking the issue being fixed by this PR
Closes#11
ACKs for top commit:
notmandatory:
ACK da0b577
Tree-SHA512: 5d714e8f44a5ba1c3d956a5b2d93abba363ca1eb965dcc6d9492f1fef2239a10b560929af2bba221a49cb97e611253f2f1be5f41441122047f361848ec4e13f1
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
let two_path_descriptor = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1>/*)";
2811
+
2812
+
// Test successful creation of a two-path wallet
2813
+
let params = Wallet::create_from_two_path_descriptor(two_path_descriptor);
2814
+
let wallet = params.network(Network::Testnet).create_wallet_no_persist();
2815
+
assert!(wallet.is_ok());
2816
+
2817
+
let wallet = wallet.unwrap();
2818
+
2819
+
// Verify that the wallet has both external and internal keychains
2820
+
let keychains:Vec<_> = wallet.keychains().collect();
2821
+
assert_eq!(keychains.len(),2);
2822
+
2823
+
// Verify that the descriptors are different (receive vs change)
let single_path_descriptor = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/0/*)";
2846
+
let params = Wallet::create_from_two_path_descriptor(single_path_descriptor);
2847
+
let wallet = params.network(Network::Testnet).create_wallet_no_persist();
let three_path_descriptor = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1;2>/*)";
2852
+
let params = Wallet::create_from_two_path_descriptor(three_path_descriptor);
2853
+
let wallet = params.network(Network::Testnet).create_wallet_no_persist();
0 commit comments