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
2 changes: 2 additions & 0 deletions packages/wasm-miniscript/js/ast/fromWasmNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ function fromUnknown(v: unknown): Node | Node[] {
return wrap("v", value);
case "ZeroNotEqual":
return wrap("n", value);
case "Drop":
return wrap("r", value);

// Conjunctions
case "AndV":
Expand Down
6 changes: 6 additions & 0 deletions packages/wasm-miniscript/test/descriptorFixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,12 @@ export const fixtures = {
script: "51207e8c409f0ab01197f9676efc3a9505f1f09ed0f21693e46a3aa3b6b54d437aa2",
checksumRequired: true,
},
{
descriptor:
"wsh(and_v(r:after(1),pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))",
script: "0020823bcb22035958d32afe8ec04357535a3e73da3ed9cd90a4251970f9995077a5",
checksumRequired: false,
},
],
invalid: [
{
Expand Down
11 changes: 11 additions & 0 deletions packages/wasm-miniscript/test/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as fs from "fs/promises";
export async function getFixture(path: string, defaultValue: unknown): Promise<unknown> {
try {
return JSON.parse(await fs.readFile(path, "utf8"));
} catch (e) {
if (e.code === "ENOENT") {
await fs.writeFile(path, JSON.stringify(defaultValue, null, 2));
throw new Error(`Fixture not found at ${path}, created a new one`);
}
}
}
1 change: 1 addition & 0 deletions packages/wasm-miniscript/test/fixtures/59.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions packages/wasm-miniscript/test/fixtures/opdrop.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"0200000000010100000000000000000000000000000000000000000000000000000000000000000000000000feffffff0100e1f505000000002200206e3041069586d8bd9aec6ab1ac95f17d612c45cc1a76a4791aedab1c28a2109e040047304402207e7faabc574e1d4b482c1e3415fe7f1a9eb1d6a6d19982b6c1e5f2f2f9bb51eb02205716944bae604e3a25d450a9a413133b246ffe0fb17038dab217167060294f6e01473044022052ae4cf5f4093b655a4a86c5233832dd1907c7496123e806630ef3d30c60f00e02205a44f2c8fffc49358021787b123a4ae1d0b97735f6cf4769809fb2d214c1b657016e020004b175522102ae7c3c0ebc315a33151a1985ebb1fdcae72b3b91c38e3193c40ebabfffe9c343210260ba2407f7c75d525db9f171e9b2f3cf5ba3f0d7fc6067b20d4b91585432f9742103eadd6e4300dac62f1d4cf1131a06c5e140911f04245c64934c27510e93dbe84353ae00040000"
99 changes: 99 additions & 0 deletions packages/wasm-miniscript/test/opdrop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import * as assert from "assert";
import * as utxolib from "@bitgo/utxo-lib";
import { Descriptor } from "../js";
import { finalizePsbt, updateInputWithDescriptor } from "./psbt.util";
import { getFixture } from "./fixtures";

const rootWalletKeys = new utxolib.bitgo.RootWalletKeys(utxolib.testutil.getKeyTriple("wasm"));

function getDescriptorOpDropP2ms(locktime: number, keys: utxolib.BIP32Interface[]) {
const xpubs = keys.map((key) => key.toBase58() + "/*");
// the `r:` prefix is a custom BitGo modification of miniscript to allow OP_DROP
return `wsh(and_v(r:after(${locktime}),multi(2,${xpubs.join(",")})))`;
}

describe("CLV with OP_DROP", function () {
// OP_DROP not enabled on main branch
return;

const locktime = 1024;
const descriptor = Descriptor.fromString(
getDescriptorOpDropP2ms(locktime, rootWalletKeys.triple),
"derivable",
);
it("has expected AST", () => {
assert.deepStrictEqual(descriptor.node(), {
Wsh: {
Ms: {
AndV: [
{
Drop: {
After: {
absLockTime: 1024,
},
},
},
{
Multi: [
2,
{
XPub: "xpub661MyMwAqRbcFNusVUbSN3nbanHMtJjLgZGrs1wxH6f77kKQd6Vq4HfkZQNPC1vSbN6RTiBWJJV6FwJtCfBon2SgaT2J3MSkydukstKjwbJ/*",
},
{
XPub: "xpub661MyMwAqRbcFo3t7PUqvbgvAcEuuoeVib5aapsg52inrG6KGF5aNtR5ey1FNCt1zJpMQiNec5XpofQmLNRhHvQRbhkc8UsWwwMwsXW6ogU/*",
},
{
XPub: "xpub661MyMwAqRbcGg7f22Kcg2gy1F4jBjWR3xQTECVeJPHmxvhg5gUAZC6EYFtnyi6aMDQir1kV8HzCqC2FzTowGgEZqRh7rinqUCDeNDdmYzH/*",
},
],
},
],
},
},
});
});

it("has expected asm", () => {
assert.deepStrictEqual(descriptor.atDerivationIndex(0).toAsmString().split(" "), [
"OP_PUSHBYTES_2",
"0004",
"OP_CLTV",
"OP_DROP",
"OP_PUSHNUM_2",
"OP_PUSHBYTES_33",
"02ae7c3c0ebc315a33151a1985ebb1fdcae72b3b91c38e3193c40ebabfffe9c343",
"OP_PUSHBYTES_33",
"0260ba2407f7c75d525db9f171e9b2f3cf5ba3f0d7fc6067b20d4b91585432f974",
"OP_PUSHBYTES_33",
"03eadd6e4300dac62f1d4cf1131a06c5e140911f04245c64934c27510e93dbe843",
"OP_PUSHNUM_3",
"OP_CHECKMULTISIG",
]);
});

it("can be signed", async function () {
const psbt = Object.assign(new utxolib.Psbt({ network: utxolib.networks.bitcoin }), {
locktime,
});
const signers = rootWalletKeys.triple.slice(0, 2);
const descriptorAt0 = descriptor.atDerivationIndex(0);
const script = Buffer.from(descriptorAt0.scriptPubkey());
psbt.addInput({
hash: Buffer.alloc(32),
index: 0,
sequence: 0xfffffffe,
witnessUtxo: { script, value: BigInt(1e8) },
});
psbt.addOutput({ script, value: BigInt(1e8) });
updateInputWithDescriptor(psbt, 0, descriptorAt0);
for (const signer of signers) {
psbt.signAllInputsHD(signer);
}
finalizePsbt(psbt);
const signedTx = psbt.extractTransaction().toBuffer();
assert.strictEqual(
signedTx.toString("hex"),
await getFixture("test/fixtures/opdrop.json", signedTx.toString("hex")),
);
});
});
20 changes: 19 additions & 1 deletion packages/wasm-miniscript/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,33 @@ function assertKnownDescriptorType(descriptor: Descriptor) {
}
}

function assertIsErrorUnknownWrapper(error: unknown, wrapper: string) {
assert.ok(error instanceof Error);
assert.ok(error.message.includes(`Error: unknown wrapper «${wrapper}»`));
}

describe("Descriptor fixtures", function () {
fixtures.valid.forEach((fixture, i) => {
describe("fixture " + i, function () {
const isOpDropFixture = i === 59;
let descriptor: Descriptor;

before("setup descriptor", function () {
descriptor = Descriptor.fromString(fixture.descriptor, "derivable");
try {
descriptor = Descriptor.fromString(fixture.descriptor, "derivable");
} catch (e) {
if (isOpDropFixture) {
assertIsErrorUnknownWrapper(e, "r:");
return;
}
throw e;
}
});

if (isOpDropFixture) {
return;
}

it("should round-trip (pkType string)", function () {
let descriptorString = Descriptor.fromString(fixture.descriptor, "string").toString();
if (fixture.checksumRequired === false) {
Expand Down