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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.

## [Unreleased]

- Fixed `compile` rejecting policies that are valid for the requested script type

## [4.0.0]

- Added persistance to existing async payjoin integration
Expand Down
22 changes: 7 additions & 15 deletions src/handlers/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use {
key::{Parity, rand},
secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey},
},
miniscript::{Descriptor, Miniscript, descriptor::TapTree, policy::Concrete},
miniscript::{Descriptor, descriptor::TapTree, policy::Concrete},
},
std::{str::FromStr, sync::Arc},
};
Expand Down Expand Up @@ -83,22 +83,14 @@ impl AppCommand<AppContext<Init>> for CompileCommand {
let policy: Concrete<String> = Concrete::from_str(&self.policy)
.map_err(|e| Error::Generic(format!("Invalid policy: {e}")))?;

let legacy_policy: Miniscript<String, bdk_wallet::miniscript::Legacy> = policy
.compile()
.map_err(|e| Error::Generic(e.to_string()))?;
let segwit_policy: Miniscript<String, bdk_wallet::miniscript::Segwitv0> = policy
.compile()
.map_err(|e| Error::Generic(e.to_string()))?;
let taproot_policy: Miniscript<String, bdk_wallet::miniscript::Tap> = policy
.compile()
.map_err(|e| Error::Generic(e.to_string()))?;

let mut r = None;

// Compile per branch, not once up front: the contexts have different script
// limits, and the narrowest one would reject policies valid for the requested type.
let descriptor = match self.script_type.as_str() {
"sh" => Descriptor::new_sh(legacy_policy),
"wsh" => Descriptor::new_wsh(segwit_policy),
"sh-wsh" => Descriptor::new_sh_wsh(segwit_policy),
"sh" => Descriptor::new_sh(policy.compile()?),
"wsh" => Descriptor::new_wsh(policy.compile()?),
"sh-wsh" => Descriptor::new_sh_wsh(policy.compile()?),
"tr" => {
// Use a randomized unspendable internal key (H + rG) instead of a fixed NUMS
// point. This improves privacy by preventing observers from determining whether
Expand All @@ -118,7 +110,7 @@ impl AppCommand<AppContext<Init>> for CompileCommand {
.map_err(|e| Error::Generic(format!("Failed to tweak NUMS key: {e}")))?;
let (xonly_internal_key, _) = internal_key_point.x_only_public_key();

let tree = TapTree::Leaf(Arc::new(taproot_policy));
let tree = TapTree::Leaf(Arc::new(policy.compile()?));
Descriptor::new_tr(xonly_internal_key.to_string(), Some(tree))
}
_ => {
Expand Down
20 changes: 20 additions & 0 deletions tests/integration/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,26 @@ mod test_compile {
.stdout(predicate::str::contains("wsh("));
}

/// A policy can be valid for taproot and still exceed the limits of the
/// legacy context, whose 520-byte redeemScript cap does not apply to it.
/// Compiling for `tr` must not be blocked by the other contexts.
#[test]
fn test_compile_taproot_policy_beyond_legacy_limits() {
let temp_dir = TempDir::new().unwrap();
let cli = BdkCli::new("testnet", Some(temp_dir.path().to_path_buf()));

let keys = (1..=20)
.map(|i| format!("pk(K{i:02})"))
.collect::<Vec<_>>()
.join(",");
let policy = format!("thresh(2,{keys})");

cli.cmd("compile", &[&policy, "--type", "tr"])
.assert()
.success()
.stdout(predicate::str::contains("tr("));
}

#[test]
fn test_compile_invalid_policy() {
let temp_dir = TempDir::new().unwrap();
Expand Down
Loading