@izi-noir/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +458 -0
- package/dist/IProvingSystem-D9TnEig0.d.ts +140 -0
- package/dist/IProvingSystem-TKNofoo8.d.cts +140 -0
- package/dist/index.cjs +2793 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1196 -0
- package/dist/index.d.ts +1196 -0
- package/dist/index.js +2730 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/arkworks.cjs +824 -0
- package/dist/providers/arkworks.cjs.map +1 -0
- package/dist/providers/arkworks.d.cts +121 -0
- package/dist/providers/arkworks.d.ts +121 -0
- package/dist/providers/arkworks.js +791 -0
- package/dist/providers/arkworks.js.map +1 -0
- package/dist/providers/barretenberg.cjs +822 -0
- package/dist/providers/barretenberg.cjs.map +1 -0
- package/dist/providers/barretenberg.d.cts +18 -0
- package/dist/providers/barretenberg.d.ts +18 -0
- package/dist/providers/barretenberg.js +790 -0
- package/dist/providers/barretenberg.js.map +1 -0
- package/dist/providers/solana.cjs +262 -0
- package/dist/providers/solana.cjs.map +1 -0
- package/dist/providers/solana.d.cts +223 -0
- package/dist/providers/solana.d.ts +223 -0
- package/dist/providers/solana.js +222 -0
- package/dist/providers/solana.js.map +1 -0
- package/dist/providers/sunspot.cjs +475 -0
- package/dist/providers/sunspot.cjs.map +1 -0
- package/dist/providers/sunspot.d.cts +210 -0
- package/dist/providers/sunspot.d.ts +210 -0
- package/dist/providers/sunspot.js +443 -0
- package/dist/providers/sunspot.js.map +1 -0
- package/dist/types-CaaigonG.d.cts +93 -0
- package/dist/types-CaaigonG.d.ts +93 -0
- package/dist/wasm/nodejs/arkworks_groth16_wasm.js +448 -0
- package/dist/wasm/nodejs/arkworks_groth16_wasm_bg.wasm +0 -0
- package/dist/wasm/web/arkworks_groth16_wasm.js +536 -0
- package/dist/wasm/web/arkworks_groth16_wasm_bg.wasm +0 -0
- package/dist/wasmInit-KV6DTj4J.d.ts +282 -0
- package/dist/wasmInit-iEYiiB8M.d.cts +282 -0
- package/package.json +87 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2793 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/infra/provingSystems/Barretenberg.ts
|
|
34
|
+
var Barretenberg_exports = {};
|
|
35
|
+
__export(Barretenberg_exports, {
|
|
36
|
+
Barretenberg: () => Barretenberg
|
|
37
|
+
});
|
|
38
|
+
function stringToStream(content) {
|
|
39
|
+
return new ReadableStream({
|
|
40
|
+
start(controller) {
|
|
41
|
+
controller.enqueue(new TextEncoder().encode(content));
|
|
42
|
+
controller.close();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function isNodeJs2() {
|
|
47
|
+
return typeof globalThis.process !== "undefined" && globalThis.process.versions != null && globalThis.process.versions.node != null;
|
|
48
|
+
}
|
|
49
|
+
async function createTempDir() {
|
|
50
|
+
if (!isNodeJs2()) {
|
|
51
|
+
return { basePath: "/", cleanup: null };
|
|
52
|
+
}
|
|
53
|
+
const fs = await import("fs/promises");
|
|
54
|
+
const os = await import("os");
|
|
55
|
+
const path = await import("path");
|
|
56
|
+
const basePath = await fs.mkdtemp(path.join(os.tmpdir(), "noir-circuit-"));
|
|
57
|
+
const cleanup = async () => {
|
|
58
|
+
await fs.rm(basePath, { recursive: true, force: true });
|
|
59
|
+
};
|
|
60
|
+
return { basePath, cleanup };
|
|
61
|
+
}
|
|
62
|
+
var import_noir_wasm, import_noir_js, import_bb, Barretenberg;
|
|
63
|
+
var init_Barretenberg = __esm({
|
|
64
|
+
"src/infra/provingSystems/Barretenberg.ts"() {
|
|
65
|
+
"use strict";
|
|
66
|
+
import_noir_wasm = require("@noir-lang/noir_wasm");
|
|
67
|
+
import_noir_js = require("@noir-lang/noir_js");
|
|
68
|
+
import_bb = require("@aztec/bb.js");
|
|
69
|
+
Barretenberg = class {
|
|
70
|
+
async compile(noirCode) {
|
|
71
|
+
const { basePath, cleanup } = await createTempDir();
|
|
72
|
+
const fm = (0, import_noir_wasm.createFileManager)(basePath);
|
|
73
|
+
const nargoToml = `[package]
|
|
74
|
+
name = "circuit"
|
|
75
|
+
type = "bin"
|
|
76
|
+
authors = [""]
|
|
77
|
+
|
|
78
|
+
[dependencies]
|
|
79
|
+
`;
|
|
80
|
+
try {
|
|
81
|
+
if (isNodeJs2()) {
|
|
82
|
+
await fm.writeFile("./src/main.nr", stringToStream(noirCode));
|
|
83
|
+
await fm.writeFile("./Nargo.toml", stringToStream(nargoToml));
|
|
84
|
+
} else {
|
|
85
|
+
fm.writeFile("./src/main.nr", stringToStream(noirCode));
|
|
86
|
+
fm.writeFile("./Nargo.toml", stringToStream(nargoToml));
|
|
87
|
+
}
|
|
88
|
+
const result = await (0, import_noir_wasm.compile)(fm);
|
|
89
|
+
const compiled = result.program;
|
|
90
|
+
if (!compiled || !compiled.bytecode) {
|
|
91
|
+
throw new Error("Compilation failed: no bytecode generated");
|
|
92
|
+
}
|
|
93
|
+
return compiled;
|
|
94
|
+
} finally {
|
|
95
|
+
if (cleanup) {
|
|
96
|
+
await cleanup();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async generateProof(circuit, inputs) {
|
|
101
|
+
const noir = new import_noir_js.Noir(circuit);
|
|
102
|
+
const { witness } = await noir.execute(inputs);
|
|
103
|
+
const barretenberg = await import_bb.Barretenberg.new({ threads: 1 });
|
|
104
|
+
const backend = new import_bb.UltraHonkBackend(circuit.bytecode, barretenberg);
|
|
105
|
+
try {
|
|
106
|
+
const proofData = await backend.generateProof(witness);
|
|
107
|
+
return {
|
|
108
|
+
proof: proofData.proof,
|
|
109
|
+
publicInputs: proofData.publicInputs || []
|
|
110
|
+
};
|
|
111
|
+
} finally {
|
|
112
|
+
await barretenberg.destroy();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async verifyProof(circuit, proof, publicInputs) {
|
|
116
|
+
const barretenberg = await import_bb.Barretenberg.new({ threads: 1 });
|
|
117
|
+
const backend = new import_bb.UltraHonkBackend(circuit.bytecode, barretenberg);
|
|
118
|
+
try {
|
|
119
|
+
return await backend.verifyProof({ proof, publicInputs });
|
|
120
|
+
} finally {
|
|
121
|
+
await barretenberg.destroy();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// src/infra/provingSystems/ArkworksWasm.ts
|
|
129
|
+
var ArkworksWasm_exports = {};
|
|
130
|
+
__export(ArkworksWasm_exports, {
|
|
131
|
+
ArkworksWasm: () => ArkworksWasm,
|
|
132
|
+
isArkworksCircuit: () => isArkworksCircuit
|
|
133
|
+
});
|
|
134
|
+
function stringToStream2(content) {
|
|
135
|
+
return new ReadableStream({
|
|
136
|
+
start(controller) {
|
|
137
|
+
controller.enqueue(new TextEncoder().encode(content));
|
|
138
|
+
controller.close();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function isNodeJs3() {
|
|
143
|
+
return typeof globalThis.process !== "undefined" && globalThis.process.versions != null && globalThis.process.versions.node != null;
|
|
144
|
+
}
|
|
145
|
+
async function createTempDir2() {
|
|
146
|
+
if (!isNodeJs3()) {
|
|
147
|
+
return { basePath: "/", cleanup: null };
|
|
148
|
+
}
|
|
149
|
+
const fs = await import("fs/promises");
|
|
150
|
+
const os = await import("os");
|
|
151
|
+
const path = await import("path");
|
|
152
|
+
const basePath = await fs.mkdtemp(path.join(os.tmpdir(), "arkworks-circuit-"));
|
|
153
|
+
const cleanup = async () => {
|
|
154
|
+
await fs.rm(basePath, { recursive: true, force: true });
|
|
155
|
+
};
|
|
156
|
+
return { basePath, cleanup };
|
|
157
|
+
}
|
|
158
|
+
function isArkworksCircuit(circuit) {
|
|
159
|
+
return "__arkworks" in circuit && circuit.__arkworks === true;
|
|
160
|
+
}
|
|
161
|
+
async function initWasm() {
|
|
162
|
+
if (wasmModule) {
|
|
163
|
+
return wasmModule;
|
|
164
|
+
}
|
|
165
|
+
if (wasmInitPromise2) {
|
|
166
|
+
return wasmInitPromise2;
|
|
167
|
+
}
|
|
168
|
+
wasmInitPromise2 = (async () => {
|
|
169
|
+
try {
|
|
170
|
+
if (isNodeJs3()) {
|
|
171
|
+
const module2 = await import("../wasm/nodejs/arkworks_groth16_wasm.js");
|
|
172
|
+
wasmModule = module2;
|
|
173
|
+
} else {
|
|
174
|
+
const module2 = await import("../wasm/web/arkworks_groth16_wasm.js");
|
|
175
|
+
if (typeof module2.default === "function") {
|
|
176
|
+
await module2.default();
|
|
177
|
+
}
|
|
178
|
+
wasmModule = module2;
|
|
179
|
+
}
|
|
180
|
+
return wasmModule;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
wasmInitPromise2 = null;
|
|
183
|
+
throw new Error(
|
|
184
|
+
`Failed to initialize arkworks-groth16-wasm: ${error instanceof Error ? error.message : String(error)}
|
|
185
|
+
Make sure the WASM module is built: cd packages/arkworks-groth16-wasm && npm run build`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
})();
|
|
189
|
+
return wasmInitPromise2;
|
|
190
|
+
}
|
|
191
|
+
function base64ToUint8Array(b64) {
|
|
192
|
+
const binaryString = atob(b64);
|
|
193
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
194
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
195
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
196
|
+
}
|
|
197
|
+
return bytes;
|
|
198
|
+
}
|
|
199
|
+
function uint8ArrayToBase64(bytes) {
|
|
200
|
+
let binary = "";
|
|
201
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
202
|
+
binary += String.fromCharCode(bytes[i]);
|
|
203
|
+
}
|
|
204
|
+
return btoa(binary);
|
|
205
|
+
}
|
|
206
|
+
function publicInputsToGnarkBase64(publicInputs) {
|
|
207
|
+
const FIELD_SIZE2 = 32;
|
|
208
|
+
const bytes = new Uint8Array(publicInputs.length * FIELD_SIZE2);
|
|
209
|
+
for (let i = 0; i < publicInputs.length; i++) {
|
|
210
|
+
const input = publicInputs[i];
|
|
211
|
+
const hex = input.startsWith("0x") ? input.slice(2) : input;
|
|
212
|
+
const inputBytes = hexToBytes(hex.padStart(64, "0"));
|
|
213
|
+
bytes.set(inputBytes, i * FIELD_SIZE2);
|
|
214
|
+
}
|
|
215
|
+
return uint8ArrayToBase64(bytes);
|
|
216
|
+
}
|
|
217
|
+
function hexToBytes(hex) {
|
|
218
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
219
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
220
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
221
|
+
}
|
|
222
|
+
return bytes;
|
|
223
|
+
}
|
|
224
|
+
var import_noir_wasm2, import_noir_js2, wasmModule, wasmInitPromise2, ArkworksWasm;
|
|
225
|
+
var init_ArkworksWasm = __esm({
|
|
226
|
+
"src/infra/provingSystems/ArkworksWasm.ts"() {
|
|
227
|
+
"use strict";
|
|
228
|
+
import_noir_wasm2 = require("@noir-lang/noir_wasm");
|
|
229
|
+
import_noir_js2 = require("@noir-lang/noir_js");
|
|
230
|
+
wasmModule = null;
|
|
231
|
+
wasmInitPromise2 = null;
|
|
232
|
+
ArkworksWasm = class {
|
|
233
|
+
config;
|
|
234
|
+
constructor(config = {}) {
|
|
235
|
+
this.config = {
|
|
236
|
+
keepArtifacts: false,
|
|
237
|
+
cacheKeys: true,
|
|
238
|
+
...config
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Compile Noir code to a circuit with ACIR for Groth16 proving
|
|
243
|
+
*/
|
|
244
|
+
async compile(noirCode) {
|
|
245
|
+
const wasm = await initWasm();
|
|
246
|
+
const { basePath, cleanup } = await createTempDir2();
|
|
247
|
+
const fm = (0, import_noir_wasm2.createFileManager)(basePath);
|
|
248
|
+
const nargoToml = `[package]
|
|
249
|
+
name = "circuit"
|
|
250
|
+
type = "bin"
|
|
251
|
+
authors = [""]
|
|
252
|
+
|
|
253
|
+
[dependencies]
|
|
254
|
+
`;
|
|
255
|
+
try {
|
|
256
|
+
if (isNodeJs3()) {
|
|
257
|
+
await fm.writeFile("./src/main.nr", stringToStream2(noirCode));
|
|
258
|
+
await fm.writeFile("./Nargo.toml", stringToStream2(nargoToml));
|
|
259
|
+
} else {
|
|
260
|
+
fm.writeFile("./src/main.nr", stringToStream2(noirCode));
|
|
261
|
+
fm.writeFile("./Nargo.toml", stringToStream2(nargoToml));
|
|
262
|
+
}
|
|
263
|
+
const result = await (0, import_noir_wasm2.compile)(fm);
|
|
264
|
+
const compiled = result.program;
|
|
265
|
+
if (!compiled || !compiled.bytecode) {
|
|
266
|
+
throw new Error("Compilation failed: no bytecode generated");
|
|
267
|
+
}
|
|
268
|
+
const acirJson = JSON.stringify({
|
|
269
|
+
functions: [
|
|
270
|
+
{
|
|
271
|
+
current_witness_index: compiled.abi.parameters.length + 1,
|
|
272
|
+
opcodes: [],
|
|
273
|
+
// Will be extracted from bytecode during prove
|
|
274
|
+
private_parameters: compiled.abi.parameters.filter((p) => p.visibility === "private").map((_, i) => i + 1),
|
|
275
|
+
public_parameters: {
|
|
276
|
+
witnesses: compiled.abi.parameters.filter((p) => p.visibility === "public").map((_, i) => i + 1)
|
|
277
|
+
},
|
|
278
|
+
return_values: { witnesses: [] }
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
});
|
|
282
|
+
let provingKey;
|
|
283
|
+
let verifyingKey;
|
|
284
|
+
let verifyingKeyGnark;
|
|
285
|
+
if (this.config.cacheKeys) {
|
|
286
|
+
try {
|
|
287
|
+
const setupResult = wasm.setup(acirJson);
|
|
288
|
+
provingKey = setupResult.proving_key;
|
|
289
|
+
verifyingKey = setupResult.verifying_key;
|
|
290
|
+
verifyingKeyGnark = setupResult.verifying_key_gnark;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.warn("Deferred setup: will run during proof generation");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const arkworksCircuit = {
|
|
296
|
+
...compiled,
|
|
297
|
+
__arkworks: true,
|
|
298
|
+
acirJson,
|
|
299
|
+
provingKey,
|
|
300
|
+
verifyingKey,
|
|
301
|
+
verifyingKeyGnark
|
|
302
|
+
};
|
|
303
|
+
return arkworksCircuit;
|
|
304
|
+
} finally {
|
|
305
|
+
if (cleanup) {
|
|
306
|
+
await cleanup();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Generate a Groth16 proof
|
|
312
|
+
*/
|
|
313
|
+
async generateProof(circuit, inputs) {
|
|
314
|
+
const wasm = await initWasm();
|
|
315
|
+
if (!isArkworksCircuit(circuit)) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
"ArkworksWasm.generateProof requires an ArkworksCompiledCircuit. Use ArkworksWasm.compile() first."
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const noir = new import_noir_js2.Noir(circuit);
|
|
321
|
+
const { witness } = await noir.execute(inputs);
|
|
322
|
+
const witnessMap = {};
|
|
323
|
+
for (const [index, value] of witness.entries()) {
|
|
324
|
+
witnessMap[index.toString()] = String(value);
|
|
325
|
+
}
|
|
326
|
+
const witnessJson = JSON.stringify(witnessMap);
|
|
327
|
+
let provingKey = circuit.provingKey;
|
|
328
|
+
if (!provingKey) {
|
|
329
|
+
const setupResult = wasm.setup(circuit.acirJson);
|
|
330
|
+
provingKey = setupResult.proving_key;
|
|
331
|
+
circuit.provingKey = provingKey;
|
|
332
|
+
circuit.verifyingKey = setupResult.verifying_key;
|
|
333
|
+
circuit.verifyingKeyGnark = setupResult.verifying_key_gnark;
|
|
334
|
+
}
|
|
335
|
+
const proofResult = wasm.prove(provingKey, circuit.acirJson, witnessJson);
|
|
336
|
+
const proofBytes = base64ToUint8Array(proofResult.proof_gnark);
|
|
337
|
+
return {
|
|
338
|
+
proof: proofBytes,
|
|
339
|
+
publicInputs: proofResult.public_inputs
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Verify a Groth16 proof
|
|
344
|
+
*/
|
|
345
|
+
async verifyProof(circuit, proof, publicInputs) {
|
|
346
|
+
const wasm = await initWasm();
|
|
347
|
+
if (!isArkworksCircuit(circuit)) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
"ArkworksWasm.verifyProof requires an ArkworksCompiledCircuit. Use ArkworksWasm.compile() first."
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
let verifyingKeyGnark = circuit.verifyingKeyGnark;
|
|
353
|
+
if (!verifyingKeyGnark) {
|
|
354
|
+
const setupResult = wasm.setup(circuit.acirJson);
|
|
355
|
+
circuit.provingKey = setupResult.proving_key;
|
|
356
|
+
circuit.verifyingKey = setupResult.verifying_key;
|
|
357
|
+
verifyingKeyGnark = setupResult.verifying_key_gnark;
|
|
358
|
+
circuit.verifyingKeyGnark = verifyingKeyGnark;
|
|
359
|
+
}
|
|
360
|
+
const proofB64 = uint8ArrayToBase64(proof);
|
|
361
|
+
const publicInputsGnarkB64 = publicInputsToGnarkBase64(publicInputs);
|
|
362
|
+
return wasm.verify_gnark(
|
|
363
|
+
verifyingKeyGnark,
|
|
364
|
+
proofB64,
|
|
365
|
+
publicInputsGnarkB64,
|
|
366
|
+
publicInputs.length
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Get the verifying key in gnark format for on-chain deployment
|
|
371
|
+
*/
|
|
372
|
+
async getVerifyingKeyGnark(circuit) {
|
|
373
|
+
const wasm = await initWasm();
|
|
374
|
+
if (!isArkworksCircuit(circuit)) {
|
|
375
|
+
throw new Error("getVerifyingKeyGnark requires an ArkworksCompiledCircuit");
|
|
376
|
+
}
|
|
377
|
+
if (!circuit.verifyingKeyGnark) {
|
|
378
|
+
const setupResult = wasm.setup(circuit.acirJson);
|
|
379
|
+
circuit.provingKey = setupResult.proving_key;
|
|
380
|
+
circuit.verifyingKey = setupResult.verifying_key;
|
|
381
|
+
circuit.verifyingKeyGnark = setupResult.verifying_key_gnark;
|
|
382
|
+
}
|
|
383
|
+
return base64ToUint8Array(circuit.verifyingKeyGnark);
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// src/infra/chainFormatters/SolanaFormatter.ts
|
|
390
|
+
var SolanaFormatter_exports = {};
|
|
391
|
+
__export(SolanaFormatter_exports, {
|
|
392
|
+
SolanaFormatter: () => SolanaFormatter
|
|
393
|
+
});
|
|
394
|
+
var G1_SIZE, G2_SIZE, SolanaFormatter;
|
|
395
|
+
var init_SolanaFormatter = __esm({
|
|
396
|
+
"src/infra/chainFormatters/SolanaFormatter.ts"() {
|
|
397
|
+
"use strict";
|
|
398
|
+
G1_SIZE = 64;
|
|
399
|
+
G2_SIZE = 128;
|
|
400
|
+
SolanaFormatter = class {
|
|
401
|
+
constructor(arkworksProvider) {
|
|
402
|
+
this.arkworksProvider = arkworksProvider;
|
|
403
|
+
}
|
|
404
|
+
chainId = "solana";
|
|
405
|
+
/**
|
|
406
|
+
* Format a generic proof for Solana on-chain verification.
|
|
407
|
+
*
|
|
408
|
+
* @param proofData - Generic proof data from Arkworks
|
|
409
|
+
* @param circuit - The compiled circuit (must be Arkworks circuit)
|
|
410
|
+
* @param metadata - Circuit metadata with public input count
|
|
411
|
+
* @returns SolanaProofData ready for on-chain verification
|
|
412
|
+
*/
|
|
413
|
+
async formatProof(proofData, circuit, metadata) {
|
|
414
|
+
const vkBytes = await this.arkworksProvider.getVerifyingKeyGnark(circuit);
|
|
415
|
+
const vkBase64 = this.uint8ArrayToBase64(vkBytes);
|
|
416
|
+
const nrPublicInputs = metadata.numPublicInputs;
|
|
417
|
+
const publicInputsBytes = proofData.publicInputs.map((input) => {
|
|
418
|
+
const hex = input.startsWith("0x") ? input.slice(2) : input;
|
|
419
|
+
return this.hexToBytes(hex.padStart(64, "0"));
|
|
420
|
+
});
|
|
421
|
+
const { accountSize, estimatedRent } = this.getChainMetadata(nrPublicInputs);
|
|
422
|
+
return {
|
|
423
|
+
verifyingKey: {
|
|
424
|
+
base64: vkBase64,
|
|
425
|
+
bytes: vkBytes,
|
|
426
|
+
nrPublicInputs
|
|
427
|
+
},
|
|
428
|
+
proof: {
|
|
429
|
+
base64: this.uint8ArrayToBase64(proofData.proof),
|
|
430
|
+
bytes: proofData.proof
|
|
431
|
+
},
|
|
432
|
+
publicInputs: {
|
|
433
|
+
hex: proofData.publicInputs,
|
|
434
|
+
bytes: publicInputsBytes
|
|
435
|
+
},
|
|
436
|
+
accountSize,
|
|
437
|
+
estimatedRent
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get Solana-specific metadata for a circuit.
|
|
442
|
+
*
|
|
443
|
+
* @param publicInputCount - Number of public inputs in the circuit
|
|
444
|
+
* @returns Solana metadata with account size and rent estimates
|
|
445
|
+
*/
|
|
446
|
+
getChainMetadata(publicInputCount) {
|
|
447
|
+
const accountSize = this.calculateVkAccountSize(publicInputCount);
|
|
448
|
+
const estimatedRent = this.calculateVkAccountRent(publicInputCount);
|
|
449
|
+
return {
|
|
450
|
+
chainId: "solana",
|
|
451
|
+
accountSize,
|
|
452
|
+
estimatedRent
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Calculate the size of a VK account for a given number of public inputs.
|
|
457
|
+
* Matches the Rust `vk_account_size` function in the Solana program.
|
|
458
|
+
*/
|
|
459
|
+
calculateVkAccountSize(nrPublicInputs) {
|
|
460
|
+
const fixedSize = 8 + 32 + 1 + G1_SIZE + G2_SIZE * 3 + 4;
|
|
461
|
+
return fixedSize + (nrPublicInputs + 1) * G1_SIZE;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Calculate the minimum rent for a VK account.
|
|
465
|
+
*/
|
|
466
|
+
calculateVkAccountRent(nrPublicInputs, rentExemptionPerByte = 6960) {
|
|
467
|
+
const size = this.calculateVkAccountSize(nrPublicInputs);
|
|
468
|
+
return size * rentExemptionPerByte;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Convert Uint8Array to base64 string.
|
|
472
|
+
*/
|
|
473
|
+
uint8ArrayToBase64(bytes) {
|
|
474
|
+
if (typeof btoa === "function") {
|
|
475
|
+
let binary = "";
|
|
476
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
477
|
+
binary += String.fromCharCode(bytes[i]);
|
|
478
|
+
}
|
|
479
|
+
return btoa(binary);
|
|
480
|
+
}
|
|
481
|
+
return Buffer.from(bytes).toString("base64");
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Convert hex string to Uint8Array.
|
|
485
|
+
*/
|
|
486
|
+
hexToBytes(hex) {
|
|
487
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
488
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
489
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
490
|
+
bytes[i] = parseInt(cleanHex.substring(i * 2, i * 2 + 2), 16);
|
|
491
|
+
}
|
|
492
|
+
return bytes;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// src/index.ts
|
|
499
|
+
var index_exports = {};
|
|
500
|
+
__export(index_exports, {
|
|
501
|
+
AcornParser: () => AcornParser,
|
|
502
|
+
ArkworksWasm: () => ArkworksWasm,
|
|
503
|
+
Barretenberg: () => Barretenberg,
|
|
504
|
+
Chain: () => Chain,
|
|
505
|
+
CircuitRegistry: () => CircuitRegistry,
|
|
506
|
+
CreateProofUseCase: () => CreateProofUseCase,
|
|
507
|
+
IZI_NOIR_PROGRAM_ID: () => IZI_NOIR_PROGRAM_ID,
|
|
508
|
+
IziNoir: () => IziNoir,
|
|
509
|
+
NETWORK_ENDPOINTS: () => NETWORK_ENDPOINTS,
|
|
510
|
+
OffchainVerifier: () => OffchainVerifier,
|
|
511
|
+
Provider: () => Provider,
|
|
512
|
+
SolanaFormatter: () => SolanaFormatter,
|
|
513
|
+
SolanaTransactionBuilder: () => SolanaTransactionBuilder,
|
|
514
|
+
VkDeploymentManager: () => VkDeploymentManager,
|
|
515
|
+
batchVerify: () => batchVerify,
|
|
516
|
+
buildInitVkFromBytesData: () => buildInitVkFromBytesData,
|
|
517
|
+
buildVerifyProofData: () => buildVerifyProofData,
|
|
518
|
+
calculateVkAccountRent: () => calculateVkAccountRent,
|
|
519
|
+
calculateVkAccountSize: () => calculateVkAccountSize,
|
|
520
|
+
createArkworksWasmContainer: () => createArkworksWasmContainer,
|
|
521
|
+
createDefaultContainer: () => createDefaultContainer,
|
|
522
|
+
createNodeVkDeploymentManager: () => createNodeVkDeploymentManager,
|
|
523
|
+
createVerificationEndpoint: () => createVerificationEndpoint,
|
|
524
|
+
createVerifierMiddleware: () => createVerifierMiddleware,
|
|
525
|
+
defineCircuit: () => defineCircuit,
|
|
526
|
+
generateNoir: () => generateNoir,
|
|
527
|
+
getGlobalRegistry: () => getGlobalRegistry,
|
|
528
|
+
initNoirWasm: () => initNoirWasm,
|
|
529
|
+
isArkworksCircuit: () => isArkworksCircuit,
|
|
530
|
+
isWasmInitialized: () => isWasmInitialized,
|
|
531
|
+
markWasmInitialized: () => markWasmInitialized,
|
|
532
|
+
parseProof: () => parseProof,
|
|
533
|
+
parsePublicInputs: () => parsePublicInputs,
|
|
534
|
+
parseVerifyingKey: () => parseVerifyingKey
|
|
535
|
+
});
|
|
536
|
+
module.exports = __toCommonJS(index_exports);
|
|
537
|
+
|
|
538
|
+
// src/domain/types/chain.ts
|
|
539
|
+
var Chain = /* @__PURE__ */ ((Chain2) => {
|
|
540
|
+
Chain2["Solana"] = "solana";
|
|
541
|
+
Chain2["Ethereum"] = "ethereum";
|
|
542
|
+
return Chain2;
|
|
543
|
+
})(Chain || {});
|
|
544
|
+
|
|
545
|
+
// src/domain/types/provider.ts
|
|
546
|
+
var Provider = /* @__PURE__ */ ((Provider2) => {
|
|
547
|
+
Provider2["Barretenberg"] = "barretenberg";
|
|
548
|
+
Provider2["Arkworks"] = "arkworks";
|
|
549
|
+
Provider2["Sunspot"] = "sunspot";
|
|
550
|
+
return Provider2;
|
|
551
|
+
})(Provider || {});
|
|
552
|
+
|
|
553
|
+
// src/infra/wasm/wasmInit.ts
|
|
554
|
+
var wasmInitPromise = null;
|
|
555
|
+
var wasmInitialized = false;
|
|
556
|
+
function isNodeJs() {
|
|
557
|
+
return typeof globalThis.process !== "undefined" && globalThis.process.versions != null && globalThis.process.versions.node != null;
|
|
558
|
+
}
|
|
559
|
+
async function initNoirWasm() {
|
|
560
|
+
if (wasmInitialized) return;
|
|
561
|
+
if (!wasmInitPromise) {
|
|
562
|
+
wasmInitPromise = initWasmInternal();
|
|
563
|
+
}
|
|
564
|
+
await wasmInitPromise;
|
|
565
|
+
wasmInitialized = true;
|
|
566
|
+
}
|
|
567
|
+
async function initWasmInternal() {
|
|
568
|
+
if (isNodeJs()) {
|
|
569
|
+
await import("@noir-lang/acvm_js/nodejs/acvm_js.js");
|
|
570
|
+
await import("@noir-lang/noirc_abi/nodejs/noirc_abi_wasm.js");
|
|
571
|
+
} else {
|
|
572
|
+
const [{ default: initACVM }, { default: initNoirC }] = await Promise.all([
|
|
573
|
+
import("@noir-lang/acvm_js"),
|
|
574
|
+
import("@noir-lang/noirc_abi")
|
|
575
|
+
]);
|
|
576
|
+
await Promise.all([initACVM(), initNoirC()]);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function isWasmInitialized() {
|
|
580
|
+
return wasmInitialized;
|
|
581
|
+
}
|
|
582
|
+
function markWasmInitialized() {
|
|
583
|
+
wasmInitialized = true;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/IziNoir.ts
|
|
587
|
+
var IziNoir = class _IziNoir {
|
|
588
|
+
provingSystem;
|
|
589
|
+
compiledCircuit = null;
|
|
590
|
+
chainFormatters = /* @__PURE__ */ new Map();
|
|
591
|
+
chain;
|
|
592
|
+
_verifyingKey;
|
|
593
|
+
_lastProof;
|
|
594
|
+
constructor(provingSystem, chain) {
|
|
595
|
+
this.provingSystem = provingSystem;
|
|
596
|
+
this.chain = chain;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Get the verifying key from the last proof generation.
|
|
600
|
+
* Only available after calling prove() with a chain configured.
|
|
601
|
+
*/
|
|
602
|
+
get vk() {
|
|
603
|
+
return this._verifyingKey;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Get the configured chain, if any.
|
|
607
|
+
*/
|
|
608
|
+
get targetChain() {
|
|
609
|
+
return this.chain;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Check if operating in offchain mode (no chain configured).
|
|
613
|
+
*/
|
|
614
|
+
get isOffchain() {
|
|
615
|
+
return this.chain === void 0;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Register a chain formatter for chain-specific proof formatting.
|
|
619
|
+
*
|
|
620
|
+
* @param formatter - The chain formatter to register
|
|
621
|
+
*/
|
|
622
|
+
registerChainFormatter(formatter) {
|
|
623
|
+
this.chainFormatters.set(formatter.chainId, formatter);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Get a registered chain formatter.
|
|
627
|
+
*
|
|
628
|
+
* @param chainId - The chain ID to get the formatter for
|
|
629
|
+
* @returns The formatter or undefined if not registered
|
|
630
|
+
*/
|
|
631
|
+
getChainFormatter(chainId) {
|
|
632
|
+
return this.chainFormatters.get(chainId);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Initialize IziNoir with the specified provider and optional chain.
|
|
636
|
+
*
|
|
637
|
+
* @param config - Configuration specifying the provider, chain, and optional circuit paths
|
|
638
|
+
* @returns Initialized IziNoir instance
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* ```typescript
|
|
642
|
+
* // On-chain mode (Solana)
|
|
643
|
+
* const izi = await IziNoir.init({
|
|
644
|
+
* provider: Provider.Arkworks,
|
|
645
|
+
* chain: Chain.Solana
|
|
646
|
+
* });
|
|
647
|
+
*
|
|
648
|
+
* // Offchain mode (no chain formatting)
|
|
649
|
+
* const iziOffchain = await IziNoir.init({
|
|
650
|
+
* provider: Provider.Arkworks
|
|
651
|
+
* });
|
|
652
|
+
*
|
|
653
|
+
* // Barretenberg (browser-compatible, ~16KB proofs, offchain only)
|
|
654
|
+
* const bb = await IziNoir.init({ provider: Provider.Barretenberg });
|
|
655
|
+
* ```
|
|
656
|
+
*/
|
|
657
|
+
static async init(config) {
|
|
658
|
+
await initNoirWasm();
|
|
659
|
+
let provingSystem;
|
|
660
|
+
switch (config.provider) {
|
|
661
|
+
case "barretenberg" /* Barretenberg */: {
|
|
662
|
+
if (config.chain) {
|
|
663
|
+
throw new Error(
|
|
664
|
+
"Barretenberg provider does not support chain formatting. Use Provider.Arkworks for on-chain proofs."
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
const { Barretenberg: Barretenberg2 } = await Promise.resolve().then(() => (init_Barretenberg(), Barretenberg_exports));
|
|
668
|
+
provingSystem = new Barretenberg2();
|
|
669
|
+
return new _IziNoir(provingSystem);
|
|
670
|
+
}
|
|
671
|
+
case "arkworks" /* Arkworks */: {
|
|
672
|
+
const { ArkworksWasm: ArkworksWasm2 } = await Promise.resolve().then(() => (init_ArkworksWasm(), ArkworksWasm_exports));
|
|
673
|
+
const arkworksInstance = new ArkworksWasm2();
|
|
674
|
+
provingSystem = arkworksInstance;
|
|
675
|
+
const instance = new _IziNoir(provingSystem, config.chain);
|
|
676
|
+
if (config.chain === "solana" /* Solana */ || !config.chain) {
|
|
677
|
+
const { SolanaFormatter: SolanaFormatter2 } = await Promise.resolve().then(() => (init_SolanaFormatter(), SolanaFormatter_exports));
|
|
678
|
+
instance.registerChainFormatter(new SolanaFormatter2(arkworksInstance));
|
|
679
|
+
}
|
|
680
|
+
return instance;
|
|
681
|
+
}
|
|
682
|
+
case "sunspot" /* Sunspot */: {
|
|
683
|
+
throw new Error(
|
|
684
|
+
'Sunspot is not available in the main entry point. Import from "@izi-noir/sdk/sunspot" for Sunspot support.'
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
default:
|
|
688
|
+
throw new Error(`Unknown provider: ${config.provider}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get the underlying proving system instance.
|
|
693
|
+
* Useful for advanced use cases.
|
|
694
|
+
*/
|
|
695
|
+
getProvingSystem() {
|
|
696
|
+
return this.provingSystem;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Get the currently compiled circuit, if any.
|
|
700
|
+
*/
|
|
701
|
+
getCompiledCircuit() {
|
|
702
|
+
return this.compiledCircuit;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Compile Noir code into a circuit.
|
|
706
|
+
*
|
|
707
|
+
* @param noirCode - The Noir source code to compile
|
|
708
|
+
* @returns The compiled circuit
|
|
709
|
+
*/
|
|
710
|
+
async compile(noirCode) {
|
|
711
|
+
this.compiledCircuit = await this.provingSystem.compile(noirCode);
|
|
712
|
+
return this.compiledCircuit;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Generate a proof for the given inputs.
|
|
716
|
+
*
|
|
717
|
+
* If a chain is configured, returns chain-formatted proof data and stores
|
|
718
|
+
* the verifying key in `this.vk`. Otherwise, returns raw proof data.
|
|
719
|
+
*
|
|
720
|
+
* @param inputs - The inputs (both public and private) for the circuit
|
|
721
|
+
* @param circuit - Optional circuit to use (defaults to last compiled circuit)
|
|
722
|
+
* @returns The proof data - type depends on configured chain
|
|
723
|
+
* @throws Error if no circuit is available
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
726
|
+
* ```typescript
|
|
727
|
+
* // With chain configured - returns SolanaProofData
|
|
728
|
+
* const izi = await IziNoir.init({ provider: Provider.Arkworks, chain: Chain.Solana });
|
|
729
|
+
* await izi.compile(noirCode);
|
|
730
|
+
* const proof = await izi.prove({ expected: '100', secret: '10' });
|
|
731
|
+
* // proof is SolanaProofData, izi.vk is available
|
|
732
|
+
*
|
|
733
|
+
* // Offchain mode - returns ProofData
|
|
734
|
+
* const iziOffchain = await IziNoir.init({ provider: Provider.Arkworks });
|
|
735
|
+
* await iziOffchain.compile(noirCode);
|
|
736
|
+
* const rawProof = await iziOffchain.prove({ expected: '100', secret: '10' });
|
|
737
|
+
* // rawProof is ProofData, iziOffchain.vk is undefined
|
|
738
|
+
* ```
|
|
739
|
+
*/
|
|
740
|
+
async prove(inputs, circuit) {
|
|
741
|
+
const circuitToUse = circuit || this.compiledCircuit;
|
|
742
|
+
if (!circuitToUse) {
|
|
743
|
+
throw new Error("No circuit available. Call compile() first or provide a circuit.");
|
|
744
|
+
}
|
|
745
|
+
const rawProof = await this.provingSystem.generateProof(circuitToUse, inputs);
|
|
746
|
+
if (!this.chain) {
|
|
747
|
+
this._lastProof = rawProof;
|
|
748
|
+
return rawProof;
|
|
749
|
+
}
|
|
750
|
+
const formatter = this.chainFormatters.get(this.chain);
|
|
751
|
+
if (!formatter) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
`No formatter registered for chain: ${this.chain}. This is an internal error - please report it.`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
const metadata = {
|
|
757
|
+
numPublicInputs: rawProof.publicInputs.length
|
|
758
|
+
};
|
|
759
|
+
const formattedProof = await formatter.formatProof(rawProof, circuitToUse, metadata);
|
|
760
|
+
const chainProof = formattedProof;
|
|
761
|
+
this._verifyingKey = chainProof.verifyingKey;
|
|
762
|
+
this._lastProof = chainProof;
|
|
763
|
+
return chainProof;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Verify a proof.
|
|
767
|
+
* Available in both on-chain and offchain modes.
|
|
768
|
+
*
|
|
769
|
+
* @param proof - The proof bytes to verify
|
|
770
|
+
* @param publicInputs - The public inputs that were used
|
|
771
|
+
* @param circuit - Optional circuit to use (defaults to last compiled circuit)
|
|
772
|
+
* @returns true if the proof is valid, false otherwise
|
|
773
|
+
* @throws Error if no circuit is available
|
|
774
|
+
*/
|
|
775
|
+
async verify(proof, publicInputs, circuit) {
|
|
776
|
+
const circuitToUse = circuit || this.compiledCircuit;
|
|
777
|
+
if (!circuitToUse) {
|
|
778
|
+
throw new Error("No circuit available. Call compile() first or provide a circuit.");
|
|
779
|
+
}
|
|
780
|
+
return this.provingSystem.verifyProof(circuitToUse, proof, publicInputs);
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Convenience method: compile, prove, and verify in one call.
|
|
784
|
+
*
|
|
785
|
+
* @param noirCode - The Noir source code to compile
|
|
786
|
+
* @param inputs - The inputs (both public and private) for the circuit
|
|
787
|
+
* @returns Object containing proof data and verification result
|
|
788
|
+
*
|
|
789
|
+
* @example
|
|
790
|
+
* ```typescript
|
|
791
|
+
* const { proof, verified } = await izi.createProof(noirCode, {
|
|
792
|
+
* x: '100',
|
|
793
|
+
* y: '10',
|
|
794
|
+
* });
|
|
795
|
+
* console.log(`Verified: ${verified}`);
|
|
796
|
+
* ```
|
|
797
|
+
*/
|
|
798
|
+
async createProof(noirCode, inputs) {
|
|
799
|
+
const circuit = await this.compile(noirCode);
|
|
800
|
+
const proof = await this.prove(inputs, circuit);
|
|
801
|
+
const proofBytes = "proof" in proof && proof.proof instanceof Uint8Array ? proof.proof : proof.proof.bytes;
|
|
802
|
+
const pubInputs = Array.isArray(proof.publicInputs) ? proof.publicInputs : proof.publicInputs.hex;
|
|
803
|
+
const verified = await this.verify(proofBytes, pubInputs, circuit);
|
|
804
|
+
return { proof, verified };
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Get deployment data for the verifying key.
|
|
808
|
+
* Returns the data needed to deploy to the configured blockchain.
|
|
809
|
+
* Use with SolanaTransactionBuilder to build and send the transaction.
|
|
810
|
+
*
|
|
811
|
+
* @param options - Optional configuration
|
|
812
|
+
* @returns Deployment data that can be used with SolanaTransactionBuilder
|
|
813
|
+
* @throws Error if no chain is configured (offchain mode)
|
|
814
|
+
* @throws Error if prove() hasn't been called yet
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* ```typescript
|
|
818
|
+
* const izi = await IziNoir.init({ provider: Provider.Arkworks, chain: Chain.Solana });
|
|
819
|
+
* await izi.compile(noirCode);
|
|
820
|
+
* await izi.prove(inputs);
|
|
821
|
+
*
|
|
822
|
+
* // Get deployment data
|
|
823
|
+
* const deployData = izi.getDeployData();
|
|
824
|
+
*
|
|
825
|
+
* // Use with SolanaTransactionBuilder in your frontend
|
|
826
|
+
* const builder = new SolanaTransactionBuilder({ programId: deployData.programId });
|
|
827
|
+
* const { initVk, rentLamports, accountSize } = builder.buildInitAndVerifyInstructions(
|
|
828
|
+
* deployData.proofData,
|
|
829
|
+
* vkAccountPubkey,
|
|
830
|
+
* authority,
|
|
831
|
+
* payer
|
|
832
|
+
* );
|
|
833
|
+
* ```
|
|
834
|
+
*/
|
|
835
|
+
getDeployData(options) {
|
|
836
|
+
if (!this.chain) {
|
|
837
|
+
throw new Error("Cannot deploy in offchain mode. Initialize with a chain parameter.");
|
|
838
|
+
}
|
|
839
|
+
if (!this._verifyingKey || !this._lastProof) {
|
|
840
|
+
throw new Error("Must call prove() before getDeployData().");
|
|
841
|
+
}
|
|
842
|
+
if (this.chain !== "solana" /* Solana */) {
|
|
843
|
+
throw new Error(`Deployment for ${this.chain} is not yet supported.`);
|
|
844
|
+
}
|
|
845
|
+
return {
|
|
846
|
+
proofData: this._lastProof,
|
|
847
|
+
programId: options?.programId ?? "EYhRED7EuMyyVjx57aDXUD9h6ArnEKng64qtz8999KrS",
|
|
848
|
+
computeUnits: options?.computeUnits ?? 4e5
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/index.ts
|
|
854
|
+
init_SolanaFormatter();
|
|
855
|
+
init_Barretenberg();
|
|
856
|
+
init_ArkworksWasm();
|
|
857
|
+
|
|
858
|
+
// src/infra/parser/AcornParser.ts
|
|
859
|
+
var acorn = __toESM(require("acorn"), 1);
|
|
860
|
+
var walk = __toESM(require("acorn-walk"), 1);
|
|
861
|
+
|
|
862
|
+
// src/infra/parser/operatorMaps.ts
|
|
863
|
+
var BINARY_OPERATOR_MAP = {
|
|
864
|
+
// Equality
|
|
865
|
+
"==": "==",
|
|
866
|
+
"===": "==",
|
|
867
|
+
"!=": "!=",
|
|
868
|
+
"!==": "!=",
|
|
869
|
+
// Arithmetic
|
|
870
|
+
"+": "+",
|
|
871
|
+
"-": "-",
|
|
872
|
+
"*": "*",
|
|
873
|
+
"/": "/",
|
|
874
|
+
"%": "%",
|
|
875
|
+
// Comparison
|
|
876
|
+
"<": "<",
|
|
877
|
+
">": ">",
|
|
878
|
+
"<=": "<=",
|
|
879
|
+
">=": ">=",
|
|
880
|
+
// Logical (JS && and || map to Noir & and |)
|
|
881
|
+
"&&": "&",
|
|
882
|
+
"||": "|"
|
|
883
|
+
};
|
|
884
|
+
var UNARY_OPERATOR_MAP = {
|
|
885
|
+
"!": "!",
|
|
886
|
+
"-": "-"
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// src/infra/parser/utils.ts
|
|
890
|
+
var MUT_PREFIX = "mut_";
|
|
891
|
+
function stripMutPrefix(name) {
|
|
892
|
+
return name.startsWith(MUT_PREFIX) ? name.slice(MUT_PREFIX.length) : name;
|
|
893
|
+
}
|
|
894
|
+
function isMutable(name) {
|
|
895
|
+
return name.startsWith(MUT_PREFIX);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// src/infra/parser/ExpressionParser.ts
|
|
899
|
+
var ExpressionParser = class {
|
|
900
|
+
/**
|
|
901
|
+
* Parses an AST node into a circuit expression.
|
|
902
|
+
* @returns The parsed expression, or null if the node type is not recognized.
|
|
903
|
+
*/
|
|
904
|
+
parse(node) {
|
|
905
|
+
switch (node.type) {
|
|
906
|
+
case "Identifier":
|
|
907
|
+
return this.parseIdentifier(node);
|
|
908
|
+
case "Literal":
|
|
909
|
+
return this.parseLiteral(node);
|
|
910
|
+
case "BinaryExpression":
|
|
911
|
+
case "LogicalExpression":
|
|
912
|
+
return this.parseBinaryExpression(node);
|
|
913
|
+
case "MemberExpression":
|
|
914
|
+
return this.parseMemberExpression(node);
|
|
915
|
+
case "ArrayExpression":
|
|
916
|
+
return this.parseArrayExpression(node);
|
|
917
|
+
case "CallExpression":
|
|
918
|
+
return this.parseCallExpression(node);
|
|
919
|
+
case "UnaryExpression":
|
|
920
|
+
return this.parseUnaryExpression(node);
|
|
921
|
+
case "ConditionalExpression":
|
|
922
|
+
return this.parseConditionalExpression(node);
|
|
923
|
+
default:
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
parseIdentifier(node) {
|
|
928
|
+
const name = node.name;
|
|
929
|
+
return { kind: "identifier", name: stripMutPrefix(name) };
|
|
930
|
+
}
|
|
931
|
+
parseLiteral(node) {
|
|
932
|
+
return { kind: "literal", value: node.value };
|
|
933
|
+
}
|
|
934
|
+
parseBinaryExpression(node) {
|
|
935
|
+
const operator = BINARY_OPERATOR_MAP[node.operator];
|
|
936
|
+
if (!operator) {
|
|
937
|
+
throw new Error(`Unsupported operator: ${node.operator}`);
|
|
938
|
+
}
|
|
939
|
+
const left = this.parse(node.left);
|
|
940
|
+
const right = this.parse(node.right);
|
|
941
|
+
if (!left || !right) {
|
|
942
|
+
throw new Error("Could not parse binary expression operands");
|
|
943
|
+
}
|
|
944
|
+
return { kind: "binary", left, operator, right };
|
|
945
|
+
}
|
|
946
|
+
parseMemberExpression(node) {
|
|
947
|
+
const obj = node.object;
|
|
948
|
+
const prop = node.property;
|
|
949
|
+
if (node.computed) {
|
|
950
|
+
const object = this.parse(obj);
|
|
951
|
+
const index = this.parse(prop);
|
|
952
|
+
if (!object || !index) {
|
|
953
|
+
throw new Error("Could not parse member expression");
|
|
954
|
+
}
|
|
955
|
+
return {
|
|
956
|
+
kind: "member",
|
|
957
|
+
object,
|
|
958
|
+
index
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
if (prop.type === "Identifier" && prop.name === "length") {
|
|
962
|
+
const object = this.parse(obj);
|
|
963
|
+
if (!object) {
|
|
964
|
+
throw new Error("Could not parse object for .length");
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
kind: "call",
|
|
968
|
+
callee: object,
|
|
969
|
+
method: "len",
|
|
970
|
+
args: []
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
parseArrayExpression(node) {
|
|
976
|
+
const elements = [];
|
|
977
|
+
for (const elem of node.elements) {
|
|
978
|
+
if (elem) {
|
|
979
|
+
const parsed = this.parse(elem);
|
|
980
|
+
if (parsed) {
|
|
981
|
+
elements.push(parsed);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
kind: "array_literal",
|
|
987
|
+
elements
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
parseCallExpression(node) {
|
|
991
|
+
const callee = node.callee;
|
|
992
|
+
const args = [];
|
|
993
|
+
for (const arg of node.arguments) {
|
|
994
|
+
const parsed = this.parse(arg);
|
|
995
|
+
if (parsed) {
|
|
996
|
+
args.push(parsed);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (callee.type === "MemberExpression" && !callee.computed) {
|
|
1000
|
+
const obj = this.parse(callee.object);
|
|
1001
|
+
const method = callee.property.name;
|
|
1002
|
+
if (obj) {
|
|
1003
|
+
return {
|
|
1004
|
+
kind: "call",
|
|
1005
|
+
callee: obj,
|
|
1006
|
+
method,
|
|
1007
|
+
args
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const calleeExpr = this.parse(callee);
|
|
1012
|
+
if (calleeExpr) {
|
|
1013
|
+
return {
|
|
1014
|
+
kind: "call",
|
|
1015
|
+
callee: calleeExpr,
|
|
1016
|
+
args
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
parseUnaryExpression(node) {
|
|
1022
|
+
const unaryOp = UNARY_OPERATOR_MAP[node.operator];
|
|
1023
|
+
if (!unaryOp) {
|
|
1024
|
+
throw new Error(`Unsupported unary operator: ${node.operator}`);
|
|
1025
|
+
}
|
|
1026
|
+
if (node.operator === "-" && node.argument.type === "Literal") {
|
|
1027
|
+
const val = node.argument.value;
|
|
1028
|
+
if (typeof val === "number" || typeof val === "bigint") {
|
|
1029
|
+
return { kind: "literal", value: -val };
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const operand = this.parse(node.argument);
|
|
1033
|
+
if (!operand) {
|
|
1034
|
+
throw new Error("Could not parse unary expression operand");
|
|
1035
|
+
}
|
|
1036
|
+
return { kind: "unary", operator: unaryOp, operand };
|
|
1037
|
+
}
|
|
1038
|
+
parseConditionalExpression(node) {
|
|
1039
|
+
const condition = this.parse(node.test);
|
|
1040
|
+
const consequent = this.parse(node.consequent);
|
|
1041
|
+
const alternate = this.parse(node.alternate);
|
|
1042
|
+
if (!condition || !consequent || !alternate) {
|
|
1043
|
+
throw new Error("Could not parse ternary expression");
|
|
1044
|
+
}
|
|
1045
|
+
return {
|
|
1046
|
+
kind: "if_expr",
|
|
1047
|
+
condition,
|
|
1048
|
+
consequent,
|
|
1049
|
+
alternate
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
// src/infra/parser/StatementParser.ts
|
|
1055
|
+
var StatementParser = class {
|
|
1056
|
+
constructor(exprParser, forLoopParser) {
|
|
1057
|
+
this.exprParser = exprParser;
|
|
1058
|
+
this.forLoopParser = forLoopParser;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Parses a statement AST node.
|
|
1062
|
+
* @returns The parsed statement, or null if the node type is not recognized.
|
|
1063
|
+
*/
|
|
1064
|
+
parse(node) {
|
|
1065
|
+
switch (node.type) {
|
|
1066
|
+
case "VariableDeclaration":
|
|
1067
|
+
return this.parseVariableDeclaration(node);
|
|
1068
|
+
case "ExpressionStatement":
|
|
1069
|
+
return this.parseExpressionStatement(node);
|
|
1070
|
+
case "IfStatement":
|
|
1071
|
+
return this.parseIfStatement(node);
|
|
1072
|
+
case "ForStatement":
|
|
1073
|
+
return this.forLoopParser.parse(node, (n) => this.parseBlock(n));
|
|
1074
|
+
default:
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Parses a block or single statement into an array of statements.
|
|
1080
|
+
*/
|
|
1081
|
+
parseBlock(node) {
|
|
1082
|
+
if (node.type === "BlockStatement") {
|
|
1083
|
+
const statements = [];
|
|
1084
|
+
for (const stmt of node.body) {
|
|
1085
|
+
const parsed = this.parse(stmt);
|
|
1086
|
+
if (parsed) {
|
|
1087
|
+
statements.push(parsed);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return statements;
|
|
1091
|
+
} else {
|
|
1092
|
+
const parsed = this.parse(node);
|
|
1093
|
+
return parsed ? [parsed] : [];
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Parses variable declarations: let x = 5; const y = 10;
|
|
1098
|
+
* Uses mut_ prefix convention for mutable variables.
|
|
1099
|
+
*/
|
|
1100
|
+
parseVariableDeclaration(node) {
|
|
1101
|
+
const declaration = node.declarations[0];
|
|
1102
|
+
if (!declaration || declaration.type !== "VariableDeclarator") {
|
|
1103
|
+
throw new Error("Invalid variable declaration");
|
|
1104
|
+
}
|
|
1105
|
+
const id = declaration.id;
|
|
1106
|
+
if (id.type !== "Identifier") {
|
|
1107
|
+
throw new Error("Variable declaration must have a simple identifier");
|
|
1108
|
+
}
|
|
1109
|
+
const init = declaration.init;
|
|
1110
|
+
if (!init) {
|
|
1111
|
+
throw new Error("Variable declaration must have an initializer");
|
|
1112
|
+
}
|
|
1113
|
+
const initializer = this.exprParser.parse(init);
|
|
1114
|
+
if (!initializer) {
|
|
1115
|
+
throw new Error("Could not parse variable initializer");
|
|
1116
|
+
}
|
|
1117
|
+
const name = id.name;
|
|
1118
|
+
return {
|
|
1119
|
+
kind: "variable_declaration",
|
|
1120
|
+
name: stripMutPrefix(name),
|
|
1121
|
+
mutable: isMutable(name),
|
|
1122
|
+
initializer
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Parses expression statements: assert(), assignments.
|
|
1127
|
+
*/
|
|
1128
|
+
parseExpressionStatement(node) {
|
|
1129
|
+
const expr = node.expression;
|
|
1130
|
+
if (expr.type === "CallExpression") {
|
|
1131
|
+
const callee = expr.callee;
|
|
1132
|
+
if (callee.type === "Identifier" && callee.name === "assert") {
|
|
1133
|
+
return this.parseAssertCall(expr);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (expr.type === "AssignmentExpression") {
|
|
1137
|
+
return this.parseAssignment(expr);
|
|
1138
|
+
}
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Parses assert() function calls.
|
|
1143
|
+
*/
|
|
1144
|
+
parseAssertCall(expr) {
|
|
1145
|
+
const args = expr.arguments;
|
|
1146
|
+
if (args.length === 0) {
|
|
1147
|
+
throw new Error("assert() requires at least one argument");
|
|
1148
|
+
}
|
|
1149
|
+
const condition = this.exprParser.parse(args[0]);
|
|
1150
|
+
if (!condition) {
|
|
1151
|
+
throw new Error("Could not parse assert condition");
|
|
1152
|
+
}
|
|
1153
|
+
const message = args.length > 1 && args[1].type === "Literal" ? String(args[1].value) : void 0;
|
|
1154
|
+
return { kind: "assert", condition, message };
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Parses assignment expressions: x = 5;
|
|
1158
|
+
*/
|
|
1159
|
+
parseAssignment(expr) {
|
|
1160
|
+
const left = expr.left;
|
|
1161
|
+
const right = expr.right;
|
|
1162
|
+
if (left.type !== "Identifier") {
|
|
1163
|
+
throw new Error("Assignment target must be an identifier");
|
|
1164
|
+
}
|
|
1165
|
+
const value = this.exprParser.parse(right);
|
|
1166
|
+
if (!value) {
|
|
1167
|
+
throw new Error("Could not parse assignment value");
|
|
1168
|
+
}
|
|
1169
|
+
const name = left.name;
|
|
1170
|
+
return {
|
|
1171
|
+
kind: "assignment",
|
|
1172
|
+
target: stripMutPrefix(name),
|
|
1173
|
+
value
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Parses if statements: if (condition) { ... } else { ... }
|
|
1178
|
+
*/
|
|
1179
|
+
parseIfStatement(node) {
|
|
1180
|
+
const condition = this.exprParser.parse(node.test);
|
|
1181
|
+
if (!condition) {
|
|
1182
|
+
throw new Error("Could not parse if condition");
|
|
1183
|
+
}
|
|
1184
|
+
const consequent = this.parseBlock(node.consequent);
|
|
1185
|
+
const alternate = node.alternate ? this.parseBlock(node.alternate) : void 0;
|
|
1186
|
+
return {
|
|
1187
|
+
kind: "if_statement",
|
|
1188
|
+
condition,
|
|
1189
|
+
consequent,
|
|
1190
|
+
alternate
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
// src/infra/parser/ForLoopParser.ts
|
|
1196
|
+
var ForLoopParser = class {
|
|
1197
|
+
constructor(exprParser) {
|
|
1198
|
+
this.exprParser = exprParser;
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Parses a for statement into a circuit for_statement.
|
|
1202
|
+
* @param node The ForStatement AST node
|
|
1203
|
+
* @param parseBlock Function to parse the loop body
|
|
1204
|
+
*/
|
|
1205
|
+
parse(node, parseBlock) {
|
|
1206
|
+
const { variable, start } = this.validateInit(node.init);
|
|
1207
|
+
const { end, inclusive } = this.validateTest(node.test, variable);
|
|
1208
|
+
this.validateUpdate(node.update, variable);
|
|
1209
|
+
const body = parseBlock(node.body);
|
|
1210
|
+
return {
|
|
1211
|
+
kind: "for_statement",
|
|
1212
|
+
variable,
|
|
1213
|
+
start,
|
|
1214
|
+
end,
|
|
1215
|
+
inclusive,
|
|
1216
|
+
body
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Validates the for loop initializer.
|
|
1221
|
+
* Must be: `let i = start`
|
|
1222
|
+
*/
|
|
1223
|
+
validateInit(init) {
|
|
1224
|
+
if (!init || init.type !== "VariableDeclaration") {
|
|
1225
|
+
throw new Error("For loop init must be a variable declaration (let i = start)");
|
|
1226
|
+
}
|
|
1227
|
+
const declaration = init.declarations[0];
|
|
1228
|
+
if (!declaration || declaration.id.type !== "Identifier") {
|
|
1229
|
+
throw new Error("For loop must declare a simple variable");
|
|
1230
|
+
}
|
|
1231
|
+
const variable = declaration.id.name;
|
|
1232
|
+
const startInit = declaration.init;
|
|
1233
|
+
if (!startInit) {
|
|
1234
|
+
throw new Error("For loop variable must have an initializer");
|
|
1235
|
+
}
|
|
1236
|
+
const start = this.exprParser.parse(startInit);
|
|
1237
|
+
if (!start) {
|
|
1238
|
+
throw new Error("Could not parse for loop start value");
|
|
1239
|
+
}
|
|
1240
|
+
return { variable, start };
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Validates the for loop test condition.
|
|
1244
|
+
* Must be: `i < end` or `i <= end`
|
|
1245
|
+
*/
|
|
1246
|
+
validateTest(test, variable) {
|
|
1247
|
+
if (!test || test.type !== "BinaryExpression") {
|
|
1248
|
+
throw new Error("For loop test must be a comparison (i < end or i <= end)");
|
|
1249
|
+
}
|
|
1250
|
+
const testLeft = test.left;
|
|
1251
|
+
if (testLeft.type !== "Identifier" || testLeft.name !== variable) {
|
|
1252
|
+
throw new Error("For loop test must compare the loop variable");
|
|
1253
|
+
}
|
|
1254
|
+
const operator = test.operator;
|
|
1255
|
+
if (operator !== "<" && operator !== "<=") {
|
|
1256
|
+
throw new Error("For loop test must use < or <= operator");
|
|
1257
|
+
}
|
|
1258
|
+
const end = this.exprParser.parse(test.right);
|
|
1259
|
+
if (!end) {
|
|
1260
|
+
throw new Error("Could not parse for loop end value");
|
|
1261
|
+
}
|
|
1262
|
+
return { end, inclusive: operator === "<=" };
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Validates the for loop update expression.
|
|
1266
|
+
* Must be: `i++`, `++i`, or `i = i + 1`
|
|
1267
|
+
*/
|
|
1268
|
+
validateUpdate(update, variable) {
|
|
1269
|
+
if (!update) {
|
|
1270
|
+
throw new Error("For loop must have an update expression");
|
|
1271
|
+
}
|
|
1272
|
+
if (!this.isSimpleIncrement(update, variable)) {
|
|
1273
|
+
throw new Error("For loop update must be i++ or i = i + 1 or ++i");
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Checks if an expression is a simple increment of the variable.
|
|
1278
|
+
* Accepts: i++, ++i, i = i + 1
|
|
1279
|
+
*/
|
|
1280
|
+
isSimpleIncrement(node, variable) {
|
|
1281
|
+
if (node.type === "UpdateExpression") {
|
|
1282
|
+
const arg = node.argument;
|
|
1283
|
+
return node.operator === "++" && arg.type === "Identifier" && arg.name === variable;
|
|
1284
|
+
}
|
|
1285
|
+
if (node.type === "AssignmentExpression") {
|
|
1286
|
+
const left = node.left;
|
|
1287
|
+
const right = node.right;
|
|
1288
|
+
if (left.type !== "Identifier" || left.name !== variable) {
|
|
1289
|
+
return false;
|
|
1290
|
+
}
|
|
1291
|
+
if (right.type === "BinaryExpression") {
|
|
1292
|
+
const binLeft = right.left;
|
|
1293
|
+
const binRight = right.right;
|
|
1294
|
+
return right.operator === "+" && binLeft.type === "Identifier" && binLeft.name === variable && binRight.type === "Literal" && binRight.value === 1;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return false;
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
// src/infra/parser/AcornParser.ts
|
|
1302
|
+
var AcornParser = class {
|
|
1303
|
+
exprParser = new ExpressionParser();
|
|
1304
|
+
forLoopParser = new ForLoopParser(this.exprParser);
|
|
1305
|
+
stmtParser = new StatementParser(this.exprParser, this.forLoopParser);
|
|
1306
|
+
/**
|
|
1307
|
+
* Parses a circuit function into public/private parameters and statements.
|
|
1308
|
+
*
|
|
1309
|
+
* @param fn The circuit function with signature ([public], [private]) => { ... }
|
|
1310
|
+
* @param _publicInputs Public input values (used for validation, not parsing)
|
|
1311
|
+
* @param _privateInputs Private input values (used for validation, not parsing)
|
|
1312
|
+
*/
|
|
1313
|
+
parse(fn, _publicInputs, _privateInputs) {
|
|
1314
|
+
const fnSource = fn.toString();
|
|
1315
|
+
const ast = acorn.parse(`(${fnSource})`, {
|
|
1316
|
+
ecmaVersion: 2022,
|
|
1317
|
+
sourceType: "module"
|
|
1318
|
+
});
|
|
1319
|
+
const fnNode = this.findFunctionNode(ast);
|
|
1320
|
+
const { publicParams, privateParams } = this.extractParameters(fnNode);
|
|
1321
|
+
const statements = this.parseBody(fnNode);
|
|
1322
|
+
return { publicParams, privateParams, statements };
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Finds the arrow function or function expression in the AST.
|
|
1326
|
+
*/
|
|
1327
|
+
findFunctionNode(ast) {
|
|
1328
|
+
let fnNode = null;
|
|
1329
|
+
walk.simple(ast, {
|
|
1330
|
+
ArrowFunctionExpression(node) {
|
|
1331
|
+
fnNode = node;
|
|
1332
|
+
},
|
|
1333
|
+
FunctionExpression(node) {
|
|
1334
|
+
fnNode = node;
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
if (!fnNode) {
|
|
1338
|
+
throw new Error("Could not find function in source");
|
|
1339
|
+
}
|
|
1340
|
+
return fnNode;
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Extracts public and private parameters from the function signature.
|
|
1344
|
+
* Expects pattern: ([pub1, pub2], [priv1, priv2]) => { ... }
|
|
1345
|
+
*/
|
|
1346
|
+
extractParameters(fnNode) {
|
|
1347
|
+
const params = fnNode.params;
|
|
1348
|
+
if (params.length !== 2) {
|
|
1349
|
+
throw new Error(
|
|
1350
|
+
"Circuit function must have exactly 2 parameters: (publicArgs, privateArgs)"
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
const publicParams = [];
|
|
1354
|
+
const privateParams = [];
|
|
1355
|
+
const pubParam = params[0];
|
|
1356
|
+
if (pubParam.type === "ArrayPattern") {
|
|
1357
|
+
pubParam.elements.forEach((elem, idx) => {
|
|
1358
|
+
if (elem && elem.type === "Identifier") {
|
|
1359
|
+
publicParams.push({ name: elem.name, index: idx });
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
const privParam = params[1];
|
|
1364
|
+
if (privParam.type === "ArrayPattern") {
|
|
1365
|
+
privParam.elements.forEach((elem, idx) => {
|
|
1366
|
+
if (elem && elem.type === "Identifier") {
|
|
1367
|
+
privateParams.push({ name: elem.name, index: idx });
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
return { publicParams, privateParams };
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Parses the function body into statements.
|
|
1375
|
+
*/
|
|
1376
|
+
parseBody(fnNode) {
|
|
1377
|
+
const body = fnNode.body;
|
|
1378
|
+
const statements = [];
|
|
1379
|
+
if (body.type === "BlockStatement") {
|
|
1380
|
+
for (const stmt of body.body) {
|
|
1381
|
+
const parsed = this.stmtParser.parse(stmt);
|
|
1382
|
+
if (parsed) {
|
|
1383
|
+
statements.push(parsed);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
} else {
|
|
1387
|
+
const expr = this.exprParser.parse(body);
|
|
1388
|
+
if (expr) {
|
|
1389
|
+
statements.push({ kind: "assert", condition: expr });
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return statements;
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
// src/container.ts
|
|
1397
|
+
init_Barretenberg();
|
|
1398
|
+
init_ArkworksWasm();
|
|
1399
|
+
function createDefaultContainer() {
|
|
1400
|
+
return {
|
|
1401
|
+
parser: new AcornParser(),
|
|
1402
|
+
provingSystem: new Barretenberg()
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
function createArkworksWasmContainer(config) {
|
|
1406
|
+
return {
|
|
1407
|
+
parser: new AcornParser(),
|
|
1408
|
+
provingSystem: new ArkworksWasm(config)
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// src/application/services/NoirGenerator.ts
|
|
1413
|
+
function generateNoir(circuit) {
|
|
1414
|
+
const params = generateParams(circuit);
|
|
1415
|
+
const body = generateBody(circuit.statements);
|
|
1416
|
+
return `fn main(${params}) {
|
|
1417
|
+
${body}
|
|
1418
|
+
}
|
|
1419
|
+
`;
|
|
1420
|
+
}
|
|
1421
|
+
function generateParams(circuit) {
|
|
1422
|
+
const allParams = [];
|
|
1423
|
+
for (const param of circuit.privateParams) {
|
|
1424
|
+
allParams.push(`${param.name}: Field`);
|
|
1425
|
+
}
|
|
1426
|
+
for (const param of circuit.publicParams) {
|
|
1427
|
+
allParams.push(`${param.name}: pub Field`);
|
|
1428
|
+
}
|
|
1429
|
+
return allParams.join(", ");
|
|
1430
|
+
}
|
|
1431
|
+
function generateBody(statements, indent = 1) {
|
|
1432
|
+
const lines = [];
|
|
1433
|
+
const indentStr = " ".repeat(indent);
|
|
1434
|
+
for (const stmt of statements) {
|
|
1435
|
+
const generated = generateStatement(stmt, indent);
|
|
1436
|
+
if (generated.includes("\n")) {
|
|
1437
|
+
lines.push(generated);
|
|
1438
|
+
} else {
|
|
1439
|
+
lines.push(`${indentStr}${generated}`);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return lines.join("\n");
|
|
1443
|
+
}
|
|
1444
|
+
function generateStatement(stmt, indent = 1) {
|
|
1445
|
+
const indentStr = " ".repeat(indent);
|
|
1446
|
+
switch (stmt.kind) {
|
|
1447
|
+
case "assert": {
|
|
1448
|
+
const condition = generateExpr(stmt.condition);
|
|
1449
|
+
if (stmt.message) {
|
|
1450
|
+
return `assert(${condition}, "${stmt.message}");`;
|
|
1451
|
+
}
|
|
1452
|
+
return `assert(${condition});`;
|
|
1453
|
+
}
|
|
1454
|
+
case "variable_declaration": {
|
|
1455
|
+
const init = generateExpr(stmt.initializer);
|
|
1456
|
+
const mutKeyword = stmt.mutable ? "let mut" : "let";
|
|
1457
|
+
const typeStr = inferType(stmt.initializer);
|
|
1458
|
+
return `${mutKeyword} ${stmt.name}: ${typeStr} = ${init};`;
|
|
1459
|
+
}
|
|
1460
|
+
case "assignment": {
|
|
1461
|
+
const value = generateExpr(stmt.value);
|
|
1462
|
+
return `${stmt.target} = ${value};`;
|
|
1463
|
+
}
|
|
1464
|
+
case "if_statement": {
|
|
1465
|
+
const condition = generateExpr(stmt.condition);
|
|
1466
|
+
const consequentBody = generateBody(stmt.consequent, indent + 1);
|
|
1467
|
+
let result = `${indentStr}if ${condition} {
|
|
1468
|
+
${consequentBody}
|
|
1469
|
+
${indentStr}}`;
|
|
1470
|
+
if (stmt.alternate && stmt.alternate.length > 0) {
|
|
1471
|
+
const alternateBody = generateBody(stmt.alternate, indent + 1);
|
|
1472
|
+
result += ` else {
|
|
1473
|
+
${alternateBody}
|
|
1474
|
+
${indentStr}}`;
|
|
1475
|
+
}
|
|
1476
|
+
return result;
|
|
1477
|
+
}
|
|
1478
|
+
case "for_statement": {
|
|
1479
|
+
const start = generateExpr(stmt.start);
|
|
1480
|
+
const end = generateExpr(stmt.end);
|
|
1481
|
+
const rangeOp = stmt.inclusive ? "..=" : "..";
|
|
1482
|
+
const bodyContent = generateBody(stmt.body, indent + 1);
|
|
1483
|
+
return `${indentStr}for ${stmt.variable} in ${start}${rangeOp}${end} {
|
|
1484
|
+
${bodyContent}
|
|
1485
|
+
${indentStr}}`;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
function generateExpr(expr) {
|
|
1490
|
+
switch (expr.kind) {
|
|
1491
|
+
case "identifier":
|
|
1492
|
+
return expr.name;
|
|
1493
|
+
case "literal":
|
|
1494
|
+
return formatLiteral(expr.value);
|
|
1495
|
+
case "binary": {
|
|
1496
|
+
const left = generateExpr(expr.left);
|
|
1497
|
+
const right = generateExpr(expr.right);
|
|
1498
|
+
return `${left} ${expr.operator} ${right}`;
|
|
1499
|
+
}
|
|
1500
|
+
case "unary": {
|
|
1501
|
+
const operand = generateExpr(expr.operand);
|
|
1502
|
+
if (expr.operand.kind === "binary") {
|
|
1503
|
+
return `${expr.operator}(${operand})`;
|
|
1504
|
+
}
|
|
1505
|
+
return `${expr.operator}${operand}`;
|
|
1506
|
+
}
|
|
1507
|
+
case "member": {
|
|
1508
|
+
const object = generateExpr(expr.object);
|
|
1509
|
+
const index = generateExpr(expr.index);
|
|
1510
|
+
return `${object}[${index}]`;
|
|
1511
|
+
}
|
|
1512
|
+
case "array_literal": {
|
|
1513
|
+
const elements = expr.elements.map((e) => generateExpr(e)).join(", ");
|
|
1514
|
+
return `[${elements}]`;
|
|
1515
|
+
}
|
|
1516
|
+
case "call": {
|
|
1517
|
+
const args = expr.args.map((a) => generateExpr(a)).join(", ");
|
|
1518
|
+
if (expr.method) {
|
|
1519
|
+
const callee2 = generateExpr(expr.callee);
|
|
1520
|
+
return `${callee2}.${expr.method}(${args})`;
|
|
1521
|
+
}
|
|
1522
|
+
const callee = generateExpr(expr.callee);
|
|
1523
|
+
return `${callee}(${args})`;
|
|
1524
|
+
}
|
|
1525
|
+
case "if_expr": {
|
|
1526
|
+
const condition = generateExpr(expr.condition);
|
|
1527
|
+
const consequent = generateExpr(expr.consequent);
|
|
1528
|
+
const alternate = generateExpr(expr.alternate);
|
|
1529
|
+
return `if ${condition} { ${consequent} } else { ${alternate} }`;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
function formatLiteral(value) {
|
|
1534
|
+
if (typeof value === "bigint") {
|
|
1535
|
+
return value.toString();
|
|
1536
|
+
}
|
|
1537
|
+
if (typeof value === "number") {
|
|
1538
|
+
return value.toString();
|
|
1539
|
+
}
|
|
1540
|
+
if (typeof value === "string" && value.startsWith("0x")) {
|
|
1541
|
+
return value;
|
|
1542
|
+
}
|
|
1543
|
+
return `"${value}"`;
|
|
1544
|
+
}
|
|
1545
|
+
function inferType(expr) {
|
|
1546
|
+
switch (expr.kind) {
|
|
1547
|
+
case "array_literal":
|
|
1548
|
+
const elementType = expr.elements.length > 0 ? inferType(expr.elements[0]) : "Field";
|
|
1549
|
+
return `[${elementType}; ${expr.elements.length}]`;
|
|
1550
|
+
case "literal":
|
|
1551
|
+
if (typeof expr.value === "boolean") {
|
|
1552
|
+
return "bool";
|
|
1553
|
+
}
|
|
1554
|
+
return "Field";
|
|
1555
|
+
case "if_expr":
|
|
1556
|
+
return inferType(expr.consequent);
|
|
1557
|
+
default:
|
|
1558
|
+
return "Field";
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// src/application/CreateProofUseCase.ts
|
|
1563
|
+
var CreateProofUseCase = class {
|
|
1564
|
+
constructor(deps) {
|
|
1565
|
+
this.deps = deps;
|
|
1566
|
+
}
|
|
1567
|
+
async execute(publicInputs, privateInputs, circuitFn) {
|
|
1568
|
+
const totalStart = performance.now();
|
|
1569
|
+
const timings = {
|
|
1570
|
+
parseMs: 0,
|
|
1571
|
+
generateMs: 0,
|
|
1572
|
+
compileMs: 0,
|
|
1573
|
+
witnessMs: 0,
|
|
1574
|
+
proofMs: 0,
|
|
1575
|
+
verifyMs: 0,
|
|
1576
|
+
totalMs: 0
|
|
1577
|
+
};
|
|
1578
|
+
const parseStart = performance.now();
|
|
1579
|
+
const parsed = this.deps.parser.parse(circuitFn, publicInputs, privateInputs);
|
|
1580
|
+
timings.parseMs = performance.now() - parseStart;
|
|
1581
|
+
const generateStart = performance.now();
|
|
1582
|
+
const noirCode = generateNoir(parsed);
|
|
1583
|
+
timings.generateMs = performance.now() - generateStart;
|
|
1584
|
+
const compileStart = performance.now();
|
|
1585
|
+
const circuit = await this.deps.provingSystem.compile(noirCode);
|
|
1586
|
+
timings.compileMs = performance.now() - compileStart;
|
|
1587
|
+
const toNoirInput = (val) => typeof val === "bigint" ? val.toString() : val;
|
|
1588
|
+
const inputs = {};
|
|
1589
|
+
for (const param of parsed.privateParams) {
|
|
1590
|
+
inputs[param.name] = toNoirInput(privateInputs[param.index]);
|
|
1591
|
+
}
|
|
1592
|
+
for (const param of parsed.publicParams) {
|
|
1593
|
+
inputs[param.name] = toNoirInput(publicInputs[param.index]);
|
|
1594
|
+
}
|
|
1595
|
+
const witnessStart = performance.now();
|
|
1596
|
+
const { proof, publicInputs: proofPublicInputs } = await this.deps.provingSystem.generateProof(circuit, inputs);
|
|
1597
|
+
const proofEnd = performance.now();
|
|
1598
|
+
const proveTime = proofEnd - witnessStart;
|
|
1599
|
+
timings.witnessMs = proveTime * 0.1;
|
|
1600
|
+
timings.proofMs = proveTime * 0.9;
|
|
1601
|
+
const verifyStart = performance.now();
|
|
1602
|
+
const verified = await this.deps.provingSystem.verifyProof(circuit, proof, proofPublicInputs);
|
|
1603
|
+
timings.verifyMs = performance.now() - verifyStart;
|
|
1604
|
+
timings.totalMs = performance.now() - totalStart;
|
|
1605
|
+
return {
|
|
1606
|
+
proof,
|
|
1607
|
+
publicInputs: proofPublicInputs,
|
|
1608
|
+
verified,
|
|
1609
|
+
noirCode,
|
|
1610
|
+
timings
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
// src/providers/solana.ts
|
|
1616
|
+
var G1_SIZE2 = 64;
|
|
1617
|
+
var G2_SIZE2 = 128;
|
|
1618
|
+
var FIELD_SIZE = 32;
|
|
1619
|
+
var PROOF_SIZE = 256;
|
|
1620
|
+
var IZI_NOIR_PROGRAM_ID = "EYhRED7EuMyyVjx57aDXUD9h6ArnEKng64qtz8999KrS";
|
|
1621
|
+
function parseVerifyingKey(vkBase64, nrPubinputs) {
|
|
1622
|
+
const vkBytes = base64ToBytes(vkBase64);
|
|
1623
|
+
const expectedLen = G1_SIZE2 + G2_SIZE2 * 3 + G1_SIZE2 * (nrPubinputs + 1);
|
|
1624
|
+
if (vkBytes.length !== expectedLen) {
|
|
1625
|
+
throw new Error(
|
|
1626
|
+
`Invalid VK size: expected ${expectedLen} bytes for ${nrPubinputs} public inputs, got ${vkBytes.length}`
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
let offset = 0;
|
|
1630
|
+
const alphaG1 = vkBytes.slice(offset, offset + G1_SIZE2);
|
|
1631
|
+
offset += G1_SIZE2;
|
|
1632
|
+
const betaG2 = vkBytes.slice(offset, offset + G2_SIZE2);
|
|
1633
|
+
offset += G2_SIZE2;
|
|
1634
|
+
const gammaG2 = vkBytes.slice(offset, offset + G2_SIZE2);
|
|
1635
|
+
offset += G2_SIZE2;
|
|
1636
|
+
const deltaG2 = vkBytes.slice(offset, offset + G2_SIZE2);
|
|
1637
|
+
offset += G2_SIZE2;
|
|
1638
|
+
const k = [];
|
|
1639
|
+
for (let i = 0; i <= nrPubinputs; i++) {
|
|
1640
|
+
k.push(vkBytes.slice(offset, offset + G1_SIZE2));
|
|
1641
|
+
offset += G1_SIZE2;
|
|
1642
|
+
}
|
|
1643
|
+
return { alphaG1, betaG2, gammaG2, deltaG2, k };
|
|
1644
|
+
}
|
|
1645
|
+
function parseProof(proofBase64) {
|
|
1646
|
+
const proofBytes = base64ToBytes(proofBase64);
|
|
1647
|
+
if (proofBytes.length !== PROOF_SIZE) {
|
|
1648
|
+
throw new Error(`Invalid proof size: expected ${PROOF_SIZE} bytes, got ${proofBytes.length}`);
|
|
1649
|
+
}
|
|
1650
|
+
return {
|
|
1651
|
+
a: proofBytes.slice(0, G1_SIZE2),
|
|
1652
|
+
b: proofBytes.slice(G1_SIZE2, G1_SIZE2 + G2_SIZE2),
|
|
1653
|
+
c: proofBytes.slice(G1_SIZE2 + G2_SIZE2, PROOF_SIZE)
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
function parsePublicInputs(inputs) {
|
|
1657
|
+
return inputs.map((input) => {
|
|
1658
|
+
if (input.startsWith("0x")) {
|
|
1659
|
+
const hex2 = input.slice(2).padStart(64, "0");
|
|
1660
|
+
return hexToBytes2(hex2);
|
|
1661
|
+
}
|
|
1662
|
+
const num = BigInt(input);
|
|
1663
|
+
const hex = num.toString(16).padStart(64, "0");
|
|
1664
|
+
return hexToBytes2(hex);
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
function calculateVkAccountSize(nrPubinputs) {
|
|
1668
|
+
const fixedSize = 8 + 32 + 1 + G1_SIZE2 + G2_SIZE2 * 3 + 4;
|
|
1669
|
+
return fixedSize + (nrPubinputs + 1) * G1_SIZE2;
|
|
1670
|
+
}
|
|
1671
|
+
function calculateVkAccountRent(nrPubinputs, rentExemptionPerByte = 6960) {
|
|
1672
|
+
const size = calculateVkAccountSize(nrPubinputs);
|
|
1673
|
+
return size * rentExemptionPerByte;
|
|
1674
|
+
}
|
|
1675
|
+
function base64ToBytes(base64) {
|
|
1676
|
+
if (typeof atob === "function") {
|
|
1677
|
+
const binary = atob(base64);
|
|
1678
|
+
const bytes = new Uint8Array(binary.length);
|
|
1679
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1680
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1681
|
+
}
|
|
1682
|
+
return bytes;
|
|
1683
|
+
}
|
|
1684
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
1685
|
+
}
|
|
1686
|
+
function hexToBytes2(hex) {
|
|
1687
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1688
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
1689
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1690
|
+
bytes[i] = parseInt(cleanHex.substring(i * 2, i * 2 + 2), 16);
|
|
1691
|
+
}
|
|
1692
|
+
return bytes;
|
|
1693
|
+
}
|
|
1694
|
+
function buildInitVkFromBytesData(nrPubinputs, vkBytes) {
|
|
1695
|
+
const discriminator = new Uint8Array([
|
|
1696
|
+
166,
|
|
1697
|
+
58,
|
|
1698
|
+
82,
|
|
1699
|
+
247,
|
|
1700
|
+
19,
|
|
1701
|
+
129,
|
|
1702
|
+
138,
|
|
1703
|
+
59
|
|
1704
|
+
]);
|
|
1705
|
+
const vkLen = new Uint8Array(4);
|
|
1706
|
+
new DataView(vkLen.buffer).setUint32(0, vkBytes.length, true);
|
|
1707
|
+
const data = new Uint8Array(discriminator.length + 1 + 4 + vkBytes.length);
|
|
1708
|
+
let offset = 0;
|
|
1709
|
+
data.set(discriminator, offset);
|
|
1710
|
+
offset += discriminator.length;
|
|
1711
|
+
data[offset] = nrPubinputs;
|
|
1712
|
+
offset += 1;
|
|
1713
|
+
data.set(vkLen, offset);
|
|
1714
|
+
offset += 4;
|
|
1715
|
+
data.set(vkBytes, offset);
|
|
1716
|
+
return data;
|
|
1717
|
+
}
|
|
1718
|
+
function buildVerifyProofData(proofBytes, publicInputs) {
|
|
1719
|
+
const discriminator = new Uint8Array([
|
|
1720
|
+
30,
|
|
1721
|
+
8,
|
|
1722
|
+
242,
|
|
1723
|
+
54,
|
|
1724
|
+
68,
|
|
1725
|
+
154,
|
|
1726
|
+
75,
|
|
1727
|
+
92
|
|
1728
|
+
]);
|
|
1729
|
+
if (proofBytes.length !== PROOF_SIZE) {
|
|
1730
|
+
throw new Error(`Invalid proof size: expected ${PROOF_SIZE}, got ${proofBytes.length}`);
|
|
1731
|
+
}
|
|
1732
|
+
const proofVecLen = 4 + proofBytes.length;
|
|
1733
|
+
const inputsVecLen = 4 + publicInputs.length * FIELD_SIZE;
|
|
1734
|
+
const totalSize = discriminator.length + proofVecLen + inputsVecLen;
|
|
1735
|
+
const data = new Uint8Array(totalSize);
|
|
1736
|
+
let offset = 0;
|
|
1737
|
+
data.set(discriminator, offset);
|
|
1738
|
+
offset += discriminator.length;
|
|
1739
|
+
new DataView(data.buffer).setUint32(offset, proofBytes.length, true);
|
|
1740
|
+
offset += 4;
|
|
1741
|
+
data.set(proofBytes, offset);
|
|
1742
|
+
offset += proofBytes.length;
|
|
1743
|
+
new DataView(data.buffer).setUint32(offset, publicInputs.length, true);
|
|
1744
|
+
offset += 4;
|
|
1745
|
+
for (const input of publicInputs) {
|
|
1746
|
+
if (input.length !== FIELD_SIZE) {
|
|
1747
|
+
throw new Error(`Invalid public input size: expected ${FIELD_SIZE}, got ${input.length}`);
|
|
1748
|
+
}
|
|
1749
|
+
data.set(input, offset);
|
|
1750
|
+
offset += FIELD_SIZE;
|
|
1751
|
+
}
|
|
1752
|
+
return data;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// src/solana/TransactionBuilder.ts
|
|
1756
|
+
var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
|
|
1757
|
+
var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
|
|
1758
|
+
var SolanaTransactionBuilder = class {
|
|
1759
|
+
programId;
|
|
1760
|
+
computeUnits;
|
|
1761
|
+
priorityFee;
|
|
1762
|
+
constructor(config = {}) {
|
|
1763
|
+
this.programId = config.programId ?? IZI_NOIR_PROGRAM_ID;
|
|
1764
|
+
this.computeUnits = config.computeUnits ?? 4e5;
|
|
1765
|
+
this.priorityFee = config.priorityFee ?? 0;
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Builds the instruction data for initializing a VK account.
|
|
1769
|
+
*
|
|
1770
|
+
* @param solanaProofData - Proof data from `izi.proveForSolana()`
|
|
1771
|
+
* @param accounts - Account public keys
|
|
1772
|
+
* @returns Instruction data ready for transaction building
|
|
1773
|
+
*
|
|
1774
|
+
* @example
|
|
1775
|
+
* ```typescript
|
|
1776
|
+
* const initInstruction = builder.buildInitVkInstruction(solanaProofData, {
|
|
1777
|
+
* vkAccount: vkKeypair.publicKey.toBase58(),
|
|
1778
|
+
* authority: wallet.publicKey.toBase58(),
|
|
1779
|
+
* payer: wallet.publicKey.toBase58(),
|
|
1780
|
+
* });
|
|
1781
|
+
* ```
|
|
1782
|
+
*/
|
|
1783
|
+
buildInitVkInstruction(solanaProofData, accounts) {
|
|
1784
|
+
const data = buildInitVkFromBytesData(
|
|
1785
|
+
solanaProofData.verifyingKey.nrPublicInputs,
|
|
1786
|
+
solanaProofData.verifyingKey.bytes
|
|
1787
|
+
);
|
|
1788
|
+
return {
|
|
1789
|
+
data,
|
|
1790
|
+
programId: this.programId,
|
|
1791
|
+
keys: [
|
|
1792
|
+
{ pubkey: accounts.vkAccount, isSigner: true, isWritable: true },
|
|
1793
|
+
{ pubkey: accounts.authority, isSigner: true, isWritable: false },
|
|
1794
|
+
{ pubkey: accounts.payer, isSigner: true, isWritable: true },
|
|
1795
|
+
{ pubkey: SYSTEM_PROGRAM_ID, isSigner: false, isWritable: false }
|
|
1796
|
+
]
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Builds the instruction data for verifying a proof.
|
|
1801
|
+
*
|
|
1802
|
+
* @param proofBytes - 256-byte Groth16 proof
|
|
1803
|
+
* @param publicInputs - Array of 32-byte public inputs
|
|
1804
|
+
* @param accounts - Account public keys
|
|
1805
|
+
* @returns Instruction data ready for transaction building
|
|
1806
|
+
*
|
|
1807
|
+
* @example
|
|
1808
|
+
* ```typescript
|
|
1809
|
+
* const verifyInstruction = builder.buildVerifyProofInstruction(
|
|
1810
|
+
* solanaProofData.proof.bytes,
|
|
1811
|
+
* solanaProofData.publicInputs.bytes,
|
|
1812
|
+
* { vkAccount: vkAccountPubkey.toBase58() }
|
|
1813
|
+
* );
|
|
1814
|
+
* ```
|
|
1815
|
+
*/
|
|
1816
|
+
buildVerifyProofInstruction(proofBytes, publicInputs, accounts) {
|
|
1817
|
+
const data = buildVerifyProofData(proofBytes, publicInputs);
|
|
1818
|
+
return {
|
|
1819
|
+
data,
|
|
1820
|
+
programId: this.programId,
|
|
1821
|
+
keys: [
|
|
1822
|
+
{ pubkey: accounts.vkAccount, isSigner: false, isWritable: false }
|
|
1823
|
+
]
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Builds the instruction data for closing a VK account.
|
|
1828
|
+
*
|
|
1829
|
+
* @param accounts - Account public keys
|
|
1830
|
+
* @returns Instruction data ready for transaction building
|
|
1831
|
+
*/
|
|
1832
|
+
buildCloseVkInstruction(accounts) {
|
|
1833
|
+
const discriminator = new Uint8Array([
|
|
1834
|
+
154,
|
|
1835
|
+
71,
|
|
1836
|
+
2,
|
|
1837
|
+
43,
|
|
1838
|
+
135,
|
|
1839
|
+
151,
|
|
1840
|
+
132,
|
|
1841
|
+
74
|
|
1842
|
+
]);
|
|
1843
|
+
return {
|
|
1844
|
+
data: discriminator,
|
|
1845
|
+
programId: this.programId,
|
|
1846
|
+
keys: [
|
|
1847
|
+
{ pubkey: accounts.vkAccount, isSigner: false, isWritable: true },
|
|
1848
|
+
{ pubkey: accounts.authority, isSigner: true, isWritable: true }
|
|
1849
|
+
]
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Builds a compute budget instruction to set compute unit limit.
|
|
1854
|
+
*
|
|
1855
|
+
* @param units - Number of compute units (default: configured value)
|
|
1856
|
+
* @returns Instruction data
|
|
1857
|
+
*/
|
|
1858
|
+
buildSetComputeUnitLimitInstruction(units) {
|
|
1859
|
+
const cu = units ?? this.computeUnits;
|
|
1860
|
+
const data = new Uint8Array(5);
|
|
1861
|
+
data[0] = 2;
|
|
1862
|
+
new DataView(data.buffer).setUint32(1, cu, true);
|
|
1863
|
+
return {
|
|
1864
|
+
data,
|
|
1865
|
+
programId: COMPUTE_BUDGET_PROGRAM_ID,
|
|
1866
|
+
keys: []
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Builds a compute budget instruction to set priority fee.
|
|
1871
|
+
*
|
|
1872
|
+
* @param microLamports - Priority fee in microLamports per CU
|
|
1873
|
+
* @returns Instruction data
|
|
1874
|
+
*/
|
|
1875
|
+
buildSetComputeUnitPriceInstruction(microLamports) {
|
|
1876
|
+
const fee = microLamports ?? this.priorityFee;
|
|
1877
|
+
const data = new Uint8Array(9);
|
|
1878
|
+
data[0] = 3;
|
|
1879
|
+
const view = new DataView(data.buffer);
|
|
1880
|
+
view.setBigUint64(1, BigInt(fee), true);
|
|
1881
|
+
return {
|
|
1882
|
+
data,
|
|
1883
|
+
programId: COMPUTE_BUDGET_PROGRAM_ID,
|
|
1884
|
+
keys: []
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Builds all instructions needed to initialize a VK and verify a proof.
|
|
1889
|
+
*
|
|
1890
|
+
* This is a convenience method that combines:
|
|
1891
|
+
* - Compute budget instructions (if configured)
|
|
1892
|
+
* - Init VK instruction
|
|
1893
|
+
* - Verify proof instruction
|
|
1894
|
+
*
|
|
1895
|
+
* @param solanaProofData - Proof data from `izi.proveForSolana()`
|
|
1896
|
+
* @param vkAccountPubkey - Public key for the new VK account
|
|
1897
|
+
* @param authorityPubkey - Authority for the VK account
|
|
1898
|
+
* @param payerPubkey - Payer for the transaction
|
|
1899
|
+
* @returns All instruction data and metadata
|
|
1900
|
+
*
|
|
1901
|
+
* @example
|
|
1902
|
+
* ```typescript
|
|
1903
|
+
* const result = builder.buildInitAndVerifyInstructions(
|
|
1904
|
+
* solanaProofData,
|
|
1905
|
+
* vkKeypair.publicKey.toBase58(),
|
|
1906
|
+
* wallet.publicKey.toBase58(),
|
|
1907
|
+
* wallet.publicKey.toBase58()
|
|
1908
|
+
* );
|
|
1909
|
+
*
|
|
1910
|
+
* // Use with @solana/web3.js:
|
|
1911
|
+
* const transaction = new Transaction();
|
|
1912
|
+
* if (result.computeBudget) {
|
|
1913
|
+
* transaction.add(toTransactionInstruction(result.computeBudget));
|
|
1914
|
+
* }
|
|
1915
|
+
* transaction.add(toTransactionInstruction(result.initVk));
|
|
1916
|
+
* // ... sign and send
|
|
1917
|
+
* ```
|
|
1918
|
+
*/
|
|
1919
|
+
buildInitAndVerifyInstructions(solanaProofData, vkAccountPubkey, authorityPubkey, payerPubkey) {
|
|
1920
|
+
const result = {
|
|
1921
|
+
initVk: this.buildInitVkInstruction(solanaProofData, {
|
|
1922
|
+
vkAccount: vkAccountPubkey,
|
|
1923
|
+
authority: authorityPubkey,
|
|
1924
|
+
payer: payerPubkey
|
|
1925
|
+
}),
|
|
1926
|
+
verifyProof: this.buildVerifyProofInstruction(
|
|
1927
|
+
solanaProofData.proof.bytes,
|
|
1928
|
+
solanaProofData.publicInputs.bytes,
|
|
1929
|
+
{ vkAccount: vkAccountPubkey }
|
|
1930
|
+
),
|
|
1931
|
+
rentLamports: solanaProofData.estimatedRent,
|
|
1932
|
+
accountSize: solanaProofData.accountSize
|
|
1933
|
+
};
|
|
1934
|
+
if (this.computeUnits > 0) {
|
|
1935
|
+
result.computeBudget = this.buildSetComputeUnitLimitInstruction();
|
|
1936
|
+
}
|
|
1937
|
+
if (this.priorityFee > 0) {
|
|
1938
|
+
result.priorityFee = this.buildSetComputeUnitPriceInstruction();
|
|
1939
|
+
}
|
|
1940
|
+
return result;
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Validates that SolanaProofData has the correct format.
|
|
1944
|
+
*
|
|
1945
|
+
* @param data - Proof data to validate
|
|
1946
|
+
* @throws Error if validation fails
|
|
1947
|
+
*/
|
|
1948
|
+
validateSolanaProofData(data) {
|
|
1949
|
+
if (data.proof.bytes.length !== PROOF_SIZE) {
|
|
1950
|
+
throw new Error(
|
|
1951
|
+
`Invalid proof size: expected ${PROOF_SIZE} bytes, got ${data.proof.bytes.length}`
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
const expectedVkSize = G1_SIZE2 + G2_SIZE2 * 3 + G1_SIZE2 * (data.verifyingKey.nrPublicInputs + 1);
|
|
1955
|
+
if (data.verifyingKey.bytes.length !== expectedVkSize) {
|
|
1956
|
+
throw new Error(
|
|
1957
|
+
`Invalid VK size: expected ${expectedVkSize} bytes for ${data.verifyingKey.nrPublicInputs} public inputs, got ${data.verifyingKey.bytes.length}`
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
for (let i = 0; i < data.publicInputs.bytes.length; i++) {
|
|
1961
|
+
if (data.publicInputs.bytes[i].length !== FIELD_SIZE) {
|
|
1962
|
+
throw new Error(
|
|
1963
|
+
`Invalid public input ${i} size: expected ${FIELD_SIZE} bytes, got ${data.publicInputs.bytes[i].length}`
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
if (data.publicInputs.bytes.length !== data.verifyingKey.nrPublicInputs) {
|
|
1968
|
+
throw new Error(
|
|
1969
|
+
`Public inputs count mismatch: VK expects ${data.verifyingKey.nrPublicInputs}, got ${data.publicInputs.bytes.length}`
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Gets the program ID being used.
|
|
1975
|
+
*/
|
|
1976
|
+
getProgramId() {
|
|
1977
|
+
return this.programId;
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Gets the configured compute units.
|
|
1981
|
+
*/
|
|
1982
|
+
getComputeUnits() {
|
|
1983
|
+
return this.computeUnits;
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Gets the configured priority fee.
|
|
1987
|
+
*/
|
|
1988
|
+
getPriorityFee() {
|
|
1989
|
+
return this.priorityFee;
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
|
|
1993
|
+
// src/deployment/VkDeploymentManager.ts
|
|
1994
|
+
var NETWORK_ENDPOINTS = {
|
|
1995
|
+
"mainnet-beta": "https://api.mainnet-beta.solana.com",
|
|
1996
|
+
devnet: "https://api.devnet.solana.com",
|
|
1997
|
+
testnet: "https://api.testnet.solana.com",
|
|
1998
|
+
localnet: "http://localhost:8899"
|
|
1999
|
+
};
|
|
2000
|
+
var VkDeploymentManager = class {
|
|
2001
|
+
config;
|
|
2002
|
+
deployments;
|
|
2003
|
+
builder;
|
|
2004
|
+
dirty = false;
|
|
2005
|
+
constructor(config) {
|
|
2006
|
+
this.config = {
|
|
2007
|
+
network: config.network,
|
|
2008
|
+
rpcEndpoint: config.rpcEndpoint ?? NETWORK_ENDPOINTS[config.network],
|
|
2009
|
+
configDir: config.configDir ?? ".izi-noir",
|
|
2010
|
+
programId: config.programId ?? "",
|
|
2011
|
+
computeUnits: config.computeUnits ?? 4e5
|
|
2012
|
+
};
|
|
2013
|
+
this.deployments = /* @__PURE__ */ new Map();
|
|
2014
|
+
this.builder = new SolanaTransactionBuilder({
|
|
2015
|
+
programId: this.config.programId || void 0,
|
|
2016
|
+
computeUnits: this.config.computeUnits
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Loads deployment state from disk.
|
|
2021
|
+
*
|
|
2022
|
+
* In browser environments, this is a no-op.
|
|
2023
|
+
* In Node.js, reads from {configDir}/deployments.json
|
|
2024
|
+
*/
|
|
2025
|
+
async load() {
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Loads deployment state from a JSON object.
|
|
2029
|
+
*/
|
|
2030
|
+
loadFromJson(state) {
|
|
2031
|
+
this.deployments.clear();
|
|
2032
|
+
for (const deployment of state.deployments) {
|
|
2033
|
+
if (deployment.network === this.config.network) {
|
|
2034
|
+
const key = this.makeDeploymentKey(deployment.circuitId, deployment.vkHash);
|
|
2035
|
+
this.deployments.set(key, deployment);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
this.dirty = false;
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Saves deployment state.
|
|
2042
|
+
*
|
|
2043
|
+
* Returns the state object for custom persistence.
|
|
2044
|
+
*/
|
|
2045
|
+
async save() {
|
|
2046
|
+
const state = this.toJson();
|
|
2047
|
+
this.dirty = false;
|
|
2048
|
+
return state;
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Exports deployment state to JSON.
|
|
2052
|
+
*/
|
|
2053
|
+
toJson() {
|
|
2054
|
+
return {
|
|
2055
|
+
version: "1.0",
|
|
2056
|
+
deployments: Array.from(this.deployments.values())
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Ensures a VK is deployed, skipping if already exists.
|
|
2061
|
+
*
|
|
2062
|
+
* @returns Deployment result with VK account address
|
|
2063
|
+
*/
|
|
2064
|
+
async ensureDeployed(options) {
|
|
2065
|
+
const { circuitName, solanaProofData, authority, payer, forceRedeploy } = options;
|
|
2066
|
+
const vkHash = await this.hashVkBytes(solanaProofData.verifyingKey.bytes);
|
|
2067
|
+
const deploymentKey = this.makeDeploymentKey(circuitName, vkHash);
|
|
2068
|
+
const existing = this.deployments.get(deploymentKey);
|
|
2069
|
+
if (existing && !forceRedeploy) {
|
|
2070
|
+
return {
|
|
2071
|
+
deployed: false,
|
|
2072
|
+
vkAccount: existing.vkAccount,
|
|
2073
|
+
deployment: existing
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
const vkAccountPubkey = this.generateVkAccountPlaceholder();
|
|
2077
|
+
const instructions = [];
|
|
2078
|
+
instructions.push(this.builder.buildSetComputeUnitLimitInstruction());
|
|
2079
|
+
const initVk = this.builder.buildInitVkInstruction(solanaProofData, {
|
|
2080
|
+
vkAccount: vkAccountPubkey,
|
|
2081
|
+
authority,
|
|
2082
|
+
payer
|
|
2083
|
+
});
|
|
2084
|
+
instructions.push(initVk);
|
|
2085
|
+
const signers = [
|
|
2086
|
+
{ pubkey: vkAccountPubkey, isNewKeypair: true, role: "vkAccount" },
|
|
2087
|
+
{ pubkey: authority, isNewKeypair: false, role: "authority" }
|
|
2088
|
+
];
|
|
2089
|
+
if (payer !== authority) {
|
|
2090
|
+
signers.push({ pubkey: payer, isNewKeypair: false, role: "payer" });
|
|
2091
|
+
}
|
|
2092
|
+
const txSignature = await options.sendTransaction(instructions, signers);
|
|
2093
|
+
const deployment = {
|
|
2094
|
+
circuitId: circuitName,
|
|
2095
|
+
vkAccount: vkAccountPubkey,
|
|
2096
|
+
authority,
|
|
2097
|
+
deployedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2098
|
+
txSignature,
|
|
2099
|
+
network: this.config.network,
|
|
2100
|
+
vkHash,
|
|
2101
|
+
nrPublicInputs: solanaProofData.verifyingKey.nrPublicInputs
|
|
2102
|
+
};
|
|
2103
|
+
this.deployments.set(deploymentKey, deployment);
|
|
2104
|
+
this.dirty = true;
|
|
2105
|
+
return {
|
|
2106
|
+
deployed: true,
|
|
2107
|
+
vkAccount: vkAccountPubkey,
|
|
2108
|
+
txSignature,
|
|
2109
|
+
deployment
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Gets deployment by circuit name.
|
|
2114
|
+
*/
|
|
2115
|
+
getDeployment(circuitName) {
|
|
2116
|
+
for (const [key, deployment] of this.deployments) {
|
|
2117
|
+
if (deployment.circuitId === circuitName) {
|
|
2118
|
+
return deployment;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
return void 0;
|
|
2122
|
+
}
|
|
2123
|
+
/**
|
|
2124
|
+
* Gets deployment by VK hash.
|
|
2125
|
+
*/
|
|
2126
|
+
getDeploymentByVkHash(vkHash) {
|
|
2127
|
+
for (const deployment of this.deployments.values()) {
|
|
2128
|
+
if (deployment.vkHash === vkHash) {
|
|
2129
|
+
return deployment;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
return void 0;
|
|
2133
|
+
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Gets all deployments.
|
|
2136
|
+
*/
|
|
2137
|
+
getAllDeployments() {
|
|
2138
|
+
return Array.from(this.deployments.values());
|
|
2139
|
+
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Checks if there are unsaved changes.
|
|
2142
|
+
*/
|
|
2143
|
+
isDirty() {
|
|
2144
|
+
return this.dirty;
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Gets the RPC endpoint.
|
|
2148
|
+
*/
|
|
2149
|
+
getRpcEndpoint() {
|
|
2150
|
+
return this.config.rpcEndpoint;
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Gets the network.
|
|
2154
|
+
*/
|
|
2155
|
+
getNetwork() {
|
|
2156
|
+
return this.config.network;
|
|
2157
|
+
}
|
|
2158
|
+
/**
|
|
2159
|
+
* Prepares instructions for deployment without sending.
|
|
2160
|
+
*
|
|
2161
|
+
* Useful when you need to combine with other instructions
|
|
2162
|
+
* or use a specific signing flow.
|
|
2163
|
+
*/
|
|
2164
|
+
prepareDeployment(solanaProofData, accounts) {
|
|
2165
|
+
const instructions = [];
|
|
2166
|
+
instructions.push(this.builder.buildSetComputeUnitLimitInstruction());
|
|
2167
|
+
instructions.push(
|
|
2168
|
+
this.builder.buildInitVkInstruction(solanaProofData, {
|
|
2169
|
+
vkAccount: accounts.vkAccount,
|
|
2170
|
+
authority: accounts.authority,
|
|
2171
|
+
payer: accounts.payer
|
|
2172
|
+
})
|
|
2173
|
+
);
|
|
2174
|
+
const signers = [
|
|
2175
|
+
{ pubkey: accounts.vkAccount, isNewKeypair: true, role: "vkAccount" },
|
|
2176
|
+
{ pubkey: accounts.authority, isNewKeypair: false, role: "authority" }
|
|
2177
|
+
];
|
|
2178
|
+
if (accounts.payer !== accounts.authority) {
|
|
2179
|
+
signers.push({ pubkey: accounts.payer, isNewKeypair: false, role: "payer" });
|
|
2180
|
+
}
|
|
2181
|
+
return {
|
|
2182
|
+
instructions,
|
|
2183
|
+
signers,
|
|
2184
|
+
rentLamports: solanaProofData.estimatedRent
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Records an external deployment (made outside this manager).
|
|
2189
|
+
*/
|
|
2190
|
+
recordDeployment(deployment) {
|
|
2191
|
+
const key = this.makeDeploymentKey(deployment.circuitId, deployment.vkHash);
|
|
2192
|
+
this.deployments.set(key, deployment);
|
|
2193
|
+
this.dirty = true;
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Removes a deployment record.
|
|
2197
|
+
*/
|
|
2198
|
+
removeDeployment(circuitName) {
|
|
2199
|
+
for (const [key, deployment] of this.deployments) {
|
|
2200
|
+
if (deployment.circuitId === circuitName) {
|
|
2201
|
+
this.deployments.delete(key);
|
|
2202
|
+
this.dirty = true;
|
|
2203
|
+
return true;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return false;
|
|
2207
|
+
}
|
|
2208
|
+
// Private helpers
|
|
2209
|
+
makeDeploymentKey(circuitId, vkHash) {
|
|
2210
|
+
return `${circuitId}:${vkHash.slice(0, 16)}`;
|
|
2211
|
+
}
|
|
2212
|
+
async hashVkBytes(bytes) {
|
|
2213
|
+
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
2214
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
|
|
2215
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
2216
|
+
return Array.from(hashArray).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2217
|
+
}
|
|
2218
|
+
let hash = 0;
|
|
2219
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2220
|
+
hash = (hash << 5) - hash + bytes[i] | 0;
|
|
2221
|
+
}
|
|
2222
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
2223
|
+
}
|
|
2224
|
+
generateVkAccountPlaceholder() {
|
|
2225
|
+
return "PENDING_VK_ACCOUNT_KEYPAIR";
|
|
2226
|
+
}
|
|
2227
|
+
};
|
|
2228
|
+
async function createNodeVkDeploymentManager(config) {
|
|
2229
|
+
const manager = new VkDeploymentManager(config);
|
|
2230
|
+
await manager.load();
|
|
2231
|
+
return manager;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// src/registry/CircuitRegistry.ts
|
|
2235
|
+
var CircuitRegistry = class {
|
|
2236
|
+
circuits = /* @__PURE__ */ new Map();
|
|
2237
|
+
/**
|
|
2238
|
+
* Registers a new circuit.
|
|
2239
|
+
*
|
|
2240
|
+
* @param definition - Circuit definition with metadata
|
|
2241
|
+
* @returns The registered circuit with computed fields
|
|
2242
|
+
* @throws Error if circuit with same name and version exists
|
|
2243
|
+
*/
|
|
2244
|
+
register(definition) {
|
|
2245
|
+
const { name, version } = definition;
|
|
2246
|
+
if (!/^\d+\.\d+\.\d+/.test(version)) {
|
|
2247
|
+
throw new Error(
|
|
2248
|
+
`Invalid version format: ${version}. Use semantic versioning (e.g., '1.0.0')`
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
if (typeof definition.jsCircuit !== "function") {
|
|
2252
|
+
throw new Error("jsCircuit must be a function");
|
|
2253
|
+
}
|
|
2254
|
+
const existing = this.circuits.get(name) ?? [];
|
|
2255
|
+
if (existing.some((c) => c.version === version)) {
|
|
2256
|
+
throw new Error(
|
|
2257
|
+
`Circuit '${name}' version ${version} already registered. Use a new version.`
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
const functionHash = this.hashFunction(definition.jsCircuit);
|
|
2261
|
+
const registered = {
|
|
2262
|
+
...definition,
|
|
2263
|
+
registeredAt: /* @__PURE__ */ new Date(),
|
|
2264
|
+
id: `${name}@${version}`,
|
|
2265
|
+
functionHash
|
|
2266
|
+
};
|
|
2267
|
+
existing.push(registered);
|
|
2268
|
+
existing.sort((a, b) => this.compareVersions(b.version, a.version));
|
|
2269
|
+
this.circuits.set(name, existing);
|
|
2270
|
+
return registered;
|
|
2271
|
+
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Gets a circuit by name.
|
|
2274
|
+
*
|
|
2275
|
+
* @param name - Circuit name
|
|
2276
|
+
* @param options - Lookup options (version, etc.)
|
|
2277
|
+
* @returns The circuit or undefined if not found
|
|
2278
|
+
*/
|
|
2279
|
+
get(name, options) {
|
|
2280
|
+
const versions = this.circuits.get(name);
|
|
2281
|
+
if (!versions || versions.length === 0) {
|
|
2282
|
+
return void 0;
|
|
2283
|
+
}
|
|
2284
|
+
if (options?.version) {
|
|
2285
|
+
return versions.find((c) => c.version === options.version);
|
|
2286
|
+
}
|
|
2287
|
+
return versions[0];
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* Gets all versions of a circuit.
|
|
2291
|
+
*
|
|
2292
|
+
* @param name - Circuit name
|
|
2293
|
+
* @returns Array of all registered versions
|
|
2294
|
+
*/
|
|
2295
|
+
getVersions(name) {
|
|
2296
|
+
return this.circuits.get(name) ?? [];
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Checks if a circuit exists.
|
|
2300
|
+
*
|
|
2301
|
+
* @param name - Circuit name
|
|
2302
|
+
* @param version - Optional specific version
|
|
2303
|
+
*/
|
|
2304
|
+
has(name, version) {
|
|
2305
|
+
const circuit = this.get(name, version ? { version } : void 0);
|
|
2306
|
+
return circuit !== void 0;
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Gets all registered circuit names.
|
|
2310
|
+
*/
|
|
2311
|
+
names() {
|
|
2312
|
+
return Array.from(this.circuits.keys());
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Gets all registered circuits.
|
|
2316
|
+
*/
|
|
2317
|
+
all() {
|
|
2318
|
+
const result = [];
|
|
2319
|
+
for (const versions of this.circuits.values()) {
|
|
2320
|
+
result.push(...versions);
|
|
2321
|
+
}
|
|
2322
|
+
return result;
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Searches circuits by tag.
|
|
2326
|
+
*
|
|
2327
|
+
* @param tag - Tag to search for
|
|
2328
|
+
* @returns Circuits with matching tag
|
|
2329
|
+
*/
|
|
2330
|
+
findByTag(tag) {
|
|
2331
|
+
const result = [];
|
|
2332
|
+
for (const versions of this.circuits.values()) {
|
|
2333
|
+
for (const circuit of versions) {
|
|
2334
|
+
if (circuit.tags?.includes(tag)) {
|
|
2335
|
+
result.push(circuit);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
return result;
|
|
2340
|
+
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Removes a circuit from the registry.
|
|
2343
|
+
*
|
|
2344
|
+
* @param name - Circuit name
|
|
2345
|
+
* @param version - Optional specific version (removes all if not specified)
|
|
2346
|
+
* @returns true if circuit was removed
|
|
2347
|
+
*/
|
|
2348
|
+
remove(name, version) {
|
|
2349
|
+
if (!version) {
|
|
2350
|
+
return this.circuits.delete(name);
|
|
2351
|
+
}
|
|
2352
|
+
const versions = this.circuits.get(name);
|
|
2353
|
+
if (!versions) return false;
|
|
2354
|
+
const index = versions.findIndex((c) => c.version === version);
|
|
2355
|
+
if (index === -1) return false;
|
|
2356
|
+
versions.splice(index, 1);
|
|
2357
|
+
if (versions.length === 0) {
|
|
2358
|
+
this.circuits.delete(name);
|
|
2359
|
+
}
|
|
2360
|
+
return true;
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Exports the registry to a serializable format.
|
|
2364
|
+
*/
|
|
2365
|
+
export() {
|
|
2366
|
+
const circuits = [];
|
|
2367
|
+
for (const [name, versions] of this.circuits) {
|
|
2368
|
+
for (const circuit of versions) {
|
|
2369
|
+
circuits.push({
|
|
2370
|
+
name: circuit.name,
|
|
2371
|
+
version: circuit.version,
|
|
2372
|
+
description: circuit.description,
|
|
2373
|
+
publicInputs: circuit.publicInputs,
|
|
2374
|
+
privateInputs: circuit.privateInputs,
|
|
2375
|
+
tags: circuit.tags,
|
|
2376
|
+
author: circuit.author,
|
|
2377
|
+
license: circuit.license,
|
|
2378
|
+
registeredAt: circuit.registeredAt.toISOString(),
|
|
2379
|
+
functionHash: circuit.functionHash
|
|
2380
|
+
// Note: jsCircuit is not serializable
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
return {
|
|
2385
|
+
version: "1.0",
|
|
2386
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2387
|
+
circuits
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
/**
|
|
2391
|
+
* Generates documentation for a circuit.
|
|
2392
|
+
*
|
|
2393
|
+
* @param name - Circuit name
|
|
2394
|
+
* @returns Markdown documentation
|
|
2395
|
+
*/
|
|
2396
|
+
generateDocs(name) {
|
|
2397
|
+
const circuit = this.get(name);
|
|
2398
|
+
if (!circuit) {
|
|
2399
|
+
return `Circuit '${name}' not found.`;
|
|
2400
|
+
}
|
|
2401
|
+
const lines = [
|
|
2402
|
+
`# ${circuit.name}`,
|
|
2403
|
+
"",
|
|
2404
|
+
`**Version:** ${circuit.version}`
|
|
2405
|
+
];
|
|
2406
|
+
if (circuit.description) {
|
|
2407
|
+
lines.push("", circuit.description);
|
|
2408
|
+
}
|
|
2409
|
+
if (circuit.author) {
|
|
2410
|
+
lines.push("", `**Author:** ${circuit.author}`);
|
|
2411
|
+
}
|
|
2412
|
+
if (circuit.license) {
|
|
2413
|
+
lines.push(`**License:** ${circuit.license}`);
|
|
2414
|
+
}
|
|
2415
|
+
if (circuit.tags && circuit.tags.length > 0) {
|
|
2416
|
+
lines.push("", `**Tags:** ${circuit.tags.join(", ")}`);
|
|
2417
|
+
}
|
|
2418
|
+
lines.push("", "## Public Inputs", "");
|
|
2419
|
+
if (circuit.publicInputs.length === 0) {
|
|
2420
|
+
lines.push("None");
|
|
2421
|
+
} else {
|
|
2422
|
+
lines.push("| Name | Type | Description |", "|------|------|-------------|");
|
|
2423
|
+
for (const input of circuit.publicInputs) {
|
|
2424
|
+
lines.push(`| ${input.name} | ${input.type} | ${input.description ?? "-"} |`);
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
lines.push("", "## Private Inputs", "");
|
|
2428
|
+
if (circuit.privateInputs.length === 0) {
|
|
2429
|
+
lines.push("None");
|
|
2430
|
+
} else {
|
|
2431
|
+
lines.push("| Name | Type | Description |", "|------|------|-------------|");
|
|
2432
|
+
for (const input of circuit.privateInputs) {
|
|
2433
|
+
lines.push(`| ${input.name} | ${input.type} | ${input.description ?? "-"} |`);
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
lines.push("", "## Circuit Function", "", "```javascript", circuit.jsCircuit.toString(), "```");
|
|
2437
|
+
return lines.join("\n");
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Validates circuit inputs against the definition.
|
|
2441
|
+
*
|
|
2442
|
+
* @param name - Circuit name
|
|
2443
|
+
* @param publicInputs - Public inputs to validate
|
|
2444
|
+
* @param privateInputs - Private inputs to validate
|
|
2445
|
+
* @throws Error if validation fails
|
|
2446
|
+
*/
|
|
2447
|
+
validateInputs(name, publicInputs, privateInputs) {
|
|
2448
|
+
const circuit = this.get(name);
|
|
2449
|
+
if (!circuit) {
|
|
2450
|
+
throw new Error(`Circuit '${name}' not found`);
|
|
2451
|
+
}
|
|
2452
|
+
if (publicInputs.length !== circuit.publicInputs.length) {
|
|
2453
|
+
throw new Error(
|
|
2454
|
+
`Expected ${circuit.publicInputs.length} public inputs, got ${publicInputs.length}`
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
if (privateInputs.length !== circuit.privateInputs.length) {
|
|
2458
|
+
throw new Error(
|
|
2459
|
+
`Expected ${circuit.privateInputs.length} private inputs, got ${privateInputs.length}`
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
for (let i = 0; i < publicInputs.length; i++) {
|
|
2463
|
+
this.validateInput(
|
|
2464
|
+
publicInputs[i],
|
|
2465
|
+
circuit.publicInputs[i],
|
|
2466
|
+
`public input '${circuit.publicInputs[i].name}'`
|
|
2467
|
+
);
|
|
2468
|
+
}
|
|
2469
|
+
for (let i = 0; i < privateInputs.length; i++) {
|
|
2470
|
+
this.validateInput(
|
|
2471
|
+
privateInputs[i],
|
|
2472
|
+
circuit.privateInputs[i],
|
|
2473
|
+
`private input '${circuit.privateInputs[i].name}'`
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
// Private helpers
|
|
2478
|
+
hashFunction(fn) {
|
|
2479
|
+
const str = fn.toString();
|
|
2480
|
+
let hash = 0;
|
|
2481
|
+
for (let i = 0; i < str.length; i++) {
|
|
2482
|
+
hash = (hash << 5) - hash + str.charCodeAt(i) | 0;
|
|
2483
|
+
}
|
|
2484
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
2485
|
+
}
|
|
2486
|
+
compareVersions(a, b) {
|
|
2487
|
+
const partsA = a.split(".").map((n) => parseInt(n, 10));
|
|
2488
|
+
const partsB = b.split(".").map((n) => parseInt(n, 10));
|
|
2489
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
2490
|
+
const numA = partsA[i] ?? 0;
|
|
2491
|
+
const numB = partsB[i] ?? 0;
|
|
2492
|
+
if (numA > numB) return 1;
|
|
2493
|
+
if (numA < numB) return -1;
|
|
2494
|
+
}
|
|
2495
|
+
return 0;
|
|
2496
|
+
}
|
|
2497
|
+
validateInput(value, def, location) {
|
|
2498
|
+
switch (def.type) {
|
|
2499
|
+
case "bool":
|
|
2500
|
+
if (typeof value !== "boolean" && value !== 0 && value !== 1) {
|
|
2501
|
+
throw new Error(`${location}: expected boolean, got ${typeof value}`);
|
|
2502
|
+
}
|
|
2503
|
+
break;
|
|
2504
|
+
case "Field":
|
|
2505
|
+
case "u8":
|
|
2506
|
+
case "u16":
|
|
2507
|
+
case "u32":
|
|
2508
|
+
case "u64":
|
|
2509
|
+
if (typeof value !== "number" && typeof value !== "bigint") {
|
|
2510
|
+
throw new Error(`${location}: expected number/bigint, got ${typeof value}`);
|
|
2511
|
+
}
|
|
2512
|
+
break;
|
|
2513
|
+
default:
|
|
2514
|
+
break;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
var globalRegistry = null;
|
|
2519
|
+
function getGlobalRegistry() {
|
|
2520
|
+
if (!globalRegistry) {
|
|
2521
|
+
globalRegistry = new CircuitRegistry();
|
|
2522
|
+
}
|
|
2523
|
+
return globalRegistry;
|
|
2524
|
+
}
|
|
2525
|
+
function defineCircuit(metadata, jsCircuit) {
|
|
2526
|
+
const registry = getGlobalRegistry();
|
|
2527
|
+
return registry.register({ ...metadata, jsCircuit });
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// src/server/OffchainVerifier.ts
|
|
2531
|
+
var OffchainVerifier = class {
|
|
2532
|
+
circuits = /* @__PURE__ */ new Map();
|
|
2533
|
+
compiledCircuits = /* @__PURE__ */ new Map();
|
|
2534
|
+
config;
|
|
2535
|
+
constructor(config = {}) {
|
|
2536
|
+
this.config = config;
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Registers a circuit for verification.
|
|
2540
|
+
*
|
|
2541
|
+
* @param name - Unique name for the circuit
|
|
2542
|
+
* @param config - Circuit configuration
|
|
2543
|
+
*/
|
|
2544
|
+
async registerCircuit(name, config) {
|
|
2545
|
+
this.circuits.set(name, config);
|
|
2546
|
+
if (config.compiledCircuit) {
|
|
2547
|
+
this.compiledCircuits.set(name, config.compiledCircuit);
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
if (this.config.compiler) {
|
|
2551
|
+
const compiled = await this.config.compiler(config.jsCircuit);
|
|
2552
|
+
this.compiledCircuits.set(name, compiled);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Registers a pre-compiled circuit.
|
|
2557
|
+
*
|
|
2558
|
+
* @param name - Unique name for the circuit
|
|
2559
|
+
* @param compiled - Pre-compiled circuit
|
|
2560
|
+
*/
|
|
2561
|
+
registerCompiledCircuit(name, compiled) {
|
|
2562
|
+
this.compiledCircuits.set(name, compiled);
|
|
2563
|
+
}
|
|
2564
|
+
/**
|
|
2565
|
+
* Verifies a ZK proof.
|
|
2566
|
+
*
|
|
2567
|
+
* @param request - Verification request with circuit name, proof, and public inputs
|
|
2568
|
+
* @returns Verification result
|
|
2569
|
+
*/
|
|
2570
|
+
async verify(request) {
|
|
2571
|
+
const startTime = performance.now();
|
|
2572
|
+
const { circuitName, proof, publicInputs } = request;
|
|
2573
|
+
const compiled = this.compiledCircuits.get(circuitName);
|
|
2574
|
+
if (!compiled) {
|
|
2575
|
+
return {
|
|
2576
|
+
verified: false,
|
|
2577
|
+
circuitName,
|
|
2578
|
+
verificationTimeMs: performance.now() - startTime,
|
|
2579
|
+
error: `Circuit '${circuitName}' not registered`,
|
|
2580
|
+
publicInputs
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
const config = this.circuits.get(circuitName);
|
|
2584
|
+
if (config?.publicInputCount !== void 0) {
|
|
2585
|
+
if (publicInputs.length !== config.publicInputCount) {
|
|
2586
|
+
return {
|
|
2587
|
+
verified: false,
|
|
2588
|
+
circuitName,
|
|
2589
|
+
verificationTimeMs: performance.now() - startTime,
|
|
2590
|
+
error: `Expected ${config.publicInputCount} public inputs, got ${publicInputs.length}`,
|
|
2591
|
+
publicInputs
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
try {
|
|
2596
|
+
const proofBytes = this.toUint8Array(proof);
|
|
2597
|
+
const publicInputBytes = publicInputs.map(
|
|
2598
|
+
(input) => this.publicInputToBytes(input)
|
|
2599
|
+
);
|
|
2600
|
+
if (!this.config.verifier) {
|
|
2601
|
+
return {
|
|
2602
|
+
verified: false,
|
|
2603
|
+
circuitName,
|
|
2604
|
+
verificationTimeMs: performance.now() - startTime,
|
|
2605
|
+
error: "No verifier configured. Provide a verifier in the config.",
|
|
2606
|
+
publicInputs
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
const verified = await this.config.verifier(
|
|
2610
|
+
compiled,
|
|
2611
|
+
proofBytes,
|
|
2612
|
+
publicInputBytes
|
|
2613
|
+
);
|
|
2614
|
+
return {
|
|
2615
|
+
verified,
|
|
2616
|
+
circuitName,
|
|
2617
|
+
verificationTimeMs: performance.now() - startTime,
|
|
2618
|
+
publicInputs
|
|
2619
|
+
};
|
|
2620
|
+
} catch (error) {
|
|
2621
|
+
return {
|
|
2622
|
+
verified: false,
|
|
2623
|
+
circuitName,
|
|
2624
|
+
verificationTimeMs: performance.now() - startTime,
|
|
2625
|
+
error: error instanceof Error ? error.message : "Unknown verification error",
|
|
2626
|
+
publicInputs
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* Checks if a circuit is registered.
|
|
2632
|
+
*
|
|
2633
|
+
* @param name - Circuit name
|
|
2634
|
+
*/
|
|
2635
|
+
hasCircuit(name) {
|
|
2636
|
+
return this.compiledCircuits.has(name);
|
|
2637
|
+
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Gets all registered circuit names.
|
|
2640
|
+
*/
|
|
2641
|
+
getCircuitNames() {
|
|
2642
|
+
return Array.from(this.compiledCircuits.keys());
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Removes a circuit from the verifier.
|
|
2646
|
+
*
|
|
2647
|
+
* @param name - Circuit name to remove
|
|
2648
|
+
*/
|
|
2649
|
+
removeCircuit(name) {
|
|
2650
|
+
this.circuits.delete(name);
|
|
2651
|
+
return this.compiledCircuits.delete(name);
|
|
2652
|
+
}
|
|
2653
|
+
// Private helpers
|
|
2654
|
+
toUint8Array(input) {
|
|
2655
|
+
if (input instanceof Uint8Array) {
|
|
2656
|
+
return input;
|
|
2657
|
+
}
|
|
2658
|
+
const hex = input.startsWith("0x") ? input.slice(2) : input;
|
|
2659
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
2660
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2661
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
2662
|
+
}
|
|
2663
|
+
return bytes;
|
|
2664
|
+
}
|
|
2665
|
+
publicInputToBytes(input) {
|
|
2666
|
+
let value;
|
|
2667
|
+
if (typeof input === "string") {
|
|
2668
|
+
value = input.startsWith("0x") ? BigInt(input) : BigInt(input);
|
|
2669
|
+
} else if (typeof input === "bigint") {
|
|
2670
|
+
value = input;
|
|
2671
|
+
} else {
|
|
2672
|
+
value = BigInt(input);
|
|
2673
|
+
}
|
|
2674
|
+
const bytes = new Uint8Array(32);
|
|
2675
|
+
for (let i = 31; i >= 0; i--) {
|
|
2676
|
+
bytes[i] = Number(value & 0xffn);
|
|
2677
|
+
value >>= 8n;
|
|
2678
|
+
}
|
|
2679
|
+
return bytes;
|
|
2680
|
+
}
|
|
2681
|
+
};
|
|
2682
|
+
function createVerifierMiddleware(verifier) {
|
|
2683
|
+
return async (req, res, next) => {
|
|
2684
|
+
try {
|
|
2685
|
+
const { circuitName, proof, publicInputs } = req.body;
|
|
2686
|
+
if (!circuitName || typeof circuitName !== "string") {
|
|
2687
|
+
res.status(400).json({
|
|
2688
|
+
error: "Missing or invalid circuitName"
|
|
2689
|
+
});
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
if (!proof) {
|
|
2693
|
+
res.status(400).json({
|
|
2694
|
+
error: "Missing proof"
|
|
2695
|
+
});
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
if (!Array.isArray(publicInputs)) {
|
|
2699
|
+
res.status(400).json({
|
|
2700
|
+
error: "Missing or invalid publicInputs (expected array)"
|
|
2701
|
+
});
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
if (!verifier.hasCircuit(circuitName)) {
|
|
2705
|
+
res.status(404).json({
|
|
2706
|
+
error: `Circuit '${circuitName}' not found`
|
|
2707
|
+
});
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
const result = await verifier.verify({
|
|
2711
|
+
circuitName,
|
|
2712
|
+
proof,
|
|
2713
|
+
publicInputs
|
|
2714
|
+
});
|
|
2715
|
+
if (result.verified) {
|
|
2716
|
+
res.status(200).json(result);
|
|
2717
|
+
} else {
|
|
2718
|
+
res.status(400).json(result);
|
|
2719
|
+
}
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
next(error instanceof Error ? error : new Error(String(error)));
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
function createVerificationEndpoint(verifier, options = {}) {
|
|
2726
|
+
const baseMiddleware = createVerifierMiddleware(verifier);
|
|
2727
|
+
const wrappedMiddleware = async (req, res, next) => {
|
|
2728
|
+
if (options.allowedCircuits && options.allowedCircuits.length > 0 && req.body.circuitName) {
|
|
2729
|
+
if (!options.allowedCircuits.includes(req.body.circuitName)) {
|
|
2730
|
+
res.status(403).json({
|
|
2731
|
+
error: `Circuit '${req.body.circuitName}' not allowed`
|
|
2732
|
+
});
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
await baseMiddleware(req, res, next);
|
|
2737
|
+
};
|
|
2738
|
+
return {
|
|
2739
|
+
path: options.path ?? "/verify",
|
|
2740
|
+
middleware: wrappedMiddleware
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
async function batchVerify(verifier, requests) {
|
|
2744
|
+
const startTime = performance.now();
|
|
2745
|
+
const results = await Promise.all(
|
|
2746
|
+
requests.map((request) => verifier.verify(request))
|
|
2747
|
+
);
|
|
2748
|
+
const verifiedCount = results.filter((r) => r.verified).length;
|
|
2749
|
+
return {
|
|
2750
|
+
results,
|
|
2751
|
+
totalTimeMs: performance.now() - startTime,
|
|
2752
|
+
verifiedCount,
|
|
2753
|
+
failedCount: results.length - verifiedCount
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2757
|
+
0 && (module.exports = {
|
|
2758
|
+
AcornParser,
|
|
2759
|
+
ArkworksWasm,
|
|
2760
|
+
Barretenberg,
|
|
2761
|
+
Chain,
|
|
2762
|
+
CircuitRegistry,
|
|
2763
|
+
CreateProofUseCase,
|
|
2764
|
+
IZI_NOIR_PROGRAM_ID,
|
|
2765
|
+
IziNoir,
|
|
2766
|
+
NETWORK_ENDPOINTS,
|
|
2767
|
+
OffchainVerifier,
|
|
2768
|
+
Provider,
|
|
2769
|
+
SolanaFormatter,
|
|
2770
|
+
SolanaTransactionBuilder,
|
|
2771
|
+
VkDeploymentManager,
|
|
2772
|
+
batchVerify,
|
|
2773
|
+
buildInitVkFromBytesData,
|
|
2774
|
+
buildVerifyProofData,
|
|
2775
|
+
calculateVkAccountRent,
|
|
2776
|
+
calculateVkAccountSize,
|
|
2777
|
+
createArkworksWasmContainer,
|
|
2778
|
+
createDefaultContainer,
|
|
2779
|
+
createNodeVkDeploymentManager,
|
|
2780
|
+
createVerificationEndpoint,
|
|
2781
|
+
createVerifierMiddleware,
|
|
2782
|
+
defineCircuit,
|
|
2783
|
+
generateNoir,
|
|
2784
|
+
getGlobalRegistry,
|
|
2785
|
+
initNoirWasm,
|
|
2786
|
+
isArkworksCircuit,
|
|
2787
|
+
isWasmInitialized,
|
|
2788
|
+
markWasmInitialized,
|
|
2789
|
+
parseProof,
|
|
2790
|
+
parsePublicInputs,
|
|
2791
|
+
parseVerifyingKey
|
|
2792
|
+
});
|
|
2793
|
+
//# sourceMappingURL=index.cjs.map
|