@heliuslabs/zolana-web-prover 0.2.0-alpha → 0.3.0-alpha

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 (38) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +29 -11
  3. package/dist/abortable.d.ts +2 -0
  4. package/dist/abortable.js +30 -0
  5. package/dist/errors.js +38 -0
  6. package/dist/index.d.ts +2 -1
  7. package/dist/index.js +5 -909
  8. package/dist/key-digests.js +49 -0
  9. package/dist/key-store.d.ts +67 -0
  10. package/dist/key-store.js +318 -0
  11. package/dist/measurement.js +0 -0
  12. package/dist/proof-requests.js +68 -0
  13. package/dist/prover-fetch.d.ts +18 -0
  14. package/dist/prover-fetch.js +74 -0
  15. package/dist/prover.worker.js +147 -0
  16. package/dist/proving-threads.js +10 -0
  17. package/dist/runtime/accelerator/LICENSE-APACHE +201 -0
  18. package/dist/runtime/accelerator/LICENSE-MIT +19 -0
  19. package/dist/runtime/accelerator/gnark_kernel.d.ts +88 -0
  20. package/dist/runtime/accelerator/gnark_kernel.js +455 -0
  21. package/dist/runtime/accelerator/gnark_kernel_bg.wasm +0 -0
  22. package/dist/runtime/accelerator/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.js +107 -0
  23. package/dist/runtime/runtime-manifest.json +49 -0
  24. package/dist/{dist/bridge → runtime}/zolana-prover.wasm +0 -0
  25. package/dist/scripts/assets.mjs +70 -0
  26. package/dist/scripts/install-keys.mjs +46 -0
  27. package/dist/shapes.d.ts +15 -9
  28. package/dist/shapes.js +86 -0
  29. package/dist/wasm-prover.d.ts +16 -17
  30. package/dist/wasm-prover.js +383 -0
  31. package/package.json +7 -5
  32. package/dist/assets/prover.worker-sn1Mixyo.js +0 -637
  33. package/dist/dist/bridge/manifest.json +0 -23
  34. package/dist/dist/bridge/transfer-2x2.json +0 -1
  35. package/dist/runtime.lock.json +0 -14
  36. package/dist/scripts/setup-assets.mjs +0 -216
  37. package/dist/wasm/prover/provingkeys/proving-keys.lock +0 -531
  38. /package/dist/{dist/bridge → vendor}/wasm_exec.js +0 -0
package/dist/index.js CHANGED
@@ -1,909 +1,5 @@
1
- //#region src/key-digests.ts
2
- /** The pinned key set's version folder on the proving-keys CDN. */
3
- var KEY_SET = "3a1c88f90f609eb1";
4
- /** Where the pinned key set is served from; the default `keyBaseUrl`. */
5
- var KEY_BASE_URL = "https://d3gbdb0egjwcw9.cloudfront.net/proving-keys/3a1c88f90f609eb1/";
6
- /** Size and sha256 of every proving key the browser prover can load. */
7
- var KEY_DIGESTS = Object.freeze({
8
- "merge_24_1.key": {
9
- size: 173052578,
10
- sha256: "a0dfaa1e73d2d9e1159d2ebab82baa6d0602482d75f24dcbff596322dc389c88"
11
- },
12
- "merge_54_1.key": {
13
- size: 379646246,
14
- sha256: "951ec83dc53d112a46a1eb9cd308e6abd933291eb3109647a479e2321fce7423"
15
- },
16
- "merge_8_1.key": {
17
- size: 55767611,
18
- sha256: "f59571876452541eaf20cfc583a082b9b6cc788bf85719bd337398b2d962ad23"
19
- },
20
- "transfer_confidential_1_16.key": {
21
- size: 18119894,
22
- sha256: "aecae052aeb709ef4656d6da110bd1403d21bc5891e60691ee9cbc0ebb46dcb9"
23
- },
24
- "transfer_confidential_1_2.key": {
25
- size: 9268057,
26
- sha256: "7ef987966b0dfba5d86eab7c67068be00dbe581d823d45cdbbf6d338bfacb640"
27
- },
28
- "transfer_confidential_1_4.key": {
29
- size: 11320903,
30
- sha256: "94bddced9e46a0e0c4fbf6bc59c6d083e1d577a60bf4d8520129f41505f4f1dd"
31
- },
32
- "transfer_confidential_1_8.key": {
33
- size: 13695140,
34
- sha256: "3471ab2908cbe15b757164db5bb7c7f82a89036c557fc26fe66a7148a4338f58"
35
- },
36
- "transfer_confidential_12_2.key": {
37
- size: 90894233,
38
- sha256: "3415363ec3ae33b730c7445aeb7850aa1bc044443e6ef8a7731e2c56fd1df6e7"
39
- },
40
- "transfer_confidential_12_4.key": {
41
- size: 91932405,
42
- sha256: "36519896a5e5b4d1ef008d6883548944027eb21ea3e6bc72ba00fde9ea9efa4a"
43
- },
44
- "transfer_confidential_12_8.key": {
45
- size: 94355203,
46
- sha256: "131973e42165b1222ecfd2692796cfb035d5aa4a08ce7bf5e545795283ee344d"
47
- },
48
- "transfer_confidential_16_2.key": {
49
- size: 114870607,
50
- sha256: "27370f44f8b9c0c05f9d2c6738ab5294582fd40c869114baf0d7fb15e6d08e2a"
51
- },
52
- "transfer_confidential_16_4.key": {
53
- size: 115913395,
54
- sha256: "7cdef518b59307d578c2eecb71e811945ee56b1906283e8c70fe0924343dfb2a"
55
- },
56
- "transfer_confidential_16_8.key": {
57
- size: 118361316,
58
- sha256: "d8052824a163d41ddf8540bc72dbfe94b5c52be21b6a0f3039b2f01f7380641f"
59
- },
60
- "transfer_confidential_2_16.key": {
61
- size: 26327780,
62
- sha256: "27ef8be4d400bb9c724f79a4928f7f6ff4dc95a970f17c365b38430267986bfd"
63
- },
64
- "transfer_confidential_2_2.key": {
65
- size: 16407295,
66
- sha256: "e810d52962f4558b0bd50b768a89490759e15a0623d4c2c43318f541b8cc61ff"
67
- },
68
- "transfer_confidential_2_4.key": {
69
- size: 17416909,
70
- sha256: "c93338402cdc8fd8ec2f70f2de9fa29ee3e35b064ca803f8ad4d18070e4861fe"
71
- },
72
- "transfer_confidential_2_8.key": {
73
- size: 21887446,
74
- sha256: "a38085e7a38b725909e7fe81be544c6e5a1b4ec3561cc07f941eac15825c578a"
75
- },
76
- "transfer_confidential_24_2.key": {
77
- size: 179892108,
78
- sha256: "7b98beac6e0f89d568a30f569c690267bc37fcb8472ad88c292b89433c2f91d9"
79
- },
80
- "transfer_confidential_24_4.key": {
81
- size: 180955692,
82
- sha256: "4e3c664a197a7b2cc5616b7c01dc463e92fa666f2990ee42ceb2d7ae81f1ece4"
83
- },
84
- "transfer_confidential_3_2.key": {
85
- size: 24424541,
86
- sha256: "0537e6669e6dae3304648bd427bf3f9ec3e5aaa66134d15e0ff142794f2e966b"
87
- },
88
- "transfer_confidential_3_4.key": {
89
- size: 25433887,
90
- sha256: "4710053ed9a08908c419ede4c9a3138d9f78e6d185c6846a2a9c2f4f846c0d4d"
91
- },
92
- "transfer_confidential_3_8.key": {
93
- size: 27804471,
94
- sha256: "e559f7256c67be5dc857f601b22c0e929ba615f91032da09db32c00cd98255d1"
95
- },
96
- "transfer_confidential_32_2.key": {
97
- size: 228023589,
98
- sha256: "70f4a61a248d240ca6b20730e6ab8a88acb9f4153c1e4688f6861bf6339a5a0b"
99
- },
100
- "transfer_confidential_4_16.key": {
101
- size: 38204323,
102
- sha256: "917cf5f0976d245b08756b1147e04a1f0924c5f5b800d57bd57d305b43e8f432"
103
- },
104
- "transfer_confidential_4_2.key": {
105
- size: 30346432,
106
- sha256: "aea0c474e4c67499c45f11f30d2d03e53ee5dd7c6b7940a0f61cfefa2e61e38e"
107
- },
108
- "transfer_confidential_4_4.key": {
109
- size: 31361454,
110
- sha256: "6d83eef0b16f03b8f2b2a469441d67264bc34932d8b261f344f5bb39f29a1fbe"
111
- },
112
- "transfer_confidential_4_8.key": {
113
- size: 33727897,
114
- sha256: "27fe418512ee0be5cb2f82290eab4ab20ed2d32549e1d8d7bfa1fa5b45df62e9"
115
- },
116
- "transfer_confidential_40_2.key": {
117
- size: 275193373,
118
- sha256: "530779155340a05cbd90dedc5e94c5960cf5105f2866bdb041e3a56d06c9bb8b"
119
- },
120
- "transfer_confidential_48_2.key": {
121
- size: 356237189,
122
- sha256: "4738674f40f19233f1963954ae4f96a763d11f1adcd82b4828ca8c420cc5b049"
123
- },
124
- "transfer_confidential_49_2.key": {
125
- size: 362114005,
126
- sha256: "e39b30ba966198a03158276989d91f1b2fb05f695e56550fec6452d8a3110b6b"
127
- },
128
- "transfer_confidential_5_16.key": {
129
- size: 48520274,
130
- sha256: "1faf95183f53bd29610ebda52703244ef63f45a2d26706e88bfd3197132bc20e"
131
- },
132
- "transfer_confidential_5_2.key": {
133
- size: 36449879,
134
- sha256: "ae6699a19d810a7e0ef663dff3fc32e0b8e4bdeae9089c583746f95ae3b0e1e9"
135
- },
136
- "transfer_confidential_5_4.key": {
137
- size: 37464519,
138
- sha256: "6779a069f3abc85f853b754ff2b58021532d26fe9642eb9ba42744da91733517"
139
- },
140
- "transfer_confidential_5_8.key": {
141
- size: 44025900,
142
- sha256: "2806aff6bc7bd34233a2ff42a42e5524c680930944c3fc84d6d58e773c033cb5"
143
- },
144
- "transfer_confidential_6_2.key": {
145
- size: 46571502,
146
- sha256: "fe62dff0397662cb25d89ac9de976cc3d23adfd2b7a72f5f8ad8671d677da605"
147
- },
148
- "transfer_confidential_6_4.key": {
149
- size: 47591772,
150
- sha256: "116a3d4030047d182eedcc9effa66dd9c66ed9bd3ad9d82c2242dc47eef1f244"
151
- },
152
- "transfer_confidential_6_8.key": {
153
- size: 49957349,
154
- sha256: "79281967e4aac47dc187d3a6555ab54800cef3928e5696969dcf4afb69b339da"
155
- },
156
- "transfer_confidential_8_16.key": {
157
- size: 66535995,
158
- sha256: "8adc64d3886619e3b8e65b3b0c98eaac61bfdfbc8a51aa6ba05166b853feb328"
159
- },
160
- "transfer_confidential_8_2.key": {
161
- size: 58604067,
162
- sha256: "24250fd74a86f1a07847e9dbb769cf9b2bebf20d29f18018186ea7900f69bbe4"
163
- },
164
- "transfer_confidential_8_4.key": {
165
- size: 59617404,
166
- sha256: "82975fe1145c827a76f8b76e229bb9c8501f993b1a4f7709446b7717f30bd07a"
167
- },
168
- "transfer_confidential_8_8.key": {
169
- size: 62007104,
170
- sha256: "f8972d03f57baef87702aaddb87a3b831d2377723e930fb25675fe3e473d8aec"
171
- }
172
- });
173
- //#endregion
174
- //#region src/shapes.ts
175
- /**
176
- * The transfer circuit shapes and the proving keys that back them.
177
- *
178
- * The shape list mirrors `SPP_SUPPORTED_SHAPES` in the SDK, `SupportedShapes` in
179
- * `prover-test/spp/protocol/shape.go`, and the on-chain verifier -- cheapest
180
- * first, so the order doubles as the smallest-fit search order.
181
- *
182
- * Key sizes come from `key-digests.ts`, generated from `proving-keys.lock`, which
183
- * is also what a downloaded key is validated against.
184
- */
185
- function pinnedSize(keyFile) {
186
- const digest = KEY_DIGESTS[keyFile];
187
- if (digest === void 0) throw new Error(`${keyFile} is not in the pinned key set`);
188
- return digest.size;
189
- }
190
- function entry(inputs, outputs) {
191
- const keyFile = `transfer_confidential_${String(inputs)}_${String(outputs)}.key`;
192
- return Object.freeze({
193
- inputs,
194
- outputs,
195
- label: `${String(inputs)}x${String(outputs)}`,
196
- keyFile,
197
- keyBytes: pinnedSize(keyFile)
198
- });
199
- }
200
- /** Confidential (default transact) rail only, ring rail keys are out of scope. */
201
- var TRANSFER_SHAPES = Object.freeze([
202
- entry(1, 2),
203
- entry(1, 4),
204
- entry(1, 8),
205
- entry(2, 2),
206
- entry(2, 4),
207
- entry(1, 16),
208
- entry(2, 8),
209
- entry(3, 2),
210
- entry(3, 4),
211
- entry(2, 16),
212
- entry(3, 8),
213
- entry(4, 2),
214
- entry(4, 4),
215
- entry(4, 8),
216
- entry(5, 2),
217
- entry(5, 4),
218
- entry(4, 16),
219
- entry(5, 8),
220
- entry(6, 2),
221
- entry(6, 4),
222
- entry(5, 16),
223
- entry(6, 8),
224
- entry(8, 2),
225
- entry(8, 4),
226
- entry(8, 8),
227
- entry(8, 16),
228
- entry(12, 2),
229
- entry(12, 4),
230
- entry(12, 8),
231
- entry(16, 2),
232
- entry(16, 4),
233
- entry(16, 8),
234
- entry(24, 2),
235
- entry(24, 4),
236
- entry(32, 2),
237
- entry(40, 2),
238
- entry(48, 2),
239
- entry(49, 2)
240
- ]);
241
- function mergeEntry(inputs) {
242
- const keyFile = `merge_${String(inputs)}_1.key`;
243
- return Object.freeze({
244
- inputs,
245
- outputs: 1,
246
- label: `merge ${String(inputs)}x1`,
247
- keyFile,
248
- keyBytes: pinnedSize(keyFile)
249
- });
250
- }
251
- /** The smallest merge circuit. */
252
- var MERGE_SHAPE = mergeEntry(8);
253
- /** The merge circuits, by input count, mirroring `SupportedInputCounts` in `spp_merge/shared`. */
254
- var MERGE_SHAPES = Object.freeze([
255
- MERGE_SHAPE,
256
- mergeEntry(24),
257
- mergeEntry(54)
258
- ]);
259
- var MERGE_KEY_FILE = MERGE_SHAPE.keyFile;
260
- var MERGE_KEY_BYTES = MERGE_SHAPE.keyBytes;
261
- /**
262
- * The proving key a `/prove` request needs, resolved from the request itself
263
- * rather than guessed ahead of time.
264
- *
265
- * The protocol derives the shape from the real input/output counts with a
266
- * smallest-fit rule, so a caller cannot reliably predict which key a given
267
- * transfer will use; reading it off the request is the only way to be right.
268
- * Mirrors the server's LazyKeyManager cache key.
269
- */
270
- function keyForProveRequest(circuitType, inputs, outputs) {
271
- if (circuitType === "merge") return MERGE_SHAPES.find((shape) => shape.inputs === inputs && shape.outputs === outputs);
272
- if (circuitType !== "transfer-confidential") return void 0;
273
- return TRANSFER_SHAPES.find((shape) => shape.inputs === inputs && shape.outputs === outputs);
274
- }
275
- /** Smallest shape that holds `inputs`/`outputs`, matching `CanonicalShape`. */
276
- function canonicalShape(inputs, outputs) {
277
- const found = TRANSFER_SHAPES.find((shape) => inputs <= shape.inputs && outputs <= shape.outputs);
278
- if (found === void 0) throw new Error(`no supported shape holds ${String(inputs)}x${String(outputs)}`);
279
- return found;
280
- }
281
- function shapeByLabel(label) {
282
- const found = TRANSFER_SHAPES.find((shape) => shape.label === label);
283
- if (found === void 0) throw new Error(`unknown shape ${label}`);
284
- return found;
285
- }
286
- function formatBytes(bytes) {
287
- if (bytes < 1024) return `${String(bytes)} B`;
288
- if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
289
- return `${(bytes / 1048576).toFixed(1)} MB`;
290
- }
291
- //#endregion
292
- //#region src/abortable.ts
293
- /** Race work against cancellation even when an I/O implementation ignores signals. */
294
- function abortable(work, signal) {
295
- if (signal === void 0) return work;
296
- return new Promise((resolve, reject) => {
297
- const abort = () => reject(signal.reason);
298
- const cleanup = () => signal.removeEventListener("abort", abort);
299
- work.then((value) => {
300
- cleanup();
301
- if (signal.aborted) reject(signal.reason);
302
- else resolve(value);
303
- }, (error) => {
304
- cleanup();
305
- reject(signal.aborted ? signal.reason : error);
306
- });
307
- if (signal.aborted) abort();
308
- else signal.addEventListener("abort", abort, { once: true });
309
- });
310
- }
311
- //#endregion
312
- //#region src/errors.ts
313
- var messages = {
314
- wasm_prover_error: "Prover operation failed",
315
- wasm_init_failed: "Prover initialization failed; check runtime assets, threads and isolation headers",
316
- wasm_worker_failed: "Prover worker failed",
317
- wasm_worker_not_started: "Prover worker is not started",
318
- wasm_worker_terminated: "Prover worker terminated",
319
- wasm_invalid_response: "Invalid prover response",
320
- wasm_invalid_request: "Unsupported circuit or malformed request",
321
- wasm_key_manifest_error: "The proving key is not in the pinned key set",
322
- wasm_key_download_failed: "Proving-key download failed",
323
- wasm_key_digest_mismatch: "Proving key does not match the lockfile; stage the pinned assets",
324
- wasm_key_load_failed: "Proving-key loading failed",
325
- wasm_prove_failed: "Proof generation failed",
326
- wasm_verify_failed: "Native gnark verification rejected the proof"
327
- };
328
- var WasmProverError = class extends Error {
329
- code;
330
- constructor(code = "wasm_prover_error") {
331
- const safeCode = Object.hasOwn(messages, code) ? code : "wasm_prover_error";
332
- super(messages[safeCode]);
333
- this.name = "WasmProverError";
334
- this.code = safeCode;
335
- }
336
- };
337
- function operationError(kind) {
338
- switch (kind) {
339
- case "init": return new WasmProverError("wasm_init_failed");
340
- case "loadKey": return new WasmProverError("wasm_key_load_failed");
341
- case "prove": return new WasmProverError("wasm_prove_failed");
342
- case "verify": return new WasmProverError("wasm_verify_failed");
343
- default: return new WasmProverError("wasm_worker_failed");
344
- }
345
- }
346
- //#endregion
347
- //#region src/proving-threads.ts
348
- /**
349
- * Starting point for the arithmetic pool, using the browser's available logical
350
- * processors. Cap large machines at the 18-worker configuration benchmarked by
351
- * this demo. This is a default, not a measurement of the fastest pool size.
352
- */
353
- function automaticProvingThreads(reported = globalThis.navigator?.hardwareConcurrency) {
354
- if (reported === void 0 || !Number.isFinite(reported) || reported < 1) return 4;
355
- return Math.min(18, Math.floor(reported));
356
- }
357
- //#endregion
358
- //#region src/proof-requests.ts
359
- /** How `url` addresses the prover at `proverUrl`; undefined if it is not a proving route. */
360
- function proveRoute(url, proverUrl) {
361
- const endpoint = new URL(proverUrl);
362
- const prove = `${endpoint.pathname.replace(/\/+$/u, "")}/prove`;
363
- if (url.origin !== endpoint.origin) return void 0;
364
- if (url.pathname === prove) return { kind: "prove" };
365
- if (!url.pathname.startsWith(`${prove}/`)) return void 0;
366
- const key = url.pathname.slice(prove.length + 1);
367
- return /^[a-z0-9_-]+_\d+_\d+$/u.test(key) ? {
368
- kind: "prove",
369
- keyFile: `${key}.key`
370
- } : { kind: "unsupported" };
371
- }
372
- /** Observe only public circuit metadata; never retain the witness body. */
373
- function observeProofRequests(fetch, proverUrl, onShape) {
374
- return async (input, init) => {
375
- if (proveRoute(new URL(input instanceof Request ? input.url : String(input)), proverUrl)?.kind === "prove") {
376
- const signal = init?.signal !== void 0 ? init.signal ?? void 0 : input instanceof Request ? input.signal : void 0;
377
- signal?.throwIfAborted();
378
- const body = typeof init?.body === "string" ? init.body : input instanceof Request ? await abortable(input.clone().text(), signal) : void 0;
379
- if (body !== void 0) {
380
- const shape = proofRequestShape(body);
381
- if (shape !== void 0) onShape(shape);
382
- }
383
- }
384
- return fetch(input, init);
385
- };
386
- }
387
- function proofRequestShape(body) {
388
- try {
389
- const parsed = JSON.parse(body);
390
- if (typeof parsed !== "object" || parsed === null || !("circuitType" in parsed) || typeof parsed.circuitType !== "string") return void 0;
391
- if (parsed.circuitType === "merge") {
392
- if (!("inputs" in parsed) || !Array.isArray(parsed.inputs)) return void 0;
393
- return keyForProveRequest(parsed.circuitType, parsed.inputs.length, 1);
394
- }
395
- if (!("nInputs" in parsed) || !("nOutputs" in parsed) || typeof parsed.nInputs !== "number" || typeof parsed.nOutputs !== "number") return void 0;
396
- return keyForProveRequest(parsed.circuitType, parsed.nInputs, parsed.nOutputs);
397
- } catch {
398
- return;
399
- }
400
- }
401
- //#endregion
402
- //#region src/wasm-prover.ts
403
- /**
404
- * Local proving in the browser, wired into the SDK without patching it.
405
- *
406
- * `ProverClient` takes an injectable `fetch`, and the wasm module's `prove`
407
- * accepts and returns exactly the JSON a `POST /prove/<key>` exchange uses (it mirrors
408
- * `server.processProofSync`). So the whole integration is a `fetch` that
409
- * recognizes the prover URL and answers it from wasm instead of the network.
410
- * Everything else -- indexer calls, Solana RPC -- falls through untouched.
411
- *
412
- * Mopro supplies the threaded Rust arithmetic kernel; Zolana retains its
413
- * native witness and proof formats.
414
- */
415
- /**
416
- * Owns the worker, the key cache, and the `fetch` shim.
417
- *
418
- * Proving keys are cached in the Cache API rather than memory: they are 9-380 MB
419
- * each and immutable (the lockfile pins their sha256 and the CloudFront prefix
420
- * is version-hashed), so re-downloading one per page load is pure waste. After
421
- * a shape's first load the cache keeps the bridge's raw re-encoding instead of
422
- * the pinned file: the pinned file's compressed points take about 240 ms per MB
423
- * to decompress in wasm on every load, and the raw form skips that.
424
- */
425
- var WasmProver = class {
426
- #worker;
427
- #factory;
428
- #initialization;
429
- #lifetime = new AbortController();
430
- /** The encoding of the raw keys the started worker's bridge exports and accepts. */
431
- #rawKeyFormat = "";
432
- #cachePrepared = false;
433
- #nextId = 1;
434
- #queue = Promise.resolve();
435
- #threads = 0;
436
- get threads() {
437
- return this.#threads;
438
- }
439
- #enqueue(operation, external) {
440
- const lifetime = this.#lifetime.signal;
441
- const signal = external === void 0 ? lifetime : AbortSignal.any([lifetime, external]);
442
- const next = this.#queue.then(async () => {
443
- signal.throwIfAborted();
444
- const abort = () => {
445
- if (!lifetime.aborted) this.#fail(signal.reason);
446
- };
447
- signal.addEventListener("abort", abort, { once: true });
448
- try {
449
- return await abortable(operation(signal), signal);
450
- } finally {
451
- signal.removeEventListener("abort", abort);
452
- }
453
- });
454
- this.#queue = next.catch(() => {});
455
- return abortable(next, signal);
456
- }
457
- #pending = /* @__PURE__ */ new Map();
458
- #loaded = /* @__PURE__ */ new Set();
459
- #options;
460
- #fetch;
461
- constructor(options) {
462
- this.#options = options;
463
- this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
464
- this.#factory = options.workerFactory ?? createProverWorker;
465
- }
466
- /**
467
- * Starts the worker and instantiates the module.
468
- *
469
- * The module must run in a worker: Go's js/wasm runtime shares the thread it
470
- * is instantiated on, and `groth16.Prove` blocks for seconds, so on the main
471
- * thread it would freeze the page for the whole proof.
472
- */
473
- start(source = this.#factory) {
474
- if (this.#initialization !== void 0) return this.#initialization;
475
- if (source === void 0) return Promise.reject(new WasmProverError("wasm_worker_not_started"));
476
- this.#factory = typeof source === "function" ? source : void 0;
477
- let worker;
478
- try {
479
- worker = typeof source === "function" ? source() : source;
480
- } catch {
481
- return Promise.reject(new WasmProverError("wasm_init_failed"));
482
- }
483
- this.#worker = worker;
484
- worker.addEventListener("message", (event) => {
485
- if (this.#worker !== worker) return;
486
- const response = event.data;
487
- if ("fatal" in response) {
488
- this.#fail(new WasmProverError("wasm_worker_failed"));
489
- return;
490
- }
491
- const pending = this.#pending.get(response.id);
492
- if (pending === void 0) return;
493
- this.#pending.delete(response.id);
494
- pending.resolve(response);
495
- });
496
- worker.addEventListener("error", (event) => {
497
- event.preventDefault();
498
- if (this.#worker !== worker) return;
499
- this.#fail(new WasmProverError("wasm_worker_failed"));
500
- });
501
- worker.addEventListener("messageerror", () => {
502
- if (this.#worker === worker) this.#fail(new WasmProverError("wasm_invalid_response"));
503
- });
504
- const initialization = this.#call({
505
- kind: "init",
506
- wasmUrl: this.#options.wasmUrl,
507
- threads: this.#options.threads ?? automaticProvingThreads()
508
- }).then((ready) => {
509
- if (this.#worker !== worker) throw new WasmProverError("wasm_worker_terminated");
510
- const value = ready.value;
511
- if (typeof value !== "object" || value === null || !("threads" in value) || typeof value.threads !== "number" || !("rawKeyFormat" in value) || typeof value.rawKeyFormat !== "string") throw new WasmProverError("wasm_invalid_response");
512
- this.#threads = value.threads;
513
- this.#rawKeyFormat = value.rawKeyFormat;
514
- }).catch((error) => {
515
- if (this.#worker === worker || this.#initialization === initialization) this.#fail(error);
516
- throw error;
517
- });
518
- this.#initialization = initialization;
519
- return initialization;
520
- }
521
- async #ensureStarted(signal) {
522
- signal.throwIfAborted();
523
- if (this.#initialization !== void 0) await abortable(this.#initialization, signal);
524
- else if (this.#factory !== void 0) await abortable(this.start(this.#factory), signal);
525
- else throw new WasmProverError("wasm_worker_not_started");
526
- signal.throwIfAborted();
527
- }
528
- /** Downloads (or reads from cache) and deserializes one shape's proving key. */
529
- async ensureKey(shape, signal) {
530
- return this.#enqueue((active) => this.#loadKey(shape, active), signal);
531
- }
532
- /**
533
- * Downloads, checks and decodes `shapes` ahead of the proofs that need them, so
534
- * a first proof doesn't wait seconds for its key. With the Cache API each
535
- * shape's decoded form is kept and later loads take a fraction of a second;
536
- * shapes already kept are skipped. Each shape is queued on its own, so a proof
537
- * requested meanwhile runs after the shape in progress. Aborting skips the
538
- * remaining shapes; aborting the one in progress restarts the worker. Without
539
- * the Cache API only the last shape stays loaded.
540
- */
541
- async prefetch(options) {
542
- for (const shape of options.shapes) await this.#enqueue(async (active) => {
543
- if (!await this.#rawKeyKept(shape, active)) await this.#loadKey(shape, active, options.onProgress);
544
- }, options.signal);
545
- }
546
- /** Whether the cache holds this shape's raw form; the bytes are checked when loaded. */
547
- async #rawKeyKept(shape, signal) {
548
- await this.#ensureStarted(signal);
549
- const expected = KEY_DIGESTS[shape.keyFile];
550
- const cache = await this.#openKeyCache(signal);
551
- if (expected === void 0 || cache === void 0) return false;
552
- const url = this.#keyUrl(shape);
553
- const hit = await optionalCache(() => cache.match(rawKeyUrl(url)), signal);
554
- return hit?.headers.get(rawSourceHeader) === expected.sha256 && hit.headers.get(rawFormatHeader) === this.#rawKeyFormat;
555
- }
556
- async #loadKey(shape, signal, onProgress) {
557
- await this.#ensureStarted(signal);
558
- if (this.#loaded.has(shape.keyFile)) {
559
- this.#options.onMeasurement?.({
560
- step: "key-load",
561
- ms: 0,
562
- bytes: shape.keyBytes,
563
- note: `${shape.keyFile} already deserialized in this instance`
564
- });
565
- return;
566
- }
567
- const url = this.#keyUrl(shape);
568
- const expected = KEY_DIGESTS[shape.keyFile];
569
- if (expected === void 0) throw new WasmProverError("wasm_key_manifest_error");
570
- const cache = await this.#openKeyCache(signal);
571
- const progress = (stage, loaded) => {
572
- const update = {
573
- keyFile: shape.keyFile,
574
- stage,
575
- loaded,
576
- total: expected.size
577
- };
578
- this.#options.onKeyProgress?.(update);
579
- onProgress?.(update);
580
- };
581
- const downloaded = this.#options.onKeyProgress === void 0 && onProgress === void 0 ? void 0 : (loaded) => progress("download", loaded);
582
- const started = performance.now();
583
- let key = await this.#cachedRawKey(cache, url, expected, signal) ?? await this.#pinnedKey(cache, url, expected, signal, downloaded);
584
- signal.throwIfAborted();
585
- this.#options.onMeasurement?.({
586
- step: "key-fetch",
587
- ms: performance.now() - started,
588
- bytes: key.bytes.byteLength,
589
- note: `${shape.keyFile} from ${key.raw ? "the raw-key cache" : key.source}`
590
- });
591
- signal.throwIfAborted();
592
- this.#loaded.clear();
593
- progress("decode", expected.size);
594
- let response;
595
- try {
596
- response = await this.#installKey(shape, key, expected, cache);
597
- } catch (error) {
598
- if (!key.raw) throw error;
599
- signal.throwIfAborted();
600
- await optionalCache(() => cache?.delete(rawKeyUrl(url)), signal);
601
- key = await this.#pinnedKey(cache, url, expected, signal, downloaded);
602
- signal.throwIfAborted();
603
- response = await this.#installKey(shape, key, expected, cache);
604
- }
605
- signal.throwIfAborted();
606
- const info = response.value;
607
- this.#options.onMeasurement?.({
608
- step: "key-load",
609
- ms: response.ms,
610
- bytes: shape.keyBytes,
611
- note: info?.nbPublic === void 0 ? shape.keyFile : `${shape.keyFile} as ${String(info.key)} nbPublic=${String(info.nbPublic)} nbSecret=${String(info.nbSecret)}`
612
- });
613
- signal.throwIfAborted();
614
- const raw = info?.raw;
615
- if (cache !== void 0 && raw instanceof Uint8Array && raw.buffer instanceof ArrayBuffer) await this.#storeRawKey(cache, url, expected, new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength), signal);
616
- this.#loaded.clear();
617
- this.#loaded.add(shape.keyFile);
618
- }
619
- #keyUrl(shape) {
620
- return `${(this.#options.keyBaseUrl ?? "https://d3gbdb0egjwcw9.cloudfront.net/proving-keys/3a1c88f90f609eb1/").replace(/\/+$/u, "")}/${shape.keyFile}`;
621
- }
622
- /**
623
- * Opens this key set's cache. The first call also deletes the stores of other
624
- * key sets, so a rotation frees its keys at once, and asks for persistence
625
- * when the caller opted in.
626
- */
627
- async #openKeyCache(signal) {
628
- if (!this.#cachePrepared) {
629
- this.#cachePrepared = true;
630
- const names = await optionalCache(() => globalThis.caches?.keys(), signal);
631
- for (const name of names ?? []) if (name.startsWith(keyCachePrefix) && name !== keyCacheName) await optionalCache(() => globalThis.caches?.delete(name), signal);
632
- if (this.#options.persistStorage === true) await optionalCache(() => globalThis.navigator?.storage?.persist(), signal);
633
- }
634
- return optionalCache(() => globalThis.caches?.open(keyCacheName), signal);
635
- }
636
- #installKey(shape, key, expected, cache) {
637
- const exportRaw = !key.raw && cache !== void 0;
638
- return this.#call({
639
- kind: "loadKey",
640
- fileName: shape.keyFile,
641
- key: key.bytes,
642
- sha256: expected.sha256,
643
- exportRaw
644
- }, [key.bytes]);
645
- }
646
- /**
647
- * The raw re-encoding cached by an earlier load, if it was derived from this
648
- * pinned key by a bridge writing the same format and is intact.
649
- */
650
- async #cachedRawKey(cache, url, expected, signal) {
651
- if (cache === void 0) return void 0;
652
- const hit = await optionalCache(() => cache.match(rawKeyUrl(url)), signal);
653
- if (hit === void 0) return void 0;
654
- const digest = hit.headers.get(rawDigestHeader);
655
- if (hit.headers.get(rawSourceHeader) === expected.sha256 && hit.headers.get(rawFormatHeader) === this.#rawKeyFormat && digest !== null) {
656
- const bytes = await optionalCache(() => hit.arrayBuffer(), signal);
657
- if (bytes !== void 0 && await abortable(sha256Hex(bytes), signal) === digest) return {
658
- bytes,
659
- raw: true,
660
- source: "cache"
661
- };
662
- }
663
- await optionalCache(() => cache.delete(rawKeyUrl(url)), signal);
664
- }
665
- async #pinnedKey(cache, url, expected, signal, downloaded) {
666
- if (cache !== void 0) {
667
- const hit = await optionalCache(() => cache.match(url), signal);
668
- if (hit !== void 0) {
669
- const cached = await optionalCache(() => hit.arrayBuffer(), signal);
670
- if (cached !== void 0 && await abortable(matchesDigest(cached, expected), signal)) return {
671
- bytes: cached,
672
- raw: false,
673
- source: "cache"
674
- };
675
- await optionalCache(() => cache.delete(url), signal);
676
- }
677
- }
678
- const response = await abortable(this.#fetch(url, { signal }), signal);
679
- if (!response.ok) throw new WasmProverError("wasm_key_download_failed");
680
- const bytes = await abortable(readKey(response, expected.size, downloaded), signal);
681
- if (!await abortable(matchesDigest(bytes, expected), signal)) throw new WasmProverError("wasm_key_digest_mismatch");
682
- if (cache !== void 0) await optionalCache(() => cache.put(url, new Response(bytes.slice(0))), signal);
683
- return {
684
- bytes,
685
- raw: false,
686
- source: "network"
687
- };
688
- }
689
- /** Keeps the raw form in place of the pinned file, which is no longer read. */
690
- async #storeRawKey(cache, url, expected, raw, signal) {
691
- const digest = await abortable(sha256Hex(raw), signal);
692
- const headers = {
693
- "content-type": "application/octet-stream",
694
- [rawSourceHeader]: expected.sha256,
695
- [rawFormatHeader]: this.#rawKeyFormat,
696
- [rawDigestHeader]: digest
697
- };
698
- if (await optionalCache(async () => {
699
- await cache.put(rawKeyUrl(url), new Response(raw, { headers }));
700
- return true;
701
- }, signal) === true) await optionalCache(() => cache.delete(url), signal);
702
- }
703
- /**
704
- * A `fetch` for `ZolanaClientConfig.fetch`. Proof requests to `/prove/<key>`
705
- * are answered from wasm, other paths below `/prove` are refused, and
706
- * everything else is delegated, so one shim covers the client.
707
- */
708
- createFetch() {
709
- return async (input, init) => {
710
- const url = requestUrl(input);
711
- const route = url === void 0 ? void 0 : proveRoute(url, this.#options.proverUrl);
712
- if (route === void 0) return await this.#fetch(input, init);
713
- const signal = init?.signal !== void 0 ? init.signal ?? void 0 : input instanceof Request ? input.signal : void 0;
714
- signal?.throwIfAborted();
715
- try {
716
- if (route.kind === "unsupported") throw new WasmProverError("wasm_invalid_request");
717
- const body = await abortable(readBody(input, init), signal);
718
- if (body === void 0) throw new WasmProverError("wasm_invalid_request");
719
- if (route.keyFile !== void 0 && proofRequestShape(body)?.keyFile !== route.keyFile) throw new WasmProverError("wasm_invalid_request");
720
- const result = await this.proveRequest(body, signal);
721
- signal?.throwIfAborted();
722
- return new Response(result.proof, {
723
- status: 200,
724
- headers: { "content-type": "application/json" }
725
- });
726
- } catch (error) {
727
- rethrowAbort(error, signal);
728
- return proverErrorResponse(error, this.#options.onMeasurement);
729
- }
730
- };
731
- }
732
- /**
733
- * Prove and verify one request locally, keeping key switching atomic.
734
- * Aborting queued work leaves the active request alone. Aborting active work
735
- * discards the runtime and rejects its other outstanding requests. A factory
736
- * supplied to start() lets a later request initialize a fresh worker.
737
- */
738
- async proveRequest(body, signal) {
739
- return this.#enqueue(async (active) => {
740
- const shape = proofRequestShape(body);
741
- if (shape === void 0) throw new WasmProverError("wasm_invalid_request");
742
- await this.#loadKey(shape, active);
743
- active.throwIfAborted();
744
- const response = await this.#call({
745
- kind: "prove",
746
- body
747
- });
748
- if (typeof response.value !== "string") throw new WasmProverError("wasm_invalid_response");
749
- const proof = response.value;
750
- active.throwIfAborted();
751
- const verified = await this.#call({
752
- kind: "verify",
753
- body,
754
- proof
755
- });
756
- if (typeof verified.value !== "object" || verified.value === null || !("valid" in verified.value) || verified.value.valid !== true) throw new WasmProverError("wasm_verify_failed");
757
- active.throwIfAborted();
758
- this.#options.onMeasurement?.({
759
- step: "transfer-prove",
760
- ms: response.ms,
761
- note: `Mopro proof, ${String(this.#threads)} arithmetic workers; locally verified`
762
- });
763
- return {
764
- proof,
765
- proveMs: response.ms,
766
- verifyMs: verified.ms
767
- };
768
- }, signal);
769
- }
770
- async loadedKeys() {
771
- return this.#enqueue(async (signal) => {
772
- await this.#ensureStarted(signal);
773
- const value = (await this.#call({ kind: "loadedKeys" })).value;
774
- if (typeof value === "object" && value !== null && "keys" in value && Array.isArray(value.keys)) return value.keys.map(String);
775
- throw new WasmProverError("wasm_invalid_response");
776
- });
777
- }
778
- terminate() {
779
- this.#factory = void 0;
780
- this.#fail(new WasmProverError("wasm_worker_terminated"));
781
- }
782
- #fail(error) {
783
- this.#worker?.terminate();
784
- this.#worker = void 0;
785
- this.#initialization = void 0;
786
- this.#threads = 0;
787
- this.#loaded.clear();
788
- for (const pending of this.#pending.values()) pending.reject(error);
789
- this.#pending.clear();
790
- const lifetime = this.#lifetime;
791
- this.#lifetime = new AbortController();
792
- this.#queue = Promise.resolve();
793
- lifetime.abort(error);
794
- }
795
- async #call(request, transfer = []) {
796
- const worker = this.#worker;
797
- if (worker === void 0) throw new WasmProverError("wasm_worker_not_started");
798
- const id = this.#nextId++;
799
- const response = await new Promise((resolve, reject) => {
800
- this.#pending.set(id, {
801
- resolve,
802
- reject
803
- });
804
- try {
805
- worker.postMessage({
806
- ...request,
807
- id
808
- }, [...transfer]);
809
- } catch {
810
- this.#fail(new WasmProverError("wasm_worker_failed"));
811
- }
812
- });
813
- if (!response.ok) throw operationError(request.kind);
814
- return response;
815
- }
816
- };
817
- function createProverWorker() {
818
- return new Worker(new URL(
819
- /* @vite-ignore */
820
- new URL("assets/prover.worker-sn1Mixyo.js", import.meta.url).href,
821
- "" + import.meta.url
822
- ), {
823
- type: "module",
824
- name: "zolana-web-prover"
825
- });
826
- }
827
- function requestUrl(input) {
828
- try {
829
- if (typeof input === "string") return new URL(input);
830
- if (input instanceof URL) return input;
831
- return new URL(input.url);
832
- } catch {
833
- return;
834
- }
835
- }
836
- async function readBody(input, init) {
837
- if (typeof init?.body === "string") return init.body;
838
- if (input instanceof Request) return await input.clone().text();
839
- }
840
- /** Also matches 0.1.0's single `zolana-proving-keys` store. */
841
- var keyCachePrefix = "zolana-proving-keys";
842
- var keyCacheName = `${keyCachePrefix}-${KEY_SET}`;
843
- var rawSourceHeader = "x-zolana-pinned-sha256";
844
- var rawFormatHeader = "x-zolana-raw-format";
845
- var rawDigestHeader = "x-zolana-raw-sha256";
846
- /** The Cache API ignores fragments, so the raw entry gets its own query. */
847
- function rawKeyUrl(url) {
848
- return `${url}?zolana-key=raw`;
849
- }
850
- /** Reads a key body, reporting bytes as they arrive and stopping past the pinned size. */
851
- async function readKey(response, size, downloaded) {
852
- const reader = downloaded === void 0 ? void 0 : response.body?.getReader();
853
- if (reader === void 0) return response.arrayBuffer();
854
- const bytes = new Uint8Array(size);
855
- let loaded = 0;
856
- for (;;) {
857
- const { done, value } = await reader.read();
858
- if (done) break;
859
- if (loaded + value.byteLength > size) {
860
- await reader.cancel();
861
- throw new WasmProverError("wasm_key_digest_mismatch");
862
- }
863
- bytes.set(value, loaded);
864
- loaded += value.byteLength;
865
- downloaded?.(loaded);
866
- }
867
- return loaded === size ? bytes.buffer : bytes.buffer.slice(0, loaded);
868
- }
869
- async function sha256Hex(bytes) {
870
- const digest = await crypto.subtle.digest("SHA-256", bytes);
871
- return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
872
- }
873
- async function matchesDigest(bytes, expected) {
874
- if (bytes.byteLength !== expected.size) return false;
875
- return await sha256Hex(bytes) === expected.sha256;
876
- }
877
- /**
878
- * A 500 carrying the reason, so the SDK's prover error path handles it as a
879
- * server rejection rather than a retryable transport fault.
880
- */
881
- function proverErrorResponse(error, onMeasurement) {
882
- const safe = new WasmProverError(error instanceof WasmProverError ? error.code : void 0);
883
- onMeasurement?.({
884
- step: "transfer-prove",
885
- ms: 0,
886
- note: safe.code
887
- });
888
- return new Response(JSON.stringify({
889
- code: safe.code,
890
- message: safe.message
891
- }), {
892
- status: 500,
893
- headers: { "content-type": "application/json" }
894
- });
895
- }
896
- function rethrowAbort(error, signal) {
897
- signal?.throwIfAborted();
898
- if (error instanceof Error && error.name === "AbortError") throw error;
899
- }
900
- async function optionalCache(operation, signal) {
901
- try {
902
- return await abortable(Promise.resolve().then(operation), signal);
903
- } catch (error) {
904
- rethrowAbort(error, signal);
905
- return;
906
- }
907
- }
908
- //#endregion
909
- export { KEY_BASE_URL, MERGE_KEY_BYTES, MERGE_KEY_FILE, MERGE_SHAPE, MERGE_SHAPES, TRANSFER_SHAPES, WasmProver, WasmProver as ZolanaWebProver, WasmProverError, automaticProvingThreads, canonicalShape, formatBytes, keyForProveRequest, observeProofRequests, shapeByLabel };
1
+ export { TRANSFER_SHAPES, MERGE_KEY_BYTES, MERGE_KEY_FILE, MERGE_SHAPE, MERGE_SHAPES, keyForProveRequest, canonicalShape, formatBytes, neededKeys, shapeByLabel, } from "./shapes.js";
2
+ export { KEY_BASE_URL } from "./key-digests.js";
3
+ export { WasmProver, WasmProver as ZolanaWebProver, WasmProverError, } from "./wasm-prover.js";
4
+ export { automaticProvingThreads } from "./proving-threads.js";
5
+ export { observeProofRequests } from "./proof-requests.js";