@bitgo/wasm-utxo 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/js/fixedScriptWallet/BitGoPsbt.d.ts +99 -0
- package/dist/cjs/js/fixedScriptWallet/BitGoPsbt.js +122 -0
- package/dist/cjs/js/wasm/wasm_utxo.d.ts +86 -0
- package/dist/cjs/js/wasm/wasm_utxo.js +276 -24
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm.d.ts +21 -17
- package/dist/esm/js/fixedScriptWallet/BitGoPsbt.d.ts +99 -0
- package/dist/esm/js/fixedScriptWallet/BitGoPsbt.js +122 -0
- package/dist/esm/js/wasm/wasm_utxo.d.ts +86 -0
- package/dist/esm/js/wasm/wasm_utxo_bg.js +276 -24
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm.d.ts +21 -17
- package/package.json +3 -3
- package/dist/cjs/package.json +0 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +0 -1
- package/dist/esm/test/address/utxolibCompat.d.ts +0 -1
- package/dist/esm/test/address/utxolibCompat.js +0 -107
- package/dist/esm/test/ast/formatNode.d.ts +0 -1
- package/dist/esm/test/ast/formatNode.js +0 -15
- package/dist/esm/test/bip32.d.ts +0 -1
- package/dist/esm/test/bip32.js +0 -242
- package/dist/esm/test/descriptorFixtures.d.ts +0 -25
- package/dist/esm/test/descriptorFixtures.js +0 -605
- package/dist/esm/test/descriptorUtil.d.ts +0 -13
- package/dist/esm/test/descriptorUtil.js +0 -52
- package/dist/esm/test/ecpair.d.ts +0 -1
- package/dist/esm/test/ecpair.js +0 -137
- package/dist/esm/test/fixedScript/address.d.ts +0 -1
- package/dist/esm/test/fixedScript/address.js +0 -66
- package/dist/esm/test/fixedScript/finalizeExtract.d.ts +0 -1
- package/dist/esm/test/fixedScript/finalizeExtract.js +0 -66
- package/dist/esm/test/fixedScript/fixtureUtil.d.ts +0 -95
- package/dist/esm/test/fixedScript/fixtureUtil.js +0 -55
- package/dist/esm/test/fixedScript/parseTransactionWithWalletKeys.d.ts +0 -1
- package/dist/esm/test/fixedScript/parseTransactionWithWalletKeys.js +0 -168
- package/dist/esm/test/fixedScript/verifySignature.d.ts +0 -1
- package/dist/esm/test/fixedScript/verifySignature.js +0 -187
- package/dist/esm/test/fixedScriptToDescriptor.d.ts +0 -1
- package/dist/esm/test/fixedScriptToDescriptor.js +0 -93
- package/dist/esm/test/fixtures.d.ts +0 -1
- package/dist/esm/test/fixtures.js +0 -16
- package/dist/esm/test/opdrop.d.ts +0 -1
- package/dist/esm/test/opdrop.js +0 -85
- package/dist/esm/test/psbt.util.d.ts +0 -8
- package/dist/esm/test/psbt.util.js +0 -113
- package/dist/esm/test/psbtFixedScriptCompat.d.ts +0 -1
- package/dist/esm/test/psbtFixedScriptCompat.js +0 -116
- package/dist/esm/test/psbtFixedScriptCompatFixtures.d.ts +0 -10
- package/dist/esm/test/psbtFixedScriptCompatFixtures.js +0 -53
- package/dist/esm/test/psbtFromDescriptor.d.ts +0 -1
- package/dist/esm/test/psbtFromDescriptor.js +0 -104
- package/dist/esm/test/psbtFromDescriptor.util.d.ts +0 -63
- package/dist/esm/test/psbtFromDescriptor.util.js +0 -101
- package/dist/esm/test/test.d.ts +0 -1
- package/dist/esm/test/test.js +0 -123
- package/dist/esm/tsconfig.tsbuildinfo +0 -1
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
|
-
import { fixedScriptWallet, BIP32 } from "../../js/index.js";
|
|
4
|
-
import { loadPsbtFixture, loadWalletKeysFromFixture, getPsbtBuffer, loadReplayProtectionKeyFromFixture, } from "./fixtureUtil.js";
|
|
5
|
-
/**
|
|
6
|
-
* Get expected signature state for an input based on type and signing stage
|
|
7
|
-
* @param inputType - The type of input (e.g., "p2shP2pk", "p2trMusig2")
|
|
8
|
-
* @param signatureStage - The signing stage (unsigned, halfsigned, fullsigned)
|
|
9
|
-
* @returns Expected signature state for replay protection OR multi-key signatures
|
|
10
|
-
*/
|
|
11
|
-
function getExpectedSignatures(inputType, signatureStage) {
|
|
12
|
-
// p2shP2pk inputs use replay protection signature verification
|
|
13
|
-
if (inputType === "p2shP2pk") {
|
|
14
|
-
return {
|
|
15
|
-
hasReplayProtectionSignature: signatureStage === "halfsigned" || signatureStage === "fullsigned",
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
switch (signatureStage) {
|
|
19
|
-
case "unsigned":
|
|
20
|
-
return { user: false, backup: false, bitgo: false };
|
|
21
|
-
case "halfsigned":
|
|
22
|
-
// User signs first
|
|
23
|
-
return { user: true, backup: false, bitgo: false };
|
|
24
|
-
case "fullsigned":
|
|
25
|
-
// p2trMusig2 uses user + backup for 2-of-2 MuSig2
|
|
26
|
-
if (inputType === "p2trMusig2") {
|
|
27
|
-
return { user: true, backup: true, bitgo: false };
|
|
28
|
-
}
|
|
29
|
-
// Regular multisig uses user + bitgo
|
|
30
|
-
return { user: true, backup: false, bitgo: true };
|
|
31
|
-
default:
|
|
32
|
-
throw new Error(`Unknown signature stage: ${String(signatureStage)}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Verify signature state for a specific input in a PSBT
|
|
37
|
-
* @param bitgoPsbt - The PSBT to verify
|
|
38
|
-
* @param rootWalletKeys - Wallet keys for verification
|
|
39
|
-
* @param inputIndex - The input index to verify
|
|
40
|
-
* @param inputType - The type of input (for replay protection handling)
|
|
41
|
-
* @param expectedSignatures - Expected signature state for each key or replay protection
|
|
42
|
-
*/
|
|
43
|
-
function verifyInputSignatures(bitgoPsbt, parsed, rootWalletKeys, replayProtectionKey, inputIndex, expectedSignatures) {
|
|
44
|
-
// Handle replay protection inputs (P2shP2pk)
|
|
45
|
-
if ("hasReplayProtectionSignature" in expectedSignatures) {
|
|
46
|
-
const hasReplaySig = bitgoPsbt.verifyReplayProtectionSignature(inputIndex, {
|
|
47
|
-
publicKeys: [replayProtectionKey],
|
|
48
|
-
});
|
|
49
|
-
assert.strictEqual(hasReplaySig, expectedSignatures.hasReplayProtectionSignature, `Input ${inputIndex} replay protection signature mismatch`);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (parsed.inputs[inputIndex].scriptType === "p2shP2pk") {
|
|
53
|
-
const hasReplaySig = bitgoPsbt.verifySignature(inputIndex, replayProtectionKey);
|
|
54
|
-
assert.ok("hasReplayProtectionSignature" in expectedSignatures, "Expected hasReplayProtectionSignature to be present");
|
|
55
|
-
assert.strictEqual(hasReplaySig, expectedSignatures.hasReplayProtectionSignature, `Input ${inputIndex} replay protection signature mismatch`);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
// Handle standard multisig inputs
|
|
59
|
-
const hasUserSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.userKey());
|
|
60
|
-
const hasBackupSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.backupKey());
|
|
61
|
-
const hasBitGoSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.bitgoKey());
|
|
62
|
-
assert.strictEqual(hasUserSig, expectedSignatures.user, `Input ${inputIndex} user key signature mismatch`);
|
|
63
|
-
assert.strictEqual(hasBackupSig, expectedSignatures.backup, `Input ${inputIndex} backup key signature mismatch`);
|
|
64
|
-
assert.strictEqual(hasBitGoSig, expectedSignatures.bitgo, `Input ${inputIndex} BitGo key signature mismatch`);
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Helper to verify signatures for all inputs in a PSBT
|
|
68
|
-
* @param bitgoPsbt - The PSBT to verify
|
|
69
|
-
* @param fixture - The test fixture containing input metadata
|
|
70
|
-
* @param rootWalletKeys - Wallet keys for verification
|
|
71
|
-
* @param replayProtectionKey - Key for replay protection inputs
|
|
72
|
-
* @param signatureStage - The signing stage (unsigned, halfsigned, fullsigned)
|
|
73
|
-
*/
|
|
74
|
-
function verifyAllInputSignatures(bitgoPsbt, fixture, rootWalletKeys, replayProtectionKey, signatureStage) {
|
|
75
|
-
const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
|
|
76
|
-
publicKeys: [replayProtectionKey],
|
|
77
|
-
});
|
|
78
|
-
fixture.psbtInputs.forEach((input, index) => {
|
|
79
|
-
verifyInputSignatures(bitgoPsbt, parsed, rootWalletKeys, replayProtectionKey, index, getExpectedSignatures(input.type, signatureStage));
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
describe("verifySignature", function () {
|
|
83
|
-
const supportedNetworks = utxolib.getNetworkList().filter((network) => {
|
|
84
|
-
return (utxolib.isMainnet(network) &&
|
|
85
|
-
network !== utxolib.networks.bitcoincash &&
|
|
86
|
-
network !== utxolib.networks.bitcoingold &&
|
|
87
|
-
network !== utxolib.networks.bitcoinsv &&
|
|
88
|
-
network !== utxolib.networks.ecash &&
|
|
89
|
-
network !== utxolib.networks.zcash);
|
|
90
|
-
});
|
|
91
|
-
supportedNetworks.forEach((network) => {
|
|
92
|
-
const networkName = utxolib.getNetworkName(network);
|
|
93
|
-
describe(`network: ${networkName}`, function () {
|
|
94
|
-
let rootWalletKeys;
|
|
95
|
-
let replayProtectionKey;
|
|
96
|
-
let unsignedFixture;
|
|
97
|
-
let halfsignedFixture;
|
|
98
|
-
let fullsignedFixture;
|
|
99
|
-
let unsignedBitgoPsbt;
|
|
100
|
-
let halfsignedBitgoPsbt;
|
|
101
|
-
let fullsignedBitgoPsbt;
|
|
102
|
-
before(function () {
|
|
103
|
-
unsignedFixture = loadPsbtFixture(networkName, "unsigned");
|
|
104
|
-
halfsignedFixture = loadPsbtFixture(networkName, "halfsigned");
|
|
105
|
-
fullsignedFixture = loadPsbtFixture(networkName, "fullsigned");
|
|
106
|
-
rootWalletKeys = loadWalletKeysFromFixture(fullsignedFixture);
|
|
107
|
-
replayProtectionKey = loadReplayProtectionKeyFromFixture(fullsignedFixture);
|
|
108
|
-
unsignedBitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(getPsbtBuffer(unsignedFixture), networkName);
|
|
109
|
-
halfsignedBitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(getPsbtBuffer(halfsignedFixture), networkName);
|
|
110
|
-
fullsignedBitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(getPsbtBuffer(fullsignedFixture), networkName);
|
|
111
|
-
});
|
|
112
|
-
describe("unsigned PSBT", function () {
|
|
113
|
-
it("should return false for unsigned inputs", function () {
|
|
114
|
-
verifyAllInputSignatures(unsignedBitgoPsbt, unsignedFixture, rootWalletKeys, replayProtectionKey, "unsigned");
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
describe("half-signed PSBT", function () {
|
|
118
|
-
it("should return true for signed xpubs and false for unsigned", function () {
|
|
119
|
-
verifyAllInputSignatures(halfsignedBitgoPsbt, halfsignedFixture, rootWalletKeys, replayProtectionKey, "halfsigned");
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
describe("fully signed PSBT", function () {
|
|
123
|
-
it("should have 2 signatures (2-of-3 multisig)", function () {
|
|
124
|
-
verifyAllInputSignatures(fullsignedBitgoPsbt, fullsignedFixture, rootWalletKeys, replayProtectionKey, "fullsigned");
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
describe("error handling", function () {
|
|
128
|
-
it("should throw error for out of bounds input index", function () {
|
|
129
|
-
assert.throws(() => {
|
|
130
|
-
fullsignedBitgoPsbt.verifySignature(999, rootWalletKeys.userKey());
|
|
131
|
-
}, (error) => {
|
|
132
|
-
return error.message.includes("Input index 999 out of bounds");
|
|
133
|
-
}, "Should throw error for out of bounds input index");
|
|
134
|
-
});
|
|
135
|
-
it("should throw error for invalid xpub", function () {
|
|
136
|
-
assert.throws(() => {
|
|
137
|
-
fullsignedBitgoPsbt.verifySignature(0, "invalid-xpub");
|
|
138
|
-
}, (error) => {
|
|
139
|
-
return error.message.includes("Invalid");
|
|
140
|
-
}, "Should throw error for invalid xpub");
|
|
141
|
-
});
|
|
142
|
-
it("should return false for xpub not in derivation path", function () {
|
|
143
|
-
// Create a different xpub that's not in the wallet
|
|
144
|
-
// Use a proper 32-byte seed (256 bits)
|
|
145
|
-
const differentSeed = Buffer.alloc(32, 0xaa); // 32 bytes filled with 0xaa
|
|
146
|
-
const differentKey = BIP32.fromSeed(differentSeed);
|
|
147
|
-
const differentXpub = differentKey.neutered();
|
|
148
|
-
const result = fullsignedBitgoPsbt.verifySignature(0, differentXpub);
|
|
149
|
-
assert.strictEqual(result, false, "Should return false for xpub not in PSBT derivation paths");
|
|
150
|
-
});
|
|
151
|
-
it("should verify signature with raw public key (Uint8Array)", function () {
|
|
152
|
-
// Verify that xpub-based verification works
|
|
153
|
-
const userKey = rootWalletKeys.userKey();
|
|
154
|
-
const hasXpubSig = fullsignedBitgoPsbt.verifySignature(0, userKey);
|
|
155
|
-
// This test specifically checks that raw public key verification works
|
|
156
|
-
// We test the underlying WASM API by ensuring both xpub and raw pubkey
|
|
157
|
-
// calls reach the correct methods
|
|
158
|
-
// Use a random public key that's not in the PSBT to test the API works
|
|
159
|
-
const randomSeed = Buffer.alloc(32, 0xcc);
|
|
160
|
-
const randomKey = BIP32.fromSeed(randomSeed);
|
|
161
|
-
const randomPubkey = randomKey.publicKey;
|
|
162
|
-
// This should return false (no signature for this key)
|
|
163
|
-
const result = fullsignedBitgoPsbt.verifySignature(0, randomPubkey);
|
|
164
|
-
assert.strictEqual(result, false, "Should return false for public key not in PSBT");
|
|
165
|
-
// Verify the xpub check still works (regression test)
|
|
166
|
-
assert.strictEqual(hasXpubSig, true, "Should still verify with xpub");
|
|
167
|
-
});
|
|
168
|
-
it("should return false for raw public key with no signature", function () {
|
|
169
|
-
// Create a random public key that's not in the PSBT
|
|
170
|
-
const randomSeed = Buffer.alloc(32, 0xbb);
|
|
171
|
-
const randomKey = BIP32.fromSeed(randomSeed);
|
|
172
|
-
const randomPubkey = randomKey.publicKey;
|
|
173
|
-
const result = fullsignedBitgoPsbt.verifySignature(0, randomPubkey);
|
|
174
|
-
assert.strictEqual(result, false, "Should return false for public key not in PSBT signatures");
|
|
175
|
-
});
|
|
176
|
-
it("should throw error for invalid key length", function () {
|
|
177
|
-
const invalidKey = Buffer.alloc(31); // Invalid length (should be 32 for private key or 33 for public key)
|
|
178
|
-
assert.throws(() => {
|
|
179
|
-
fullsignedBitgoPsbt.verifySignature(0, invalidKey);
|
|
180
|
-
}, (error) => {
|
|
181
|
-
return error.message.includes("Invalid key length");
|
|
182
|
-
}, "Should throw error for invalid key length");
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import * as assert from "assert";
|
|
2
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
|
-
import { Descriptor } from "../js/index.js";
|
|
4
|
-
import { getDescriptorForScriptType } from "./descriptorUtil.js";
|
|
5
|
-
const rootWalletKeys = new utxolib.bitgo.RootWalletKeys(utxolib.testutil.getKeyTriple("wasm"));
|
|
6
|
-
const scriptTypes = ["p2sh", "p2shP2wsh", "p2wsh"];
|
|
7
|
-
const scope = ["external", "internal"];
|
|
8
|
-
const index = [0, 1, 2];
|
|
9
|
-
/** Get the expected max weight to satisfy the descriptor */
|
|
10
|
-
function getExpectedMaxWeightToSatisfy(scriptType) {
|
|
11
|
-
switch (scriptType) {
|
|
12
|
-
case "p2sh":
|
|
13
|
-
return 256;
|
|
14
|
-
case "p2shP2wsh":
|
|
15
|
-
return 99;
|
|
16
|
-
case "p2wsh":
|
|
17
|
-
return 64;
|
|
18
|
-
default:
|
|
19
|
-
throw new Error("unexpected script type");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
/** Compute the total size of the input, including overhead */
|
|
23
|
-
function getTotalInputSize(vSize) {
|
|
24
|
-
const sizeOpPushData1 = 1;
|
|
25
|
-
const sizeOpPushData2 = 2;
|
|
26
|
-
return (32 /* txid */ +
|
|
27
|
-
4 /* vout */ +
|
|
28
|
-
4 /* nSequence */ +
|
|
29
|
-
(vSize < 255 ? sizeOpPushData1 : sizeOpPushData2) +
|
|
30
|
-
vSize);
|
|
31
|
-
}
|
|
32
|
-
/** Get the full expected vSize of the input including overhead */
|
|
33
|
-
function getExpectedVSize(scriptType) {
|
|
34
|
-
// https://github.com/BitGo/BitGoJS/blob/master/modules/unspents/docs/input-costs.md
|
|
35
|
-
switch (scriptType) {
|
|
36
|
-
case "p2sh":
|
|
37
|
-
return 298;
|
|
38
|
-
case "p2shP2wsh":
|
|
39
|
-
return 140;
|
|
40
|
-
case "p2wsh":
|
|
41
|
-
return 105;
|
|
42
|
-
default:
|
|
43
|
-
throw new Error("unexpected script type");
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function runTest(scriptType, index, scope) {
|
|
47
|
-
describe(`scriptType=${scriptType}, index=${index}, scope=${scope}`, function () {
|
|
48
|
-
const chainCode = scope === "external"
|
|
49
|
-
? utxolib.bitgo.getExternalChainCode(scriptType)
|
|
50
|
-
: utxolib.bitgo.getInternalChainCode(scriptType);
|
|
51
|
-
const derivedKeys = rootWalletKeys.deriveForChainAndIndex(chainCode, index);
|
|
52
|
-
const scriptUtxolib = utxolib.bitgo.outputScripts.createOutputScript2of3(derivedKeys.publicKeys, scriptType).scriptPubKey;
|
|
53
|
-
let descriptor;
|
|
54
|
-
before(function () {
|
|
55
|
-
descriptor = Descriptor.fromString(getDescriptorForScriptType(rootWalletKeys, scriptType, scope), "derivable");
|
|
56
|
-
});
|
|
57
|
-
it("descriptor should have expected format", function () {
|
|
58
|
-
const [x1, x2, x3] = rootWalletKeys.triple.map((xpub) => xpub.neutered().toBase58());
|
|
59
|
-
if (scriptType === "p2sh" && scope === "external") {
|
|
60
|
-
// spot check
|
|
61
|
-
assert.ok(descriptor
|
|
62
|
-
.toString()
|
|
63
|
-
.startsWith(`sh(multi(2,${x1}/0/0/0/*,${x2}/0/0/0/*,${x3}/0/0/0/*))`));
|
|
64
|
-
}
|
|
65
|
-
if (scriptType === "p2shP2wsh" && scope === "internal") {
|
|
66
|
-
// spot check
|
|
67
|
-
assert.ok(descriptor
|
|
68
|
-
.toString()
|
|
69
|
-
.startsWith(`sh(wsh(multi(2,${x1}/0/0/11/*,${x2}/0/0/11/*,${x3}/0/0/11/*)))`));
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
it("address should match descriptor", function () {
|
|
73
|
-
const scriptFromDescriptor = Buffer.from(descriptor.atDerivationIndex(index).scriptPubkey());
|
|
74
|
-
assert.deepStrictEqual(scriptUtxolib.toString("hex"), scriptFromDescriptor.toString("hex"));
|
|
75
|
-
});
|
|
76
|
-
it("should have expected weights", function () {
|
|
77
|
-
assert.ok(Number.isInteger(descriptor.maxWeightToSatisfy()));
|
|
78
|
-
const vSize = Math.ceil(descriptor.maxWeightToSatisfy() / 4);
|
|
79
|
-
console.log(scriptType, "scriptLength", descriptor.atDerivationIndex(0).encode().length, "vSize", vSize);
|
|
80
|
-
assert.equal(vSize, getExpectedMaxWeightToSatisfy(scriptType));
|
|
81
|
-
assert.equal(getTotalInputSize(vSize), getExpectedVSize(scriptType));
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
describe("fixedScript to descriptor", function () {
|
|
86
|
-
scriptTypes.forEach((scriptType) => {
|
|
87
|
-
index.forEach((index) => {
|
|
88
|
-
scope.forEach((scope) => {
|
|
89
|
-
runTest(scriptType, index, scope);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function getFixture(path: string, defaultValue: unknown): Promise<unknown>;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises";
|
|
2
|
-
export async function getFixture(path, defaultValue) {
|
|
3
|
-
try {
|
|
4
|
-
return JSON.parse(await fs.readFile(path, "utf8"));
|
|
5
|
-
}
|
|
6
|
-
catch (e) {
|
|
7
|
-
if (typeof e === "object" &&
|
|
8
|
-
e !== null &&
|
|
9
|
-
"code" in e &&
|
|
10
|
-
e.code === "ENOENT") {
|
|
11
|
-
await fs.writeFile(path, JSON.stringify(defaultValue, null, 2));
|
|
12
|
-
throw new Error(`Fixture not found at ${path}, created a new one`);
|
|
13
|
-
}
|
|
14
|
-
throw e;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/esm/test/opdrop.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import * as assert from "assert";
|
|
2
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
|
-
import { Descriptor } from "../js/index.js";
|
|
4
|
-
import { finalizePsbt, updateInputWithDescriptor } from "./psbt.util.js";
|
|
5
|
-
import { getFixture } from "./fixtures.js";
|
|
6
|
-
const rootWalletKeys = new utxolib.bitgo.RootWalletKeys(utxolib.testutil.getKeyTriple("wasm"));
|
|
7
|
-
function getDescriptorOpDropP2ms(locktime, keys) {
|
|
8
|
-
const xpubs = keys.map((key) => key.toBase58() + "/*");
|
|
9
|
-
// the `r:` prefix is a custom BitGo modification of miniscript to allow OP_DROP
|
|
10
|
-
return `wsh(and_v(r:after(${locktime}),multi(2,${xpubs.join(",")})))`;
|
|
11
|
-
}
|
|
12
|
-
describe("CLV with OP_DROP", function () {
|
|
13
|
-
const locktime = 1024;
|
|
14
|
-
const descriptor = Descriptor.fromString(getDescriptorOpDropP2ms(locktime, rootWalletKeys.triple), "derivable");
|
|
15
|
-
it("has expected AST", () => {
|
|
16
|
-
assert.deepStrictEqual(descriptor.node(), {
|
|
17
|
-
Wsh: {
|
|
18
|
-
Ms: {
|
|
19
|
-
AndV: [
|
|
20
|
-
{
|
|
21
|
-
Drop: {
|
|
22
|
-
After: {
|
|
23
|
-
absLockTime: 1024,
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
Multi: [
|
|
29
|
-
2,
|
|
30
|
-
{
|
|
31
|
-
XPub: "xpub661MyMwAqRbcFNusVUbSN3nbanHMtJjLgZGrs1wxH6f77kKQd6Vq4HfkZQNPC1vSbN6RTiBWJJV6FwJtCfBon2SgaT2J3MSkydukstKjwbJ/*",
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
XPub: "xpub661MyMwAqRbcFo3t7PUqvbgvAcEuuoeVib5aapsg52inrG6KGF5aNtR5ey1FNCt1zJpMQiNec5XpofQmLNRhHvQRbhkc8UsWwwMwsXW6ogU/*",
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
XPub: "xpub661MyMwAqRbcGg7f22Kcg2gy1F4jBjWR3xQTECVeJPHmxvhg5gUAZC6EYFtnyi6aMDQir1kV8HzCqC2FzTowGgEZqRh7rinqUCDeNDdmYzH/*",
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
it("has expected asm", () => {
|
|
47
|
-
assert.deepStrictEqual(descriptor.atDerivationIndex(0).toAsmString().split(" "), [
|
|
48
|
-
"OP_PUSHBYTES_2",
|
|
49
|
-
"0004",
|
|
50
|
-
"OP_CLTV",
|
|
51
|
-
"OP_DROP",
|
|
52
|
-
"OP_PUSHNUM_2",
|
|
53
|
-
"OP_PUSHBYTES_33",
|
|
54
|
-
"02ae7c3c0ebc315a33151a1985ebb1fdcae72b3b91c38e3193c40ebabfffe9c343",
|
|
55
|
-
"OP_PUSHBYTES_33",
|
|
56
|
-
"0260ba2407f7c75d525db9f171e9b2f3cf5ba3f0d7fc6067b20d4b91585432f974",
|
|
57
|
-
"OP_PUSHBYTES_33",
|
|
58
|
-
"03eadd6e4300dac62f1d4cf1131a06c5e140911f04245c64934c27510e93dbe843",
|
|
59
|
-
"OP_PUSHNUM_3",
|
|
60
|
-
"OP_CHECKMULTISIG",
|
|
61
|
-
]);
|
|
62
|
-
});
|
|
63
|
-
it("can be signed", async function () {
|
|
64
|
-
const psbt = Object.assign(new utxolib.Psbt({ network: utxolib.networks.bitcoin }), {
|
|
65
|
-
locktime,
|
|
66
|
-
});
|
|
67
|
-
const signers = rootWalletKeys.triple.slice(0, 2);
|
|
68
|
-
const descriptorAt0 = descriptor.atDerivationIndex(0);
|
|
69
|
-
const script = Buffer.from(descriptorAt0.scriptPubkey());
|
|
70
|
-
psbt.addInput({
|
|
71
|
-
hash: Buffer.alloc(32),
|
|
72
|
-
index: 0,
|
|
73
|
-
sequence: 0xfffffffe,
|
|
74
|
-
witnessUtxo: { script, value: BigInt(1e8) },
|
|
75
|
-
});
|
|
76
|
-
psbt.addOutput({ script, value: BigInt(1e8) });
|
|
77
|
-
updateInputWithDescriptor(psbt, 0, descriptorAt0);
|
|
78
|
-
for (const signer of signers) {
|
|
79
|
-
psbt.signAllInputsHD(signer);
|
|
80
|
-
}
|
|
81
|
-
finalizePsbt(psbt);
|
|
82
|
-
const signedTx = psbt.extractTransaction().toBuffer();
|
|
83
|
-
assert.strictEqual(signedTx.toString("hex"), await getFixture("test/fixtures/opdrop.json", signedTx.toString("hex")));
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
2
|
-
import { Descriptor, Psbt } from "../js/index.js";
|
|
3
|
-
export declare function toWrappedPsbt(psbt: utxolib.bitgo.UtxoPsbt | utxolib.Psbt | Buffer | Uint8Array): Psbt;
|
|
4
|
-
export declare function toUtxoPsbt(psbt: Psbt | Buffer | Uint8Array): utxolib.bitgo.UtxoPsbt<utxolib.bitgo.UtxoTransaction<bigint>>;
|
|
5
|
-
export declare function updateInputWithDescriptor(psbt: utxolib.Psbt, inputIndex: number, descriptor: Descriptor): void;
|
|
6
|
-
export declare function updateOutputWithDescriptor(psbt: utxolib.Psbt, outputIndex: number, descriptor: Descriptor): void;
|
|
7
|
-
export declare function finalizePsbt(psbt: utxolib.Psbt): void;
|
|
8
|
-
export declare function assertEqualPsbt(a: utxolib.Psbt, b: utxolib.Psbt): void;
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import * as assert from "node:assert";
|
|
2
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
|
-
import { Psbt } from "../js/index.js";
|
|
4
|
-
export function toWrappedPsbt(psbt) {
|
|
5
|
-
if (psbt instanceof utxolib.bitgo.UtxoPsbt || psbt instanceof utxolib.Psbt) {
|
|
6
|
-
psbt = psbt.toBuffer();
|
|
7
|
-
}
|
|
8
|
-
if (psbt instanceof Buffer || psbt instanceof Uint8Array) {
|
|
9
|
-
return Psbt.deserialize(psbt);
|
|
10
|
-
}
|
|
11
|
-
throw new Error("Invalid input");
|
|
12
|
-
}
|
|
13
|
-
export function toUtxoPsbt(psbt) {
|
|
14
|
-
if (psbt instanceof Psbt) {
|
|
15
|
-
psbt = psbt.serialize();
|
|
16
|
-
}
|
|
17
|
-
if (psbt instanceof Buffer || psbt instanceof Uint8Array) {
|
|
18
|
-
return utxolib.bitgo.UtxoPsbt.fromBuffer(Buffer.from(psbt), {
|
|
19
|
-
network: utxolib.networks.bitcoin,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
throw new Error("Invalid input");
|
|
23
|
-
}
|
|
24
|
-
export function updateInputWithDescriptor(psbt, inputIndex, descriptor) {
|
|
25
|
-
const wrappedPsbt = toWrappedPsbt(psbt);
|
|
26
|
-
wrappedPsbt.updateInputWithDescriptor(inputIndex, descriptor);
|
|
27
|
-
psbt.data.inputs[inputIndex] = toUtxoPsbt(wrappedPsbt).data.inputs[inputIndex];
|
|
28
|
-
}
|
|
29
|
-
export function updateOutputWithDescriptor(psbt, outputIndex, descriptor) {
|
|
30
|
-
const wrappedPsbt = toWrappedPsbt(psbt);
|
|
31
|
-
wrappedPsbt.updateOutputWithDescriptor(outputIndex, descriptor);
|
|
32
|
-
psbt.data.outputs[outputIndex] = toUtxoPsbt(wrappedPsbt).data.outputs[outputIndex];
|
|
33
|
-
}
|
|
34
|
-
export function finalizePsbt(psbt) {
|
|
35
|
-
const wrappedPsbt = toWrappedPsbt(psbt);
|
|
36
|
-
wrappedPsbt.finalize();
|
|
37
|
-
const unwrappedPsbt = toUtxoPsbt(wrappedPsbt);
|
|
38
|
-
for (let i = 0; i < psbt.data.inputs.length; i++) {
|
|
39
|
-
psbt.data.inputs[i] = unwrappedPsbt.data.inputs[i];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function toEntries(k, v, path) {
|
|
43
|
-
if (matchPath(path, ["data", "inputs", any, "sighashType"])) {
|
|
44
|
-
return [];
|
|
45
|
-
}
|
|
46
|
-
if (matchPath(path.slice(-1), ["unknownKeyVals"])) {
|
|
47
|
-
if (Array.isArray(v) && v.length === 0) {
|
|
48
|
-
return [];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return [[k, toPlainObject(v, path)]];
|
|
52
|
-
}
|
|
53
|
-
const any = Symbol("any");
|
|
54
|
-
function matchPath(path, pattern) {
|
|
55
|
-
if (path.length !== pattern.length) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
for (let i = 0; i < path.length; i++) {
|
|
59
|
-
if (pattern[i] !== any && path[i] !== pattern[i]) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
function normalizeBip32Derivation(v) {
|
|
66
|
-
if (!Array.isArray(v)) {
|
|
67
|
-
throw new Error("Expected bip32Derivation to be an array");
|
|
68
|
-
}
|
|
69
|
-
return v
|
|
70
|
-
.map((e) => {
|
|
71
|
-
let { path } = e;
|
|
72
|
-
if (path.startsWith("m/")) {
|
|
73
|
-
path = path.slice(2);
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
...e,
|
|
77
|
-
path,
|
|
78
|
-
};
|
|
79
|
-
})
|
|
80
|
-
.sort((a, b) => a.masterFingerprint.toString().localeCompare(b.masterFingerprint.toString()));
|
|
81
|
-
}
|
|
82
|
-
function toPlainObject(v, path) {
|
|
83
|
-
// psbts have fun getters and other types of irregular properties that we mash into shape here
|
|
84
|
-
if (v === null || v === undefined) {
|
|
85
|
-
return v;
|
|
86
|
-
}
|
|
87
|
-
if (matchPath(path, ["data", "inputs", any, "bip32Derivation"]) ||
|
|
88
|
-
matchPath(path, ["data", "outputs", any, "bip32Derivation"])) {
|
|
89
|
-
v = normalizeBip32Derivation(v);
|
|
90
|
-
}
|
|
91
|
-
switch (typeof v) {
|
|
92
|
-
case "number":
|
|
93
|
-
case "bigint":
|
|
94
|
-
case "string":
|
|
95
|
-
case "boolean":
|
|
96
|
-
return v;
|
|
97
|
-
case "object":
|
|
98
|
-
if (v instanceof Buffer || v instanceof Uint8Array) {
|
|
99
|
-
return v.toString("hex");
|
|
100
|
-
}
|
|
101
|
-
if (Array.isArray(v)) {
|
|
102
|
-
return v.map((v, i) => toPlainObject(v, [...path, i]));
|
|
103
|
-
}
|
|
104
|
-
return Object.fromEntries(Object.entries(v)
|
|
105
|
-
.flatMap(([k, v]) => toEntries(k, v, [...path, k]))
|
|
106
|
-
.sort(([a], [b]) => a.localeCompare(b)));
|
|
107
|
-
default:
|
|
108
|
-
throw new Error(`Unsupported type: ${typeof v}`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
export function assertEqualPsbt(a, b) {
|
|
112
|
-
assert.deepStrictEqual(toPlainObject(a, []), toPlainObject(b, []));
|
|
113
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
2
|
-
import * as assert from "node:assert";
|
|
3
|
-
import { getPsbtFixtures } from "./psbtFixedScriptCompatFixtures.js";
|
|
4
|
-
import { Descriptor } from "../js/index.js";
|
|
5
|
-
import { getDescriptorForScriptType } from "./descriptorUtil.js";
|
|
6
|
-
import { assertEqualPsbt, toUtxoPsbt, toWrappedPsbt, updateInputWithDescriptor, } from "./psbt.util.js";
|
|
7
|
-
import { getKey } from "@bitgo/utxo-lib/dist/src/testutil";
|
|
8
|
-
const rootWalletKeys = new utxolib.bitgo.RootWalletKeys(utxolib.testutil.getKeyTriple("wasm"));
|
|
9
|
-
function assertEqualBuffer(a, b, message) {
|
|
10
|
-
assert.strictEqual(Buffer.from(a).toString("hex"), Buffer.from(b).toString("hex"), message);
|
|
11
|
-
}
|
|
12
|
-
const fixtures = getPsbtFixtures(rootWalletKeys);
|
|
13
|
-
function describeUpdateInputWithDescriptor(psbt, scriptType) {
|
|
14
|
-
function getFixtureAtStage(stage) {
|
|
15
|
-
const f = fixtures.find((f) => f.scriptType === scriptType && f.stage === stage);
|
|
16
|
-
if (!f) {
|
|
17
|
-
throw new Error(`Could not find fixture for scriptType ${scriptType} and stage ${stage}`);
|
|
18
|
-
}
|
|
19
|
-
return f;
|
|
20
|
-
}
|
|
21
|
-
const descriptorStr = getDescriptorForScriptType(rootWalletKeys, scriptType, "internal");
|
|
22
|
-
const index = 0;
|
|
23
|
-
const descriptor = Descriptor.fromString(descriptorStr, "derivable");
|
|
24
|
-
function getWrappedPsbt() {
|
|
25
|
-
return toWrappedPsbt(psbt);
|
|
26
|
-
}
|
|
27
|
-
function getWrappedPsbtWithDescriptorInfo() {
|
|
28
|
-
const wrappedPsbt = getWrappedPsbt();
|
|
29
|
-
const descriptorAtDerivation = descriptor.atDerivationIndex(index);
|
|
30
|
-
wrappedPsbt.updateInputWithDescriptor(0, descriptorAtDerivation);
|
|
31
|
-
wrappedPsbt.updateOutputWithDescriptor(0, descriptorAtDerivation);
|
|
32
|
-
return wrappedPsbt;
|
|
33
|
-
}
|
|
34
|
-
describe("Wrapped PSBT updateInputWithDescriptor", function () {
|
|
35
|
-
it("should update the input with the descriptor", function () {
|
|
36
|
-
const wrappedPsbt = getWrappedPsbtWithDescriptorInfo();
|
|
37
|
-
const updatedPsbt = toUtxoPsbt(wrappedPsbt);
|
|
38
|
-
assertEqualPsbt(updatedPsbt, getFixtureAtStage("unsigned").psbt);
|
|
39
|
-
updatedPsbt.signAllInputsHD(rootWalletKeys.triple[0]);
|
|
40
|
-
updatedPsbt.signAllInputsHD(rootWalletKeys.triple[2]);
|
|
41
|
-
const wrappedSignedPsbt = toWrappedPsbt(updatedPsbt);
|
|
42
|
-
updatedPsbt.finalizeAllInputs();
|
|
43
|
-
wrappedSignedPsbt.finalize();
|
|
44
|
-
assertEqualBuffer(updatedPsbt.toBuffer(), wrappedSignedPsbt.serialize());
|
|
45
|
-
assertEqualBuffer(getFixtureAtStage("fullsigned")
|
|
46
|
-
.psbt.clone()
|
|
47
|
-
.finalizeAllInputs()
|
|
48
|
-
.extractTransaction()
|
|
49
|
-
.toBuffer(), updatedPsbt.extractTransaction().toBuffer());
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
describe("updateInputWithDescriptor util", function () {
|
|
53
|
-
it("should update the input with the descriptor", function () {
|
|
54
|
-
const cloned = psbt.clone();
|
|
55
|
-
updateInputWithDescriptor(cloned, 0, descriptor.atDerivationIndex(index));
|
|
56
|
-
cloned.signAllInputsHD(rootWalletKeys.triple[0]);
|
|
57
|
-
cloned.signAllInputsHD(rootWalletKeys.triple[2]);
|
|
58
|
-
cloned.finalizeAllInputs();
|
|
59
|
-
assertEqualBuffer(getFixtureAtStage("fullsigned")
|
|
60
|
-
.psbt.clone()
|
|
61
|
-
.finalizeAllInputs()
|
|
62
|
-
.extractTransaction()
|
|
63
|
-
.toBuffer(), cloned.extractTransaction().toBuffer());
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
describe("psbt signWithXprv", function () {
|
|
67
|
-
function signWithKey(keys, { checkFinalized = false } = {}) {
|
|
68
|
-
it(`signs the input with keys ${keys.join(", ")}`, function () {
|
|
69
|
-
const psbt = getWrappedPsbtWithDescriptorInfo();
|
|
70
|
-
keys.forEach((keyName) => {
|
|
71
|
-
const key = keyName === "unrelated" ? getKey(keyName) : rootWalletKeys[keyName];
|
|
72
|
-
const derivationPaths = toUtxoPsbt(psbt).data.inputs[0].bip32Derivation.map((d) => d.path);
|
|
73
|
-
assert.ok(derivationPaths.every((p) => p === derivationPaths[0]));
|
|
74
|
-
const derived = key.derivePath(derivationPaths[0]);
|
|
75
|
-
assert.deepStrictEqual(psbt.signWithXprv(key.toBase58()), {
|
|
76
|
-
// map: input index -> pubkey array
|
|
77
|
-
0: { Ecdsa: keyName === "unrelated" ? [] : [derived.publicKey.toString("hex")] },
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
if (checkFinalized) {
|
|
81
|
-
psbt.finalize();
|
|
82
|
-
assertEqualBuffer(toUtxoPsbt(psbt).extractTransaction().toBuffer(), getFixtureAtStage("fullsigned")
|
|
83
|
-
.psbt.finalizeAllInputs()
|
|
84
|
-
.extractTransaction()
|
|
85
|
-
.toBuffer());
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
signWithKey(["user"]);
|
|
90
|
-
signWithKey(["backup"]);
|
|
91
|
-
signWithKey(["bitgo"]);
|
|
92
|
-
signWithKey(["unrelated"]);
|
|
93
|
-
signWithKey(["user", "bitgo"], { checkFinalized: true });
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
describe("PSBT fixture", function () {
|
|
97
|
-
fixtures.forEach(({ psbt, scriptType, stage }) => {
|
|
98
|
-
describe(`PSBT fixture ${scriptType} ${stage}`, function () {
|
|
99
|
-
let buf;
|
|
100
|
-
let wrappedPsbt;
|
|
101
|
-
before(function () {
|
|
102
|
-
buf = psbt.toBuffer();
|
|
103
|
-
wrappedPsbt = toWrappedPsbt(buf);
|
|
104
|
-
});
|
|
105
|
-
it("should map to same hex", function () {
|
|
106
|
-
assertEqualBuffer(buf, wrappedPsbt.serialize());
|
|
107
|
-
});
|
|
108
|
-
it("should round-trip utxolib -> ms -> utxolib", function () {
|
|
109
|
-
assertEqualBuffer(buf, toUtxoPsbt(wrappedPsbt).toBuffer());
|
|
110
|
-
});
|
|
111
|
-
if (stage === "bare") {
|
|
112
|
-
describeUpdateInputWithDescriptor(psbt, scriptType);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import * as utxolib from "@bitgo/utxo-lib";
|
|
2
|
-
import { RootWalletKeys } from "@bitgo/utxo-lib/dist/src/bitgo";
|
|
3
|
-
export type PsbtStage = "bare" | "unsigned" | "halfsigned" | "fullsigned";
|
|
4
|
-
export declare function toPsbtWithPrevOutOnly(psbt: utxolib.bitgo.UtxoPsbt): utxolib.bitgo.UtxoPsbt<utxolib.bitgo.UtxoTransaction<bigint>>;
|
|
5
|
-
export type PsbtFixture = {
|
|
6
|
-
psbt: utxolib.bitgo.UtxoPsbt;
|
|
7
|
-
scriptType: utxolib.bitgo.outputScripts.ScriptType2Of3;
|
|
8
|
-
stage: PsbtStage;
|
|
9
|
-
};
|
|
10
|
-
export declare function getPsbtFixtures(keys: RootWalletKeys): PsbtFixture[];
|