Skip to content

Commit 32c096b

Browse files
committed
Merge #898: feat: add support for BIP-388 wallet policies
945adb7 feat: add support for BIP-388 wallet policies (Trevor Arjeski) 4906bed feat: add new functions on DescriptorPublicKey (Trevor Arjeski) 15e3465 refactor: parse_xkey_deriv to use FromStr instead of accepting closure (Trevor Arjeski) f5a6857 chore: implement Display for Wildcard (Trevor Arjeski) Pull request description: Implemented BIP-388 by introducing a new type, `WalletPolicy`, which can be used to create descriptor templates. The idea is pretty simple and only really required making a new type that implements `MiniscriptKey`, then the existing parser and `Translator` trait takes care of the rest. There are a few edge cases that require validation, which isn't so nice, but works for now. Test cases that require BIP-390 and BIP-387's sortedmulti_a are commented out and issues could arise with those test vectors when/if those BIPs are implemented. See `WalletPolicy`'s doc and the unit tests for usage. ACKs for top commit: apoelstra: ACK 945adb7; successfully ran local tests; looks great! Lol Tobin the errors here are well above the current standards of this crate. Tree-SHA512: 094facc19a7b221876498762a0e5b4c9e63c76e12e09bbd608166e23eabedf63739279e4e1c2edf1b614d20920d6a3624395cf1701498b05aef932fbe0cc9f71
2 parents ad66c9c + 945adb7 commit 32c096b

4 files changed

Lines changed: 639 additions & 39 deletions

File tree

src/descriptor/key.rs

Lines changed: 107 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use bitcoin::key::{PublicKey, XOnlyPublicKey};
1212
use bitcoin::secp256k1::{Secp256k1, Signing, Verification};
1313
use bitcoin::NetworkKind;
1414

15+
use super::WalletPolicyError;
1516
use crate::prelude::*;
1617
#[cfg(feature = "serde")]
1718
use crate::serde::{Deserialize, Deserializer, Serialize, Serializer};
@@ -201,6 +202,16 @@ pub enum Wildcard {
201202
Hardened,
202203
}
203204

205+
impl fmt::Display for Wildcard {
206+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207+
match self {
208+
Wildcard::None => write!(f, ""),
209+
Wildcard::Unhardened => write!(f, "/*"),
210+
Wildcard::Hardened => write!(f, "/*h"),
211+
}
212+
}
213+
}
214+
204215
impl SinglePriv {
205216
/// Returns the public key of this key.
206217
fn to_public<C: Signing>(&self, secp: &Secp256k1<C>) -> SinglePub {
@@ -412,16 +423,12 @@ impl fmt::Display for MalformedKeyDataKind {
412423
#[derive(Debug, PartialEq, Eq, Clone)]
413424
#[non_exhaustive]
414425
pub enum DescriptorKeyParseError {
415-
/// Error while parsing a BIP32 extended private key
416-
Bip32Xpriv(bip32::Error),
417-
/// Error while parsing a BIP32 extended public key
418-
Bip32Xpub(bip32::Error),
419426
/// Error while parsing a derivation index
420427
DerivationIndexError {
421428
/// The invalid index
422429
index: String,
423430
/// The underlying parse error
424-
err: bitcoin::bip32::Error,
431+
err: bip32::Error,
425432
},
426433
/// Error deriving the hardened private key.
427434
DeriveHardenedKey(bip32::Error),
@@ -444,13 +451,13 @@ pub enum DescriptorKeyParseError {
444451
WifPrivateKey(bitcoin::key::FromWifError),
445452
/// Error while parsing an X-only public key (Secp256k1 error).
446453
XonlyPublicKey(bitcoin::secp256k1::Error),
454+
/// XKey parsing error
455+
XKeyParseError(XKeyParseError),
447456
}
448457

449458
impl fmt::Display for DescriptorKeyParseError {
450459
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
451460
match self {
452-
Self::Bip32Xpriv(err) => err.fmt(f),
453-
Self::Bip32Xpub(err) => err.fmt(f),
454461
Self::DerivationIndexError { index, err } => {
455462
write!(f, "at derivation index '{index}': {err}")
456463
}
@@ -464,29 +471,59 @@ impl fmt::Display for DescriptorKeyParseError {
464471
Self::FullPublicKey(err) => err.fmt(f),
465472
Self::WifPrivateKey(err) => err.fmt(f),
466473
Self::XonlyPublicKey(err) => err.fmt(f),
474+
Self::XKeyParseError(err) => err.fmt(f),
467475
}
468476
}
469477
}
470478

471479
#[cfg(feature = "std")]
472480
impl error::Error for DescriptorKeyParseError {
473-
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
481+
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
474482
match self {
475-
Self::Bip32Xpriv(err)
476-
| Self::Bip32Xpub(err)
477-
| Self::DerivationIndexError { err, .. }
483+
Self::DerivationIndexError { err, .. }
478484
| Self::DeriveHardenedKey(err)
479485
| Self::MasterDerivationPath(err) => Some(err),
480486
Self::MasterFingerprint { err, .. } => Some(err),
481487
Self::NonDefiniteKey(err) => Some(err),
482488
Self::FullPublicKey(err) => Some(err),
483489
Self::WifPrivateKey(err) => Some(err),
484490
Self::XonlyPublicKey(err) => Some(err),
491+
Self::XKeyParseError(err) => Some(err),
485492
Self::MalformedKeyData(_) => None,
486493
}
487494
}
488495
}
489496

497+
/// An error when parsing an extended key.
498+
#[derive(Debug, PartialEq, Eq, Clone)]
499+
pub enum XKeyParseError {
500+
Bip32(bip32::Error),
501+
Bip388(WalletPolicyError),
502+
}
503+
504+
#[cfg(feature = "std")]
505+
impl error::Error for XKeyParseError {
506+
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
507+
match self {
508+
Self::Bip32(err) => Some(err),
509+
Self::Bip388(err) => Some(err),
510+
}
511+
}
512+
}
513+
514+
impl fmt::Display for XKeyParseError {
515+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
516+
match self {
517+
Self::Bip32(err) => err.fmt(f),
518+
Self::Bip388(err) => err.fmt(f),
519+
}
520+
}
521+
}
522+
523+
impl From<bip32::Error> for XKeyParseError {
524+
fn from(err: bip32::Error) -> Self { Self::Bip32(err) }
525+
}
526+
490527
impl fmt::Display for DescriptorPublicKey {
491528
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
492529
match *self {
@@ -502,22 +539,14 @@ impl fmt::Display for DescriptorPublicKey {
502539
maybe_fmt_master_id(f, &xpub.origin)?;
503540
xpub.xkey.fmt(f)?;
504541
fmt_derivation_path(f, &xpub.derivation_path)?;
505-
match xpub.wildcard {
506-
Wildcard::None => {}
507-
Wildcard::Unhardened => write!(f, "/*")?,
508-
Wildcard::Hardened => write!(f, "/*h")?,
509-
}
542+
xpub.wildcard.fmt(f)?;
510543
Ok(())
511544
}
512545
Self::MultiXPub(ref xpub) => {
513546
maybe_fmt_master_id(f, &xpub.origin)?;
514547
xpub.xkey.fmt(f)?;
515548
fmt_derivation_paths(f, xpub.derivation_paths.paths())?;
516-
match xpub.wildcard {
517-
Wildcard::None => {}
518-
Wildcard::Unhardened => write!(f, "/*")?,
519-
Wildcard::Hardened => write!(f, "/*h")?,
520-
}
549+
xpub.wildcard.fmt(f)?;
521550
Ok(())
522551
}
523552
}
@@ -610,7 +639,10 @@ fn fmt_derivation_path(f: &mut fmt::Formatter, path: &bip32::DerivationPath) ->
610639
/// Writes multiple derivation paths to the formatter, no leading 'm'.
611640
/// NOTE: we assume paths only differ at a single index, as prescribed by BIP389.
612641
/// Will panic if the list of paths is empty.
613-
fn fmt_derivation_paths(f: &mut fmt::Formatter, paths: &[bip32::DerivationPath]) -> fmt::Result {
642+
pub(crate) fn fmt_derivation_paths<W: fmt::Write>(
643+
f: &mut W,
644+
paths: &[bip32::DerivationPath],
645+
) -> fmt::Result {
614646
for (i, child) in paths[0].into_iter().enumerate() {
615647
if paths.len() > 1 && child != &paths[1][i] {
616648
write!(f, "/<")?;
@@ -642,7 +674,7 @@ impl FromStr for DescriptorPublicKey {
642674
let (key_part, origin) = parse_key_origin(s)?;
643675

644676
if key_part.contains("pub") {
645-
let (xpub, derivation_paths, wildcard) = parse_xkey_deriv(parse_bip32_xpub, key_part)?;
677+
let (xpub, derivation_paths, wildcard) = parse_xkey_deriv(key_part)?;
646678
if derivation_paths.len() > 1 {
647679
Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
648680
origin,
@@ -795,6 +827,38 @@ impl DescriptorPublicKey {
795827
}
796828
}
797829

830+
/// Derivation path without the origin prefix.
831+
///
832+
/// For wildcard keys this will return the path up to the wildcard, so you
833+
/// can get full paths by appending one additional derivation step, according
834+
/// to the wildcard type (hardened or normal).
835+
///
836+
/// For multipath extended keys, this returns `None`.
837+
pub fn derivation_path(&self) -> Option<bip32::DerivationPath> {
838+
match *self {
839+
DescriptorPublicKey::XPub(ref xpub) => Some(xpub.derivation_path.clone()),
840+
DescriptorPublicKey::Single(_) => Some(bip32::DerivationPath::from(vec![])),
841+
DescriptorPublicKey::MultiXPub(_) => None,
842+
}
843+
}
844+
845+
/// Returns a vector of derivation paths without the origin prefix.
846+
///
847+
/// For wildcard keys this will return the path up to the wildcard, so you
848+
/// can get full paths by appending one additional derivation step, according
849+
/// to the wildcard type (hardened or normal).
850+
pub fn derivation_paths(&self) -> Vec<bip32::DerivationPath> {
851+
match &self {
852+
DescriptorPublicKey::XPub(xpub) => {
853+
vec![xpub.derivation_path.clone()]
854+
}
855+
DescriptorPublicKey::Single(_) => {
856+
vec![bip32::DerivationPath::from(vec![])]
857+
}
858+
DescriptorPublicKey::MultiXPub(xpub) => xpub.derivation_paths.paths().clone(),
859+
}
860+
}
861+
798862
/// Whether or not the key has a wildcard
799863
pub fn has_wildcard(&self) -> bool {
800864
match *self {
@@ -804,7 +868,16 @@ impl DescriptorPublicKey {
804868
}
805869
}
806870

807-
/// Whether or not the key has a wildcard
871+
/// Return a Wildcard if key is a XKey
872+
pub fn wildcard(&self) -> Option<Wildcard> {
873+
match *self {
874+
DescriptorPublicKey::Single(..) => None,
875+
DescriptorPublicKey::XPub(ref xpub) => Some(xpub.wildcard),
876+
DescriptorPublicKey::MultiXPub(ref xpub) => Some(xpub.wildcard),
877+
}
878+
}
879+
880+
/// Whether or not the key has a hardened step in path
808881
pub fn has_hardened_step(&self) -> bool {
809882
let paths = match self {
810883
DescriptorPublicKey::Single(..) => &[],
@@ -925,8 +998,7 @@ impl FromStr for DescriptorSecretKey {
925998
.map_err(DescriptorKeyParseError::WifPrivateKey)?;
926999
Ok(DescriptorSecretKey::Single(SinglePriv { key: sk, origin }))
9271000
} else {
928-
let (xpriv, derivation_paths, wildcard) =
929-
parse_xkey_deriv(parse_bip32_xpriv, key_part)?;
1001+
let (xpriv, derivation_paths, wildcard) = parse_xkey_deriv(key_part)?;
9301002
if derivation_paths.len() > 1 {
9311003
Ok(DescriptorSecretKey::MultiXPrv(DescriptorMultiXKey {
9321004
origin,
@@ -1009,26 +1081,22 @@ fn parse_key_origin(s: &str) -> Result<(&str, Option<bip32::KeySource>), Descrip
10091081
}
10101082
}
10111083

1012-
fn parse_bip32_xpub(xkey_str: &str) -> Result<bip32::Xpub, DescriptorKeyParseError> {
1013-
bip32::Xpub::from_str(xkey_str).map_err(DescriptorKeyParseError::Bip32Xpub)
1014-
}
1015-
1016-
fn parse_bip32_xpriv(xkey_str: &str) -> Result<bip32::Xpriv, DescriptorKeyParseError> {
1017-
bip32::Xpriv::from_str(xkey_str).map_err(DescriptorKeyParseError::Bip32Xpriv)
1018-
}
1019-
1020-
fn parse_xkey_deriv<Key>(
1021-
parse_xkey_fn: impl Fn(&str) -> Result<Key, DescriptorKeyParseError>,
1084+
pub(crate) fn parse_xkey_deriv<Key, E>(
10221085
key_deriv: &str,
1023-
) -> Result<(Key, Vec<bip32::DerivationPath>, Wildcard), DescriptorKeyParseError> {
1086+
) -> Result<(Key, Vec<bip32::DerivationPath>, Wildcard), DescriptorKeyParseError>
1087+
where
1088+
Key: FromStr<Err = E>,
1089+
E: Into<XKeyParseError>,
1090+
{
10241091
let mut key_deriv = key_deriv.split('/');
10251092
let xkey_str = key_deriv
10261093
.next()
10271094
.ok_or(DescriptorKeyParseError::MalformedKeyData(
10281095
MalformedKeyDataKind::NoKeyAfterOrigin,
10291096
))?;
10301097

1031-
let xkey = parse_xkey_fn(xkey_str)?;
1098+
let xkey =
1099+
Key::from_str(xkey_str).map_err(|e| DescriptorKeyParseError::XKeyParseError(e.into()))?;
10321100

10331101
let mut wildcard = Wildcard::None;
10341102
let mut multipath = false;
@@ -1097,7 +1165,7 @@ fn parse_xkey_deriv<Key>(
10971165
// step all the vectors of indexes contain a single element. If it did though, one of the
10981166
// vectors contains more than one element.
10991167
// Now transform this list of vectors of steps into distinct derivation paths.
1100-
.try_fold(Vec::new(), |mut paths, index_list| {
1168+
.try_fold(Vec::new(), |mut paths, index_list| -> Result<_, DescriptorKeyParseError> {
11011169
let mut index_list = index_list?.into_iter();
11021170
let first_index = index_list
11031171
.next()

src/descriptor/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,15 @@ pub use self::tr::{
5353
pub mod checksum;
5454
mod key;
5555
mod key_map;
56+
mod wallet_policy;
5657

5758
pub use self::key::{
5859
DefiniteDescriptorKey, DerivPaths, DescriptorKeyParseError, DescriptorMultiXKey,
5960
DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, InnerXKey, MalformedKeyDataKind,
6061
NonDefiniteKeyError, SinglePriv, SinglePub, SinglePubKey, Wildcard, XKeyNetwork,
6162
};
6263
pub use self::key_map::KeyMap;
64+
pub use self::wallet_policy::{WalletPolicy, WalletPolicyError};
6365

6466
/// Script descriptor
6567
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]

0 commit comments

Comments
 (0)