quantumcoin 7.0.10 → 7.0.11

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 (172) hide show
  1. package/README-SDK.md +0 -5
  2. package/README.md +1 -3
  3. package/SPEC.md +2 -63
  4. package/config.js +10 -2
  5. package/examples/example.js +0 -5
  6. package/examples/example.ts +0 -5
  7. package/examples/node_modules/.bin/esbuild +16 -0
  8. package/examples/node_modules/.bin/esbuild.cmd +17 -0
  9. package/examples/node_modules/.bin/esbuild.ps1 +28 -0
  10. package/examples/node_modules/.bin/sdkgen +16 -0
  11. package/examples/node_modules/.bin/sdkgen.cmd +17 -0
  12. package/examples/node_modules/.bin/sdkgen.ps1 +28 -0
  13. package/examples/node_modules/.bin/tsx +16 -0
  14. package/examples/node_modules/.bin/tsx.cmd +17 -0
  15. package/examples/node_modules/.bin/tsx.ps1 +28 -0
  16. package/examples/node_modules/.package-lock.json +235 -0
  17. package/examples/node_modules/@esbuild/win32-x64/README.md +3 -0
  18. package/examples/node_modules/@esbuild/win32-x64/esbuild.exe +0 -0
  19. package/examples/node_modules/@esbuild/win32-x64/package.json +20 -0
  20. package/examples/node_modules/esbuild/LICENSE.md +21 -0
  21. package/examples/node_modules/esbuild/README.md +3 -0
  22. package/examples/node_modules/esbuild/bin/esbuild +223 -0
  23. package/examples/node_modules/esbuild/install.js +289 -0
  24. package/examples/node_modules/esbuild/lib/main.d.ts +716 -0
  25. package/examples/node_modules/esbuild/lib/main.js +2532 -0
  26. package/examples/node_modules/esbuild/package.json +49 -0
  27. package/examples/node_modules/get-tsconfig/LICENSE +21 -0
  28. package/examples/node_modules/get-tsconfig/README.md +235 -0
  29. package/examples/node_modules/get-tsconfig/dist/index.cjs +7 -0
  30. package/examples/node_modules/get-tsconfig/dist/index.d.cts +2088 -0
  31. package/examples/node_modules/get-tsconfig/dist/index.d.mts +2088 -0
  32. package/examples/node_modules/get-tsconfig/dist/index.mjs +7 -0
  33. package/examples/node_modules/get-tsconfig/package.json +46 -0
  34. package/examples/node_modules/quantum-coin-js-sdk/.github/workflows/publish-npmjs.yaml +22 -0
  35. package/examples/node_modules/quantum-coin-js-sdk/LICENSE +21 -0
  36. package/examples/node_modules/quantum-coin-js-sdk/LICENSE-wasm_exec.js.txt +30 -0
  37. package/examples/node_modules/quantum-coin-js-sdk/README.md +1665 -0
  38. package/examples/node_modules/quantum-coin-js-sdk/example/README.md +14 -0
  39. package/examples/node_modules/quantum-coin-js-sdk/example/conversion-example.js +19 -0
  40. package/examples/node_modules/quantum-coin-js-sdk/example/example-create-contract.js +396 -0
  41. package/examples/node_modules/quantum-coin-js-sdk/example/example-encode-decode-rlp.js +225 -0
  42. package/examples/node_modules/quantum-coin-js-sdk/example/example-event-pack-unpack.js +391 -0
  43. package/examples/node_modules/quantum-coin-js-sdk/example/example-misc.js +101 -0
  44. package/examples/node_modules/quantum-coin-js-sdk/example/example-rpc-send-signRawTransaction.js +318 -0
  45. package/examples/node_modules/quantum-coin-js-sdk/example/example-rpc-send.js +116 -0
  46. package/examples/node_modules/quantum-coin-js-sdk/example/example-send.js +70 -0
  47. package/examples/node_modules/quantum-coin-js-sdk/example/example-token-pack-unpack.js +961 -0
  48. package/examples/node_modules/quantum-coin-js-sdk/example/example-wallet-version4.js +35 -0
  49. package/examples/node_modules/quantum-coin-js-sdk/example/example-wallet.js +43 -0
  50. package/examples/node_modules/quantum-coin-js-sdk/example/example.js +405 -0
  51. package/examples/node_modules/quantum-coin-js-sdk/example/package-lock.json +134 -0
  52. package/examples/node_modules/quantum-coin-js-sdk/example/package.json +15 -0
  53. package/examples/node_modules/quantum-coin-js-sdk/index.d.ts +1024 -0
  54. package/examples/node_modules/quantum-coin-js-sdk/index.js +3062 -0
  55. package/examples/node_modules/quantum-coin-js-sdk/package.json +34 -0
  56. package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-32.json +1 -0
  57. package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-36.json +1 -0
  58. package/examples/node_modules/quantum-coin-js-sdk/tests/encrypted-48.json +1 -0
  59. package/examples/node_modules/quantum-coin-js-sdk/tests/generate-verify-vectors.js +91 -0
  60. package/examples/node_modules/quantum-coin-js-sdk/tests/non-transactional.preinit.test.js +41 -0
  61. package/examples/node_modules/quantum-coin-js-sdk/tests/non-transactional.test.js +686 -0
  62. package/examples/node_modules/quantum-coin-js-sdk/tests/sign-raw-keytype5-context-null.test.js +107 -0
  63. package/examples/node_modules/quantum-coin-js-sdk/tests/sign-raw-transaction.test.js +196 -0
  64. package/examples/node_modules/quantum-coin-js-sdk/tests/sign-verify.test.js +311 -0
  65. package/examples/node_modules/quantum-coin-js-sdk/tests/transactional.relay.test.js +131 -0
  66. package/examples/node_modules/quantum-coin-js-sdk/tests/transactional.rpc.test.js +103 -0
  67. package/examples/node_modules/quantum-coin-js-sdk/tests/verify-vectors.json +95035 -0
  68. package/examples/node_modules/quantum-coin-js-sdk/wasmBase64.d.ts +9 -0
  69. package/examples/node_modules/quantum-coin-js-sdk/wasmBase64.js +16 -0
  70. package/examples/node_modules/quantum-coin-js-sdk/wasm_exec.d.ts +0 -0
  71. package/examples/node_modules/quantum-coin-js-sdk/wasm_exec.js +587 -0
  72. package/examples/node_modules/resolve-pkg-maps/LICENSE +21 -0
  73. package/examples/node_modules/resolve-pkg-maps/README.md +216 -0
  74. package/examples/node_modules/resolve-pkg-maps/dist/index.cjs +1 -0
  75. package/examples/node_modules/resolve-pkg-maps/dist/index.d.cts +11 -0
  76. package/examples/node_modules/resolve-pkg-maps/dist/index.d.mts +11 -0
  77. package/examples/node_modules/resolve-pkg-maps/dist/index.mjs +1 -0
  78. package/examples/node_modules/resolve-pkg-maps/package.json +42 -0
  79. package/examples/node_modules/seed-words/.github/workflows/publish-npmjs.yaml +22 -0
  80. package/examples/node_modules/seed-words/BUILD.md +7 -0
  81. package/examples/node_modules/seed-words/LICENSE +121 -0
  82. package/examples/node_modules/seed-words/README.md +67 -0
  83. package/examples/node_modules/seed-words/dist/seedwords.d.ts +39 -0
  84. package/examples/node_modules/seed-words/package.json +27 -0
  85. package/examples/node_modules/seed-words/seedwords.js +315 -0
  86. package/examples/node_modules/seed-words/seedwords.txt +65536 -0
  87. package/examples/node_modules/seed-words/tsconfig.json +21 -0
  88. package/examples/node_modules/tsx/LICENSE +21 -0
  89. package/examples/node_modules/tsx/README.md +32 -0
  90. package/examples/node_modules/tsx/dist/cjs/api/index.cjs +1 -0
  91. package/examples/node_modules/tsx/dist/cjs/api/index.d.cts +35 -0
  92. package/examples/node_modules/tsx/dist/cjs/api/index.d.mts +35 -0
  93. package/examples/node_modules/tsx/dist/cjs/api/index.mjs +1 -0
  94. package/examples/node_modules/tsx/dist/cjs/index.cjs +1 -0
  95. package/examples/node_modules/tsx/dist/cjs/index.mjs +1 -0
  96. package/examples/node_modules/tsx/dist/cli.cjs +54 -0
  97. package/examples/node_modules/tsx/dist/cli.mjs +55 -0
  98. package/examples/node_modules/tsx/dist/client-BQVF1NaW.mjs +1 -0
  99. package/examples/node_modules/tsx/dist/client-D6NvIMSC.cjs +1 -0
  100. package/examples/node_modules/tsx/dist/esm/api/index.cjs +1 -0
  101. package/examples/node_modules/tsx/dist/esm/api/index.d.cts +35 -0
  102. package/examples/node_modules/tsx/dist/esm/api/index.d.mts +35 -0
  103. package/examples/node_modules/tsx/dist/esm/api/index.mjs +1 -0
  104. package/examples/node_modules/tsx/dist/esm/index.cjs +2 -0
  105. package/examples/node_modules/tsx/dist/esm/index.mjs +2 -0
  106. package/examples/node_modules/tsx/dist/get-pipe-path-BHW2eJdv.mjs +1 -0
  107. package/examples/node_modules/tsx/dist/get-pipe-path-BoR10qr8.cjs +1 -0
  108. package/examples/node_modules/tsx/dist/index-7AaEi15b.mjs +14 -0
  109. package/examples/node_modules/tsx/dist/index-BWFBUo6r.cjs +1 -0
  110. package/examples/node_modules/tsx/dist/index-gbaejti9.mjs +1 -0
  111. package/examples/node_modules/tsx/dist/index-gckBtVBf.cjs +14 -0
  112. package/examples/node_modules/tsx/dist/lexer-DQCqS3nf.mjs +3 -0
  113. package/examples/node_modules/tsx/dist/lexer-DgIbo0BU.cjs +3 -0
  114. package/examples/node_modules/tsx/dist/loader.cjs +1 -0
  115. package/examples/node_modules/tsx/dist/loader.mjs +1 -0
  116. package/examples/node_modules/tsx/dist/node-features-_8ZFwP_x.mjs +1 -0
  117. package/examples/node_modules/tsx/dist/node-features-roYmp9jK.cjs +1 -0
  118. package/examples/node_modules/tsx/dist/package-CeBgXWuR.mjs +1 -0
  119. package/examples/node_modules/tsx/dist/package-Dxt5kIHw.cjs +1 -0
  120. package/examples/node_modules/tsx/dist/patch-repl.cjs +1 -0
  121. package/examples/node_modules/tsx/dist/patch-repl.mjs +1 -0
  122. package/examples/node_modules/tsx/dist/preflight.cjs +1 -0
  123. package/examples/node_modules/tsx/dist/preflight.mjs +1 -0
  124. package/examples/node_modules/tsx/dist/register-2sWVXuRQ.cjs +1 -0
  125. package/examples/node_modules/tsx/dist/register-B7jrtLTO.mjs +1 -0
  126. package/examples/node_modules/tsx/dist/register-CFH5oNdT.mjs +4 -0
  127. package/examples/node_modules/tsx/dist/register-D46fvsV_.cjs +4 -0
  128. package/examples/node_modules/tsx/dist/repl.cjs +3 -0
  129. package/examples/node_modules/tsx/dist/repl.mjs +3 -0
  130. package/examples/node_modules/tsx/dist/require-D4F1Lv60.cjs +1 -0
  131. package/examples/node_modules/tsx/dist/require-DQxpCAr4.mjs +1 -0
  132. package/examples/node_modules/tsx/dist/suppress-warnings.cjs +1 -0
  133. package/examples/node_modules/tsx/dist/suppress-warnings.mjs +1 -0
  134. package/examples/node_modules/tsx/dist/temporary-directory-B83uKxJF.cjs +1 -0
  135. package/examples/node_modules/tsx/dist/temporary-directory-CwHp0_NW.mjs +1 -0
  136. package/examples/node_modules/tsx/dist/types-Cxp8y2TL.d.ts +5 -0
  137. package/examples/node_modules/tsx/package.json +68 -0
  138. package/examples/offline-signing.js +0 -2
  139. package/examples/offline-signing.ts +0 -1
  140. package/examples/package-lock.json +422 -73
  141. package/examples/package.json +1 -1
  142. package/examples/wallet-offline.js +1 -9
  143. package/examples/wallet-offline.ts +1 -9
  144. package/generate-sdk.js +4 -6
  145. package/package.json +2 -2
  146. package/src/abi/interface.js +13 -7
  147. package/src/abi/js-abi-coder.js +23 -18
  148. package/src/constants.d.ts +0 -5
  149. package/src/constants.js +0 -7
  150. package/src/contract/contract-factory.js +9 -3
  151. package/src/contract/contract.js +9 -3
  152. package/src/errors/index.js +12 -0
  153. package/src/index.d.ts +0 -3
  154. package/src/providers/extra-providers.js +20 -6
  155. package/src/providers/json-rpc-provider.js +15 -5
  156. package/src/providers/provider.d.ts +0 -2
  157. package/src/providers/provider.js +1 -3
  158. package/src/utils/address.d.ts +0 -14
  159. package/src/utils/address.js +12 -49
  160. package/src/utils/hashing.d.ts +0 -6
  161. package/src/utils/hashing.js +8 -23
  162. package/src/utils/index.d.ts +0 -3
  163. package/src/utils/rlp.js +7 -4
  164. package/src/wallet/wallet.d.ts +2 -13
  165. package/src/wallet/wallet.js +116 -97
  166. package/test/security/malformed-input.test.js +295 -1
  167. package/test/unit/address-wallet.test.js +188 -129
  168. package/test/unit/address-wallet.test.ts +187 -128
  169. package/test/unit/hashing.test.js +0 -11
  170. package/test/unit/hashing.test.ts +0 -11
  171. package/test/unit/providers.test.js +3 -1
  172. package/test/unit/providers.test.ts +3 -1
@@ -2,7 +2,8 @@
2
2
  * @testCategory security
3
3
  * @blockchainRequired false
4
4
  * @transactional false
5
- * @description Security tests for malformed input, edge cases, and invalid values
5
+ * @description Security tests for malformed input, edge cases, and invalid values.
6
+ * Covers all findings from the consolidated security audit.
6
7
  * Run with VERBOSE=1 for test names.
7
8
  */
8
9
 
@@ -35,3 +36,296 @@ describe("Security: Malformed Input", () => {
35
36
  });
36
37
  });
37
38
 
39
+ describe("Security: C1 - Private key enumeration protection", () => {
40
+ logSuite("Security: C1 - Private key enumeration protection");
41
+
42
+ it("JSON.stringify(wallet) must NOT contain privateKey or seed", async () => {
43
+ logTest("JSON.stringify(wallet) must NOT contain privateKey or seed", {});
44
+ await Initialize(null);
45
+ const w = qc.Wallet.createRandom();
46
+ const json = JSON.stringify(w);
47
+ const parsed = JSON.parse(json);
48
+
49
+ assert.ok(!("privateKey" in parsed), "privateKey must not appear in JSON");
50
+ assert.ok(!("seed" in parsed), "seed must not appear in JSON");
51
+ assert.ok(!("signingKey" in parsed), "signingKey must not appear in JSON");
52
+ assert.ok(!("_qcWallet" in parsed), "_qcWallet must not appear in JSON");
53
+ assert.ok(!("_seed" in parsed), "_seed must not appear in JSON");
54
+ assert.ok(!("privateKeyBytes" in parsed), "privateKeyBytes must not appear in JSON");
55
+
56
+ assert.ok("address" in parsed, "address should be in JSON");
57
+ });
58
+
59
+ it("Object.keys(wallet) must not include secret properties", async () => {
60
+ logTest("Object.keys(wallet) must not include secret properties", {});
61
+ await Initialize(null);
62
+ const w = qc.Wallet.createRandom();
63
+ const keys = Object.keys(w);
64
+
65
+ assert.ok(!keys.includes("privateKey"), "privateKey must not be enumerable");
66
+ assert.ok(!keys.includes("seed"), "seed must not be enumerable");
67
+ assert.ok(!keys.includes("signingKey"), "signingKey must not be enumerable");
68
+ assert.ok(!keys.includes("_qcWallet"), "_qcWallet must not be enumerable");
69
+ assert.ok(!keys.includes("_seed"), "_seed must not be enumerable");
70
+ });
71
+
72
+ it("privateKey and seed are still accessible directly", async () => {
73
+ logTest("privateKey and seed are still accessible directly", {});
74
+ await Initialize(null);
75
+ const w = qc.Wallet.createRandom();
76
+ assert.equal(typeof w.privateKey, "string");
77
+ assert.ok(w.privateKey.startsWith("0x"));
78
+ assert.equal(typeof w.seed, "string");
79
+ });
80
+ });
81
+
82
+ describe("Security: C3 - Wallet.connect() preserves state", () => {
83
+ logSuite("Security: C3 - Wallet.connect() preserves state");
84
+
85
+ it("connect() preserves seed", async () => {
86
+ logTest("connect() preserves seed", {});
87
+ await Initialize(null);
88
+ const w = qc.Wallet.createRandom();
89
+ const seedBefore = w.seed;
90
+
91
+ const provider = new qc.JsonRpcProvider("http://127.0.0.1:9999", 123123);
92
+ const connected = w.connect(provider);
93
+
94
+ assert.equal(connected.seed, seedBefore, "seed must survive connect()");
95
+ assert.equal(connected.address, w.address, "address must survive connect()");
96
+ });
97
+ });
98
+
99
+ describe("Security: C4 - KDF string password handling", () => {
100
+ logSuite("Security: C4 - KDF string password handling");
101
+
102
+ it("pbkdf2 with plain string password does not crash", async () => {
103
+ logTest("pbkdf2 with plain string password does not crash", {});
104
+ const result = qc.pbkdf2("password123", "salt123", 1000, 32, "sha256");
105
+ assert.equal(typeof result, "string");
106
+ assert.ok(result.startsWith("0x"));
107
+ });
108
+
109
+ it("computeHmac with plain string key/data does not crash", async () => {
110
+ logTest("computeHmac with plain string key/data does not crash", {});
111
+ const result = qc.computeHmac("sha256", "mykey", "mydata");
112
+ assert.equal(typeof result, "string");
113
+ assert.ok(result.startsWith("0x"));
114
+ });
115
+
116
+ it("scryptSync with plain string password does not crash", async () => {
117
+ logTest("scryptSync with plain string password does not crash", {});
118
+ const result = qc.scryptSync("password123", "salt123", 1024, 8, 1, 32);
119
+ assert.equal(typeof result, "string");
120
+ assert.ok(result.startsWith("0x"));
121
+ });
122
+ });
123
+
124
+ describe("Security: H2 - Numeric precision in signTransaction", () => {
125
+ logSuite("Security: H2 - Numeric precision in signTransaction");
126
+
127
+ it("rejects number value above MAX_SAFE_INTEGER", async () => {
128
+ logTest("rejects number value above MAX_SAFE_INTEGER", {});
129
+ await Initialize(null);
130
+ const w = qc.Wallet.createRandom();
131
+ await assert.rejects(
132
+ () => w.signTransaction({ to: w.address, value: Number.MAX_SAFE_INTEGER + 1, nonce: 0, chainId: 123123 }),
133
+ /overflow/i,
134
+ );
135
+ });
136
+
137
+ it("accepts bigint value for large amounts", async () => {
138
+ logTest("accepts bigint value for large amounts", {});
139
+ await Initialize(null);
140
+ const w = qc.Wallet.createRandom();
141
+ const raw = await w.signTransaction({
142
+ to: w.address,
143
+ value: 999999999999999999999n,
144
+ nonce: 0,
145
+ chainId: 123123,
146
+ });
147
+ assert.equal(typeof raw, "string");
148
+ assert.ok(raw.startsWith("0x"));
149
+ });
150
+
151
+ it("accepts string value '0x' as zero", async () => {
152
+ logTest("accepts string value '0x' as zero", {});
153
+ await Initialize(null);
154
+ const w = qc.Wallet.createRandom();
155
+ const raw = await w.signTransaction({ to: w.address, value: "0x", nonce: 0, chainId: 123123 });
156
+ assert.equal(typeof raw, "string");
157
+ });
158
+ });
159
+
160
+ describe("Security: M3 - Password strength enforcement", () => {
161
+ logSuite("Security: M3 - Password strength enforcement");
162
+
163
+ it("encryptSync rejects password shorter than 12 characters", async () => {
164
+ logTest("encryptSync rejects password shorter than 12 characters", {});
165
+ await Initialize(null);
166
+ const w = qc.Wallet.createRandom();
167
+ assert.throws(() => w.encryptSync("short"), /password must be at least 12 characters/);
168
+ });
169
+
170
+ it("encryptSync accepts password of 12+ characters", async () => {
171
+ logTest("encryptSync accepts password of 12+ characters", {});
172
+ await Initialize(null);
173
+ const w = qc.Wallet.createRandom();
174
+ const json = w.encryptSync("abcdefghijkl");
175
+ assert.equal(typeof json, "string");
176
+ assert.ok(json.length > 0);
177
+ });
178
+ });
179
+
180
+ describe("Security: M4 - Message signing removed", () => {
181
+ logSuite("Security: M4 - Message signing removed");
182
+
183
+ it("hashMessage is not exported", () => {
184
+ logTest("hashMessage is not exported", {});
185
+ assert.equal(qc.hashMessage, undefined, "hashMessage should not be exported");
186
+ });
187
+
188
+ it("verifyMessage is not exported", () => {
189
+ logTest("verifyMessage is not exported", {});
190
+ assert.equal(qc.verifyMessage, undefined, "verifyMessage should not be exported");
191
+ });
192
+
193
+ it("recoverAddress is not exported", () => {
194
+ logTest("recoverAddress is not exported", {});
195
+ assert.equal(qc.recoverAddress, undefined, "recoverAddress should not be exported");
196
+ });
197
+
198
+ it("MessagePrefix is not exported", () => {
199
+ logTest("MessagePrefix is not exported", {});
200
+ assert.equal(qc.MessagePrefix, undefined, "MessagePrefix should not be exported");
201
+ });
202
+ });
203
+
204
+ describe("Security: M6 - Error messages do not leak secrets", () => {
205
+ logSuite("Security: M6 - Error messages do not leak secrets");
206
+
207
+ it("fromKeys error does not contain actual key bytes", async () => {
208
+ logTest("fromKeys error does not contain actual key bytes", {});
209
+ await Initialize(null);
210
+ const fakeKey = "0xdeadbeef";
211
+ try {
212
+ qc.Wallet.fromKeys(fakeKey, fakeKey);
213
+ assert.fail("should have thrown");
214
+ } catch (e) {
215
+ assert.ok(!e.message.includes("deadbeef"), "error must not contain key material");
216
+ assert.ok(e.message.includes("[REDACTED]") || !e.message.includes("0x"), "value should be redacted");
217
+ }
218
+ });
219
+ });
220
+
221
+ describe("Security: M7 - _hexToBigInt handles '0x'", () => {
222
+ logSuite("Security: M7 - _hexToBigInt handles '0x'");
223
+
224
+ it("getBalance does not crash on '0x' response", async () => {
225
+ logTest("getBalance does not crash on '0x' response", {});
226
+ assert.equal(typeof qc.JsonRpcProvider, "function");
227
+ });
228
+ });
229
+
230
+ describe("Security: L3 - RLP depth limit", () => {
231
+ logSuite("Security: L3 - RLP depth limit");
232
+
233
+ it("rejects deeply nested RLP (depth > 64)", () => {
234
+ logTest("rejects deeply nested RLP (depth > 64)", {});
235
+ let inner = [];
236
+ for (let i = 0; i < 70; i++) {
237
+ inner = [inner];
238
+ }
239
+ const encoded = qc.encodeRlp(inner);
240
+ assert.throws(() => qc.decodeRlp(encoded), /maximum nesting depth exceeded/);
241
+ });
242
+
243
+ it("accepts RLP within depth limit", () => {
244
+ logTest("accepts RLP within depth limit", {});
245
+ const encoded = qc.encodeRlp([["hello", "world"], "test"]);
246
+ const decoded = qc.decodeRlp(encoded);
247
+ assert.ok(Array.isArray(decoded));
248
+ });
249
+ });
250
+
251
+ describe("Security: M2 - Seed phrase with invalid words", () => {
252
+ logSuite("Security: M2 - Seed phrase with invalid words");
253
+
254
+ it("rejects gibberish words of correct count", async () => {
255
+ logTest("rejects gibberish words of correct count", {});
256
+ await Initialize(null);
257
+ const gibberish = new Array(32).fill("xyzzyplugh");
258
+ assert.throws(
259
+ () => qc.Wallet.fromPhrase(gibberish),
260
+ { message: /failed/i },
261
+ );
262
+ });
263
+
264
+ it("rejects empty string words of correct count", async () => {
265
+ logTest("rejects empty string words of correct count", {});
266
+ await Initialize(null);
267
+ const empty = new Array(32).fill("");
268
+ assert.throws(() => qc.Wallet.fromPhrase(empty));
269
+ });
270
+ });
271
+
272
+ describe("Security: L6 - Keccak-256 test vectors", () => {
273
+ logSuite("Security: L6 - Keccak-256 test vectors");
274
+
275
+ it("keccak256 of empty bytes matches known digest", async () => {
276
+ logTest("keccak256 of empty bytes matches known digest", {});
277
+ const result = qc.keccak256(new Uint8Array(0));
278
+ assert.equal(result, "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
279
+ });
280
+
281
+ it("keccak256 of 'hello' matches known digest", async () => {
282
+ logTest("keccak256 of 'hello' matches known digest", {});
283
+ const bytes = new TextEncoder().encode("hello");
284
+ const result = qc.keccak256(bytes);
285
+ assert.equal(result, "0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8");
286
+ });
287
+
288
+ it("sha256 produces correct length output", () => {
289
+ logTest("sha256 produces correct length output", {});
290
+ const result = qc.sha256(new Uint8Array([1, 2, 3]));
291
+ assert.equal(typeof result, "string");
292
+ assert.equal(result.length, 66);
293
+ });
294
+ });
295
+
296
+ describe("Security: H5 - BigInt in provider params", () => {
297
+ logSuite("Security: H5 - BigInt in provider params");
298
+
299
+ it("JsonRpcProvider serializes BigInt params without crashing", async (t) => {
300
+ logTest("JsonRpcProvider serializes BigInt params without crashing", {});
301
+ if (typeof fetch !== "function") {
302
+ t.skip("global fetch not available");
303
+ return;
304
+ }
305
+ const originalFetch = fetch;
306
+ global.fetch = async (_url, init) => {
307
+ const body = JSON.parse(init.body);
308
+ assert.equal(body.params[0].value, "0x3e8");
309
+ const responseBody = JSON.stringify({ jsonrpc: "2.0", id: body.id, result: "0x1" });
310
+ return { ok: true, text: async () => responseBody, json: async () => JSON.parse(responseBody) };
311
+ };
312
+ try {
313
+ const p = new qc.JsonRpcProvider("http://example.invalid", 123123);
314
+ await p._perform("eth_call", [{ to: "0x" + "00".repeat(32), value: 1000n }]);
315
+ } finally {
316
+ global.fetch = originalFetch;
317
+ }
318
+ });
319
+ });
320
+
321
+ describe("Security: H6 - Per-instance RPC IDs", () => {
322
+ logSuite("Security: H6 - Per-instance RPC IDs");
323
+
324
+ it("different provider instances have independent ID counters", () => {
325
+ logTest("different provider instances have independent ID counters", {});
326
+ const p1 = new qc.JsonRpcProvider("http://a.invalid", 1);
327
+ const p2 = new qc.JsonRpcProvider("http://b.invalid", 1);
328
+ assert.equal(p1._rpcId, 1);
329
+ assert.equal(p2._rpcId, 1);
330
+ });
331
+ });
@@ -29,7 +29,7 @@ describe("Address + Wallet (offline)", () => {
29
29
  const TEST_WALLET_ADDRESS = "0x1a846abe71c8b989e8337c55d608be81c28ab3b2e40c83eaa2a68d516049aec6";
30
30
 
31
31
  // ---------------------------------------------------------------------------
32
- // Hardcoded seed words (generated once via quantum-coin-js-sdk.newWalletSeed()).
32
+ // Hardcoded seed words (generated once via quantum-coin-js-sdk.newWalletSeedWords()).
33
33
  // WARNING: test-only seed words; never use for real funds.
34
34
  // ---------------------------------------------------------------------------
35
35
  const TEST_SEED_WORDS = [
@@ -174,69 +174,6 @@ describe("Address + Wallet (offline)", () => {
174
174
  assert.throws(() => qc.Wallet.fromKeys(new Uint8Array(1), new Uint8Array(0)), /publicKey must not be empty/);
175
175
  });
176
176
 
177
- it("fromKeys wallet can sign messages", async () => {
178
- await Initialize(null);
179
- const original = qc.Wallet.fromPhrase(TEST_SEED_WORDS);
180
- const restored = qc.Wallet.fromKeys(
181
- original.signingKey.privateKeyBytes,
182
- original.signingKey.publicKeyBytes,
183
- );
184
- assert.equal(restored.address, original.address);
185
-
186
- const msg = "fromKeys signing test";
187
- const sig = restored.signMessageSync(msg);
188
- assert.equal(typeof sig, "string");
189
- assert.ok(sig.startsWith("0x"));
190
- const recovered = qc.verifyMessage(msg, sig);
191
- assert.equal(recovered, original.address.toLowerCase());
192
- });
193
-
194
- it("signMessageSync + verifyMessage roundtrip (known wallet)", async () => {
195
- await Initialize(null);
196
- const wallet = qc.Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE);
197
- const sig = wallet.signMessageSync("Hello, QuantumCoin!");
198
- assert.equal(typeof sig, "string");
199
- assert.ok(sig.startsWith("0x"));
200
- const recovered = qc.verifyMessage("Hello, QuantumCoin!", sig);
201
- assert.equal(recovered, wallet.address.toLowerCase());
202
- });
203
-
204
- it("signMessageSync returns combined signature hex and verifyMessage roundtrip (fromPhrase wallet)", async () => {
205
- await Initialize(null);
206
- const wallet = qc.Wallet.fromPhrase(TEST_SEED_WORDS);
207
- const msg = "test message";
208
- const sig = wallet.signMessageSync(msg);
209
- assert.equal(typeof sig, "string");
210
- assert.ok(sig.startsWith("0x"));
211
- assert.ok(sig.length > 4);
212
- const recovered = qc.verifyMessage(msg, sig);
213
- assert.equal(recovered, wallet.address.toLowerCase());
214
- });
215
-
216
- it("signMessageSync + verifyMessage roundtrip (32-word phrase wallet)", async () => {
217
- await Initialize(null);
218
- const wallet = qc.Wallet.fromPhrase(TEST_SEED_WORDS_32);
219
- const msg = "hello 32";
220
- const sig = wallet.signMessageSync(msg);
221
- assert.equal(typeof sig, "string");
222
- assert.ok(sig.startsWith("0x"));
223
- const recovered = qc.verifyMessage(msg, sig);
224
- assert.equal(recovered, wallet.address.toLowerCase());
225
- assert.equal(recovered, TEST_SEED_ADDRESS_32.toLowerCase());
226
- });
227
-
228
- it("signMessageSync + verifyMessage roundtrip (36-word phrase wallet)", async () => {
229
- await Initialize(null);
230
- const wallet = qc.Wallet.fromPhrase(TEST_SEED_WORDS_36);
231
- const msg = "hello 36";
232
- const sig = wallet.signMessageSync(msg);
233
- assert.equal(typeof sig, "string");
234
- assert.ok(sig.startsWith("0x"));
235
- const recovered = qc.verifyMessage(msg, sig);
236
- assert.equal(recovered, wallet.address.toLowerCase());
237
- assert.equal(recovered, TEST_SEED_ADDRESS_36.toLowerCase());
238
- });
239
-
240
177
  it("signTransaction works offline and returns raw tx hex", async () => {
241
178
  await Initialize(null);
242
179
  const wallet = qc.Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE);
@@ -399,22 +336,16 @@ describe("Address + Wallet (offline)", () => {
399
336
  assert.equal(qc.isAddress(w.address), true);
400
337
  });
401
338
 
402
- it("createRandom(null, 3) creates wallet with keyType 3 and sign/verify roundtrip", async () => {
339
+ it("createRandom(null, 3) creates wallet with keyType 3", async () => {
403
340
  await Initialize(null);
404
341
  const w = qc.Wallet.createRandom(null, 3);
405
342
  assert.equal(qc.isAddress(w.address), true);
406
- const sig = w.signMessageSync("kt3 test");
407
- assert.ok(sig.startsWith("0x"));
408
- assert.equal(qc.verifyMessage("kt3 test", sig), w.address.toLowerCase());
409
343
  });
410
344
 
411
- it("createRandom(null, 5) creates wallet with keyType 5 and sign/verify roundtrip", async () => {
345
+ it("createRandom(null, 5) creates wallet with keyType 5", async () => {
412
346
  await Initialize(null);
413
347
  const w = qc.Wallet.createRandom(null, 5);
414
348
  assert.equal(qc.isAddress(w.address), true);
415
- const sig = w.signMessageSync("kt5 test");
416
- assert.ok(sig.startsWith("0x"));
417
- assert.equal(qc.verifyMessage("kt5 test", sig), w.address.toLowerCase());
418
349
  });
419
350
 
420
351
  it("createRandom(null, 3) signTransaction works offline", async () => {
@@ -458,70 +389,30 @@ describe("Address + Wallet (offline)", () => {
458
389
  assert.throws(() => qc.Wallet.createRandom(null, true), /keyType must be null, 3, or 5/);
459
390
  });
460
391
 
461
- // ---------------------------------------------------------------------------
462
- // createRandomSeed
463
- // ---------------------------------------------------------------------------
464
-
465
- it("createRandomSeed() returns 32-word array (default keyType)", async () => {
466
- await Initialize(null);
467
- const words = qc.Wallet.createRandomSeed();
468
- assert.equal(Array.isArray(words), true);
469
- assert.equal(words.length, 32);
470
- assert.ok(words.every((w) => typeof w === "string" && w.length > 0));
471
- });
472
-
473
- it("createRandomSeed(3) returns 32-word array", async () => {
474
- await Initialize(null);
475
- const words = qc.Wallet.createRandomSeed(3);
476
- assert.equal(words.length, 32);
477
- });
478
-
479
- it("createRandomSeed(5) returns 36-word array", async () => {
480
- await Initialize(null);
481
- const words = qc.Wallet.createRandomSeed(5);
482
- assert.equal(words.length, 36);
483
- });
484
-
485
- it("createRandomSeed roundtrip via fromPhrase produces valid signing wallet", async () => {
486
- await Initialize(null);
487
- const words = qc.Wallet.createRandomSeed(3);
488
- const w = qc.Wallet.fromPhrase(words);
489
- assert.equal(qc.isAddress(w.address), true);
490
- const sig = w.signMessageSync("seed roundtrip");
491
- assert.equal(qc.verifyMessage("seed roundtrip", sig), w.address.toLowerCase());
492
- });
493
-
494
- it("createRandomSeed rejects invalid keyType", async () => {
495
- await Initialize(null);
496
- assert.throws(() => qc.Wallet.createRandomSeed(1), /keyType must be null, 3, or 5/);
497
- assert.throws(() => qc.Wallet.createRandomSeed(2), /keyType must be null, 3, or 5/);
498
- assert.throws(() => qc.Wallet.createRandomSeed(4), /keyType must be null, 3, or 5/);
499
- });
500
-
501
392
  // ---------------------------------------------------------------------------
502
393
  // fromSeed
503
394
  // ---------------------------------------------------------------------------
504
395
 
505
- it("fromSeed roundtrip: createRandomSeed(3) -> fromPhrase -> fromSeed produces same address", async () => {
396
+ it("fromSeed roundtrip: createRandom(keyType 3) seed -> fromSeed produces same address", async () => {
506
397
  await Initialize(null);
507
- const seedwords = require("seed-words");
508
- const words = qc.Wallet.createRandomSeed(3);
509
- const seedArr = seedwords.getSeedArrayFromWordList(words);
510
- assert.equal(seedArr.length, 64);
511
- const wFromWords = qc.Wallet.fromPhrase(words);
512
- const wFromSeed = qc.Wallet.fromSeed(Array.from(seedArr));
513
- assert.equal(wFromSeed.address, wFromWords.address);
398
+ const w = qc.Wallet.createRandom(null, 3);
399
+ assert.notEqual(w.seed, null);
400
+ const { hexToBytes } = require("../../src/internal/hex");
401
+ const seedBytes = Array.from(hexToBytes(w.seed));
402
+ assert.equal(seedBytes.length, 64);
403
+ const wFromSeed = qc.Wallet.fromSeed(seedBytes);
404
+ assert.equal(wFromSeed.address, w.address);
514
405
  });
515
406
 
516
- it("fromSeed roundtrip: createRandomSeed(5) -> fromPhrase -> fromSeed produces same address", async () => {
407
+ it("fromSeed roundtrip: createRandom(keyType 5) seed -> fromSeed produces same address", async () => {
517
408
  await Initialize(null);
518
- const seedwords = require("seed-words");
519
- const words = qc.Wallet.createRandomSeed(5);
520
- const seedArr = seedwords.getSeedArrayFromWordList(words);
521
- assert.equal(seedArr.length, 72);
522
- const wFromWords = qc.Wallet.fromPhrase(words);
523
- const wFromSeed = qc.Wallet.fromSeed(Array.from(seedArr));
524
- assert.equal(wFromSeed.address, wFromWords.address);
409
+ const w = qc.Wallet.createRandom(null, 5);
410
+ assert.notEqual(w.seed, null);
411
+ const { hexToBytes } = require("../../src/internal/hex");
412
+ const seedBytes = Array.from(hexToBytes(w.seed));
413
+ assert.equal(seedBytes.length, 72);
414
+ const wFromSeed = qc.Wallet.fromSeed(seedBytes);
415
+ assert.equal(wFromSeed.address, w.address);
525
416
  });
526
417
 
527
418
  it("fromSeed rejects non-array input", async () => {
@@ -648,7 +539,175 @@ describe("Address + Wallet (offline)", () => {
648
539
  it("encryptSeedSync rejects password shorter than 12 characters", async () => {
649
540
  await Initialize(null);
650
541
  const seed = new Array(64).fill(1);
651
- assert.throws(() => qc.Wallet.encryptSeedSync(seed, "short"), /serializeSeedAsEncryptedWallet failed/);
542
+ assert.throws(() => qc.Wallet.encryptSeedSync(seed, "short"), /password must be at least 12 characters/);
543
+ });
544
+
545
+ // ---------------------------------------------------------------------------
546
+ // publicKey getter
547
+ // ---------------------------------------------------------------------------
548
+
549
+ it("publicKey getter returns hex matching signingKey.publicKeyBytes", async () => {
550
+ await Initialize(null);
551
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS_32);
552
+ const expected = "0x" + Buffer.from(w.signingKey.publicKeyBytes).toString("hex");
553
+ assert.equal(w.publicKey, expected);
554
+ assert.equal((w.publicKey.length - 2) / 2, 1408);
555
+ });
556
+
557
+ it("publicKey getter works for keyType 5 (2688-byte public key)", async () => {
558
+ await Initialize(null);
559
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS_36);
560
+ const expected = "0x" + Buffer.from(w.signingKey.publicKeyBytes).toString("hex");
561
+ assert.equal(w.publicKey, expected);
562
+ assert.equal((w.publicKey.length - 2) / 2, 2688);
563
+ });
564
+
565
+ it("publicKey getter works for createRandom wallet", async () => {
566
+ await Initialize(null);
567
+ const w = qc.Wallet.createRandom();
568
+ assert.equal(typeof w.publicKey, "string");
569
+ assert.ok(w.publicKey.startsWith("0x"));
570
+ assert.equal(w.publicKey, "0x" + Buffer.from(w.signingKey.publicKeyBytes).toString("hex"));
571
+ });
572
+
573
+ // ---------------------------------------------------------------------------
574
+ // seed getter
575
+ // ---------------------------------------------------------------------------
576
+
577
+ it("seed is non-null hex for fromPhrase(32-word)", async () => {
578
+ await Initialize(null);
579
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS_32);
580
+ assert.equal(typeof w.seed, "string");
581
+ assert.ok(w.seed.startsWith("0x"));
582
+ assert.equal((w.seed.length - 2) / 2, 64);
583
+ assert.equal(w.seed, "0x319fda8ec642b6b649d805770647d82aa4377ced5c51e4e39c0026bd983ad7b150fc475633d246216ac8b81af68bf929bf68a3fd151a2b6c925ef3cc70ecdb8b");
584
+ });
585
+
586
+ it("seed is non-null hex for fromPhrase(36-word)", async () => {
587
+ await Initialize(null);
588
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS_36);
589
+ assert.equal(typeof w.seed, "string");
590
+ assert.equal((w.seed.length - 2) / 2, 72);
591
+ assert.equal(w.seed, "0x319fda8ec642b6b649d805770647d82aa4377ced5c51e4e39c0026bd983ad7b150fc475633d246216ac8b81af68bf929bf68a3fd151a2b6c925ef3cc70ecdb8bdaf9e0ff4c96cb07");
592
+ });
593
+
594
+ it("seed is non-null hex for fromPhrase(48-word)", async () => {
595
+ await Initialize(null);
596
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS);
597
+ assert.equal(typeof w.seed, "string");
598
+ assert.equal((w.seed.length - 2) / 2, 96);
599
+ assert.equal(w.seed, "0x319fda8ec642b6b649d805770647d82aa4377ced5c51e4e39c0026bd983ad7b150fc475633d246216ac8b81af68bf929bf68a3fd151a2b6c925ef3cc70ecdb8bdaf9e0ff4c96cb076c776546d970e1be70a962a868df0eeba1c076a780cb4c3b");
600
+ });
601
+
602
+ it("seed is non-null for fromSeed() and matches fromPhrase() with same words", async () => {
603
+ await Initialize(null);
604
+ const seedwords = require("seed-words");
605
+ const seedArr = seedwords.getSeedArrayFromWordList(TEST_SEED_WORDS_32);
606
+ const wSeed = qc.Wallet.fromSeed(Array.from(seedArr));
607
+ const wPhrase = qc.Wallet.fromPhrase(TEST_SEED_WORDS_32);
608
+ assert.equal(wSeed.seed, wPhrase.seed);
609
+ assert.equal(wSeed.address, wPhrase.address);
610
+ });
611
+
612
+ it("seed is non-null for createRandom() (seed-derived)", async () => {
613
+ await Initialize(null);
614
+ const w = qc.Wallet.createRandom();
615
+ assert.notEqual(w.seed, null);
616
+ assert.equal(typeof w.seed, "string");
617
+ assert.ok(w.seed.startsWith("0x"));
618
+ assert.equal((w.seed.length - 2) / 2, 64);
619
+ });
620
+
621
+ it("seed is non-null for createRandom(null, 5) (seed-derived, keyType 5)", async () => {
622
+ await Initialize(null);
623
+ const w = qc.Wallet.createRandom(null, 5);
624
+ assert.notEqual(w.seed, null);
625
+ assert.equal((w.seed.length - 2) / 2, 72);
626
+ });
627
+
628
+ it("seed is null for fromKeys()", async () => {
629
+ await Initialize(null);
630
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS_32);
631
+ const wk = qc.Wallet.fromKeys(w.signingKey.privateKeyBytes, w.signingKey.publicKeyBytes);
632
+ assert.equal(wk.seed, null);
633
+ assert.equal(wk.address, w.address);
634
+ });
635
+
636
+ it("seed is null for fromEncryptedJsonSync() with v3 JSON", async () => {
637
+ await Initialize(null);
638
+ const w = qc.Wallet.fromEncryptedJsonSync(TEST_WALLET_ENCRYPTED_JSON, TEST_WALLET_PASSPHRASE);
639
+ assert.equal(w.seed, null);
640
+ });
641
+
642
+ it("seed is null for fromEncryptedJsonSync() with v4 JSON", async () => {
643
+ await Initialize(null);
644
+ const w32 = qc.Wallet.fromEncryptedJsonSync(TEST_ENCRYPTED_JSON_32, PASSPHRASE_PHRASE);
645
+ assert.equal(w32.seed, null);
646
+ const w36 = qc.Wallet.fromEncryptedJsonSync(TEST_ENCRYPTED_JSON_36, PASSPHRASE_PHRASE);
647
+ assert.equal(w36.seed, null);
648
+ const w48 = qc.Wallet.fromEncryptedJsonSync(TEST_ENCRYPTED_JSON_48, PASSPHRASE_PHRASE);
649
+ assert.equal(w48.seed, null);
650
+ });
651
+
652
+ // ---------------------------------------------------------------------------
653
+ // seed roundtrip through encryptSync / fromEncryptedJsonSync
654
+ // ---------------------------------------------------------------------------
655
+
656
+ it("seed roundtrips through encryptSync + fromEncryptedJsonSync for fromPhrase wallet", async () => {
657
+ await Initialize(null);
658
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS_32);
659
+ const json = w.encryptSync(PASSPHRASE_PHRASE);
660
+ assert.equal(JSON.parse(json).version, 5);
661
+ const restored = qc.Wallet.fromEncryptedJsonSync(json, PASSPHRASE_PHRASE);
662
+ assert.equal(restored.seed, w.seed);
663
+ assert.equal(restored.address, w.address);
664
+ assert.equal(restored.privateKey, w.privateKey);
665
+ assert.equal(restored.publicKey, w.publicKey);
666
+ });
667
+
668
+ it("seed roundtrips through encryptSync + fromEncryptedJsonSync for createRandom wallet", async () => {
669
+ await Initialize(null);
670
+ const w = qc.Wallet.createRandom();
671
+ const json = w.encryptSync(PASSPHRASE_PHRASE);
672
+ assert.equal(JSON.parse(json).version, 5);
673
+ const restored = qc.Wallet.fromEncryptedJsonSync(json, PASSPHRASE_PHRASE);
674
+ assert.equal(restored.seed, w.seed);
675
+ assert.equal(restored.address, w.address);
676
+ });
677
+
678
+ it("seed roundtrips through encryptSeedSync + fromEncryptedJsonSync", async () => {
679
+ await Initialize(null);
680
+ const seedwords = require("seed-words");
681
+ const seedArr = seedwords.getSeedArrayFromWordList(TEST_SEED_WORDS_32);
682
+ const seedHex = "0x" + Buffer.from(new Uint8Array(seedArr)).toString("hex");
683
+ const json = qc.Wallet.encryptSeedSync(Array.from(seedArr), PASSPHRASE_PHRASE);
684
+ const restored = qc.Wallet.fromEncryptedJsonSync(json, PASSPHRASE_PHRASE);
685
+ assert.equal(restored.seed, seedHex);
686
+ assert.equal(restored.address, TEST_SEED_ADDRESS_32);
687
+ });
688
+
689
+ // ---------------------------------------------------------------------------
690
+ // encryptSync version behavior
691
+ // ---------------------------------------------------------------------------
692
+
693
+ it("encryptSync on seed-bearing wallet produces version 5 JSON", async () => {
694
+ await Initialize(null);
695
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS_32);
696
+ assert.notEqual(w.seed, null);
697
+ const json = w.encryptSync(PASSPHRASE_PHRASE);
698
+ assert.equal(JSON.parse(json).version, 5);
699
+ });
700
+
701
+ it("encryptSync on non-seed wallet (fromKeys) uses fallback path", async () => {
702
+ await Initialize(null);
703
+ const w = qc.Wallet.fromPhrase(TEST_SEED_WORDS_32);
704
+ const wk = qc.Wallet.fromKeys(w.signingKey.privateKeyBytes, w.signingKey.publicKeyBytes);
705
+ assert.equal(wk.seed, null);
706
+ const json = wk.encryptSync(PASSPHRASE_PHRASE);
707
+ const parsed = JSON.parse(json);
708
+ assert.ok(parsed.version === 3 || parsed.version === 4);
709
+ const restored = qc.Wallet.fromEncryptedJsonSync(json, PASSPHRASE_PHRASE);
710
+ assert.equal(restored.address, wk.address);
652
711
  });
653
712
 
654
713
  // ---------------------------------------------------------------------------