Skip to content

Commit 2a1417f

Browse files
feat: add fixedScriptToDescriptor with tests
This is useful in testing Issue: BTC-1348
1 parent 5f95fef commit 2a1417f

File tree

3 files changed

+111
-0
lines changed

3 files changed

+111
-0
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/wasm-miniscript/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"check-fmt": "prettier --check ."
4444
},
4545
"devDependencies": {
46+
"@bitgo/utxo-lib": "^10.1.0",
4647
"@types/mocha": "^10.0.7",
4748
"@types/node": "^20.14.10",
4849
"mocha": "^10.6.0",
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import * as assert from "assert";
2+
import * as utxolib from "@bitgo/utxo-lib";
3+
import { Descriptor } from "../js";
4+
5+
/** Expand a template with the given root wallet keys and chain code */
6+
function expand(template: string, rootWalletKeys: utxolib.bitgo.RootWalletKeys, chainCode: number) {
7+
return template.replace(/\$([0-9])/g, (_, i) => {
8+
const keyIndex = parseInt(i, 10);
9+
if (keyIndex !== 0 && keyIndex !== 1 && keyIndex !== 2) {
10+
throw new Error("Invalid key index");
11+
}
12+
const xpub = rootWalletKeys.triple[keyIndex].neutered().toBase58();
13+
const prefix = rootWalletKeys.derivationPrefixes[keyIndex];
14+
return xpub + "/" + prefix + "/" + chainCode + "/*";
15+
});
16+
}
17+
18+
/**
19+
* Get a standard output descriptor that corresponds to the proprietary HD wallet setup
20+
* used in BitGo wallets.
21+
* Only supports a subset of script types.
22+
*/
23+
function getDescriptorForScriptType(
24+
rootWalletKeys: utxolib.bitgo.RootWalletKeys,
25+
scriptType: utxolib.bitgo.outputScripts.ScriptType2Of3,
26+
scope: "internal" | "external",
27+
): string {
28+
const chain =
29+
scope === "external"
30+
? utxolib.bitgo.getExternalChainCode(scriptType)
31+
: utxolib.bitgo.getInternalChainCode(scriptType);
32+
switch (scriptType) {
33+
case "p2sh":
34+
return expand("sh(multi(2,$0,$1,$2))", rootWalletKeys, chain);
35+
case "p2shP2wsh":
36+
return expand("sh(wsh(multi(2,$0,$1,$2)))", rootWalletKeys, chain);
37+
case "p2wsh":
38+
return expand("wsh(multi(2,$0,$1,$2))", rootWalletKeys, chain);
39+
default:
40+
throw new Error(`Unsupported script type ${scriptType}`);
41+
}
42+
}
43+
44+
const rootWalletKeys = new utxolib.bitgo.RootWalletKeys(utxolib.testutil.getKeyTriple("wasm"));
45+
const scriptTypes = ["p2sh", "p2shP2wsh", "p2wsh"] as const;
46+
const scope = ["external", "internal"] as const;
47+
const index = [0, 1, 2];
48+
49+
function runTest(
50+
scriptType: utxolib.bitgo.outputScripts.ScriptType2Of3,
51+
index: number,
52+
scope: "internal" | "external",
53+
) {
54+
describe(`scriptType=${scriptType}, index=${index}, scope=${scope}`, function () {
55+
const chainCode =
56+
scope === "external"
57+
? utxolib.bitgo.getExternalChainCode(scriptType)
58+
: utxolib.bitgo.getInternalChainCode(scriptType);
59+
const derivedKeys = rootWalletKeys.deriveForChainAndIndex(chainCode, index);
60+
const scriptUtxolib = utxolib.bitgo.outputScripts.createOutputScript2of3(
61+
derivedKeys.publicKeys,
62+
scriptType,
63+
).scriptPubKey;
64+
65+
it("descriptor should have expected format", function () {
66+
const descriptor = Descriptor.fromString(
67+
getDescriptorForScriptType(rootWalletKeys, scriptType, scope),
68+
"derivable",
69+
);
70+
const [x1, x2, x3] = rootWalletKeys.triple.map((xpub) => xpub.neutered().toBase58());
71+
if (scriptType === "p2sh" && scope === "external") {
72+
// spot check
73+
assert.ok(
74+
descriptor
75+
.toString()
76+
.startsWith(`sh(multi(2,${x1}/0/0/0/*,${x2}/0/0/0/*,${x3}/0/0/0/*))`),
77+
);
78+
}
79+
if (scriptType === "p2shP2wsh" && scope === "internal") {
80+
// spot check
81+
assert.ok(
82+
descriptor
83+
.toString()
84+
.startsWith(`sh(wsh(multi(2,${x1}/0/0/11/*,${x2}/0/0/11/*,${x3}/0/0/11/*)))`),
85+
);
86+
}
87+
});
88+
89+
it("address should match descriptor", function () {
90+
const scriptFromDescriptor = Buffer.from(
91+
Descriptor.fromString(
92+
getDescriptorForScriptType(rootWalletKeys, scriptType, scope),
93+
"derivable",
94+
)
95+
.atDerivationIndex(index)
96+
.scriptPubkey(),
97+
);
98+
assert.deepStrictEqual(scriptUtxolib.toString("hex"), scriptFromDescriptor.toString("hex"));
99+
});
100+
});
101+
}
102+
103+
scriptTypes.forEach((scriptType) => {
104+
index.forEach((index) => {
105+
scope.forEach((scope) => {
106+
runTest(scriptType, index, scope);
107+
});
108+
});
109+
});

0 commit comments

Comments
 (0)