diff --git a/CHANGELOG.md b/CHANGELOG.md index 00f14b59..77f8babd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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] diff --git a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/DescriptorTest.kt b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/DescriptorTest.kt index a1594c04..9901d015 100644 --- a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/DescriptorTest.kt +++ b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/DescriptorTest.kt @@ -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 @@ -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 @@ -95,6 +116,9 @@ class DescriptorTest { assertFailsWith { Descriptor.newSh(invalid) } assertFailsWith { Descriptor.newShWsh(invalid) } assertFailsWith { Descriptor.newBare(invalid) } + assertFailsWith { Descriptor.newTr(invalid,invalid) } + assertFailsWith { Descriptor.newShWithWpkh(invalid) } + assertFailsWith { Descriptor.newShWithWsh(invalid) } } // BareCtx only allows pk(), pkh(), and multi(k<=3,...) at the top level. A timelock @@ -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 { - Descriptor.newBare(timelockConjunction) - Descriptor.newWpkh(timelockConjunction) - Descriptor.newWsh(timelockConjunction) - Descriptor.newPkh(timelockConjunction) - } + assertFailsWith { Descriptor.newBare(timelockConjunction) } + assertFailsWith { Descriptor.newWpkh(timelockConjunction) } + assertFailsWith { Descriptor.newPkh(timelockConjunction) } + assertFailsWith { Descriptor.newShWithWpkh(newShDescriptor.toString()) } } // Segwitv0 (new_wsh) requires all keys to be compressed. An uncompressed key is valid diff --git a/bdk-ffi/src/descriptor.rs b/bdk-ffi/src/descriptor.rs index 07e6e459..8c4e53fd 100644 --- a/bdk-ffi/src/descriptor.rs +++ b/bdk-ffi/src/descriptor.rs @@ -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, @@ -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 { - 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 { @@ -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 { - 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 { @@ -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 { - 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 { @@ -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 { - 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 { @@ -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 { + let descriptor = BdkDescriptor::::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 { + let descriptor = BdkDescriptor::::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) -> Result { + 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;