Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- New `DescriptorPublicKey::add_wildcard` method, which adds an unhardened wildcard to the derivation path of the descriptor [#853]
- New `DescriptorSecretKey::add_wildcard(wildcard_type: WildcardType)` method, which adds a wildcard to the derivation path of the descriptor [#853]
- Exposed `new_sh`, `new_wsh`,`new_bare` and `new_sh_wsh` methods on `Descriptor` type [#988]
- Exposed `new_tr`, `new_sh_with_wsh` and `new_sh_with_wpkh` methods on `Descriptor` type [#1016]


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

## [v2.3.0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,29 @@ class DescriptorTest {
val newPkhDescriptor = Descriptor.newPkh("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")
val newWpkhDescriptor = Descriptor.newWpkh("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")
val newShWpkhDescriptor = Descriptor.newShWpkh("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB")
val newTrDescriptor = Descriptor.newTr("xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB","and_v(v:pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737),older(50))")
val newShWithWpkhDescriptor = Descriptor.newShWithWpkh(newWpkhDescriptor.toString())

assertEquals(newPkDescriptor.descType(), DescriptorType.BARE)
assertEquals(newPkhDescriptor.descType(), DescriptorType.PKH)
assertEquals(newWpkhDescriptor.descType(), DescriptorType.WPKH)
assertEquals(newShWpkhDescriptor.descType(), DescriptorType.SH_WPKH)
assertEquals(newTrDescriptor.descType(), DescriptorType.TR)
assertEquals(newShWithWpkhDescriptor.descType(), DescriptorType.SH_WPKH)
}

@Test
fun createTaprootDescriptors() {
val internalKey = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
val scriptTree = "and_v(v:pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737),older(50))"

// Taproot descriptor without a script tree
val newTrNoTree = Descriptor.newTr(internalKey, null)
// Taproot descriptor with a script tree
val newTrWithTree = Descriptor.newTr(internalKey, scriptTree)

assertEquals(newTrNoTree.descType(), DescriptorType.TR)
assertEquals(newTrWithTree.descType(), DescriptorType.TR)
}

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

val newShWithWshDescriptor = Descriptor.newShWithWsh(newWshDescriptor.toString())

assertEquals(newShDescriptor.descType(), DescriptorType.SH)
assertEquals(newWshDescriptor.descType(), DescriptorType.WSH)
assertEquals(newShWshDescriptor.descType(), DescriptorType.SH_WSH)
assertEquals(newBareDescriptor.descType(), DescriptorType.BARE)
assertEquals(newShWithWshDescriptor.descType(), DescriptorType.SH_WSH)
}

@Test
Expand All @@ -95,6 +116,9 @@ class DescriptorTest {
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newSh(invalid) }
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWsh(invalid) }
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newBare(invalid) }
assertFailsWith<DescriptorException.Key> { Descriptor.newTr(invalid,invalid) }
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWithWpkh(invalid) }
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWithWsh(invalid) }
}

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

// println("Complex miniscript: $timelockConjunction")
// println("Resulting descriptor: ${Descriptor.newWsh(timelockConjunction)}")

// Can do
Descriptor.newSh(timelockConjunction)
Descriptor.newWsh(timelockConjunction)
val newShDescriptor = Descriptor.newSh(timelockConjunction)
val newWshDescriptor = Descriptor.newWsh(timelockConjunction)
Descriptor.newShWithWsh(newWshDescriptor.toString())

// No can do
assertFailsWith<DescriptorException.Miniscript> {
Descriptor.newBare(timelockConjunction)
Descriptor.newWpkh(timelockConjunction)
Descriptor.newWsh(timelockConjunction)
Descriptor.newPkh(timelockConjunction)
}
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newBare(timelockConjunction) }
assertFailsWith<DescriptorException.Key> { Descriptor.newWpkh(timelockConjunction) }
assertFailsWith<DescriptorException.Key> { Descriptor.newPkh(timelockConjunction) }
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWithWpkh(newShDescriptor.toString()) }
}

// Segwitv0 (new_wsh) requires all keys to be compressed. An uncompressed key is valid
Expand Down
88 changes: 82 additions & 6 deletions bdk-ffi/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ 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::miniscript::Miniscript as BDKMiniscript;
use bdk_wallet::miniscript::descriptor::{ConversionError, TapTree};
use bdk_wallet::miniscript::Descriptor as BdkDescriptor;
use bdk_wallet::miniscript::Miniscript as BdkMiniscript;
use bdk_wallet::template::{
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
DescriptorTemplate,
Expand Down Expand Up @@ -487,7 +488,7 @@ impl Descriptor {
/// under p2sh context or does not type check at the top level
#[uniffi::constructor]
pub fn new_wsh(mini_script: String) -> Result<Self, DescriptorError> {
let parsed_miniscript = match BDKMiniscript::from_str(&mini_script) {
let parsed_miniscript = match BdkMiniscript::from_str(&mini_script) {
Ok(miniscript) => miniscript,
Err(e) => {
return Err(DescriptorError::Miniscript {
Expand Down Expand Up @@ -517,7 +518,7 @@ impl Descriptor {
/// under wsh context or does not type check at the top level
#[uniffi::constructor]
pub fn new_sh_wsh(mini_script: String) -> Result<Self, DescriptorError> {
let parsed_miniscript = match BDKMiniscript::from_str(&mini_script) {
let parsed_miniscript = match BdkMiniscript::from_str(&mini_script) {
Ok(miniscript) => miniscript,
Err(e) => {
return Err(DescriptorError::Miniscript {
Expand Down Expand Up @@ -546,7 +547,7 @@ impl Descriptor {
/// 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
#[uniffi::constructor]
pub fn new_sh(mini_script: String) -> Result<Self, DescriptorError> {
let parsed_miniscript = match BDKMiniscript::from_str(&mini_script) {
let parsed_miniscript = match BdkMiniscript::from_str(&mini_script) {
Ok(miniscript) => miniscript,
Err(e) => {
return Err(DescriptorError::Miniscript {
Expand Down Expand Up @@ -576,7 +577,7 @@ impl Descriptor {
/// under bare context or does not type check at the top level
#[uniffi::constructor]
pub fn new_bare(mini_script: String) -> Result<Self, DescriptorError> {
let parsed_miniscript = match BDKMiniscript::from_str(&mini_script) {
let parsed_miniscript = match BdkMiniscript::from_str(&mini_script) {
Ok(miniscript) => miniscript,
Err(e) => {
return Err(DescriptorError::Miniscript {
Expand All @@ -602,6 +603,81 @@ impl Descriptor {
})
}

/// Create a new sh wrapper for the given wpkh descriptor
#[uniffi::constructor]
pub fn new_sh_with_wpkh(wpkh: String) -> Result<Self, DescriptorError> {
let descriptor = BdkDescriptor::<BdkDescriptorPublicKey>::from_str(&wpkh).map_err(|e| {
DescriptorError::Miniscript {
error_message: e.to_string(),
}
})?;

if let BdkDescriptor::Wpkh(wpkh_inner) = descriptor {
let sh_with_wpkh = bdk_wallet::miniscript::Descriptor::new_sh_with_wpkh(wpkh_inner);
Ok(Self {
extended_descriptor: ExtendedDescriptor::from(sh_with_wpkh),
key_map: KeyMap::new(),
})
} else {
Err(DescriptorError::Miniscript {
error_message: "Provided descriptor is not a valid wpkh descriptor".to_string(),
})
}
}

/// Create a new sh wrapper for the given wsh descriptor
#[uniffi::constructor]
pub fn new_sh_with_wsh(wsh: String) -> Result<Self, DescriptorError> {
let descriptor = BdkDescriptor::<BdkDescriptorPublicKey>::from_str(&wsh).map_err(|e| {
DescriptorError::Miniscript {
error_message: e.to_string(),
}
})?;

if let BdkDescriptor::Wsh(wsh_inner) = descriptor {
let sh_with_wsh = bdk_wallet::miniscript::Descriptor::new_sh_with_wsh(wsh_inner);
Ok(Self {
extended_descriptor: ExtendedDescriptor::from(sh_with_wsh),
key_map: KeyMap::new(),
})
} else {
Err(DescriptorError::Miniscript {
error_message: "Provided descriptor is not a valid wsh descriptor".to_string(),
})
}
}

/// Create new tr descriptor
/// Errors when miniscript exceeds resource limits under Tap context
#[uniffi::constructor]
pub fn new_tr(key: String, script: Option<String>) -> Result<Self, DescriptorError> {
let key = BdkDescriptorPublicKey::from_str(&key).map_err(|e| DescriptorError::Key {
error_message: e.to_string(),
})?;
let tap_tree = match script {
Some(s) => {
let ms_tap =
BdkMiniscript::from_str(&s).map_err(|e| DescriptorError::Miniscript {
error_message: e.to_string(),
})?;

Some(TapTree::Leaf(Arc::new(ms_tap)))
}
None => None,
};

let descriptor =
bdk_wallet::miniscript::Descriptor::new_tr(key, tap_tree).map_err(|e| {
DescriptorError::Miniscript {
error_message: e.to_string(),
}
})?;
Ok(Self {
extended_descriptor: ExtendedDescriptor::from(descriptor),
key_map: KeyMap::new(),
})
}

/// Dangerously convert the descriptor to a string.
pub fn to_string_with_secret(&self) -> String {
let descriptor = &self.extended_descriptor;
Expand Down
Loading