Skip to content

Commit 6dc6aca

Browse files
committed
Make DerivedDescriptorKey first-class citizen
Earlier we introduced DerivedDescriptorKey (an descriptor whose wildcards are known to have been replaced with derivation indexes) to eliminate wildcard derivation errors. This commit attempts to realise the full benefits of this idea. I tried to keep backwards compatibility. The naming of things no longer follows the logic of code so I'm going to fix that in a follow up.
1 parent d524c6d commit 6dc6aca

File tree

6 files changed

+152
-109
lines changed

6 files changed

+152
-109
lines changed

examples/xpub_descriptors.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use std::str::FromStr;
1818

1919
use miniscript::bitcoin::secp256k1::{Secp256k1, Verification};
2020
use miniscript::bitcoin::{Address, Network};
21-
use miniscript::{Descriptor, DescriptorPublicKey};
21+
use miniscript::{DerivedDescriptorKey, Descriptor, DescriptorPublicKey};
2222

2323
const XPUB_1: &str = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB";
2424
const XPUB_2: &str = "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH";
@@ -40,9 +40,9 @@ fn p2wsh<C: Verification>(secp: &Secp256k1<C>) -> Address {
4040
let s = format!("wsh(sortedmulti(1,{},{}))", XPUB_1, XPUB_2);
4141
// let s = format!("wsh(sortedmulti(1,{},{}))", XPUB_2, XPUB_1);
4242

43-
let address = Descriptor::<DescriptorPublicKey>::from_str(&s)
43+
let address = Descriptor::<DerivedDescriptorKey>::from_str(&s)
4444
.unwrap()
45-
.derived_descriptor(&secp, 0) // dummy index value if it not a wildcard
45+
.derived_descriptor(&secp)
4646
.unwrap()
4747
.address(Network::Bitcoin)
4848
.unwrap();

src/descriptor/key.rs

+56-49
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,9 @@ pub enum SinglePubKey {
7070
XOnly(XOnlyPublicKey),
7171
}
7272

73-
/// A derived [`DescriptorPublicKey`]
74-
///
75-
/// Derived keys are guaranteed to never contain wildcards
73+
/// A [`DescriptorPublicKey`] without any wildcards.
7674
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
77-
pub struct DerivedDescriptorKey {
78-
key: DescriptorPublicKey,
79-
index: u32,
80-
}
75+
pub struct DerivedDescriptorKey(DescriptorPublicKey);
8176

8277
impl fmt::Display for DescriptorSecretKey {
8378
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -357,22 +352,14 @@ impl FromStr for DescriptorPublicKey {
357352
/// Descriptor key conversion error
358353
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
359354
pub enum ConversionError {
360-
/// Attempted to convert a key with a wildcard to a bitcoin public key
361-
Wildcard,
362355
/// Attempted to convert a key with hardened derivations to a bitcoin public key
363356
HardenedChild,
364-
/// Attempted to convert a key with a hardened wildcard to a bitcoin public key
365-
HardenedWildcard,
366357
}
367358

368359
impl fmt::Display for ConversionError {
369360
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
370361
f.write_str(match *self {
371-
ConversionError::Wildcard => "uninstantiated wildcard in bip32 path",
372362
ConversionError::HardenedChild => "hardened child step in bip32 path",
373-
ConversionError::HardenedWildcard => {
374-
"hardened and uninstantiated wildcard in bip32 path"
375-
}
376363
})
377364
}
378365
}
@@ -383,7 +370,7 @@ impl error::Error for ConversionError {
383370
use self::ConversionError::*;
384371

385372
match self {
386-
Wildcard | HardenedChild | HardenedWildcard => None,
373+
HardenedChild => None,
387374
}
388375
}
389376
}
@@ -455,10 +442,7 @@ impl DescriptorPublicKey {
455442
///
456443
/// - If this key is not an xpub, returns `self`.
457444
/// - If this key is an xpub but does not have a wildcard, returns `self`.
458-
/// - Otherwise, returns the derived xpub at `index` (removing the wildcard).
459-
///
460-
/// Since it's guaranteed that extended keys won't have wildcards, the key is returned as
461-
/// [`DerivedDescriptorKey`].
445+
/// - Otherwise, returns the xpub at derivation `index` (removing the wildcard).
462446
///
463447
/// # Panics
464448
///
@@ -469,12 +453,12 @@ impl DescriptorPublicKey {
469453
DescriptorPublicKey::XPub(xpub) => {
470454
let derivation_path = match xpub.wildcard {
471455
Wildcard::None => xpub.derivation_path,
472-
Wildcard::Unhardened => xpub
473-
.derivation_path
474-
.into_child(bip32::ChildNumber::from_normal_idx(index).unwrap()),
475-
Wildcard::Hardened => xpub
476-
.derivation_path
477-
.into_child(bip32::ChildNumber::from_hardened_idx(index).unwrap()),
456+
Wildcard::Unhardened => xpub.derivation_path.into_child(
457+
bip32::ChildNumber::from_normal_idx(index).expect("index must < 2^31"),
458+
),
459+
Wildcard::Hardened => xpub.derivation_path.into_child(
460+
bip32::ChildNumber::from_hardened_idx(index).expect("index must < 2^31"),
461+
),
478462
};
479463
DescriptorPublicKey::XPub(DescriptorXKey {
480464
origin: xpub.origin,
@@ -485,7 +469,7 @@ impl DescriptorPublicKey {
485469
}
486470
};
487471

488-
DerivedDescriptorKey::new(derived, index)
472+
DerivedDescriptorKey::new(derived)
489473
.expect("The key should not contain any wildcards at this point")
490474
}
491475

@@ -494,13 +478,10 @@ impl DescriptorPublicKey {
494478
/// and returns the obtained full [`bitcoin::PublicKey`]. All BIP32 derivations
495479
/// always return a compressed key
496480
///
497-
/// Will return an error if the descriptor key has any hardened
498-
/// derivation steps in its path, or if the key has any wildcards.
481+
/// Will return an error if the descriptor key has any hardened derivation steps in its path. To
482+
/// avoid this error you should replace any such public keys first with [`translate_pk`].
499483
///
500-
/// To ensure there are no wildcards, call `.derive(0)` or similar;
501-
/// to avoid hardened derivation steps, start from a `DescriptorSecretKey`
502-
/// and call `to_public`, or call `TranslatePk2::translate_pk2` with
503-
/// some function which has access to secret key data.
484+
/// [`translate_pk`]: crate::TranslatePk::translate_pk
504485
pub fn derive_public_key<C: Verification>(
505486
&self,
506487
secp: &Secp256k1<C>,
@@ -511,8 +492,9 @@ impl DescriptorPublicKey {
511492
SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()),
512493
},
513494
DescriptorPublicKey::XPub(ref xpk) => match xpk.wildcard {
514-
Wildcard::Unhardened => Err(ConversionError::Wildcard),
515-
Wildcard::Hardened => Err(ConversionError::HardenedWildcard),
495+
Wildcard::Unhardened | Wildcard::Hardened => {
496+
unreachable!("we've excluded this error case")
497+
}
516498
Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) {
517499
Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)),
518500
Err(bip32::Error::CannotDeriveFromHardenedKey) => {
@@ -778,28 +760,47 @@ impl DerivedDescriptorKey {
778760
&self,
779761
secp: &Secp256k1<C>,
780762
) -> Result<bitcoin::PublicKey, ConversionError> {
781-
self.key.derive_public_key(secp)
782-
}
783-
784-
/// Return the derivation index of this key
785-
pub fn index(&self) -> u32 {
786-
self.index
763+
self.0.derive_public_key(secp)
787764
}
788765

789766
/// Construct an instance from a descriptor key and a derivation index
790767
///
791768
/// Returns `None` if the key contains a wildcard
792-
fn new(key: DescriptorPublicKey, index: u32) -> Option<Self> {
793-
match key {
794-
DescriptorPublicKey::XPub(ref xpk) if xpk.wildcard != Wildcard::None => None,
795-
k => Some(DerivedDescriptorKey { key: k, index }),
769+
fn new(key: DescriptorPublicKey) -> Option<Self> {
770+
if key.is_deriveable() {
771+
None
772+
} else {
773+
Some(Self(key))
796774
}
797775
}
776+
777+
/// The fingerprint of the master key associated with this key, `0x00000000` if none.
778+
pub fn master_fingerprint(&self) -> bip32::Fingerprint {
779+
self.0.master_fingerprint()
780+
}
781+
782+
/// Full path, from the master key
783+
pub fn full_derivation_path(&self) -> bip32::DerivationPath {
784+
self.0.full_derivation_path()
785+
}
786+
}
787+
788+
impl FromStr for DerivedDescriptorKey {
789+
type Err = DescriptorKeyParseError;
790+
791+
fn from_str(s: &str) -> Result<Self, Self::Err> {
792+
let inner = DescriptorPublicKey::from_str(s)?;
793+
Ok(
794+
DerivedDescriptorKey::new(inner).ok_or(DescriptorKeyParseError(
795+
"cannot parse key with a wilcard as a DerivedDescriptorKey",
796+
))?,
797+
)
798+
}
798799
}
799800

800801
impl fmt::Display for DerivedDescriptorKey {
801802
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
802-
self.key.fmt(f)
803+
self.0.fmt(f)
803804
}
804805
}
805806

@@ -812,11 +813,11 @@ impl MiniscriptKey for DerivedDescriptorKey {
812813
type Hash160 = hash160::Hash;
813814

814815
fn is_uncompressed(&self) -> bool {
815-
self.key.is_uncompressed()
816+
self.0.is_uncompressed()
816817
}
817818

818819
fn is_x_only_key(&self) -> bool {
819-
self.key.is_x_only_key()
820+
self.0.is_x_only_key()
820821
}
821822

822823
fn to_pubkeyhash(&self) -> Self {
@@ -827,7 +828,7 @@ impl MiniscriptKey for DerivedDescriptorKey {
827828
impl ToPublicKey for DerivedDescriptorKey {
828829
fn to_public_key(&self) -> bitcoin::PublicKey {
829830
let secp = Secp256k1::verification_only();
830-
self.key.derive_public_key(&secp).unwrap()
831+
self.0.derive_public_key(&secp).unwrap()
831832
}
832833

833834
fn hash_to_hash160(hash: &Self) -> hash160::Hash {
@@ -851,6 +852,12 @@ impl ToPublicKey for DerivedDescriptorKey {
851852
}
852853
}
853854

855+
impl From<DerivedDescriptorKey> for DescriptorPublicKey {
856+
fn from(d: DerivedDescriptorKey) -> Self {
857+
d.0
858+
}
859+
}
860+
854861
#[cfg(test)]
855862
mod test {
856863
use core::str::FromStr;

src/descriptor/mod.rs

+75-40
Original file line numberDiff line numberDiff line change
@@ -539,27 +539,29 @@ impl Descriptor<DescriptorPublicKey> {
539539
.expect("BIP 32 key index substitution cannot fail")
540540
}
541541

542-
/// Derive a [`Descriptor`] with a concrete [`bitcoin::PublicKey`] at a given index
543-
/// Removes all extended pubkeys and wildcards from the descriptor and only leaves
544-
/// concrete [`bitcoin::PublicKey`]. All [`bitcoin::XOnlyPublicKey`]s are converted
545-
/// to [`bitcoin::PublicKey`]s by adding a default(0x02) y-coordinate. For [`Tr`]
546-
/// descriptor, spend info is also cached.
542+
/// Convert all the public keys in the descriptor to [`bitcoin::PublicKey`] by deriving them or
543+
/// otherwise converting them. All [`bitcoin::XOnlyPublicKey`]s are converted to by adding a
544+
/// default(0x02) y-coordinate.
547545
///
548-
/// # Examples
546+
/// This is a shorthand for:
549547
///
550548
/// ```
551-
/// use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
552-
/// use miniscript::bitcoin::secp256k1;
553-
/// use std::str::FromStr;
554-
///
555-
/// // test from bip 86
556-
/// let secp = secp256k1::Secp256k1::verification_only();
557-
/// let descriptor = Descriptor::<DescriptorPublicKey>::from_str("tr(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)")
549+
/// # use miniscript::{Descriptor, DescriptorPublicKey, bitcoin::secp256k1::Secp256k1};
550+
/// # use core::str::FromStr;
551+
/// # let descriptor = Descriptor::<DescriptorPublicKey>::from_str("tr(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)")
558552
/// .expect("Valid ranged descriptor");
559-
/// let result = descriptor.derived_descriptor(&secp, 0).expect("Non-hardened derivation");
560-
/// assert_eq!(result.to_string(), "tr(03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115)#6qm9h8ym");
553+
/// # let index = 42;
554+
/// # let secp = Secp256k1::verification_only();
555+
/// let derived_descriptor = descriptor.derive(index).derived_descriptor(&secp);
556+
/// # assert_eq!(descriptor.derived_descriptor(&secp, index), derived_descriptor);
561557
/// ```
562558
///
559+
/// and is only here really here for backwards compatbility.
560+
/// See [`derive`] and `[derived_descriptor`] for more documentation.
561+
///
562+
/// [`derive`]: Self::derive
563+
/// [`derived_descriptor`]: crate::DerivedDescriptor::derived_descriptor
564+
///
563565
/// # Errors
564566
///
565567
/// This function will return an error if hardened derivation is attempted.
@@ -568,29 +570,7 @@ impl Descriptor<DescriptorPublicKey> {
568570
secp: &secp256k1::Secp256k1<C>,
569571
index: u32,
570572
) -> Result<Descriptor<bitcoin::PublicKey>, ConversionError> {
571-
struct Derivator<'a, C: secp256k1::Verification>(&'a secp256k1::Secp256k1<C>);
572-
573-
impl<'a, C: secp256k1::Verification>
574-
PkTranslator<DerivedDescriptorKey, bitcoin::PublicKey, ConversionError>
575-
for Derivator<'a, C>
576-
{
577-
fn pk(
578-
&mut self,
579-
pk: &DerivedDescriptorKey,
580-
) -> Result<bitcoin::PublicKey, ConversionError> {
581-
pk.derive_public_key(&self.0)
582-
}
583-
584-
fn pkh(
585-
&mut self,
586-
pkh: &DerivedDescriptorKey,
587-
) -> Result<bitcoin::hashes::hash160::Hash, ConversionError> {
588-
Ok(pkh.derive_public_key(&self.0)?.to_pubkeyhash())
589-
}
590-
}
591-
592-
let derived = self.derive(index).translate_pk(&mut Derivator(secp))?;
593-
Ok(derived)
573+
self.derive(index).derived_descriptor(&secp)
594574
}
595575

596576
/// Parse a descriptor that may contain secret keys
@@ -744,6 +724,59 @@ impl Descriptor<DescriptorPublicKey> {
744724
}
745725
}
746726

727+
impl Descriptor<DerivedDescriptorKey> {
728+
/// Convert all the public keys in the descriptor to [`bitcoin::PublicKey`] by deriving them or
729+
/// otherwise converting them. All [`bitcoin::XOnlyPublicKey`]s are converted to by adding a
730+
/// default(0x02) y-coordinate.
731+
///
732+
/// # Examples
733+
///
734+
/// ```
735+
/// use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
736+
/// use miniscript::bitcoin::secp256k1;
737+
/// use std::str::FromStr;
738+
///
739+
/// // test from bip 86
740+
/// let secp = secp256k1::Secp256k1::verification_only();
741+
/// let descriptor = Descriptor::<DescriptorPublicKey>::from_str("tr(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)")
742+
/// .expect("Valid ranged descriptor");
743+
/// let result = descriptor.derive(0).derived_descriptor(&secp).expect("Non-hardened derivation");
744+
/// assert_eq!(result.to_string(), "tr(03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115)#6qm9h8ym");
745+
/// ```
746+
///
747+
/// # Errors
748+
///
749+
/// This function will return an error if hardened derivation is attempted.
750+
pub fn derived_descriptor<C: secp256k1::Verification>(
751+
&self,
752+
secp: &secp256k1::Secp256k1<C>,
753+
) -> Result<Descriptor<bitcoin::PublicKey>, ConversionError> {
754+
struct Derivator<'a, C: secp256k1::Verification>(&'a secp256k1::Secp256k1<C>);
755+
756+
impl<'a, C: secp256k1::Verification>
757+
PkTranslator<DerivedDescriptorKey, bitcoin::PublicKey, ConversionError>
758+
for Derivator<'a, C>
759+
{
760+
fn pk(
761+
&mut self,
762+
pk: &DerivedDescriptorKey,
763+
) -> Result<bitcoin::PublicKey, ConversionError> {
764+
pk.derive_public_key(&self.0)
765+
}
766+
767+
fn pkh(
768+
&mut self,
769+
pkh: &DerivedDescriptorKey,
770+
) -> Result<bitcoin::hashes::hash160::Hash, ConversionError> {
771+
Ok(pkh.derive_public_key(&self.0)?.to_pubkeyhash())
772+
}
773+
}
774+
775+
let derived = self.translate_pk(&mut Derivator(secp))?;
776+
Ok(derived)
777+
}
778+
}
779+
747780
impl_from_tree!(
748781
Descriptor<Pk>,
749782
/// Parse an expression tree into a descriptor.
@@ -1564,12 +1597,14 @@ mod tests {
15641597

15651598
// Same address
15661599
let addr_one = desc_one
1567-
.derived_descriptor(&secp_ctx, index)
1600+
.derive(index)
1601+
.derived_descriptor(&secp_ctx)
15681602
.unwrap()
15691603
.address(bitcoin::Network::Bitcoin)
15701604
.unwrap();
15711605
let addr_two = desc_two
1572-
.derived_descriptor(&secp_ctx, index)
1606+
.derive(index)
1607+
.derived_descriptor(&secp_ctx)
15731608
.unwrap()
15741609
.address(bitcoin::Network::Bitcoin)
15751610
.unwrap();

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ use std::error;
135135
use bitcoin::blockdata::{opcodes, script};
136136
use bitcoin::hashes::{hash160, ripemd160, sha256, Hash};
137137

138-
pub use crate::descriptor::{Descriptor, DescriptorPublicKey};
138+
pub use crate::descriptor::{DerivedDescriptorKey, Descriptor, DescriptorPublicKey};
139139
pub use crate::interpreter::Interpreter;
140140
pub use crate::miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0, Tap};
141141
pub use crate::miniscript::decode::Terminal;

0 commit comments

Comments
 (0)