Skip to content

Commit dbbe2b9

Browse files
OttoAllmendingerllm-git
andcommitted
feat(wasm-utxo): implement ExtParamsConfig for descriptor parsing
Add ExtParamsConfig struct to enable fine-grained control over miniscript analysis checks when parsing descriptors. This allows callers to selectively enable non-standard behaviors like drop operations, timelock mixing, or malleability. The configuration uses camelCase field names for JavaScript compatibility and defaults all flags to false for sane behavior. Issue: BTC-3357 Co-authored-by: llm-git <llm-git@ttll.de>
1 parent 0c15036 commit dbbe2b9

3 files changed

Lines changed: 101 additions & 0 deletions

File tree

packages/wasm-utxo/js/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ export type DescriptorPkType = "derivable" | "definite" | "string";
3838

3939
export type ScriptContext = "tap" | "segwitv0" | "legacy";
4040

41+
export interface ExtParamsConfig {
42+
drop?: boolean;
43+
topUnsafe?: boolean;
44+
resourceLimitations?: boolean;
45+
timelockMixing?: boolean;
46+
malleability?: boolean;
47+
repeatedPk?: boolean;
48+
rawPkh?: boolean;
49+
}
50+
4151
declare module "./wasm/wasm_utxo.js" {
4252
interface WrapDescriptor {
4353
/** These are not the same types of nodes as in the ast module */
@@ -48,6 +58,11 @@ declare module "./wasm/wasm_utxo.js" {
4858
namespace WrapDescriptor {
4959
function fromString(descriptor: string, pkType: DescriptorPkType): WrapDescriptor;
5060
function fromStringDetectType(descriptor: string): WrapDescriptor;
61+
function fromStringExt(
62+
descriptor: string,
63+
pkType: "definite",
64+
extParams: ExtParamsConfig,
65+
): WrapDescriptor;
5166
}
5267

5368
interface WrapMiniscript {

packages/wasm-utxo/src/wasm/descriptor.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::error::WasmUtxoError;
2+
use crate::wasm::try_from_js_value::get_field;
23
use crate::wasm::try_into_js_value::TryIntoJsValue;
34
use miniscript::bitcoin::secp256k1::{Secp256k1, Signing};
45
use miniscript::bitcoin::ScriptBuf;
@@ -156,6 +157,83 @@ impl WrapDescriptor {
156157
}
157158
}
158159

160+
fn from_string_definite_ext(
161+
descriptor: &str,
162+
ext_params: &miniscript::miniscript::analyzable::ExtParams,
163+
) -> Result<WrapDescriptor, WasmUtxoError> {
164+
let desc = Descriptor::<DefiniteDescriptorKey>::from_str_ext(descriptor, ext_params)?;
165+
Ok(WrapDescriptor(WrapDescriptorEnum::Definite(desc)))
166+
}
167+
168+
/// Parse a descriptor string with custom ExtParams for taproot leaf validation.
169+
///
170+
/// This allows control over which miniscript analysis checks are applied to
171+
/// taproot leaves. All options default to false (sane behavior).
172+
///
173+
/// # Arguments
174+
/// * `descriptor` - A string containing the descriptor to parse
175+
/// * `pk_type` - The type of public key ("definite" only for now)
176+
/// * `ext_params_config` - JavaScript object with optional boolean flags:
177+
/// - `drop`: Allow drop operations (r: wrapper)
178+
/// - `topUnsafe`: Allow scripts without signatures on all paths
179+
/// - `resourceLimitations`: Allow scripts exceeding resource limits
180+
/// - `timelockMixing`: Allow CSV + CLTV mixing
181+
/// - `malleability`: Allow malleable scripts
182+
/// - `repeatedPk`: Allow repeated public keys
183+
/// - `rawPkh`: Allow raw pubkey hash fragments
184+
///
185+
/// # Example
186+
/// ```javascript
187+
/// // Allow r:older() only (for sBTC)
188+
/// Descriptor.fromStringExt(desc, "definite", { drop: true })
189+
///
190+
/// // Allow multiple extensions
191+
/// Descriptor.fromStringExt(desc, "definite", { drop: true, malleability: true })
192+
///
193+
/// // All sane (default)
194+
/// Descriptor.fromStringExt(desc, "definite", {})
195+
/// ```
196+
#[wasm_bindgen(js_name = fromStringExt, skip_typescript)]
197+
pub fn from_string_ext(
198+
descriptor: &str,
199+
pk_type: &str,
200+
ext_params_config: JsValue,
201+
) -> Result<WrapDescriptor, WasmUtxoError> {
202+
let flag = |key| -> Result<bool, WasmUtxoError> {
203+
Ok(get_field::<Option<bool>>(&ext_params_config, key)?.unwrap_or(false))
204+
};
205+
206+
let mut params = miniscript::miniscript::analyzable::ExtParams::sane();
207+
if flag("drop")? {
208+
params = params.drop();
209+
}
210+
if flag("topUnsafe")? {
211+
params = params.top_unsafe();
212+
}
213+
if flag("resourceLimitations")? {
214+
params = params.exceed_resource_limitations();
215+
}
216+
if flag("timelockMixing")? {
217+
params = params.timelock_mixing();
218+
}
219+
if flag("malleability")? {
220+
params = params.malleability();
221+
}
222+
if flag("repeatedPk")? {
223+
params = params.repeated_pk();
224+
}
225+
if flag("rawPkh")? {
226+
params = params.raw_pkh();
227+
}
228+
229+
match pk_type {
230+
"definite" => WrapDescriptor::from_string_definite_ext(descriptor, &params),
231+
_ => Err(WasmUtxoError::new(
232+
"fromStringExt only supports 'definite' pk_type",
233+
)),
234+
}
235+
}
236+
159237
/// Parse a descriptor string, automatically detecting the appropriate public key type.
160238
/// This will check if the descriptor contains wildcards to determine if it should be
161239
/// parsed as derivable or definite.

packages/wasm-utxo/src/wasm/try_from_js_value.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ impl TryFromJsValue for u8 {
7878
}
7979
}
8080

81+
impl TryFromJsValue for bool {
82+
fn try_from_js_value(value: &JsValue) -> Result<Self, WasmUtxoError> {
83+
value
84+
.as_bool()
85+
.ok_or_else(|| WasmUtxoError::new("Expected a boolean"))
86+
}
87+
}
88+
8189
impl TryFromJsValue for u32 {
8290
fn try_from_js_value(value: &JsValue) -> Result<Self, WasmUtxoError> {
8391
value

0 commit comments

Comments
 (0)