Skip to content

Commit

Permalink
refactor: use static methods for miniscript and descriptor
Browse files Browse the repository at this point in the history
Issue: BTC-1338
  • Loading branch information
OttoAllmendinger committed Aug 21, 2024
1 parent d7a0d08 commit 9b792a7
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 132 deletions.
15 changes: 4 additions & 11 deletions packages/wasm-miniscript-ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import {
descriptorFromString,
miniscriptFromBitcoinScript,
miniscriptFromString,
ScriptContext,
} from "@bitgo/wasm-miniscript";

import * as utxolib from "@bitgo/utxo-lib";
import { Descriptor, Miniscript, ScriptContext } from "@bitgo/wasm-miniscript";

import "./style.css";

import { getElement } from "./html";
import { buildOptions, getOptions, Options } from "./options";
import { getHtmlForAst } from "./htmlAST";
import { Descriptor, Miniscript } from "@bitgo/wasm-miniscript";
import { fromHex, toHex } from "./hex";
import { getShare, setShare, Share } from "./sharing";

Expand All @@ -22,7 +15,7 @@ function createMiniscriptFromBitcoinScriptDetectScriptContext(
const formats = ["tap", "segwitv0", "legacy"] as const;
for (const format of formats) {
try {
return [miniscriptFromBitcoinScript(script, format), format];
return [Miniscript.fromBitcoinScript(script, format), format];
} catch (e) {
// ignore
}
Expand Down Expand Up @@ -168,7 +161,7 @@ function applyUpdate(changedEl: HTMLElement, options: Options) {
changedEl === elEditDescriptor ||
changedEl === getElement("input-derivation-index", HTMLInputElement)
) {
const descriptor = descriptorFromString(elEditDescriptor.value, "derivable");
const descriptor = Descriptor.fromString(elEditDescriptor.value, "derivable");
setHtmlContent(elDescriptorAst, getHtmlForAst(descriptor.node()));
const descriptorAtIndex = descriptor.atDerivationIndex(options.derivationIndex);
return applyUpdateWith(
Expand All @@ -189,7 +182,7 @@ function applyUpdate(changedEl: HTMLElement, options: Options) {
changedEl === getElement("input-script-context", HTMLSelectElement)
) {
try {
const script = miniscriptFromString(elEditMiniscript.value, options.scriptContext);
const script = Miniscript.fromString(elEditMiniscript.value, options.scriptContext);
return applyUpdateWith(
changedEl,
{ descriptor: null, miniscript: script, scriptBytes: undefined, scriptAsm: undefined },
Expand Down
11 changes: 3 additions & 8 deletions packages/wasm-miniscript-ui/src/sharing.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import * as t from "io-ts";
import {
Descriptor,
descriptorFromString,
Miniscript,
miniscriptFromString,
} from "@bitgo/wasm-miniscript";
import { Descriptor, Miniscript } from "@bitgo/wasm-miniscript";
import { fromHex, toHex } from "./hex";
import { ScriptContext } from "./codec";

Expand Down Expand Up @@ -67,11 +62,11 @@ export function getShare(
}

if ("d" in v) {
return { descriptor: descriptorFromString(v.d, "derivable") };
return { descriptor: Descriptor.fromString(v.d, "derivable") };
}
if ("ms" in v && "sc" in v) {
return {
miniscript: miniscriptFromString(v.ms, v.sc),
miniscript: Miniscript.fromString(v.ms, v.sc),
scriptContext: v.sc,
};
}
Expand Down
56 changes: 17 additions & 39 deletions packages/wasm-miniscript/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,28 @@ import * as wasm from "./wasm/wasm_miniscript";
// and forgets to include it in the bundle
void wasm;

export type MiniscriptNode = unknown;

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

export function isMiniscript(obj: unknown): obj is Miniscript {
return obj instanceof wasm.WrapMiniscript;
}
export type DescriptorPkType = "derivable" | "definite" | "string";

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

export function miniscriptFromString(script: string, scriptContext: ScriptContext): Miniscript {
return wasm.miniscript_from_string(script, scriptContext);
}

export function miniscriptFromBitcoinScript(
script: Uint8Array,
scriptContext: ScriptContext,
): Miniscript {
return wasm.miniscript_from_bitcoin_script(script, scriptContext);
}
declare module "./wasm/wasm_miniscript" {
interface WrapDescriptor {
node(): unknown;
}

export type DescriptorNode = unknown;
namespace WrapDescriptor {
function fromString(descriptor: string, pkType: DescriptorPkType): WrapDescriptor;
}

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

export function isDescriptor(obj: unknown): obj is Descriptor {
return obj instanceof wasm.WrapDescriptor;
namespace WrapMiniscript {
function fromString(miniscript: string, ctx: ScriptContext): WrapMiniscript;
function fromBitcoinScript(script: Uint8Array, ctx: ScriptContext): WrapMiniscript;
}
}

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

export function descriptorFromString(descriptor: string, pkType: DescriptorPkType): Descriptor {
return wasm.descriptor_from_string(descriptor, pkType);
}
export { WrapDescriptor as Descriptor } from "./wasm/wasm_miniscript";
export { WrapMiniscript as Miniscript } from "./wasm/wasm_miniscript";
52 changes: 24 additions & 28 deletions packages/wasm-miniscript/src/descriptor.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::str::FromStr;
use miniscript::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey};
use miniscript::bitcoin::ScriptBuf;
use crate::try_into_js_value::TryIntoJsValue;
use miniscript::bitcoin::secp256k1::Secp256k1;
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 crate::try_into_js_value::TryIntoJsValue;

enum WrapDescriptorEnum {
Derivable(Descriptor<DescriptorPublicKey>, KeyMap),
Expand Down Expand Up @@ -58,18 +58,14 @@ impl WrapDescriptor {
#[wasm_bindgen(js_name = scriptPubkey)]
pub fn script_pubkey(&self) -> Result<Vec<u8>, JsError> {
match &self.0 {
WrapDescriptorEnum::Definite(desc) => {
Ok(desc.script_pubkey().to_bytes())
}
WrapDescriptorEnum::Definite(desc) => Ok(desc.script_pubkey().to_bytes()),
_ => Err(JsError::new("Cannot derive from a non-definite descriptor")),
}
}

fn explicit_script(&self) -> Result<ScriptBuf, JsError> {
match &self.0 {
WrapDescriptorEnum::Definite(desc) => {
Ok(desc.explicit_script()?)
}
WrapDescriptorEnum::Definite(desc) => Ok(desc.explicit_script()?),
WrapDescriptorEnum::Derivable(_, _) => {
Err(JsError::new("Cannot encode a derivable descriptor"))
}
Expand All @@ -85,24 +81,24 @@ impl WrapDescriptor {
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, 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)))
#[wasm_bindgen(js_name = fromString, skip_typescript)]
pub fn 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")),
}
_ => Err(JsError::new("Invalid descriptor type")),
}
}
}
5 changes: 2 additions & 3 deletions packages/wasm-miniscript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ mod miniscript;
mod error;
mod descriptor;

pub use miniscript::miniscript_from_string;
pub use miniscript::miniscript_from_bitcoin_script;
pub use descriptor::descriptor_from_string;
pub use miniscript::WrapMiniscript;
pub use descriptor::WrapDescriptor;
72 changes: 36 additions & 36 deletions packages/wasm-miniscript/src/miniscript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,42 @@ impl WrapMiniscript {
pub fn to_asm_string(&self) -> Result<String, JsError> {
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> {
match context_type {
"tap" => Ok(WrapMiniscript::from(
Miniscript::<XOnlyPublicKey, Tap>::from_str(script).map_err(JsError::from)?,
)),
"segwitv0" => Ok(WrapMiniscript::from(
Miniscript::<PublicKey, Segwitv0>::from_str(script).map_err(JsError::from)?,
)),
"legacy" => Ok(WrapMiniscript::from(
Miniscript::<PublicKey, Legacy>::from_str(script).map_err(JsError::from)?,
)),
_ => Err(JsError::new("Invalid context type")),
}
}

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

impl From<Miniscript<XOnlyPublicKey, Tap>> for WrapMiniscript {
Expand All @@ -68,42 +104,6 @@ impl From<Miniscript<PublicKey, Legacy>> for WrapMiniscript {
}
}

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

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

#[test]
pub fn panic_xprv() {
use miniscript::bitcoin::secp256k1::Secp256k1;
Expand Down
14 changes: 7 additions & 7 deletions packages/wasm-miniscript/test/test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as assert from "assert";
import { descriptorFromString, miniscriptFromString, miniscriptFromBitcoinScript } from "../js";
import { Miniscript, Descriptor } from "../js";
import { fixtures } from "./descriptorFixtures";

describe("AST", function () {
it("should get ast", function () {
const pubkey = Buffer.alloc(32, 1).toString("hex");
const result = miniscriptFromString(`multi_a(1,${pubkey})`, "tap");
const result = Miniscript.fromString(`multi_a(1,${pubkey})`, "tap");
console.dir(result.node(), { depth: null });
console.dir(result.encode(), { depth: null });
console.dir(miniscriptFromBitcoinScript(result.encode(), "tap").toString());
console.dir(Miniscript.fromBitcoinScript(result.encode(), "tap").toString());
});
});

Expand All @@ -20,7 +20,7 @@ function removeChecksum(descriptor: string): string {
describe("Descriptor fixtures", function () {
fixtures.valid.forEach((fixture, i) => {
it("should parse fixture " + i, function () {
const descriptor = descriptorFromString(fixture.descriptor, "string");
const descriptor = Descriptor.fromString(fixture.descriptor, "string");
assert.doesNotThrow(() => descriptor.node());
let descriptorString = descriptor.toString();
if (fixture.checksumRequired === false) {
Expand All @@ -34,7 +34,7 @@ describe("Descriptor fixtures", function () {
assert.strictEqual(descriptorString, expected);

assert.doesNotThrow(() =>
descriptorFromString(fixture.descriptor, "derivable").atDerivationIndex(0),
Descriptor.fromString(fixture.descriptor, "derivable").atDerivationIndex(0),
);

const nonDerivable = [33, 34, 35, 41, 42, 43];
Expand All @@ -43,14 +43,14 @@ describe("Descriptor fixtures", function () {
console.log("Skipping encoding test for fixture", fixture.descriptor, i);
} else {
assert.doesNotThrow(() =>
descriptorFromString(fixture.descriptor, "derivable").atDerivationIndex(0).encode(),
Descriptor.fromString(fixture.descriptor, "derivable").atDerivationIndex(0).encode(),
);

let descriptorString = fixture.descriptor;
if (fixture.checksumRequired === false) {
descriptorString = removeChecksum(descriptorString);
}
const descriptor = descriptorFromString(descriptorString, "derivable");
const descriptor = Descriptor.fromString(descriptorString, "derivable");
assert.strictEqual(
Buffer.from(descriptor.atDerivationIndex(fixture.index ?? 0).scriptPubkey()).toString(
"hex",
Expand Down

0 comments on commit 9b792a7

Please sign in to comment.