diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 250e128ed..d108caab1 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -223,7 +223,8 @@ impl DescriptorXKey { let xprv = self .xkey .derive_priv(secp, &hardened_path) - .map_err(|_| DescriptorKeyParseError("Unable to derive the hardened steps"))?; + .map_err(DescriptorKeyParseError::DeriveHardenedKey)?; + let xpub = bip32::Xpub::from_priv(secp, &xprv); let origin = match &self.origin { @@ -276,7 +277,9 @@ impl DescriptorMultiXKey { if child_num.is_normal() { Ok(*child_num) } else { - Err(DescriptorKeyParseError("Can't make a multi-xpriv with hardened derivation steps that are not shared among all paths into a public key.")) + Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMultiXKeyDerivation, + )) } }) .collect() @@ -295,7 +298,7 @@ impl DescriptorMultiXKey { let xprv = self .xkey .derive_priv(secp, &hardened_path) - .map_err(|_| DescriptorKeyParseError("Unable to derive the hardened steps"))?; + .map_err(DescriptorKeyParseError::DeriveHardenedKey)?; let xpub = bip32::Xpub::from_priv(secp, &xprv); let origin = match &self.origin { @@ -324,18 +327,145 @@ impl DescriptorMultiXKey { } } +/// Kinds of malformed key data +#[derive(Debug, PartialEq, Clone)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum MalformedKeyDataKind { + EmptyKey, + EncounteredUnprintableCharacter, + InvalidFullPublicKeyPrefix, + InvalidMasterFingerprintLength, + InvalidMultiIndexStep, + InvalidMultiXKeyDerivation, + InvalidPublicKeyLength, + InvalidWildcardInDerivationPath, + KeyTooShort, + MultipleFingerprintsInPublicKey, + MultipleDerivationPathIndexSteps, + NoKeyAfterOrigin, + NoMasterFingerprintFound, + UnclosedSquareBracket, + WildcardAsDerivedDescriptorKey, +} + +impl fmt::Display for MalformedKeyDataKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use MalformedKeyDataKind::*; + + let err = match self { + EmptyKey => "empty key", + EncounteredUnprintableCharacter => "encountered an unprintable character", + InvalidFullPublicKeyPrefix => "only full public keys with prefixes '02', '03' or '04' are allowed", + InvalidMasterFingerprintLength => "master fingerprint should be 8 characters long", + InvalidMultiIndexStep => "invalid multi index step in multipath descriptor", + InvalidMultiXKeyDerivation => "can't make a multi-xpriv with hardened derivation steps that are not shared among all paths into a public key", + InvalidPublicKeyLength => "public keys must be 64, 66 or 130 characters in size", + InvalidWildcardInDerivationPath => "'*' may only appear as last element in a derivation path", + KeyTooShort => "key too short", + MultipleFingerprintsInPublicKey => "multiple ']' in Descriptor Public Key", + MultipleDerivationPathIndexSteps => "'<' may only appear once in a derivation path", + NoKeyAfterOrigin => "no key after origin", + NoMasterFingerprintFound => "no master fingerprint found after '['", + UnclosedSquareBracket => "unclosed '['", + WildcardAsDerivedDescriptorKey => "cannot parse key with a wilcard as a DerivedDescriptorKey", + }; + + f.write_str(err) + } +} + /// Descriptor Key parsing errors -// FIXME: replace with error enums -#[derive(Debug, PartialEq, Clone, Copy)] -pub struct DescriptorKeyParseError(&'static str); +#[derive(Debug, PartialEq, Clone)] +#[non_exhaustive] +pub enum DescriptorKeyParseError { + /// Error while parsing a BIP32 extended private key + Bip32Xpriv(bip32::Error), + /// Error while parsing a BIP32 extended public key + Bip32Xpub(bip32::Error), + /// Error while parsing a derivation index + DerivationIndexError { + /// The invalid index + index: String, + /// The underlying parse error + err: bitcoin::bip32::Error, + }, + /// Error deriving the hardened private key. + DeriveHardenedKey(bip32::Error), + /// Error indicating the key data was malformed + MalformedKeyData(MalformedKeyDataKind), + /// Error while parsing the master derivation path. + MasterDerivationPath(bip32::Error), + /// Error indicating a malformed master fingerprint (invalid hex). + MasterFingerprint { + /// The invalid fingerprint + fingerprint: String, + /// The underlying parse error + err: bitcoin::hex::HexToArrayError, + }, + /// Error while parsing a simple public key. + FullPublicKey(bitcoin::key::ParsePublicKeyError), + /// Error while parsing a WIF private key. + WifPrivateKey(bitcoin::key::FromWifError), + /// Error while parsing an X-only public key (Secp256k1 error). + XonlyPublicKey(bitcoin::secp256k1::Error), +} impl fmt::Display for DescriptorKeyParseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.0) } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DescriptorKeyParseError::Bip32Xpriv(err) => { + write!(f, "error while parsing BIP32 Xpriv: {err}") + } + DescriptorKeyParseError::Bip32Xpub(err) => { + write!(f, "error while parsing BIP32 Xpub: {err}") + } + DescriptorKeyParseError::DerivationIndexError { index, err } => { + write!(f, "error while parsing derivation index '{index}': {err}") + } + DescriptorKeyParseError::DeriveHardenedKey(err) => { + write!(f, "unable to derive the hardened steps: {err}") + } + DescriptorKeyParseError::MalformedKeyData(err) => { + write!(f, "{err}") + } + DescriptorKeyParseError::MasterDerivationPath(err) => { + write!(f, "error while parsing master derivation path: {err}") + } + DescriptorKeyParseError::MasterFingerprint { fingerprint, err } => { + write!(f, "error while parsing master fingerprint '{fingerprint}': {err}") + } + DescriptorKeyParseError::FullPublicKey(err) => { + write!(f, "error while parsing full public key: {err}") + } + DescriptorKeyParseError::WifPrivateKey(err) => { + write!(f, "error while parsing WIF private key: {err}") + } + DescriptorKeyParseError::XonlyPublicKey(err) => { + write!(f, "error while parsing xonly public key: {err}") + } + } + } } #[cfg(feature = "std")] impl error::Error for DescriptorKeyParseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DescriptorKeyParseError::*; + + match self { + Bip32Xpriv(err) + | Bip32Xpub(err) + | DerivationIndexError { err, .. } + | DeriveHardenedKey(err) + | MasterDerivationPath(err) => Some(err), + MasterFingerprint { err, .. } => Some(err), + FullPublicKey(err) => Some(err), + WifPrivateKey(err) => Some(err), + XonlyPublicKey(err) => Some(err), + MalformedKeyData(_) => None, + } + } } impl fmt::Display for DescriptorPublicKey { @@ -485,16 +615,15 @@ impl FromStr for DescriptorPublicKey { fn from_str(s: &str) -> Result { // A "raw" public key without any origin is the least we accept. if s.len() < 64 { - return Err(DescriptorKeyParseError( - "Key too short (<64 characters); use parse_descriptor for parsing \ - descriptors with private keys.", + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::KeyTooShort, )); } let (key_part, origin) = parse_key_origin(s)?; if key_part.contains("pub") { - let (xpub, derivation_paths, wildcard) = parse_xkey_deriv::(key_part)?; + let (xpub, derivation_paths, wildcard) = parse_xkey_deriv(parse_bip32_xpub, key_part)?; if derivation_paths.len() > 1 { Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey { origin, @@ -513,9 +642,8 @@ impl FromStr for DescriptorPublicKey { } else { let key = match key_part.len() { 64 => { - let x_only_key = XOnlyPublicKey::from_str(key_part).map_err(|_| { - DescriptorKeyParseError("Error while parsing simple xonly key") - })?; + let x_only_key = XOnlyPublicKey::from_str(key_part) + .map_err(DescriptorKeyParseError::XonlyPublicKey)?; SinglePubKey::XOnly(x_only_key) } 66 | 130 => { @@ -523,18 +651,17 @@ impl FromStr for DescriptorPublicKey { || &key_part[0..2] == "03" || &key_part[0..2] == "04") { - return Err(DescriptorKeyParseError( - "Only publickeys with prefixes 02/03/04 are allowed", + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidFullPublicKeyPrefix, )); } - let key = bitcoin::PublicKey::from_str(key_part).map_err(|_| { - DescriptorKeyParseError("Error while parsing simple public key") - })?; + let key = bitcoin::PublicKey::from_str(key_part) + .map_err(DescriptorKeyParseError::FullPublicKey)?; SinglePubKey::FullKey(key) } _ => { - return Err(DescriptorKeyParseError( - "Public keys must be 64/66/130 characters in size", + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidPublicKeyLength, )) } }; @@ -772,10 +899,11 @@ impl FromStr for DescriptorSecretKey { if key_part.len() <= 52 { let sk = bitcoin::PrivateKey::from_str(key_part) - .map_err(|_| DescriptorKeyParseError("Error while parsing a WIF private key"))?; + .map_err(DescriptorKeyParseError::WifPrivateKey)?; Ok(DescriptorSecretKey::Single(SinglePriv { key: sk, origin })) } else { - let (xpriv, derivation_paths, wildcard) = parse_xkey_deriv::(key_part)?; + let (xpriv, derivation_paths, wildcard) = + parse_xkey_deriv(parse_bip32_xpriv, key_part)?; if derivation_paths.len() > 1 { Ok(DescriptorSecretKey::MultiXPrv(DescriptorMultiXKey { origin, @@ -799,42 +927,57 @@ impl FromStr for DescriptorSecretKey { fn parse_key_origin(s: &str) -> Result<(&str, Option), DescriptorKeyParseError> { for ch in s.as_bytes() { if *ch < 20 || *ch > 127 { - return Err(DescriptorKeyParseError("Encountered an unprintable character")); + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::EncounteredUnprintableCharacter, + )); } } if s.is_empty() { - return Err(DescriptorKeyParseError("Empty key")); + return Err(DescriptorKeyParseError::MalformedKeyData(MalformedKeyDataKind::EmptyKey)); } let mut parts = s[1..].split(']'); if let Some('[') = s.chars().next() { let mut raw_origin = parts .next() - .ok_or(DescriptorKeyParseError("Unclosed '['"))? + .ok_or(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::UnclosedSquareBracket, + ))? .split('/'); let origin_id_hex = raw_origin .next() - .ok_or(DescriptorKeyParseError("No master fingerprint found after '['"))?; + .ok_or(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::NoMasterFingerprintFound, + ))?; if origin_id_hex.len() != 8 { - return Err(DescriptorKeyParseError("Master fingerprint should be 8 characters long")); + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMasterFingerprintLength, + )); } - let parent_fingerprint = bip32::Fingerprint::from_hex(origin_id_hex).map_err(|_| { - DescriptorKeyParseError("Malformed master fingerprint, expected 8 hex chars") + let parent_fingerprint = bip32::Fingerprint::from_hex(origin_id_hex).map_err(|err| { + DescriptorKeyParseError::MasterFingerprint { + fingerprint: origin_id_hex.to_owned(), + err, + } })?; let origin_path = raw_origin .map(bip32::ChildNumber::from_str) .collect::>() - .map_err(|_| DescriptorKeyParseError("Error while parsing master derivation path"))?; + .map_err(DescriptorKeyParseError::MasterDerivationPath)?; let key = parts .next() - .ok_or(DescriptorKeyParseError("No key after origin."))?; + .ok_or(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::NoKeyAfterOrigin, + ))?; if parts.next().is_some() { - Err(DescriptorKeyParseError("Multiple ']' in Descriptor Public Key")) + Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MultipleFingerprintsInPublicKey, + )) } else { Ok((key, Some((parent_fingerprint, origin_path)))) } @@ -843,16 +986,26 @@ fn parse_key_origin(s: &str) -> Result<(&str, Option), Descrip } } -/// Parse an extended key concatenated to a derivation path. -fn parse_xkey_deriv( +fn parse_bip32_xpub(xkey_str: &str) -> Result { + bip32::Xpub::from_str(xkey_str).map_err(DescriptorKeyParseError::Bip32Xpub) +} + +fn parse_bip32_xpriv(xkey_str: &str) -> Result { + bip32::Xpriv::from_str(xkey_str).map_err(DescriptorKeyParseError::Bip32Xpriv) +} + +fn parse_xkey_deriv( + parse_xkey_fn: impl Fn(&str) -> Result, key_deriv: &str, -) -> Result<(K, Vec, Wildcard), DescriptorKeyParseError> { +) -> Result<(Key, Vec, Wildcard), DescriptorKeyParseError> { let mut key_deriv = key_deriv.split('/'); let xkey_str = key_deriv .next() - .ok_or(DescriptorKeyParseError("No key found after origin description"))?; - let xkey = - K::from_str(xkey_str).map_err(|_| DescriptorKeyParseError("Error while parsing xkey."))?; + .ok_or(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::NoKeyAfterOrigin, + ))?; + + let xkey = parse_xkey_fn(xkey_str)?; let mut wildcard = Wildcard::None; let mut multipath = false; @@ -865,8 +1018,8 @@ fn parse_xkey_deriv( wildcard = Wildcard::Hardened; None } else if wildcard != Wildcard::None { - Some(Err(DescriptorKeyParseError( - "'*' may only appear as last element in a derivation path.", + Some(Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidWildcardInDerivationPath, ))) } else { // BIP389 defines a new step in the derivation path. This step contains two or more @@ -874,8 +1027,8 @@ fn parse_xkey_deriv( if p.starts_with('<') && p.ends_with('>') { // There may only be one occurence of this step. if multipath { - return Some(Err(DescriptorKeyParseError( - "'<' may only appear once in a derivation path.", + return Some(Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MultipleDerivationPathIndexSteps, ))); } multipath = true; @@ -883,8 +1036,8 @@ fn parse_xkey_deriv( // The step must contain at least two derivation indexes. // So it's at least '<' + a number + ';' + a number + '>'. if p.len() < 5 || !p.contains(';') { - return Some(Err(DescriptorKeyParseError( - "Invalid multi index step in multipath descriptor.", + return Some(Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMultiIndexStep, ))); } @@ -894,10 +1047,11 @@ fn parse_xkey_deriv( indexes .into_iter() .map(|s| { - bip32::ChildNumber::from_str(s).map_err(|_| { - DescriptorKeyParseError( - "Error while parsing index in key derivation path.", - ) + bip32::ChildNumber::from_str(s).map_err(|err| { + DescriptorKeyParseError::DerivationIndexError { + index: s.to_owned(), + err, + } }) }) .collect::, _>>(), @@ -907,8 +1061,9 @@ fn parse_xkey_deriv( Some( bip32::ChildNumber::from_str(p) .map(|i| vec![i]) - .map_err(|_| { - DescriptorKeyParseError("Error while parsing key derivation path") + .map_err(|err| DescriptorKeyParseError::DerivationIndexError { + index: p.to_owned(), + err, }), ) } @@ -1139,8 +1294,8 @@ impl FromStr for DefiniteDescriptorKey { fn from_str(s: &str) -> Result { let inner = DescriptorPublicKey::from_str(s)?; - DefiniteDescriptorKey::new(inner).ok_or(DescriptorKeyParseError( - "cannot parse key with a wilcard as a DerivedDescriptorKey", + DefiniteDescriptorKey::new(inner).ok_or(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::WildcardAsDerivedDescriptorKey, )) } } @@ -1215,8 +1370,7 @@ mod test { use serde_test::{assert_tokens, Token}; use super::{ - DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, - MiniscriptKey, Wildcard, + DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, MiniscriptKey, Wildcard, }; use crate::prelude::*; @@ -1225,52 +1379,50 @@ mod test { // And ones with misplaced wildcard let desc = "[78412e3a/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*/44"; assert_eq!( - DescriptorPublicKey::from_str(desc), - Err(DescriptorKeyParseError( - "\'*\' may only appear as last element in a derivation path." - )) + DescriptorPublicKey::from_str(desc).unwrap_err().to_string(), + "\'*\' may only appear as last element in a derivation path" ); // And ones with invalid fingerprints let desc = "[NonHexor]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*"; assert_eq!( - DescriptorPublicKey::from_str(desc), - Err(DescriptorKeyParseError("Malformed master fingerprint, expected 8 hex chars")) + DescriptorPublicKey::from_str(desc).unwrap_err().to_string(), + "error while parsing master fingerprint 'NonHexor': failed to parse hex digit" ); // And ones with invalid xpubs.. let desc = "[78412e3a]xpub1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaLcgJvLJuZZvRcEL/1/*"; assert_eq!( - DescriptorPublicKey::from_str(desc), - Err(DescriptorKeyParseError("Error while parsing xkey.")) + DescriptorPublicKey::from_str(desc).unwrap_err().to_string(), + "error while parsing BIP32 Xpub: base58 encoding error" ); // ..or invalid raw keys let desc = "[78412e3a]0208a117f3897c3a13c9384b8695eed98dc31bc2500feb19a1af424cd47a5d83/1/*"; assert_eq!( - DescriptorPublicKey::from_str(desc), - Err(DescriptorKeyParseError("Public keys must be 64/66/130 characters in size")) + DescriptorPublicKey::from_str(desc).unwrap_err().to_string(), + "public keys must be 64, 66 or 130 characters in size", ); // ..or invalid separators let desc = "[78412e3a]]03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8"; assert_eq!( - DescriptorPublicKey::from_str(desc), - Err(DescriptorKeyParseError("Multiple \']\' in Descriptor Public Key")) + DescriptorPublicKey::from_str(desc).unwrap_err().to_string(), + "multiple \']\' in Descriptor Public Key" ); // fuzzer errors let desc = "[11111f11]033333333333333333333333333333323333333333333333333333333433333333]]333]]3]]101333333333333433333]]]10]333333mmmm"; assert_eq!( - DescriptorPublicKey::from_str(desc), - Err(DescriptorKeyParseError("Multiple \']\' in Descriptor Public Key")) + DescriptorPublicKey::from_str(desc).unwrap_err().to_string(), + "multiple \']\' in Descriptor Public Key" ); // fuzz failure, hybrid keys let desc = "0777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777"; assert_eq!( - DescriptorPublicKey::from_str(desc), - Err(DescriptorKeyParseError("Only publickeys with prefixes 02/03/04 are allowed")) + DescriptorPublicKey::from_str(desc).unwrap_err().to_string(), + "only full public keys with prefixes '02', '03' or '04' are allowed" ); } @@ -1279,22 +1431,24 @@ mod test { // Xpubs are invalid let secret_key = "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"; assert_eq!( - DescriptorSecretKey::from_str(secret_key), - Err(DescriptorKeyParseError("Error while parsing xkey.")) + DescriptorSecretKey::from_str(secret_key) + .unwrap_err() + .to_string(), + "error while parsing BIP32 Xpriv: unknown version magic bytes: [4, 136, 178, 30]" ); // And ones with invalid fingerprints let desc = "[NonHexor]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/*"; assert_eq!( - DescriptorSecretKey::from_str(desc), - Err(DescriptorKeyParseError("Malformed master fingerprint, expected 8 hex chars")) + DescriptorSecretKey::from_str(desc).unwrap_err().to_string(), + "error while parsing master fingerprint 'NonHexor': failed to parse hex digit" ); // ..or invalid raw keys let desc = "[78412e3a]L32jTfVLei6BYTPUpwpJSkrHx8iL9GZzeErVS8y4Y/1/*"; assert_eq!( - DescriptorSecretKey::from_str(desc), - Err(DescriptorKeyParseError("Error while parsing a WIF private key")) + DescriptorSecretKey::from_str(desc).unwrap_err().to_string(), + "error while parsing WIF private key: invalid base58" ); } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 55ae6d009..5d8444022 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -50,7 +50,7 @@ mod key; pub use self::key::{ ConversionError, DefiniteDescriptorKey, DerivPaths, DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, InnerXKey, - SinglePriv, SinglePub, SinglePubKey, Wildcard, + MalformedKeyDataKind, SinglePriv, SinglePub, SinglePubKey, Wildcard, }; /// Alias type for a map of public key to secret key