diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 3c726501a..528b03c2f 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -534,6 +534,8 @@ pub enum DescriptorKeyParseError { XonlyPublicKey(bitcoin::secp256k1::Error), /// XKey parsing error XKeyParseError(XKeyParseError), + /// Unexpected XPrivateKey when parsing [`Descriptor`][crate::Descriptor]. + UnexpectedXPrivateKey, } impl fmt::Display for DescriptorKeyParseError { @@ -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", + ), } } } @@ -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, } } } @@ -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 })) + } } } } @@ -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::*; @@ -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::().unwrap_err().to_string(), + DescriptorKeyParseError::UnexpectedXPrivateKey.to_string() + ); } #[test] diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 368bc88b6..5a62f61aa 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -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::>().unwrap_err().to_string(), DescriptorKeyParseError::UnexpectedXPrivateKey.to_string()); + + let (descriptor, key_map) = + Descriptor::::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))";