Skip to content

Commit d291ac6

Browse files
committed
feat: expose derived_address helper on Descriptor
1 parent fa23c99 commit d291ac6

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

bdk-ffi/src/descriptor.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use crate::bitcoin::Address;
12
use crate::bitcoin::DescriptorId;
23
use crate::bitcoin::DescriptorType;
4+
use crate::bitcoin::Script;
35
use crate::error::DescriptorError;
46
use crate::error::MiniscriptError;
57
use crate::keys::DescriptorPublicKey;
@@ -12,6 +14,7 @@ use bdk_wallet::chain::DescriptorExt;
1214
use bdk_wallet::descriptor::{ExtendedDescriptor, IntoWalletDescriptor};
1315
use bdk_wallet::keys::DescriptorPublicKey as BdkDescriptorPublicKey;
1416
use bdk_wallet::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap};
17+
use bdk_wallet::miniscript::descriptor::ConversionError;
1518
use bdk_wallet::template::{
1619
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
1720
DescriptorTemplate,
@@ -355,6 +358,34 @@ impl Descriptor {
355358
pub fn desc_type(&self) -> DescriptorType {
356359
self.extended_descriptor.desc_type()
357360
}
361+
362+
pub fn derived_address(
363+
&self,
364+
index: u32,
365+
network: Network,
366+
) -> Result<Arc<Address>, DescriptorError> {
367+
if self.extended_descriptor.is_multipath() {
368+
return Err(DescriptorError::MultiPath);
369+
}
370+
371+
let secp = Secp256k1::verification_only();
372+
let derived_descriptor = self
373+
.extended_descriptor
374+
.at_derivation_index(index)
375+
.and_then(|desc| desc.derived_descriptor(&secp))
376+
.map_err(|error| match error {
377+
ConversionError::HardenedChild => DescriptorError::HardenedDerivationXpub,
378+
ConversionError::MultiKey => DescriptorError::MultiPath,
379+
})?;
380+
let script_pubkey = derived_descriptor.script_pubkey();
381+
let address = Address::from_script(Arc::new(Script(script_pubkey)), network).map_err(
382+
|error| DescriptorError::Miniscript {
383+
error_message: error.to_string(),
384+
},
385+
)?;
386+
387+
Ok(Arc::new(address))
388+
}
358389
}
359390

360391
impl Display for Descriptor {

bdk-ffi/src/tests/descriptor.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::descriptor::Descriptor;
33
use crate::error::DescriptorError;
44
use crate::keys::{DerivationPath, DescriptorSecretKey, Mnemonic};
55
use assert_matches::assert_matches;
6+
use bdk_wallet::bitcoin::secp256k1::Secp256k1;
67
use bdk_wallet::KeychainKind;
78

89
fn get_descriptor_secret_key() -> DescriptorSecretKey {
@@ -161,3 +162,44 @@ fn test_max_weight_to_satisfy() {
161162
// Verify the method works and returns a positive weight
162163
assert!(weight > 0, "Weight must be positive");
163164
}
165+
166+
#[test]
167+
fn test_descriptor_derived_address() {
168+
let descriptor = Descriptor::new_bip84(
169+
&get_descriptor_secret_key(),
170+
KeychainKind::External,
171+
Network::Testnet,
172+
);
173+
174+
let derived = descriptor
175+
.derived_address(0, Network::Testnet)
176+
.expect("derive address");
177+
178+
let secp = Secp256k1::verification_only();
179+
let expected_descriptor = descriptor
180+
.extended_descriptor
181+
.derived_descriptor(&secp, 0)
182+
.expect("derive descriptor");
183+
let expected_address = bdk_wallet::bitcoin::Address::from_script(
184+
&expected_descriptor.script_pubkey(),
185+
Network::Testnet,
186+
)
187+
.expect("address from script");
188+
189+
assert_eq!(derived.to_string(), expected_address.to_string());
190+
}
191+
192+
#[test]
193+
fn test_descriptor_derived_address_multipath_error() {
194+
let descriptor = Descriptor::new(
195+
"wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1>/*)".to_string(),
196+
Network::Testnet,
197+
)
198+
.expect("multipath descriptor parses");
199+
200+
let error = descriptor
201+
.derived_address(0, Network::Testnet)
202+
.unwrap_err();
203+
204+
assert_matches!(error, DescriptorError::MultiPath);
205+
}

0 commit comments

Comments
 (0)