Skip to content

Commit f9a0423

Browse files
feat(miniscript): add auto-detection of descriptor type
Add fromStringDetectType() that auto-detects whether a descriptor contains wildcards. If it does, returns a derivable descriptor, otherwise a definite one. Issue: BTC-1826
1 parent 9d9e9eb commit f9a0423

File tree

3 files changed

+88
-6
lines changed

3 files changed

+88
-6
lines changed

packages/wasm-miniscript/js/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ declare module "./wasm/wasm_miniscript" {
2020

2121
namespace WrapDescriptor {
2222
function fromString(descriptor: string, pkType: DescriptorPkType): WrapDescriptor;
23+
function fromStringDetectType(descriptor: string): WrapDescriptor;
2324
}
2425

2526
interface WrapMiniscript {

packages/wasm-miniscript/src/descriptor.rs

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::try_into_js_value::TryIntoJsValue;
2-
use miniscript::bitcoin::secp256k1::Secp256k1;
2+
use miniscript::bitcoin::secp256k1::{Context, Secp256k1, Signing};
33
use miniscript::bitcoin::ScriptBuf;
44
use miniscript::descriptor::KeyMap;
55
use miniscript::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey};
@@ -69,7 +69,7 @@ impl WrapDescriptor {
6969
pub fn script_pubkey(&self) -> Result<Vec<u8>, WasmMiniscriptError> {
7070
match &self.0 {
7171
WrapDescriptorEnum::Definite(desc) => Ok(desc.script_pubkey().to_bytes()),
72-
_ => Err(WasmMiniscriptError::new("Cannot derive from a non-definite descriptor")),
72+
_ => Err(WasmMiniscriptError::new("Cannot encode a derivable descriptor")),
7373
}
7474
}
7575

@@ -105,8 +105,7 @@ impl WrapDescriptor {
105105
.map_err(|_| WasmMiniscriptError::new("Weight exceeds u32"))
106106
}
107107

108-
fn from_string_derivable(descriptor: &str) -> Result<WrapDescriptor, WasmMiniscriptError> {
109-
let secp = Secp256k1::new();
108+
fn from_string_derivable<C: Signing>(secp: &Secp256k1<C>, descriptor: &str) -> Result<WrapDescriptor, WasmMiniscriptError> {
110109
let (desc, keys) = Descriptor::parse_descriptor(&secp, descriptor)?;
111110
Ok(WrapDescriptor(WrapDescriptorEnum::Derivable(desc, keys)))
112111
}
@@ -116,10 +115,32 @@ impl WrapDescriptor {
116115
Ok(WrapDescriptor(WrapDescriptorEnum::Definite(desc)))
117116
}
118117

118+
/// Parse a descriptor string with an explicit public key type.
119+
///
120+
/// Note that this function permits parsing a non-derivable descriptor with a derivable key type.
121+
/// Use `from_string_detect_type` to automatically detect the key type.
122+
///
123+
/// # Arguments
124+
/// * `descriptor` - A string containing the descriptor to parse
125+
/// * `pk_type` - The type of public key to expect:
126+
/// - "derivable": For descriptors containing derivation paths (eg. xpubs)
127+
/// - "definite": For descriptors with fully specified keys
128+
/// - "string": For descriptors with string placeholders
129+
///
130+
/// # Returns
131+
/// * `Result<WrapDescriptor, WasmMiniscriptError>` - The parsed descriptor or an error
132+
///
133+
/// # Example
134+
/// ```
135+
/// let desc = WrapDescriptor::from_string(
136+
/// "pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/*)",
137+
/// "derivable"
138+
/// );
139+
/// ```
119140
#[wasm_bindgen(js_name = fromString, skip_typescript)]
120141
pub fn from_string(descriptor: &str, pk_type: &str) -> Result<WrapDescriptor, WasmMiniscriptError> {
121142
match pk_type {
122-
"derivable" => WrapDescriptor::from_string_derivable(descriptor),
143+
"derivable" => WrapDescriptor::from_string_derivable(&Secp256k1::new(), descriptor),
123144
"definite" => WrapDescriptor::from_string_definite(descriptor),
124145
"string" => {
125146
let desc = Descriptor::<String>::from_str(descriptor)?;
@@ -128,4 +149,60 @@ impl WrapDescriptor {
128149
_ => Err(WasmMiniscriptError::new("Invalid descriptor type")),
129150
}
130151
}
131-
}
152+
153+
/// Parse a descriptor string, automatically detecting the appropriate public key type.
154+
/// This will check if the descriptor contains wildcards to determine if it should be
155+
/// parsed as derivable or definite.
156+
///
157+
/// # Arguments
158+
/// * `descriptor` - A string containing the descriptor to parse
159+
///
160+
/// # Returns
161+
/// * `Result<WrapDescriptor, WasmMiniscriptError>` - The parsed descriptor or an error
162+
///
163+
/// # Example
164+
/// ```
165+
/// // Will be parsed as definite since it has no wildcards
166+
/// let desc = WrapDescriptor::from_string_detect_type(
167+
/// "pk(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)"
168+
/// );
169+
///
170+
/// // Will be parsed as derivable since it contains a wildcard (*)
171+
/// let desc = WrapDescriptor::from_string_detect_type(
172+
/// "pk(xpub.../0/*)"
173+
/// );
174+
/// ```
175+
#[wasm_bindgen(js_name = fromStringDetectType, skip_typescript)]
176+
pub fn from_string_detect_type(descriptor: &str) -> Result<WrapDescriptor, WasmMiniscriptError> {
177+
let secp = Secp256k1::new();
178+
let (descriptor, _key_map) = Descriptor::parse_descriptor(&secp, descriptor).map_err(|_| WasmMiniscriptError::new("Invalid descriptor"))?;
179+
if descriptor.has_wildcard() {
180+
WrapDescriptor::from_string_derivable(&secp, &descriptor.to_string())
181+
} else {
182+
WrapDescriptor::from_string_definite(&descriptor.to_string())
183+
}
184+
}
185+
}
186+
187+
impl FromStr for WrapDescriptor {
188+
type Err = WasmMiniscriptError;
189+
fn from_str(s: &str) -> Result<Self, Self::Err> {
190+
WrapDescriptor::from_string_detect_type(s)
191+
}
192+
}
193+
194+
#[cfg(test)]
195+
mod tests {
196+
use crate::WrapDescriptor;
197+
198+
#[test]
199+
fn test_detect_type() {
200+
let desc = WrapDescriptor::from_string_detect_type("pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)").unwrap();
201+
202+
assert_eq!(desc.has_wildcard(), false);
203+
assert_eq!(match desc {
204+
WrapDescriptor { 0: crate::descriptor::WrapDescriptorEnum::Definite(_) } => true,
205+
_ => false,
206+
}, true);
207+
}
208+
}

packages/wasm-miniscript/test/test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ describe("Descriptor fixtures", function () {
8484
return;
8585
}
8686

87+
it("should detect descriptor type", function () {
88+
Descriptor.fromStringDetectType(fixture.descriptor);
89+
});
90+
8791
it("should round-trip (pkType string)", function () {
8892
let descriptorString = Descriptor.fromString(fixture.descriptor, "string").toString();
8993
if (fixture.checksumRequired === false) {

0 commit comments

Comments
 (0)