Skip to content

Commit

Permalink
feat: improve wrapper structs
Browse files Browse the repository at this point in the history
* add to `type Descriptor`:
  - `hasWildcard` method
  - `atDerivationIndex` method
  - `toAsmString` method
* add to `type Miniscript`:
  - `toAsmString` method
* add parameter `pkType` to `descriptorFromString` function

Issue: BTC-1317
  • Loading branch information
OttoAllmendinger committed Jul 16, 2024
1 parent 3c603e6 commit d5b41bc
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
.idea/
*.iml
*.tsbuildinfo
4 changes: 2 additions & 2 deletions packages/wasm-miniscript/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/wasm-miniscript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
miniscript = "12"
miniscript = { version = "12.1.0", features = ["no-std"] }
33 changes: 26 additions & 7 deletions packages/wasm-miniscript/js/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import * as wasm from "./wasm/wasm_miniscript";

type MiniscriptNode = unknown;
// we need to access the wasm module here, otherwise webpack gets all weird
// and forgets to include it in the bundle
void wasm;

type Miniscript = {
export type MiniscriptNode = unknown;

export type Miniscript = {
node(): MiniscriptNode;
toString(): string;
encode(): Uint8Array;
toAsmString(): string;
};

type ScriptContext = "tap" | "segwitv0" | "legacy";
export function isMiniscript(obj: unknown): obj is Miniscript {
return obj instanceof wasm.WrapMiniscript;
}

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

export function miniscriptFromString(script: string, scriptContext: ScriptContext): Miniscript {
return wasm.miniscript_from_string(script, scriptContext);
Expand All @@ -21,13 +30,23 @@ export function miniscriptFromBitcoinScript(
return wasm.miniscript_from_bitcoin_script(script, scriptContext);
}

type DescriptorNode = unknown;
export type DescriptorNode = unknown;

type Descriptor = {
export type Descriptor = {
node(): DescriptorNode;
toString(): string;
hasWildcard(): boolean;
atDerivationIndex(index: number): Descriptor;
encode(): Uint8Array;
toAsmString(): string;
};

export function descriptorFromString(descriptor: string): Descriptor {
return wasm.descriptor_from_string(descriptor);
export function isDescriptor(obj: unknown): obj is Descriptor {
return obj instanceof wasm.WrapDescriptor;
}

type DescriptorPkType = "derivable" | "definite" | "string";

export function descriptorFromString(descriptor: string, pkType: DescriptorPkType): Descriptor {
return wasm.descriptor_from_string(descriptor, pkType);
}
184 changes: 163 additions & 21 deletions packages/wasm-miniscript/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
mod traits;

use crate::traits::TryIntoJsValue;
use miniscript::bitcoin::secp256k1::Secp256k1;
use miniscript::bitcoin::{PublicKey, ScriptBuf};
use miniscript::descriptor::KeyMap;
use miniscript::{
bitcoin, bitcoin::XOnlyPublicKey, DefiniteDescriptorKey, Descriptor, DescriptorPublicKey,
Legacy, Miniscript, Segwitv0, Tap,
};
use std::fmt;
use std::str::FromStr;
use miniscript::{bitcoin, bitcoin::XOnlyPublicKey, Descriptor, Legacy, Miniscript, Segwitv0, Tap};
use miniscript::bitcoin::PublicKey;
use wasm_bindgen::prelude::*;
use crate::traits::TryIntoJsValue;

#[derive(Debug, Clone)]
enum WrapError {
Miniscript(String),
Bitcoin(String),
}

impl std::error::Error for WrapError {}
impl fmt::Display for WrapError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
WrapError::Miniscript(e) => write!(f, "Miniscript error: {}", e),
WrapError::Bitcoin(e) => write!(f, "Bitcoin error: {}", e),
}
}
}

impl From<miniscript::Error> for WrapError {
fn from(e: miniscript::Error) -> Self {
WrapError::Miniscript(e.to_string())
}
}

impl From<bitcoin::consensus::encode::Error> for WrapError {
fn from(e: bitcoin::consensus::encode::Error) -> Self {
WrapError::Bitcoin(e.to_string())
}
}

fn wrap_err<T, E: std::fmt::Debug>(r: Result<T, E>) -> Result<T, JsValue> {
r.map_err(|e| JsValue::from_str(&format!("{:?}", e)))
Expand Down Expand Up @@ -34,7 +68,7 @@ pub struct WrapMiniscript(WrapMiniscriptEnum);
#[wasm_bindgen]
impl WrapMiniscript {
#[wasm_bindgen(js_name = node)]
pub fn node(&self) -> Result<JsValue, JsValue> {
pub fn node(&self) -> Result<JsValue, JsError> {
unwrap_apply!(&self.0, |ms| ms.try_to_js_value())
}

Expand All @@ -47,6 +81,11 @@ impl WrapMiniscript {
pub fn encode(&self) -> Vec<u8> {
unwrap_apply!(&self.0, |ms| ms.encode().into_bytes())
}

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

impl From<Miniscript<XOnlyPublicKey, Tap>> for WrapMiniscript {
Expand All @@ -70,42 +109,145 @@ impl From<Miniscript<PublicKey, Legacy>> for WrapMiniscript {
#[wasm_bindgen]
pub fn miniscript_from_string(script: &str, context_type: &str) -> Result<WrapMiniscript, JsValue> {
match context_type {
"tap" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<XOnlyPublicKey, Tap>::from_str(script))?)),
"segwitv0" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<PublicKey, Segwitv0>::from_str(script))?)),
"legacy" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<PublicKey, Legacy>::from_str(script))?)),
_ => Err(JsValue::from_str("Invalid context type"))
"tap" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<
XOnlyPublicKey,
Tap,
>::from_str(script))?)),
"segwitv0" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<
PublicKey,
Segwitv0,
>::from_str(script))?)),
"legacy" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<
PublicKey,
Legacy,
>::from_str(script))?)),
_ => Err(JsValue::from_str("Invalid context type")),
}
}

#[wasm_bindgen]
pub fn miniscript_from_bitcoin_script(script: &[u8], context_type: &str) -> Result<WrapMiniscript, JsValue> {
pub fn miniscript_from_bitcoin_script(
script: &[u8],
context_type: &str,
) -> Result<WrapMiniscript, JsValue> {
let script = bitcoin::Script::from_bytes(script);
match context_type {
"tap" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<XOnlyPublicKey, Tap>::parse(script))?)),
"segwitv0" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<PublicKey, Segwitv0>::parse(script))?)),
"legacy" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<PublicKey, Legacy>::parse(script))?)),
_ => Err(JsValue::from_str("Invalid context type"))
"tap" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<
XOnlyPublicKey,
Tap,
>::parse(script))?)),
"segwitv0" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<
PublicKey,
Segwitv0,
>::parse(script))?)),
"legacy" => Ok(WrapMiniscript::from(wrap_err(Miniscript::<
PublicKey,
Legacy,
>::parse(script))?)),
_ => Err(JsValue::from_str("Invalid context type")),
}
}

enum WrapDescriptorEnum {
Derivable(Descriptor<DescriptorPublicKey>, KeyMap),
Definite(Descriptor<DefiniteDescriptorKey>),
String(Descriptor<String>),
}

#[wasm_bindgen]
pub struct WrapDescriptor(Descriptor<String>);
pub struct WrapDescriptor(WrapDescriptorEnum);

#[wasm_bindgen]
impl WrapDescriptor {
pub fn node(&self) -> Result<JsValue, JsValue> {
self.0.try_to_js_value()
pub fn node(&self) -> Result<JsValue, JsError> {
Ok(match &self.0 {
WrapDescriptorEnum::Derivable(desc, _) => desc.try_to_js_value()?,
WrapDescriptorEnum::Definite(desc) => desc.try_to_js_value()?,
WrapDescriptorEnum::String(desc) => desc.try_to_js_value()?,
})
}

#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.0.to_string()
match &self.0 {
WrapDescriptorEnum::Derivable(desc, _) => desc.to_string(),
WrapDescriptorEnum::Definite(desc) => desc.to_string(),
WrapDescriptorEnum::String(desc) => desc.to_string(),
}
}

#[wasm_bindgen(js_name = hasWildcard)]
pub fn has_wildcard(&self) -> bool {
match &self.0 {
WrapDescriptorEnum::Derivable(desc, _) => desc.has_wildcard(),
WrapDescriptorEnum::Definite(_) => false,
WrapDescriptorEnum::String(_) => false,
}
}

#[wasm_bindgen(js_name = atDerivationIndex)]
pub fn at_derivation_index(&self, index: u32) -> Result<WrapDescriptor, JsError> {
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")),
}
}

fn explicit_script(&self) -> Result<ScriptBuf, JsError> {
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")),
}
}

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

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

#[wasm_bindgen]
pub fn descriptor_from_string(descriptor: &str) -> Result<WrapDescriptor, JsValue> {
Ok(
WrapDescriptor(Descriptor::<String>::from_str(descriptor)
.map_err(|e| js_sys::Error::new(&format!("{:?}", e)))?))
pub fn descriptor_from_string(descriptor: &str, pk_type: &str) -> Result<WrapDescriptor, JsError> {
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)))
}
"string" => {
let desc = Descriptor::<String>::from_str(descriptor)?;
Ok(WrapDescriptor(WrapDescriptorEnum::String(desc)))
}
_ => Err(JsError::new("Invalid descriptor type")),
}
}


#[test]
pub fn panic_xprv() {

let (d,m) = Descriptor::parse_descriptor(
&Secp256k1::new(),
"wsh(multi(2,xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0,xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt/1/2/*,xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi/10/20/30/40/*'))",
).unwrap();

let dd = d.at_derivation_index(0).unwrap();

let _ = dd.explicit_script().unwrap();
}
Loading

0 comments on commit d5b41bc

Please sign in to comment.