Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/target
/coverage
*.profdata
*.profraw
Cargo.lock
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ path = "src/bin/main.rs"
required-features = ["cli"]

[features]
default = ["miniscript_latest"]
miniscript_latest = ["miniscript_12_3_5"]
miniscript_12_3_5 = ["mscript_12_3_5"]
miniscript_12_0 = ["mscript_12_0"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ let descriptor = EncryptedBackup::new()
|---------------------|---------|-------------------------------------------------------|
| `miniscript_12_0` | – | Compile against `miniscript` v0.12.0 |
| `miniscript_12_3_5` | – | Compile against `miniscript` v0.12.3.5 |
| `miniscript_latest` | | Alias for `miniscript_12_3_5` |
| `miniscript_latest` | | Alias for `miniscript_12_3_5` |
| `devices` | ✓ | Enable automatic enumeration of signing devices. |
| `tokio` | ✓ | Pull in `tokio` runtime used by the `devices`feature. |

Expand Down
157 changes: 154 additions & 3 deletions src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub use mscript_12_0 as miniscript;
pub use mscript_12_3_5 as miniscript;

use std::collections::{BTreeSet, HashSet};
use std::str::FromStr;

use miniscript::{
bitcoin::{self, bip32::DerivationPath, secp256k1},
Expand Down Expand Up @@ -33,12 +34,28 @@ fn dpk_to_deriv_path(key: &DescriptorPublicKey) -> Option<DerivationPath> {
}
}

// See
// https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#constructing-and-spending-taproot-outputs:
// > One example of such a point is H =
// > lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) which is constructed
// > by taking the hash of the standard uncompressed encoding of the secp256k1 base point G as X
// > coordinate.
pub fn bip341_nums() -> bitcoin::secp256k1::PublicKey {
bitcoin::secp256k1::PublicKey::from_str(
"0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
)
.expect("Valid pubkey: NUMS from BIP341")
}

pub fn descr_to_dpks(
descriptor: &Descriptor<DescriptorPublicKey>,
) -> Result<Vec<DescriptorPublicKey>, Error> {
let mut keys = BTreeSet::new();
descriptor.for_each_key(|k| {
keys.insert(k.clone());
let pk = dpk_to_pk(k);
if pk != bip341_nums() {
keys.insert(k.clone());
}
true
});
let keys: Vec<_> = keys.into_iter().collect();
Expand Down Expand Up @@ -71,15 +88,22 @@ pub mod tests {
use super::*;
use std::str::FromStr;

use miniscript::{Descriptor, DescriptorPublicKey};
use miniscript::{
bitcoin::bip32::{self, ChainCode, ChildNumber, Fingerprint},
descriptor::{
self, DerivPaths, DescriptorMultiXKey, DescriptorXKey, SinglePub, SinglePubKey,
Wildcard,
},
Descriptor, DescriptorPublicKey, ToPublicKey,
};

pub fn descr_1() -> Descriptor<DescriptorPublicKey> {
let descr_str = "wsh(or_d(pk([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<0;1>/*),and_v(v:pkh([58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<2;3>/*),older(52596))))#pggrcdd0";

Descriptor::<DescriptorPublicKey>::from_str(descr_str).unwrap()
}

fn dpk_1() -> DescriptorPublicKey {
pub fn dpk_1() -> DescriptorPublicKey {
let dpk_str = "[58b7f8dc/48'/1'/0'/2']tpubDEPBvXvhta3pjVaKokqC3eeMQnszj9ehFaA2zD5nSdkaccwGAizu8jVB2NeSpvmP2P52MBoZvNCixqXRJnTyXx51FQzARR63tjxQSyP3Btw/<0;1>/*";
DescriptorPublicKey::from_str(dpk_str).unwrap()
}
Expand Down Expand Up @@ -108,6 +132,53 @@ pub mod tests {
assert_eq!(pk, expected);
let pk = dpk_to_pk(&dpk_2());
assert_eq!(pk, expected);

// Single
let single_str = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
let dpk = DescriptorPublicKey::from_str(single_str).unwrap();
let pk = dpk_to_pk(&dpk);
let expected = bitcoin::secp256k1::PublicKey::from_str(single_str).unwrap();
assert_eq!(expected, pk);

// Single Xonly
let xonly = bitcoin::PublicKey::from_str(single_str)
.unwrap()
.to_x_only_pubkey();
let dpk = DescriptorPublicKey::Single(SinglePub {
origin: None,
key: descriptor::SinglePubKey::XOnly(xonly),
});
let pk = dpk_to_pk(&dpk);
assert_eq!(expected, pk);

// Xpub
let xpub = bip32::Xpub {
network: bitcoin::NetworkKind::Test,
depth: 1,
parent_fingerprint: Fingerprint::from_str("00000000").unwrap(),
child_number: ChildNumber::from_normal_idx(0).unwrap(),
public_key: bitcoin::secp256k1::PublicKey::from_str(single_str).unwrap(),
chain_code: ChainCode::from(&[1u8; 32]),
};
let dpk = DescriptorPublicKey::XPub(DescriptorXKey {
origin: None,
xkey: xpub,
derivation_path: DerivationPath::default(),
wildcard: Wildcard::None,
});
let pk = dpk_to_pk(&dpk);
assert_eq!(expected, pk);

// MultiXpub
let dpk = DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
origin: None,
xkey: xpub,
derivation_paths: DerivPaths::new(vec![DerivationPath::from_str("0").unwrap()])
.unwrap(),
wildcard: Wildcard::None,
});
let pk = dpk_to_pk(&dpk);
assert_eq!(expected, pk);
}

#[test]
Expand All @@ -118,6 +189,68 @@ pub mod tests {
assert_eq!(deriv_2, DerivationPath::from_str("48'/1'/0'/2'").unwrap());
let deriv_3 = dpk_to_deriv_path(&dpk_3());
assert!(deriv_3.is_none());

let dp = DerivationPath::from_str("0/0").unwrap();
let origin = Some((Fingerprint::from_str("aabbccdd").unwrap(), dp.clone()));

// Single
let single_str = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
let dpk = DescriptorPublicKey::from_str(single_str).unwrap();
let none = dpk_to_deriv_path(&dpk);
assert!(none.is_none());
let single_pk = SinglePubKey::FullKey(dpk_to_pk(&dpk).into());
let dpk = DescriptorPublicKey::Single(SinglePub {
origin: origin.clone(),
key: single_pk,
});
let deriv = dpk_to_deriv_path(&dpk).unwrap();
assert_eq!(deriv, dp);

// Xpub
let xpub = bip32::Xpub {
network: bitcoin::NetworkKind::Test,
depth: 1,
parent_fingerprint: Fingerprint::from_str("00000000").unwrap(),
child_number: ChildNumber::from_normal_idx(0).unwrap(),
public_key: bitcoin::secp256k1::PublicKey::from_str(single_str).unwrap(),
chain_code: ChainCode::from(&[1u8; 32]),
};
let dpk = DescriptorPublicKey::XPub(DescriptorXKey {
origin: None,
xkey: xpub,
derivation_path: DerivationPath::default(),
wildcard: Wildcard::None,
});
let none = dpk_to_deriv_path(&dpk);
assert!(none.is_none());
let dpk = DescriptorPublicKey::XPub(DescriptorXKey {
origin: origin.clone(),
xkey: xpub,
derivation_path: DerivationPath::default(),
wildcard: Wildcard::None,
});
let deriv = dpk_to_deriv_path(&dpk).unwrap();
assert_eq!(deriv, dp);

// MultiXpub
let dpk = DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
origin: None,
xkey: xpub,
derivation_paths: DerivPaths::new(vec![DerivationPath::from_str("0").unwrap()])
.unwrap(),
wildcard: Wildcard::None,
});
let none = dpk_to_deriv_path(&dpk);
assert!(none.is_none());
let dpk = DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
origin: origin.clone(),
xkey: xpub,
derivation_paths: DerivPaths::new(vec![DerivationPath::from_str("0").unwrap()])
.unwrap(),
wildcard: Wildcard::None,
});
let deriv = dpk_to_deriv_path(&dpk).unwrap();
assert_eq!(deriv, dp);
}

#[test]
Expand All @@ -127,6 +260,24 @@ pub mod tests {
assert_eq!(dpks, expected);
}

#[test]
fn test_descriptor_to_dpk_unspendable() {
let descr_str = "tr(tpubD6NzVbkrYhZ4XWBqjZ7DTB4eFvi8eQZ79UvNbQFsxXiaMNaBn83jpMWTXLX2Gx6JgC5n9jWvx6vnijcAUgxXmRtFd4ntasRGNsYSCvQteSr/<0;1>/*,{and_v(v:and_v(v:pk([d4ab66f1/48'/1'/0'/2']tpubDEXYN145WM4rVKtcWpySBYiVQ229pmrnyAGJT14BBh2QJr7ABJswchDicZfFaauLyXhDad1nCoCZQEwAW87JPotP93ykC9WJvoASnBjYBxW/<2;3>/*),pk([79af2d8a/48'/1'/0'/2']tpubDEtHs6m9crfv1oeETj6EXteAtW7eoSSBVBaypEdWZt8VftbHF9R12xSZpzWGNuAofeGPL6cz48dLdCYbVioHL8ygA56yuPW76Xz5WZ3dt8o/<2;3>/*)),older(52596)),and_v(v:pk([d4ab66f1/48'/1'/0'/2']tpubDEXYN145WM4rVKtcWpySBYiVQ229pmrnyAGJT14BBh2QJr7ABJswchDicZfFaauLyXhDad1nCoCZQEwAW87JPotP93ykC9WJvoASnBjYBxW/<0;1>/*),pk([79af2d8a/48'/1'/0'/2']tpubDEtHs6m9crfv1oeETj6EXteAtW7eoSSBVBaypEdWZt8VftbHF9R12xSZpzWGNuAofeGPL6cz48dLdCYbVioHL8ygA56yuPW76Xz5WZ3dt8o/<0;1>/*))})#vudj49fm";
let descriptor = Descriptor::<DescriptorPublicKey>::from_str(descr_str).unwrap();
// unspendable keys must have been dropped
let keys = descr_to_dpks(&descriptor).unwrap();
for key in keys {
let pk = dpk_to_pk(&key);
assert_ne!(pk, bip341_nums());
}
// but the descriptor contains unspendable
let contains_unspendable = descriptor.for_any_key(|k| {
let pk = dpk_to_pk(k);
pk == bip341_nums()
});
assert!(contains_unspendable);
}

#[test]
fn test_dpks_to_deriv_paths() {
let dpks = vec![dpk_1(), dpk_2()];
Expand Down
Loading