Skip to content

Commit f0a9c1e

Browse files
committed
allow hardened wildcards in descriptors
1 parent 0d32407 commit f0a9c1e

File tree

3 files changed

+80
-76
lines changed

3 files changed

+80
-76
lines changed

examples/xpub_descriptors.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,14 @@ extern crate miniscript;
1919
use miniscript::bitcoin::{self, secp256k1};
2020
use miniscript::{Descriptor, DescriptorPublicKey, DescriptorPublicKeyCtx, DescriptorTrait};
2121

22-
use bitcoin::util::bip32;
23-
2422
use std::str::FromStr;
2523
fn main() {
2624
// For deriving from descriptors, we need to provide a secp context
2725
let secp_ctx = secp256k1::Secp256k1::verification_only();
2826
// Child number to derive public key
29-
// This is used only when xpub is wildcard
30-
let child_number = bip32::ChildNumber::from_normal_idx(0).unwrap();
31-
let desc_ctx = DescriptorPublicKeyCtx::new(&secp_ctx, child_number);
27+
// This is used only when xpub is ranged
28+
let index = 0;
29+
let desc_ctx = DescriptorPublicKeyCtx::new(&secp_ctx, index);
3230
// P2WSH and single xpubs
3331
let addr_one = Descriptor::<DescriptorPublicKey>::from_str(
3432
"wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))",
@@ -53,14 +51,14 @@ fn main() {
5351
"sh(wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*)))",
5452
)
5553
.unwrap()
56-
.derive(bitcoin::util::bip32::ChildNumber::from_normal_idx(5).unwrap())
54+
.derive(5)
5755
.address(desc_ctx, bitcoin::Network::Bitcoin).unwrap();
5856

5957
let addr_two = Descriptor::<DescriptorPublicKey>::from_str(
6058
"sh(wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*)))",
6159
)
6260
.unwrap()
63-
.derive(bitcoin::util::bip32::ChildNumber::from_normal_idx(5).unwrap())
61+
.derive(5)
6462
.address(desc_ctx, bitcoin::Network::Bitcoin).unwrap();
6563
let expected = bitcoin::Address::from_str("325zcVBN5o2eqqqtGwPjmtDd8dJRyYP82s").unwrap();
6664
assert_eq!(addr_one, expected);

src/descriptor/key.rs

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ impl fmt::Display for DescriptorSecretKey {
6161
maybe_fmt_master_id(f, &xprv.origin)?;
6262
xprv.xkey.fmt(f)?;
6363
fmt_derivation_path(f, &xprv.derivation_path)?;
64-
if xprv.is_wildcard {
65-
write!(f, "/*")?;
64+
match xprv.is_wildcard {
65+
Wildcard::None => {}
66+
Wildcard::Unhardened => write!(f, "/*")?,
67+
Wildcard::Hardened => write!(f, "/*h")?,
6668
}
6769
Ok(())
6870
}
@@ -102,6 +104,17 @@ impl InnerXKey for bip32::ExtendedPrivKey {
102104
}
103105
}
104106

107+
/// Whether a descriptor has a wildcard in it
108+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
109+
pub enum Wildcard {
110+
/// No wildcard
111+
None,
112+
/// Unhardened wildcard, e.g. *
113+
Unhardened,
114+
/// Unhardened wildcard, e.g. *h
115+
Hardened,
116+
}
117+
105118
/// Instance of an extended key with origin and derivation path
106119
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
107120
pub struct DescriptorXKey<K: InnerXKey> {
@@ -112,7 +125,7 @@ pub struct DescriptorXKey<K: InnerXKey> {
112125
/// The derivation path
113126
pub derivation_path: bip32::DerivationPath,
114127
/// Whether the descriptor is wildcard
115-
pub is_wildcard: bool,
128+
pub is_wildcard: Wildcard,
116129
}
117130

118131
impl DescriptorSinglePriv {
@@ -206,8 +219,10 @@ impl fmt::Display for DescriptorPublicKey {
206219
maybe_fmt_master_id(f, &xpub.origin)?;
207220
xpub.xkey.fmt(f)?;
208221
fmt_derivation_path(f, &xpub.derivation_path)?;
209-
if xpub.is_wildcard {
210-
write!(f, "/*")?;
222+
match xpub.is_wildcard {
223+
Wildcard::None => {}
224+
Wildcard::Unhardened => write!(f, "/*")?,
225+
Wildcard::Hardened => write!(f, "/*h")?,
211226
}
212227
Ok(())
213228
}
@@ -307,25 +322,26 @@ impl FromStr for DescriptorPublicKey {
307322
impl DescriptorPublicKey {
308323
/// Derives the specified child key if self is a wildcard xpub. Otherwise returns self.
309324
///
310-
/// Panics if given a hardened child number
311-
pub fn derive(self, child_number: bip32::ChildNumber) -> DescriptorPublicKey {
312-
debug_assert!(child_number.is_normal());
313-
314-
match self {
315-
DescriptorPublicKey::SinglePub(_) => self,
316-
DescriptorPublicKey::XPub(xpub) => {
317-
if xpub.is_wildcard {
318-
DescriptorPublicKey::XPub(DescriptorXKey {
319-
origin: xpub.origin,
320-
xkey: xpub.xkey,
321-
derivation_path: xpub.derivation_path.into_child(child_number),
322-
is_wildcard: false,
323-
})
324-
} else {
325-
DescriptorPublicKey::XPub(xpub)
325+
/// Panics if given a child number ≥ 2^31
326+
pub fn derive(mut self, index: u32) -> DescriptorPublicKey {
327+
if let DescriptorPublicKey::XPub(mut xpub) = self {
328+
match xpub.is_wildcard {
329+
Wildcard::None => {}
330+
Wildcard::Unhardened => {
331+
xpub.derivation_path = xpub
332+
.derivation_path
333+
.into_child(bip32::ChildNumber::from_normal_idx(index).unwrap())
334+
}
335+
Wildcard::Hardened => {
336+
xpub.derivation_path = xpub
337+
.derivation_path
338+
.into_child(bip32::ChildNumber::from_hardened_idx(index).unwrap())
326339
}
327340
}
341+
xpub.is_wildcard = Wildcard::None;
342+
self = DescriptorPublicKey::XPub(xpub);
328343
}
344+
self
329345
}
330346
}
331347

@@ -417,25 +433,24 @@ impl<K: InnerXKey> DescriptorXKey<K> {
417433
/// Parse an extended key concatenated to a derivation path.
418434
fn parse_xkey_deriv(
419435
key_deriv: &str,
420-
) -> Result<(K, bip32::DerivationPath, bool), DescriptorKeyParseError> {
436+
) -> Result<(K, bip32::DerivationPath, Wildcard), DescriptorKeyParseError> {
421437
let mut key_deriv = key_deriv.split('/');
422438
let xkey_str = key_deriv.next().ok_or(DescriptorKeyParseError(
423439
"No key found after origin description",
424440
))?;
425441
let xkey = K::from_str(xkey_str)
426442
.map_err(|_| DescriptorKeyParseError("Error while parsing xkey."))?;
427443

428-
let mut is_wildcard = false;
444+
let mut is_wildcard = Wildcard::None;
429445
let derivation_path = key_deriv
430446
.filter_map(|p| {
431-
if !is_wildcard && p == "*" {
432-
is_wildcard = true;
447+
if is_wildcard == Wildcard::None && p == "*" {
448+
is_wildcard = Wildcard::Unhardened;
433449
None
434-
} else if !is_wildcard && p == "*'" {
435-
Some(Err(DescriptorKeyParseError(
436-
"Hardened derivation is currently not supported.",
437-
)))
438-
} else if is_wildcard {
450+
} else if is_wildcard == Wildcard::None && (p == "*'" || p == "*h") {
451+
is_wildcard = Wildcard::Hardened;
452+
None
453+
} else if is_wildcard != Wildcard::None {
439454
Some(Err(DescriptorKeyParseError(
440455
"'*' may only appear as last element in a derivation path.",
441456
)))
@@ -507,14 +522,15 @@ impl<K: InnerXKey> DescriptorXKey<K> {
507522
),
508523
};
509524

510-
let path_excluding_wildcard = if self.is_wildcard && path.as_ref().len() > 0 {
511-
path.into_iter()
512-
.take(path.as_ref().len() - 1)
513-
.cloned()
514-
.collect()
515-
} else {
516-
path.clone()
517-
};
525+
let path_excluding_wildcard =
526+
if self.is_wildcard != Wildcard::None && path.as_ref().len() > 0 {
527+
path.into_iter()
528+
.take(path.as_ref().len() - 1)
529+
.cloned()
530+
.collect()
531+
} else {
532+
path.clone()
533+
};
518534

519535
if &compare_fingerprint == fingerprint
520536
&& compare_path
@@ -542,16 +558,16 @@ impl MiniscriptKey for DescriptorPublicKey {
542558
pub struct DescriptorPublicKeyCtx<'secp, C: 'secp + secp256k1::Verification> {
543559
/// The underlying secp context
544560
secp_ctx: &'secp secp256k1::Secp256k1<C>,
545-
/// The child_number in case the descriptor is wildcard
546-
/// If the DescriptorPublicKey is not wildcard this field is not used.
547-
child_number: bip32::ChildNumber,
561+
/// The index in case the descriptor is ranged
562+
/// If the DescriptorPublicKey is unranged this field is not used.
563+
index: u32,
548564
}
549565

550566
impl<'secp, C: secp256k1::Verification> Clone for DescriptorPublicKeyCtx<'secp, C> {
551567
fn clone(&self) -> Self {
552568
Self {
553569
secp_ctx: &self.secp_ctx,
554-
child_number: self.child_number.clone(),
570+
index: self.index,
555571
}
556572
}
557573
}
@@ -560,10 +576,10 @@ impl<'secp, C: secp256k1::Verification> Copy for DescriptorPublicKeyCtx<'secp, C
560576

561577
impl<'secp, C: secp256k1::Verification> DescriptorPublicKeyCtx<'secp, C> {
562578
/// Create a new context
563-
pub fn new(secp_ctx: &'secp secp256k1::Secp256k1<C>, child_number: bip32::ChildNumber) -> Self {
579+
pub fn new(secp_ctx: &'secp secp256k1::Secp256k1<C>, index: u32) -> Self {
564580
Self {
565581
secp_ctx: secp_ctx,
566-
child_number: child_number,
582+
index: index,
567583
}
568584
}
569585
}
@@ -572,12 +588,12 @@ impl<'secp, C: secp256k1::Verification> ToPublicKey<DescriptorPublicKeyCtx<'secp
572588
for DescriptorPublicKey
573589
{
574590
fn to_public_key(&self, to_pk_ctx: DescriptorPublicKeyCtx<'secp, C>) -> bitcoin::PublicKey {
575-
let xpub = self.clone().derive(to_pk_ctx.child_number);
591+
let xpub = self.clone().derive(to_pk_ctx.index);
576592
match xpub {
577593
DescriptorPublicKey::SinglePub(ref spub) => spub.key.to_public_key(NullCtx),
578594
DescriptorPublicKey::XPub(ref xpub) => {
579595
// derives if wildcard, otherwise returns self
580-
debug_assert!(!xpub.is_wildcard);
596+
debug_assert!(xpub.is_wildcard == Wildcard::None);
581597
xpub.xkey
582598
.derive_pub(to_pk_ctx.secp_ctx, &xpub.derivation_path)
583599
.expect("Shouldn't fail, only normal derivations")
@@ -613,15 +629,6 @@ mod test {
613629
))
614630
);
615631

616-
// And even if they they claim it for the wildcard!
617-
let desc = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/42/*'";
618-
assert_eq!(
619-
DescriptorPublicKey::from_str(desc),
620-
Err(DescriptorKeyParseError(
621-
"Hardened derivation is currently not supported."
622-
))
623-
);
624-
625632
// And ones with misplaced wildcard
626633
let desc = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*/44";
627634
assert_eq!(

src/descriptor/mod.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ use std::{
3131

3232
use bitcoin::hashes::hash160;
3333
use bitcoin::secp256k1;
34-
use bitcoin::util::bip32;
3534
use bitcoin::{self, Script};
3635

3736
use self::checksum::verify_checksum;
@@ -661,9 +660,9 @@ where
661660
}
662661

663662
impl Descriptor<DescriptorPublicKey> {
664-
/// Derives all wildcard keys in the descriptor using the supplied `child_number`
665-
pub fn derive(&self, child_number: bip32::ChildNumber) -> Descriptor<DescriptorPublicKey> {
666-
self.translate_pk2_infallible(|pk| pk.clone().derive(child_number))
663+
/// Derives all wildcard keys in the descriptor using the supplied index
664+
pub fn derive(&self, index: u32) -> Descriptor<DescriptorPublicKey> {
665+
self.translate_pk2_infallible(|pk| pk.clone().derive(index))
667666
}
668667

669668
/// Parse a descriptor that may contain secret keys
@@ -795,6 +794,7 @@ mod tests {
795794
use bitcoin::hashes::{hash160, sha256};
796795
use bitcoin::util::bip32;
797796
use bitcoin::{self, secp256k1, PublicKey};
797+
use descriptor::key::Wildcard;
798798
use descriptor::{
799799
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePub, DescriptorXKey,
800800
};
@@ -1398,7 +1398,7 @@ mod tests {
13981398
)),
13991399
xkey: bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap(),
14001400
derivation_path: (&[bip32::ChildNumber::from_normal_idx(1).unwrap()][..]).into(),
1401-
is_wildcard: true,
1401+
is_wildcard: Wildcard::Unhardened,
14021402
});
14031403
assert_eq!(expected, key.parse().unwrap());
14041404
assert_eq!(format!("{}", expected), key);
@@ -1409,7 +1409,7 @@ mod tests {
14091409
origin: None,
14101410
xkey: bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap(),
14111411
derivation_path: (&[bip32::ChildNumber::from_normal_idx(1).unwrap()][..]).into(),
1412-
is_wildcard: false,
1412+
is_wildcard: Wildcard::None,
14131413
});
14141414
assert_eq!(expected, key.parse().unwrap());
14151415
assert_eq!(format!("{}", expected), key);
@@ -1420,7 +1420,7 @@ mod tests {
14201420
origin: None,
14211421
xkey: bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4YqYr3amYH15zjxHvBkUUeadieW8AxTZC7aY2L8aPSk3tpW6yW1QnWzXAB7zoiaNMfwXPPz9S68ZCV4yWvkVXjdeksLskCed").unwrap(),
14221422
derivation_path: (&[bip32::ChildNumber::from_normal_idx(1).unwrap()][..]).into(),
1423-
is_wildcard: false,
1423+
is_wildcard: Wildcard::None,
14241424
});
14251425
assert_eq!(expected, key.parse().unwrap());
14261426
assert_eq!(format!("{}", expected), key);
@@ -1431,7 +1431,7 @@ mod tests {
14311431
origin: None,
14321432
xkey: bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap(),
14331433
derivation_path: bip32::DerivationPath::from(&[][..]),
1434-
is_wildcard: false,
1434+
is_wildcard: Wildcard::None,
14351435
});
14361436
assert_eq!(expected, key.parse().unwrap());
14371437
assert_eq!(format!("{}", expected), key);
@@ -1486,8 +1486,8 @@ mod tests {
14861486
fn test_sortedmulti() {
14871487
fn _test_sortedmulti(raw_desc_one: &str, raw_desc_two: &str, raw_addr_expected: &str) {
14881488
let secp_ctx = secp256k1::Secp256k1::verification_only();
1489-
let child_number = bip32::ChildNumber::from_normal_idx(5).unwrap();
1490-
let desc_ctx = DescriptorPublicKeyCtx::new(&secp_ctx, child_number);
1489+
let index = 5;
1490+
let desc_ctx = DescriptorPublicKeyCtx::new(&secp_ctx, index);
14911491

14921492
// Parse descriptor
14931493
let mut desc_one = Descriptor::<DescriptorPublicKey>::from_str(raw_desc_one).unwrap();
@@ -1499,8 +1499,8 @@ mod tests {
14991499

15001500
// Derive a child if the descriptor is ranged
15011501
if raw_desc_one.contains("*") && raw_desc_two.contains("*") {
1502-
desc_one = desc_one.derive(child_number);
1503-
desc_two = desc_two.derive(child_number);
1502+
desc_one = desc_one.derive(index);
1503+
desc_two = desc_two.derive(index);
15041504
}
15051505

15061506
// Same address
@@ -1582,8 +1582,7 @@ pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHW
15821582
pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
15831583
let policy: policy::concrete::Policy<DescriptorPublicKey> = descriptor_str.parse().unwrap();
15841584
let descriptor = Descriptor::new_sh(policy.compile().unwrap()).unwrap();
1585-
let derived_descriptor =
1586-
descriptor.derive(bip32::ChildNumber::from_normal_idx(42).unwrap());
1585+
let derived_descriptor = descriptor.derive(42);
15871586

15881587
let res_descriptor_str = "thresh(2,\
15891588
pk([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/42),\

0 commit comments

Comments
 (0)