From f21ac743c2ef423cb4ce11ae3974e3c0436eaea5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 25 Nov 2025 13:28:41 -0600 Subject: [PATCH] feat: expose `derived_address` helper on `Descriptor` --- bdk-ffi/src/descriptor.rs | 29 ++++++++++++++++++++++++++ bdk-ffi/src/tests/descriptor.rs | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/bdk-ffi/src/descriptor.rs b/bdk-ffi/src/descriptor.rs index c07de859..e52769d7 100644 --- a/bdk-ffi/src/descriptor.rs +++ b/bdk-ffi/src/descriptor.rs @@ -1,3 +1,4 @@ +use crate::bitcoin::Address; use crate::bitcoin::DescriptorId; use crate::bitcoin::DescriptorType; use crate::error::DescriptorError; @@ -12,6 +13,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 +357,33 @@ impl Descriptor { pub fn desc_type(&self) -> DescriptorType { self.extended_descriptor.desc_type() } + + pub fn derive_address( + &self, + index: u32, + network: Network, + ) -> Result, DescriptorError> { + if self.extended_descriptor.is_multipath() { + return Err(DescriptorError::MultiPath); + } + + let derived_descriptor = self + .extended_descriptor + .at_derivation_index(index) + .map_err(|error| match error { + ConversionError::HardenedChild => DescriptorError::HardenedDerivationXpub, + ConversionError::MultiKey => DescriptorError::MultiPath, + })?; + + let address = derived_descriptor + .address(network) + .map_err(|error| DescriptorError::Miniscript { + error_message: error.to_string(), + })? + .into(); + + 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..c6dcb432 100644 --- a/bdk-ffi/src/tests/descriptor.rs +++ b/bdk-ffi/src/tests/descriptor.rs @@ -161,3 +161,39 @@ 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_derive_address() { + let descriptor = Descriptor::new_bip84( + &get_descriptor_secret_key(), + KeychainKind::External, + Network::Testnet, + ); + + let derived = descriptor + .derive_address(0, Network::Testnet) + .expect("derive address"); + + let expected_descriptor = descriptor + .extended_descriptor + .at_derivation_index(0) + .expect("derive descriptor"); + let expected_address = expected_descriptor + .address(Network::Testnet) + .expect("address from descriptor"); + + assert_eq!(derived.to_string(), expected_address.to_string()); +} + +#[test] +fn test_descriptor_derive_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.derive_address(0, Network::Testnet).unwrap_err(); + + assert_matches!(error, DescriptorError::MultiPath); +}