Skip to content

Commit 351c2c0

Browse files
authored
Merge pull request #219 from apoelstra/2021-01--wpk-improvements
some improvements to DescriptorPublicKey
2 parents a65f03e + b29c3cd commit 351c2c0

File tree

11 files changed

+352
-11
lines changed

11 files changed

+352
-11
lines changed

src/descriptor/bare.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ use expression::{self, FromTree};
2626
use miniscript::context::ScriptContext;
2727
use policy::{semantic, Liftable};
2828
use util::{varint_len, witness_to_scriptsig};
29-
use {BareCtx, Error, Miniscript, MiniscriptKey, Satisfier, ToPublicKey, TranslatePk};
29+
use {
30+
BareCtx, Error, ForEach, ForEachKey, Miniscript, MiniscriptKey, Satisfier, ToPublicKey,
31+
TranslatePk,
32+
};
3033

3134
use super::{
3235
checksum::{desc_checksum, verify_checksum},
@@ -163,6 +166,16 @@ where
163166
}
164167
}
165168

169+
impl<Pk: MiniscriptKey> ForEachKey<Pk> for Bare<Pk> {
170+
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, pred: F) -> bool
171+
where
172+
Pk: 'a,
173+
Pk::Hash: 'a,
174+
{
175+
self.ms.for_each_key(pred)
176+
}
177+
}
178+
166179
impl<P: MiniscriptKey, Q: MiniscriptKey> TranslatePk<P, Q> for Bare<P> {
167180
type Output = Bare<Q>;
168181

@@ -327,6 +340,16 @@ where
327340
}
328341
}
329342

343+
impl<Pk: MiniscriptKey> ForEachKey<Pk> for Pkh<Pk> {
344+
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, mut pred: F) -> bool
345+
where
346+
Pk: 'a,
347+
Pk::Hash: 'a,
348+
{
349+
pred(ForEach::Key(&self.pk))
350+
}
351+
}
352+
330353
impl<P: MiniscriptKey, Q: MiniscriptKey> TranslatePk<P, Q> for Pkh<P> {
331354
type Output = Pkh<Q>;
332355

src/descriptor/key.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ use std::{error, fmt, str::FromStr};
33
use bitcoin::{
44
self,
55
hashes::hex::FromHex,
6+
hashes::Hash,
67
secp256k1,
78
secp256k1::{Secp256k1, Signing},
89
util::bip32,
10+
XpubIdentifier,
911
};
1012

1113
use MiniscriptKey;
@@ -343,6 +345,61 @@ impl fmt::Display for ConversionError {
343345
impl error::Error for ConversionError {}
344346

345347
impl DescriptorPublicKey {
348+
/// The fingerprint of the master key associated with this key
349+
pub fn master_fingerprint(&self) -> bip32::Fingerprint {
350+
match *self {
351+
DescriptorPublicKey::XPub(ref xpub) => {
352+
if let Some((fingerprint, _)) = xpub.origin {
353+
fingerprint
354+
} else {
355+
xpub.xkey.fingerprint()
356+
}
357+
}
358+
DescriptorPublicKey::SinglePub(ref single) => {
359+
if let Some((fingerprint, _)) = single.origin {
360+
fingerprint
361+
} else {
362+
let mut engine = XpubIdentifier::engine();
363+
single.key.write_into(&mut engine);
364+
bip32::Fingerprint::from(&XpubIdentifier::from_engine(engine)[..])
365+
}
366+
}
367+
}
368+
}
369+
370+
/// Full path, from the master key
371+
///
372+
/// For wildcard keys this will return the path up to the wildcard, so you
373+
/// can get full paths by appending one additional derivation step, according
374+
/// to the wildcard type (hardened or normal)
375+
pub fn full_derivation_path(&self) -> bip32::DerivationPath {
376+
match *self {
377+
DescriptorPublicKey::XPub(ref xpub) => {
378+
let origin_path = if let Some((_, ref path)) = xpub.origin {
379+
path.clone()
380+
} else {
381+
bip32::DerivationPath::from(vec![])
382+
};
383+
origin_path.extend(&xpub.derivation_path)
384+
}
385+
DescriptorPublicKey::SinglePub(ref single) => {
386+
if let Some((_, ref path)) = single.origin {
387+
path.clone()
388+
} else {
389+
bip32::DerivationPath::from(vec![])
390+
}
391+
}
392+
}
393+
}
394+
395+
/// Whether or not the key has a wildcards
396+
pub fn is_deriveable(&self) -> bool {
397+
match *self {
398+
DescriptorPublicKey::SinglePub(..) => false,
399+
DescriptorPublicKey::XPub(ref xpub) => xpub.wildcard != Wildcard::None,
400+
}
401+
}
402+
346403
/// If this public key has a wildcard, replace it by the given index
347404
///
348405
/// Panics if given an index ≥ 2^31
@@ -699,28 +756,60 @@ mod test {
699756
);
700757
}
701758

759+
#[test]
760+
fn test_wildcard() {
761+
let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2").unwrap();
762+
assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00");
763+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'/2");
764+
assert_eq!(public_key.is_deriveable(), false);
765+
766+
let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/*").unwrap();
767+
assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00");
768+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'");
769+
assert_eq!(public_key.is_deriveable(), true);
770+
771+
let public_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/*h").unwrap();
772+
assert_eq!(public_key.master_fingerprint().to_string(), "abcdef00");
773+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'");
774+
assert_eq!(public_key.is_deriveable(), true);
775+
}
776+
702777
#[test]
703778
fn test_deriv_on_xprv() {
704779
let secp = secp256k1::Secp256k1::signing_only();
705780

706781
let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2").unwrap();
707782
let public_key = secret_key.as_public(&secp).unwrap();
708783
assert_eq!(public_key.to_string(), "[2cbe2a6d/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2");
784+
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
785+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'/2");
786+
assert_eq!(public_key.is_deriveable(), false);
709787

710788
let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2'").unwrap();
711789
let public_key = secret_key.as_public(&secp).unwrap();
712790
assert_eq!(public_key.to_string(), "[2cbe2a6d/0'/1'/2']tpubDDPuH46rv4dbFtmF6FrEtJEy1CvLZonyBoVxF6xsesHdYDdTBrq2mHhm8AbsPh39sUwL2nZyxd6vo4uWNTU9v4t893CwxjqPnwMoUACLvMV");
791+
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
792+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0'/1'/2'");
713793

714794
let secret_key = DescriptorSecretKey::from_str("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0/1/2").unwrap();
715795
let public_key = secret_key.as_public(&secp).unwrap();
716796
assert_eq!(public_key.to_string(), "tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr/0/1/2");
797+
assert_eq!(public_key.master_fingerprint().to_string(), "2cbe2a6d");
798+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0/1/2");
717799

718800
let secret_key = DescriptorSecretKey::from_str("[aabbccdd]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0/1/2").unwrap();
719801
let public_key = secret_key.as_public(&secp).unwrap();
720802
assert_eq!(public_key.to_string(), "[aabbccdd]tpubD6NzVbkrYhZ4WQdzxL7NmJN7b85ePo4p6RSj9QQHF7te2RR9iUeVSGgnGkoUsB9LBRosgvNbjRv9bcsJgzgBd7QKuxDm23ZewkTRzNSLEDr/0/1/2");
803+
assert_eq!(public_key.master_fingerprint().to_string(), "aabbccdd");
804+
assert_eq!(public_key.full_derivation_path().to_string(), "m/0/1/2");
721805

722806
let secret_key = DescriptorSecretKey::from_str("[aabbccdd/90']tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/0'/1'/2").unwrap();
723807
let public_key = secret_key.as_public(&secp).unwrap();
724808
assert_eq!(public_key.to_string(), "[aabbccdd/90'/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2");
809+
assert_eq!(public_key.master_fingerprint().to_string(), "aabbccdd");
810+
assert_eq!(
811+
public_key.full_derivation_path().to_string(),
812+
"m/90'/0'/1'/2"
813+
);
725814
}
726815
}

src/descriptor/mod.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ use self::checksum::verify_checksum;
3636
use expression;
3737
use miniscript;
3838
use miniscript::{Legacy, Miniscript, Segwitv0};
39-
use {BareCtx, Error, MiniscriptKey, Satisfier, ToPublicKey, TranslatePk, TranslatePk2};
39+
use {
40+
BareCtx, Error, ForEach, ForEachKey, MiniscriptKey, Satisfier, ToPublicKey, TranslatePk,
41+
TranslatePk2,
42+
};
4043

4144
mod bare;
4245
mod segwitv0;
@@ -424,7 +427,28 @@ where
424427
}
425428
}
426429

430+
impl<Pk: MiniscriptKey> ForEachKey<Pk> for Descriptor<Pk> {
431+
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, pred: F) -> bool
432+
where
433+
Pk: 'a,
434+
Pk::Hash: 'a,
435+
{
436+
match *self {
437+
Descriptor::Bare(ref bare) => bare.for_each_key(pred),
438+
Descriptor::Pkh(ref pkh) => pkh.for_each_key(pred),
439+
Descriptor::Wpkh(ref wpkh) => wpkh.for_each_key(pred),
440+
Descriptor::Wsh(ref wsh) => wsh.for_each_key(pred),
441+
Descriptor::Sh(ref sh) => sh.for_each_key(pred),
442+
}
443+
}
444+
}
445+
427446
impl Descriptor<DescriptorPublicKey> {
447+
/// Whether or not the descriptor has any wildcards
448+
pub fn is_deriveable(&self) -> bool {
449+
self.for_any_key(|key| key.as_key().is_deriveable())
450+
}
451+
428452
/// Derives all wildcard keys in the descriptor using the supplied index
429453
///
430454
/// Panics if given an index ≥ 2^31

src/descriptor/segwitv0.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ use expression::{self, FromTree};
2424
use miniscript::context::{ScriptContext, ScriptContextError};
2525
use policy::{semantic, Liftable};
2626
use util::varint_len;
27-
use {Error, Miniscript, MiniscriptKey, Satisfier, Segwitv0, ToPublicKey, TranslatePk};
27+
use {
28+
Error, ForEach, ForEachKey, Miniscript, MiniscriptKey, Satisfier, Segwitv0, ToPublicKey,
29+
TranslatePk,
30+
};
2831

2932
use super::{
3033
checksum::{desc_checksum, verify_checksum},
@@ -230,6 +233,19 @@ where
230233
}
231234
}
232235

236+
impl<Pk: MiniscriptKey> ForEachKey<Pk> for Wsh<Pk> {
237+
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, pred: F) -> bool
238+
where
239+
Pk: 'a,
240+
Pk::Hash: 'a,
241+
{
242+
match self.inner {
243+
WshInner::SortedMulti(ref smv) => smv.for_each_key(pred),
244+
WshInner::Ms(ref ms) => ms.for_each_key(pred),
245+
}
246+
}
247+
}
248+
233249
impl<P: MiniscriptKey, Q: MiniscriptKey> TranslatePk<P, Q> for Wsh<P> {
234250
type Output = Wsh<Q>;
235251

@@ -410,6 +426,16 @@ where
410426
}
411427
}
412428

429+
impl<Pk: MiniscriptKey> ForEachKey<Pk> for Wpkh<Pk> {
430+
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, mut pred: F) -> bool
431+
where
432+
Pk: 'a,
433+
Pk::Hash: 'a,
434+
{
435+
pred(ForEach::Key(&self.pk))
436+
}
437+
}
438+
413439
impl<P: MiniscriptKey, Q: MiniscriptKey> TranslatePk<P, Q> for Wpkh<P> {
414440
type Output = Wpkh<Q>;
415441

src/descriptor/sh.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ use miniscript::context::ScriptContext;
2727
use policy::{semantic, Liftable};
2828
use push_opcode_size;
2929
use util::{varint_len, witness_to_scriptsig};
30-
use {Error, Legacy, Miniscript, MiniscriptKey, Satisfier, Segwitv0, ToPublicKey, TranslatePk};
30+
use {
31+
Error, ForEach, ForEachKey, Legacy, Miniscript, MiniscriptKey, Satisfier, Segwitv0,
32+
ToPublicKey, TranslatePk,
33+
};
3134

3235
use super::{
3336
checksum::{desc_checksum, verify_checksum},
@@ -322,6 +325,21 @@ where
322325
}
323326
}
324327

328+
impl<Pk: MiniscriptKey> ForEachKey<Pk> for Sh<Pk> {
329+
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, pred: F) -> bool
330+
where
331+
Pk: 'a,
332+
Pk::Hash: 'a,
333+
{
334+
match self.inner {
335+
ShInner::Wsh(ref wsh) => wsh.for_each_key(pred),
336+
ShInner::SortedMulti(ref smv) => smv.for_each_key(pred),
337+
ShInner::Wpkh(ref wpkh) => wpkh.for_each_key(pred),
338+
ShInner::Ms(ref ms) => ms.for_each_key(pred),
339+
}
340+
}
341+
}
342+
325343
impl<P: MiniscriptKey, Q: MiniscriptKey> TranslatePk<P, Q> for Sh<P> {
326344
type Output = Sh<Q>;
327345

src/descriptor/sortedmulti.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use expression;
2424
use miniscript::{self, context::ScriptContext, decode::Terminal};
2525
use policy;
2626
use script_num_size;
27-
use {errstr, Error, Miniscript, MiniscriptKey, Satisfier, ToPublicKey};
27+
use {errstr, Error, ForEach, ForEachKey, Miniscript, MiniscriptKey, Satisfier, ToPublicKey};
2828

2929
/// Contents of a "sortedmulti" descriptor
3030
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -84,9 +84,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
8484

8585
pks.map(|pks| SortedMultiVec::new(k as usize, pks))?
8686
}
87-
}
8887

89-
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
9088
/// This will panic if translatefpk returns an uncompressed key when
9189
/// converting to a Segwit descriptor. To prevent this panic, ensure
9290
/// translatefpk returns an error in this case instead.
@@ -106,6 +104,17 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
106104
})
107105
}
108106
}
107+
108+
impl<Pk: MiniscriptKey, Ctx: ScriptContext> ForEachKey<Pk> for SortedMultiVec<Pk, Ctx> {
109+
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, mut pred: F) -> bool
110+
where
111+
Pk: 'a,
112+
Pk::Hash: 'a,
113+
{
114+
self.pks.iter().all(|key| pred(ForEach::Key(key)))
115+
}
116+
}
117+
109118
impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
110119
/// utility function to sanity a sorted multi vec
111120
pub fn sanity_check(&self) -> Result<(), Error> {

src/lib.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,44 @@ impl<
404404
{
405405
}
406406

407+
/// Either a key or a keyhash
408+
pub enum ForEach<'a, Pk: MiniscriptKey + 'a> {
409+
/// A key
410+
Key(&'a Pk),
411+
/// A keyhash
412+
Hash(&'a Pk::Hash),
413+
}
414+
415+
impl<'a, Pk: MiniscriptKey<Hash = Pk>> ForEach<'a, Pk> {
416+
/// Convenience method to avoid distinguishing between keys and hashes when these are the same type
417+
pub fn as_key(&self) -> &'a Pk {
418+
match *self {
419+
ForEach::Key(ref_key) => ref_key,
420+
ForEach::Hash(ref_key) => ref_key,
421+
}
422+
}
423+
}
424+
425+
/// Trait describing the ability to iterate over every key
426+
pub trait ForEachKey<Pk: MiniscriptKey> {
427+
/// Run a predicate on every key in the descriptor, returning whether
428+
/// the predicate returned true for every key
429+
fn for_each_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, pred: F) -> bool
430+
where
431+
Pk: 'a,
432+
Pk::Hash: 'a;
433+
434+
/// Run a predicate on every key in the descriptor, returning whether
435+
/// the predicate returned true for any key
436+
fn for_any_key<'a, F: FnMut(ForEach<'a, Pk>) -> bool>(&'a self, mut pred: F) -> bool
437+
where
438+
Pk: 'a,
439+
Pk::Hash: 'a,
440+
{
441+
!self.for_each_key(|key| !pred(key))
442+
}
443+
}
444+
407445
/// Miniscript
408446
409447
#[derive(Debug)]

0 commit comments

Comments
 (0)