Skip to content

Commit 5b09b02

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 5b09b02

3 files changed

Lines changed: 116 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2626
- New `DescriptorPublicKey::add_wildcard` method, which adds an unhardened wildcard to the derivation path of the descriptor [#853]
2727
- New `DescriptorSecretKey::add_wildcard(wildcard_type: WildcardType)` method, which adds a wildcard to the derivation path of the descriptor [#853]
2828
- Exposed `new_sh`, `new_wsh`,`new_bare` and `new_sh_wsh` methods on `Descriptor` type [#988]
29+
- Exposed `new_tr`, `new_sh_with_wsh` and `new_sh_with_wpkh` methods on `Descriptor` type [#1016]
30+
2931

3032
[#853]: https://github.com/bitcoindevkit/bdk-ffi/pull/853
3133
[#853]: https://github.com/bitcoindevkit/bdk-ffi/pull/945
@@ -34,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3436
[#986]: https://github.com/bitcoindevkit/bdk-ffi/pull/973
3537
[#986]: https://github.com/bitcoindevkit/bdk-ffi/pull/986
3638
[#986]: https://github.com/bitcoindevkit/bdk-ffi/pull/988
39+
[#1016]: https://github.com/bitcoindevkit/bdk-ffi/pull/1016
3740

3841
## [v2.3.0]
3942

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: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ 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::Descriptor as BdkDescriptor;
19+
use bdk_wallet::miniscript::Miniscript as BdkMiniscript;
1920
use bdk_wallet::template::{
2021
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
2122
DescriptorTemplate,
@@ -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,81 @@ 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).map_err(|e| {
610+
DescriptorError::Miniscript {
611+
error_message: e.to_string(),
612+
}
613+
})?;
614+
615+
if let BdkDescriptor::Wpkh(wpkh_inner) = descriptor {
616+
let sh_with_wpkh = bdk_wallet::miniscript::Descriptor::new_sh_with_wpkh(wpkh_inner);
617+
Ok(Self {
618+
extended_descriptor: ExtendedDescriptor::from(sh_with_wpkh),
619+
key_map: KeyMap::new(),
620+
})
621+
} else {
622+
Err(DescriptorError::Miniscript {
623+
error_message: "Provided descriptor is not a valid wpkh descriptor".to_string(),
624+
})
625+
}
626+
}
627+
628+
/// Create a new sh wrapper for the given wsh descriptor
629+
#[uniffi::constructor]
630+
pub fn new_sh_with_wsh(wsh: String) -> Result<Self, DescriptorError> {
631+
let descriptor = BdkDescriptor::<BdkDescriptorPublicKey>::from_str(&wsh).map_err(|e| {
632+
DescriptorError::Miniscript {
633+
error_message: e.to_string(),
634+
}
635+
})?;
636+
637+
if let BdkDescriptor::Wsh(wsh_inner) = descriptor {
638+
let sh_with_wsh = bdk_wallet::miniscript::Descriptor::new_sh_with_wsh(wsh_inner);
639+
Ok(Self {
640+
extended_descriptor: ExtendedDescriptor::from(sh_with_wsh),
641+
key_map: KeyMap::new(),
642+
})
643+
} else {
644+
Err(DescriptorError::Miniscript {
645+
error_message: "Provided descriptor is not a valid wsh descriptor".to_string(),
646+
})
647+
}
648+
}
649+
650+
/// Create new tr descriptor
651+
/// Errors when miniscript exceeds resource limits under Tap context
652+
#[uniffi::constructor]
653+
pub fn new_tr(key: String, script: Option<String>) -> Result<Self, DescriptorError> {
654+
let key = BdkDescriptorPublicKey::from_str(&key).map_err(|e| DescriptorError::Key {
655+
error_message: e.to_string(),
656+
})?;
657+
let tap_tree = match script {
658+
Some(s) => {
659+
let ms_tap =
660+
BdkMiniscript::from_str(&s).map_err(|e| DescriptorError::Miniscript {
661+
error_message: e.to_string(),
662+
})?;
663+
664+
Some(TapTree::Leaf(Arc::new(ms_tap)))
665+
}
666+
None => None,
667+
};
668+
669+
let descriptor =
670+
bdk_wallet::miniscript::Descriptor::new_tr(key, tap_tree).map_err(|e| {
671+
DescriptorError::Miniscript {
672+
error_message: e.to_string(),
673+
}
674+
})?;
675+
Ok(Self {
676+
extended_descriptor: ExtendedDescriptor::from(descriptor),
677+
key_map: KeyMap::new(),
678+
})
679+
}
680+
605681
/// Dangerously convert the descriptor to a string.
606682
pub fn to_string_with_secret(&self) -> String {
607683
let descriptor = &self.extended_descriptor;

0 commit comments

Comments
 (0)