@bitgo/wasm-utxo 1.6.0 → 1.8.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.
Files changed (63) hide show
  1. package/dist/cjs/js/bip32.d.ts +140 -0
  2. package/dist/cjs/js/bip32.js +177 -0
  3. package/dist/cjs/js/ecpair.d.ts +96 -0
  4. package/dist/cjs/js/ecpair.js +134 -0
  5. package/dist/cjs/js/fixedScriptWallet/BitGoPsbt.d.ts +242 -0
  6. package/dist/cjs/js/fixedScriptWallet/BitGoPsbt.js +274 -0
  7. package/dist/cjs/js/fixedScriptWallet/ReplayProtection.d.ts +58 -0
  8. package/dist/cjs/js/fixedScriptWallet/ReplayProtection.js +89 -0
  9. package/dist/cjs/js/fixedScriptWallet/RootWalletKeys.d.ts +66 -0
  10. package/dist/cjs/js/fixedScriptWallet/RootWalletKeys.js +108 -0
  11. package/dist/cjs/js/fixedScriptWallet/address.d.ts +20 -0
  12. package/dist/cjs/js/fixedScriptWallet/address.js +29 -0
  13. package/dist/cjs/js/fixedScriptWallet/index.d.ts +4 -0
  14. package/dist/cjs/js/fixedScriptWallet/index.js +12 -0
  15. package/dist/cjs/js/index.d.ts +5 -1
  16. package/dist/cjs/js/index.js +11 -2
  17. package/dist/cjs/js/utxolibCompat.d.ts +0 -18
  18. package/dist/cjs/js/wasm/wasm_utxo.d.ts +333 -15
  19. package/dist/cjs/js/wasm/wasm_utxo.js +1311 -201
  20. package/dist/cjs/js/wasm/wasm_utxo_bg.wasm +0 -0
  21. package/dist/cjs/js/wasm/wasm_utxo_bg.wasm.d.ts +64 -15
  22. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  23. package/dist/esm/js/bip32.d.ts +140 -0
  24. package/dist/esm/js/bip32.js +173 -0
  25. package/dist/esm/js/ecpair.d.ts +96 -0
  26. package/dist/esm/js/ecpair.js +130 -0
  27. package/dist/esm/js/fixedScriptWallet/BitGoPsbt.d.ts +242 -0
  28. package/dist/esm/js/fixedScriptWallet/BitGoPsbt.js +270 -0
  29. package/dist/esm/js/fixedScriptWallet/ReplayProtection.d.ts +58 -0
  30. package/dist/esm/js/fixedScriptWallet/ReplayProtection.js +85 -0
  31. package/dist/esm/js/fixedScriptWallet/RootWalletKeys.d.ts +66 -0
  32. package/dist/esm/js/fixedScriptWallet/RootWalletKeys.js +104 -0
  33. package/dist/esm/js/fixedScriptWallet/address.d.ts +20 -0
  34. package/dist/esm/js/fixedScriptWallet/address.js +25 -0
  35. package/dist/esm/js/fixedScriptWallet/index.d.ts +4 -0
  36. package/dist/esm/js/fixedScriptWallet/index.js +4 -0
  37. package/dist/esm/js/index.d.ts +5 -1
  38. package/dist/esm/js/index.js +8 -1
  39. package/dist/esm/js/utxolibCompat.d.ts +0 -18
  40. package/dist/esm/js/wasm/wasm_utxo.d.ts +333 -15
  41. package/dist/esm/js/wasm/wasm_utxo_bg.js +1301 -199
  42. package/dist/esm/js/wasm/wasm_utxo_bg.wasm +0 -0
  43. package/dist/esm/js/wasm/wasm_utxo_bg.wasm.d.ts +64 -15
  44. package/dist/esm/test/bip32.js +242 -0
  45. package/dist/esm/test/ecpair.d.ts +1 -0
  46. package/dist/esm/test/ecpair.js +137 -0
  47. package/dist/esm/test/fixedScript/fixtureUtil.d.ts +12 -2
  48. package/dist/esm/test/fixedScript/fixtureUtil.js +28 -7
  49. package/dist/esm/test/fixedScript/musig2Nonces.d.ts +1 -0
  50. package/dist/esm/test/fixedScript/musig2Nonces.js +77 -0
  51. package/dist/esm/test/fixedScript/parseTransactionWithWalletKeys.js +7 -7
  52. package/dist/esm/test/fixedScript/signAndVerifySignature.d.ts +1 -0
  53. package/dist/esm/test/fixedScript/signAndVerifySignature.js +268 -0
  54. package/dist/esm/test/fixedScript/walletKeys.util.d.ts +12 -0
  55. package/dist/esm/test/fixedScript/walletKeys.util.js +17 -0
  56. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +1 -1
  58. package/dist/cjs/js/fixedScriptWallet.d.ts +0 -146
  59. package/dist/cjs/js/fixedScriptWallet.js +0 -138
  60. package/dist/esm/js/fixedScriptWallet.d.ts +0 -146
  61. package/dist/esm/js/fixedScriptWallet.js +0 -132
  62. package/dist/esm/test/fixedScript/verifySignature.js +0 -141
  63. /package/dist/esm/test/{fixedScript/verifySignature.d.ts → bip32.d.ts} +0 -0
@@ -0,0 +1,77 @@
1
+ import assert from "assert";
2
+ import { BIP32 } from "../../js/bip32.js";
3
+ import { loadPsbtFixture, getBitGoPsbt } from "./fixtureUtil.js";
4
+ describe("MuSig2 nonce management", function () {
5
+ describe("Bitcoin mainnet", function () {
6
+ const networkName = "bitcoin";
7
+ let fixture;
8
+ let userKey;
9
+ let backupKey;
10
+ let bitgoKey;
11
+ before(function () {
12
+ fixture = loadPsbtFixture(networkName, "unsigned");
13
+ userKey = BIP32.fromBase58(fixture.walletKeys[0]);
14
+ backupKey = BIP32.fromBase58(fixture.walletKeys[1]);
15
+ bitgoKey = BIP32.fromBase58(fixture.walletKeys[2]);
16
+ });
17
+ it("should generate nonces for MuSig2 inputs with auto-generated session ID", function () {
18
+ const unsignedBitgoPsbt = getBitGoPsbt(fixture, networkName);
19
+ // Generate nonces with auto-generated session ID (no second parameter)
20
+ assert.doesNotThrow(() => {
21
+ unsignedBitgoPsbt.generateMusig2Nonces(userKey);
22
+ });
23
+ // Verify nonces were stored by serializing and deserializing
24
+ const serializedWithUserNonces = unsignedBitgoPsbt.serialize();
25
+ assert.ok(serializedWithUserNonces.length > getBitGoPsbt(fixture, networkName).serialize().length);
26
+ assert.doesNotThrow(() => {
27
+ unsignedBitgoPsbt.generateMusig2Nonces(bitgoKey);
28
+ });
29
+ const serializedWithBitgoNonces = unsignedBitgoPsbt.serialize();
30
+ assert.ok(serializedWithBitgoNonces.length > serializedWithUserNonces.length);
31
+ assert.throws(() => {
32
+ unsignedBitgoPsbt.generateMusig2Nonces(backupKey);
33
+ }, "Should throw error when generating nonces for backup key");
34
+ });
35
+ it("implements combineMusig2Nonces", function () {
36
+ const unsignedBitgoPsbtWithUserNonces = getBitGoPsbt(fixture, networkName);
37
+ unsignedBitgoPsbtWithUserNonces.generateMusig2Nonces(userKey);
38
+ const unsignedBitgoPsbtWithBitgoNonces = getBitGoPsbt(fixture, networkName);
39
+ unsignedBitgoPsbtWithBitgoNonces.generateMusig2Nonces(bitgoKey);
40
+ const unsignedBitgoPsbtWithBothNonces = getBitGoPsbt(fixture, networkName);
41
+ unsignedBitgoPsbtWithBothNonces.combineMusig2Nonces(unsignedBitgoPsbtWithUserNonces);
42
+ unsignedBitgoPsbtWithBothNonces.combineMusig2Nonces(unsignedBitgoPsbtWithBitgoNonces);
43
+ {
44
+ const psbt = getBitGoPsbt(fixture, networkName);
45
+ psbt.combineMusig2Nonces(unsignedBitgoPsbtWithUserNonces);
46
+ assert.strictEqual(psbt.serialize().length, unsignedBitgoPsbtWithUserNonces.serialize().length);
47
+ }
48
+ {
49
+ const psbt = getBitGoPsbt(fixture, networkName);
50
+ psbt.combineMusig2Nonces(unsignedBitgoPsbtWithBitgoNonces);
51
+ assert.strictEqual(psbt.serialize().length, unsignedBitgoPsbtWithBitgoNonces.serialize().length);
52
+ }
53
+ {
54
+ const psbt = getBitGoPsbt(fixture, networkName);
55
+ psbt.combineMusig2Nonces(unsignedBitgoPsbtWithUserNonces);
56
+ psbt.combineMusig2Nonces(unsignedBitgoPsbtWithBitgoNonces);
57
+ assert.strictEqual(psbt.serialize().length, unsignedBitgoPsbtWithBothNonces.serialize().length);
58
+ }
59
+ });
60
+ it("should reject invalid session ID length", function () {
61
+ const unsignedBitgoPsbt = getBitGoPsbt(fixture, networkName);
62
+ // Invalid session ID (wrong length)
63
+ const invalidSessionId = new Uint8Array(16); // Should be 32 bytes
64
+ assert.throws(() => {
65
+ unsignedBitgoPsbt.generateMusig2Nonces(userKey, invalidSessionId);
66
+ }, "Should throw error for invalid session ID length");
67
+ });
68
+ it("should reject custom session ID on mainnet (security)", function () {
69
+ const unsignedBitgoPsbt = getBitGoPsbt(fixture, "bitcoin");
70
+ // Custom session ID should be rejected on mainnet for security
71
+ const customSessionId = new Uint8Array(32).fill(1);
72
+ assert.throws(() => {
73
+ unsignedBitgoPsbt.generateMusig2Nonces(userKey, customSessionId);
74
+ }, /Custom session_id is only allowed on testnets/, "Should throw error when providing custom session_id on mainnet");
75
+ });
76
+ });
77
+ });
@@ -1,7 +1,7 @@
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
5
  function getExpectedInputScriptType(fixtureScriptType) {
6
6
  // Map fixture types to InputScriptType values
7
7
  // Based on the Rust mapping in src/fixed_script_wallet/test_utils/fixtures.rs
@@ -26,8 +26,6 @@ function getOtherWalletKeys() {
26
26
  return new utxolib.bitgo.RootWalletKeys(otherWalletKeys);
27
27
  }
28
28
  describe("parseTransactionWithWalletKeys", function () {
29
- // Replay protection script that matches Rust tests
30
- const replayProtectionScript = Buffer.from("a91420b37094d82a513451ff0ccd9db23aba05bc5ef387", "hex");
31
29
  const supportedNetworks = utxolib.getNetworkList().filter((network) => {
32
30
  return (utxolib.isMainnet(network) &&
33
31
  network !== utxolib.networks.bitcoincash &&
@@ -42,12 +40,14 @@ describe("parseTransactionWithWalletKeys", function () {
42
40
  let fullsignedPsbtBytes;
43
41
  let bitgoPsbt;
44
42
  let rootWalletKeys;
43
+ let replayProtectionKey;
45
44
  let fixture;
46
45
  before(function () {
47
46
  fixture = loadPsbtFixture(networkName, "fullsigned");
48
47
  fullsignedPsbtBytes = getPsbtBuffer(fixture);
49
48
  bitgoPsbt = fixedScriptWallet.BitGoPsbt.fromBytes(fullsignedPsbtBytes, networkName);
50
- rootWalletKeys = loadWalletKeysFromFixture(networkName);
49
+ rootWalletKeys = loadWalletKeysFromFixture(fixture);
50
+ replayProtectionKey = loadReplayProtectionKeyFromFixture(fixture);
51
51
  });
52
52
  it("should have matching unsigned transaction ID", function () {
53
53
  const unsignedTxid = bitgoPsbt.unsignedTxid();
@@ -59,7 +59,7 @@ describe("parseTransactionWithWalletKeys", function () {
59
59
  });
60
60
  it("should parse transaction and identify internal/external outputs", function () {
61
61
  const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
62
- outputScripts: [replayProtectionScript],
62
+ publicKeys: [replayProtectionKey],
63
63
  });
64
64
  // Verify all inputs have addresses and values
65
65
  parsed.inputs.forEach((input, i) => {
@@ -104,7 +104,7 @@ describe("parseTransactionWithWalletKeys", function () {
104
104
  });
105
105
  it("should parse inputs with correct scriptType", function () {
106
106
  const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
107
- outputScripts: [replayProtectionScript],
107
+ publicKeys: [replayProtectionKey],
108
108
  });
109
109
  // Verify all inputs have scriptType matching fixture
110
110
  parsed.inputs.forEach((input, i) => {
@@ -116,7 +116,7 @@ describe("parseTransactionWithWalletKeys", function () {
116
116
  it("should fail to parse with other wallet keys", function () {
117
117
  assert.throws(() => {
118
118
  bitgoPsbt.parseTransactionWithWalletKeys(getOtherWalletKeys(), {
119
- outputScripts: [replayProtectionScript],
119
+ publicKeys: [replayProtectionKey],
120
120
  });
121
121
  }, (error) => {
122
122
  return error.message.includes("Failed to parse transaction: Input 0: wallet validation failed");
@@ -0,0 +1,268 @@
1
+ import assert from "node:assert";
2
+ import * as utxolib from "@bitgo/utxo-lib";
3
+ import { BIP32 } from "../../js/index.js";
4
+ import { loadPsbtFixture, loadWalletKeysFromFixture, getBitGoPsbt, loadReplayProtectionKeyFromFixture, } from "./fixtureUtil.js";
5
+ /**
6
+ * Load xprivs from a fixture
7
+ * @param fixture - The test fixture
8
+ * @returns The xprivs for user, backup, and bitgo keys
9
+ */
10
+ function loadXprivsFromFixture(fixture) {
11
+ const [userXpriv, backupXpriv, bitgoXpriv] = fixture.walletKeys.map((xprv) => BIP32.fromBase58(xprv));
12
+ return {
13
+ user: userXpriv,
14
+ backup: backupXpriv,
15
+ bitgo: bitgoXpriv,
16
+ };
17
+ }
18
+ /**
19
+ * Get expected signature state for an input based on type and signing stage
20
+ * @param inputType - The type of input (e.g., "p2shP2pk", "p2trMusig2", "taprootKeyPathSpend")
21
+ * @param signatureStage - The signing stage (unsigned, halfsigned, fullsigned)
22
+ * @returns Expected signature state for replay protection OR multi-key signatures
23
+ */
24
+ function getExpectedSignatures(inputType, signatureStage) {
25
+ // p2shP2pk inputs use replay protection signature verification
26
+ if (inputType === "p2shP2pk") {
27
+ return {
28
+ hasReplayProtectionSignature: signatureStage === "halfsigned" || signatureStage === "fullsigned",
29
+ };
30
+ }
31
+ switch (signatureStage) {
32
+ case "unsigned":
33
+ return { user: false, backup: false, bitgo: false };
34
+ case "halfsigned":
35
+ // User signs first
36
+ return { user: true, backup: false, bitgo: false };
37
+ case "fullsigned":
38
+ // p2trMusig2 uses user + backup for 2-of-2 MuSig2
39
+ if (inputType === "p2trMusig2") {
40
+ return { user: true, backup: true, bitgo: false };
41
+ }
42
+ // Regular multisig uses user + bitgo
43
+ return { user: true, backup: false, bitgo: true };
44
+ default:
45
+ throw new Error(`Unknown signature stage: ${String(signatureStage)}`);
46
+ }
47
+ }
48
+ /**
49
+ * Verify signature state for a specific input in a PSBT
50
+ * @param bitgoPsbt - The PSBT to verify
51
+ * @param rootWalletKeys - Wallet keys for verification
52
+ * @param inputIndex - The input index to verify
53
+ * @param inputType - The type of input (for replay protection handling)
54
+ * @param expectedSignatures - Expected signature state for each key or replay protection
55
+ */
56
+ function verifyInputSignatures(bitgoPsbt, parsed, rootWalletKeys, replayProtectionKey, inputIndex, expectedSignatures) {
57
+ // Handle replay protection inputs (P2shP2pk)
58
+ if ("hasReplayProtectionSignature" in expectedSignatures) {
59
+ const hasReplaySig = bitgoPsbt.verifyReplayProtectionSignature(inputIndex, {
60
+ publicKeys: [replayProtectionKey],
61
+ });
62
+ assert.strictEqual(hasReplaySig, expectedSignatures.hasReplayProtectionSignature, `Input ${inputIndex} replay protection signature mismatch`);
63
+ return;
64
+ }
65
+ if (parsed.inputs[inputIndex].scriptType === "p2shP2pk") {
66
+ const hasReplaySig = bitgoPsbt.verifySignature(inputIndex, replayProtectionKey);
67
+ assert.ok("hasReplayProtectionSignature" in expectedSignatures, "Expected hasReplayProtectionSignature to be present");
68
+ assert.strictEqual(hasReplaySig, expectedSignatures.hasReplayProtectionSignature, `Input ${inputIndex} replay protection signature mismatch`);
69
+ return;
70
+ }
71
+ // Handle standard multisig inputs
72
+ const hasUserSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.userKey());
73
+ const hasBackupSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.backupKey());
74
+ const hasBitGoSig = bitgoPsbt.verifySignature(inputIndex, rootWalletKeys.bitgoKey());
75
+ const scriptType = parsed.inputs[inputIndex].scriptType;
76
+ assert.strictEqual(hasUserSig, expectedSignatures.user, `Input ${inputIndex} user key signature mismatch type=${scriptType}`);
77
+ assert.strictEqual(hasBackupSig, expectedSignatures.backup, `Input ${inputIndex} backup key signature mismatch type=${scriptType}`);
78
+ assert.strictEqual(hasBitGoSig, expectedSignatures.bitgo, `Input ${inputIndex} BitGo key signature mismatch type=${scriptType}`);
79
+ }
80
+ /**
81
+ * Helper to verify signatures for all inputs in a PSBT
82
+ * @param bitgoPsbt - The PSBT to verify
83
+ * @param fixture - The test fixture containing input metadata
84
+ * @param rootWalletKeys - Wallet keys for verification
85
+ * @param replayProtectionKey - Key for replay protection inputs
86
+ * @param signatureStage - The signing stage (unsigned, halfsigned, fullsigned)
87
+ */
88
+ function verifyAllInputSignatures(bitgoPsbt, fixture, rootWalletKeys, replayProtectionKey, signatureStage) {
89
+ const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
90
+ publicKeys: [replayProtectionKey],
91
+ });
92
+ fixture.psbtInputs.forEach((input, index) => {
93
+ verifyInputSignatures(bitgoPsbt, parsed, rootWalletKeys, replayProtectionKey, index, getExpectedSignatures(input.type, signatureStage));
94
+ });
95
+ }
96
+ function signInputAndVerify(bitgoPsbt, index, key, keyName, inputType) {
97
+ bitgoPsbt.sign(index, key);
98
+ assert.strictEqual(bitgoPsbt.verifySignature(index, key), true, `Input ${index} signature mismatch key=${keyName} type=${inputType}`);
99
+ }
100
+ /**
101
+ * Sign all inputs in a PSBT according to the signature stage
102
+ * @param bitgoPsbt - The PSBT to sign
103
+ * @param rootWalletKeys - Wallet keys for parsing the transaction
104
+ * @param xprivs - The xprivs to use for signing
105
+ * @param replayProtectionKey - The ECPair for signing replay protection (p2shP2pk) inputs
106
+ */
107
+ function signAllInputs(bitgoPsbt, rootWalletKeys, xprivs, replayProtectionKey) {
108
+ // Parse transaction to get input types
109
+ const parsed = bitgoPsbt.parseTransactionWithWalletKeys(rootWalletKeys, {
110
+ publicKeys: [replayProtectionKey],
111
+ });
112
+ // Generate MuSig2 nonces for user and backup keys (MuSig2 uses 2-of-2 with user+backup)
113
+ bitgoPsbt.generateMusig2Nonces(xprivs.user);
114
+ bitgoPsbt.generateMusig2Nonces(xprivs.bitgo);
115
+ // First pass: sign with user key (skip p2shP2pk inputs)
116
+ parsed.inputs.forEach((input, index) => {
117
+ switch (input.scriptType) {
118
+ case "p2shP2pk":
119
+ break;
120
+ default:
121
+ signInputAndVerify(bitgoPsbt, index, xprivs.user, "user", input.scriptType);
122
+ break;
123
+ }
124
+ });
125
+ // Second pass: sign with appropriate second key
126
+ parsed.inputs.forEach((input, index) => {
127
+ switch (input.scriptType) {
128
+ case "p2shP2pk":
129
+ signInputAndVerify(bitgoPsbt, index, replayProtectionKey, "replayProtection", input.scriptType);
130
+ break;
131
+ case "p2trMusig2ScriptPath":
132
+ // MuSig2 script path inputs use backup key for second signature
133
+ signInputAndVerify(bitgoPsbt, index, xprivs.backup, "backup", input.scriptType);
134
+ break;
135
+ default:
136
+ // Regular multisig uses bitgo key
137
+ signInputAndVerify(bitgoPsbt, index, xprivs.bitgo, "bitgo", input.scriptType);
138
+ break;
139
+ }
140
+ });
141
+ }
142
+ /**
143
+ * Run tests for a fixture: load PSBT, verify, sign, and verify again
144
+ * @param fixture - The test fixture
145
+ * @param networkName - The network name for deserializing the PSBT
146
+ * @param rootWalletKeys - Wallet keys for verification
147
+ * @param replayProtectionKey - Key for replay protection inputs
148
+ * @param xprivs - The xprivs to use for signing
149
+ * @param signatureStage - The current signing stage
150
+ */
151
+ function runTestsForFixture(fixture, networkName, rootWalletKeys, replayProtectionKey, xprivs, signatureStage) {
152
+ // Load PSBT from fixture
153
+ const bitgoPsbt = getBitGoPsbt(fixture, networkName);
154
+ // Verify current state
155
+ verifyAllInputSignatures(bitgoPsbt, fixture, rootWalletKeys, replayProtectionKey, signatureStage);
156
+ // Sign inputs (if not already fully signed)
157
+ if (signatureStage !== "unsigned") {
158
+ signAllInputs(bitgoPsbt, rootWalletKeys, xprivs, replayProtectionKey);
159
+ }
160
+ }
161
+ describe("verifySignature", function () {
162
+ const supportedNetworks = utxolib.getNetworkList().filter((network) => {
163
+ return (utxolib.isMainnet(network) &&
164
+ network !== utxolib.networks.bitcoincash &&
165
+ network !== utxolib.networks.bitcoingold &&
166
+ network !== utxolib.networks.bitcoinsv &&
167
+ network !== utxolib.networks.ecash &&
168
+ network !== utxolib.networks.zcash);
169
+ });
170
+ supportedNetworks.forEach((network) => {
171
+ const networkName = utxolib.getNetworkName(network);
172
+ describe(`network: ${networkName}`, function () {
173
+ let rootWalletKeys;
174
+ let replayProtectionKey;
175
+ let xprivs;
176
+ let unsignedFixture;
177
+ let halfsignedFixture;
178
+ let fullsignedFixture;
179
+ before(function () {
180
+ unsignedFixture = loadPsbtFixture(networkName, "unsigned");
181
+ halfsignedFixture = loadPsbtFixture(networkName, "halfsigned");
182
+ fullsignedFixture = loadPsbtFixture(networkName, "fullsigned");
183
+ rootWalletKeys = loadWalletKeysFromFixture(fullsignedFixture);
184
+ replayProtectionKey = loadReplayProtectionKeyFromFixture(fullsignedFixture);
185
+ xprivs = loadXprivsFromFixture(fullsignedFixture);
186
+ });
187
+ describe("unsigned PSBT", function () {
188
+ it("should return false for unsigned inputs, then sign and verify", function () {
189
+ runTestsForFixture(unsignedFixture, networkName, rootWalletKeys, replayProtectionKey, xprivs, "unsigned");
190
+ });
191
+ });
192
+ describe("half-signed PSBT", function () {
193
+ it("should return true for signed xpubs and false for unsigned, then sign and verify", function () {
194
+ runTestsForFixture(halfsignedFixture, networkName, rootWalletKeys, replayProtectionKey, xprivs, "halfsigned");
195
+ });
196
+ });
197
+ describe("fully signed PSBT", function () {
198
+ it("should have 2 signatures (2-of-3 multisig)", function () {
199
+ runTestsForFixture(fullsignedFixture, networkName, rootWalletKeys, replayProtectionKey, xprivs, "fullsigned");
200
+ });
201
+ });
202
+ describe("error handling", function () {
203
+ it("should throw error for out of bounds input index", function () {
204
+ const psbt = getBitGoPsbt(fullsignedFixture, networkName);
205
+ assert.throws(() => {
206
+ psbt.verifySignature(999, rootWalletKeys.userKey());
207
+ }, (error) => {
208
+ return error.message.includes("Input index 999 out of bounds");
209
+ }, "Should throw error for out of bounds input index");
210
+ });
211
+ it("should throw error for invalid xpub", function () {
212
+ const psbt = getBitGoPsbt(fullsignedFixture, networkName);
213
+ assert.throws(() => {
214
+ psbt.verifySignature(0, "invalid-xpub");
215
+ }, (error) => {
216
+ return error.message.includes("Invalid");
217
+ }, "Should throw error for invalid xpub");
218
+ });
219
+ it("should return false for xpub not in derivation path", function () {
220
+ const psbt = getBitGoPsbt(fullsignedFixture, networkName);
221
+ // Create a different xpub that's not in the wallet
222
+ // Use a proper 32-byte seed (256 bits)
223
+ const differentSeed = Buffer.alloc(32, 0xaa); // 32 bytes filled with 0xaa
224
+ const differentKey = BIP32.fromSeed(differentSeed);
225
+ const differentXpub = differentKey.neutered();
226
+ const result = psbt.verifySignature(0, differentXpub);
227
+ assert.strictEqual(result, false, "Should return false for xpub not in PSBT derivation paths");
228
+ });
229
+ it("should verify signature with raw public key (Uint8Array)", function () {
230
+ const psbt = getBitGoPsbt(fullsignedFixture, networkName);
231
+ // Verify that xpub-based verification works
232
+ const userKey = rootWalletKeys.userKey();
233
+ const hasXpubSig = psbt.verifySignature(0, userKey);
234
+ // This test specifically checks that raw public key verification works
235
+ // We test the underlying WASM API by ensuring both xpub and raw pubkey
236
+ // calls reach the correct methods
237
+ // Use a random public key that's not in the PSBT to test the API works
238
+ const randomSeed = Buffer.alloc(32, 0xcc);
239
+ const randomKey = BIP32.fromSeed(randomSeed);
240
+ const randomPubkey = randomKey.publicKey;
241
+ // This should return false (no signature for this key)
242
+ const result = psbt.verifySignature(0, randomPubkey);
243
+ assert.strictEqual(result, false, "Should return false for public key not in PSBT");
244
+ // Verify the xpub check still works (regression test)
245
+ assert.strictEqual(hasXpubSig, true, "Should still verify with xpub");
246
+ });
247
+ it("should return false for raw public key with no signature", function () {
248
+ const psbt = getBitGoPsbt(fullsignedFixture, networkName);
249
+ // Create a random public key that's not in the PSBT
250
+ const randomSeed = Buffer.alloc(32, 0xbb);
251
+ const randomKey = BIP32.fromSeed(randomSeed);
252
+ const randomPubkey = randomKey.publicKey;
253
+ const result = psbt.verifySignature(0, randomPubkey);
254
+ assert.strictEqual(result, false, "Should return false for public key not in PSBT signatures");
255
+ });
256
+ it("should throw error for invalid key length", function () {
257
+ const psbt = getBitGoPsbt(fullsignedFixture, networkName);
258
+ const invalidKey = Buffer.alloc(31); // Invalid length (should be 32 for private key or 33 for public key)
259
+ assert.throws(() => {
260
+ psbt.verifySignature(0, invalidKey);
261
+ }, (error) => {
262
+ return error.message.includes("Invalid key length");
263
+ }, "Should throw error for invalid key length");
264
+ });
265
+ });
266
+ });
267
+ });
268
+ });
@@ -0,0 +1,12 @@
1
+ import * as utxolib from "@bitgo/utxo-lib";
2
+ import type { Triple } from "../../js/triple.js";
3
+ /**
4
+ * Convert utxolib BIP32 keys to WASM wallet keys format (Triple<string>)
5
+ */
6
+ export declare function toWasmWalletKeys(keys: [utxolib.BIP32Interface, utxolib.BIP32Interface, utxolib.BIP32Interface]): Triple<string>;
7
+ /**
8
+ * Get standard replay protection configuration
9
+ */
10
+ export declare function getStandardReplayProtection(): {
11
+ outputScripts: Uint8Array[];
12
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Convert utxolib BIP32 keys to WASM wallet keys format (Triple<string>)
3
+ */
4
+ export function toWasmWalletKeys(keys) {
5
+ return [
6
+ keys[0].neutered().toBase58(),
7
+ keys[1].neutered().toBase58(),
8
+ keys[2].neutered().toBase58(),
9
+ ];
10
+ }
11
+ /**
12
+ * Get standard replay protection configuration
13
+ */
14
+ export function getStandardReplayProtection() {
15
+ const replayProtectionScript = Buffer.from("a91420b37094d82a513451ff0ccd9db23aba05bc5ef387", "hex");
16
+ return { outputScripts: [replayProtectionScript] };
17
+ }