Skip to content

Commit 35baa97

Browse files
committed
descriptor: replace ToPublicKey on DescriptorPublicKey with fallible method
1 parent f0a9c1e commit 35baa97

File tree

3 files changed

+78
-45
lines changed

3 files changed

+78
-45
lines changed

examples/xpub_descriptors.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,29 @@
1717
extern crate miniscript;
1818

1919
use miniscript::bitcoin::{self, secp256k1};
20-
use miniscript::{Descriptor, DescriptorPublicKey, DescriptorPublicKeyCtx, DescriptorTrait};
20+
use miniscript::descriptor::PkTranslate2;
21+
use miniscript::{Descriptor, DescriptorPublicKey, DescriptorTrait, NullCtx};
2122

2223
use std::str::FromStr;
2324
fn main() {
2425
// For deriving from descriptors, we need to provide a secp context
2526
let secp_ctx = secp256k1::Secp256k1::verification_only();
26-
// Child number to derive public key
27-
// This is used only when xpub is ranged
28-
let index = 0;
29-
let desc_ctx = DescriptorPublicKeyCtx::new(&secp_ctx, index);
3027
// P2WSH and single xpubs
3128
let addr_one = Descriptor::<DescriptorPublicKey>::from_str(
3229
"wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH))",
3330
)
3431
.unwrap()
35-
.address(desc_ctx, bitcoin::Network::Bitcoin).unwrap();
32+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
33+
.unwrap()
34+
.address(NullCtx, bitcoin::Network::Bitcoin).unwrap();
3635

3736
let addr_two = Descriptor::<DescriptorPublicKey>::from_str(
3837
"wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB))",
3938
)
4039
.unwrap()
41-
.address(desc_ctx, bitcoin::Network::Bitcoin).unwrap();
40+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
41+
.unwrap()
42+
.address(NullCtx, bitcoin::Network::Bitcoin).unwrap();
4243
let expected = bitcoin::Address::from_str(
4344
"bc1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcspx5y6a",
4445
)
@@ -52,14 +53,18 @@ fn main() {
5253
)
5354
.unwrap()
5455
.derive(5)
55-
.address(desc_ctx, bitcoin::Network::Bitcoin).unwrap();
56+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
57+
.unwrap()
58+
.address(NullCtx, bitcoin::Network::Bitcoin).unwrap();
5659

5760
let addr_two = Descriptor::<DescriptorPublicKey>::from_str(
5861
"sh(wsh(sortedmulti(1,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*)))",
5962
)
6063
.unwrap()
6164
.derive(5)
62-
.address(desc_ctx, bitcoin::Network::Bitcoin).unwrap();
65+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
66+
.unwrap()
67+
.address(NullCtx, bitcoin::Network::Bitcoin).unwrap();
6368
let expected = bitcoin::Address::from_str("325zcVBN5o2eqqqtGwPjmtDd8dJRyYP82s").unwrap();
6469
assert_eq!(addr_one, expected);
6570
assert_eq!(addr_two, expected);

src/descriptor/key.rs

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

33
use bitcoin::{
44
self,
5-
hashes::{hash160, hex::FromHex},
5+
hashes::hex::FromHex,
66
secp256k1,
77
secp256k1::{Secp256k1, Signing},
88
util::bip32,
99
};
1010

1111
use MiniscriptKey;
12-
use NullCtx;
13-
use ToPublicKey;
1412

1513
/// The MiniscriptKey corresponding to Descriptors. This can
1614
/// either be Single public key or a Xpub
@@ -319,10 +317,35 @@ impl FromStr for DescriptorPublicKey {
319317
}
320318
}
321319

320+
/// Descriptor key conversion error
321+
#[derive(Debug, PartialEq, Clone, Copy)]
322+
pub enum ConversionError {
323+
/// Attempted to convert a key with a wildcard to a bitcoin public key
324+
Wildcard,
325+
/// Attempted to convert a key with hardened derivations to a bitcoin public key
326+
HardenedChild,
327+
/// Attempted to convert a key with a hardened wildcard to a bitcoin public key
328+
HardenedWildcard,
329+
}
330+
331+
impl fmt::Display for ConversionError {
332+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
333+
f.write_str(match *self {
334+
ConversionError::Wildcard => "uninstantiated wildcard in bip32 path",
335+
ConversionError::HardenedChild => "hardened child step in bip32 path",
336+
ConversionError::HardenedWildcard => {
337+
"hardened and uninstantiated wildcard in bip32 path"
338+
}
339+
})
340+
}
341+
}
342+
343+
impl error::Error for ConversionError {}
344+
322345
impl DescriptorPublicKey {
323-
/// Derives the specified child key if self is a wildcard xpub. Otherwise returns self.
346+
/// If this public key has a wildcard, replace it by the given index
324347
///
325-
/// Panics if given a child number ≥ 2^31
348+
/// Panics if given an index ≥ 2^31
326349
pub fn derive(mut self, index: u32) -> DescriptorPublicKey {
327350
if let DescriptorPublicKey::XPub(mut xpub) = self {
328351
match xpub.is_wildcard {
@@ -343,6 +366,35 @@ impl DescriptorPublicKey {
343366
}
344367
self
345368
}
369+
370+
/// Computes the public key corresponding to this descriptor key
371+
///
372+
/// Will return an error if the descriptor key has any hardened
373+
/// derivation steps in its path, or if the key has any wildcards.
374+
///
375+
/// To ensure there are no wildcards, call `.derive(0)` or similar;
376+
/// to avoid hardened derivation steps, start from a `DescriptorSecretKey`
377+
/// and call `as_public`, or call `TranslatePk2::translate_pk2` with
378+
/// some function which has access to secret key data.
379+
pub fn derive_public_key<C: secp256k1::Verification>(
380+
&self,
381+
secp: &Secp256k1<C>,
382+
) -> Result<bitcoin::PublicKey, ConversionError> {
383+
match *self {
384+
DescriptorPublicKey::SinglePub(ref pk) => Ok(pk.key),
385+
DescriptorPublicKey::XPub(ref xpk) => match xpk.is_wildcard {
386+
Wildcard::Unhardened => Err(ConversionError::Wildcard),
387+
Wildcard::Hardened => Err(ConversionError::HardenedWildcard),
388+
Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) {
389+
Ok(xpub) => Ok(xpub.public_key),
390+
Err(bip32::Error::CannotDeriveFromHardenedKey) => {
391+
Err(ConversionError::HardenedChild)
392+
}
393+
Err(e) => unreachable!("cryptographically unreachable: {}", e),
394+
},
395+
},
396+
}
397+
}
346398
}
347399

348400
impl FromStr for DescriptorSecretKey {
@@ -584,32 +636,6 @@ impl<'secp, C: secp256k1::Verification> DescriptorPublicKeyCtx<'secp, C> {
584636
}
585637
}
586638

587-
impl<'secp, C: secp256k1::Verification> ToPublicKey<DescriptorPublicKeyCtx<'secp, C>>
588-
for DescriptorPublicKey
589-
{
590-
fn to_public_key(&self, to_pk_ctx: DescriptorPublicKeyCtx<'secp, C>) -> bitcoin::PublicKey {
591-
let xpub = self.clone().derive(to_pk_ctx.index);
592-
match xpub {
593-
DescriptorPublicKey::SinglePub(ref spub) => spub.key.to_public_key(NullCtx),
594-
DescriptorPublicKey::XPub(ref xpub) => {
595-
// derives if wildcard, otherwise returns self
596-
debug_assert!(xpub.is_wildcard == Wildcard::None);
597-
xpub.xkey
598-
.derive_pub(to_pk_ctx.secp_ctx, &xpub.derivation_path)
599-
.expect("Shouldn't fail, only normal derivations")
600-
.public_key
601-
}
602-
}
603-
}
604-
605-
fn hash_to_hash160(
606-
hash: &Self::Hash,
607-
to_pk_ctx: DescriptorPublicKeyCtx<'secp, C>,
608-
) -> hash160::Hash {
609-
hash.to_public_key(to_pk_ctx).to_pubkeyhash()
610-
}
611-
}
612-
613639
#[cfg(test)]
614640
mod test {
615641
use super::{DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey};

src/descriptor/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -785,8 +785,7 @@ serde_string_impl_pk!(Descriptor, "a script descriptor");
785785
#[cfg(test)]
786786
mod tests {
787787
use super::checksum::desc_checksum;
788-
use super::DescriptorPublicKeyCtx;
789-
use super::DescriptorTrait;
788+
use super::{DescriptorTrait, PkTranslate2};
790789
use bitcoin::blockdata::opcodes::all::{OP_CLTV, OP_CSV};
791790
use bitcoin::blockdata::script::Instruction;
792791
use bitcoin::blockdata::{opcodes, script};
@@ -1487,7 +1486,6 @@ mod tests {
14871486
fn _test_sortedmulti(raw_desc_one: &str, raw_desc_two: &str, raw_addr_expected: &str) {
14881487
let secp_ctx = secp256k1::Secp256k1::verification_only();
14891488
let index = 5;
1490-
let desc_ctx = DescriptorPublicKeyCtx::new(&secp_ctx, index);
14911489

14921490
// Parse descriptor
14931491
let mut desc_one = Descriptor::<DescriptorPublicKey>::from_str(raw_desc_one).unwrap();
@@ -1505,10 +1503,14 @@ mod tests {
15051503

15061504
// Same address
15071505
let addr_one = desc_one
1508-
.address(desc_ctx, bitcoin::Network::Bitcoin)
1506+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
1507+
.unwrap()
1508+
.address(NullCtx, bitcoin::Network::Bitcoin)
15091509
.unwrap();
15101510
let addr_two = desc_two
1511-
.address(desc_ctx, bitcoin::Network::Bitcoin)
1511+
.translate_pk2(|xpk| xpk.derive_public_key(&secp_ctx))
1512+
.unwrap()
1513+
.address(NullCtx, bitcoin::Network::Bitcoin)
15121514
.unwrap();
15131515
let addr_expected = bitcoin::Address::from_str(raw_addr_expected).unwrap();
15141516
assert_eq!(addr_one, addr_expected);

0 commit comments

Comments
 (0)