Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- New `WildcardType` enum [#853]
- 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]

[#853]: https://github.com/bitcoindevkit/bdk-ffi/pull/853
[#853]: https://github.com/bitcoindevkit/bdk-ffi/pull/945
[#853]: https://github.com/bitcoindevkit/bdk-ffi/pull/949
[#986]: https://github.com/bitcoindevkit/bdk-ffi/pull/971
[#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

## [v2.3.0]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,69 @@ class DescriptorTest {
assertEquals(newShWpkhDescriptor.descType(), DescriptorType.SH_WPKH)
}

@Test
fun createMiniscriptDescriptors() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting closer to an ACK but I'd love to see a bunch of ways these constructors can fail if possible (at the moment I'm not intuitively understanding the boundaries of what the methods do here, and good tests to showcase failed attemtps would help out IMO). Not a blocker directly, I'll try a bunch locally and if everything works as expected I can ACK and we can add tests later.

val newShDescriptor = Descriptor.newSh("pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)")
val newWshDescriptor = Descriptor.newWsh("pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)")
val newShWshDescriptor = Descriptor.newShWsh("pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)")
val newBareDescriptor = Descriptor.newBare("pk(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)")

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

@Test
fun miniscriptConstructorsRejectInvalidExpressions() {
val invalid = "not_a_valid_miniscript"

assertFailsWith<DescriptorException.Miniscript> { Descriptor.newWsh(invalid) }
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newSh(invalid) }
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newShWsh(invalid) }
assertFailsWith<DescriptorException.Miniscript> { Descriptor.newBare(invalid) }
}

// BareCtx only allows pk(), pkh(), and multi(k<=3,...) at the top level. A timelock
// conjunction is valid miniscript for new_wsh but is rejected by new_bare.
@Test
fun notAllMiniscriptsWorkEverywhere() {
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)

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

// Segwitv0 (new_wsh) requires all keys to be compressed. An uncompressed key is valid
// in Legacy context (new_sh) but rejected by new_wsh.
@Test
fun newWshRejectsUncompressedKey() {
// Satoshi's genesis block public key — a well-known uncompressed key.
val uncompressedPk = "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
val expression = "pk($uncompressedPk)"

// Can do
Descriptor.newSh(expression)

// No can do
assertFailsWith<DescriptorException.Miniscript> {
Descriptor.newWsh(expression)
}
}

// Cannot create addr() descriptor.
@Test
fun cannotCreateAddrDescriptor() {
Expand Down
120 changes: 120 additions & 0 deletions bdk-ffi/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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::template::{
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
DescriptorTemplate,
Expand Down Expand Up @@ -482,6 +483,125 @@ impl Descriptor {
})
}

/// Create a new wsh descriptor from witness script Errors when miniscript exceeds resource limits
/// 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) {
Ok(miniscript) => miniscript,
Err(e) => {
return Err(DescriptorError::Miniscript {
error_message: e.to_string(),
});
}
};

let miniscript_descriptor =
match bdk_wallet::miniscript::Descriptor::new_wsh(parsed_miniscript) {
Ok(descriptor) => descriptor,
Err(e) => {
return Err(DescriptorError::Miniscript {
error_message: e.to_string(),
})
}
};
let extended_descriptor = ExtendedDescriptor::from(miniscript_descriptor);

Ok(Self {
extended_descriptor,
key_map: KeyMap::new(),
})
}

/// Create a new sh wrapped wsh descriptor with witness script Errors when miniscript exceeds resource limits
/// 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) {
Ok(miniscript) => miniscript,
Err(e) => {
return Err(DescriptorError::Miniscript {
error_message: e.to_string(),
});
}
};

let miniscript_descriptor =
match bdk_wallet::miniscript::Descriptor::new_sh_wsh(parsed_miniscript) {
Ok(descriptor) => descriptor,
Err(e) => {
return Err(DescriptorError::Miniscript {
error_message: e.to_string(),
})
}
};
let extended_descriptor = ExtendedDescriptor::from(miniscript_descriptor);

Ok(Self {
extended_descriptor,
key_map: KeyMap::new(),
})
}

/// 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) {
Ok(miniscript) => miniscript,
Err(e) => {
return Err(DescriptorError::Miniscript {
error_message: e.to_string(),
});
}
};

let miniscript_descriptor =
match bdk_wallet::miniscript::Descriptor::new_sh(parsed_miniscript) {
Ok(descriptor) => descriptor,
Err(e) => {
return Err(DescriptorError::Miniscript {
error_message: e.to_string(),
})
}
};
let extended_descriptor = ExtendedDescriptor::from(miniscript_descriptor);

Ok(Self {
extended_descriptor,
key_map: KeyMap::new(),
})
}

/// Create a new bare descriptor from witness script Errors when miniscript exceeds resource limits
/// 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) {
Ok(miniscript) => miniscript,
Err(e) => {
return Err(DescriptorError::Miniscript {
error_message: e.to_string(),
});
}
};

let miniscript_descriptor =
match bdk_wallet::miniscript::Descriptor::new_bare(parsed_miniscript) {
Ok(descriptor) => descriptor,
Err(e) => {
return Err(DescriptorError::Miniscript {
error_message: e.to_string(),
})
}
};
let extended_descriptor = ExtendedDescriptor::from(miniscript_descriptor);

Ok(Self {
extended_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