diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 0f21f20c..d0a6e76a 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -25,6 +25,7 @@ roundtrip_descriptor, roundtrip_concrete, compile_descriptor, roundtrip_confidential, +parse_descriptor_definite, ] steps: - name: Install test dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index 629200b8..c71ba460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ - `ct()` descriptors accept `eltr` with script leaves that contain no extension fragments. - Miniscript and policy strings accept a trailing descriptor checksum, which is verified. The text after the last `#` is read as the checksum, so a key containing `#` is only accepted when a valid checksum follows. - `pegin()` and `legacy_pegin()` descriptors can be parsed from strings. `LegacyPegin` displays its inner descriptor without the inner checksum. A successful parse does not check the restrictions that pegin spending places on the descriptor. Unsupported forms, such as an `eltr` Elements descriptor, still parse and can panic in the unchanged Bitcoin-side methods, including `bitcoin_address`, `bitcoin_witness_script` and `get_bitcoin_satisfaction`. +- Breaking: `DescriptorKeyParseError` is a `#[non_exhaustive]` enum instead of a struct holding a string, and no longer derives `Copy`. The new `MalformedKeyDataKind` enum describes malformed key data. Messages start with a lowercase letter and follow rust-miniscript. The Elements-only `HexPrivateKey` variant reports a bad hex view key in a `ct()` descriptor. Ported from rust-miniscript 497c5a3 and bd5db6a. +- Breaking: `ConversionError` is replaced by the `#[non_exhaustive]` `NonDefiniteKeyError`, with `Wildcard`, `Multipath`, `HardenedStep` and `MaximumDepthExceeded` variants. It is returned by `DefiniteDescriptorKey::new`, `DescriptorPublicKey::at_derivation_index`, `Descriptor::at_derivation_index`, `Descriptor::derived_descriptor`, `Descriptor::find_derivation_index_for_spk`, `confidential::Descriptor::at_derivation_index` and the PSET `update_with_descriptor_unchecked` methods, and held by `UtxoUpdateError::DerivationError` and `OutputUpdateError::DerivationError`. `Error::Conversion` becomes `Error::NonDefiniteKey`. Ported from rust-miniscript 258873f, dad3532, 7b3b3a8, 4930ccb and 613ed0d. +- Breaking: `DefiniteDescriptorKey::new` is public and returns `Result`. Keys with a wildcard were already rejected. It now also rejects a hardened derivation step after an xpub (`HardenedStep`), several derivation paths (`Multipath`), and a derivation deeper than BIP32's maximum depth of 255 (`MaximumDepthExceeded`), checked in the order wildcard, hardened step, multipath, depth. `DescriptorPublicKey::at_derivation_index`, and the descriptor methods built on it, return the same errors. `DefiniteDescriptorKey::from_str` wraps them in `DescriptorKeyParseError::NonDefiniteKey`, so `Descriptor::::from_str` now rejects hardened and multipath keys, which previously parsed and panicked when derived, for example in `script_pubkey`. A `ct()` descriptor whose bare blinding key has a hardened wildcard now fails in `at_derivation_index` instead of `address`. Ported from rust-miniscript dad3532, 7b3b3a8, 97c948d and 613ed0d. +- Parsing an xpub or xprv whose derivation goes deeper than BIP32's maximum depth of 255 fails with `DescriptorKeyParseError::MalformedKeyData(MalformedKeyDataKind::MaximumDepthExceeded)`, so such a string never reaches the constructor. A wildcard or multipath step counts as one step, and origin steps don't count. The constructor's `NonDefiniteKeyError::MaximumDepthExceeded` is only returned for keys built in code. Ported from rust-miniscript f2862f7. +- Breaking: `DefiniteDescriptorKey::derive_public_key` returns a `bitcoin::PublicKey` and `Descriptor::::derived_descriptor` returns a descriptor, instead of a `Result`. Ported from rust-miniscript c169af0. +- Breaking: the deprecated `DescriptorPublicKey::is_deriveable`, `DescriptorPublicKey::derive`, `Descriptor::is_deriveable` and `Descriptor::derive` are removed. Use `has_wildcard` and `at_derivation_index`. Ported from rust-miniscript 77ed78b. +- Breaking: `UtxoUpdateError` and `OutputUpdateError` derive only `Debug`, `PartialEq`, `Eq` and `Clone`. `NonDefiniteKeyError`, `MalformedKeyDataKind` and `DescriptorKeyParseError` derive `Eq`. Ported from rust-miniscript 627d963. +- Add `DescriptorPublicKey::has_hardened_step`. Ported from rust-miniscript 7b3b3a8 and, for its doc line, 4906bed (partial). +- `DescriptorSecretKey::to_public` converts a multi-xpriv into a multi-xpub when all hardened derivation steps are shared among its paths, instead of always failing. Ported from rust-miniscript 7d3e1e8. # 0.5.0 - Sep 20, 2026 diff --git a/bitcoind-tests/tests/test_arith.rs b/bitcoind-tests/tests/test_arith.rs index c67e844f..8ddae8a4 100644 --- a/bitcoind-tests/tests/test_arith.rs +++ b/bitcoind-tests/tests/test_arith.rs @@ -49,7 +49,7 @@ pub fn test_desc_satisfy(cl: &ElementsD, testdata: &TestData, desc: &str) -> Vec .at_derivation_index(0) .unwrap(); - let derived_desc = definite_desc.derived_descriptor(&secp).unwrap(); + let derived_desc = definite_desc.derived_descriptor(&secp); let desc_address = derived_desc.address(&PARAMS).unwrap(); // No blinding // Next send some btc to each address corresponding to the miniscript diff --git a/bitcoind-tests/tests/test_csfs.rs b/bitcoind-tests/tests/test_csfs.rs index 05bb15be..243d1449 100644 --- a/bitcoind-tests/tests/test_csfs.rs +++ b/bitcoind-tests/tests/test_csfs.rs @@ -50,7 +50,7 @@ pub fn test_desc_satisfy(cl: &ElementsD, testdata: &TestData, desc: &str) -> Vec .at_derivation_index(0) .unwrap(); - let derived_desc = definite_desc.derived_descriptor(&secp).unwrap(); + let derived_desc = definite_desc.derived_descriptor(&secp); let desc_address = derived_desc.address(&PARAMS).unwrap(); // No blinding // Next send some btc to each address corresponding to the miniscript diff --git a/bitcoind-tests/tests/test_desc.rs b/bitcoind-tests/tests/test_desc.rs index 719dbb15..be66d804 100644 --- a/bitcoind-tests/tests/test_desc.rs +++ b/bitcoind-tests/tests/test_desc.rs @@ -86,7 +86,7 @@ pub fn test_desc_satisfy( .at_derivation_index(0) .unwrap(); - let derived_desc = definite_desc.derived_descriptor(&secp).unwrap(); + let derived_desc = definite_desc.derived_descriptor(&secp); let desc_address = derived_desc.address(&PARAMS); // No blinding let desc_address = desc_address.map_err(|_x| DescError::AddressComputationError)?; diff --git a/bitcoind-tests/tests/test_introspect.rs b/bitcoind-tests/tests/test_introspect.rs index e6aee474..4a1544d8 100644 --- a/bitcoind-tests/tests/test_introspect.rs +++ b/bitcoind-tests/tests/test_introspect.rs @@ -49,7 +49,7 @@ pub fn test_desc_satisfy(cl: &ElementsD, testdata: &TestData, desc: &str) -> Vec .at_derivation_index(0) .unwrap(); - let derived_desc = definite_desc.derived_descriptor(&secp).unwrap(); + let derived_desc = definite_desc.derived_descriptor(&secp); let desc_address = derived_desc.address(&PARAMS).unwrap(); // No blinding // Next send some btc to each address corresponding to the miniscript diff --git a/examples/xpub_descriptors.rs b/examples/xpub_descriptors.rs index d0e600d8..7ad1592c 100644 --- a/examples/xpub_descriptors.rs +++ b/examples/xpub_descriptors.rs @@ -46,7 +46,6 @@ fn p2wsh(secp: &Secp256k1) -> Address { let address = Descriptor::::from_str(&s) .unwrap() .derived_descriptor(secp) - .unwrap() .address(&elements::AddressParams::ELEMENTS) .unwrap(); let expected = elements::Address::from_str( diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 4f09e76e..cd6feb4c 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -48,3 +48,7 @@ path = "fuzz_targets/compile_descriptor.rs" [[bin]] name = "roundtrip_confidential" path = "fuzz_targets/roundtrip_confidential.rs" + +[[bin]] +name = "parse_descriptor_definite" +path = "fuzz_targets/parse_descriptor_definite.rs" diff --git a/fuzz/fuzz_targets/parse_descriptor_definite.rs b/fuzz/fuzz_targets/parse_descriptor_definite.rs new file mode 100644 index 00000000..886ff458 --- /dev/null +++ b/fuzz/fuzz_targets/parse_descriptor_definite.rs @@ -0,0 +1,23 @@ +#![allow(unexpected_cfgs)] + +extern crate elements_miniscript as miniscript; + +use honggfuzz::fuzz; +use miniscript::{DefiniteDescriptorKey, Descriptor}; + +fn do_test(data: &[u8]) { + let data_str = String::from_utf8_lossy(data); + + if let Ok(desc) = data_str.parse::>() { + let _ = desc.to_string(); + let _ = desc.address(&miniscript::elements::AddressParams::ELEMENTS); + } +} + +fn main() { + loop { + fuzz!(|data| { + do_test(data); + }); + } +} diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs index 4e8703e0..b72a4612 100644 --- a/src/confidential/mod.rs +++ b/src/confidential/mod.rs @@ -28,7 +28,7 @@ use elements::secp256k1_zkp; use crate::descriptor::checksum::{self, verify_checksum}; use crate::descriptor::{ - ConversionError, DefiniteDescriptorKey, DescriptorSecretKey, DescriptorPublicKey, + DefiniteDescriptorKey, DescriptorSecretKey, DescriptorPublicKey, NonDefiniteKeyError, DescriptorXKey, Wildcard }; use crate::expression::FromTree; @@ -81,12 +81,10 @@ impl Key { Err(Error::Unexpected("wildcard blinding key".into())) } else { // Convert into a DefiniteDescriptorKey, note that we are deriving the xpub - // since there is not wildcard. + // since there is not wildcard. This fails for a hardened derivation step. // Consider adding DescriptorPublicKey::to_definite_descriptor - let pk = pk.clone().at_derivation_index(0).expect("single or xpub without wildcards"); - // Derive explicitly rather than relying on ToPublicKey::to_public_key, - // which panics on keys with hardened derivation steps. - let pk = pk.derive_public_key(secp)?; + let pk = pk.clone().at_derivation_index(0)?; + let pk = pk.derive_public_key(secp); Ok(bare::tweak_key(secp, spk, &pk)) } }, @@ -98,7 +96,7 @@ impl Key { if pk.has_wildcard() { Err(Error::Unexpected("wildcard blinding key".into())) } else { - let pk = pk.at_derivation_index(0).expect("single or xprv without wildcards"); + let pk = pk.at_derivation_index(0)?; Ok(bare::tweak_key(secp, spk, &pk)) } } @@ -130,7 +128,7 @@ impl Descriptor { /// /// # Errors /// - If index ≥ 2^31 - pub fn at_derivation_index(&self, index: u32) -> Result, ConversionError> { + pub fn at_derivation_index(&self, index: u32) -> Result, NonDefiniteKeyError> { let definite_key = match self.key.clone() { Key::Slip77(k) => Key::Slip77(k), Key::Bare(k) => Key::Bare(k.at_derivation_index(index)?.into_descriptor_public_key()), @@ -143,12 +141,12 @@ impl Descriptor { Wildcard::Unhardened => xprv.derivation_path.into_child( bip32::ChildNumber::from_normal_idx(index) .ok() - .ok_or(ConversionError::HardenedChild)?, + .ok_or(NonDefiniteKeyError::HardenedStep)?, ), Wildcard::Hardened => xprv.derivation_path.into_child( bip32::ChildNumber::from_hardened_idx(index) .ok() - .ok_or(ConversionError::HardenedChild)?, + .ok_or(NonDefiniteKeyError::HardenedStep)?, ), }; DescriptorSecretKey::XPrv(DescriptorXKey { @@ -158,7 +156,7 @@ impl Descriptor { wildcard: Wildcard::None, }) }, - DescriptorSecretKey::MultiXPrv(_) => return Err(ConversionError::MultiKey), + DescriptorSecretKey::MultiXPrv(_) => return Err(NonDefiniteKeyError::Multipath), }), }; let definite_descriptor = self.descriptor.at_derivation_index(index)?; @@ -487,7 +485,7 @@ mod tests { ), ( "ct(L3jXxwef3fpB7hcrFozcWgHeJCPSAFiZ1Ji2YJMPxceaGvy3PC1q,elwpkh(03774eec7a3d550d18e9f89414152025b3b0ad6a342b19481f702d843cff06dfc4))#gcy6hcfz", - "unexpected «Key too short (<66 char), doesn't match any format»", + "unexpected «key too short»", ), ]; @@ -572,9 +570,8 @@ mod tests { assert!(matches!(desc.key, Key::View(_))); } - // A blinding key with a hardened derivation step cannot be derived from an xpub. This - // must be reported as an error rather than panicking in `ToPublicKey::to_public_key`, - // which unwraps the `ConversionError` returned by `derive_public_key`. + // A blinding key with a hardened derivation step cannot be derived from an xpub. This is + // reported as an error when the blinding key becomes definite. #[test] fn descriptor_hardened_blinding_key() { let secp = secp256k1_zkp::Secp256k1::new(); @@ -592,27 +589,23 @@ mod tests { assert!(matches!( desc.address(&secp, params).unwrap_err(), - Error::Conversion(ConversionError::HardenedChild) + Error::NonDefiniteKey(NonDefiniteKeyError::HardenedStep) )); } // Same case, reached through `at_derivation_index` on a hardened wildcard blinding - // key: the wildcard is replaced by a hardened child, which the xpub cannot derive. + // key: the wildcard is replaced by a hardened child, which the xpub cannot derive, so + // the descriptor does not become definite. #[test] fn descriptor_hardened_wildcard_blinding_key() { - let secp = secp256k1_zkp::Secp256k1::new(); - let params = &elements::AddressParams::LIQUID; - let xpub = "xpub661MyMwAqRbcEcT9W98HZP2kFzyzQQZkYnrRnrM8uD8kH8kSeFoQHq1x2iihLgC6PXGy5LrjCL66uSNhJ8pwjfx2rMUTLWuRMns2EG9xnjs"; let desc_str = format!("ct({}/*h,elwpkh({}/*))", xpub, xpub); let desc = Descriptor::::from_str(&desc_str).unwrap(); - let definite_desc = desc.at_derivation_index(1).unwrap(); - - assert!(matches!( - definite_desc.address(&secp, params).unwrap_err(), - Error::Conversion(ConversionError::HardenedChild) - )); + assert_eq!( + desc.at_derivation_index(1).unwrap_err(), + NonDefiniteKeyError::HardenedStep + ); } #[test] diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 7e457ced..9dcf6e57 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -6,7 +6,6 @@ use std::{error, fmt, hash}; use bitcoin::bip32::XKeyIdentifier; use bitcoin::hashes::{hash160, ripemd160, sha256, Hash, HashEngine}; -use bitcoin::hex::FromHex; use bitcoin::{self, bip32}; use elements::secp256k1_zkp::{Secp256k1, Signing, Verification}; @@ -276,7 +275,7 @@ 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 { @@ -305,6 +304,91 @@ impl DescriptorXKey { } } +impl DescriptorMultiXKey { + /// Returns the public version of this multi-key, applying all the hardened derivation steps that + /// are shared among all derivation paths before turning it into a public key. + /// + /// Errors if there are hardened derivation steps that are not shared among all paths. + fn to_public( + &self, + secp: &Secp256k1, + ) -> Result, DescriptorKeyParseError> { + let deriv_paths = self.derivation_paths.paths(); + + let shared_prefix: Vec<_> = deriv_paths[0] + .into_iter() + .enumerate() + .take_while(|(index, child_num)| { + deriv_paths[1..].iter().all(|other_path| { + other_path.len() > *index && other_path[*index] == **child_num + }) + }) + .map(|(_, child_num)| *child_num) + .collect(); + + let suffixes: Vec> = deriv_paths + .iter() + .map(|path| { + path.into_iter() + .skip(shared_prefix.len()) + .map(|child_num| { + if child_num.is_normal() { + Ok(*child_num) + } else { + Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::InvalidMultiXKeyDerivation, + )) + } + }) + .collect() + }) + .collect::>()?; + + let unhardened = shared_prefix + .iter() + .rev() + .take_while(|c| c.is_normal()) + .count(); + let last_hardened_idx = shared_prefix.len() - unhardened; + let hardened_path = &shared_prefix[..last_hardened_idx]; + let unhardened_path = &shared_prefix[last_hardened_idx..]; + + let xprv = self + .xkey + .derive_priv(secp, &hardened_path) + .map_err(DescriptorKeyParseError::DeriveHardenedKey)?; + let xpub = bip32::Xpub::from_priv(secp, &xprv); + + let origin = match &self.origin { + Some((fingerprint, path)) => Some(( + *fingerprint, + path.into_iter() + .chain(hardened_path.iter()) + .copied() + .collect(), + )), + None if !hardened_path.is_empty() => { + Some((self.xkey.fingerprint(secp), hardened_path.into())) + } + None => None, + }; + let new_deriv_paths = suffixes + .into_iter() + .map(|suffix| { + let path = unhardened_path.iter().copied().chain(suffix); + path.collect::>().into() + }) + .collect(); + + Ok(DescriptorMultiXKey { + origin, + xkey: xpub, + derivation_paths: DerivPaths::new(new_deriv_paths).expect("not empty"), + wildcard: self.wildcard, + }) + } +} + impl fmt::Display for DescriptorXKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { maybe_fmt_master_id(f, &self.origin)?; @@ -361,20 +445,161 @@ where } } +/// Kinds of malformed key data +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum NonDefiniteKeyError { + Wildcard, + Multipath, + HardenedStep, + MaximumDepthExceeded, +} + +impl fmt::Display for NonDefiniteKeyError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::Wildcard => f.write_str("key with a wildcard cannot be a DerivedDescriptorKey"), + Self::Multipath => f.write_str("multipath key cannot be a DerivedDescriptorKey"), + Self::HardenedStep => { + f.write_str("key with hardened derivation steps cannot be a DerivedDescriptorKey") + } + Self::MaximumDepthExceeded => f.write_str( + "key deeper than BIP32's maximum depth of 255 cannot be a DerivedDescriptorKey", + ), + } + } +} + +impl error::Error for NonDefiniteKeyError {} + +/// Kinds of malformed key data +#[derive(Debug, PartialEq, Eq, Clone)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum MalformedKeyDataKind { + EmptyKey, + EncounteredUnprintableCharacter, + InvalidFullPublicKeyPrefix, + InvalidMasterFingerprintLength, + InvalidMultiIndexStep, + InvalidMultiXKeyDerivation, + InvalidPublicKeyLength, + InvalidWildcardInDerivationPath, + KeyTooShort, + MaximumDepthExceeded, + MultipleFingerprintsInPublicKey, + MultipleDerivationPathIndexSteps, + NoKeyAfterOrigin, + NoMasterFingerprintFound, + UnclosedSquareBracket, +} + +impl fmt::Display for MalformedKeyDataKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let err = match self { + Self::EmptyKey => "empty key", + Self::EncounteredUnprintableCharacter => "encountered an unprintable character", + Self::InvalidFullPublicKeyPrefix => "only full public keys with prefixes '02', '03' or '04' are allowed", + Self::InvalidMasterFingerprintLength => "master fingerprint should be 8 characters long", + Self::InvalidMultiIndexStep => "invalid multi index step in multipath descriptor", + Self::InvalidMultiXKeyDerivation => "can't make a multi-xpriv with hardened derivation steps that are not shared among all paths into a public key", + Self::InvalidPublicKeyLength => "public keys must be 64, 66 or 130 characters in size", + Self::InvalidWildcardInDerivationPath => "'*' may only appear as last element in a derivation path", + Self::KeyTooShort => "key too short", + Self::MaximumDepthExceeded => { + "key derivation would go deeper than BIP32's maximum depth of 255" + } + Self::MultipleFingerprintsInPublicKey => "multiple ']' in Descriptor Public Key", + Self::MultipleDerivationPathIndexSteps => "'<' may only appear once in a derivation path", + Self::NoKeyAfterOrigin => "no key after origin", + Self::NoMasterFingerprintFound => "no master fingerprint found after '['", + Self::UnclosedSquareBracket => "unclosed '['", + }; + + 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, Eq, 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, + }, + /// Attempt to construct a [`DefiniteDescriptorKey`] from an ambiguous key. + NonDefiniteKey(NonDefiniteKeyError), + /// 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), + /// Error while parsing a hex private key, the form of a `ct()` view key. Elements only. + HexPrivateKey(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 { + Self::Bip32Xpriv(err) => err.fmt(f), + Self::Bip32Xpub(err) => err.fmt(f), + Self::DerivationIndexError { index, err } => { + write!(f, "at derivation index '{index}': {err}") + } + Self::DeriveHardenedKey(err) => err.fmt(f), + Self::MalformedKeyData(err) => err.fmt(f), + Self::MasterDerivationPath(err) => err.fmt(f), + Self::MasterFingerprint { fingerprint, err } => { + write!(f, "on master fingerprint '{fingerprint}': {err}") + } + Self::NonDefiniteKey(err) => err.fmt(f), + Self::FullPublicKey(err) => err.fmt(f), + Self::WifPrivateKey(err) => err.fmt(f), + Self::XonlyPublicKey(err) => err.fmt(f), + Self::HexPrivateKey(err) => err.fmt(f), + } } } impl error::Error for DescriptorKeyParseError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None + match self { + Self::Bip32Xpriv(err) + | Self::Bip32Xpub(err) + | Self::DerivationIndexError { err, .. } + | Self::DeriveHardenedKey(err) + | Self::MasterDerivationPath(err) => Some(err), + Self::MasterFingerprint { err, .. } => Some(err), + Self::NonDefiniteKey(err) => Some(err), + Self::FullPublicKey(err) => Some(err), + Self::WifPrivateKey(err) => Some(err), + Self::XonlyPublicKey(err) => Some(err), + Self::HexPrivateKey(err) => Some(err), + Self::MalformedKeyData(_) => None, + } } } @@ -394,9 +619,8 @@ impl DescriptorSecretKey { /// If the key is an "XPrv", the hardened derivation steps will be applied /// before converting it to a public key. /// - /// It will return an error if the key is a "multi-xpriv", as we wouldn't - /// always be able to apply hardened derivation steps if there are multiple - /// paths. + /// It will return an error if the key is a "multi-xpriv" that includes + /// hardened derivation steps not shared for all paths. pub fn to_public( &self, secp: &Secp256k1, @@ -404,10 +628,8 @@ impl DescriptorSecretKey { let pk = match self { DescriptorSecretKey::Single(prv) => DescriptorPublicKey::Single(prv.to_public(secp)), DescriptorSecretKey::XPrv(xprv) => DescriptorPublicKey::XPub(xprv.to_public(secp)?), - DescriptorSecretKey::MultiXPrv(_) => { - return Err(DescriptorKeyParseError( - "Can't make an extended private key with multiple paths into a public key.", - )) + DescriptorSecretKey::MultiXPrv(xprv) => { + DescriptorPublicKey::MultiXPub(xprv.to_public(secp)?) } }; @@ -506,15 +728,20 @@ 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 (<66 char), doesn't match any format", + 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_exceeds_max_depth(xpub.depth, &derivation_paths, wildcard) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MaximumDepthExceeded, + )); + } if derivation_paths.len() > 1 { Ok(DescriptorPublicKey::MultiXPub(DescriptorMultiXKey { origin, @@ -533,10 +760,8 @@ impl FromStr for DescriptorPublicKey { } else { let key = match key_part.len() { 64 => { - let x_only_key = - bitcoin::key::XOnlyPublicKey::from_str(key_part).map_err(|_| { - DescriptorKeyParseError("Error while parsing simple xonly key") - })?; + let x_only_key = bitcoin::key::XOnlyPublicKey::from_str(key_part) + .map_err(DescriptorKeyParseError::XonlyPublicKey)?; SinglePubKey::XOnly(x_only_key) } 66 | 130 => { @@ -544,18 +769,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, )) } }; @@ -564,34 +788,6 @@ impl FromStr for DescriptorPublicKey { } } -/// Descriptor key conversion error -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub enum ConversionError { - /// Attempted to convert a key with hardened derivations to a bitcoin public key - HardenedChild, - /// Attempted to convert a key with multiple derivation paths to a bitcoin public key - MultiKey, -} - -impl fmt::Display for ConversionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match *self { - ConversionError::HardenedChild => "hardened child step in bip32 path", - ConversionError::MultiKey => "multiple existing keys", - }) - } -} - -impl error::Error for ConversionError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use self::ConversionError::*; - - match self { - HardenedChild | MultiKey => None, - } - } -} - impl DescriptorPublicKey { /// The fingerprint of the master key associated with this key, `0x00000000` if none. pub fn master_fingerprint(&self) -> bip32::Fingerprint { @@ -659,12 +855,6 @@ impl DescriptorPublicKey { } } - /// Whether or not the key has a wildcard - #[deprecated(note = "use has_wildcard instead")] - pub fn is_deriveable(&self) -> bool { - self.has_wildcard() - } - /// Whether or not the key has a wildcard pub fn has_wildcard(&self) -> bool { match *self { @@ -674,10 +864,39 @@ impl DescriptorPublicKey { } } - #[deprecated(note = "use at_derivation_index instead")] - /// Deprecated name for [`Self::at_derivation_index`]. - pub fn derive(self, index: u32) -> Result { - self.at_derivation_index(index) + /// Whether or not the key has a hardened step in path + pub fn has_hardened_step(&self) -> bool { + let paths = match self { + DescriptorPublicKey::Single(..) => &[], + DescriptorPublicKey::XPub(xpub) => core::slice::from_ref(&xpub.derivation_path), + DescriptorPublicKey::MultiXPub(xpub) => &xpub.derivation_paths.paths()[..], + }; + for p in paths { + for step in p.into_iter() { + if step.is_hardened() { + return true; + } + } + } + false + } + + /// Whether deriving this key, including a wildcard step, would go deeper than BIP32's maximum + /// depth of 255. + fn exceeds_max_depth(&self) -> bool { + match self { + Self::Single(..) => false, + Self::XPub(xpub) => derivation_exceeds_max_depth( + xpub.xkey.depth, + core::slice::from_ref(&xpub.derivation_path), + xpub.wildcard, + ), + Self::MultiXPub(xpub) => derivation_exceeds_max_depth( + xpub.xkey.depth, + xpub.derivation_paths.paths(), + xpub.wildcard, + ), + } } /// Replaces any wildcard (i.e. `/*`) in the key with a particular derivation index, turning it into a @@ -692,7 +911,13 @@ impl DescriptorPublicKey { /// # Errors /// /// - If `index` is hardened. - pub fn at_derivation_index(self, index: u32) -> Result { + /// - If derivation path contains a hardened step. + /// - If the key contains multi-path derivations + /// - If deriving the key would go deeper than BIP32's maximum depth of 255 + pub fn at_derivation_index( + self, + index: u32, + ) -> Result { let definite = match self { DescriptorPublicKey::Single(_) => self, DescriptorPublicKey::XPub(xpub) => { @@ -701,12 +926,12 @@ impl DescriptorPublicKey { Wildcard::Unhardened => xpub.derivation_path.into_child( bip32::ChildNumber::from_normal_idx(index) .ok() - .ok_or(ConversionError::HardenedChild)?, + .ok_or(NonDefiniteKeyError::HardenedStep)?, ), Wildcard::Hardened => xpub.derivation_path.into_child( bip32::ChildNumber::from_hardened_idx(index) .ok() - .ok_or(ConversionError::HardenedChild)?, + .ok_or(NonDefiniteKeyError::HardenedStep)?, ), }; DescriptorPublicKey::XPub(DescriptorXKey { @@ -716,11 +941,10 @@ impl DescriptorPublicKey { wildcard: Wildcard::None, }) } - DescriptorPublicKey::MultiXPub(_) => return Err(ConversionError::MultiKey), + DescriptorPublicKey::MultiXPub(_) => return Err(NonDefiniteKeyError::Multipath), }; - Ok(DefiniteDescriptorKey::new(definite) - .expect("The key should not contain any wildcards at this point")) + DefiniteDescriptorKey::new(definite) } /// Whether or not this key has multiple derivation paths. @@ -771,24 +995,29 @@ impl DescriptorSecretKey { let (key_part, origin) = parse_key_origin(s)?; if single_hex && key_part.len() == 64 { - let bytes = Vec::::from_hex(key_part) - .map_err(|_| DescriptorKeyParseError("Error while parsing a HEX private key"))?; + let sk = bitcoin::secp256k1::SecretKey::from_str(key_part) + .map_err(DescriptorKeyParseError::HexPrivateKey)?; let network = bitcoin::network::Network::Bitcoin; // Use any network - let sk = bitcoin::PrivateKey::from_slice(&bytes, network) - .map_err(|_| DescriptorKeyParseError("Error while parsing a HEX private key"))?; + let sk = bitcoin::PrivateKey::new(sk, network); Ok(DescriptorSecretKey::Single(SinglePriv { key: sk, origin: None, })) } else if !single_hex && 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: None, })) } 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_exceeds_max_depth(xpriv.depth, &derivation_paths, wildcard) { + return Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MaximumDepthExceeded, + )); + } if derivation_paths.len() > 1 { Ok(DescriptorSecretKey::MultiXPrv(DescriptorMultiXKey { origin, @@ -820,47 +1049,56 @@ 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 '['", - ))?; + let origin_id_hex = raw_origin + .next() + .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_str(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)))) @@ -870,16 +1108,40 @@ fn parse_key_origin(s: &str) -> Result<(&str, Option), Descrip } } -/// Parse an extended key concatenated to a derivation path. -fn parse_xkey_deriv( +/// Whether deriving from an extended key at `depth` along any of `paths`, plus one more step for a +/// wildcard, would go deeper than BIP32's maximum depth of 255. +/// +/// The paths hold only the steps after the extended key. Its depth already includes the steps of +/// its origin, so those are not counted again. +fn derivation_exceeds_max_depth( + depth: u8, + paths: &[bip32::DerivationPath], + wildcard: Wildcard, +) -> bool { + let longest = paths.iter().map(|path| path.len()).max().unwrap_or(0); + longest + usize::from(wildcard != Wildcard::None) > usize::from(u8::MAX - depth) +} + +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."))?; + let xkey_str = key_deriv + .next() + .ok_or(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::NoKeyAfterOrigin, + ))?; + + let xkey = parse_xkey_fn(xkey_str)?; let mut wildcard = Wildcard::None; let mut multipath = false; @@ -892,8 +1154,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 @@ -901,8 +1163,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; @@ -910,8 +1172,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, ))); } @@ -921,10 +1183,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::, _>>(), @@ -934,8 +1197,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, }), ) } @@ -1095,41 +1359,45 @@ impl DefiniteDescriptorKey { /// avoid this error you should replace any such public keys first with [`translate_pk`]. /// /// [`translate_pk`]: crate::TranslatePk::translate_pk - pub fn derive_public_key( - &self, - secp: &Secp256k1, - ) -> Result { + pub fn derive_public_key(&self, secp: &Secp256k1) -> bitcoin::PublicKey { match self.0 { DescriptorPublicKey::Single(ref pk) => match pk.key { - SinglePubKey::FullKey(pk) => Ok(pk), - SinglePubKey::XOnly(xpk) => Ok(xpk.to_public_key()), + SinglePubKey::FullKey(pk) => pk, + SinglePubKey::XOnly(xpk) => xpk.to_public_key(), }, DescriptorPublicKey::XPub(ref xpk) => match xpk.wildcard { Wildcard::Unhardened | Wildcard::Hardened => { - unreachable!("we've excluded this error case") + unreachable!("impossible by construction of DefiniteDescriptorKey") } Wildcard::None => match xpk.xkey.derive_pub(secp, &xpk.derivation_path.as_ref()) { - Ok(xpub) => Ok(bitcoin::PublicKey::new(xpub.public_key)), + Ok(xpub) => bitcoin::PublicKey::new(xpub.public_key), Err(bip32::Error::CannotDeriveFromHardenedKey) => { - Err(ConversionError::HardenedChild) + unreachable!("impossible by construction of DefiniteDescriptorKey") } Err(e) => unreachable!("cryptographically unreachable: {}", e), }, }, DescriptorPublicKey::MultiXPub(_) => { - unreachable!("A definite key cannot contain a multipath key.") + unreachable!("impossible by construction of DefiniteDescriptorKey") } } } - /// Construct an instance from a descriptor key and a derivation index + /// Constructs an instance from a descriptor key. /// - /// Returns `None` if the key contains a wildcard - fn new(key: DescriptorPublicKey) -> Option { + /// Returns an error if the key has a wildcard, a hardened derivation step or several derivation + /// paths, or if deriving it would go deeper than BIP32's maximum depth of 255. + pub fn new(key: DescriptorPublicKey) -> Result { if key.has_wildcard() { - None + Err(NonDefiniteKeyError::Wildcard) + } else if key.has_hardened_step() { + Err(NonDefiniteKeyError::HardenedStep) + } else if key.is_multipath() { + Err(NonDefiniteKeyError::Multipath) + } else if key.exceeds_max_depth() { + Err(NonDefiniteKeyError::MaximumDepthExceeded) } else { - Some(Self(key)) + Ok(Self(key)) } } @@ -1158,10 +1426,8 @@ impl FromStr for DefiniteDescriptorKey { type Err = DescriptorKeyParseError; 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", - )) + let d = DescriptorPublicKey::from_str(s)?; + DefiniteDescriptorKey::new(d).map_err(DescriptorKeyParseError::NonDefiniteKey) } } @@ -1193,7 +1459,7 @@ impl MiniscriptKey for DefiniteDescriptorKey { impl ToPublicKey for DefiniteDescriptorKey { fn to_public_key(&self) -> bitcoin::PublicKey { let secp = Secp256k1::verification_only(); - self.derive_public_key(&secp).unwrap() + self.derive_public_key(&secp) } fn to_sha256(hash: &sha256::Hash) -> sha256::Hash { @@ -1257,70 +1523,60 @@ mod test { use super::{ DescriptorKeyParseError, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, - MiniscriptKey, Wildcard, + DescriptorXKey, MalformedKeyDataKind, MiniscriptKey, Wildcard, }; + use crate::descriptor::key::NonDefiniteKeyError; + use crate::{DefiniteDescriptorKey, Descriptor}; #[test] fn parse_descriptor_key_errors() { // 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(), + "on 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(), + "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" ); } @@ -1329,27 +1585,22 @@ 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(), + "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(), + "on 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" - )) - ); + assert_eq!(DescriptorSecretKey::from_str(desc).unwrap_err().to_string(), "invalid base58"); } #[test] @@ -1629,6 +1880,20 @@ mod test { DescriptorPublicKey::from_str("tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/2/4/<0;1;>").unwrap_err(); } + #[test] + fn test_multixprv_to_public() { + let secp = secp256k1::Secp256k1::signing_only(); + + // Works if all hardended derivation steps are part of the shared path + let xprv = get_multipath_xprv("[01020304/5]tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1'/2'/3/<4;5>/6"); + let xpub = DescriptorPublicKey::MultiXPub(xprv.to_public(&secp).unwrap()); // wrap in a DescriptorPublicKey to have Display + assert_eq!(xpub.to_string(), "[01020304/5/1'/2']tpubDBTRkEMEFkUbk3WTz6CFSULyswkTPpPr38AWibf5TVkB5GxuBxbSbmdFGr3jmswwemknyYxAGoX7BJnKfyPy4WXaHmcrxZhfzFwoUFvFtm5/3/<4;5>/6"); + + // Fails if they're part of the multi-path specifier or following it + get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3';4'>/5").to_public(&secp).unwrap_err(); + get_multipath_xprv("tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/2/<3;4>/5/6'").to_public(&secp).unwrap_err(); + } + #[test] #[cfg(feature = "serde")] fn test_descriptor_public_key_serde() { @@ -1636,4 +1901,116 @@ mod test { let public_key = DescriptorPublicKey::from_str(desc).unwrap(); assert_tokens(&public_key, &[Token::String(desc)]); } + + #[test] + fn definite_keys() { + // basic xpub + let desc = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" + .parse::() + .unwrap(); + assert!(matches!(DefiniteDescriptorKey::new(desc), Ok(..))); + // xpub with wildcard + let desc = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/*" + .parse::() + .unwrap(); + assert!(matches!(DefiniteDescriptorKey::new(desc), Err(NonDefiniteKeyError::Wildcard))); + // multipath xpub + let desc = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/<0;1>" + .parse::() + .unwrap(); + assert!(matches!(DefiniteDescriptorKey::new(desc), Err(NonDefiniteKeyError::Multipath))); + // xpub with hardened path + let desc = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/1'/2" + .parse::() + .unwrap(); + assert!(matches!( + DefiniteDescriptorKey::new(desc), + Err(NonDefiniteKeyError::HardenedStep) + )); + } + + #[test] + fn definite_key_maximum_depth() { + let key = |depth: u8, steps: usize, wildcard: Wildcard| { + let mut xkey = bip32::Xpub::from_str("xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8").unwrap(); + xkey.depth = depth; + DescriptorPublicKey::XPub(DescriptorXKey { + origin: None, + xkey, + derivation_path: vec![bip32::ChildNumber::from(0); steps].into(), + wildcard, + }) + }; + let new = |key| DefiniteDescriptorKey::new(key).map(|_| ()); + let too_deep = Err(NonDefiniteKeyError::MaximumDepthExceeded); + + // BIP32 depth stops at 255, so an xpub at `depth` can take 255 - depth more steps. + assert_eq!(new(key(0, 255, Wildcard::None)), Ok(())); + assert_eq!(new(key(0, 256, Wildcard::None)), too_deep); + assert_eq!(new(key(200, 55, Wildcard::None)), Ok(())); + assert_eq!(new(key(200, 56, Wildcard::None)), too_deep); + + // Deriving at an index turns the wildcard into one more step. + assert!(key(200, 54, Wildcard::Unhardened) + .at_derivation_index(7) + .is_ok()); + assert_eq!( + key(200, 55, Wildcard::Unhardened) + .at_derivation_index(7) + .map(|_| ()), + too_deep + ); + + // Origin steps don't count, since the xpub's depth already includes them. + let mut key_with_origin = key(0, 255, Wildcard::None); + if let DescriptorPublicKey::XPub(ref mut xpub) = key_with_origin { + xpub.origin = Some(( + bip32::Fingerprint::default(), + vec![bip32::ChildNumber::from(0); 300].into(), + )); + } + assert_eq!(new(key_with_origin), Ok(())); + + // A descriptor with such a key doesn't become definite, so it can't panic later when + // deriving its script pubkey. + let desc = Descriptor::::new_tr(key(0, 256, Wildcard::None), None) + .unwrap(); + assert_eq!(desc.at_derivation_index(0).map(|_| ()), too_deep); + } + + #[test] + fn parse_key_maximum_depth() { + // BIP32 test vector 1 master keys, at depth 0. + let xpub = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"; + let xprv = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"; + let steps = |n: usize| "/0".repeat(n); + let public = |s: String| DescriptorPublicKey::from_str(&s).map(|_| ()); + let secret = |s: String| DescriptorSecretKey::from_str(&s).map(|_| ()); + let too_deep = Err(DescriptorKeyParseError::MalformedKeyData( + MalformedKeyDataKind::MaximumDepthExceeded, + )); + + // BIP32 depth stops at 255. A wildcard or a multipath step counts as one more step. + assert_eq!(public(format!("{xpub}{}", steps(255))), Ok(())); + assert_eq!(public(format!("{xpub}{}", steps(256))), too_deep); + assert_eq!(public(format!("{xpub}{}/*", steps(254))), Ok(())); + assert_eq!(public(format!("{xpub}{}/*", steps(255))), too_deep); + assert_eq!(public(format!("{xpub}{}/<0;1>", steps(254))), Ok(())); + assert_eq!(public(format!("{xpub}{}/<0;1>", steps(255))), too_deep); + assert_eq!(secret(format!("{xprv}{}/*", steps(254))), Ok(())); + assert_eq!(secret(format!("{xprv}{}/*", steps(255))), too_deep); + + // An xpub at depth 200 can take 55 more steps. + let mut xkey = bip32::Xpub::from_str(xpub).unwrap(); + xkey.depth = 200; + assert_eq!(public(format!("{xkey}{}", steps(55))), Ok(())); + assert_eq!(public(format!("{xkey}{}", steps(56))), too_deep); + + // Origin steps don't count, since the xpub's depth already includes them. + assert_eq!(public(format!("[d34db33f{}]{xpub}{}", steps(300), steps(255))), Ok(())); + + // A descriptor with such a key fails to parse, before anything can be derived from it. + let desc = format!("eltr({xpub}{})", steps(256)); + assert!(Descriptor::::from_str(&desc).is_err()); + } } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 68f67928..afa1a6c7 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -52,9 +52,9 @@ mod key; pub use self::csfs_cov::{CovError, CovOperations, LegacyCSFSCov, LegacyCovSatisfier}; pub(crate) use self::key::maybe_fmt_master_id; pub use self::key::{ - ConversionError, DefiniteDescriptorKey, DerivPaths, DescriptorKeyParseError, - DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, InnerXKey, - SinglePriv, SinglePub, SinglePubKey, Wildcard, + DefiniteDescriptorKey, DerivPaths, DescriptorKeyParseError, DescriptorMultiXKey, + DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, InnerXKey, MalformedKeyDataKind, + NonDefiniteKeyError, SinglePriv, SinglePub, SinglePubKey, Wildcard, }; pub use self::tr::{TapLeafScript, TapTree, Tr}; /// Alias type for a map of public key to secret key @@ -808,12 +808,6 @@ impl ForEachKey for Descriptor { } impl Descriptor { - /// Whether or not the descriptor has any wildcards - #[deprecated(note = "use has_wildcards instead")] - pub fn is_deriveable(&self) -> bool { - self.has_wildcard() - } - /// Whether or not the descriptor has any wildcards i.e. `/*`. pub fn has_wildcard(&self) -> bool { self.for_any_key(|key| key.has_wildcard()) @@ -824,34 +818,29 @@ impl Descriptor { /// /// # Errors /// - If index ≥ 2^31 + /// - If the descriptor contains multi-path derivations + /// - If deriving a key would go deeper than BIP32's maximum depth of 255 pub fn at_derivation_index( &self, index: u32, - ) -> Result, ConversionError> { + ) -> Result, NonDefiniteKeyError> { struct Derivator(u32); - impl Translator for Derivator { + impl Translator + for Derivator + { fn pk( &mut self, pk: &DescriptorPublicKey, - ) -> Result { + ) -> Result { pk.clone().at_derivation_index(self.0) } - translate_hash_clone!(DescriptorPublicKey, DescriptorPublicKey, ConversionError); + translate_hash_clone!(DescriptorPublicKey, DescriptorPublicKey, NonDefiniteKeyError); } self.translate_pk(&mut Derivator(index)) } - #[deprecated(note = "use at_derivation_index instead")] - /// Deprecated name for [`Self::at_derivation_index`]. - pub fn derive( - &self, - index: u32, - ) -> Result, ConversionError> { - self.at_derivation_index(index) - } - /// Convert all the public keys in the descriptor to [`bitcoin::PublicKey`] by deriving them or /// otherwise converting them. All [`bitcoin::key::XOnlyPublicKey`]s are converted to by adding a /// default(0x02) y-coordinate. @@ -865,7 +854,7 @@ impl Descriptor { /// .expect("Valid ranged descriptor"); /// # let index = 42; /// # let secp = Secp256k1::verification_only(); - /// let derived_descriptor = descriptor.at_derivation_index(index).unwrap().derived_descriptor(&secp).unwrap(); + /// let derived_descriptor = descriptor.at_derivation_index(index).unwrap().derived_descriptor(&secp); /// # assert_eq!(descriptor.derived_descriptor(&secp, index).unwrap(), derived_descriptor); /// ``` /// @@ -876,13 +865,14 @@ impl Descriptor { /// /// # Errors /// - /// This function will return an error if hardened derivation is attempted. + /// This function will return an error for multi-path descriptors, if hardened derivation is + /// attempted, or if deriving a key would go deeper than BIP32's maximum depth of 255. pub fn derived_descriptor( &self, secp: &secp256k1_zkp::Secp256k1, index: u32, - ) -> Result, ConversionError> { - self.at_derivation_index(index)?.derived_descriptor(secp) + ) -> Result, NonDefiniteKeyError> { + Ok(self.at_derivation_index(index)?.derived_descriptor(secp)) } /// Parse a descriptor that may contain secret keys @@ -1078,7 +1068,7 @@ impl Descriptor { secp: &secp256k1_zkp::Secp256k1, script_pubkey: &Script, range: Range, - ) -> Result)>, ConversionError> { + ) -> Result)>, NonDefiniteKeyError> { let range = if self.has_wildcard() { range } else { 0..1 }; for i in range { @@ -1145,7 +1135,7 @@ impl Descriptor { /// let secp = secp256k1::Secp256k1::verification_only(); /// let descriptor = Descriptor::::from_str("eltr(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/*)") /// .expect("Valid ranged descriptor"); - /// let result = descriptor.at_derivation_index(0).unwrap().derived_descriptor(&secp).expect("Non-hardened derivation"); + /// let result = descriptor.at_derivation_index(0).unwrap().derived_descriptor(&secp); /// assert_eq!(result.to_string(), "eltr(03cc8a4bc64d897bddc5fbc2f670f7a8ba0b386779106cf1223c6fc5d7cd6fc115)#hr5pt2wj"); /// ``` /// @@ -1155,25 +1145,31 @@ impl Descriptor { pub fn derived_descriptor( &self, secp: &secp256k1::Secp256k1, - ) -> Result, ConversionError> { + ) -> Descriptor { struct Derivator<'a, C: secp256k1::Verification>(&'a secp256k1::Secp256k1); impl<'a, C: secp256k1::Verification> - Translator + Translator for Derivator<'a, C> { fn pk( &mut self, pk: &DefiniteDescriptorKey, - ) -> Result { - pk.derive_public_key(self.0) + ) -> Result { + Ok(pk.derive_public_key(self.0)) } - translate_hash_clone!(DefiniteDescriptorKey, bitcoin::PublicKey, ConversionError); + translate_hash_clone!( + DefiniteDescriptorKey, + bitcoin::PublicKey, + core::convert::Infallible + ); } - let derived = self.translate_pk(&mut Derivator(secp))?; - Ok(derived) + match self.translate_pk(&mut Derivator(secp)) { + Ok(derived) => derived, + Err(e) => match e {}, + } } } @@ -2010,14 +2006,12 @@ mod tests { .at_derivation_index(index) .unwrap() .derived_descriptor(&secp_ctx) - .unwrap() .address(&elements::AddressParams::ELEMENTS) .unwrap(); let addr_two = desc_two .at_derivation_index(index) .unwrap() .derived_descriptor(&secp_ctx) - .unwrap() .address(&elements::AddressParams::ELEMENTS) .unwrap(); let addr_expected = elements::Address::from_str(raw_addr_expected).unwrap(); diff --git a/src/descriptor/pegin/dynafed_pegin.rs b/src/descriptor/pegin/dynafed_pegin.rs index 68911caf..0495e94e 100644 --- a/src/descriptor/pegin/dynafed_pegin.rs +++ b/src/descriptor/pegin/dynafed_pegin.rs @@ -69,7 +69,7 @@ impl Pegin { secp: &secp256k1_zkp::Secp256k1, ) -> Result, Error> { let elem_desc = self.elem_desc.at_derivation_index(arg)?; - let elem_desc = elem_desc.derived_descriptor(secp)?; + let elem_desc = elem_desc.derived_descriptor(secp); Ok(Pegin::new(self.fed_desc.clone(), elem_desc)) } } @@ -386,7 +386,7 @@ mod tests { let elem_desc = "ct(slip77(ab5824f4477b4ebb00a132adfd8eb0b7935cf24f6ac151add5d1913db374ce92),elwpkh([759db348/84'/1'/0']tpubDCRMaF33e44pcJj534LXVhFbHibPbJ5vuLhSSPFAw57kYURv4tzXFL6LSnd78bkjqdmE3USedkbpXJUPA1tdzKfuYSL7PianceqAhwL2UkA/0/*))"; let elem_desc: ConfidentialDescriptor = elem_desc.parse().unwrap(); let elem_desc = elem_desc.descriptor.at_derivation_index(0).unwrap(); - let elem_desc = elem_desc.derived_descriptor(&secp).unwrap(); + let elem_desc = elem_desc.derived_descriptor(&secp); let pegin = Pegin::new(d.clone(), elem_desc); assert_eq!(pegin.to_string(), "pegin(wsh(or_d(multi(11,020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261,02675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af99,02896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d48,029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c,02a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc4010,031c41fdbcebe17bec8d49816e00ca1b5ac34766b91c9f2ac37d39c63e5e008afb,03079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b,03111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2,0318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa0840174,03230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de1,035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a6,03bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c,03cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d17546,03d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d424828,03ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a),and_v(v:older(4032),multi(2,03aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79,0291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807,0386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb)))),elwpkh(0321da398ca2ddc09be89caa26e6730ae84751b6ea3a1ca46aa365bb5e1c3d9620))#qp4fan9q"); diff --git a/src/lib.rs b/src/lib.rs index d76928e4..b4948cd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -367,8 +367,8 @@ pub enum Error { /// The federation descriptor has a type not supported UnsupportedAddressForPegin, - /// Conversion error in descriptor - Conversion(descriptor::ConversionError), + /// A descriptor key cannot become a [`descriptor::DefiniteDescriptorKey`] + NonDefiniteKey(descriptor::NonDefiniteKeyError), } #[doc(hidden)] @@ -453,9 +453,9 @@ impl From for Error { } } -impl From for Error { - fn from(e: descriptor::ConversionError) -> Error { - Error::Conversion(e) +impl From for Error { + fn from(e: descriptor::NonDefiniteKeyError) -> Error { + Error::NonDefiniteKey(e) } } @@ -533,7 +533,7 @@ impl fmt::Display for Error { Error::MultiATooManyKeys(k) => write!(f, "MultiA too many keys {}", k), Error::TrNoScriptCode => write!(f, "No script code for Tr descriptors"), Error::MultipathDescLenMismatch => write!(f, "At least two BIP389 key expressions in the descriptor contain tuples of derivation indexes of different lengths"), - Error::Conversion(ref e) => e.fmt(f), + Error::NonDefiniteKey(ref e) => e.fmt(f), Error::UnsupportedAddressForPegin => write!(f, "Cannot create the address from the pegin descriptor, the federation descriptor is of an unsuppported type"), } @@ -586,7 +586,7 @@ impl error::Error for Error { ContextError(e) => Some(e), AnalysisError(e) => Some(e), PubKeyCtxError(e, _) => Some(e), - Conversion(e) => Some(e), + NonDefiniteKey(e) => Some(e), } } } diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 9fad0318..fd3a5e1d 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -1008,14 +1008,14 @@ pub trait PsbtInputExt { fn update_with_descriptor_unchecked( &mut self, descriptor: &Descriptor>, - ) -> Result>, descriptor::ConversionError>; + ) -> Result>, descriptor::NonDefiniteKeyError>; } impl PsbtInputExt for psbt::Input { fn update_with_descriptor_unchecked( &mut self, descriptor: &Descriptor>, - ) -> Result>, descriptor::ConversionError> + ) -> Result>, descriptor::NonDefiniteKeyError> { let (derived, _) = update_item_with_descriptor_helper(self, descriptor, None)?; Ok(derived) @@ -1043,14 +1043,14 @@ pub trait PsbtOutputExt { fn update_with_descriptor_unchecked( &mut self, descriptor: &Descriptor, - ) -> Result, descriptor::ConversionError>; + ) -> Result, descriptor::NonDefiniteKeyError>; } impl PsbtOutputExt for psbt::Output { fn update_with_descriptor_unchecked( &mut self, descriptor: &Descriptor, - ) -> Result, descriptor::ConversionError> { + ) -> Result, descriptor::NonDefiniteKeyError> { let (derived, _) = update_item_with_descriptor_helper(self, descriptor, None)?; Ok(derived) } @@ -1063,20 +1063,20 @@ struct KeySourceLookUp( pub secp256k1::Secp256k1, ); -impl Translator +impl Translator for KeySourceLookUp { fn pk( &mut self, xpk: &DefiniteDescriptorKey, - ) -> Result { - let derived = xpk.derive_public_key(&self.1)?; + ) -> Result { + let derived = xpk.derive_public_key(&self.1); self.0.insert( derived.to_public_key(), ( xpk.master_fingerprint(), xpk.full_derivation_path() - .ok_or(descriptor::ConversionError::MultiKey)?, + .expect("definite keys cannot be multikeys"), ), ); Ok(derived) @@ -1085,7 +1085,7 @@ impl Translator( item: &mut F, descriptor: &Descriptor>, @@ -1193,12 +1196,12 @@ fn update_item_with_descriptor_helper( Descriptor>, bool, ), - descriptor::ConversionError, + descriptor::NonDefiniteKeyError, > { let secp = secp256k1::Secp256k1::verification_only(); let derived = if let Descriptor::Tr(_) = &descriptor { - let derived = descriptor.derived_descriptor(&secp)?; + let derived = descriptor.derived_descriptor(&secp); if let Some(check_script) = check_script { if check_script != &derived.script_pubkey() { @@ -1210,13 +1213,13 @@ fn update_item_with_descriptor_helper( // NOTE: they will both always be Tr if let (Descriptor::Tr(tr_derived), Descriptor::Tr(tr_xpk)) = (&derived, descriptor) { - update_tr_psbt_helper(item, tr_derived, tr_xpk)?; + update_tr_psbt_helper(item, tr_derived, tr_xpk); } derived } else if let Descriptor::TrExt(_) = &descriptor { // Repeat the same code for Tr with extensions. Annoying to dedup this code without macros - let derived = descriptor.derived_descriptor(&secp)?; + let derived = descriptor.derived_descriptor(&secp); if let Some(check_script) = check_script { if check_script != &derived.script_pubkey() { @@ -1226,13 +1229,16 @@ fn update_item_with_descriptor_helper( // NOTE: they will both always be Tr if let (Descriptor::TrExt(tr_derived), Descriptor::TrExt(tr_xpk)) = (&derived, descriptor) { - update_tr_psbt_helper(item, tr_derived, tr_xpk)?; + update_tr_psbt_helper(item, tr_derived, tr_xpk); } derived } else { let mut bip32_derivation = KeySourceLookUp(BTreeMap::new(), Secp256k1::verification_only()); - let derived = descriptor.translate_pk(&mut bip32_derivation)?; + let derived = match descriptor.translate_pk(&mut bip32_derivation) { + Ok(derived) => derived, + Err(e) => match e {}, + }; if let Some(check_script) = check_script { if check_script != &derived.script_pubkey() { @@ -1272,7 +1278,7 @@ fn update_tr_psbt_helper( item: &mut F, tr_derived: &Tr, tr_xpk: &Tr, -) -> Result<(), descriptor::ConversionError> +) where Ext: ParseableExt, Ext2: Extension, @@ -1292,7 +1298,7 @@ where ik_xpk.master_fingerprint(), ik_xpk .full_derivation_path() - .ok_or(descriptor::ConversionError::MultiKey)?, + .expect("definite keys cannot be multikeys"), ), ), ); @@ -1331,7 +1337,7 @@ where ( xpk.master_fingerprint(), xpk.full_derivation_path() - .ok_or(descriptor::ConversionError::MultiKey)?, + .expect("definite keys cannot be multikeys"), ), ) }); @@ -1354,7 +1360,6 @@ where } _ => {} } - Ok(()) } // Get a script from witness script pubkey hash @@ -1369,14 +1374,14 @@ fn script_code_wpkh(script: &Script) -> Script { } /// Return error type for [`PsbtExt::update_input_with_descriptor`] -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum UtxoUpdateError { /// Index out of bounds IndexOutOfBounds(usize, usize), /// The unsigned transaction didn't have an input at that index MissingInputUtxo, /// Derivation error - DerivationError(descriptor::ConversionError), + DerivationError(descriptor::NonDefiniteKeyError), /// The PSBT's `witness_utxo` and/or `non_witness_utxo` were invalid or missing UtxoCheck, /// The PSBT's `witness_utxo` and/or `non_witness_utxo` had a script_pubkey that did not match @@ -1417,14 +1422,14 @@ impl error::Error for UtxoUpdateError { } /// Return error type for [`PsbtExt::update_output_with_descriptor`] -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum OutputUpdateError { /// Index out of bounds IndexOutOfBounds(usize, usize), /// The raw unsigned transaction didn't have an output at that index MissingTxOut, /// Derivation error - DerivationError(descriptor::ConversionError), + DerivationError(descriptor::NonDefiniteKeyError), /// The output's script_pubkey did not match the descriptor MismatchedScriptPubkey, }