Skip to content
Open
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
103 changes: 60 additions & 43 deletions src/descriptor/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,8 @@ pub enum DescriptorKeyParseError {
XonlyPublicKey(bitcoin::secp256k1::Error),
/// XKey parsing error
XKeyParseError(XKeyParseError),
/// Unexpected XPrivateKey when parsing [`Descriptor<DescriptorPublicKey>`][crate::Descriptor].
UnexpectedXPrivateKey,
}

impl fmt::Display for DescriptorKeyParseError {
Expand All @@ -553,6 +555,9 @@ impl fmt::Display for DescriptorKeyParseError {
Self::WifPrivateKey(err) => err.fmt(f),
Self::XonlyPublicKey(err) => err.fmt(f),
Self::XKeyParseError(err) => err.fmt(f),
Self::UnexpectedXPrivateKey => f.write_str(
"unexpected private extended key. Use `Descriptor::parse_descriptor` instead to preserve private key data in the returned KeyMap",
),
}
}
}
Expand All @@ -570,7 +575,7 @@ impl error::Error for DescriptorKeyParseError {
Self::WifPrivateKey(err) => Some(err),
Self::XonlyPublicKey(err) => Some(err),
Self::XKeyParseError(err) => Some(err),
Self::MalformedKeyData(_) => None,
Self::MalformedKeyData(_) | Self::UnexpectedXPrivateKey => None,
}
}
}
Expand Down Expand Up @@ -735,50 +740,54 @@ impl FromStr for DescriptorPublicKey {

let (key_part, origin) = parse_key_origin(s)?;

if key_part.contains("pub") {
let (xpub, derivation_paths, wildcard) = parse_xkey_deriv(key_part)?;
if derivation_paths.len() > 1 {
Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
origin,
xkey: xpub,
derivation_paths: DerivPaths::new(derivation_paths).expect("Not empty"),
wildcard,
}))
} else {
Ok(DescriptorPublicKey::XPub(DescriptorXKey {
origin,
xkey: xpub,
derivation_path: derivation_paths.into_iter().next().unwrap_or_default(),
wildcard,
}))
}
} else {
let key = match key_part.len() {
64 => {
let x_only_key = XOnlyPublicKey::from_str(key_part)
.map_err(DescriptorKeyParseError::XonlyPublicKey)?;
SinglePubKey::XOnly(x_only_key)
match key_part.get(..4) {
Some("xprv" | "tprv") => Err(DescriptorKeyParseError::UnexpectedXPrivateKey),
Some("xpub" | "tpub") => {
let (xpub, derivation_paths, wildcard) = parse_xkey_deriv(key_part)?;
if derivation_paths.len() > 1 {
Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey {
origin,
xkey: xpub,
derivation_paths: DerivPaths::new(derivation_paths).expect("Not empty"),
wildcard,
}))
} else {
Ok(DescriptorPublicKey::XPub(DescriptorXKey {
origin,
xkey: xpub,
derivation_path: derivation_paths.into_iter().next().unwrap_or_default(),
wildcard,
}))
}
66 | 130 => {
if !(&key_part[0..2] == "02"
|| &key_part[0..2] == "03"
|| &key_part[0..2] == "04")
{
}
_ => {
let key = match key_part.len() {
64 => {
let x_only_key = XOnlyPublicKey::from_str(key_part)
.map_err(DescriptorKeyParseError::XonlyPublicKey)?;
SinglePubKey::XOnly(x_only_key)
}
66 | 130 => {
if !(&key_part[0..2] == "02"
|| &key_part[0..2] == "03"
|| &key_part[0..2] == "04")
{
return Err(DescriptorKeyParseError::MalformedKeyData(
MalformedKeyDataKind::InvalidFullPublicKeyPrefix,
));
}
let key = bitcoin::PublicKey::from_str(key_part)
.map_err(DescriptorKeyParseError::FullPublicKey)?;
SinglePubKey::FullKey(key)
}
_ => {
return Err(DescriptorKeyParseError::MalformedKeyData(
MalformedKeyDataKind::InvalidFullPublicKeyPrefix,
));
MalformedKeyDataKind::InvalidPublicKeyLength,
))
}
let key = bitcoin::PublicKey::from_str(key_part)
.map_err(DescriptorKeyParseError::FullPublicKey)?;
SinglePubKey::FullKey(key)
}
_ => {
return Err(DescriptorKeyParseError::MalformedKeyData(
MalformedKeyDataKind::InvalidPublicKeyLength,
))
}
};
Ok(DescriptorPublicKey::Single(SinglePub { key, origin }))
};
Ok(DescriptorPublicKey::Single(SinglePub { key, origin }))
}
}
}
}
Expand Down Expand Up @@ -1518,7 +1527,8 @@ mod test {
use serde_test::{assert_tokens, Token};

use super::{
DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, MiniscriptKey, Wildcard,
DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey,
MiniscriptKey, Wildcard,
};
use crate::descriptor::key::NonDefiniteKeyError;
use crate::prelude::*;
Expand Down Expand Up @@ -1574,6 +1584,13 @@ mod test {
DescriptorPublicKey::from_str(desc).unwrap_err().to_string(),
"only full public keys with prefixes '02', '03' or '04' are allowed"
);

// unexpected xprv key when attempting to parse a DescriptorPublicKey
let desc = "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc";
assert_eq!(
desc.parse::<DescriptorPublicKey>().unwrap_err().to_string(),
DescriptorKeyParseError::UnexpectedXPrivateKey.to_string()
);
}

#[test]
Expand Down
20 changes: 20 additions & 0 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2152,6 +2152,26 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))";
assert_eq!(descriptor_str, descriptor.to_string_with_secret(&keymap));
}

// https://github.com/bitcoinfuzz/bitcoinfuzz/issues/70
// https://github.com/rust-bitcoin/rust-miniscript/issues/785
#[test]
fn parse_descriptor_preserves_xprv_in_keymap() {
let secp = &secp256k1::Secp256k1::signing_only();

for desc in [
"pk(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/*)#dl4n4vmp",
// xprv containing "pub"
"pk(xprv9s21ZrQH143K3MByafknWupz9D5c3Wz3MrAZpC5KFxUuwwbefg6BVSWFwyUbEcqxGTpubCGtQyC3m3vm8rZkmN1TNoc7n6VdZBt5NeXdxwV/*)#nyw8pgdu",
] {
assert_eq!(desc.parse::<Descriptor<DescriptorPublicKey>>().unwrap_err().to_string(), DescriptorKeyParseError::UnexpectedXPrivateKey.to_string());

let (descriptor, key_map) =
Descriptor::<DescriptorPublicKey>::parse_descriptor(secp, desc).unwrap();
assert_eq!(key_map.len(), 1);
assert_eq!(descriptor.to_string_with_secret(&key_map), desc);
}
}

#[test]
fn checksum_for_nested_sh() {
let descriptor_str = "sh(wpkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL))";
Expand Down
Loading