Skip to content

Commit

Permalink
Merge pull request #36 from BitGo/BTC-1348.add-fixedScriptToDescriptors
Browse files Browse the repository at this point in the history
feat: add fixedScriptToDescriptor with tests
  • Loading branch information
OttoAllmendinger authored Aug 21, 2024
2 parents 5f95fef + 2a1417f commit 616ad89
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 0 deletions.
1 change: 1 addition & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/wasm-miniscript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"check-fmt": "prettier --check ."
},
"devDependencies": {
"@bitgo/utxo-lib": "^10.1.0",
"@types/mocha": "^10.0.7",
"@types/node": "^20.14.10",
"mocha": "^10.6.0",
Expand Down
109 changes: 109 additions & 0 deletions packages/wasm-miniscript/test/fixedScriptToDescriptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import * as assert from "assert";
import * as utxolib from "@bitgo/utxo-lib";
import { Descriptor } from "../js";

/** Expand a template with the given root wallet keys and chain code */
function expand(template: string, rootWalletKeys: utxolib.bitgo.RootWalletKeys, chainCode: number) {
return template.replace(/\$([0-9])/g, (_, i) => {
const keyIndex = parseInt(i, 10);
if (keyIndex !== 0 && keyIndex !== 1 && keyIndex !== 2) {
throw new Error("Invalid key index");
}
const xpub = rootWalletKeys.triple[keyIndex].neutered().toBase58();
const prefix = rootWalletKeys.derivationPrefixes[keyIndex];
return xpub + "/" + prefix + "/" + chainCode + "/*";
});
}

/**
* Get a standard output descriptor that corresponds to the proprietary HD wallet setup
* used in BitGo wallets.
* Only supports a subset of script types.
*/
function getDescriptorForScriptType(
rootWalletKeys: utxolib.bitgo.RootWalletKeys,
scriptType: utxolib.bitgo.outputScripts.ScriptType2Of3,
scope: "internal" | "external",
): string {
const chain =
scope === "external"
? utxolib.bitgo.getExternalChainCode(scriptType)
: utxolib.bitgo.getInternalChainCode(scriptType);
switch (scriptType) {
case "p2sh":
return expand("sh(multi(2,$0,$1,$2))", rootWalletKeys, chain);
case "p2shP2wsh":
return expand("sh(wsh(multi(2,$0,$1,$2)))", rootWalletKeys, chain);
case "p2wsh":
return expand("wsh(multi(2,$0,$1,$2))", rootWalletKeys, chain);
default:
throw new Error(`Unsupported script type ${scriptType}`);
}
}

const rootWalletKeys = new utxolib.bitgo.RootWalletKeys(utxolib.testutil.getKeyTriple("wasm"));
const scriptTypes = ["p2sh", "p2shP2wsh", "p2wsh"] as const;
const scope = ["external", "internal"] as const;
const index = [0, 1, 2];

function runTest(
scriptType: utxolib.bitgo.outputScripts.ScriptType2Of3,
index: number,
scope: "internal" | "external",
) {
describe(`scriptType=${scriptType}, index=${index}, scope=${scope}`, function () {
const chainCode =
scope === "external"
? utxolib.bitgo.getExternalChainCode(scriptType)
: utxolib.bitgo.getInternalChainCode(scriptType);
const derivedKeys = rootWalletKeys.deriveForChainAndIndex(chainCode, index);
const scriptUtxolib = utxolib.bitgo.outputScripts.createOutputScript2of3(
derivedKeys.publicKeys,
scriptType,
).scriptPubKey;

it("descriptor should have expected format", function () {
const descriptor = Descriptor.fromString(
getDescriptorForScriptType(rootWalletKeys, scriptType, scope),
"derivable",
);
const [x1, x2, x3] = rootWalletKeys.triple.map((xpub) => xpub.neutered().toBase58());
if (scriptType === "p2sh" && scope === "external") {
// spot check
assert.ok(
descriptor
.toString()
.startsWith(`sh(multi(2,${x1}/0/0/0/*,${x2}/0/0/0/*,${x3}/0/0/0/*))`),
);
}
if (scriptType === "p2shP2wsh" && scope === "internal") {
// spot check
assert.ok(
descriptor
.toString()
.startsWith(`sh(wsh(multi(2,${x1}/0/0/11/*,${x2}/0/0/11/*,${x3}/0/0/11/*)))`),
);
}
});

it("address should match descriptor", function () {
const scriptFromDescriptor = Buffer.from(
Descriptor.fromString(
getDescriptorForScriptType(rootWalletKeys, scriptType, scope),
"derivable",
)
.atDerivationIndex(index)
.scriptPubkey(),
);
assert.deepStrictEqual(scriptUtxolib.toString("hex"), scriptFromDescriptor.toString("hex"));
});
});
}

scriptTypes.forEach((scriptType) => {
index.forEach((index) => {
scope.forEach((scope) => {
runTest(scriptType, index, scope);
});
});
});

0 comments on commit 616ad89

Please sign in to comment.