Skip to content

Commit ce1425e

Browse files
committed
Add Xonly key support for descriptor public keys
1 parent cbd02d4 commit ce1425e

File tree

3 files changed

+98
-50
lines changed

3 files changed

+98
-50
lines changed

examples/xpub_descriptors.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ fn main() {
2828
"wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))",
2929
)
3030
.unwrap()
31-
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx).map(bitcoin::PublicKey::new))
31+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
3232
.unwrap()
3333
.address(bitcoin::Network::Bitcoin).unwrap();
3434

3535
let addr_two = Descriptor::<DescriptorPublicKey>::from_str(
3636
"wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))",
3737
)
3838
.unwrap()
39-
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx).map(bitcoin::PublicKey::new))
39+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
4040
.unwrap()
4141
.address(bitcoin::Network::Bitcoin).unwrap();
4242
let expected = bitcoin::Address::from_str(
@@ -52,7 +52,7 @@ fn main() {
5252
)
5353
.unwrap()
5454
.derive(5)
55-
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx).map(bitcoin::PublicKey::new))
55+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
5656
.unwrap()
5757
.address(bitcoin::Network::Bitcoin).unwrap();
5858

@@ -61,7 +61,7 @@ fn main() {
6161
)
6262
.unwrap()
6363
.derive(5)
64-
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx).map(bitcoin::PublicKey::new))
64+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
6565
.unwrap()
6666
.address(bitcoin::Network::Bitcoin).unwrap();
6767
let expected = bitcoin::Address::from_str("325zcVBN5o2eqqqtGwPjmtDd8dJRyYP82s").unwrap();

src/descriptor/key.rs

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,25 @@ use std::{error, fmt, str::FromStr};
22

33
use bitcoin::{
44
self,
5-
hashes::hex::FromHex,
65
hashes::Hash,
6+
hashes::{hex::FromHex, HashEngine},
7+
schnorr::XOnlyPublicKey,
78
secp256k1,
89
secp256k1::{Secp256k1, Signing},
910
util::bip32,
1011
XpubIdentifier,
1112
};
1213

13-
use MiniscriptKey;
14+
use {MiniscriptKey, ToPublicKey};
15+
16+
/// Single public key without any origin or range information
17+
#[derive(Debug, Eq, PartialEq, Clone, Ord, PartialOrd, Hash)]
18+
pub enum SinglePubKey {
19+
/// FullKey (compressed or uncompressed)
20+
FullKey(bitcoin::PublicKey),
21+
/// XOnlyPublicKey
22+
XOnly(XOnlyPublicKey),
23+
}
1424

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

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

139149
Ok(DescriptorSinglePub {
140150
origin: self.origin.clone(),
141-
key: pub_key,
151+
key: SinglePubKey::FullKey(pub_key),
142152
})
143153
}
144154
}
@@ -212,7 +222,10 @@ impl fmt::Display for DescriptorPublicKey {
212222
match *self {
213223
DescriptorPublicKey::SinglePub(ref pk) => {
214224
maybe_fmt_master_id(f, &pk.origin)?;
215-
pk.key.fmt(f)?;
225+
match pk.key {
226+
SinglePubKey::FullKey(full_key) => full_key.fmt(f),
227+
SinglePubKey::XOnly(x_only_key) => x_only_key.fmt(f),
228+
}?;
216229
Ok(())
217230
}
218231
DescriptorPublicKey::XPub(ref xpub) => {
@@ -283,7 +296,7 @@ impl FromStr for DescriptorPublicKey {
283296

284297
fn from_str(s: &str) -> Result<Self, Self::Err> {
285298
// A "raw" public key without any origin is the least we accept.
286-
if s.len() < 66 {
299+
if s.len() < 64 {
287300
return Err(DescriptorKeyParseError(
288301
"Key too short (<66 char), doesn't match any format",
289302
));
@@ -302,15 +315,33 @@ impl FromStr for DescriptorPublicKey {
302315
wildcard,
303316
}))
304317
} else {
305-
if key_part.len() >= 2
306-
&& !(&key_part[0..2] == "02" || &key_part[0..2] == "03" || &key_part[0..2] == "04")
307-
{
308-
return Err(DescriptorKeyParseError(
309-
"Only publickeys with prefixes 02/03/04 are allowed",
310-
));
311-
}
312-
let key = bitcoin::PublicKey::from_str(key_part)
313-
.map_err(|_| DescriptorKeyParseError("Error while parsing simple public key"))?;
318+
let key = match key_part.len() {
319+
64 => {
320+
let x_only_key = XOnlyPublicKey::from_str(key_part).map_err(|_| {
321+
DescriptorKeyParseError("Error while parsing simple xonly key")
322+
})?;
323+
SinglePubKey::XOnly(x_only_key)
324+
}
325+
66 | 130 => {
326+
if !(&key_part[0..2] == "02"
327+
|| &key_part[0..2] == "03"
328+
|| &key_part[0..2] == "04")
329+
{
330+
return Err(DescriptorKeyParseError(
331+
"Only publickeys with prefixes 02/03/04 are allowed",
332+
));
333+
}
334+
let key = bitcoin::PublicKey::from_str(key_part).map_err(|_| {
335+
DescriptorKeyParseError("Error while parsing simple public key")
336+
})?;
337+
SinglePubKey::FullKey(key)
338+
}
339+
_ => {
340+
return Err(DescriptorKeyParseError(
341+
"Public keys must be 64/66/130 characters in size",
342+
))
343+
}
344+
};
314345
Ok(DescriptorPublicKey::SinglePub(DescriptorSinglePub {
315346
key,
316347
origin,
@@ -360,10 +391,12 @@ impl DescriptorPublicKey {
360391
fingerprint
361392
} else {
362393
let mut engine = XpubIdentifier::engine();
363-
single
364-
.key
365-
.write_into(&mut engine)
366-
.expect("engines don't error");
394+
match single.key {
395+
SinglePubKey::FullKey(pk) => {
396+
pk.write_into(&mut engine).expect("engines don't error")
397+
}
398+
SinglePubKey::XOnly(x_only_pk) => engine.input(&x_only_pk.serialize()),
399+
};
367400
bip32::Fingerprint::from(&XpubIdentifier::from_engine(engine)[..4])
368401
}
369402
}
@@ -427,7 +460,10 @@ impl DescriptorPublicKey {
427460
self
428461
}
429462

430-
/// Computes the public key corresponding to this descriptor key
463+
/// Computes the public key corresponding to this descriptor key.
464+
/// When deriving from an XOnlyPublicKey, it adds the default 0x02 y-ordinate
465+
/// and returns the obtained full [`bitcoin::PublicKey`]. All BIP32 derivations
466+
/// always return a compressed key
431467
///
432468
/// Will return an error if the descriptor key has any hardened
433469
/// derivation steps in its path, or if the key has any wildcards.
@@ -439,14 +475,17 @@ impl DescriptorPublicKey {
439475
pub fn derive_public_key<C: secp256k1::Verification>(
440476
&self,
441477
secp: &Secp256k1<C>,
442-
) -> Result<secp256k1::PublicKey, ConversionError> {
478+
) -> Result<bitcoin::PublicKey, ConversionError> {
443479
match *self {
444-
DescriptorPublicKey::SinglePub(ref pk) => Ok(pk.key.key),
480+
DescriptorPublicKey::SinglePub(ref pk) => match pk.key {
481+
SinglePubKey::FullKey(pk) => Ok(pk),
482+
SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()),
483+
},
445484
DescriptorPublicKey::XPub(ref xpk) => match xpk.wildcard {
446485
Wildcard::Unhardened => Err(ConversionError::Wildcard),
447486
Wildcard::Hardened => Err(ConversionError::HardenedWildcard),
448487
Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) {
449-
Ok(xpub) => Ok(xpub.public_key),
488+
Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)),
450489
Err(bip32::Error::CannotDeriveFromHardenedKey) => {
451490
Err(ConversionError::HardenedChild)
452491
}
@@ -656,9 +695,20 @@ impl MiniscriptKey for DescriptorPublicKey {
656695

657696
fn is_uncompressed(&self) -> bool {
658697
match self {
659-
DescriptorPublicKey::SinglePub(DescriptorSinglePub { ref key, .. }) => {
660-
key.is_uncompressed()
661-
}
698+
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
699+
key: SinglePubKey::FullKey(ref key),
700+
..
701+
}) => key.is_uncompressed(),
702+
_ => false,
703+
}
704+
}
705+
706+
fn is_x_only_key(&self) -> bool {
707+
match self {
708+
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
709+
key: SinglePubKey::FullKey(ref key),
710+
..
711+
}) => key.is_x_only_key(),
662712
_ => false,
663713
}
664714
}
@@ -708,7 +758,7 @@ mod test {
708758
assert_eq!(
709759
DescriptorPublicKey::from_str(desc),
710760
Err(DescriptorKeyParseError(
711-
"Error while parsing simple public key"
761+
"Public keys must be 64/66/130 characters in size"
712762
))
713763
);
714764

src/descriptor/mod.rs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ mod key;
5959

6060
pub use self::key::{
6161
ConversionError, DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey,
62-
DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, Wildcard,
62+
DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, SinglePubKey, Wildcard,
6363
};
6464

6565
/// Alias type for a map of public key to secret key
@@ -1392,10 +1392,12 @@ mod tests {
13921392
// Raw (compressed) pubkey
13931393
let key = "03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8";
13941394
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
1395-
key: bitcoin::PublicKey::from_str(
1396-
"03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8",
1397-
)
1398-
.unwrap(),
1395+
key: SinglePubKey::FullKey(
1396+
bitcoin::PublicKey::from_str(
1397+
"03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8",
1398+
)
1399+
.unwrap(),
1400+
),
13991401
origin: None,
14001402
});
14011403
assert_eq!(expected, key.parse().unwrap());
@@ -1404,10 +1406,10 @@ mod tests {
14041406
// Raw (uncompressed) pubkey
14051407
let key = "04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a";
14061408
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
1407-
key: bitcoin::PublicKey::from_str(
1409+
key: SinglePubKey::FullKey(bitcoin::PublicKey::from_str(
14081410
"04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a",
14091411
)
1410-
.unwrap(),
1412+
.unwrap()),
14111413
origin: None,
14121414
});
14131415
assert_eq!(expected, key.parse().unwrap());
@@ -1417,10 +1419,12 @@ mod tests {
14171419
let desc =
14181420
"[78412e3a/0'/42/0']0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8";
14191421
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
1420-
key: bitcoin::PublicKey::from_str(
1421-
"0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8",
1422-
)
1423-
.unwrap(),
1422+
key: SinglePubKey::FullKey(
1423+
bitcoin::PublicKey::from_str(
1424+
"0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8",
1425+
)
1426+
.unwrap(),
1427+
),
14241428
origin: Some((
14251429
bip32::Fingerprint::from(&[0x78, 0x41, 0x2e, 0x3a][..]),
14261430
(&[
@@ -1457,18 +1461,12 @@ mod tests {
14571461

14581462
// Same address
14591463
let addr_one = desc_one
1460-
.translate_pk2(|xpk| {
1461-
xpk.derive_public_key(&secp_ctx)
1462-
.map(bitcoin::PublicKey::new)
1463-
})
1464+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
14641465
.unwrap()
14651466
.address(bitcoin::Network::Bitcoin)
14661467
.unwrap();
14671468
let addr_two = desc_two
1468-
.translate_pk2(|xpk| {
1469-
xpk.derive_public_key(&secp_ctx)
1470-
.map(bitcoin::PublicKey::new)
1471-
})
1469+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
14721470
.unwrap()
14731471
.address(bitcoin::Network::Bitcoin)
14741472
.unwrap();

0 commit comments

Comments
 (0)