From ffe62effe70a5b06e908b75c97fa9677aed24bc1 Mon Sep 17 00:00:00 2001
From: Scott Robinson <ssr@squareup.com>
Date: Sun, 23 Jul 2023 14:59:20 +1000
Subject: [PATCH 1/6] DescriptorKey trait

---
 src/descriptor/key.rs | 47 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs
index 83308e0dc..51a1ec7f3 100644
--- a/src/descriptor/key.rs
+++ b/src/descriptor/key.rs
@@ -525,6 +525,53 @@ impl error::Error for ConversionError {
     }
 }
 
+pub trait DescriptorKey : Clone + fmt::Debug + fmt::Display + Eq + FromStr + std::hash::Hash + Ord {
+    /// The fingerprint of the master key associated with this key, `0x00000000` if none.
+    fn master_fingerprint(&self) -> bip32::Fingerprint;
+
+    /// Full path, from the master key
+    ///
+    /// For wildcard keys this will return the path up to the wildcard, so you
+    /// can get full paths by appending one additional derivation step, according
+    /// to the wildcard type (hardened or normal).
+    ///
+    /// For multipath extended keys, this returns `None`.
+    fn full_derivation_path(&self) -> Option<bip32::DerivationPath>;
+
+    /// Whether or not the key has a wildcard
+    fn has_wildcard(&self) -> bool;
+
+    /// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a
+    /// *definite* key (i.e. one where all the derivation paths are set).
+    ///
+    /// # Returns
+    ///
+    /// - If this key is not an xpub, returns `self`.
+    /// - If this key is an xpub but does not have a wildcard, returns `self`.
+    /// - Otherwise, returns the xpub at derivation `index` (removing the wildcard).
+    ///
+    /// # Errors
+    ///
+    /// - If `index` is hardened.
+    fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError>;
+
+    /// Whether or not this key has multiple derivation paths.
+    fn is_multipath(&self) -> bool;
+
+    /// Computes the public key corresponding to this descriptor key.
+    /// When deriving from an XOnlyPublicKey, it adds the default 0x02 y-coordinate
+    /// and returns the obtained full [`bitcoin::PublicKey`]. All BIP32 derivations
+    /// always return a compressed key
+    ///
+    /// Will return an error if the descriptor key has any hardened derivation steps in its path. To
+    /// avoid this error you should replace any such public keys first with [`translate_pk`].
+    ///
+    /// [`translate_pk`]: crate::TranslatePk::translate_pk
+    fn derive_public_key<C: Verification>(
+        &self,
+        secp: &Secp256k1<C>,
+    ) -> Result<bitcoin::PublicKey, ConversionError>;
+}
 impl DescriptorPublicKey {
     /// The fingerprint of the master key associated with this key, `0x00000000` if none.
     pub fn master_fingerprint(&self) -> bip32::Fingerprint {

From bfa946e36df0cee5903ae542e23440b0d3968dbf Mon Sep 17 00:00:00 2001
From: Scott Robinson <ssr@squareup.com>
Date: Sun, 23 Jul 2023 12:40:02 +1000
Subject: [PATCH 2/6] Extract specialised DescriptorKey functionality into
 DescriptorExtendedPublicKey

---
 src/descriptor/key.rs | 228 ++++++++++++++++++++++++++++--------------
 1 file changed, 151 insertions(+), 77 deletions(-)

diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs
index 51a1ec7f3..bdd1dadbf 100644
--- a/src/descriptor/key.rs
+++ b/src/descriptor/key.rs
@@ -18,13 +18,15 @@ use crate::prelude::*;
 use crate::serde::{Deserialize, Deserializer, Serialize, Serializer};
 use crate::{hash256, MiniscriptKey, ToPublicKey};
 
+type DescriptorExtendedPublicKey = DescriptorXKey<bip32::ExtendedPubKey>;
+
 /// The descriptor pubkey, either a single pubkey or an xpub.
 #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
 pub enum DescriptorPublicKey {
     /// Single public key.
     Single(SinglePub),
     /// Extended public key (xpub).
-    XPub(DescriptorXKey<bip32::ExtendedPubKey>),
+    XPub(DescriptorExtendedPublicKey),
     /// Multiple extended public keys.
     MultiXPub(DescriptorMultiXKey<bip32::ExtendedPubKey>),
 }
@@ -283,6 +285,20 @@ impl error::Error for DescriptorKeyParseError {
     }
 }
 
+impl fmt::Display for DescriptorExtendedPublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        maybe_fmt_master_id(f, &self.origin)?;
+        self.xkey.fmt(f)?;
+        fmt_derivation_path(f, &self.derivation_path)?;
+        match self.wildcard {
+            Wildcard::None => {}
+            Wildcard::Unhardened => write!(f, "/*")?,
+            Wildcard::Hardened => write!(f, "/*h")?,
+        }
+        Ok(())
+    }
+}
+
 impl fmt::Display for DescriptorPublicKey {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match *self {
@@ -294,17 +310,7 @@ impl fmt::Display for DescriptorPublicKey {
                 }?;
                 Ok(())
             }
-            DescriptorPublicKey::XPub(ref xpub) => {
-                maybe_fmt_master_id(f, &xpub.origin)?;
-                xpub.xkey.fmt(f)?;
-                fmt_derivation_path(f, &xpub.derivation_path)?;
-                match xpub.wildcard {
-                    Wildcard::None => {}
-                    Wildcard::Unhardened => write!(f, "/*")?,
-                    Wildcard::Hardened => write!(f, "/*h")?,
-                }
-                Ok(())
-            }
+            DescriptorPublicKey::XPub(ref xpub) => xpub.fmt(f),
             DescriptorPublicKey::MultiXPub(ref xpub) => {
                 maybe_fmt_master_id(f, &xpub.origin)?;
                 xpub.xkey.fmt(f)?;
@@ -432,6 +438,30 @@ fn fmt_derivation_paths(f: &mut fmt::Formatter, paths: &[bip32::DerivationPath])
     Ok(())
 }
 
+impl FromStr for DescriptorExtendedPublicKey {
+    type Err = DescriptorKeyParseError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let (key_part, origin) = parse_key_origin(s)?;
+
+        let (xpub, derivation_paths, wildcard) =
+            parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?;
+
+        if derivation_paths.len() > 1 {
+            return Err(DescriptorKeyParseError(
+                "Multiple derivation paths are not allowed for single extended keys",
+            ));
+        }
+
+        Ok(Self {
+            origin,
+            xkey: xpub,
+            derivation_path: derivation_paths.into_iter().next().unwrap_or_default(),
+            wildcard,
+        })
+    }
+}
+
 impl FromStr for DescriptorPublicKey {
     type Err = DescriptorKeyParseError;
 
@@ -456,12 +486,7 @@ impl FromStr for DescriptorPublicKey {
                     wildcard,
                 }))
             } else {
-                Ok(DescriptorPublicKey::XPub(DescriptorXKey {
-                    origin,
-                    xkey: xpub,
-                    derivation_path: derivation_paths.into_iter().next().unwrap_or_default(),
-                    wildcard,
-                }))
+                DescriptorExtendedPublicKey::from_str(s).map(Self::XPub)
             }
         } else {
             let key = match key_part.len() {
@@ -503,6 +528,8 @@ pub enum ConversionError {
     HardenedChild,
     /// Attempted to convert a key with multiple derivation paths to a bitcoin public key
     MultiKey,
+    /// Attempted to convert a key with a wildcard to a bitcoin public key
+    Wildcard,
 }
 
 impl fmt::Display for ConversionError {
@@ -510,6 +537,7 @@ impl fmt::Display for ConversionError {
         f.write_str(match *self {
             ConversionError::HardenedChild => "hardened child step in bip32 path",
             ConversionError::MultiKey => "multiple existing keys",
+            ConversionError::Wildcard => "wildcard in bip32 path",
         })
     }
 }
@@ -520,7 +548,7 @@ impl error::Error for ConversionError {
         use self::ConversionError::*;
 
         match self {
-            HardenedChild | MultiKey => None,
+            HardenedChild | MultiKey | Wildcard => None,
         }
     }
 }
@@ -572,17 +600,102 @@ pub trait DescriptorKey : Clone + fmt::Debug + fmt::Display + Eq + FromStr + std
         secp: &Secp256k1<C>,
     ) -> Result<bitcoin::PublicKey, ConversionError>;
 }
+
+impl DescriptorKey for DescriptorExtendedPublicKey {
+    /// The fingerprint of the master key associated with this key, `0x00000000` if none.
+    fn master_fingerprint(&self) -> bip32::Fingerprint {
+        if let Some((fingerprint, _)) = self.origin {
+            fingerprint
+        } else {
+            self.xkey.fingerprint()
+        }
+    }
+
+    /// Full path, from the master key
+    ///
+    /// For wildcard keys this will return the path up to the wildcard, so you
+    /// can get full paths by appending one additional derivation step, according
+    /// to the wildcard type (hardened or normal).
+    ///
+    /// For multipath extended keys, this returns `None`.
+    fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
+        let origin_path = if let Some((_, ref path)) = self.origin {
+            path.clone()
+        } else {
+            bip32::DerivationPath::from(vec![])
+        };
+        Some(origin_path.extend(&self.derivation_path))
+    }
+
+    /// Whether or not the key has a wildcard
+    fn has_wildcard(&self) -> bool {
+        self.wildcard != Wildcard::None
+    }
+
+    /// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a
+    /// *definite* key (i.e. one where all the derivation paths are set).
+    ///
+    /// # Returns
+    ///
+    /// - If this key is not an xpub, returns `self`.
+    /// - If this key is an xpub but does not have a wildcard, returns `self`.
+    /// - Otherwise, returns the xpub at derivation `index` (removing the wildcard).
+    ///
+    /// # Errors
+    ///
+    /// - If `index` is hardened.
+    fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
+        let derivation_path = match self.wildcard {
+            Wildcard::None => self.derivation_path,
+            Wildcard::Unhardened => self.derivation_path.into_child(
+                bip32::ChildNumber::from_normal_idx(index)
+                    .ok()
+                    .ok_or(ConversionError::HardenedChild)?,
+            ),
+            Wildcard::Hardened => self.derivation_path.into_child(
+                bip32::ChildNumber::from_hardened_idx(index)
+                    .ok()
+                    .ok_or(ConversionError::HardenedChild)?,
+            ),
+        };
+        let definite = DescriptorPublicKey::XPub(DescriptorXKey {
+            origin: self.origin,
+            xkey: self.xkey,
+            derivation_path,
+            wildcard: Wildcard::None,
+        });
+
+        Ok(DefiniteDescriptorKey::new(definite)
+            .expect("The key should not contain any wildcards at this point"))
+    }
+
+    /// Whether or not this key has multiple derivation paths.
+    fn is_multipath(&self) -> bool {
+        false
+    }
+
+    fn derive_public_key<C: Verification>(
+        &self,
+        secp: &Secp256k1<C>,
+    ) -> Result<bitcoin::PublicKey, ConversionError> {
+        match self.wildcard {
+            Wildcard::Unhardened | Wildcard::Hardened => Err(ConversionError::Wildcard),
+            Wildcard::None => match self.xkey.derive_pub(secp, &self.derivation_path.as_ref()) {
+                Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)),
+                Err(bip32::Error::CannotDeriveFromHardenedKey) => {
+                    Err(ConversionError::HardenedChild)
+                }
+                Err(e) => unreachable!("cryptographically unreachable: {}", e),
+            },
+        }
+    }
+}
+
 impl DescriptorPublicKey {
     /// The fingerprint of the master key associated with this key, `0x00000000` if none.
     pub fn master_fingerprint(&self) -> bip32::Fingerprint {
         match *self {
-            DescriptorPublicKey::XPub(ref xpub) => {
-                if let Some((fingerprint, _)) = xpub.origin {
-                    fingerprint
-                } else {
-                    xpub.xkey.fingerprint()
-                }
-            }
+            DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(),
             DescriptorPublicKey::MultiXPub(ref xpub) => {
                 if let Some((fingerprint, _)) = xpub.origin {
                     fingerprint
@@ -620,14 +733,7 @@ impl DescriptorPublicKey {
     /// For multipath extended keys, this returns `None`.
     pub fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
         match *self {
-            DescriptorPublicKey::XPub(ref xpub) => {
-                let origin_path = if let Some((_, ref path)) = xpub.origin {
-                    path.clone()
-                } else {
-                    bip32::DerivationPath::from(vec![])
-                };
-                Some(origin_path.extend(&xpub.derivation_path))
-            }
+            DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(),
             DescriptorPublicKey::Single(ref single) => {
                 Some(if let Some((_, ref path)) = single.origin {
                     path.clone()
@@ -649,7 +755,7 @@ impl DescriptorPublicKey {
     pub fn has_wildcard(&self) -> bool {
         match *self {
             DescriptorPublicKey::Single(..) => false,
-            DescriptorPublicKey::XPub(ref xpub) => xpub.wildcard != Wildcard::None,
+            DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(),
             DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None,
         }
     }
@@ -673,40 +779,19 @@ impl DescriptorPublicKey {
     ///
     /// - If `index` is hardened.
     pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
-        let definite = match self {
-            DescriptorPublicKey::Single(_) => self,
-            DescriptorPublicKey::XPub(xpub) => {
-                let derivation_path = match xpub.wildcard {
-                    Wildcard::None => xpub.derivation_path,
-                    Wildcard::Unhardened => xpub.derivation_path.into_child(
-                        bip32::ChildNumber::from_normal_idx(index)
-                            .ok()
-                            .ok_or(ConversionError::HardenedChild)?,
-                    ),
-                    Wildcard::Hardened => xpub.derivation_path.into_child(
-                        bip32::ChildNumber::from_hardened_idx(index)
-                            .ok()
-                            .ok_or(ConversionError::HardenedChild)?,
-                    ),
-                };
-                DescriptorPublicKey::XPub(DescriptorXKey {
-                    origin: xpub.origin,
-                    xkey: xpub.xkey,
-                    derivation_path,
-                    wildcard: Wildcard::None,
-                })
-            }
-            DescriptorPublicKey::MultiXPub(_) => return Err(ConversionError::MultiKey),
-        };
-
-        Ok(DefiniteDescriptorKey::new(definite)
-            .expect("The key should not contain any wildcards at this point"))
+        match self {
+            DescriptorPublicKey::Single(_) => Ok(DefiniteDescriptorKey::new(self)
+                .expect("The key should not contain any wildcards at this point")),
+            DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index),
+            DescriptorPublicKey::MultiXPub(_) => Err(ConversionError::MultiKey),
+        }
     }
 
     /// Whether or not this key has multiple derivation paths.
     pub fn is_multipath(&self) -> bool {
-        match *self {
-            DescriptorPublicKey::Single(..) | DescriptorPublicKey::XPub(..) => false,
+        match self {
+            DescriptorPublicKey::Single(..) => false,
+            DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(),
             DescriptorPublicKey::MultiXPub(_) => true,
         }
     }
@@ -1083,18 +1168,7 @@ impl DefiniteDescriptorKey {
                 SinglePubKey::FullKey(pk) => Ok(pk),
                 SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()),
             },
-            DescriptorPublicKey::XPub(ref xpk) => match xpk.wildcard {
-                Wildcard::Unhardened | Wildcard::Hardened => {
-                    unreachable!("we've excluded this error case")
-                }
-                Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) {
-                    Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)),
-                    Err(bip32::Error::CannotDeriveFromHardenedKey) => {
-                        Err(ConversionError::HardenedChild)
-                    }
-                    Err(e) => unreachable!("cryptographically unreachable: {}", e),
-                },
-            },
+            DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp),
             DescriptorPublicKey::MultiXPub(_) => {
                 unreachable!("A definite key cannot contain a multipath key.")
             }

From c430f70000f8d4e8ef37dc5680d6c9bb17d8758d Mon Sep 17 00:00:00 2001
From: Scott Robinson <ssr@squareup.com>
Date: Sun, 23 Jul 2023 13:07:23 +1000
Subject: [PATCH 3/6] Extract specialised DescriptorKey functionality into
 DescriptorSinglePublicKey

---
 src/descriptor/key.rs | 177 ++++++++++++++++++++++++++----------------
 1 file changed, 108 insertions(+), 69 deletions(-)

diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs
index bdd1dadbf..f0571eb61 100644
--- a/src/descriptor/key.rs
+++ b/src/descriptor/key.rs
@@ -18,6 +18,7 @@ use crate::prelude::*;
 use crate::serde::{Deserialize, Deserializer, Serialize, Serializer};
 use crate::{hash256, MiniscriptKey, ToPublicKey};
 
+type DescriptorSinglePublicKey = SinglePub;
 type DescriptorExtendedPublicKey = DescriptorXKey<bip32::ExtendedPubKey>;
 
 /// The descriptor pubkey, either a single pubkey or an xpub.
@@ -285,6 +286,16 @@ impl error::Error for DescriptorKeyParseError {
     }
 }
 
+impl fmt::Display for DescriptorSinglePublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        maybe_fmt_master_id(f, &self.origin)?;
+        match self.key {
+            SinglePubKey::FullKey(full_key) => full_key.fmt(f),
+            SinglePubKey::XOnly(x_only_key) => x_only_key.fmt(f),
+        }
+    }
+}
+
 impl fmt::Display for DescriptorExtendedPublicKey {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         maybe_fmt_master_id(f, &self.origin)?;
@@ -302,14 +313,7 @@ impl fmt::Display for DescriptorExtendedPublicKey {
 impl fmt::Display for DescriptorPublicKey {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match *self {
-            DescriptorPublicKey::Single(ref pk) => {
-                maybe_fmt_master_id(f, &pk.origin)?;
-                match pk.key {
-                    SinglePubKey::FullKey(full_key) => full_key.fmt(f),
-                    SinglePubKey::XOnly(x_only_key) => x_only_key.fmt(f),
-                }?;
-                Ok(())
-            }
+            DescriptorPublicKey::Single(ref pk) => pk.fmt(f),
             DescriptorPublicKey::XPub(ref xpub) => xpub.fmt(f),
             DescriptorPublicKey::MultiXPub(ref xpub) => {
                 maybe_fmt_master_id(f, &xpub.origin)?;
@@ -438,6 +442,41 @@ fn fmt_derivation_paths(f: &mut fmt::Formatter, paths: &[bip32::DerivationPath])
     Ok(())
 }
 
+impl FromStr for DescriptorSinglePublicKey {
+    type Err = DescriptorKeyParseError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let (key_part, origin) = parse_key_origin(s)?;
+
+        let key = match key_part.len() {
+            64 => {
+                let x_only_key = XOnlyPublicKey::from_str(key_part)
+                    .map_err(|_| DescriptorKeyParseError("Error while parsing simple xonly key"))?;
+                SinglePubKey::XOnly(x_only_key)
+            }
+            66 | 130 => {
+                if !(&key_part[0..2] == "02" || &key_part[0..2] == "03" || &key_part[0..2] == "04")
+                {
+                    return Err(DescriptorKeyParseError(
+                        "Only publickeys with prefixes 02/03/04 are allowed",
+                    ));
+                }
+                let key = bitcoin::PublicKey::from_str(key_part).map_err(|_| {
+                    DescriptorKeyParseError("Error while parsing simple public key")
+                })?;
+                SinglePubKey::FullKey(key)
+            }
+            _ => {
+                return Err(DescriptorKeyParseError(
+                    "Public keys must be 64/66/130 characters in size",
+                ))
+            }
+        };
+
+        Ok(Self { key, origin })
+    }
+}
+
 impl FromStr for DescriptorExtendedPublicKey {
     type Err = DescriptorKeyParseError;
 
@@ -489,34 +528,7 @@ impl FromStr for DescriptorPublicKey {
                 DescriptorExtendedPublicKey::from_str(s).map(Self::XPub)
             }
         } else {
-            let key = match key_part.len() {
-                64 => {
-                    let x_only_key = XOnlyPublicKey::from_str(key_part).map_err(|_| {
-                        DescriptorKeyParseError("Error while parsing simple xonly key")
-                    })?;
-                    SinglePubKey::XOnly(x_only_key)
-                }
-                66 | 130 => {
-                    if !(&key_part[0..2] == "02"
-                        || &key_part[0..2] == "03"
-                        || &key_part[0..2] == "04")
-                    {
-                        return Err(DescriptorKeyParseError(
-                            "Only publickeys with prefixes 02/03/04 are allowed",
-                        ));
-                    }
-                    let key = bitcoin::PublicKey::from_str(key_part).map_err(|_| {
-                        DescriptorKeyParseError("Error while parsing simple public key")
-                    })?;
-                    SinglePubKey::FullKey(key)
-                }
-                _ => {
-                    return Err(DescriptorKeyParseError(
-                        "Public keys must be 64/66/130 characters in size",
-                    ))
-                }
-            };
-            Ok(DescriptorPublicKey::Single(SinglePub { key, origin }))
+            DescriptorSinglePublicKey::from_str(s).map(Self::Single)
         }
     }
 }
@@ -601,6 +613,60 @@ pub trait DescriptorKey : Clone + fmt::Debug + fmt::Display + Eq + FromStr + std
     ) -> Result<bitcoin::PublicKey, ConversionError>;
 }
 
+impl DescriptorKey for DescriptorSinglePublicKey {
+    fn master_fingerprint(&self) -> bip32::Fingerprint {
+        if let Some((fingerprint, _)) = self.origin {
+            fingerprint
+        } else {
+            let mut engine = XpubIdentifier::engine();
+            match self.key {
+                SinglePubKey::FullKey(pk) => {
+                    pk.write_into(&mut engine).expect("engines don't error")
+                }
+                SinglePubKey::XOnly(x_only_pk) => engine.input(&x_only_pk.serialize()),
+            };
+            bip32::Fingerprint::from(
+                &XpubIdentifier::from_engine(engine)[..4]
+                    .try_into()
+                    .expect("4 byte slice"),
+            )
+        }
+    }
+
+    fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
+        Some(if let Some((_, ref path)) = self.origin {
+            path.clone()
+        } else {
+            bip32::DerivationPath::from(vec![])
+        })
+    }
+
+    fn has_wildcard(&self) -> bool {
+        false
+    }
+
+    fn at_derivation_index(self, _index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
+        Ok(
+            DefiniteDescriptorKey::new(DescriptorPublicKey::Single(self))
+                .expect("The key should not contain any wildcards at this point"),
+        )
+    }
+
+    fn is_multipath(&self) -> bool {
+        false
+    }
+
+    fn derive_public_key<C: Verification>(
+        &self,
+        _secp: &Secp256k1<C>,
+    ) -> Result<bitcoin::PublicKey, ConversionError> {
+        match self.key {
+            SinglePubKey::FullKey(pk) => Ok(pk),
+            SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()),
+        }
+    }
+}
+
 impl DescriptorKey for DescriptorExtendedPublicKey {
     /// The fingerprint of the master key associated with this key, `0x00000000` if none.
     fn master_fingerprint(&self) -> bip32::Fingerprint {
@@ -703,24 +769,7 @@ impl DescriptorPublicKey {
                     xpub.xkey.fingerprint()
                 }
             }
-            DescriptorPublicKey::Single(ref single) => {
-                if let Some((fingerprint, _)) = single.origin {
-                    fingerprint
-                } else {
-                    let mut engine = XpubIdentifier::engine();
-                    match single.key {
-                        SinglePubKey::FullKey(pk) => {
-                            pk.write_into(&mut engine).expect("engines don't error")
-                        }
-                        SinglePubKey::XOnly(x_only_pk) => engine.input(&x_only_pk.serialize()),
-                    };
-                    bip32::Fingerprint::from(
-                        &XpubIdentifier::from_engine(engine)[..4]
-                            .try_into()
-                            .expect("4 byte slice"),
-                    )
-                }
-            }
+            DescriptorPublicKey::Single(ref single) => single.master_fingerprint(),
         }
     }
 
@@ -734,13 +783,7 @@ impl DescriptorPublicKey {
     pub fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
         match *self {
             DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(),
-            DescriptorPublicKey::Single(ref single) => {
-                Some(if let Some((_, ref path)) = single.origin {
-                    path.clone()
-                } else {
-                    bip32::DerivationPath::from(vec![])
-                })
-            }
+            DescriptorPublicKey::Single(ref single) => single.full_derivation_path(),
             DescriptorPublicKey::MultiXPub(_) => None,
         }
     }
@@ -754,7 +797,7 @@ impl DescriptorPublicKey {
     /// Whether or not the key has a wildcard
     pub fn has_wildcard(&self) -> bool {
         match *self {
-            DescriptorPublicKey::Single(..) => false,
+            DescriptorPublicKey::Single(ref single) => single.has_wildcard(),
             DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(),
             DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None,
         }
@@ -780,8 +823,7 @@ impl DescriptorPublicKey {
     /// - If `index` is hardened.
     pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
         match self {
-            DescriptorPublicKey::Single(_) => Ok(DefiniteDescriptorKey::new(self)
-                .expect("The key should not contain any wildcards at this point")),
+            DescriptorPublicKey::Single(single) => single.at_derivation_index(index),
             DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index),
             DescriptorPublicKey::MultiXPub(_) => Err(ConversionError::MultiKey),
         }
@@ -790,7 +832,7 @@ impl DescriptorPublicKey {
     /// Whether or not this key has multiple derivation paths.
     pub fn is_multipath(&self) -> bool {
         match self {
-            DescriptorPublicKey::Single(..) => false,
+            DescriptorPublicKey::Single(single) => single.is_multipath(),
             DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(),
             DescriptorPublicKey::MultiXPub(_) => true,
         }
@@ -1164,10 +1206,7 @@ impl DefiniteDescriptorKey {
         secp: &Secp256k1<C>,
     ) -> Result<bitcoin::PublicKey, ConversionError> {
         match self.0 {
-            DescriptorPublicKey::Single(ref pk) => match pk.key {
-                SinglePubKey::FullKey(pk) => Ok(pk),
-                SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()),
-            },
+            DescriptorPublicKey::Single(ref pk) => pk.derive_public_key(secp),
             DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp),
             DescriptorPublicKey::MultiXPub(_) => {
                 unreachable!("A definite key cannot contain a multipath key.")

From 687fd4e7718e2ba20e15a80c4c83f4338f9f7bb4 Mon Sep 17 00:00:00 2001
From: Scott Robinson <ssr@squareup.com>
Date: Sun, 23 Jul 2023 13:10:41 +1000
Subject: [PATCH 4/6] Extract specialised DescriptorKey functionality into
 DescriptorMultiExtendedPublicKey

---
 src/descriptor/key.rs | 113 ++++++++++++++++++++++++++++++------------
 1 file changed, 81 insertions(+), 32 deletions(-)

diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs
index f0571eb61..b445739cc 100644
--- a/src/descriptor/key.rs
+++ b/src/descriptor/key.rs
@@ -20,6 +20,7 @@ use crate::{hash256, MiniscriptKey, ToPublicKey};
 
 type DescriptorSinglePublicKey = SinglePub;
 type DescriptorExtendedPublicKey = DescriptorXKey<bip32::ExtendedPubKey>;
+type DescriptorMultiExtendedPublicKey = DescriptorMultiXKey<bip32::ExtendedPubKey>;
 
 /// The descriptor pubkey, either a single pubkey or an xpub.
 #[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
@@ -29,7 +30,7 @@ pub enum DescriptorPublicKey {
     /// Extended public key (xpub).
     XPub(DescriptorExtendedPublicKey),
     /// Multiple extended public keys.
-    MultiXPub(DescriptorMultiXKey<bip32::ExtendedPubKey>),
+    MultiXPub(DescriptorMultiExtendedPublicKey),
 }
 
 /// The descriptor secret key, either a single private key or an xprv.
@@ -310,22 +311,26 @@ impl fmt::Display for DescriptorExtendedPublicKey {
     }
 }
 
+impl fmt::Display for DescriptorMultiExtendedPublicKey {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        maybe_fmt_master_id(f, &self.origin)?;
+        self.xkey.fmt(f)?;
+        fmt_derivation_paths(f, self.derivation_paths.paths())?;
+        match self.wildcard {
+            Wildcard::None => {}
+            Wildcard::Unhardened => write!(f, "/*")?,
+            Wildcard::Hardened => write!(f, "/*h")?,
+        }
+        Ok(())
+    }
+}
+
 impl fmt::Display for DescriptorPublicKey {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match *self {
             DescriptorPublicKey::Single(ref pk) => pk.fmt(f),
             DescriptorPublicKey::XPub(ref xpub) => xpub.fmt(f),
-            DescriptorPublicKey::MultiXPub(ref xpub) => {
-                maybe_fmt_master_id(f, &xpub.origin)?;
-                xpub.xkey.fmt(f)?;
-                fmt_derivation_paths(f, xpub.derivation_paths.paths())?;
-                match xpub.wildcard {
-                    Wildcard::None => {}
-                    Wildcard::Unhardened => write!(f, "/*")?,
-                    Wildcard::Hardened => write!(f, "/*h")?,
-                }
-                Ok(())
-            }
+            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.fmt(f),
         }
     }
 }
@@ -501,6 +506,30 @@ impl FromStr for DescriptorExtendedPublicKey {
     }
 }
 
+impl FromStr for DescriptorMultiExtendedPublicKey {
+    type Err = DescriptorKeyParseError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let (key_part, origin) = parse_key_origin(s)?;
+
+        let (xpub, derivation_paths, wildcard) =
+            parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?;
+
+        if derivation_paths.len() < 2 {
+            return Err(DescriptorKeyParseError(
+                "Multiple derivation paths are required for multi extended keys",
+            ));
+        }
+
+        Ok(Self {
+            origin,
+            xkey: xpub,
+            derivation_paths: DerivPaths::new(derivation_paths).expect("Not empty"),
+            wildcard,
+        })
+    }
+}
+
 impl FromStr for DescriptorPublicKey {
     type Err = DescriptorKeyParseError;
 
@@ -518,12 +547,7 @@ impl FromStr for DescriptorPublicKey {
             let (xpub, derivation_paths, wildcard) =
                 parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?;
             if derivation_paths.len() > 1 {
-                Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
-                    origin,
-                    xkey: xpub,
-                    derivation_paths: DerivPaths::new(derivation_paths).expect("Not empty"),
-                    wildcard,
-                }))
+                DescriptorMultiExtendedPublicKey::from_str(s).map(Self::MultiXPub)
             } else {
                 DescriptorExtendedPublicKey::from_str(s).map(Self::XPub)
             }
@@ -757,18 +781,45 @@ impl DescriptorKey for DescriptorExtendedPublicKey {
     }
 }
 
+impl DescriptorKey for DescriptorMultiExtendedPublicKey {
+    fn master_fingerprint(&self) -> bip32::Fingerprint {
+        if let Some((fingerprint, _)) = self.origin {
+            fingerprint
+        } else {
+            self.xkey.fingerprint()
+        }
+    }
+
+    fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
+        None
+    }
+
+    fn has_wildcard(&self) -> bool {
+        self.wildcard != Wildcard::None
+    }
+
+    fn at_derivation_index(self, _index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
+        Err(ConversionError::MultiKey)
+    }
+
+    fn is_multipath(&self) -> bool {
+        true
+    }
+
+    fn derive_public_key<C: Verification>(
+        &self,
+        _secp: &Secp256k1<C>,
+    ) -> Result<bitcoin::PublicKey, ConversionError> {
+        Err(ConversionError::MultiKey)
+    }
+}
+
 impl DescriptorPublicKey {
     /// The fingerprint of the master key associated with this key, `0x00000000` if none.
     pub fn master_fingerprint(&self) -> bip32::Fingerprint {
         match *self {
             DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(),
-            DescriptorPublicKey::MultiXPub(ref xpub) => {
-                if let Some((fingerprint, _)) = xpub.origin {
-                    fingerprint
-                } else {
-                    xpub.xkey.fingerprint()
-                }
-            }
+            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.master_fingerprint(),
             DescriptorPublicKey::Single(ref single) => single.master_fingerprint(),
         }
     }
@@ -784,7 +835,7 @@ impl DescriptorPublicKey {
         match *self {
             DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(),
             DescriptorPublicKey::Single(ref single) => single.full_derivation_path(),
-            DescriptorPublicKey::MultiXPub(_) => None,
+            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.full_derivation_path(),
         }
     }
 
@@ -799,7 +850,7 @@ impl DescriptorPublicKey {
         match *self {
             DescriptorPublicKey::Single(ref single) => single.has_wildcard(),
             DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(),
-            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.wildcard != Wildcard::None,
+            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.has_wildcard(),
         }
     }
 
@@ -825,7 +876,7 @@ impl DescriptorPublicKey {
         match self {
             DescriptorPublicKey::Single(single) => single.at_derivation_index(index),
             DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index),
-            DescriptorPublicKey::MultiXPub(_) => Err(ConversionError::MultiKey),
+            DescriptorPublicKey::MultiXPub(xpub) => xpub.at_derivation_index(index),
         }
     }
 
@@ -834,7 +885,7 @@ impl DescriptorPublicKey {
         match self {
             DescriptorPublicKey::Single(single) => single.is_multipath(),
             DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(),
-            DescriptorPublicKey::MultiXPub(_) => true,
+            DescriptorPublicKey::MultiXPub(xpub) => xpub.is_multipath(),
         }
     }
 
@@ -1208,9 +1259,7 @@ impl DefiniteDescriptorKey {
         match self.0 {
             DescriptorPublicKey::Single(ref pk) => pk.derive_public_key(secp),
             DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp),
-            DescriptorPublicKey::MultiXPub(_) => {
-                unreachable!("A definite key cannot contain a multipath key.")
-            }
+            DescriptorPublicKey::MultiXPub(ref xpk) => xpk.derive_public_key(secp),
         }
     }
 

From 9c55ad30fedec0af9f7b59180ccbbad1896f7463 Mon Sep 17 00:00:00 2001
From: Scott Robinson <ssr@squareup.com>
Date: Mon, 24 Jul 2023 20:59:23 +1000
Subject: [PATCH 5/6] Simplify DescriptorPublicKey::from_str

---
 src/descriptor/key.rs | 15 ++-------------
 1 file changed, 2 insertions(+), 13 deletions(-)

diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs
index b445739cc..56312d190 100644
--- a/src/descriptor/key.rs
+++ b/src/descriptor/key.rs
@@ -534,19 +534,8 @@ impl FromStr for DescriptorPublicKey {
     type Err = DescriptorKeyParseError;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        // A "raw" public key without any origin is the least we accept.
-        if s.len() < 64 {
-            return Err(DescriptorKeyParseError(
-                "Key too short (<66 char), doesn't match any format",
-            ));
-        }
-
-        let (key_part, origin) = parse_key_origin(s)?;
-
-        if key_part.contains("pub") {
-            let (xpub, derivation_paths, wildcard) =
-                parse_xkey_deriv::<bip32::ExtendedPubKey>(key_part)?;
-            if derivation_paths.len() > 1 {
+        if s.contains("pub") {
+            if s.contains("<") {
                 DescriptorMultiExtendedPublicKey::from_str(s).map(Self::MultiXPub)
             } else {
                 DescriptorExtendedPublicKey::from_str(s).map(Self::XPub)

From b18ba59d03a937f9283db0b5cc84c3a2f456a562 Mon Sep 17 00:00:00 2001
From: Scott Robinson <ssr@squareup.com>
Date: Mon, 24 Jul 2023 21:11:07 +1000
Subject: [PATCH 6/6] Make DescriptorPublicKey a DescriptorKey

---
 src/descriptor/key.rs | 123 +++++++++++++++++++-----------------------
 src/descriptor/mod.rs |   1 +
 2 files changed, 56 insertions(+), 68 deletions(-)

diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs
index 56312d190..c3c304f38 100644
--- a/src/descriptor/key.rs
+++ b/src/descriptor/key.rs
@@ -804,80 +804,18 @@ impl DescriptorKey for DescriptorMultiExtendedPublicKey {
 }
 
 impl DescriptorPublicKey {
-    /// The fingerprint of the master key associated with this key, `0x00000000` if none.
-    pub fn master_fingerprint(&self) -> bip32::Fingerprint {
-        match *self {
-            DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(),
-            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.master_fingerprint(),
-            DescriptorPublicKey::Single(ref single) => single.master_fingerprint(),
-        }
-    }
-
-    /// Full path, from the master key
-    ///
-    /// For wildcard keys this will return the path up to the wildcard, so you
-    /// can get full paths by appending one additional derivation step, according
-    /// to the wildcard type (hardened or normal).
-    ///
-    /// For multipath extended keys, this returns `None`.
-    pub fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
-        match *self {
-            DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(),
-            DescriptorPublicKey::Single(ref single) => single.full_derivation_path(),
-            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.full_derivation_path(),
-        }
-    }
-
     /// Whether or not the key has a wildcard
     #[deprecated(note = "use has_wildcard instead")]
     pub fn is_deriveable(&self) -> bool {
         self.has_wildcard()
     }
 
-    /// Whether or not the key has a wildcard
-    pub fn has_wildcard(&self) -> bool {
-        match *self {
-            DescriptorPublicKey::Single(ref single) => single.has_wildcard(),
-            DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(),
-            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.has_wildcard(),
-        }
-    }
-
     #[deprecated(note = "use at_derivation_index instead")]
     /// Deprecated name for [`Self::at_derivation_index`].
     pub fn derive(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
         self.at_derivation_index(index)
     }
 
-    /// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a
-    /// *definite* key (i.e. one where all the derivation paths are set).
-    ///
-    /// # Returns
-    ///
-    /// - If this key is not an xpub, returns `self`.
-    /// - If this key is an xpub but does not have a wildcard, returns `self`.
-    /// - Otherwise, returns the xpub at derivation `index` (removing the wildcard).
-    ///
-    /// # Errors
-    ///
-    /// - If `index` is hardened.
-    pub fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
-        match self {
-            DescriptorPublicKey::Single(single) => single.at_derivation_index(index),
-            DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index),
-            DescriptorPublicKey::MultiXPub(xpub) => xpub.at_derivation_index(index),
-        }
-    }
-
-    /// Whether or not this key has multiple derivation paths.
-    pub fn is_multipath(&self) -> bool {
-        match self {
-            DescriptorPublicKey::Single(single) => single.is_multipath(),
-            DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(),
-            DescriptorPublicKey::MultiXPub(xpub) => xpub.is_multipath(),
-        }
-    }
-
     /// Get as many keys as derivation paths in this key.
     ///
     /// For raw public key and single-path extended keys it will return the key itself.
@@ -910,6 +848,59 @@ impl DescriptorPublicKey {
     }
 }
 
+impl DescriptorKey for DescriptorPublicKey {
+    fn master_fingerprint(&self) -> bip32::Fingerprint {
+        match *self {
+            DescriptorPublicKey::XPub(ref xpub) => xpub.master_fingerprint(),
+            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.master_fingerprint(),
+            DescriptorPublicKey::Single(ref single) => single.master_fingerprint(),
+        }
+    }
+
+    fn full_derivation_path(&self) -> Option<bip32::DerivationPath> {
+        match *self {
+            DescriptorPublicKey::XPub(ref xpub) => xpub.full_derivation_path(),
+            DescriptorPublicKey::Single(ref single) => single.full_derivation_path(),
+            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.full_derivation_path(),
+        }
+    }
+
+    fn has_wildcard(&self) -> bool {
+        match *self {
+            DescriptorPublicKey::Single(ref single) => single.has_wildcard(),
+            DescriptorPublicKey::XPub(ref xpub) => xpub.has_wildcard(),
+            DescriptorPublicKey::MultiXPub(ref xpub) => xpub.has_wildcard(),
+        }
+    }
+
+    fn at_derivation_index(self, index: u32) -> Result<DefiniteDescriptorKey, ConversionError> {
+        match self {
+            DescriptorPublicKey::Single(single) => single.at_derivation_index(index),
+            DescriptorPublicKey::XPub(xpub) => xpub.at_derivation_index(index),
+            DescriptorPublicKey::MultiXPub(xpub) => xpub.at_derivation_index(index),
+        }
+    }
+
+    fn is_multipath(&self) -> bool {
+        match self {
+            DescriptorPublicKey::Single(single) => single.is_multipath(),
+            DescriptorPublicKey::XPub(xpub) => xpub.is_multipath(),
+            DescriptorPublicKey::MultiXPub(xpub) => xpub.is_multipath(),
+        }
+    }
+
+    fn derive_public_key<C: Verification>(
+        &self,
+        secp: &Secp256k1<C>,
+    ) -> Result<bitcoin::PublicKey, ConversionError> {
+        match self {
+            DescriptorPublicKey::Single(single) => single.derive_public_key(secp),
+            DescriptorPublicKey::XPub(xpub) => xpub.derive_public_key(secp),
+            DescriptorPublicKey::MultiXPub(xpub) => xpub.derive_public_key(secp),
+        }
+    }
+}
+
 impl FromStr for DescriptorSecretKey {
     type Err = DescriptorKeyParseError;
 
@@ -1245,11 +1236,7 @@ impl DefiniteDescriptorKey {
         &self,
         secp: &Secp256k1<C>,
     ) -> Result<bitcoin::PublicKey, ConversionError> {
-        match self.0 {
-            DescriptorPublicKey::Single(ref pk) => pk.derive_public_key(secp),
-            DescriptorPublicKey::XPub(ref xpk) => xpk.derive_public_key(secp),
-            DescriptorPublicKey::MultiXPub(ref xpk) => xpk.derive_public_key(secp),
-        }
+        self.0.derive_public_key(secp)
     }
 
     /// Construct an instance from a descriptor key and a derivation index
@@ -1386,7 +1373,7 @@ mod test {
 
     use super::{
         DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey,
-        MiniscriptKey, Wildcard,
+        DescriptorKey, MiniscriptKey, Wildcard,
     };
     use crate::prelude::*;
 
diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs
index 99d89f381..42b4b3b11 100644
--- a/src/descriptor/mod.rs
+++ b/src/descriptor/mod.rs
@@ -22,6 +22,7 @@ use bitcoin::{secp256k1, Address, Network, Script, ScriptBuf, TxIn, Witness};
 use sync::Arc;
 
 use self::checksum::verify_checksum;
+use self::key::DescriptorKey;
 use crate::miniscript::{Legacy, Miniscript, Segwitv0};
 use crate::prelude::*;
 use crate::{