Skip to content

Commit 6a89bd0

Browse files
committed
Merge #298: Add support for x-only keys in descriptor
602e94f Add simple API for derivation (sanket1729) 4763870 Add Xonly key support for descriptor public keys (sanket1729) ff5a4bf Add MiniscriptKey::is_xonly (sanket1729) Pull request description: . ACKs for top commit: apoelstra: ACK 602e94f Tree-SHA512: 3489a0474b4991298f590aaea554bd68d82ce1913e59a08dbdb645878937d2cc5c89f8f7ba66397bf21c75747c74507e33298c01985b760990da97c706252707
2 parents fa4eefd + 602e94f commit 6a89bd0

File tree

5 files changed

+203
-53
lines changed

5 files changed

+203
-53
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-coordinate
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: 55 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
@@ -557,10 +557,48 @@ impl Descriptor<DescriptorPublicKey> {
557557
/// Derives all wildcard keys in the descriptor using the supplied index
558558
///
559559
/// Panics if given an index ≥ 2^31
560+
///
561+
/// In most cases, you would want to use [`Self::derived_descriptor`] directly to obtain
562+
/// a [`Descriptor<bitcoin::PublicKey>`]
560563
pub fn derive(&self, index: u32) -> Descriptor<DescriptorPublicKey> {
561564
self.translate_pk2_infallible(|pk| pk.clone().derive(index))
562565
}
563566

567+
/// Derive a [`Descriptor`] with a concrete [`bitcoin::PublicKey`] at a given index
568+
/// Removes all extended pubkeys and wildcards from the descriptor and only leaves
569+
/// concrete [`bitcoin::PublicKey`]. All [`crate::XOnlyKey`]s are converted to [`bitcoin::PublicKey`]
570+
/// by adding a default(0x02) y-coordinate. For [`crate::descriptor::Tr`] descriptor,
571+
/// spend info is also cached.
572+
///
573+
/// # Examples
574+
///
575+
/// ```
576+
/// use miniscript::descriptor::{Descriptor, DescriptorPublicKey};
577+
/// use miniscript::bitcoin::secp256k1;
578+
/// use std::str::FromStr;
579+
///
580+
/// // test from bip 86
581+
/// let secp = secp256k1::Secp256k1::verification_only();
582+
/// let descriptor = Descriptor::<DescriptorPublicKey>::from_str("tr(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)")
583+
/// .expect("Valid ranged descriptor");
584+
/// let result = descriptor.derived_descriptor(0, &secp).expect("Non-hardened derivation");
585+
/// assert_eq!(result.to_string(), "tr(03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115)#6qm9h8ym");
586+
/// ```
587+
///
588+
/// # Errors
589+
///
590+
/// This function will return an error if hardened derivation is attempted.
591+
pub fn derived_descriptor<C: secp256k1::Verification>(
592+
&self,
593+
index: u32,
594+
secp: &secp256k1::Secp256k1<C>,
595+
) -> Result<Descriptor<bitcoin::PublicKey>, ConversionError> {
596+
let derived = self
597+
.derive(index)
598+
.translate_pk2(|xpk| xpk.derive_public_key(secp))?;
599+
Ok(derived)
600+
}
601+
564602
/// Parse a descriptor that may contain secret keys
565603
///
566604
/// Internally turns every secret key found into the corresponding public key and then returns a
@@ -1392,10 +1430,12 @@ mod tests {
13921430
// Raw (compressed) pubkey
13931431
let key = "03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8";
13941432
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
1395-
key: bitcoin::PublicKey::from_str(
1396-
"03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8",
1397-
)
1398-
.unwrap(),
1433+
key: SinglePubKey::FullKey(
1434+
bitcoin::PublicKey::from_str(
1435+
"03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8",
1436+
)
1437+
.unwrap(),
1438+
),
13991439
origin: None,
14001440
});
14011441
assert_eq!(expected, key.parse().unwrap());
@@ -1404,10 +1444,10 @@ mod tests {
14041444
// Raw (uncompressed) pubkey
14051445
let key = "04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a";
14061446
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
1407-
key: bitcoin::PublicKey::from_str(
1447+
key: SinglePubKey::FullKey(bitcoin::PublicKey::from_str(
14081448
"04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a",
14091449
)
1410-
.unwrap(),
1450+
.unwrap()),
14111451
origin: None,
14121452
});
14131453
assert_eq!(expected, key.parse().unwrap());
@@ -1417,10 +1457,12 @@ mod tests {
14171457
let desc =
14181458
"[78412e3a/0'/42/0']0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8";
14191459
let expected = DescriptorPublicKey::SinglePub(DescriptorSinglePub {
1420-
key: bitcoin::PublicKey::from_str(
1421-
"0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8",
1422-
)
1423-
.unwrap(),
1460+
key: SinglePubKey::FullKey(
1461+
bitcoin::PublicKey::from_str(
1462+
"0231c7d3fc85c148717848033ce276ae2b464a4e2c367ed33886cc428b8af48ff8",
1463+
)
1464+
.unwrap(),
1465+
),
14241466
origin: Some((
14251467
bip32::Fingerprint::from(&[0x78, 0x41, 0x2e, 0x3a][..]),
14261468
(&[
@@ -1457,18 +1499,12 @@ mod tests {
14571499

14581500
// Same address
14591501
let addr_one = desc_one
1460-
.translate_pk2(|xpk| {
1461-
xpk.derive_public_key(&secp_ctx)
1462-
.map(bitcoin::PublicKey::new)
1463-
})
1502+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
14641503
.unwrap()
14651504
.address(bitcoin::Network::Bitcoin)
14661505
.unwrap();
14671506
let addr_two = desc_two
1468-
.translate_pk2(|xpk| {
1469-
xpk.derive_public_key(&secp_ctx)
1470-
.map(bitcoin::PublicKey::new)
1471-
})
1507+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
14721508
.unwrap()
14731509
.address(bitcoin::Network::Bitcoin)
14741510
.unwrap();

src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Ha
136136
fn is_uncompressed(&self) -> bool {
137137
false
138138
}
139+
140+
/// Check if the publicKey is x-only. The default
141+
/// implementation returns false
142+
//
143+
// This is required to know what in DescriptorPublicKey to know whether the inner
144+
// key in allowed in descriptor context
145+
fn is_x_only_key(&self) -> bool {
146+
false
147+
}
148+
139149
/// The associated Hash type with the publicKey
140150
type Hash: Clone + Eq + Ord + fmt::Display + fmt::Debug + hash::Hash;
141151

@@ -165,6 +175,10 @@ impl MiniscriptKey for bitcoin::secp256k1::XOnlyPublicKey {
165175
fn to_pubkeyhash(&self) -> Self::Hash {
166176
hash160::Hash::hash(&self.serialize())
167177
}
178+
179+
fn is_x_only_key(&self) -> bool {
180+
true
181+
}
168182
}
169183

170184
impl MiniscriptKey for String {

0 commit comments

Comments
 (0)