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
1 change: 1 addition & 0 deletions packages/wasm-miniscript/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare module "./wasm/wasm_miniscript" {

namespace WrapDescriptor {
function fromString(descriptor: string, pkType: DescriptorPkType): WrapDescriptor;
function fromStringDetectType(descriptor: string): WrapDescriptor;
}

interface WrapMiniscript {
Expand Down
162 changes: 133 additions & 29 deletions packages/wasm-miniscript/src/descriptor.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::error::WasmMiniscriptError;
use crate::try_into_js_value::TryIntoJsValue;
use miniscript::bitcoin::secp256k1::Secp256k1;
use miniscript::bitcoin::secp256k1::{Context, Secp256k1, Signing};
use miniscript::bitcoin::ScriptBuf;
use miniscript::descriptor::KeyMap;
use miniscript::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey};
use std::str::FromStr;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsError, JsValue};
use wasm_bindgen::prelude::*;

pub(crate) enum WrapDescriptorEnum {
Derivable(Descriptor<DescriptorPublicKey>, KeyMap),
Expand All @@ -18,7 +18,7 @@ pub struct WrapDescriptor(pub(crate) WrapDescriptorEnum);

#[wasm_bindgen]
impl WrapDescriptor {
pub fn node(&self) -> Result<JsValue, JsError> {
pub fn node(&self) -> Result<JsValue, WasmMiniscriptError> {
Ok(match &self.0 {
WrapDescriptorEnum::Derivable(desc, _) => desc.try_to_js_value()?,
WrapDescriptorEnum::Definite(desc) => desc.try_to_js_value()?,
Expand All @@ -45,18 +45,20 @@ impl WrapDescriptor {
}

#[wasm_bindgen(js_name = atDerivationIndex)]
pub fn at_derivation_index(&self, index: u32) -> Result<WrapDescriptor, JsError> {
pub fn at_derivation_index(&self, index: u32) -> Result<WrapDescriptor, WasmMiniscriptError> {
match &self.0 {
WrapDescriptorEnum::Derivable(desc, _keys) => {
let d = desc.at_derivation_index(index)?;
Ok(WrapDescriptor(WrapDescriptorEnum::Definite(d)))
}
_ => Err(JsError::new("Cannot derive from a definite descriptor")),
_ => Err(WasmMiniscriptError::new(
"Cannot derive from a definite descriptor",
)),
}
}

#[wasm_bindgen(js_name = descType)]
pub fn desc_type(&self) -> Result<JsValue, JsError> {
pub fn desc_type(&self) -> Result<JsValue, WasmMiniscriptError> {
(match &self.0 {
WrapDescriptorEnum::Derivable(desc, _) => desc.desc_type(),
WrapDescriptorEnum::Definite(desc) => desc.desc_type(),
Expand All @@ -66,34 +68,38 @@ impl WrapDescriptor {
}

#[wasm_bindgen(js_name = scriptPubkey)]
pub fn script_pubkey(&self) -> Result<Vec<u8>, JsError> {
pub fn script_pubkey(&self) -> Result<Vec<u8>, WasmMiniscriptError> {
match &self.0 {
WrapDescriptorEnum::Definite(desc) => Ok(desc.script_pubkey().to_bytes()),
_ => Err(JsError::new("Cannot derive from a non-definite descriptor")),
_ => Err(WasmMiniscriptError::new(
"Cannot encode a derivable descriptor",
)),
}
}

fn explicit_script(&self) -> Result<ScriptBuf, JsError> {
fn explicit_script(&self) -> Result<ScriptBuf, WasmMiniscriptError> {
match &self.0 {
WrapDescriptorEnum::Definite(desc) => Ok(desc.explicit_script()?),
WrapDescriptorEnum::Derivable(_, _) => {
Err(JsError::new("Cannot encode a derivable descriptor"))
}
WrapDescriptorEnum::String(_) => Err(JsError::new("Cannot encode a string descriptor")),
WrapDescriptorEnum::Derivable(_, _) => Err(WasmMiniscriptError::new(
"Cannot encode a derivable descriptor",
)),
WrapDescriptorEnum::String(_) => Err(WasmMiniscriptError::new(
"Cannot encode a string descriptor",
)),
}
}

pub fn encode(&self) -> Result<Vec<u8>, JsError> {
pub fn encode(&self) -> Result<Vec<u8>, WasmMiniscriptError> {
Ok(self.explicit_script()?.to_bytes())
}

#[wasm_bindgen(js_name = toAsmString)]
pub fn to_asm_string(&self) -> Result<String, JsError> {
pub fn to_asm_string(&self) -> Result<String, WasmMiniscriptError> {
Ok(self.explicit_script()?.to_asm_string())
}

#[wasm_bindgen(js_name = maxWeightToSatisfy)]
pub fn max_weight_to_satisfy(&self) -> Result<u32, JsError> {
pub fn max_weight_to_satisfy(&self) -> Result<u32, WasmMiniscriptError> {
let weight = (match &self.0 {
WrapDescriptorEnum::Derivable(desc, _) => desc.max_weight_to_satisfy(),
WrapDescriptorEnum::Definite(desc) => desc.max_weight_to_satisfy(),
Expand All @@ -102,26 +108,124 @@ impl WrapDescriptor {
weight
.to_wu()
.try_into()
.map_err(|_| JsError::new("Weight exceeds u32"))
.map_err(|_| WasmMiniscriptError::new("Weight exceeds u32"))
}

fn from_string_derivable<C: Signing>(
secp: &Secp256k1<C>,
descriptor: &str,
) -> Result<WrapDescriptor, WasmMiniscriptError> {
let (desc, keys) = Descriptor::parse_descriptor(&secp, descriptor)?;
Ok(WrapDescriptor(WrapDescriptorEnum::Derivable(desc, keys)))
}

fn from_string_definite(descriptor: &str) -> Result<WrapDescriptor, WasmMiniscriptError> {
let desc = Descriptor::<DefiniteDescriptorKey>::from_str(descriptor)?;
Ok(WrapDescriptor(WrapDescriptorEnum::Definite(desc)))
}

/// Parse a descriptor string with an explicit public key type.
///
/// Note that this function permits parsing a non-derivable descriptor with a derivable key type.
/// Use `from_string_detect_type` to automatically detect the key type.
///
/// # Arguments
/// * `descriptor` - A string containing the descriptor to parse
/// * `pk_type` - The type of public key to expect:
/// - "derivable": For descriptors containing derivation paths (eg. xpubs)
/// - "definite": For descriptors with fully specified keys
/// - "string": For descriptors with string placeholders
///
/// # Returns
/// * `Result<WrapDescriptor, WasmMiniscriptError>` - The parsed descriptor or an error
///
/// # Example
/// ```
/// let desc = WrapDescriptor::from_string(
/// "pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8/*)",
/// "derivable"
/// );
/// ```
#[wasm_bindgen(js_name = fromString, skip_typescript)]
pub fn from_string(descriptor: &str, pk_type: &str) -> Result<WrapDescriptor, JsError> {
pub fn from_string(
descriptor: &str,
pk_type: &str,
) -> Result<WrapDescriptor, WasmMiniscriptError> {
match pk_type {
"derivable" => {
let secp = Secp256k1::new();
let (desc, keys) = Descriptor::parse_descriptor(&secp, descriptor)?;
Ok(WrapDescriptor(WrapDescriptorEnum::Derivable(desc, keys)))
}
"definite" => {
let desc = Descriptor::<DefiniteDescriptorKey>::from_str(descriptor)?;
Ok(WrapDescriptor(WrapDescriptorEnum::Definite(desc)))
}
"derivable" => WrapDescriptor::from_string_derivable(&Secp256k1::new(), descriptor),
"definite" => WrapDescriptor::from_string_definite(descriptor),
"string" => {
let desc = Descriptor::<String>::from_str(descriptor)?;
Ok(WrapDescriptor(WrapDescriptorEnum::String(desc)))
}
_ => Err(JsError::new("Invalid descriptor type")),
_ => Err(WasmMiniscriptError::new("Invalid descriptor type")),
}
}

/// Parse a descriptor string, automatically detecting the appropriate public key type.
/// This will check if the descriptor contains wildcards to determine if it should be
/// parsed as derivable or definite.
///
/// # Arguments
/// * `descriptor` - A string containing the descriptor to parse
///
/// # Returns
/// * `Result<WrapDescriptor, WasmMiniscriptError>` - The parsed descriptor or an error
///
/// # Example
/// ```
/// // Will be parsed as definite since it has no wildcards
/// let desc = WrapDescriptor::from_string_detect_type(
/// "pk(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)"
/// );
///
/// // Will be parsed as derivable since it contains a wildcard (*)
/// let desc = WrapDescriptor::from_string_detect_type(
/// "pk(xpub.../0/*)"
/// );
/// ```
#[wasm_bindgen(js_name = fromStringDetectType, skip_typescript)]
pub fn from_string_detect_type(
descriptor: &str,
) -> Result<WrapDescriptor, WasmMiniscriptError> {
let secp = Secp256k1::new();
let (descriptor, _key_map) = Descriptor::parse_descriptor(&secp, descriptor)
.map_err(|_| WasmMiniscriptError::new("Invalid descriptor"))?;
if descriptor.has_wildcard() {
WrapDescriptor::from_string_derivable(&secp, &descriptor.to_string())
} else {
WrapDescriptor::from_string_definite(&descriptor.to_string())
}
}
}

impl FromStr for WrapDescriptor {
type Err = WasmMiniscriptError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
WrapDescriptor::from_string_detect_type(s)
}
}

#[cfg(test)]
mod tests {
use crate::WrapDescriptor;

#[test]
fn test_detect_type() {
let desc = WrapDescriptor::from_string_detect_type(
"pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)",
)
.unwrap();

assert_eq!(desc.has_wildcard(), false);
assert_eq!(
match desc {
WrapDescriptor {
0: crate::descriptor::WrapDescriptorEnum::Definite(_),
} => true,
_ => false,
},
true
);
}
}
45 changes: 45 additions & 0 deletions packages/wasm-miniscript/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use core::fmt;

#[derive(Debug, Clone)]
pub enum WasmMiniscriptError {
StringError(String),
}

impl std::error::Error for WasmMiniscriptError {}
impl fmt::Display for WasmMiniscriptError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WasmMiniscriptError::StringError(s) => write!(f, "{}", s),
}
}
}

impl From<&str> for WasmMiniscriptError {
fn from(s: &str) -> Self {
WasmMiniscriptError::StringError(s.to_string())
}
}

impl From<String> for WasmMiniscriptError {
fn from(s: String) -> Self {
WasmMiniscriptError::StringError(s)
}
}

impl From<miniscript::Error> for WasmMiniscriptError {
fn from(err: miniscript::Error) -> Self {
WasmMiniscriptError::StringError(err.to_string())
}
}

impl From<miniscript::descriptor::ConversionError> for WasmMiniscriptError {
fn from(err: miniscript::descriptor::ConversionError) -> Self {
WasmMiniscriptError::StringError(err.to_string())
}
}

impl WasmMiniscriptError {
pub fn new(s: &str) -> WasmMiniscriptError {
WasmMiniscriptError::StringError(s.to_string())
}
}
1 change: 1 addition & 0 deletions packages/wasm-miniscript/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod descriptor;
mod error;
mod miniscript;
mod psbt;
mod try_into_js_value;
Expand Down
39 changes: 24 additions & 15 deletions packages/wasm-miniscript/src/miniscript.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::error::WasmMiniscriptError;
use crate::try_into_js_value::TryIntoJsValue;
use miniscript::bitcoin::{PublicKey, XOnlyPublicKey};
use miniscript::{bitcoin, Legacy, Miniscript, Segwitv0, Tap};
use std::str::FromStr;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsError, JsValue};

use crate::try_into_js_value::TryIntoJsValue;
use wasm_bindgen::JsValue;

// Define the macro to simplify operations on WrapMiniscriptEnum variants
// apply a func to the miniscript variant
Expand All @@ -30,7 +30,7 @@ pub struct WrapMiniscript(WrapMiniscriptEnum);
#[wasm_bindgen]
impl WrapMiniscript {
#[wasm_bindgen(js_name = node)]
pub fn node(&self) -> Result<JsValue, JsError> {
pub fn node(&self) -> Result<JsValue, WasmMiniscriptError> {
unwrap_apply!(&self.0, |ms| ms.try_to_js_value())
}

Expand All @@ -45,43 +45,52 @@ impl WrapMiniscript {
}

#[wasm_bindgen(js_name = toAsmString)]
pub fn to_asm_string(&self) -> Result<String, JsError> {
pub fn to_asm_string(&self) -> Result<String, WasmMiniscriptError> {
unwrap_apply!(&self.0, |ms| Ok(ms.encode().to_asm_string()))
}

#[wasm_bindgen(js_name = fromString, skip_typescript)]
pub fn from_string(script: &str, context_type: &str) -> Result<WrapMiniscript, JsError> {
pub fn from_string(
script: &str,
context_type: &str,
) -> Result<WrapMiniscript, WasmMiniscriptError> {
match context_type {
"tap" => Ok(WrapMiniscript::from(
Miniscript::<XOnlyPublicKey, Tap>::from_str(script).map_err(JsError::from)?,
Miniscript::<XOnlyPublicKey, Tap>::from_str(script)
.map_err(WasmMiniscriptError::from)?,
)),
"segwitv0" => Ok(WrapMiniscript::from(
Miniscript::<PublicKey, Segwitv0>::from_str(script).map_err(JsError::from)?,
Miniscript::<PublicKey, Segwitv0>::from_str(script)
.map_err(WasmMiniscriptError::from)?,
)),
"legacy" => Ok(WrapMiniscript::from(
Miniscript::<PublicKey, Legacy>::from_str(script).map_err(JsError::from)?,
Miniscript::<PublicKey, Legacy>::from_str(script)
.map_err(WasmMiniscriptError::from)?,
)),
_ => Err(JsError::new("Invalid context type")),
_ => Err(WasmMiniscriptError::new("Invalid context type")),
}
}

#[wasm_bindgen(js_name = fromBitcoinScript, skip_typescript)]
pub fn from_bitcoin_script(
script: &[u8],
context_type: &str,
) -> Result<WrapMiniscript, JsError> {
) -> Result<WrapMiniscript, WasmMiniscriptError> {
let script = bitcoin::Script::from_bytes(script);
match context_type {
"tap" => Ok(WrapMiniscript::from(
Miniscript::<XOnlyPublicKey, Tap>::parse(script).map_err(JsError::from)?,
Miniscript::<XOnlyPublicKey, Tap>::parse(script)
.map_err(WasmMiniscriptError::from)?,
)),
"segwitv0" => Ok(WrapMiniscript::from(
Miniscript::<PublicKey, Segwitv0>::parse(script).map_err(JsError::from)?,
Miniscript::<PublicKey, Segwitv0>::parse(script)
.map_err(WasmMiniscriptError::from)?,
)),
"legacy" => Ok(WrapMiniscript::from(
Miniscript::<PublicKey, Legacy>::parse(script).map_err(JsError::from)?,
Miniscript::<PublicKey, Legacy>::parse(script)
.map_err(WasmMiniscriptError::from)?,
)),
_ => Err(JsError::new("Invalid context type")),
_ => Err(WasmMiniscriptError::new("Invalid context type")),
}
}
}
Expand Down
Loading