Skip to content

Commit 3951110

Browse files
committed
fix(wallet)!: Change method LoadParams::descriptors
to just `descriptor` that takes a `KeychainKind` and optional `D: IntoWalletDescriptor` representing the expected value of the descriptor in the changeset. Add method `LoadParams::extract_keys` that will use any private keys in the provided descriptors to add a signer to the wallet.
1 parent b802714 commit 3951110

File tree

8 files changed

+166
-76
lines changed

8 files changed

+166
-76
lines changed

crates/wallet/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,10 @@ let network = Network::Testnet;
7979
let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
8080
let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)";
8181
let wallet_opt = Wallet::load()
82-
.descriptors(descriptor, change_descriptor)
8382
.network(network)
83+
.descriptor(KeychainKind::External, Some(descriptor))
84+
.descriptor(KeychainKind::Internal, Some(change_descriptor))
85+
.extract_keys()
8486
.load_wallet(&mut db)
8587
.expect("wallet");
8688
let mut wallet = match wallet_opt {

crates/wallet/src/wallet/mod.rs

Lines changed: 90 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,9 @@ pub enum LoadMismatch {
246246
/// Keychain identifying the descriptor.
247247
keychain: KeychainKind,
248248
/// The loaded descriptor.
249-
loaded: ExtendedDescriptor,
249+
loaded: Option<ExtendedDescriptor>,
250250
/// The expected descriptor.
251-
expected: ExtendedDescriptor,
251+
expected: Option<ExtendedDescriptor>,
252252
},
253253
}
254254

@@ -401,11 +401,15 @@ impl Wallet {
401401
));
402402

403403
let (change_descriptor, change_signers) = match params.change_descriptor {
404-
Some(desc_fn) => {
405-
let (descriptor, mut keymap) = desc_fn(&secp, network)?;
406-
keymap.extend(params.change_descriptor_keymap);
407-
let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, &secp));
408-
(Some(descriptor), change_signers)
404+
Some(make_desc) => {
405+
let (change_descriptor, mut internal_keymap) = make_desc(&secp, network)?;
406+
internal_keymap.extend(params.change_descriptor_keymap);
407+
let change_signers = Arc::new(SignersContainer::build(
408+
internal_keymap,
409+
&change_descriptor,
410+
&secp,
411+
));
412+
(Some(change_descriptor), change_signers)
409413
}
410414
None => (None, Arc::new(SignersContainer::new())),
411415
};
@@ -442,7 +446,7 @@ impl Wallet {
442446
/// Note that the descriptor secret keys are not persisted to the db. You can either add
443447
/// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. Or you can
444448
/// add keys when building the wallet using [`LoadParams::keymap`] and/or
445-
/// [`LoadParams::descriptors`].
449+
/// [`LoadParams::descriptor`].
446450
///
447451
/// # Synopsis
448452
///
@@ -467,7 +471,9 @@ impl Wallet {
467471
/// let mut conn = bdk_wallet::rusqlite::Connection::open(file_path)?;
468472
/// let mut wallet = Wallet::load()
469473
/// // check loaded descriptors matches these values and extract private keys
470-
/// .descriptors(EXTERNAL_DESC, INTERNAL_DESC)
474+
/// .descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
475+
/// .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
476+
/// .extract_keys()
471477
/// // you can also manually add private keys
472478
/// .keymap(KeychainKind::External, external_keymap)
473479
/// .keymap(KeychainKind::Internal, internal_keymap)
@@ -499,42 +505,6 @@ impl Wallet {
499505
let chain = LocalChain::from_changeset(changeset.local_chain)
500506
.map_err(|_| LoadError::MissingGenesis)?;
501507

502-
let mut descriptor_keymap = params.descriptor_keymap;
503-
let descriptor = changeset
504-
.descriptor
505-
.ok_or(LoadError::MissingDescriptor(KeychainKind::External))?;
506-
check_wallet_descriptor(&descriptor).map_err(LoadError::Descriptor)?;
507-
508-
let (change_descriptor, change_signers) = match changeset.change_descriptor {
509-
Some(change_descriptor) => {
510-
check_wallet_descriptor(&change_descriptor).map_err(LoadError::Descriptor)?;
511-
let mut change_descriptor_keymap = params.change_descriptor_keymap;
512-
513-
// check params match loaded
514-
if let Some(exp_change_descriptor) = params.check_change_descriptor {
515-
let (exp_change_descriptor, keymap) =
516-
(exp_change_descriptor)(&secp, network).map_err(LoadError::Descriptor)?;
517-
change_descriptor_keymap.extend(keymap);
518-
519-
if change_descriptor.descriptor_id() != exp_change_descriptor.descriptor_id() {
520-
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
521-
keychain: KeychainKind::Internal,
522-
loaded: change_descriptor,
523-
expected: exp_change_descriptor,
524-
}));
525-
}
526-
}
527-
let change_signers = Arc::new(SignersContainer::build(
528-
change_descriptor_keymap,
529-
&change_descriptor,
530-
&secp,
531-
));
532-
(Some(change_descriptor), change_signers)
533-
}
534-
None => (None, Arc::new(SignersContainer::new())),
535-
};
536-
537-
// checks
538508
if let Some(exp_network) = params.check_network {
539509
if network != exp_network {
540510
return Err(LoadError::Mismatch(LoadMismatch::Network {
@@ -551,25 +521,88 @@ impl Wallet {
551521
}));
552522
}
553523
}
554-
if let Some(exp_descriptor) = params.check_descriptor {
555-
let (exp_descriptor, keymap) =
556-
(exp_descriptor)(&secp, network).map_err(LoadError::Descriptor)?;
557-
descriptor_keymap.extend(keymap);
558524

559-
if descriptor.descriptor_id() != exp_descriptor.descriptor_id() {
525+
let descriptor = changeset
526+
.descriptor
527+
.ok_or(LoadError::MissingDescriptor(KeychainKind::External))?;
528+
check_wallet_descriptor(&descriptor).map_err(LoadError::Descriptor)?;
529+
let mut external_keymap = params.descriptor_keymap;
530+
531+
if let Some(expected) = params.check_descriptor {
532+
if let Some(make_desc) = expected {
533+
let (exp_desc, keymap) =
534+
make_desc(&secp, network).map_err(LoadError::Descriptor)?;
535+
if descriptor.descriptor_id() != exp_desc.descriptor_id() {
536+
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
537+
keychain: KeychainKind::External,
538+
loaded: Some(descriptor),
539+
expected: Some(exp_desc),
540+
}));
541+
}
542+
if params.extract_keys {
543+
external_keymap.extend(keymap);
544+
}
545+
} else {
560546
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
561547
keychain: KeychainKind::External,
562-
loaded: descriptor,
563-
expected: exp_descriptor,
548+
loaded: Some(descriptor),
549+
expected: None,
564550
}));
565551
}
566552
}
553+
let signers = Arc::new(SignersContainer::build(external_keymap, &descriptor, &secp));
554+
555+
let (mut change_descriptor, mut change_signers) = (None, Arc::new(SignersContainer::new()));
556+
match (changeset.change_descriptor, params.check_change_descriptor) {
557+
// empty signer
558+
(None, None) => {}
559+
(None, Some(expect)) => {
560+
// expected desc but none loaded
561+
if let Some(make_desc) = expect {
562+
let (exp_desc, _) = make_desc(&secp, network).map_err(LoadError::Descriptor)?;
563+
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
564+
keychain: KeychainKind::Internal,
565+
loaded: None,
566+
expected: Some(exp_desc),
567+
}));
568+
}
569+
}
570+
// nothing expected
571+
(Some(desc), None) => {
572+
check_wallet_descriptor(&desc).map_err(LoadError::Descriptor)?;
573+
change_descriptor = Some(desc);
574+
}
575+
(Some(desc), Some(expect)) => match expect {
576+
// expected none for existing
577+
None => {
578+
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
579+
keychain: KeychainKind::Internal,
580+
loaded: Some(desc),
581+
expected: None,
582+
}))
583+
}
584+
// parameters must match
585+
Some(make_desc) => {
586+
let (exp_desc, keymap) =
587+
make_desc(&secp, network).map_err(LoadError::Descriptor)?;
588+
if desc.descriptor_id() != exp_desc.descriptor_id() {
589+
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
590+
keychain: KeychainKind::Internal,
591+
loaded: Some(desc),
592+
expected: Some(exp_desc),
593+
}));
594+
}
595+
let mut internal_keymap = params.change_descriptor_keymap;
596+
if params.extract_keys {
597+
internal_keymap.extend(keymap);
598+
}
599+
change_signers =
600+
Arc::new(SignersContainer::build(internal_keymap, &desc, &secp));
601+
change_descriptor = Some(desc);
602+
}
603+
},
604+
}
567605

568-
let signers = Arc::new(SignersContainer::build(
569-
descriptor_keymap,
570-
&descriptor,
571-
&secp,
572-
));
573606
let index = create_indexer(descriptor, change_descriptor, params.lookahead)
574607
.map_err(LoadError::Descriptor)?;
575608

crates/wallet/src/wallet/params.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,9 @@ pub struct LoadParams {
144144
pub(crate) lookahead: u32,
145145
pub(crate) check_network: Option<Network>,
146146
pub(crate) check_genesis_hash: Option<BlockHash>,
147-
pub(crate) check_descriptor: Option<DescriptorToExtract>,
148-
pub(crate) check_change_descriptor: Option<DescriptorToExtract>,
147+
pub(crate) check_descriptor: Option<Option<DescriptorToExtract>>,
148+
pub(crate) check_change_descriptor: Option<Option<DescriptorToExtract>>,
149+
pub(crate) extract_keys: bool,
149150
}
150151

151152
impl LoadParams {
@@ -161,6 +162,7 @@ impl LoadParams {
161162
check_genesis_hash: None,
162163
check_descriptor: None,
163164
check_change_descriptor: None,
165+
extract_keys: false,
164166
}
165167
}
166168

@@ -174,14 +176,21 @@ impl LoadParams {
174176
self
175177
}
176178

177-
/// Checks that `descriptor` of `keychain` matches this, and extracts private keys (if
178-
/// available).
179-
pub fn descriptors<D>(mut self, descriptor: D, change_descriptor: D) -> Self
179+
/// Checks the `expected_descriptor` matches exactly what is loaded for `keychain`.
180+
///
181+
/// # Note
182+
///
183+
/// You must also specify [`extract_keys`](Self::extract_keys) if you wish to add a signer
184+
/// for an expected descriptor containing secrets.
185+
pub fn descriptor<D>(mut self, keychain: KeychainKind, expected_descriptor: Option<D>) -> Self
180186
where
181187
D: IntoWalletDescriptor + 'static,
182188
{
183-
self.check_descriptor = Some(make_descriptor_to_extract(descriptor));
184-
self.check_change_descriptor = Some(make_descriptor_to_extract(change_descriptor));
189+
let expected = expected_descriptor.map(|d| make_descriptor_to_extract(d));
190+
match keychain {
191+
KeychainKind::External => self.check_descriptor = Some(expected),
192+
KeychainKind::Internal => self.check_change_descriptor = Some(expected),
193+
}
185194
self
186195
}
187196

@@ -203,6 +212,13 @@ impl LoadParams {
203212
self
204213
}
205214

215+
/// Whether to try extracting private keys from the *provided descriptors* upon loading.
216+
/// See also [`LoadParams::descriptor`].
217+
pub fn extract_keys(mut self) -> Self {
218+
self.extract_keys = true;
219+
self
220+
}
221+
206222
/// Load [`PersistedWallet`] with the given `Db`.
207223
pub fn load_wallet<Db>(
208224
self,

crates/wallet/tests/wallet.rs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,18 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
134134
};
135135

136136
// recover wallet
137+
{
138+
let mut db = open_db(&file_path).context("failed to recover db")?;
139+
let _ = Wallet::load()
140+
.network(Network::Testnet)
141+
.load_wallet(&mut db)?
142+
.expect("wallet must exist");
143+
}
137144
{
138145
let mut db = open_db(&file_path).context("failed to recover db")?;
139146
let wallet = Wallet::load()
140-
.descriptors(external_desc, internal_desc)
147+
.descriptor(KeychainKind::External, Some(external_desc))
148+
.descriptor(KeychainKind::Internal, Some(internal_desc))
141149
.network(Network::Testnet)
142150
.load_wallet(&mut db)?
143151
.expect("wallet must exist");
@@ -240,7 +248,16 @@ fn wallet_load_checks() -> anyhow::Result<()> {
240248
);
241249
assert_matches!(
242250
Wallet::load()
243-
.descriptors(internal_desc, external_desc)
251+
.descriptor(KeychainKind::External, Some(internal_desc))
252+
.load_wallet(&mut open_db(&file_path)?),
253+
Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(
254+
LoadMismatch::Descriptor { .. }
255+
))),
256+
"unexpected descriptors check result",
257+
);
258+
assert_matches!(
259+
Wallet::load()
260+
.descriptor(KeychainKind::External, Option::<&str>::None)
244261
.load_wallet(&mut open_db(&file_path)?),
245262
Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(
246263
LoadMismatch::Descriptor { .. }
@@ -279,25 +296,39 @@ fn single_descriptor_wallet_persist_and_recover() {
279296
.network(Network::Testnet)
280297
.create_wallet(&mut db)
281298
.unwrap();
282-
283299
let _ = wallet.reveal_addresses_to(KeychainKind::External, 2);
284300
assert!(wallet.persist(&mut db).unwrap());
285301

286302
// should recover persisted wallet
287303
let secp = wallet.secp_ctx();
288304
let (_, keymap) = <Descriptor<DescriptorPublicKey>>::parse_descriptor(secp, desc).unwrap();
289-
let wallet = LoadParams::new()
290-
.keymap(KeychainKind::External, keymap.clone())
305+
assert!(!keymap.is_empty());
306+
let wallet = Wallet::load()
307+
.descriptor(KeychainKind::External, Some(desc))
308+
.extract_keys()
291309
.load_wallet(&mut db)
292310
.unwrap()
293311
.expect("must have loaded changeset");
294-
295312
assert_eq!(wallet.derivation_index(KeychainKind::External), Some(2));
296313
// should have private key
297314
assert_eq!(
298315
wallet.get_signers(KeychainKind::External).as_key_map(secp),
299316
keymap,
300317
);
318+
319+
// should error on wrong internal params
320+
let desc = get_test_wpkh();
321+
let (exp_desc, _) = <Descriptor<DescriptorPublicKey>>::parse_descriptor(secp, desc).unwrap();
322+
let err = Wallet::load()
323+
.descriptor(KeychainKind::Internal, Some(desc))
324+
.extract_keys()
325+
.load_wallet(&mut db);
326+
assert_matches!(
327+
err,
328+
Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(LoadMismatch::Descriptor { keychain, loaded, expected })))
329+
if keychain == KeychainKind::Internal && loaded.is_none() && expected == Some(exp_desc),
330+
"single descriptor wallet should refuse change descriptor param"
331+
);
301332
}
302333

303334
#[test]

example-crates/wallet_electrum/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ fn main() -> Result<(), anyhow::Error> {
2626
let mut db = Store::<bdk_wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
2727

2828
let wallet_opt = Wallet::load()
29-
.descriptors(EXTERNAL_DESC, INTERNAL_DESC)
3029
.network(NETWORK)
30+
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
31+
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
32+
.extract_keys()
3133
.load_wallet(&mut db)?;
3234
let mut wallet = match wallet_opt {
3335
Some(wallet) => wallet,

example-crates/wallet_esplora_async/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ async fn main() -> Result<(), anyhow::Error> {
2323
let mut conn = Connection::open(DB_PATH)?;
2424

2525
let wallet_opt = Wallet::load()
26-
.descriptors(EXTERNAL_DESC, INTERNAL_DESC)
2726
.network(NETWORK)
27+
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
28+
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
29+
.extract_keys()
2830
.load_wallet(&mut conn)?;
2931
let mut wallet = match wallet_opt {
3032
Some(wallet) => wallet,

example-crates/wallet_esplora_blocking/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ fn main() -> Result<(), anyhow::Error> {
2222
let mut db = Store::<bdk_wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), DB_PATH)?;
2323

2424
let wallet_opt = Wallet::load()
25-
.descriptors(EXTERNAL_DESC, INTERNAL_DESC)
2625
.network(NETWORK)
26+
.descriptor(KeychainKind::External, Some(EXTERNAL_DESC))
27+
.descriptor(KeychainKind::Internal, Some(INTERNAL_DESC))
28+
.extract_keys()
2729
.load_wallet(&mut db)?;
2830
let mut wallet = match wallet_opt {
2931
Some(wallet) => wallet,

0 commit comments

Comments
 (0)