Skip to content

Commit cc80a01

Browse files
author
Scott Robinson
committed
Extract specialised DescriptorPublicKey functionality into DescriptorExtendedPublicKey
1 parent 0fab60b commit cc80a01

File tree

1 file changed

+151
-77
lines changed

1 file changed

+151
-77
lines changed

src/descriptor/key.rs

+151-77
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ use crate::prelude::*;
1818
use crate::serde::{Deserialize, Deserializer, Serialize, Serializer};
1919
use crate::{hash256, MiniscriptKey, ToPublicKey};
2020

21+
type DescriptorExtendedPublicKey = DescriptorXKey<bip32::ExtendedPubKey>;
22+
2123
/// The descriptor pubkey, either a single pubkey or an xpub.
2224
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
2325
pub enum DescriptorPublicKey {
2426
/// Single public key.
2527
Single(SinglePub),
2628
/// Extended public key (xpub).
27-
XPub(DescriptorXKey<bip32::ExtendedPubKey>),
29+
XPub(DescriptorExtendedPublicKey),
2830
/// Multiple extended public keys.
2931
MultiXPub(DescriptorMultiXKey<bip32::ExtendedPubKey>),
3032
}
@@ -283,6 +285,20 @@ impl error::Error for DescriptorKeyParseError {
283285
}
284286
}
285287

288+
impl fmt::Display for DescriptorExtendedPublicKey {
289+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290+
maybe_fmt_master_id(f, &self.origin)?;
291+
self.xkey.fmt(f)?;
292+
fmt_derivation_path(f, &self.derivation_path)?;
293+
match self.wildcard {
294+
Wildcard::None => {}
295+
Wildcard::Unhardened => write!(f, "/*")?,
296+
Wildcard::Hardened => write!(f, "/*h")?,
297+
}
298+
Ok(())
299+
}
300+
}
301+
286302
impl fmt::Display for DescriptorPublicKey {
287303
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
288304
match *self {
@@ -294,17 +310,7 @@ impl fmt::Display for DescriptorPublicKey {
294310
}?;
295311
Ok(())
296312
}
297-
DescriptorPublicKey::XPub(ref xpub) => {
298-
maybe_fmt_master_id(f, &xpub.origin)?;
299-
xpub.xkey.fmt(f)?;
300-
fmt_derivation_path(f, &xpub.derivation_path)?;
301-
match xpub.wildcard {
302-
Wildcard::None => {}
303-
Wildcard::Unhardened => write!(f, "/*")?,
304-
Wildcard::Hardened => write!(f, "/*h")?,
305-
}
306-
Ok(())
307-
}
313+
DescriptorPublicKey::XPub(ref xpub) => xpub.fmt(f),
308314
DescriptorPublicKey::MultiXPub(ref xpub) => {
309315
maybe_fmt_master_id(f, &xpub.origin)?;
310316
xpub.xkey.fmt(f)?;
@@ -432,6 +438,30 @@ fn fmt_derivation_paths(f: &mut fmt::Formatter, paths: &[bip32::DerivationPath])
432438
Ok(())
433439
}
434440

441+
impl FromStr for DescriptorExtendedPublicKey {
442+
type Err = DescriptorKeyParseError;
443+
444+
fn from_str(s: &str) -> Result<Self, Self::Err> {
445+
let (key_part, origin) = parse_key_origin(s)?;
446+
447+
let (xpub, derivation_paths, wildcard) =
448+
parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?;
449+
450+
if derivation_paths.len() > 1 {
451+
return Err(DescriptorKeyParseError(
452+
"Multiple derivation paths are not allowed for single extended keys",
453+
));
454+
}
455+
456+
Ok(Self {
457+
origin,
458+
xkey: xpub,
459+
derivation_path: derivation_paths.into_iter().next().unwrap_or_default(),
460+
wildcard,
461+
})
462+
}
463+
}
464+
435465
impl FromStr for DescriptorPublicKey {
436466
type Err = DescriptorKeyParseError;
437467

@@ -456,12 +486,7 @@ impl FromStr for DescriptorPublicKey {
456486
wildcard,
457487
}))
458488
} else {
459-
Ok(DescriptorPublicKey::XPub(DescriptorXKey {
460-
origin,
461-
xkey: xpub,
462-
derivation_path: derivation_paths.into_iter().next().unwrap_or_default(),
463-
wildcard,
464-
}))
489+
DescriptorExtendedPublicKey::from_str(s).map(Self::XPub)
465490
}
466491
} else {
467492
let key = match key_part.len() {
@@ -503,13 +528,16 @@ pub enum ConversionError {
503528
HardenedChild,
504529
/// Attempted to convert a key with multiple derivation paths to a bitcoin public key
505530
MultiKey,
531+
/// Attempted to convert a key with a wildcard to a bitcoin public key
532+
Wildcard,
506533
}
507534

508535
impl fmt::Display for ConversionError {
509536
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
510537
f.write_str(match *self {
511538
ConversionError::HardenedChild => "hardened child step in bip32 path",
512539
ConversionError::MultiKey => "multiple existing keys",
540+
ConversionError::Wildcard => "wildcard in bip32 path",
513541
})
514542
}
515543
}
@@ -520,7 +548,7 @@ impl error::Error for ConversionError {
520548
use self::ConversionError::*;
521549

522550
match self {
523-
HardenedChild | MultiKey => None,
551+
HardenedChild | MultiKey | Wildcard => None,
524552
}
525553
}
526554
}
@@ -572,17 +600,102 @@ pub trait DescriptorKey : Clone + fmt::Debug + fmt::Display + Eq + FromStr + std
572600
secp: &Secp256k1<C>,
573601
) -> Result<bitcoin::PublicKey, ConversionError>;
574602
}
603+
604+
impl DescriptorKey for DescriptorExtendedPublicKey {
605+
/// The fingerprint of the master key associated with this key, `0x00000000` if none.
606+
fn master_fingerprint(&self) -> bip32::Fingerprint {
607+
if let Some((fingerprint, _)) = self.origin {
608+
fingerprint
609+
} else {
610+
self.xkey.fingerprint()
611+
}
612+
}
613+
614+
/// Full path, from the master key
615+
///
616+
/// For wildcard keys this will return the path up to the wildcard, so you
617+
/// can get full paths by appending one additional derivation step, according
618+
/// to the wildcard type (hardened or normal).
619+
///
620+
/// For multipath extended keys, this returns `None`.
621+
fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
622+
let origin_path = if let Some((_, ref path)) = self.origin {
623+
path.clone()
624+
} else {
625+
bip32::DerivationPath::from(vec![])
626+
};
627+
Some(origin_path.extend(&self.derivation_path))
628+
}
629+
630+
/// Whether or not the key has a wildcard
631+
fn has_wildcard(&self) -> bool {
632+
self.wildcard != Wildcard::None
633+
}
634+
635+
/// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a
636+
/// *definite* key (i.e. one where all the derivation paths are set).
637+
///
638+
/// # Returns
639+
///
640+
/// - If this key is not an xpub, returns `self`.
641+
/// - If this key is an xpub but does not have a wildcard, returns `self`.
642+
/// - Otherwise, returns the xpub at derivation `index` (removing the wildcard).
643+
///
644+
/// # Errors
645+
///
646+
/// - If `index` is hardened.
647+
fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
648+
let derivation_path = match self.wildcard {
649+
Wildcard::None => self.derivation_path,
650+
Wildcard::Unhardened => self.derivation_path.into_child(
651+
bip32::ChildNumber::from_normal_idx(index)
652+
.ok()
653+
.ok_or(ConversionError::HardenedChild)?,
654+
),
655+
Wildcard::Hardened => self.derivation_path.into_child(
656+
bip32::ChildNumber::from_hardened_idx(index)
657+
.ok()
658+
.ok_or(ConversionError::HardenedChild)?,
659+
),
660+
};
661+
let definite = DescriptorPublicKey::XPub(DescriptorXKey {
662+
origin: self.origin,
663+
xkey: self.xkey,
664+
derivation_path,
665+
wildcard: Wildcard::None,
666+
});
667+
668+
Ok(DefiniteDescriptorKey::new(definite)
669+
.expect("The key should not contain any wildcards at this point"))
670+
}
671+
672+
/// Whether or not this key has multiple derivation paths.
673+
fn is_multipath(&self) -> bool {
674+
false
675+
}
676+
677+
fn derive_public_key<C: Verification>(
678+
&self,
679+
secp: &Secp256k1<C>,
680+
) -> Result<bitcoin::PublicKey, ConversionError> {
681+
match self.wildcard {
682+
Wildcard::Unhardened | Wildcard::Hardened => Err(ConversionError::Wildcard),
683+
Wildcard::None => match self.xkey.derive_pub(secp, &self.derivation_path.as_ref()) {
684+
Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)),
685+
Err(bip32::Error::CannotDeriveFromHardenedKey) => {
686+
Err(ConversionError::HardenedChild)
687+
}
688+
Err(e) => unreachable!("cryptographically unreachable: {}", e),
689+
},
690+
}
691+
}
692+
}
693+
575694
impl DescriptorPublicKey {
576695
/// The fingerprint of the master key associated with this key, `0x00000000` if none.
577696
pub fn master_fingerprint(&self) -> bip32::Fingerprint {
578697
match *self {
579-
DescriptorPublicKey::XPub(ref xpub) => {
580-
if let Some((fingerprint, _)) = xpub.origin {
581-
fingerprint
582-
} else {
583-
xpub.xkey.fingerprint()
584-
}
585-
}
698+
DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(),
586699
DescriptorPublicKey::MultiXPub(ref xpub) => {
587700
if let Some((fingerprint, _)) = xpub.origin {
588701
fingerprint
@@ -620,14 +733,7 @@ impl DescriptorPublicKey {
620733
/// For multipath extended keys, this returns `None`.
621734
pub fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
622735
match *self {
623-
DescriptorPublicKey::XPub(ref xpub) => {
624-
let origin_path = if let Some((_, ref path)) = xpub.origin {
625-
path.clone()
626-
} else {
627-
bip32::DerivationPath::from(vec![])
628-
};
629-
Some(origin_path.extend(&xpub.derivation_path))
630-
}
736+
DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(),
631737
DescriptorPublicKey::Single(ref single) => {
632738
Some(if let Some((_, ref path)) = single.origin {
633739
path.clone()
@@ -649,7 +755,7 @@ impl DescriptorPublicKey {
649755
pub fn has_wildcard(&self) -> bool {
650756
match *self {
651757
DescriptorPublicKey::Single(..) => false,
652-
DescriptorPublicKey::XPub(ref xpub) => xpub.wildcard != Wildcard::None,
758+
DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(),
653759
DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None,
654760
}
655761
}
@@ -673,40 +779,19 @@ impl DescriptorPublicKey {
673779
///
674780
/// - If `index` is hardened.
675781
pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
676-
let definite = match self {
677-
DescriptorPublicKey::Single(_) => self,
678-
DescriptorPublicKey::XPub(xpub) => {
679-
let derivation_path = match xpub.wildcard {
680-
Wildcard::None => xpub.derivation_path,
681-
Wildcard::Unhardened => xpub.derivation_path.into_child(
682-
bip32::ChildNumber::from_normal_idx(index)
683-
.ok()
684-
.ok_or(ConversionError::HardenedChild)?,
685-
),
686-
Wildcard::Hardened => xpub.derivation_path.into_child(
687-
bip32::ChildNumber::from_hardened_idx(index)
688-
.ok()
689-
.ok_or(ConversionError::HardenedChild)?,
690-
),
691-
};
692-
DescriptorPublicKey::XPub(DescriptorXKey {
693-
origin: xpub.origin,
694-
xkey: xpub.xkey,
695-
derivation_path,
696-
wildcard: Wildcard::None,
697-
})
698-
}
699-
DescriptorPublicKey::MultiXPub(_) => return Err(ConversionError::MultiKey),
700-
};
701-
702-
Ok(DefiniteDescriptorKey::new(definite)
703-
.expect("The key should not contain any wildcards at this point"))
782+
match self {
783+
DescriptorPublicKey::Single(_) => Ok(DefiniteDescriptorKey::new(self)
784+
.expect("The key should not contain any wildcards at this point")),
785+
DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index),
786+
DescriptorPublicKey::MultiXPub(_) => Err(ConversionError::MultiKey),
787+
}
704788
}
705789

706790
/// Whether or not this key has multiple derivation paths.
707791
pub fn is_multipath(&self) -> bool {
708-
match *self {
709-
DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => false,
792+
match self {
793+
DescriptorPublicKey::Single(..) => false,
794+
DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(),
710795
DescriptorPublicKey::MultiXPub(_) => true,
711796
}
712797
}
@@ -1083,18 +1168,7 @@ impl DefiniteDescriptorKey {
10831168
SinglePubKey::FullKey(pk) => Ok(pk),
10841169
SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()),
10851170
},
1086-
DescriptorPublicKey::XPub(ref xpk) => match xpk.wildcard {
1087-
Wildcard::Unhardened | Wildcard::Hardened => {
1088-
unreachable!("we've excluded this error case")
1089-
}
1090-
Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) {
1091-
Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)),
1092-
Err(bip32::Error::CannotDeriveFromHardenedKey) => {
1093-
Err(ConversionError::HardenedChild)
1094-
}
1095-
Err(e) => unreachable!("cryptographically unreachable: {}", e),
1096-
},
1097-
},
1171+
DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp),
10981172
DescriptorPublicKey::MultiXPub(_) => {
10991173
unreachable!("A definite key cannot contain a multipath key.")
11001174
}

0 commit comments

Comments
 (0)