Skip to content

Commit 7230490

Browse files
ItoroDthunderbiscuit
authored andcommitted
feat: Add descriptor miniscript constructor method
- Add new_tr, new_sh_with_wsh, and new_sh_with_wpkh
1 parent f2a32e6 commit 7230490

2 files changed

Lines changed: 113 additions & 17 deletions

File tree

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

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,29 @@ 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+
val newShWithWpkhDescriptor = Descriptor.newShWithWpkh(newWpkhDescriptor.toString())
7072

7173
assertEquals(newPkDescriptor.descType(), DescriptorType.BARE)
7274
assertEquals(newPkhDescriptor.descType(), DescriptorType.PKH)
7375
assertEquals(newWpkhDescriptor.descType(), DescriptorType.WPKH)
7476
assertEquals(newShWpkhDescriptor.descType(), DescriptorType.SH_WPKH)
77+
assertEquals(newTrDescriptor.descType(), DescriptorType.TR)
78+
assertEquals(newShWithWpkhDescriptor.descType(), DescriptorType.SH_WPKH)
79+
}
80+
81+
@Test
82+
fun createTaprootDescriptors() {
83+
val internalKey = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
84+
val scriptTree = "and_v(v:pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737),older(50))"
85+
86+
// Taproot descriptor without a script tree
87+
val newTrNoTree = Descriptor.newTr(internalKey, null)
88+
// Taproot descriptor with a script tree
89+
val newTrWithTree = Descriptor.newTr(internalKey, scriptTree)
90+
91+
assertEquals(newTrNoTree.descType(), DescriptorType.TR)
92+
assertEquals(newTrWithTree.descType(), DescriptorType.TR)
7593
}
7694

7795
@Test
@@ -81,10 +99,13 @@ class DescriptorTest {
8199
val newShWshDescriptor = Descriptor.newShWsh("pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)")
82100
val newBareDescriptor = Descriptor.newBare("pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)")
83101

102+
val newShWithWshDescriptor = Descriptor.newShWithWsh(newWshDescriptor.toString())
103+
84104
assertEquals(newShDescriptor.descType(), DescriptorType.SH)
85105
assertEquals(newWshDescriptor.descType(), DescriptorType.WSH)
86106
assertEquals(newShWshDescriptor.descType(), DescriptorType.SH_WSH)
87107
assertEquals(newBareDescriptor.descType(), DescriptorType.BARE)
108+
assertEquals(newShWithWshDescriptor.descType(), DescriptorType.SH_WSH)
88109
}
89110

90111
@Test
@@ -95,6 +116,9 @@ class DescriptorTest {
95116
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newSh(invalid) }
96117
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWsh(invalid) }
97118
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newBare(invalid) }
119+
assertFailsWith<DescriptorException.Key> { Descriptor.newTr(invalid,invalid) }
120+
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWithWpkh(invalid) }
121+
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWithWsh(invalid) }
98122
}
99123

100124
// BareCtx only allows pk(), pkh(), and multi(k<=3,...) at the top level. A timelock
@@ -104,20 +128,16 @@ class DescriptorTest {
104128
val compressedPk = "02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737"
105129
val timelockConjunction = "and_v(v:pk($compressedPk),after(1000))"
106130

107-
// println("Complex miniscript: $timelockConjunction")
108-
// println("Resulting descriptor: ${Descriptor.newWsh(timelockConjunction)}")
109-
110131
// Can do
111-
Descriptor.newSh(timelockConjunction)
112-
Descriptor.newWsh(timelockConjunction)
132+
val newShDescriptor = Descriptor.newSh(timelockConjunction)
133+
val newWshDescriptor = Descriptor.newWsh(timelockConjunction)
134+
Descriptor.newShWithWsh(newWshDescriptor.toString())
113135

114136
// No can do
115-
assertFailsWith<DescriptorException.Miniscript> {
116-
Descriptor.newBare(timelockConjunction)
117-
Descriptor.newWpkh(timelockConjunction)
118-
Descriptor.newWsh(timelockConjunction)
119-
Descriptor.newPkh(timelockConjunction)
120-
}
137+
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newBare(timelockConjunction) }
138+
assertFailsWith<DescriptorException.Key> { Descriptor.newWpkh(timelockConjunction) }
139+
assertFailsWith<DescriptorException.Key> { Descriptor.newPkh(timelockConjunction) }
140+
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWithWpkh(newShDescriptor.toString()) }
121141
}
122142

123143
// Segwitv0 (new_wsh) requires all keys to be compressed. An uncompressed key is valid

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)