diff --git a/src/descriptor/error.rs b/src/descriptor/error.rs index 495f999c..64605061 100644 --- a/src/descriptor/error.rs +++ b/src/descriptor/error.rs @@ -31,6 +31,8 @@ pub enum Error { InvalidHDKeyPath, /// The provided descriptor doesn't match its checksum InvalidDescriptorChecksum, + /// The descriptor contains hardened derivation steps on public extended keys + HardenedDerivationXpub, /// Error thrown while working with [`keys`](crate::keys) Key(crate::keys::KeyError), diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 10000786..ee4f0629 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -198,6 +198,36 @@ impl IntoWalletDescriptor for DescriptorTemplateOut { } } +/// Wrapper for `IntoWalletDescriptor` that performs additional checks on the keys contained in the +/// descriptor +pub(crate) fn into_wallet_descriptor_checked( + inner: T, + secp: &SecpCtx, + network: Network, +) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> { + let (descriptor, keymap) = inner.into_wallet_descriptor(secp, network)?; + + // Ensure the keys don't contain any hardened derivation steps or hardened wildcards + let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| { + if let DescriptorPublicKey::XPub(DescriptorXKey { + derivation_path, + wildcard, + .. + }) = k.as_key() + { + return *wildcard == Wildcard::Hardened + || derivation_path.into_iter().any(ChildNumber::is_hardened); + } + + false + }); + if descriptor_contains_hardened_steps { + return Err(DescriptorError::HardenedDerivationXpub); + } + + Ok((descriptor, keymap)) +} + #[doc(hidden)] /// Used internally mainly by the `descriptor!()` and `fragment!()` macros pub trait CheckMiniscript { @@ -740,4 +770,18 @@ mod test { .unwrap(); assert_eq!(wallet_desc, wallet_desc2) } + + #[test] + fn test_into_wallet_descriptor_checked() { + let secp = Secp256k1::new(); + + let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)"; + let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + DescriptorError::HardenedDerivationXpub + )); + } } diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index b2729ec7..8bb254c4 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -66,8 +66,9 @@ use crate::blockchain::{Blockchain, Progress}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::descriptor::derived::AsDerived; use crate::descriptor::{ - get_checksum, DerivedDescriptor, DerivedDescriptorMeta, DescriptorMeta, DescriptorScripts, - ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils, + get_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DerivedDescriptorMeta, + DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, + Policy, XKeyUtils, }; use crate::error::Error; use crate::psbt::PSBTUtils; @@ -134,7 +135,7 @@ where ) -> Result { let secp = Secp256k1::new(); - let (descriptor, keymap) = descriptor.into_wallet_descriptor(&secp, network)?; + let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?; database.check_descriptor_checksum( KeychainKind::External, get_checksum(&descriptor.to_string())?.as_bytes(), @@ -143,7 +144,7 @@ where let (change_descriptor, change_signers) = match change_descriptor { Some(desc) => { let (change_descriptor, change_keymap) = - desc.into_wallet_descriptor(&secp, network)?; + into_wallet_descriptor_checked(desc, &secp, network)?; database.check_descriptor_checksum( KeychainKind::Internal, get_checksum(&change_descriptor.to_string())?.as_bytes(),