Skip to content

Commit 4a729f8

Browse files
committed
feat: add support for BIP-388 wallet policies
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.
1 parent 08f7b62 commit 4a729f8

4 files changed

Lines changed: 616 additions & 39 deletions

File tree

src/descriptor/key.rs

Lines changed: 101 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,53 @@ 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+
#[derive(Debug, PartialEq, Eq, Clone)]
498+
pub enum XKeyParseError {
499+
Bip32(bip32::Error),
500+
Bip388(WalletPolicyError),
501+
}
502+
503+
#[cfg(feature = "std")]
504+
impl error::Error for XKeyParseError {
505+
fn source(&self) -> Option<&(dyn error::Error + 'static)> { None }
506+
}
507+
508+
impl fmt::Display for XKeyParseError {
509+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510+
match self {
511+
XKeyParseError::Bip32(err) => err.fmt(f),
512+
XKeyParseError::Bip388(err) => err.fmt(f),
513+
}
514+
}
515+
}
516+
517+
impl From<bip32::Error> for XKeyParseError {
518+
fn from(err: bip32::Error) -> Self { Self::Bip32(err) }
519+
}
520+
490521
impl fmt::Display for DescriptorPublicKey {
491522
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
492523
match *self {
@@ -502,22 +533,14 @@ impl fmt::Display for DescriptorPublicKey {
502533
maybe_fmt_master_id(f, &xpub.origin)?;
503534
xpub.xkey.fmt(f)?;
504535
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-
}
536+
xpub.wildcard.fmt(f)?;
510537
Ok(())
511538
}
512539
Self::MultiXPub(ref xpub) => {
513540
maybe_fmt_master_id(f, &xpub.origin)?;
514541
xpub.xkey.fmt(f)?;
515542
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-
}
543+
xpub.wildcard.fmt(f)?;
521544
Ok(())
522545
}
523546
}
@@ -610,7 +633,10 @@ fn fmt_derivation_path(f: &mut fmt::Formatter, path: &bip32::DerivationPath) ->
610633
/// Writes multiple derivation paths to the formatter, no leading 'm'.
611634
/// NOTE: we assume paths only differ at a single index, as prescribed by BIP389.
612635
/// Will panic if the list of paths is empty.
613-
fn fmt_derivation_paths(f: &mut fmt::Formatter, paths: &[bip32::DerivationPath]) -> fmt::Result {
636+
pub(crate) fn fmt_derivation_paths<W: fmt::Write>(
637+
f: &mut W,
638+
paths: &[bip32::DerivationPath],
639+
) -> fmt::Result {
614640
for (i, child) in paths[0].into_iter().enumerate() {
615641
if paths.len() > 1 && child != &paths[1][i] {
616642
write!(f, "/<")?;
@@ -642,7 +668,7 @@ impl FromStr for DescriptorPublicKey {
642668
let (key_part, origin) = parse_key_origin(s)?;
643669

644670
if key_part.contains("pub") {
645-
let (xpub, derivation_paths, wildcard) = parse_xkey_deriv(parse_bip32_xpub, key_part)?;
671+
let (xpub, derivation_paths, wildcard) = parse_xkey_deriv(key_part)?;
646672
if derivation_paths.len() > 1 {
647673
Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
648674
origin,
@@ -795,6 +821,38 @@ impl DescriptorPublicKey {
795821
}
796822
}
797823

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

807-
/// Whether or not the key has a wildcard
865+
/// Return a Wildcard if key is a XKey
866+
pub fn wildcard(&self) -> Option<Wildcard> {
867+
match *self {
868+
DescriptorPublicKey::Single(..) => None,
869+
DescriptorPublicKey::XPub(ref xpub) => Some(xpub.wildcard),
870+
DescriptorPublicKey::MultiXPub(ref xpub) => Some(xpub.wildcard),
871+
}
872+
}
873+
874+
/// Whether or not the key has a hardened step in path
808875
pub fn has_hardened_step(&self) -> bool {
809876
let paths = match self {
810877
DescriptorPublicKey::Single(..) => &[],
@@ -925,8 +992,7 @@ impl FromStr for DescriptorSecretKey {
925992
.map_err(DescriptorKeyParseError::WifPrivateKey)?;
926993
Ok(DescriptorSecretKey::Single(SinglePriv { key: sk, origin }))
927994
} else {
928-
let (xpriv, derivation_paths, wildcard) =
929-
parse_xkey_deriv(parse_bip32_xpriv, key_part)?;
995+
let (xpriv, derivation_paths, wildcard) = parse_xkey_deriv(key_part)?;
930996
if derivation_paths.len() > 1 {
931997
Ok(DescriptorSecretKey::MultiXPrv(DescriptorMultiXKey {
932998
origin,
@@ -1009,26 +1075,22 @@ fn parse_key_origin(s: &str) -> Result<(&str, Option<bip32::KeySource>), Descrip
10091075
}
10101076
}
10111077

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>,
1078+
pub(crate) fn parse_xkey_deriv<Key, E>(
10221079
key_deriv: &str,
1023-
) -> Result<(Key, Vec<bip32::DerivationPath>, Wildcard), DescriptorKeyParseError> {
1080+
) -> Result<(Key, Vec<bip32::DerivationPath>, Wildcard), DescriptorKeyParseError>
1081+
where
1082+
Key: FromStr<Err = E>,
1083+
E: Into<XKeyParseError>,
1084+
{
10241085
let mut key_deriv = key_deriv.split('/');
10251086
let xkey_str = key_deriv
10261087
.next()
10271088
.ok_or(DescriptorKeyParseError::MalformedKeyData(
10281089
MalformedKeyDataKind::NoKeyAfterOrigin,
10291090
))?;
10301091

1031-
let xkey = parse_xkey_fn(xkey_str)?;
1092+
let xkey =
1093+
Key::from_str(xkey_str).map_err(|e| DescriptorKeyParseError::XKeyParseError(e.into()))?;
10321094

10331095
let mut wildcard = Wildcard::None;
10341096
let mut multipath = false;
@@ -1097,7 +1159,7 @@ fn parse_xkey_deriv<Key>(
10971159
// step all the vectors of indexes contain a single element. If it did though, one of the
10981160
// vectors contains more than one element.
10991161
// Now transform this list of vectors of steps into distinct derivation paths.
1100-
.try_fold(Vec::new(), |mut paths, index_list| {
1162+
.try_fold(Vec::new(), |mut paths, index_list| -> Result<_, DescriptorKeyParseError> {
11011163
let mut index_list = index_list?.into_iter();
11021164
let first_index = index_list
11031165
.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)