Skip to content

Commit 075073b

Browse files
feat: require explicit wildcard derivation for descriptor key types
1 parent 35dcab1 commit 075073b

File tree

1 file changed

+90
-30
lines changed

1 file changed

+90
-30
lines changed

bdk-ffi/src/keys.rs

Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl Display for Mnemonic {
6565
}
6666

6767
/// A BIP-32 derivation path.
68-
#[derive(uniffi::Object)]
68+
#[derive(uniffi::Object, Debug)]
6969
pub struct DerivationPath {
7070
inner_mutex: Mutex<BdkDerivationPath>,
7171
}
@@ -83,23 +83,23 @@ impl DerivationPath {
8383
}
8484
}
8585

86-
/// A descriptor containing secret data.
86+
/// The descriptor secret key, either a single private key or an xprv.
8787
#[derive(Debug, uniffi::Object)]
8888
#[uniffi::export(Debug, Display)]
8989
pub struct DescriptorSecretKey(pub(crate) BdkDescriptorSecretKey);
9090

9191
#[uniffi::export]
9292
impl DescriptorSecretKey {
93-
/// Construct a secret descriptor using a mnemonic.
93+
/// Construct a secret descriptor key using a mnemonic.
9494
#[uniffi::constructor]
9595
pub fn new(network: Network, mnemonic: &Mnemonic, password: Option<String>) -> Self {
9696
let mnemonic = mnemonic.0.clone();
9797
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
9898
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
9999
origin: None,
100100
xkey: xkey.into_xprv(network).unwrap(),
101-
derivation_path: BdkDerivationPath::master(),
102-
wildcard: Wildcard::Unhardened,
101+
derivation_path: BdkDerivationPath::default(),
102+
wildcard: Wildcard::None,
103103
});
104104
Self(descriptor_secret_key)
105105
}
@@ -160,6 +160,24 @@ impl DescriptorSecretKey {
160160
}
161161
}
162162

163+
pub fn add_wildcard(&self) -> Result<Arc<Self>, DescriptorKeyError> {
164+
let descriptor_secret_key = &self.0;
165+
match descriptor_secret_key {
166+
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
167+
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
168+
let descriptor_secret_key_with_wildcard =
169+
BdkDescriptorSecretKey::XPrv(DescriptorXKey {
170+
origin: descriptor_x_key.origin.clone(),
171+
xkey: descriptor_x_key.xkey,
172+
derivation_path: descriptor_x_key.derivation_path.clone(),
173+
wildcard: Wildcard::Unhardened,
174+
});
175+
Ok(Arc::new(Self(descriptor_secret_key_with_wildcard)))
176+
}
177+
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
178+
}
179+
}
180+
163181
/// Return the descriptor public key corresponding to this secret.
164182
pub fn as_public(&self) -> Arc<DescriptorPublicKey> {
165183
let secp = Secp256k1::new();
@@ -199,6 +217,21 @@ pub struct DescriptorPublicKey(pub(crate) BdkDescriptorPublicKey);
199217

200218
#[uniffi::export]
201219
impl DescriptorPublicKey {
220+
/// Construct a public descriptor key using a mnemonic.
221+
#[uniffi::constructor]
222+
pub fn new(network: Network, mnemonic: &Mnemonic, password: Option<String>) -> Self {
223+
let secp = Secp256k1::new();
224+
let mnemonic = mnemonic.0.clone();
225+
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
226+
let descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
227+
origin: None,
228+
xkey: xkey.into_xpub(network, &secp),
229+
derivation_path: BdkDerivationPath::default(),
230+
wildcard: Wildcard::None,
231+
});
232+
Self(descriptor_public_key)
233+
}
234+
202235
/// Attempt to parse a string as a descriptor public key.
203236
#[uniffi::constructor]
204237
pub fn from_string(public_key: String) -> Result<Self, DescriptorKeyError> {
@@ -256,6 +289,24 @@ impl DescriptorPublicKey {
256289
}
257290
}
258291

292+
pub fn add_wildcard(&self) -> Result<Arc<Self>, DescriptorKeyError> {
293+
let descriptor_public_key = &self.0;
294+
match descriptor_public_key {
295+
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
296+
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
297+
let descriptor_public_key_with_wildcard =
298+
BdkDescriptorPublicKey::XPub(DescriptorXKey {
299+
origin: descriptor_x_key.origin.clone(),
300+
xkey: descriptor_x_key.xkey,
301+
derivation_path: descriptor_x_key.derivation_path.clone(),
302+
wildcard: Wildcard::Unhardened,
303+
});
304+
Ok(Arc::new(Self(descriptor_public_key_with_wildcard)))
305+
}
306+
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
307+
}
308+
}
309+
259310
/// Whether or not this key has multiple derivation paths.
260311
pub fn is_multipath(&self) -> bool {
261312
self.0.is_multipath()
@@ -320,38 +371,38 @@ mod test {
320371
#[test]
321372
fn test_generate_descriptor_secret_key() {
322373
let master_dsk = get_inner();
323-
assert_eq!(master_dsk.to_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
324-
assert_eq!(master_dsk.as_public().to_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
374+
assert_eq!(master_dsk.to_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h");
375+
assert_eq!(master_dsk.as_public().to_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa");
325376
}
326377

327378
#[test]
328379
fn test_derive_self() {
329380
let master_dsk = get_inner();
330381
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m").unwrap();
331-
assert_eq!(derived_dsk.to_string(), "[d1d04177]tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
382+
assert_eq!(derived_dsk.to_string(), "[d1d04177]tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h");
332383
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
333384
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m").unwrap();
334-
assert_eq!(derived_dpk.to_string(), "[d1d04177]tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
385+
assert_eq!(derived_dpk.to_string(), "[d1d04177]tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa");
335386
}
336387

337388
#[test]
338389
fn test_derive_descriptors_keys() {
339390
let master_dsk = get_inner();
340391
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
341-
assert_eq!(derived_dsk.to_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
392+
assert_eq!(derived_dsk.to_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ");
342393
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
343394
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m/0").unwrap();
344-
assert_eq!(derived_dpk.to_string(), "[d1d04177/0]tpubD9oaCiP1MPmQdndm7DCD3D3QU34pWd6BbKSRedoZF1UJcNhEk3PJwkALNYkhxeTKL29oGNR7psqvT1KZydCGqUDEKXN6dVQJY2R8ooLPy8m/*");
395+
assert_eq!(derived_dpk.to_string(), "[d1d04177/0]tpubD9oaCiP1MPmQdndm7DCD3D3QU34pWd6BbKSRedoZF1UJcNhEk3PJwkALNYkhxeTKL29oGNR7psqvT1KZydCGqUDEKXN6dVQJY2R8ooLPy8m");
345396
}
346397

347398
#[test]
348399
fn test_extend_descriptor_keys() {
349400
let master_dsk = get_inner();
350401
let extended_dsk: &DescriptorSecretKey = &extend_dsk(&master_dsk, "m/0").unwrap();
351-
assert_eq!(extended_dsk.to_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/0/*");
402+
assert_eq!(extended_dsk.to_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/0");
352403
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
353404
let extended_dpk: &DescriptorPublicKey = &extend_dpk(master_dpk, "m/0").unwrap();
354-
assert_eq!(extended_dpk.to_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*");
405+
assert_eq!(extended_dpk.to_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0");
355406
let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
356407
let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap();
357408
let result = extended_key.derive(&DerivationPath::new("m/0".to_string()).unwrap());
@@ -360,13 +411,13 @@ mod test {
360411
}
361412

362413
#[test]
363-
fn test_from_str_inner() {
414+
fn test_from_str() {
364415
let key1 = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
365416
let key2 = "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/1/1/*";
366417
let private_descriptor_key1 = DescriptorSecretKey::from_string(key1.to_string()).unwrap();
367418
let private_descriptor_key2 = DescriptorSecretKey::from_string(key2.to_string()).unwrap();
368-
dbg!(private_descriptor_key1);
369-
dbg!(private_descriptor_key2);
419+
dbg!(private_descriptor_key1.to_string());
420+
dbg!(private_descriptor_key2.to_string());
370421
// Should error out because you can't produce a DescriptorSecretKey from an xpub
371422
let key0 = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi";
372423
assert!(DescriptorSecretKey::from_string(key0.to_string()).is_err());
@@ -377,10 +428,10 @@ mod test {
377428
let master_dsk = get_inner();
378429
// derive DescriptorSecretKey with path "m/0" from master
379430
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
380-
assert_eq!(derived_dsk.to_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
431+
assert_eq!(derived_dsk.to_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ");
381432
// extend derived_dsk with path "m/0"
382433
let extended_dsk: &DescriptorSecretKey = &extend_dsk(derived_dsk, "m/0").unwrap();
383-
assert_eq!(extended_dsk.to_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/0/*");
434+
assert_eq!(extended_dsk.to_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/0");
384435
}
385436

386437
#[test]
@@ -390,16 +441,25 @@ mod test {
390441
assert!(derived_dpk.is_err());
391442
}
392443

393-
// TODO 7: It appears that the to_hex() method is not available anymore.
394-
// Look into the correct way to pull the hex out of the DescriptorSecretKey.
395-
// Note: ToHex was removed in bitcoin_hashes 0.12.0
396-
// #[test]
397-
// fn test_retrieve_master_secret_key() {
398-
// let master_dpk = get_inner();
399-
// let master_private_key = master_dpk.secret_bytes().to_hex();
400-
// assert_eq!(
401-
// master_private_key,
402-
// "e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936"
403-
// )
404-
// }
444+
#[test]
445+
fn test_add_wildcard() {
446+
let mnemonic = Mnemonic::from_string("awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome".to_string()).unwrap();
447+
let key = DescriptorSecretKey::new(Network::Testnet, &mnemonic, None);
448+
println!("{:?}", key.to_string());
449+
450+
let derivation0 = "84/2h/1".to_string();
451+
let derivation_path = DerivationPath::new(derivation0).unwrap();
452+
453+
let extended_key = key.extend(&derivation_path).unwrap();
454+
assert_eq!(extended_key.to_string(), "tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/84/2'/1");
455+
456+
let extended_key_with_wildcard = extended_key.add_wildcard().unwrap();
457+
assert_eq!(extended_key_with_wildcard.to_string(), "tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/84/2'/1/*");
458+
459+
// You can call add_wildcard as many times as you want, it doesn't do anything after the first call
460+
let extended_key_with_wildcard_again = extended_key_with_wildcard.add_wildcard().unwrap();
461+
let extended_key_with_wildcard_again_again =
462+
extended_key_with_wildcard_again.add_wildcard().unwrap();
463+
assert_eq!(extended_key_with_wildcard_again_again.to_string(), "tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/84/2'/1/*");
464+
}
405465
}

0 commit comments

Comments
 (0)