Skip to content

Commit 69440a4

Browse files
committed
Merge #345: Define a new type for derived DescriptorPublicKeys
9cf065b Define a new type for derived `DescriptorPublicKey`s (Alekos Filini) Pull request description: After a brief chat on IRC and in PR #339, here's a quick draft for a new type specific to derived `DescriptorPublicKey`s. This is the same design we use in BDK, where we wrap a normal `DescriptorPublicKey` and a secp context in a new struct. This has the drawback that the struct carries a lifetime and generic for the ctx, but I couldn't think of any other way to get this to work unless we drop the implementation of `ToPublicKey` for `DerivedDescriptorKey` which in my opinion can be very useful. ACKs for top commit: apoelstra: ACK 9cf065b sanket1729: ACK 9cf065b Tree-SHA512: 3dd486d52c589d104da9611c65229988825cc79115a1ff45d1a39aa63e49d2d2a76b40b21a278a58d0b8566b2b37682ffc5efbbb78779ef4e5dadeaf14bbcc6c
2 parents 3c111a2 + 9cf065b commit 69440a4

File tree

2 files changed

+95
-18
lines changed

2 files changed

+95
-18
lines changed

src/descriptor/key.rs

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ use std::{error, fmt, str::FromStr};
22

33
use bitcoin::{
44
self,
5-
hashes::Hash,
5+
hashes::{hash160, Hash},
66
hashes::{hex::FromHex, HashEngine},
7-
secp256k1,
8-
secp256k1::{Secp256k1, Signing},
7+
secp256k1::{Secp256k1, Signing, Verification},
98
util::bip32,
109
XOnlyPublicKey, XpubIdentifier,
1110
};
@@ -70,6 +69,15 @@ pub enum SinglePubKey {
7069
XOnly(XOnlyPublicKey),
7170
}
7271

72+
/// A derived [`DescriptorPublicKey`]
73+
///
74+
/// Derived keys are guaranteed to never contain wildcards
75+
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
76+
pub struct DerivedDescriptorKey {
77+
key: DescriptorPublicKey,
78+
index: u32,
79+
}
80+
7381
impl fmt::Display for DescriptorSecretKey {
7482
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7583
match self {
@@ -434,11 +442,14 @@ impl DescriptorPublicKey {
434442
/// - If this key is an xpub but does not have a wildcard, returns `self`.
435443
/// - Otherwise, returns the derived xpub at `index` (removing the wildcard).
436444
///
445+
/// Since it's guaranteed that extended keys won't have wildcards, the key is returned as
446+
/// [`DerivedDescriptorKey`].
447+
///
437448
/// # Panics
438449
///
439450
/// If `index` ≥ 2^31
440-
pub fn derive(self, index: u32) -> DescriptorPublicKey {
441-
match self {
451+
pub fn derive(self, index: u32) -> DerivedDescriptorKey {
452+
let derived = match self {
442453
DescriptorPublicKey::Single(_) => self,
443454
DescriptorPublicKey::XPub(xpub) => {
444455
let derivation_path = match xpub.wildcard {
@@ -457,7 +468,10 @@ impl DescriptorPublicKey {
457468
wildcard: Wildcard::None,
458469
})
459470
}
460-
}
471+
};
472+
473+
DerivedDescriptorKey::new(derived, index)
474+
.expect("The key should not contain any wildcards at this point")
461475
}
462476

463477
/// Computes the public key corresponding to this descriptor key.
@@ -472,7 +486,7 @@ impl DescriptorPublicKey {
472486
/// to avoid hardened derivation steps, start from a `DescriptorSecretKey`
473487
/// and call `to_public`, or call `TranslatePk2::translate_pk2` with
474488
/// some function which has access to secret key data.
475-
pub fn derive_public_key<C: secp256k1::Verification>(
489+
pub fn derive_public_key<C: Verification>(
476490
&self,
477491
secp: &Secp256k1<C>,
478492
) -> Result<bitcoin::PublicKey, ConversionError> {
@@ -717,6 +731,70 @@ impl MiniscriptKey for DescriptorPublicKey {
717731
}
718732
}
719733

734+
impl DerivedDescriptorKey {
735+
/// Computes the raw [`bitcoin::PublicKey`] for this descriptor key.
736+
///
737+
/// Will return an error if the key has any hardened derivation steps
738+
/// in its path, but unlike [`DescriptorPublicKey::derive_public_key`]
739+
/// this won't error in case of wildcards, because derived keys are
740+
/// guaranteed to never contain one.
741+
pub fn derive_public_key<C: Verification>(
742+
&self,
743+
secp: &Secp256k1<C>,
744+
) -> Result<bitcoin::PublicKey, ConversionError> {
745+
self.key.derive_public_key(secp)
746+
}
747+
748+
/// Return the derivation index of this key
749+
pub fn index(&self) -> u32 {
750+
self.index
751+
}
752+
753+
/// Construct an instance from a descriptor key and a derivation index
754+
///
755+
/// Returns `None` if the key contains a wildcard
756+
fn new(key: DescriptorPublicKey, index: u32) -> Option<Self> {
757+
match key {
758+
DescriptorPublicKey::XPub(ref xpk) if xpk.wildcard != Wildcard::None => None,
759+
k => Some(DerivedDescriptorKey { key: k, index }),
760+
}
761+
}
762+
}
763+
764+
impl fmt::Display for DerivedDescriptorKey {
765+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
766+
self.key.fmt(f)
767+
}
768+
}
769+
770+
impl MiniscriptKey for DerivedDescriptorKey {
771+
// This allows us to be able to derive public keys even for PkH s
772+
type Hash = Self;
773+
774+
fn is_uncompressed(&self) -> bool {
775+
self.key.is_uncompressed()
776+
}
777+
778+
fn is_x_only_key(&self) -> bool {
779+
self.key.is_x_only_key()
780+
}
781+
782+
fn to_pubkeyhash(&self) -> Self {
783+
self.clone()
784+
}
785+
}
786+
787+
impl ToPublicKey for DerivedDescriptorKey {
788+
fn to_public_key(&self) -> bitcoin::PublicKey {
789+
let secp = Secp256k1::verification_only();
790+
self.key.derive_public_key(&secp).unwrap()
791+
}
792+
793+
fn hash_to_hash160(hash: &Self) -> hash160::Hash {
794+
hash.to_public_key().to_pubkeyhash()
795+
}
796+
}
797+
720798
#[cfg(test)]
721799
mod test {
722800
use super::{DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey};

src/descriptor/mod.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ mod checksum;
6363
mod key;
6464

6565
pub use self::key::{
66-
ConversionError, DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey,
67-
DescriptorXKey, InnerXKey, SinglePriv, SinglePub, SinglePubKey, Wildcard,
66+
ConversionError, DerivedDescriptorKey, DescriptorKeyParseError, DescriptorPublicKey,
67+
DescriptorSecretKey, DescriptorXKey, InnerXKey, SinglePriv, SinglePub, SinglePubKey, Wildcard,
6868
};
6969

7070
/// Alias type for a map of public key to secret key
@@ -661,7 +661,7 @@ impl Descriptor<DescriptorPublicKey> {
661661
///
662662
/// In most cases, you would want to use [`Self::derived_descriptor`] directly to obtain
663663
/// a [`Descriptor<bitcoin::PublicKey>`]
664-
pub fn derive(&self, index: u32) -> Descriptor<DescriptorPublicKey> {
664+
pub fn derive(&self, index: u32) -> Descriptor<DerivedDescriptorKey> {
665665
self.translate_pk2_infallible(|pk| pk.clone().derive(index))
666666
}
667667

@@ -1616,18 +1616,17 @@ mod tests {
16161616
let index = 5;
16171617

16181618
// Parse descriptor
1619-
let mut desc_one = Descriptor::<DescriptorPublicKey>::from_str(raw_desc_one).unwrap();
1620-
let mut desc_two = Descriptor::<DescriptorPublicKey>::from_str(raw_desc_two).unwrap();
1619+
let desc_one = Descriptor::<DescriptorPublicKey>::from_str(raw_desc_one).unwrap();
1620+
let desc_two = Descriptor::<DescriptorPublicKey>::from_str(raw_desc_two).unwrap();
16211621

16221622
// Same string formatting
16231623
assert_eq!(desc_one.to_string(), raw_desc_one);
16241624
assert_eq!(desc_two.to_string(), raw_desc_two);
16251625

1626-
// Derive a child if the descriptor is ranged
1627-
if raw_desc_one.contains("*") && raw_desc_two.contains("*") {
1628-
desc_one = desc_one.derive(index);
1629-
desc_two = desc_two.derive(index);
1630-
}
1626+
// Derive a child in case the descriptor is ranged. If it's not this won't have any
1627+
// effect
1628+
let desc_one = desc_one.derive(index);
1629+
let desc_two = desc_two.derive(index);
16311630

16321631
// Same address
16331632
let addr_one = desc_one
@@ -1723,7 +1722,7 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
17231722
res_descriptor_str.parse().unwrap();
17241723
let res_descriptor = Descriptor::new_sh(res_policy.compile().unwrap()).unwrap();
17251724

1726-
assert_eq!(res_descriptor, derived_descriptor);
1725+
assert_eq!(res_descriptor.to_string(), derived_descriptor.to_string());
17271726
}
17281727

17291728
#[test]

0 commit comments

Comments
 (0)