Skip to content

Commit 0eb287f

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 33456b8 commit 0eb287f

File tree

3 files changed

+104
-6
lines changed

3 files changed

+104
-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: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::error::WasmMiniscriptError;
22
use crate::try_into_js_value::TryIntoJsValue;
3-
use miniscript::bitcoin::secp256k1::Secp256k1;
3+
use miniscript::bitcoin::secp256k1::{Context, Secp256k1, Signing};
44
use miniscript::bitcoin::ScriptBuf;
55
use miniscript::descriptor::KeyMap;
66
use miniscript::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey};
@@ -71,7 +71,9 @@ impl WrapDescriptor {
7171
pub fn script_pubkey(&self) -> Result<Vec<u8>, WasmMiniscriptError> {
7272
match &self.0 {
7373
WrapDescriptorEnum::Definite(desc) => Ok(desc.script_pubkey().to_bytes()),
74-
_ => Err(WasmMiniscriptError::new("Cannot derive from a non-definite descriptor")),
74+
_ => Err(WasmMiniscriptError::new(
75+
"Cannot encode a derivable descriptor",
76+
)),
7577
}
7678
}
7779

@@ -109,8 +111,10 @@ impl WrapDescriptor {
109111
.map_err(|_| WasmMiniscriptError::new("Weight exceeds u32"))
110112
}
111113

112-
fn from_string_derivable(descriptor: &str) -> Result<WrapDescriptor, WasmMiniscriptError> {
113-
let secp = Secp256k1::new();
114+
fn from_string_derivable<C: Signing>(
115+
secp: &Secp256k1<C>,
116+
descriptor: &str,
117+
) -> Result<WrapDescriptor, WasmMiniscriptError> {
114118
let (desc, keys) = Descriptor::parse_descriptor(&secp, descriptor)?;
115119
Ok(WrapDescriptor(WrapDescriptorEnum::Derivable(desc, keys)))
116120
}
@@ -120,13 +124,35 @@ impl WrapDescriptor {
120124
Ok(WrapDescriptor(WrapDescriptorEnum::Definite(desc)))
121125
}
122126

127+
/// Parse a descriptor string with an explicit public key type.
128+
///
129+
/// Note that this function permits parsing a non-derivable descriptor with a derivable key type.
130+
/// Use `from_string_detect_type` to automatically detect the key type.
131+
///
132+
/// # Arguments
133+
/// * `descriptor` - A string containing the descriptor to parse
134+
/// * `pk_type` - The type of public key to expect:
135+
/// - "derivable": For descriptors containing derivation paths (eg. xpubs)
136+
/// - "definite": For descriptors with fully specified keys
137+
/// - "string": For descriptors with string placeholders
138+
///
139+
/// # Returns
140+
/// * `Result<WrapDescriptor, WasmMiniscriptError>` - The parsed descriptor or an error
141+
///
142+
/// # Example
143+
/// ```
144+
/// let desc = WrapDescriptor::from_string(
145+
/// "pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/*)",
146+
/// "derivable"
147+
/// );
148+
/// ```
123149
#[wasm_bindgen(js_name = fromString, skip_typescript)]
124150
pub fn from_string(
125151
descriptor: &str,
126152
pk_type: &str,
127153
) -> Result<WrapDescriptor, WasmMiniscriptError> {
128154
match pk_type {
129-
"derivable" => WrapDescriptor::from_string_derivable(descriptor),
155+
"derivable" => WrapDescriptor::from_string_derivable(&Secp256k1::new(), descriptor),
130156
"definite" => WrapDescriptor::from_string_definite(descriptor),
131157
"string" => {
132158
let desc = Descriptor::<String>::from_str(descriptor)?;
@@ -135,4 +161,71 @@ impl WrapDescriptor {
135161
_ => Err(WasmMiniscriptError::new("Invalid descriptor type")),
136162
}
137163
}
138-
}
164+
165+
/// Parse a descriptor string, automatically detecting the appropriate public key type.
166+
/// This will check if the descriptor contains wildcards to determine if it should be
167+
/// parsed as derivable or definite.
168+
///
169+
/// # Arguments
170+
/// * `descriptor` - A string containing the descriptor to parse
171+
///
172+
/// # Returns
173+
/// * `Result<WrapDescriptor, WasmMiniscriptError>` - The parsed descriptor or an error
174+
///
175+
/// # Example
176+
/// ```
177+
/// // Will be parsed as definite since it has no wildcards
178+
/// let desc = WrapDescriptor::from_string_detect_type(
179+
/// "pk(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)"
180+
/// );
181+
///
182+
/// // Will be parsed as derivable since it contains a wildcard (*)
183+
/// let desc = WrapDescriptor::from_string_detect_type(
184+
/// "pk(xpub.../0/*)"
185+
/// );
186+
/// ```
187+
#[wasm_bindgen(js_name = fromStringDetectType, skip_typescript)]
188+
pub fn from_string_detect_type(
189+
descriptor: &str,
190+
) -> Result<WrapDescriptor, WasmMiniscriptError> {
191+
let secp = Secp256k1::new();
192+
let (descriptor, _key_map) = Descriptor::parse_descriptor(&secp, descriptor)
193+
.map_err(|_| WasmMiniscriptError::new("Invalid descriptor"))?;
194+
if descriptor.has_wildcard() {
195+
WrapDescriptor::from_string_derivable(&secp, &descriptor.to_string())
196+
} else {
197+
WrapDescriptor::from_string_definite(&descriptor.to_string())
198+
}
199+
}
200+
}
201+
202+
impl FromStr for WrapDescriptor {
203+
type Err = WasmMiniscriptError;
204+
fn from_str(s: &str) -> Result<Self, Self::Err> {
205+
WrapDescriptor::from_string_detect_type(s)
206+
}
207+
}
208+
209+
#[cfg(test)]
210+
mod tests {
211+
use crate::WrapDescriptor;
212+
213+
#[test]
214+
fn test_detect_type() {
215+
let desc = WrapDescriptor::from_string_detect_type(
216+
"pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)",
217+
)
218+
.unwrap();
219+
220+
assert_eq!(desc.has_wildcard(), false);
221+
assert_eq!(
222+
match desc {
223+
WrapDescriptor {
224+
0: crate::descriptor::WrapDescriptorEnum::Definite(_),
225+
} => true,
226+
_ => false,
227+
},
228+
true
229+
);
230+
}
231+
}

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)