Skip to content

Add support for x-only keys in descriptor #298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions examples/xpub_descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ fn main() {
"wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))",
)
.unwrap()
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx).map(bitcoin::PublicKey::new))
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
.unwrap()
.address(bitcoin::Network::Bitcoin).unwrap();

let addr_two = Descriptor::<DescriptorPublicKey>::from_str(
"wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))",
)
.unwrap()
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx).map(bitcoin::PublicKey::new))
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
.unwrap()
.address(bitcoin::Network::Bitcoin).unwrap();
let expected = bitcoin::Address::from_str(
Expand All @@ -52,7 +52,7 @@ fn main() {
)
.unwrap()
.derive(5)
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx).map(bitcoin::PublicKey::new))
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
.unwrap()
.address(bitcoin::Network::Bitcoin).unwrap();

Expand All @@ -61,7 +61,7 @@ fn main() {
)
.unwrap()
.derive(5)
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx).map(bitcoin::PublicKey::new))
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
.unwrap()
.address(bitcoin::Network::Bitcoin).unwrap();
let expected = bitcoin::Address::from_str("325zcVBN5o2eqqqtGwPjmtDd8dJRyYP82s").unwrap();
Expand Down
104 changes: 77 additions & 27 deletions src/descriptor/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ use std::{error, fmt, str::FromStr};

use bitcoin::{
self,
hashes::hex::FromHex,
hashes::Hash,
hashes::{hex::FromHex, HashEngine},
schnorr::XOnlyPublicKey,
secp256k1,
secp256k1::{Secp256k1, Signing},
util::bip32,
XpubIdentifier,
};

use MiniscriptKey;
use {MiniscriptKey, ToPublicKey};

/// Single public key without any origin or range information
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
pub enum SinglePubKey {
/// FullKey (compressed or uncompressed)
FullKey(bitcoin::PublicKey),
/// XOnlyPublicKey
XOnly(XOnlyPublicKey),
}

/// The MiniscriptKey corresponding to Descriptors. This can
/// either be Single public key or a Xpub
Expand All @@ -28,7 +38,7 @@ pub struct DescriptorSinglePub {
/// Origin information
pub origin: Option<(bip32::Fingerprint, bip32::DerivationPath)>,
/// The key
pub key: bitcoin::PublicKey,
pub key: SinglePubKey,
}

/// A Single Descriptor Secret Key with optional origin information
Expand Down Expand Up @@ -138,7 +148,7 @@ impl DescriptorSinglePriv {

Ok(DescriptorSinglePub {
origin: self.origin.clone(),
key: pub_key,
key: SinglePubKey::FullKey(pub_key),
})
}
}
Expand Down Expand Up @@ -212,7 +222,10 @@ impl fmt::Display for DescriptorPublicKey {
match *self {
DescriptorPublicKey::SinglePub(ref pk) => {
maybe_fmt_master_id(f, &pk.origin)?;
pk.key.fmt(f)?;
match pk.key {
SinglePubKey::FullKey(full_key) => full_key.fmt(f),
SinglePubKey::XOnly(x_only_key) => x_only_key.fmt(f),
}?;
Ok(())
}
DescriptorPublicKey::XPub(ref xpub) => {
Expand Down Expand Up @@ -283,7 +296,7 @@ impl FromStr for DescriptorPublicKey {

fn from_str(s: &str) -> Result<Self, Self::Err> {
// A "raw" public key without any origin is the least we accept.
if s.len() < 66 {
if s.len() < 64 {
return Err(DescriptorKeyParseError(
"Key too short (<66 char), doesn't match any format",
));
Expand All @@ -302,15 +315,33 @@ impl FromStr for DescriptorPublicKey {
wildcard,
}))
} else {
if key_part.len() >= 2
&& !(&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"))?;
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::SinglePub(DescriptorSinglePub {
key,
origin,
Expand Down Expand Up @@ -360,10 +391,12 @@ impl DescriptorPublicKey {
fingerprint
} else {
let mut engine = XpubIdentifier::engine();
single
.key
.write_into(&mut engine)
.expect("engines don't error");
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])
}
}
Expand Down Expand Up @@ -427,7 +460,10 @@ impl DescriptorPublicKey {
self
}

/// Computes the public key corresponding to this descriptor key
/// 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, or if the key has any wildcards.
Expand All @@ -439,14 +475,17 @@ impl DescriptorPublicKey {
pub fn derive_public_key<C: secp256k1::Verification>(
&self,
secp: &Secp256k1<C>,
) -> Result<secp256k1::PublicKey, ConversionError> {
) -> Result<bitcoin::PublicKey, ConversionError> {
match *self {
DescriptorPublicKey::SinglePub(ref pk) => Ok(pk.key.key),
DescriptorPublicKey::SinglePub(ref pk) => match pk.key {
SinglePubKey::FullKey(pk) => Ok(pk),
SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()),
},
DescriptorPublicKey::XPub(ref xpk) => match xpk.wildcard {
Wildcard::Unhardened => Err(ConversionError::Wildcard),
Wildcard::Hardened => Err(ConversionError::HardenedWildcard),
Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) {
Ok(xpub) => Ok(xpub.public_key),
Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)),
Err(bip32::Error::CannotDeriveFromHardenedKey) => {
Err(ConversionError::HardenedChild)
}
Expand Down Expand Up @@ -656,9 +695,20 @@ impl MiniscriptKey for DescriptorPublicKey {

fn is_uncompressed(&self) -> bool {
match self {
DescriptorPublicKey::SinglePub(DescriptorSinglePub { ref key, .. }) => {
key.is_uncompressed()
}
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(ref key),
..
}) => key.is_uncompressed(),
_ => false,
}
}

fn is_x_only_key(&self) -> bool {
match self {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(ref key),
..
}) => key.is_x_only_key(),
_ => false,
}
}
Expand Down Expand Up @@ -708,7 +758,7 @@ mod test {
assert_eq!(
DescriptorPublicKey::from_str(desc),
Err(DescriptorKeyParseError(
"Error while parsing simple public key"
"Public keys must be 64/66/130 characters in size"
))
);

Expand Down
74 changes: 55 additions & 19 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ mod key;

pub use self::key::{
ConversionError, DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey,
DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, Wildcard,
DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, SinglePubKey, Wildcard,
};

/// Alias type for a map of public key to secret key
Expand Down Expand Up @@ -557,10 +557,48 @@ impl Descriptor<DescriptorPublicKey> {
/// Derives all wildcard keys in the descriptor using the supplied index
///
/// Panics if given an index ≥ 2^31
///
/// In most cases, you would want to use [`Self::derived_descriptor`] directly to obtain
/// a [`Descriptor<bitcoin::PublicKey>`]
pub fn derive(&self, index: u32) -> Descriptor<DescriptorPublicKey> {
self.translate_pk2_infallible(|pk| pk.clone().derive(index))
}

/// Derive a [`Descriptor`] with a concrete [`bitcoin::PublicKey`] at a given index
/// Removes all extended pubkeys and wildcards from the descriptor and only leaves
/// concrete [`bitcoin::PublicKey`]. All [`crate::XOnlyKey`]s are converted to [`bitcoin::PublicKey`]
/// by adding a default(0x02) y-coordinate. For [`crate::descriptor::Tr`] descriptor,
/// spend info is also cached.
///
/// # Examples
///
/// ```
/// use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
/// use miniscript::bitcoin::secp256k1;
/// use std::str::FromStr;
///
/// // test from bip 86
/// let secp = secp256k1::Secp256k1::verification_only();
/// let descriptor = Descriptor::<DescriptorPublicKey>::from_str("tr(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)")
/// .expect("Valid ranged descriptor");
/// let result = descriptor.derived_descriptor(0, &secp).expect("Non-hardened derivation");
/// assert_eq!(result.to_string(), "tr(03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115)#6qm9h8ym");
/// ```
///
/// # Errors
///
/// This function will return an error if hardened derivation is attempted.
pub fn derived_descriptor<C: secp256k1::Verification>(
&self,
index: u32,
secp: &secp256k1::Secp256k1<C>,
) -> Result<Descriptor<bitcoin::PublicKey>, ConversionError> {
let derived = self
.derive(index)
.translate_pk2(|xpk| xpk.derive_public_key(secp))?;
Ok(derived)
}

/// Parse a descriptor that may contain secret keys
///
/// Internally turns every secret key found into the corresponding public key and then returns a
Expand Down Expand Up @@ -1392,10 +1430,12 @@ mod tests {
// Raw (compressed) pubkey
let key = "03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8";
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: bitcoin::PublicKey::from_str(
"03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8",
)
.unwrap(),
key: SinglePubKey::FullKey(
bitcoin::PublicKey::from_str(
"03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8",
)
.unwrap(),
),
origin: None,
});
assert_eq!(expected, key.parse().unwrap());
Expand All @@ -1404,10 +1444,10 @@ mod tests {
// Raw (uncompressed) pubkey
let key = "04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a";
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: bitcoin::PublicKey::from_str(
key: SinglePubKey::FullKey(bitcoin::PublicKey::from_str(
"04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a",
)
.unwrap(),
.unwrap()),
origin: None,
});
assert_eq!(expected, key.parse().unwrap());
Expand All @@ -1417,10 +1457,12 @@ mod tests {
let desc =
"[78412e3a/0'/42/0']0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8";
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: bitcoin::PublicKey::from_str(
"0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8",
)
.unwrap(),
key: SinglePubKey::FullKey(
bitcoin::PublicKey::from_str(
"0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8",
)
.unwrap(),
),
origin: Some((
bip32::Fingerprint::from(&[0x78, 0x41, 0x2e, 0x3a][..]),
(&[
Expand Down Expand Up @@ -1457,18 +1499,12 @@ mod tests {

// Same address
let addr_one = desc_one
.translate_pk2(|xpk| {
xpk.derive_public_key(&secp_ctx)
.map(bitcoin::PublicKey::new)
})
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
.unwrap()
.address(bitcoin::Network::Bitcoin)
.unwrap();
let addr_two = desc_two
.translate_pk2(|xpk| {
xpk.derive_public_key(&secp_ctx)
.map(bitcoin::PublicKey::new)
})
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
.unwrap()
.address(bitcoin::Network::Bitcoin)
.unwrap();
Expand Down
14 changes: 14 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Ha
fn is_uncompressed(&self) -> bool {
false
}

/// Check if the publicKey is x-only. The default
/// implementation returns false
//
// This is required to know what in DescriptorPublicKey to know whether the inner
// key in allowed in descriptor context
fn is_x_only_key(&self) -> bool {
false
}

/// The associated Hash type with the publicKey
type Hash: Clone + Eq + Ord + fmt::Display + fmt::Debug + hash::Hash;

Expand Down Expand Up @@ -165,6 +175,10 @@ impl MiniscriptKey for bitcoin::secp256k1::XOnlyPublicKey {
fn to_pubkeyhash(&self) -> Self::Hash {
hash160::Hash::hash(&self.serialize())
}

fn is_x_only_key(&self) -> bool {
true
}
}

impl MiniscriptKey for String {
Expand Down
Loading