Skip to content

Commit 00d3bc7

Browse files
committed
feat: Add descriptor miniscript constructor method
- Add new_tr, new_sh_with_wsh, and new_sh_with_wpkh
1 parent d23b2db commit 00d3bc7

2 files changed

Lines changed: 109 additions & 11 deletions

File tree

bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/DescriptorTest.kt

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,32 @@ class DescriptorTest {
6767
val newPkhDescriptor = Descriptor.newPkh("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")
6868
val newWpkhDescriptor = Descriptor.newWpkh("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")
6969
val newShWpkhDescriptor = Descriptor.newShWpkh("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")
70+
val newTrDescriptor = Descriptor.newTr("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB","and_v(v:pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737),older(50))")
71+
println(newWpkhDescriptor)
72+
73+
74+
val newShWithWpkhDescriptor = Descriptor.newShWithWpkh(newWpkhDescriptor.toString())
7075

7176
assertEquals(newPkDescriptor.descType(), DescriptorType.BARE)
7277
assertEquals(newPkhDescriptor.descType(), DescriptorType.PKH)
7378
assertEquals(newWpkhDescriptor.descType(), DescriptorType.WPKH)
7479
assertEquals(newShWpkhDescriptor.descType(), DescriptorType.SH_WPKH)
80+
assertEquals(newTrDescriptor.descType(), DescriptorType.TR)
81+
assertEquals(newShWithWpkhDescriptor.descType(), DescriptorType.SH_WPKH)
82+
}
83+
84+
@Test
85+
fun createTaprootDescriptors() {
86+
val internalKey = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
87+
val scriptTree = "and_v(v:pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737),older(50))"
88+
89+
// Taproot descriptor without a script tree
90+
val newTrNoTree = Descriptor.newTr(internalKey, null)
91+
// Taproot descriptor with a script tree
92+
val newTrWithTree = Descriptor.newTr(internalKey, scriptTree)
93+
94+
assertEquals(newTrNoTree.descType(), DescriptorType.TR)
95+
assertEquals(newTrWithTree.descType(), DescriptorType.TR)
7596
}
7697

7798
@Test
@@ -81,10 +102,13 @@ class DescriptorTest {
81102
val newShWshDescriptor = Descriptor.newShWsh("pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)")
82103
val newBareDescriptor = Descriptor.newBare("pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)")
83104

105+
val newShWithWshDescriptor = Descriptor.newShWithWsh(newWshDescriptor.toString())
106+
84107
assertEquals(newShDescriptor.descType(), DescriptorType.SH)
85108
assertEquals(newWshDescriptor.descType(), DescriptorType.WSH)
86109
assertEquals(newShWshDescriptor.descType(), DescriptorType.SH_WSH)
87110
assertEquals(newBareDescriptor.descType(), DescriptorType.BARE)
111+
assertEquals(newShWithWshDescriptor.descType(), DescriptorType.SH_WSH)
88112
}
89113

90114
@Test
@@ -95,6 +119,9 @@ class DescriptorTest {
95119
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newSh(invalid) }
96120
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWsh(invalid) }
97121
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newBare(invalid) }
122+
assertFailsWith<DescriptorException.Key> { Descriptor.newTr(invalid,invalid) }
123+
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWithWpkh(invalid) }
124+
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWithWsh(invalid) }
98125
}
99126

100127
// BareCtx only allows pk(), pkh(), and multi(k<=3,...) at the top level. A timelock
@@ -104,19 +131,18 @@ class DescriptorTest {
104131
val compressedPk = "02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737"
105132
val timelockConjunction = "and_v(v:pk($compressedPk),after(1000))"
106133

107-
// println("Complex miniscript: $timelockConjunction")
108-
// println("Resulting descriptor: ${Descriptor.newWsh(timelockConjunction)}")
109-
110134
// Can do
111-
Descriptor.newSh(timelockConjunction)
112-
Descriptor.newWsh(timelockConjunction)
135+
val newShDescriptor = Descriptor.newSh(timelockConjunction)
136+
val newWshDescriptor = Descriptor.newWsh(timelockConjunction)
137+
Descriptor.newShWithWsh(newWshDescriptor.toString())
113138

114139
// No can do
115140
assertFailsWith<DescriptorException.Miniscript> {
116141
Descriptor.newBare(timelockConjunction)
117142
Descriptor.newWpkh(timelockConjunction)
118143
Descriptor.newWsh(timelockConjunction)
119144
Descriptor.newPkh(timelockConjunction)
145+
Descriptor.newShWithWpkh(newShDescriptor.toString())
120146
}
121147
}
122148

bdk-ffi/src/descriptor.rs

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ use bdk_wallet::chain::DescriptorExt;
1414
use bdk_wallet::descriptor::{ExtendedDescriptor, IntoWalletDescriptor};
1515
use bdk_wallet::keys::DescriptorPublicKey as BdkDescriptorPublicKey;
1616
use bdk_wallet::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap};
17-
use bdk_wallet::miniscript::descriptor::ConversionError;
18-
use bdk_wallet::miniscript::Miniscript as BDKMiniscript;
17+
use bdk_wallet::miniscript::descriptor::{ConversionError, TapTree};
18+
use bdk_wallet::miniscript::Miniscript as BdkMiniscript;
1919
use bdk_wallet::template::{
2020
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
2121
DescriptorTemplate,
2222
};
23+
use bdk_wallet::miniscript::Descriptor as BdkDescriptor;
2324

2425
use std::fmt::Display;
2526
use std::str::FromStr;
@@ -487,7 +488,7 @@ impl Descriptor {
487488
/// under p2sh context or does not type check at the top level
488489
#[uniffi::constructor]
489490
pub fn new_wsh(mini_script: String) -> Result<Self, DescriptorError> {
490-
let parsed_miniscript = match BDKMiniscript::from_str(&mini_script) {
491+
let parsed_miniscript = match BdkMiniscript::from_str(&mini_script) {
491492
Ok(miniscript) => miniscript,
492493
Err(e) => {
493494
return Err(DescriptorError::Miniscript {
@@ -517,7 +518,7 @@ impl Descriptor {
517518
/// under wsh context or does not type check at the top level
518519
#[uniffi::constructor]
519520
pub fn new_sh_wsh(mini_script: String) -> Result<Self, DescriptorError> {
520-
let parsed_miniscript = match BDKMiniscript::from_str(&mini_script) {
521+
let parsed_miniscript = match BdkMiniscript::from_str(&mini_script) {
521522
Ok(miniscript) => miniscript,
522523
Err(e) => {
523524
return Err(DescriptorError::Miniscript {
@@ -546,7 +547,7 @@ impl Descriptor {
546547
/// Create a new sh for a given redeem script Errors when miniscript exceeds resource limits under p2sh context or does not type check at the top level
547548
#[uniffi::constructor]
548549
pub fn new_sh(mini_script: String) -> Result<Self, DescriptorError> {
549-
let parsed_miniscript = match BDKMiniscript::from_str(&mini_script) {
550+
let parsed_miniscript = match BdkMiniscript::from_str(&mini_script) {
550551
Ok(miniscript) => miniscript,
551552
Err(e) => {
552553
return Err(DescriptorError::Miniscript {
@@ -576,7 +577,7 @@ impl Descriptor {
576577
/// under bare context or does not type check at the top level
577578
#[uniffi::constructor]
578579
pub fn new_bare(mini_script: String) -> Result<Self, DescriptorError> {
579-
let parsed_miniscript = match BDKMiniscript::from_str(&mini_script) {
580+
let parsed_miniscript = match BdkMiniscript::from_str(&mini_script) {
580581
Ok(miniscript) => miniscript,
581582
Err(e) => {
582583
return Err(DescriptorError::Miniscript {
@@ -602,6 +603,77 @@ impl Descriptor {
602603
})
603604
}
604605

606+
/// Create a new sh wrapper for the given wpkh descriptor
607+
#[uniffi::constructor]
608+
pub fn new_sh_with_wpkh(wpkh: String) -> Result<Self, DescriptorError> {
609+
let descriptor = BdkDescriptor::<BdkDescriptorPublicKey>::from_str(&wpkh)
610+
.map_err(|e| DescriptorError::Miniscript {
611+
error_message: e.to_string(),
612+
})?;
613+
614+
if let BdkDescriptor::Wpkh(wpkh_inner) = descriptor {
615+
let sh_with_wpkh = bdk_wallet::miniscript::Descriptor::new_sh_with_wpkh(wpkh_inner);
616+
Ok(Self {
617+
extended_descriptor: ExtendedDescriptor::from(sh_with_wpkh),
618+
key_map: KeyMap::new(),
619+
})
620+
} else {
621+
Err(DescriptorError::Miniscript {
622+
error_message: "Provided descriptor is not a valid wpkh descriptor".to_string(),
623+
})
624+
}
625+
}
626+
627+
/// Create a new sh wrapper for the given wsh descriptor
628+
#[uniffi::constructor]
629+
pub fn new_sh_with_wsh(wsh: String) -> Result<Self, DescriptorError> {
630+
let descriptor = BdkDescriptor::<BdkDescriptorPublicKey>::from_str(&wsh)
631+
.map_err(|e| DescriptorError::Miniscript {
632+
error_message: e.to_string(),
633+
})?;
634+
635+
if let BdkDescriptor::Wsh(wsh_inner) = descriptor {
636+
let sh_with_wsh = bdk_wallet::miniscript::Descriptor::new_sh_with_wsh(wsh_inner);
637+
Ok(Self {
638+
extended_descriptor: ExtendedDescriptor::from(sh_with_wsh),
639+
key_map: KeyMap::new(),
640+
})
641+
} else {
642+
Err(DescriptorError::Miniscript {
643+
error_message: "Provided descriptor is not a valid wsh descriptor".to_string(),
644+
})
645+
}
646+
}
647+
648+
/// Create new tr descriptor
649+
/// Errors when miniscript exceeds resource limits under Tap context
650+
#[uniffi::constructor]
651+
pub fn new_tr(key:String, script:Option<String>) -> Result<Self, DescriptorError> {
652+
let key = BdkDescriptorPublicKey::from_str(&key).map_err(|e| DescriptorError::Key {
653+
error_message: e.to_string(),
654+
})?;
655+
let tap_tree = match script {
656+
Some(s) => {
657+
let ms_tap = BdkMiniscript::from_str(&s).map_err(|e| {
658+
DescriptorError::Miniscript {
659+
error_message: e.to_string(),
660+
}
661+
})?;
662+
663+
Some(TapTree::Leaf(Arc::new(ms_tap)))
664+
}
665+
None => None,
666+
};
667+
668+
let descriptor = bdk_wallet::miniscript::Descriptor::new_tr(key, tap_tree).map_err(|e| DescriptorError::Miniscript {
669+
error_message: e.to_string(),
670+
})?;
671+
Ok(Self {
672+
extended_descriptor: ExtendedDescriptor::from(descriptor),
673+
key_map: KeyMap::new(),
674+
})
675+
}
676+
605677
/// Dangerously convert the descriptor to a string.
606678
pub fn to_string_with_secret(&self) -> String {
607679
let descriptor = &self.extended_descriptor;

0 commit comments

Comments
 (0)