Skip to content

Commit ceae5a6

Browse files
Merge pull request #54 from BitGo/BTC-1829.add-opdrop-fixture
feat: add OP_DROP fixture without enabling support
2 parents 3f87455 + 1f4f7b8 commit ceae5a6

File tree

7 files changed

+139
-1
lines changed

7 files changed

+139
-1
lines changed

packages/wasm-miniscript/js/ast/fromWasmNode.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ function fromUnknown(v: unknown): Node | Node[] {
7878
return wrap("v", value);
7979
case "ZeroNotEqual":
8080
return wrap("n", value);
81+
case "Drop":
82+
return wrap("r", value);
8183

8284
// Conjunctions
8385
case "AndV":

packages/wasm-miniscript/test/descriptorFixtures.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,12 @@ export const fixtures = {
351351
script: "51207e8c409f0ab01197f9676efc3a9505f1f09ed0f21693e46a3aa3b6b54d437aa2",
352352
checksumRequired: true,
353353
},
354+
{
355+
descriptor:
356+
"wsh(and_v(r:after(1),pkh(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204)))",
357+
script: "0020823bcb22035958d32afe8ec04357535a3e73da3ed9cd90a4251970f9995077a5",
358+
checksumRequired: false,
359+
},
354360
],
355361
invalid: [
356362
{
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as fs from "fs/promises";
2+
export async function getFixture(path: string, defaultValue: unknown): Promise<unknown> {
3+
try {
4+
return JSON.parse(await fs.readFile(path, "utf8"));
5+
} catch (e) {
6+
if (e.code === "ENOENT") {
7+
await fs.writeFile(path, JSON.stringify(defaultValue, null, 2));
8+
throw new Error(`Fixture not found at ${path}, created a new one`);
9+
}
10+
}
11+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"0200000000010100000000000000000000000000000000000000000000000000000000000000000000000000feffffff0100e1f505000000002200206e3041069586d8bd9aec6ab1ac95f17d612c45cc1a76a4791aedab1c28a2109e040047304402207e7faabc574e1d4b482c1e3415fe7f1a9eb1d6a6d19982b6c1e5f2f2f9bb51eb02205716944bae604e3a25d450a9a413133b246ffe0fb17038dab217167060294f6e01473044022052ae4cf5f4093b655a4a86c5233832dd1907c7496123e806630ef3d30c60f00e02205a44f2c8fffc49358021787b123a4ae1d0b97735f6cf4769809fb2d214c1b657016e020004b175522102ae7c3c0ebc315a33151a1985ebb1fdcae72b3b91c38e3193c40ebabfffe9c343210260ba2407f7c75d525db9f171e9b2f3cf5ba3f0d7fc6067b20d4b91585432f9742103eadd6e4300dac62f1d4cf1131a06c5e140911f04245c64934c27510e93dbe84353ae00040000"
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as assert from "assert";
2+
import * as utxolib from "@bitgo/utxo-lib";
3+
import { Descriptor } from "../js";
4+
import { finalizePsbt, updateInputWithDescriptor } from "./psbt.util";
5+
import { getFixture } from "./fixtures";
6+
7+
const rootWalletKeys = new utxolib.bitgo.RootWalletKeys(utxolib.testutil.getKeyTriple("wasm"));
8+
9+
function getDescriptorOpDropP2ms(locktime: number, keys: utxolib.BIP32Interface[]) {
10+
const xpubs = keys.map((key) => key.toBase58() + "/*");
11+
// the `r:` prefix is a custom BitGo modification of miniscript to allow OP_DROP
12+
return `wsh(and_v(r:after(${locktime}),multi(2,${xpubs.join(",")})))`;
13+
}
14+
15+
describe("CLV with OP_DROP", function () {
16+
// OP_DROP not enabled on main branch
17+
return;
18+
19+
const locktime = 1024;
20+
const descriptor = Descriptor.fromString(
21+
getDescriptorOpDropP2ms(locktime, rootWalletKeys.triple),
22+
"derivable",
23+
);
24+
it("has expected AST", () => {
25+
assert.deepStrictEqual(descriptor.node(), {
26+
Wsh: {
27+
Ms: {
28+
AndV: [
29+
{
30+
Drop: {
31+
After: {
32+
absLockTime: 1024,
33+
},
34+
},
35+
},
36+
{
37+
Multi: [
38+
2,
39+
{
40+
XPub: "xpub661MyMwAqRbcFNusVUbSN3nbanHMtJjLgZGrs1wxH6f77kKQd6Vq4HfkZQNPC1vSbN6RTiBWJJV6FwJtCfBon2SgaT2J3MSkydukstKjwbJ/*",
41+
},
42+
{
43+
XPub: "xpub661MyMwAqRbcFo3t7PUqvbgvAcEuuoeVib5aapsg52inrG6KGF5aNtR5ey1FNCt1zJpMQiNec5XpofQmLNRhHvQRbhkc8UsWwwMwsXW6ogU/*",
44+
},
45+
{
46+
XPub: "xpub661MyMwAqRbcGg7f22Kcg2gy1F4jBjWR3xQTECVeJPHmxvhg5gUAZC6EYFtnyi6aMDQir1kV8HzCqC2FzTowGgEZqRh7rinqUCDeNDdmYzH/*",
47+
},
48+
],
49+
},
50+
],
51+
},
52+
},
53+
});
54+
});
55+
56+
it("has expected asm", () => {
57+
assert.deepStrictEqual(descriptor.atDerivationIndex(0).toAsmString().split(" "), [
58+
"OP_PUSHBYTES_2",
59+
"0004",
60+
"OP_CLTV",
61+
"OP_DROP",
62+
"OP_PUSHNUM_2",
63+
"OP_PUSHBYTES_33",
64+
"02ae7c3c0ebc315a33151a1985ebb1fdcae72b3b91c38e3193c40ebabfffe9c343",
65+
"OP_PUSHBYTES_33",
66+
"0260ba2407f7c75d525db9f171e9b2f3cf5ba3f0d7fc6067b20d4b91585432f974",
67+
"OP_PUSHBYTES_33",
68+
"03eadd6e4300dac62f1d4cf1131a06c5e140911f04245c64934c27510e93dbe843",
69+
"OP_PUSHNUM_3",
70+
"OP_CHECKMULTISIG",
71+
]);
72+
});
73+
74+
it("can be signed", async function () {
75+
const psbt = Object.assign(new utxolib.Psbt({ network: utxolib.networks.bitcoin }), {
76+
locktime,
77+
});
78+
const signers = rootWalletKeys.triple.slice(0, 2);
79+
const descriptorAt0 = descriptor.atDerivationIndex(0);
80+
const script = Buffer.from(descriptorAt0.scriptPubkey());
81+
psbt.addInput({
82+
hash: Buffer.alloc(32),
83+
index: 0,
84+
sequence: 0xfffffffe,
85+
witnessUtxo: { script, value: BigInt(1e8) },
86+
});
87+
psbt.addOutput({ script, value: BigInt(1e8) });
88+
updateInputWithDescriptor(psbt, 0, descriptorAt0);
89+
for (const signer of signers) {
90+
psbt.signAllInputsHD(signer);
91+
}
92+
finalizePsbt(psbt);
93+
const signedTx = psbt.extractTransaction().toBuffer();
94+
assert.strictEqual(
95+
signedTx.toString("hex"),
96+
await getFixture("test/fixtures/opdrop.json", signedTx.toString("hex")),
97+
);
98+
});
99+
});

packages/wasm-miniscript/test/test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,33 @@ function assertKnownDescriptorType(descriptor: Descriptor) {
5050
}
5151
}
5252

53+
function assertIsErrorUnknownWrapper(error: unknown, wrapper: string) {
54+
assert.ok(error instanceof Error);
55+
assert.ok(error.message.includes(`Error: unknown wrapper «${wrapper}»`));
56+
}
57+
5358
describe("Descriptor fixtures", function () {
5459
fixtures.valid.forEach((fixture, i) => {
5560
describe("fixture " + i, function () {
61+
const isOpDropFixture = i === 59;
5662
let descriptor: Descriptor;
5763

5864
before("setup descriptor", function () {
59-
descriptor = Descriptor.fromString(fixture.descriptor, "derivable");
65+
try {
66+
descriptor = Descriptor.fromString(fixture.descriptor, "derivable");
67+
} catch (e) {
68+
if (isOpDropFixture) {
69+
assertIsErrorUnknownWrapper(e, "r:");
70+
return;
71+
}
72+
throw e;
73+
}
6074
});
6175

76+
if (isOpDropFixture) {
77+
return;
78+
}
79+
6280
it("should round-trip (pkType string)", function () {
6381
let descriptorString = Descriptor.fromString(fixture.descriptor, "string").toString();
6482
if (fixture.checksumRequired === false) {

0 commit comments

Comments
 (0)