@bitgo/wasm-utxo 1.5.0 → 1.7.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/ast/formatNode.js +2 -2
- package/dist/cjs/js/ast/fromWasmNode.js +2 -1
- package/dist/cjs/js/bip32.d.ts +140 -0
- package/dist/cjs/js/bip32.js +177 -0
- package/dist/cjs/js/ecpair.d.ts +96 -0
- package/dist/cjs/js/ecpair.js +134 -0
- package/dist/cjs/js/{fixedScriptWallet.d.ts → fixedScriptWallet/BitGoPsbt.d.ts} +40 -41
- package/dist/cjs/js/{fixedScriptWallet.js → fixedScriptWallet/BitGoPsbt.js} +50 -36
- package/dist/cjs/js/fixedScriptWallet/ReplayProtection.d.ts +58 -0
- package/dist/cjs/js/fixedScriptWallet/ReplayProtection.js +89 -0
- package/dist/cjs/js/fixedScriptWallet/RootWalletKeys.d.ts +66 -0
- package/dist/cjs/js/fixedScriptWallet/RootWalletKeys.js +108 -0
- package/dist/cjs/js/fixedScriptWallet/address.d.ts +20 -0
- package/dist/cjs/js/fixedScriptWallet/address.js +29 -0
- package/dist/cjs/js/fixedScriptWallet/index.d.ts +4 -0
- package/dist/cjs/js/fixedScriptWallet/index.js +12 -0
- package/dist/cjs/js/index.d.ts +5 -1
- package/dist/cjs/js/index.js +11 -2
- package/dist/cjs/js/utxolibCompat.d.ts +0 -18
- package/dist/cjs/js/wasm/wasm_utxo.d.ts +254 -22
- package/dist/cjs/js/wasm/wasm_utxo.js +1081 -223
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/cjs/js/wasm/wasm_utxo_bg.wasm.d.ts +53 -8
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/js/ast/formatNode.js +2 -2
- package/dist/esm/js/ast/fromWasmNode.js +2 -1
- package/dist/esm/js/bip32.d.ts +140 -0
- package/dist/esm/js/bip32.js +173 -0
- package/dist/esm/js/ecpair.d.ts +96 -0
- package/dist/esm/js/ecpair.js +130 -0
- package/dist/esm/js/{fixedScriptWallet.d.ts → fixedScriptWallet/BitGoPsbt.d.ts} +40 -41
- package/dist/esm/js/{fixedScriptWallet.js → fixedScriptWallet/BitGoPsbt.js} +49 -33
- package/dist/esm/js/fixedScriptWallet/ReplayProtection.d.ts +58 -0
- package/dist/esm/js/fixedScriptWallet/ReplayProtection.js +85 -0
- package/dist/esm/js/fixedScriptWallet/RootWalletKeys.d.ts +66 -0
- package/dist/esm/js/fixedScriptWallet/RootWalletKeys.js +104 -0
- package/dist/esm/js/fixedScriptWallet/address.d.ts +20 -0
- package/dist/esm/js/fixedScriptWallet/address.js +25 -0
- package/dist/esm/js/fixedScriptWallet/index.d.ts +4 -0
- package/dist/esm/js/fixedScriptWallet/index.js +4 -0
- package/dist/esm/js/index.d.ts +5 -1
- package/dist/esm/js/index.js +8 -1
- package/dist/esm/js/utxolibCompat.d.ts +0 -18
- package/dist/esm/js/wasm/wasm_utxo.d.ts +254 -22
- package/dist/esm/js/wasm/wasm_utxo_bg.js +1070 -220
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm +0 -0
- package/dist/esm/js/wasm/wasm_utxo_bg.wasm.d.ts +53 -8
- package/dist/esm/test/address/utxolibCompat.js +12 -10
- package/dist/esm/test/bip32.d.ts +1 -0
- package/dist/esm/test/bip32.js +242 -0
- package/dist/esm/test/descriptorUtil.js +1 -1
- package/dist/esm/test/ecpair.d.ts +1 -0
- package/dist/esm/test/ecpair.js +137 -0
- package/dist/esm/test/fixedScript/address.js +9 -7
- package/dist/esm/test/fixedScript/fixtureUtil.d.ts +5 -3
- package/dist/esm/test/fixedScript/fixtureUtil.js +18 -7
- package/dist/esm/test/fixedScript/parseTransactionWithWalletKeys.js +39 -11
- package/dist/esm/test/fixedScript/verifySignature.js +73 -27
- package/dist/esm/test/fixedScriptToDescriptor.js +6 -4
- package/dist/esm/test/fixtures.js +5 -1
- package/dist/esm/test/psbt.util.js +1 -4
- package/dist/esm/test/psbtFixedScriptCompat.js +19 -17
- package/dist/esm/test/psbtFixedScriptCompatFixtures.js +1 -1
- package/dist/esm/test/psbtFromDescriptor.js +5 -8
- package/dist/esm/test/psbtFromDescriptor.util.js +3 -3
- package/dist/esm/test/test.js +5 -4
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -3
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
3
|
import { fixedScriptWallet } from "../../js/index.js";
|
|
4
|
-
import { loadPsbtFixture, loadWalletKeysFromFixture, getPsbtBuffer } from "./fixtureUtil.js";
|
|
4
|
+
import { loadPsbtFixture, loadWalletKeysFromFixture, getPsbtBuffer, loadReplayProtectionKeyFromFixture, } from "./fixtureUtil.js";
|
|
5
|
+
function getExpectedInputScriptType(fixtureScriptType) {
|
|
6
|
+
// Map fixture types to InputScriptType values
|
|
7
|
+
// Based on the Rust mapping in src/fixed_script_wallet/test_utils/fixtures.rs
|
|
8
|
+
switch (fixtureScriptType) {
|
|
9
|
+
case "p2shP2pk":
|
|
10
|
+
case "p2sh":
|
|
11
|
+
case "p2shP2wsh":
|
|
12
|
+
case "p2wsh":
|
|
13
|
+
return fixtureScriptType;
|
|
14
|
+
case "p2tr":
|
|
15
|
+
return "p2trLegacy";
|
|
16
|
+
case "p2trMusig2":
|
|
17
|
+
return "p2trMusig2ScriptPath";
|
|
18
|
+
case "taprootKeyPathSpend":
|
|
19
|
+
return "p2trMusig2KeyPath";
|
|
20
|
+
default:
|
|
21
|
+
throw new Error(`Unknown fixture script type: ${fixtureScriptType}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
5
24
|
function getOtherWalletKeys() {
|
|
6
25
|
const otherWalletKeys = utxolib.testutil.getKeyTriple("too many secrets");
|
|
7
26
|
return new utxolib.bitgo.RootWalletKeys(otherWalletKeys);
|
|
8
27
|
}
|
|
9
28
|
describe("parseTransactionWithWalletKeys", function () {
|
|
10
|
-
// Replay protection script that matches Rust tests
|
|
11
|
-
const replayProtectionScript = Buffer.from("a91420b37094d82a513451ff0ccd9db23aba05bc5ef387", "hex");
|
|
12
29
|
const supportedNetworks = utxolib.getNetworkList().filter((network) => {
|
|
13
30
|
return (utxolib.isMainnet(network) &&
|
|
14
31
|
network !== utxolib.networks.bitcoincash &&
|
|
@@ -17,20 +34,20 @@ describe("parseTransactionWithWalletKeys", function () {
|
|
|
17
34
|
network !== utxolib.networks.ecash &&
|
|
18
35
|
network !== utxolib.networks.zcash);
|
|
19
36
|
});
|
|
20
|
-
function hasReplayProtection(network) {
|
|
21
|
-
const mainnet = utxolib.getMainnet(network);
|
|
22
|
-
return mainnet === utxolib.networks.bitcoincash;
|
|
23
|
-
}
|
|
24
37
|
supportedNetworks.forEach((network) => {
|
|
25
38
|
const networkName = utxolib.getNetworkName(network);
|
|
26
39
|
describe(`network: ${networkName}`, function () {
|
|
27
40
|
let fullsignedPsbtBytes;
|
|
28
41
|
let bitgoPsbt;
|
|
29
42
|
let rootWalletKeys;
|
|
43
|
+
let replayProtectionKey;
|
|
44
|
+
let fixture;
|
|
30
45
|
before(function () {
|
|
31
|
-
|
|
46
|
+
fixture = loadPsbtFixture(networkName, "fullsigned");
|
|
47
|
+
fullsignedPsbtBytes = getPsbtBuffer(fixture);
|
|
32
48
|
bitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(fullsignedPsbtBytes, networkName);
|
|
33
|
-
rootWalletKeys = loadWalletKeysFromFixture(
|
|
49
|
+
rootWalletKeys = loadWalletKeysFromFixture(fixture);
|
|
50
|
+
replayProtectionKey = loadReplayProtectionKeyFromFixture(fixture);
|
|
34
51
|
});
|
|
35
52
|
it("should have matching unsigned transaction ID", function () {
|
|
36
53
|
const unsignedTxid = bitgoPsbt.unsignedTxid();
|
|
@@ -42,7 +59,7 @@ describe("parseTransactionWithWalletKeys", function () {
|
|
|
42
59
|
});
|
|
43
60
|
it("should parse transaction and identify internal/external outputs", function () {
|
|
44
61
|
const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
|
|
45
|
-
|
|
62
|
+
publicKeys: [replayProtectionKey],
|
|
46
63
|
});
|
|
47
64
|
// Verify all inputs have addresses and values
|
|
48
65
|
parsed.inputs.forEach((input, i) => {
|
|
@@ -85,10 +102,21 @@ describe("parseTransactionWithWalletKeys", function () {
|
|
|
85
102
|
assert.ok(typeof parsed.virtualSize === "number", "Virtual size should be a number");
|
|
86
103
|
assert.ok(parsed.virtualSize > 0, "Virtual size should be > 0");
|
|
87
104
|
});
|
|
105
|
+
it("should parse inputs with correct scriptType", function () {
|
|
106
|
+
const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
|
|
107
|
+
publicKeys: [replayProtectionKey],
|
|
108
|
+
});
|
|
109
|
+
// Verify all inputs have scriptType matching fixture
|
|
110
|
+
parsed.inputs.forEach((input, i) => {
|
|
111
|
+
const fixtureInput = fixture.psbtInputs[i];
|
|
112
|
+
const expectedScriptType = getExpectedInputScriptType(fixtureInput.type);
|
|
113
|
+
assert.strictEqual(input.scriptType, expectedScriptType, `Input ${i} scriptType should be ${expectedScriptType}, got ${input.scriptType}`);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
88
116
|
it("should fail to parse with other wallet keys", function () {
|
|
89
117
|
assert.throws(() => {
|
|
90
118
|
bitgoPsbt.parseTransactionWithWalletKeys(getOtherWalletKeys(), {
|
|
91
|
-
|
|
119
|
+
publicKeys: [replayProtectionKey],
|
|
92
120
|
});
|
|
93
121
|
}, (error) => {
|
|
94
122
|
return error.message.includes("Failed to parse transaction: Input 0: wallet validation failed");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
|
-
import { fixedScriptWallet } from "../../js/index.js";
|
|
4
|
-
import { loadPsbtFixture, loadWalletKeysFromFixture, getPsbtBuffer, } from "./fixtureUtil.js";
|
|
3
|
+
import { fixedScriptWallet, BIP32 } from "../../js/index.js";
|
|
4
|
+
import { loadPsbtFixture, loadWalletKeysFromFixture, getPsbtBuffer, loadReplayProtectionKeyFromFixture, } from "./fixtureUtil.js";
|
|
5
5
|
/**
|
|
6
6
|
* Get expected signature state for an input based on type and signing stage
|
|
7
7
|
* @param inputType - The type of input (e.g., "p2shP2pk", "p2trMusig2")
|
|
@@ -29,7 +29,7 @@ function getExpectedSignatures(inputType, signatureStage) {
|
|
|
29
29
|
// Regular multisig uses user + bitgo
|
|
30
30
|
return { user: true, backup: false, bitgo: true };
|
|
31
31
|
default:
|
|
32
|
-
throw new Error(`Unknown signature stage: ${signatureStage}`);
|
|
32
|
+
throw new Error(`Unknown signature stage: ${String(signatureStage)}`);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
@@ -40,25 +40,45 @@ function getExpectedSignatures(inputType, signatureStage) {
|
|
|
40
40
|
* @param inputType - The type of input (for replay protection handling)
|
|
41
41
|
* @param expectedSignatures - Expected signature state for each key or replay protection
|
|
42
42
|
*/
|
|
43
|
-
function verifyInputSignatures(bitgoPsbt, rootWalletKeys, inputIndex, expectedSignatures) {
|
|
43
|
+
function verifyInputSignatures(bitgoPsbt, parsed, rootWalletKeys, replayProtectionKey, inputIndex, expectedSignatures) {
|
|
44
44
|
// Handle replay protection inputs (P2shP2pk)
|
|
45
45
|
if ("hasReplayProtectionSignature" in expectedSignatures) {
|
|
46
|
-
const replayProtectionScript = Buffer.from("a91420b37094d82a513451ff0ccd9db23aba05bc5ef387", "hex");
|
|
47
46
|
const hasReplaySig = bitgoPsbt.verifyReplayProtectionSignature(inputIndex, {
|
|
48
|
-
|
|
47
|
+
publicKeys: [replayProtectionKey],
|
|
49
48
|
});
|
|
50
49
|
assert.strictEqual(hasReplaySig, expectedSignatures.hasReplayProtectionSignature, `Input ${inputIndex} replay protection signature mismatch`);
|
|
51
50
|
return;
|
|
52
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
|
+
}
|
|
53
58
|
// Handle standard multisig inputs
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const hasBitGoSig = bitgoPsbt.verifySignature(inputIndex, xpubs[2].toBase58());
|
|
59
|
+
const hasUserSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.userKey());
|
|
60
|
+
const hasBackupSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.backupKey());
|
|
61
|
+
const hasBitGoSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.bitgoKey());
|
|
58
62
|
assert.strictEqual(hasUserSig, expectedSignatures.user, `Input ${inputIndex} user key signature mismatch`);
|
|
59
63
|
assert.strictEqual(hasBackupSig, expectedSignatures.backup, `Input ${inputIndex} backup key signature mismatch`);
|
|
60
64
|
assert.strictEqual(hasBitGoSig, expectedSignatures.bitgo, `Input ${inputIndex} BitGo key signature mismatch`);
|
|
61
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
|
+
}
|
|
62
82
|
describe("verifySignature", function () {
|
|
63
83
|
const supportedNetworks = utxolib.getNetworkList().filter((network) => {
|
|
64
84
|
return (utxolib.isMainnet(network) &&
|
|
@@ -72,6 +92,7 @@ describe("verifySignature", function () {
|
|
|
72
92
|
const networkName = utxolib.getNetworkName(network);
|
|
73
93
|
describe(`network: ${networkName}`, function () {
|
|
74
94
|
let rootWalletKeys;
|
|
95
|
+
let replayProtectionKey;
|
|
75
96
|
let unsignedFixture;
|
|
76
97
|
let halfsignedFixture;
|
|
77
98
|
let fullsignedFixture;
|
|
@@ -79,42 +100,34 @@ describe("verifySignature", function () {
|
|
|
79
100
|
let halfsignedBitgoPsbt;
|
|
80
101
|
let fullsignedBitgoPsbt;
|
|
81
102
|
before(function () {
|
|
82
|
-
rootWalletKeys = loadWalletKeysFromFixture(networkName);
|
|
83
103
|
unsignedFixture = loadPsbtFixture(networkName, "unsigned");
|
|
84
104
|
halfsignedFixture = loadPsbtFixture(networkName, "halfsigned");
|
|
85
105
|
fullsignedFixture = loadPsbtFixture(networkName, "fullsigned");
|
|
106
|
+
rootWalletKeys = loadWalletKeysFromFixture(fullsignedFixture);
|
|
107
|
+
replayProtectionKey = loadReplayProtectionKeyFromFixture(fullsignedFixture);
|
|
86
108
|
unsignedBitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(getPsbtBuffer(unsignedFixture), networkName);
|
|
87
109
|
halfsignedBitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(getPsbtBuffer(halfsignedFixture), networkName);
|
|
88
110
|
fullsignedBitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(getPsbtBuffer(fullsignedFixture), networkName);
|
|
89
111
|
});
|
|
90
112
|
describe("unsigned PSBT", function () {
|
|
91
113
|
it("should return false for unsigned inputs", function () {
|
|
92
|
-
|
|
93
|
-
unsignedFixture.psbtInputs.forEach((input, index) => {
|
|
94
|
-
verifyInputSignatures(unsignedBitgoPsbt, rootWalletKeys, index, getExpectedSignatures(input.type, "unsigned"));
|
|
95
|
-
});
|
|
114
|
+
verifyAllInputSignatures(unsignedBitgoPsbt, unsignedFixture, rootWalletKeys, replayProtectionKey, "unsigned");
|
|
96
115
|
});
|
|
97
116
|
});
|
|
98
117
|
describe("half-signed PSBT", function () {
|
|
99
118
|
it("should return true for signed xpubs and false for unsigned", function () {
|
|
100
|
-
|
|
101
|
-
verifyInputSignatures(halfsignedBitgoPsbt, rootWalletKeys, index, getExpectedSignatures(input.type, "halfsigned"));
|
|
102
|
-
});
|
|
119
|
+
verifyAllInputSignatures(halfsignedBitgoPsbt, halfsignedFixture, rootWalletKeys, replayProtectionKey, "halfsigned");
|
|
103
120
|
});
|
|
104
121
|
});
|
|
105
122
|
describe("fully signed PSBT", function () {
|
|
106
123
|
it("should have 2 signatures (2-of-3 multisig)", function () {
|
|
107
|
-
|
|
108
|
-
fullsignedFixture.psbtInputs.forEach((input, index) => {
|
|
109
|
-
verifyInputSignatures(fullsignedBitgoPsbt, rootWalletKeys, index, getExpectedSignatures(input.type, "fullsigned"));
|
|
110
|
-
});
|
|
124
|
+
verifyAllInputSignatures(fullsignedBitgoPsbt, fullsignedFixture, rootWalletKeys, replayProtectionKey, "fullsigned");
|
|
111
125
|
});
|
|
112
126
|
});
|
|
113
127
|
describe("error handling", function () {
|
|
114
128
|
it("should throw error for out of bounds input index", function () {
|
|
115
|
-
const xpubs = rootWalletKeys.triple;
|
|
116
129
|
assert.throws(() => {
|
|
117
|
-
fullsignedBitgoPsbt.verifySignature(999,
|
|
130
|
+
fullsignedBitgoPsbt.verifySignature(999, rootWalletKeys.userKey());
|
|
118
131
|
}, (error) => {
|
|
119
132
|
return error.message.includes("Input index 999 out of bounds");
|
|
120
133
|
}, "Should throw error for out of bounds input index");
|
|
@@ -123,18 +136,51 @@ describe("verifySignature", function () {
|
|
|
123
136
|
assert.throws(() => {
|
|
124
137
|
fullsignedBitgoPsbt.verifySignature(0, "invalid-xpub");
|
|
125
138
|
}, (error) => {
|
|
126
|
-
return error.message.includes("Invalid
|
|
139
|
+
return error.message.includes("Invalid");
|
|
127
140
|
}, "Should throw error for invalid xpub");
|
|
128
141
|
});
|
|
129
142
|
it("should return false for xpub not in derivation path", function () {
|
|
130
143
|
// Create a different xpub that's not in the wallet
|
|
131
144
|
// Use a proper 32-byte seed (256 bits)
|
|
132
145
|
const differentSeed = Buffer.alloc(32, 0xaa); // 32 bytes filled with 0xaa
|
|
133
|
-
const differentKey =
|
|
146
|
+
const differentKey = BIP32.fromSeed(differentSeed);
|
|
134
147
|
const differentXpub = differentKey.neutered();
|
|
135
|
-
const result = fullsignedBitgoPsbt.verifySignature(0, differentXpub
|
|
148
|
+
const result = fullsignedBitgoPsbt.verifySignature(0, differentXpub);
|
|
136
149
|
assert.strictEqual(result, false, "Should return false for xpub not in PSBT derivation paths");
|
|
137
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
|
+
});
|
|
138
184
|
});
|
|
139
185
|
});
|
|
140
186
|
});
|
|
@@ -82,10 +82,12 @@ function runTest(scriptType, index, scope) {
|
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
describe("fixedScript to descriptor", function () {
|
|
86
|
+
scriptTypes.forEach((scriptType) => {
|
|
87
|
+
index.forEach((index) => {
|
|
88
|
+
scope.forEach((scope) => {
|
|
89
|
+
runTest(scriptType, index, scope);
|
|
90
|
+
});
|
|
89
91
|
});
|
|
90
92
|
});
|
|
91
93
|
});
|
|
@@ -4,9 +4,13 @@ export async function getFixture(path, defaultValue) {
|
|
|
4
4
|
return JSON.parse(await fs.readFile(path, "utf8"));
|
|
5
5
|
}
|
|
6
6
|
catch (e) {
|
|
7
|
-
if (e
|
|
7
|
+
if (typeof e === "object" &&
|
|
8
|
+
e !== null &&
|
|
9
|
+
"code" in e &&
|
|
10
|
+
e.code === "ENOENT") {
|
|
8
11
|
await fs.writeFile(path, JSON.stringify(defaultValue, null, 2));
|
|
9
12
|
throw new Error(`Fixture not found at ${path}, created a new one`);
|
|
10
13
|
}
|
|
14
|
+
throw e;
|
|
11
15
|
}
|
|
12
16
|
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import * as assert from "node:assert";
|
|
2
2
|
import * as utxolib from "@bitgo/utxo-lib";
|
|
3
3
|
import { Psbt } from "../js/index.js";
|
|
4
|
-
function toAddress(descriptor, network) {
|
|
5
|
-
utxolib.address.fromOutputScript(Buffer.from(descriptor.scriptPubkey()), network);
|
|
6
|
-
}
|
|
7
4
|
export function toWrappedPsbt(psbt) {
|
|
8
5
|
if (psbt instanceof utxolib.bitgo.UtxoPsbt || psbt instanceof utxolib.Psbt) {
|
|
9
6
|
psbt = psbt.toBuffer();
|
|
@@ -69,7 +66,7 @@ function normalizeBip32Derivation(v) {
|
|
|
69
66
|
if (!Array.isArray(v)) {
|
|
70
67
|
throw new Error("Expected bip32Derivation to be an array");
|
|
71
68
|
}
|
|
72
|
-
return
|
|
69
|
+
return v
|
|
73
70
|
.map((e) => {
|
|
74
71
|
let { path } = e;
|
|
75
72
|
if (path.startsWith("m/")) {
|
|
@@ -65,7 +65,7 @@ function describeUpdateInputWithDescriptor(psbt, scriptType) {
|
|
|
65
65
|
});
|
|
66
66
|
describe("psbt signWithXprv", function () {
|
|
67
67
|
function signWithKey(keys, { checkFinalized = false } = {}) {
|
|
68
|
-
it(`signs the input with keys ${keys}`, function () {
|
|
68
|
+
it(`signs the input with keys ${keys.join(", ")}`, function () {
|
|
69
69
|
const psbt = getWrappedPsbtWithDescriptorInfo();
|
|
70
70
|
keys.forEach((keyName) => {
|
|
71
71
|
const key = keyName === "unrelated" ? getKey(keyName) : rootWalletKeys[keyName];
|
|
@@ -93,22 +93,24 @@ function describeUpdateInputWithDescriptor(psbt, scriptType) {
|
|
|
93
93
|
signWithKey(["user", "bitgo"], { checkFinalized: true });
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
}
|
|
109
114
|
});
|
|
110
|
-
if (stage === "bare") {
|
|
111
|
-
describeUpdateInputWithDescriptor(psbt, scriptType);
|
|
112
|
-
}
|
|
113
115
|
});
|
|
114
116
|
});
|
|
@@ -14,7 +14,7 @@ export function toPsbtWithPrevOutOnly(psbt) {
|
|
|
14
14
|
...(witnessUtxo ? { witnessUtxo } : { nonWitnessUtxo }),
|
|
15
15
|
});
|
|
16
16
|
});
|
|
17
|
-
psbt.txOutputs.forEach((output
|
|
17
|
+
psbt.txOutputs.forEach((output) => {
|
|
18
18
|
psbtCopy.addOutput(output);
|
|
19
19
|
});
|
|
20
20
|
return psbtCopy;
|
|
@@ -8,9 +8,6 @@ import { toWrappedPsbt } from "./psbt.util.js";
|
|
|
8
8
|
function toKeyWithPath(k, path = "*") {
|
|
9
9
|
return k.neutered().toBase58() + "/" + path;
|
|
10
10
|
}
|
|
11
|
-
function toKeyPlain(k) {
|
|
12
|
-
return k.toString("hex");
|
|
13
|
-
}
|
|
14
11
|
function toECPair(k) {
|
|
15
12
|
assert(k.privateKey);
|
|
16
13
|
return ECPair.fromPrivateKey(k.privateKey);
|
|
@@ -39,8 +36,8 @@ function describeSignDescriptor(name, descriptor, { signBip32 = [], signECPair =
|
|
|
39
36
|
[isTaproot ? "Schnorr" : "Ecdsa"]: keys.map((key) => key.publicKey.subarray(isTaproot ? 1 : 0).toString("hex")),
|
|
40
37
|
};
|
|
41
38
|
}
|
|
42
|
-
signBip32.forEach((signSeq
|
|
43
|
-
it(`should sign ${signSeq.map((k) => getKeyName(k))} xprv`, function () {
|
|
39
|
+
signBip32.forEach((signSeq) => {
|
|
40
|
+
it(`should sign ${signSeq.map((k) => getKeyName(k)).join(", ")} xprv`, function () {
|
|
44
41
|
const wrappedPsbt = toWrappedPsbt(psbt);
|
|
45
42
|
signSeq.forEach((key) => {
|
|
46
43
|
assert.deepStrictEqual(wrappedPsbt.signWithXprv(key.toBase58()), {
|
|
@@ -50,7 +47,7 @@ function describeSignDescriptor(name, descriptor, { signBip32 = [], signECPair =
|
|
|
50
47
|
});
|
|
51
48
|
wrappedPsbt.finalize();
|
|
52
49
|
});
|
|
53
|
-
it(`should sign ${signSeq.map((k) => getKeyName(k))} prv buffer`, function () {
|
|
50
|
+
it(`should sign ${signSeq.map((k) => getKeyName(k)).join(", ")} prv buffer`, function () {
|
|
54
51
|
const wrappedPsbt = toWrappedPsbt(psbt);
|
|
55
52
|
signSeq.forEach((key) => {
|
|
56
53
|
assert.deepStrictEqual(wrappedPsbt.signWithPrv(key.derive(0).privateKey), {
|
|
@@ -62,8 +59,8 @@ function describeSignDescriptor(name, descriptor, { signBip32 = [], signECPair =
|
|
|
62
59
|
});
|
|
63
60
|
});
|
|
64
61
|
});
|
|
65
|
-
signECPair.forEach((signSeq
|
|
66
|
-
it(`should sign ${signSeq.map((k) => getKeyName(k))} ec pair`, function () {
|
|
62
|
+
signECPair.forEach((signSeq) => {
|
|
63
|
+
it(`should sign ${signSeq.map((k) => getKeyName(k)).join(", ")} ec pair`, function () {
|
|
67
64
|
const wrappedPsbt = toWrappedPsbt(psbt);
|
|
68
65
|
signSeq.forEach((key) => {
|
|
69
66
|
assert(key.privateKey);
|
|
@@ -10,7 +10,7 @@ export function toDerivedDescriptorWalletOutput(output, descriptor) {
|
|
|
10
10
|
const derivedDescriptor = descriptor.atDerivationIndex(output.descriptorIndex);
|
|
11
11
|
const script = createScriptPubKeyFromDescriptor(derivedDescriptor);
|
|
12
12
|
if (!script.equals(output.witnessUtxo.script)) {
|
|
13
|
-
throw new Error(`Script mismatch: descriptor ${output.descriptorName} ${descriptor.toString()} script=${script}`);
|
|
13
|
+
throw new Error(`Script mismatch: descriptor ${output.descriptorName} ${descriptor.toString()} script=${script.toString("hex")}`);
|
|
14
14
|
}
|
|
15
15
|
return {
|
|
16
16
|
hash: output.hash,
|
|
@@ -33,7 +33,7 @@ function updateInputsWithDescriptors(psbt, descriptors) {
|
|
|
33
33
|
wrappedPsbt.updateInputWithDescriptor(inputIndex, descriptor);
|
|
34
34
|
}
|
|
35
35
|
const unwrappedPsbt = toUtxoPsbt(wrappedPsbt);
|
|
36
|
-
for (
|
|
36
|
+
for (let inputIndex = 0; inputIndex < psbt.txInputs.length; inputIndex++) {
|
|
37
37
|
psbt.data.inputs[inputIndex] = unwrappedPsbt.data.inputs[inputIndex];
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -45,7 +45,7 @@ function updateOutputsWithDescriptors(psbt, descriptors) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
const unwrappedPsbt = toUtxoPsbt(wrappedPsbt);
|
|
48
|
-
for (
|
|
48
|
+
for (let outputIndex = 0; outputIndex < psbt.txOutputs.length; outputIndex++) {
|
|
49
49
|
psbt.data.outputs[outputIndex] = unwrappedPsbt.data.outputs[outputIndex];
|
|
50
50
|
}
|
|
51
51
|
}
|
package/dist/esm/test/test.js
CHANGED
|
@@ -86,7 +86,7 @@ describe("Descriptor fixtures", function () {
|
|
|
86
86
|
}
|
|
87
87
|
assert.strictEqual(descriptorString, fixture.descriptor);
|
|
88
88
|
});
|
|
89
|
-
it("should parse (pkType derivable)",
|
|
89
|
+
it("should parse (pkType derivable)", function () {
|
|
90
90
|
const descriptor = Descriptor.fromString(fixture.descriptor, "derivable");
|
|
91
91
|
if (isDerivable(i)) {
|
|
92
92
|
assert.doesNotThrow(() => Descriptor.fromString(fixture.descriptor, "derivable").atDerivationIndex(0));
|
|
@@ -95,8 +95,9 @@ describe("Descriptor fixtures", function () {
|
|
|
95
95
|
}
|
|
96
96
|
const scriptPubKey = Buffer.from(descriptor.atDerivationIndex(fixture.index ?? 0).scriptPubkey());
|
|
97
97
|
assert.strictEqual(scriptPubKey.toString("hex"), fixture.script);
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const descType = descriptor.descType();
|
|
99
|
+
if (descType !== "Bare") {
|
|
100
|
+
assert.strictEqual(scriptPubKey.length, getScriptPubKeyLength(descType), `Unexpected scriptPubKey length for descriptor ${descType}: ${scriptPubKey.length}`);
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
else {
|
|
@@ -106,7 +107,7 @@ describe("Descriptor fixtures", function () {
|
|
|
106
107
|
assert.ok(Number.isInteger(descriptor.maxWeightToSatisfy()));
|
|
107
108
|
assertKnownDescriptorType(descriptor);
|
|
108
109
|
});
|
|
109
|
-
it("can round-trip with formatNode(toWasmNode(.))",
|
|
110
|
+
it("can round-trip with formatNode(toWasmNode(.))", function () {
|
|
110
111
|
const ast = fromDescriptor(descriptor);
|
|
111
112
|
assert.strictEqual(formatNode(ast), removeChecksum(descriptor.toString()));
|
|
112
113
|
});
|