diff --git a/bdk-ffi/src/descriptor.rs b/bdk-ffi/src/descriptor.rs index c07de859..3d0750cd 100644 --- a/bdk-ffi/src/descriptor.rs +++ b/bdk-ffi/src/descriptor.rs @@ -1,5 +1,7 @@ +use crate::bitcoin::Address; use crate::bitcoin::DescriptorId; use crate::bitcoin::DescriptorType; +use crate::bitcoin::Script; use crate::error::DescriptorError; use crate::error::MiniscriptError; use crate::keys::DescriptorPublicKey; @@ -12,6 +14,7 @@ use bdk_wallet::chain::DescriptorExt; use bdk_wallet::descriptor::{ExtendedDescriptor, IntoWalletDescriptor}; use bdk_wallet::keys::DescriptorPublicKey as BdkDescriptorPublicKey; use bdk_wallet::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap}; +use bdk_wallet::miniscript::descriptor::ConversionError; use bdk_wallet::template::{ Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public, DescriptorTemplate, @@ -355,6 +358,35 @@ impl Descriptor { pub fn desc_type(&self) -> DescriptorType { self.extended_descriptor.desc_type() } + + pub fn derived_address( + &self, + index: u32, + network: Network, + ) -> Result, DescriptorError> { + if self.extended_descriptor.is_multipath() { + return Err(DescriptorError::MultiPath); + } + + let secp = Secp256k1::verification_only(); + let derived_descriptor = self + .extended_descriptor + .at_derivation_index(index) + .and_then(|desc| desc.derived_descriptor(&secp)) + .map_err(|error| match error { + ConversionError::HardenedChild => DescriptorError::HardenedDerivationXpub, + ConversionError::MultiKey => DescriptorError::MultiPath, + })?; + let script_pubkey = derived_descriptor.script_pubkey(); + let address = + Address::from_script(Arc::new(Script(script_pubkey)), network).map_err(|error| { + DescriptorError::Miniscript { + error_message: error.to_string(), + } + })?; + + Ok(Arc::new(address)) + } } impl Display for Descriptor { diff --git a/bdk-ffi/src/tests/descriptor.rs b/bdk-ffi/src/tests/descriptor.rs index fed07b89..f527426e 100644 --- a/bdk-ffi/src/tests/descriptor.rs +++ b/bdk-ffi/src/tests/descriptor.rs @@ -3,6 +3,7 @@ use crate::descriptor::Descriptor; use crate::error::DescriptorError; use crate::keys::{DerivationPath, DescriptorSecretKey, Mnemonic}; use assert_matches::assert_matches; +use bdk_wallet::bitcoin::secp256k1::Secp256k1; use bdk_wallet::KeychainKind; fn get_descriptor_secret_key() -> DescriptorSecretKey { @@ -161,3 +162,42 @@ fn test_max_weight_to_satisfy() { // Verify the method works and returns a positive weight assert!(weight > 0, "Weight must be positive"); } + +#[test] +fn test_descriptor_derived_address() { + let descriptor = Descriptor::new_bip84( + &get_descriptor_secret_key(), + KeychainKind::External, + Network::Testnet, + ); + + let derived = descriptor + .derived_address(0, Network::Testnet) + .expect("derive address"); + + let secp = Secp256k1::verification_only(); + let expected_descriptor = descriptor + .extended_descriptor + .derived_descriptor(&secp, 0) + .expect("derive descriptor"); + let expected_address = bdk_wallet::bitcoin::Address::from_script( + &expected_descriptor.script_pubkey(), + Network::Testnet, + ) + .expect("address from script"); + + assert_eq!(derived.to_string(), expected_address.to_string()); +} + +#[test] +fn test_descriptor_derived_address_multipath_error() { + let descriptor = Descriptor::new( + "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1>/*)".to_string(), + Network::Testnet, + ) + .expect("multipath descriptor parses"); + + let error = descriptor.derived_address(0, Network::Testnet).unwrap_err(); + + assert_matches!(error, DescriptorError::MultiPath); +}