@aztec/bb-prover 0.0.1-commit.b2a5d0dd1 → 0.0.1-commit.b3d3157a
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/dest/avm_proving_tests/avm_proving_tester.d.ts +9 -5
- package/dest/avm_proving_tests/avm_proving_tester.d.ts.map +1 -1
- package/dest/avm_proving_tests/avm_proving_tester.js +146 -104
- package/dest/bb/bb_js_backend.d.ts +196 -0
- package/dest/bb/bb_js_backend.d.ts.map +1 -0
- package/dest/bb/bb_js_backend.js +379 -0
- package/dest/bb/bb_js_debug.d.ts +52 -0
- package/dest/bb/bb_js_debug.d.ts.map +1 -0
- package/dest/bb/bb_js_debug.js +176 -0
- package/dest/bb/file_names.d.ts +4 -0
- package/dest/bb/file_names.d.ts.map +1 -0
- package/dest/bb/file_names.js +5 -0
- package/dest/config.d.ts +7 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/index.d.ts +3 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -1
- package/dest/prover/client/bb_private_kernel_prover.d.ts +10 -2
- package/dest/prover/client/bb_private_kernel_prover.d.ts.map +1 -1
- package/dest/prover/client/bb_private_kernel_prover.js +25 -1
- package/dest/prover/proof_utils.d.ts +11 -1
- package/dest/prover/proof_utils.d.ts.map +1 -1
- package/dest/prover/proof_utils.js +24 -1
- package/dest/prover/server/bb_prover.d.ts +4 -5
- package/dest/prover/server/bb_prover.d.ts.map +1 -1
- package/dest/prover/server/bb_prover.js +207 -78
- package/dest/verification_key/verification_key_data.js +1 -1
- package/dest/verifier/batch_chonk_verifier.d.ts +13 -2
- package/dest/verifier/batch_chonk_verifier.d.ts.map +1 -1
- package/dest/verifier/batch_chonk_verifier.js +210 -58
- package/dest/verifier/bb_verifier.d.ts +4 -1
- package/dest/verifier/bb_verifier.d.ts.map +1 -1
- package/dest/verifier/bb_verifier.js +134 -45
- package/package.json +18 -17
- package/src/avm_proving_tests/avm_proving_tester.ts +45 -126
- package/src/bb/bb_js_backend.ts +435 -0
- package/src/bb/bb_js_debug.ts +227 -0
- package/src/bb/file_names.ts +6 -0
- package/src/config.ts +6 -1
- package/src/index.ts +2 -1
- package/src/prover/client/bb_private_kernel_prover.ts +100 -0
- package/src/prover/proof_utils.ts +41 -1
- package/src/prover/server/bb_prover.ts +132 -137
- package/src/verification_key/verification_key_data.ts +1 -1
- package/src/verifier/batch_chonk_verifier.ts +204 -65
- package/src/verifier/bb_verifier.ts +66 -76
- package/dest/bb/execute.d.ts +0 -108
- package/dest/bb/execute.d.ts.map +0 -1
- package/dest/bb/execute.js +0 -652
- package/src/bb/execute.ts +0 -687
|
@@ -9,8 +9,8 @@ import type { Tx } from '@aztec/stdlib/tx';
|
|
|
9
9
|
|
|
10
10
|
import { Unpackr } from 'msgpackr';
|
|
11
11
|
import { execFile } from 'node:child_process';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { rmSync } from 'node:fs';
|
|
13
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
14
14
|
import * as os from 'node:os';
|
|
15
15
|
import * as path from 'node:path';
|
|
16
16
|
import { promisify } from 'node:util';
|
|
@@ -18,6 +18,8 @@ import { promisify } from 'node:util';
|
|
|
18
18
|
import type { BBConfig } from '../config.js';
|
|
19
19
|
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
+
const RESULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
22
|
+
const STOP_DRAIN_TIMEOUT_MS = 5_000;
|
|
21
23
|
|
|
22
24
|
/** Result from the FIFO, matching the C++ VerifyResult struct. */
|
|
23
25
|
interface FifoVerifyResult {
|
|
@@ -34,11 +36,12 @@ interface PendingRequest {
|
|
|
34
36
|
resolve: (result: IVCProofVerificationResult) => void;
|
|
35
37
|
reject: (error: Error) => void;
|
|
36
38
|
totalTimer: Timer;
|
|
39
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* Batch verifier for Chonk IVC proofs. Uses the bb batch verifier service
|
|
41
|
-
* which batches IPA verification into a
|
|
44
|
+
* which batches IPA verification into a constant number of SRS MSMs for better throughput.
|
|
42
45
|
*
|
|
43
46
|
* Architecture:
|
|
44
47
|
* - Spawns a persistent `bb msgpack run` process via Barretenberg (native backend)
|
|
@@ -48,7 +51,8 @@ interface PendingRequest {
|
|
|
48
51
|
*/
|
|
49
52
|
export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
50
53
|
private bb!: Barretenberg;
|
|
51
|
-
private
|
|
54
|
+
private fifoDir: string | undefined;
|
|
55
|
+
private fifoPath = '';
|
|
52
56
|
private nextRequestId = 0;
|
|
53
57
|
private pendingRequests = new Map<number, PendingRequest>();
|
|
54
58
|
private sendQueue: SerialQueue;
|
|
@@ -58,6 +62,9 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
|
58
62
|
private vkIndexMap = new Map<string, number>();
|
|
59
63
|
/** Bound cleanup handler for process exit signals. */
|
|
60
64
|
private exitCleanup: (() => void) | null = null;
|
|
65
|
+
private stopped = false;
|
|
66
|
+
private fatalError: Error | undefined;
|
|
67
|
+
private pendingDrainedResolvers = new Set<() => void>();
|
|
61
68
|
|
|
62
69
|
private constructor(
|
|
63
70
|
private config: Pick<BBConfig, 'bbChonkVerifyConcurrency'> & Partial<Pick<BBConfig, 'bbBinaryPath'>>,
|
|
@@ -65,7 +72,6 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
|
65
72
|
private batchSize: number,
|
|
66
73
|
private label: string,
|
|
67
74
|
) {
|
|
68
|
-
this.fifoPath = path.join(os.tmpdir(), `bb-batch-${label}-${process.pid}-${Date.now()}.fifo`);
|
|
69
75
|
this.fifoReader = new FifoFrameReader();
|
|
70
76
|
this.sendQueue = new SerialQueue();
|
|
71
77
|
this.sendQueue.start(1);
|
|
@@ -106,48 +112,87 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
|
106
112
|
private async start(): Promise<void> {
|
|
107
113
|
this.logger.info('Starting BatchChonkVerifier');
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
try {
|
|
116
|
+
this.bb = await Barretenberg.new({
|
|
117
|
+
bbPath: this.config.bbBinaryPath,
|
|
118
|
+
backend: BackendType.NativeUnixSocket,
|
|
119
|
+
});
|
|
120
|
+
await this.bb.initSRSChonk();
|
|
121
|
+
|
|
122
|
+
// Keep the FIFO in a private directory so cleanup has a single owner.
|
|
123
|
+
this.fifoDir = await mkdtemp(path.join(os.tmpdir(), `bb-batch-${this.label}-${process.pid}-`));
|
|
124
|
+
this.fifoPath = path.join(this.fifoDir, 'results.fifo');
|
|
125
|
+
await execFileAsync('mkfifo', [this.fifoPath]);
|
|
126
|
+
this.registerExitCleanup();
|
|
127
|
+
this.startFifoReader();
|
|
128
|
+
|
|
129
|
+
await this.bb.chonkBatchVerifierStart({
|
|
130
|
+
vks: this.vkBuffers,
|
|
131
|
+
numCores: this.config.bbChonkVerifyConcurrency || 0,
|
|
132
|
+
batchSize: this.batchSize,
|
|
133
|
+
fifoPath: this.fifoPath,
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
this.fifoReader.stop();
|
|
137
|
+
this.deregisterExitCleanup();
|
|
138
|
+
await this.cleanupFifo();
|
|
139
|
+
await this.bb?.destroy().catch(() => {});
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
126
142
|
this.logger.info('BatchChonkVerifier started', { fifoPath: this.fifoPath });
|
|
127
143
|
}
|
|
128
144
|
|
|
129
145
|
public verifyProof(tx: Tx): Promise<IVCProofVerificationResult> {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
const totalTimer = new Timer();
|
|
147
|
+
return (async () => {
|
|
148
|
+
const circuit = tx.data.forPublic ? 'HidingKernelToPublic' : 'HidingKernelToRollup';
|
|
149
|
+
const vkIndex = this.vkIndexMap.get(circuit);
|
|
150
|
+
if (vkIndex === undefined) {
|
|
151
|
+
throw new Error(`No VK index for circuit ${circuit}`);
|
|
152
|
+
}
|
|
153
|
+
const proofWithPubInputs = tx.chonkProof.attachPublicInputs(tx.data.publicInputs().toFields());
|
|
154
|
+
const proofFields = proofWithPubInputs.fieldsWithPublicInputs.map(f => f.toBuffer());
|
|
155
|
+
return await this.enqueueProof(vkIndex, proofFields);
|
|
156
|
+
})().catch(err => {
|
|
157
|
+
this.logger.warn(`Failed to verify Chonk proof for tx ${tx.getTxHash().toString()}: ${String(err)}`);
|
|
158
|
+
return { valid: false, durationMs: 0, totalDurationMs: totalTimer.ms() };
|
|
159
|
+
});
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
/** Enqueue raw proof fields for verification. Used directly by tests with custom VKs. */
|
|
141
163
|
public enqueueProof(vkIndex: number, proofFields: Uint8Array[]): Promise<IVCProofVerificationResult> {
|
|
164
|
+
if (this.stopped) {
|
|
165
|
+
return Promise.reject(new Error('BatchChonkVerifier stopped'));
|
|
166
|
+
}
|
|
167
|
+
if (this.fatalError) {
|
|
168
|
+
return Promise.reject(this.fatalError);
|
|
169
|
+
}
|
|
170
|
+
|
|
142
171
|
const totalTimer = new Timer();
|
|
143
172
|
const requestId = this.nextRequestId++;
|
|
144
173
|
|
|
145
174
|
const resultPromise = new Promise<IVCProofVerificationResult>((resolve, reject) => {
|
|
146
|
-
|
|
175
|
+
const timeout = setTimeout(() => {
|
|
176
|
+
const pending = this.pendingRequests.get(requestId);
|
|
177
|
+
if (!pending) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.pendingRequests.delete(requestId);
|
|
181
|
+
pending.reject(new Error(`BatchChonkVerifier result timed out for request_id=${requestId}`));
|
|
182
|
+
this.notifyPendingDrained();
|
|
183
|
+
}, RESULT_TIMEOUT_MS);
|
|
184
|
+
// A pending result timer must never keep the host process alive on its own (e.g. an
|
|
185
|
+
// orphaned request at process exit); the FIFO reader keeps the loop alive while results
|
|
186
|
+
// are genuinely awaited.
|
|
187
|
+
timeout.unref();
|
|
188
|
+
this.pendingRequests.set(requestId, { resolve, reject, totalTimer, timeout });
|
|
147
189
|
});
|
|
148
190
|
|
|
149
191
|
void this.sendQueue
|
|
150
192
|
.put(async () => {
|
|
193
|
+
if (this.fatalError) {
|
|
194
|
+
throw this.fatalError;
|
|
195
|
+
}
|
|
151
196
|
await this.bb.chonkBatchVerifierQueue({
|
|
152
197
|
requestId,
|
|
153
198
|
vkIndex,
|
|
@@ -158,7 +203,9 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
|
158
203
|
const pending = this.pendingRequests.get(requestId);
|
|
159
204
|
if (pending) {
|
|
160
205
|
this.pendingRequests.delete(requestId);
|
|
206
|
+
clearTimeout(pending.timeout);
|
|
161
207
|
pending.reject(err instanceof Error ? err : new Error(String(err)));
|
|
208
|
+
this.notifyPendingDrained();
|
|
162
209
|
}
|
|
163
210
|
});
|
|
164
211
|
|
|
@@ -167,36 +214,60 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
|
167
214
|
|
|
168
215
|
public async stop(): Promise<void> {
|
|
169
216
|
this.logger.info('Stopping BatchChonkVerifier');
|
|
217
|
+
this.stopped = true;
|
|
170
218
|
|
|
171
|
-
// Stop accepting new proofs
|
|
172
|
-
await this.sendQueue.end();
|
|
173
|
-
|
|
174
|
-
// Stop the bb service (flushes remaining proofs)
|
|
175
219
|
try {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
this.
|
|
179
|
-
}
|
|
220
|
+
// Stop accepting new proofs and flush the send queue. Bound it so an unresponsive
|
|
221
|
+
// native process can't block teardown of our own event-loop handles indefinitely.
|
|
222
|
+
await this.withTimeout(this.sendQueue.end(), STOP_DRAIN_TIMEOUT_MS, 'send queue flush');
|
|
180
223
|
|
|
181
|
-
|
|
182
|
-
|
|
224
|
+
// Stop the bb service (flushes remaining proofs).
|
|
225
|
+
await this.withTimeout(this.bb.chonkBatchVerifierStop({}), STOP_DRAIN_TIMEOUT_MS, 'chonkBatchVerifierStop');
|
|
183
226
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this.
|
|
227
|
+
// Native stop flushes callbacks; keep the FIFO open until those frames are observed.
|
|
228
|
+
const drained = await this.waitForPendingRequestsToDrain(STOP_DRAIN_TIMEOUT_MS);
|
|
229
|
+
if (!drained) {
|
|
230
|
+
this.rejectPendingRequests(new Error('Timed out waiting for BatchChonkVerifier results during stop'));
|
|
231
|
+
}
|
|
232
|
+
} catch (err) {
|
|
233
|
+
this.logger.warn(`Error during BatchChonkVerifier graceful stop: ${err}`);
|
|
234
|
+
this.rejectPendingRequests(err instanceof Error ? err : new Error(String(err)));
|
|
235
|
+
} finally {
|
|
236
|
+
// Always release our own event-loop handles — the FIFO read stream (a blocking
|
|
237
|
+
// threadpool read that can't be unref'd), the unix socket, the native process, and the
|
|
238
|
+
// exit handler — so the host process exits cleanly even if the native backend is wedged.
|
|
239
|
+
this.fifoReader.stop();
|
|
240
|
+
this.deregisterExitCleanup();
|
|
241
|
+
await this.cleanupFifo();
|
|
242
|
+
await this.bb.destroy().catch(err => this.logger.warn(`Error destroying bb backend during stop: ${err}`));
|
|
192
243
|
}
|
|
193
244
|
|
|
194
|
-
// Destroy bb process
|
|
195
|
-
await this.bb.destroy();
|
|
196
|
-
|
|
197
245
|
this.logger.info('BatchChonkVerifier stopped');
|
|
198
246
|
}
|
|
199
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Races a promise against a timeout so a wedged native backend can't block teardown. The
|
|
250
|
+
* timer is unref'd so it never keeps the process alive; the underlying promise stays handled
|
|
251
|
+
* by the race even if the timeout wins, so it cannot surface as an unhandled rejection.
|
|
252
|
+
*/
|
|
253
|
+
private async withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T | void> {
|
|
254
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
255
|
+
const timeout = new Promise<void>((_, reject) => {
|
|
256
|
+
timer = setTimeout(
|
|
257
|
+
() => reject(new Error(`BatchChonkVerifier ${label} timed out after ${timeoutMs}ms`)),
|
|
258
|
+
timeoutMs,
|
|
259
|
+
);
|
|
260
|
+
timer.unref();
|
|
261
|
+
});
|
|
262
|
+
try {
|
|
263
|
+
return await Promise.race([promise, timeout]);
|
|
264
|
+
} finally {
|
|
265
|
+
if (timer) {
|
|
266
|
+
clearTimeout(timer);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
200
271
|
private startFifoReader(): void {
|
|
201
272
|
const unpackr = new Unpackr({ useRecords: false });
|
|
202
273
|
|
|
@@ -206,18 +277,20 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
|
206
277
|
this.handleResult(result);
|
|
207
278
|
} catch (err) {
|
|
208
279
|
this.logger.error(`FIFO: failed to decode msgpack result: ${err}`);
|
|
280
|
+
// A corrupt result stream cannot safely be matched to outstanding requests.
|
|
281
|
+
this.failVerifier(err instanceof Error ? err : new Error(String(err)));
|
|
209
282
|
}
|
|
210
283
|
});
|
|
211
284
|
|
|
212
285
|
this.fifoReader.on('error', (err: Error) => {
|
|
213
286
|
this.logger.error(`FIFO reader error: ${err}`);
|
|
287
|
+
this.failVerifier(err);
|
|
214
288
|
});
|
|
215
289
|
|
|
216
290
|
this.fifoReader.on('end', () => {
|
|
217
291
|
this.logger.debug('FIFO reader: stream ended');
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
this.pendingRequests.delete(id);
|
|
292
|
+
if (!this.stopped) {
|
|
293
|
+
this.failVerifier(new Error('FIFO stream ended unexpectedly'));
|
|
221
294
|
}
|
|
222
295
|
});
|
|
223
296
|
|
|
@@ -231,6 +304,7 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
|
231
304
|
return;
|
|
232
305
|
}
|
|
233
306
|
this.pendingRequests.delete(result.request_id);
|
|
307
|
+
clearTimeout(pending.timeout);
|
|
234
308
|
|
|
235
309
|
const valid = result.status === 0; // VerifyStatus::OK
|
|
236
310
|
const durationMs = result.time_in_verify_ms;
|
|
@@ -249,28 +323,93 @@ export class BatchChonkVerifier implements ClientProtocolCircuitVerifier {
|
|
|
249
323
|
}
|
|
250
324
|
|
|
251
325
|
pending.resolve(ivcResult);
|
|
326
|
+
this.notifyPendingDrained();
|
|
252
327
|
}
|
|
253
328
|
|
|
254
329
|
private registerExitCleanup(): void {
|
|
255
|
-
// Signal handlers must be synchronous — unlinkSync is intentional here
|
|
256
330
|
this.exitCleanup = () => {
|
|
257
|
-
|
|
258
|
-
unlinkSync(this.fifoPath);
|
|
259
|
-
} catch {
|
|
260
|
-
/* ignore */
|
|
261
|
-
}
|
|
331
|
+
this.cleanupFifoSync();
|
|
262
332
|
};
|
|
263
333
|
process.on('exit', this.exitCleanup);
|
|
264
|
-
process.on('SIGINT', this.exitCleanup);
|
|
265
|
-
process.on('SIGTERM', this.exitCleanup);
|
|
266
334
|
}
|
|
267
335
|
|
|
268
336
|
private deregisterExitCleanup(): void {
|
|
269
337
|
if (this.exitCleanup) {
|
|
270
338
|
process.removeListener('exit', this.exitCleanup);
|
|
271
|
-
process.removeListener('SIGINT', this.exitCleanup);
|
|
272
|
-
process.removeListener('SIGTERM', this.exitCleanup);
|
|
273
339
|
this.exitCleanup = null;
|
|
274
340
|
}
|
|
275
341
|
}
|
|
342
|
+
|
|
343
|
+
private rejectPendingRequests(error: Error): void {
|
|
344
|
+
for (const [id, pending] of Array.from(this.pendingRequests)) {
|
|
345
|
+
pending.reject(error);
|
|
346
|
+
clearTimeout(pending.timeout);
|
|
347
|
+
this.pendingRequests.delete(id);
|
|
348
|
+
}
|
|
349
|
+
this.notifyPendingDrained();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private failVerifier(error: Error): void {
|
|
353
|
+
if (!this.fatalError) {
|
|
354
|
+
this.fatalError = error;
|
|
355
|
+
}
|
|
356
|
+
this.rejectPendingRequests(error);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private waitForPendingRequestsToDrain(timeoutMs: number): Promise<boolean> {
|
|
360
|
+
if (this.pendingRequests.size === 0) {
|
|
361
|
+
return Promise.resolve(true);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
365
|
+
let onDrain: (() => void) | undefined;
|
|
366
|
+
const drained = new Promise<boolean>(resolve => {
|
|
367
|
+
onDrain = () => resolve(true);
|
|
368
|
+
this.pendingDrainedResolvers.add(onDrain);
|
|
369
|
+
});
|
|
370
|
+
const timedOut = new Promise<boolean>(resolve => {
|
|
371
|
+
timeout = setTimeout(() => resolve(false), timeoutMs);
|
|
372
|
+
timeout.unref();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return Promise.race([drained, timedOut]).finally(() => {
|
|
376
|
+
if (timeout) {
|
|
377
|
+
clearTimeout(timeout);
|
|
378
|
+
}
|
|
379
|
+
if (onDrain) {
|
|
380
|
+
this.pendingDrainedResolvers.delete(onDrain);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private notifyPendingDrained(): void {
|
|
386
|
+
if (this.pendingRequests.size > 0) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
for (const resolve of Array.from(this.pendingDrainedResolvers)) {
|
|
390
|
+
resolve();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private async cleanupFifo(): Promise<void> {
|
|
395
|
+
if (this.fifoDir) {
|
|
396
|
+
await rm(this.fifoDir, { recursive: true, force: true }).catch(() => {});
|
|
397
|
+
} else if (this.fifoPath) {
|
|
398
|
+
await rm(this.fifoPath, { force: true }).catch(() => {});
|
|
399
|
+
}
|
|
400
|
+
this.fifoDir = undefined;
|
|
401
|
+
this.fifoPath = '';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private cleanupFifoSync(): void {
|
|
405
|
+
try {
|
|
406
|
+
if (this.fifoDir) {
|
|
407
|
+
rmSync(this.fifoDir, { recursive: true, force: true });
|
|
408
|
+
} else if (this.fifoPath) {
|
|
409
|
+
rmSync(this.fifoPath, { force: true });
|
|
410
|
+
}
|
|
411
|
+
} catch {
|
|
412
|
+
/* ignore */
|
|
413
|
+
}
|
|
414
|
+
}
|
|
276
415
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { runInDirectory } from '@aztec/foundation/fs';
|
|
2
1
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
3
2
|
import { Timer } from '@aztec/foundation/timer';
|
|
4
3
|
import { ProtocolCircuitVks } from '@aztec/noir-protocol-circuits-types/server/vks';
|
|
@@ -15,28 +14,29 @@ import { Tx } from '@aztec/stdlib/tx';
|
|
|
15
14
|
import type { VerificationKeyData } from '@aztec/stdlib/vks';
|
|
16
15
|
|
|
17
16
|
import { promises as fs } from 'fs';
|
|
18
|
-
import * as path from 'path';
|
|
19
17
|
|
|
20
|
-
import {
|
|
21
|
-
BB_RESULT,
|
|
22
|
-
PROOF_FILENAME,
|
|
23
|
-
PUBLIC_INPUTS_FILENAME,
|
|
24
|
-
VK_FILENAME,
|
|
25
|
-
verifyChonkProof,
|
|
26
|
-
verifyProof,
|
|
27
|
-
} from '../bb/execute.js';
|
|
18
|
+
import { BBJsFactory } from '../bb/bb_js_backend.js';
|
|
28
19
|
import type { BBConfig } from '../config.js';
|
|
29
20
|
import { getUltraHonkFlavorForCircuit } from '../honk.js';
|
|
30
|
-
import { writeChonkProofToPath } from '../prover/proof_utils.js';
|
|
31
21
|
|
|
32
22
|
export class BBCircuitVerifier implements ClientProtocolCircuitVerifier {
|
|
23
|
+
private bbJsFactory: BBJsFactory;
|
|
24
|
+
|
|
33
25
|
private constructor(
|
|
34
26
|
private config: BBConfig,
|
|
35
27
|
private logger: Logger,
|
|
36
|
-
) {
|
|
28
|
+
) {
|
|
29
|
+
// BB_NUM_IVC_VERIFIERS bounds the number of long-lived bb processes the pool keeps alive.
|
|
30
|
+
// If 0, fall back to spawning a fresh bb per verification.
|
|
31
|
+
this.bbJsFactory = new BBJsFactory(config.bbBinaryPath, {
|
|
32
|
+
poolSize: config.numConcurrentIVCVerifiers > 0 ? config.numConcurrentIVCVerifiers : undefined,
|
|
33
|
+
logger,
|
|
34
|
+
debugDir: config.bbDebugOutputDir,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
37
|
|
|
38
38
|
public stop(): Promise<void> {
|
|
39
|
-
return
|
|
39
|
+
return this.bbJsFactory.destroy();
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
public static async new(config: BBConfig, logger = createLogger('bb-prover:verifier')) {
|
|
@@ -55,87 +55,77 @@ export class BBCircuitVerifier implements ClientProtocolCircuitVerifier {
|
|
|
55
55
|
return vk;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/** Verify an UltraHonk proof via bb.js API (no temp files). */
|
|
58
59
|
public async verifyProofForCircuit(circuit: ServerProtocolArtifact, proof: Proof) {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
const proofFileName = path.join(bbWorkingDirectory, PROOF_FILENAME);
|
|
62
|
-
const verificationKeyPath = path.join(bbWorkingDirectory, VK_FILENAME);
|
|
63
|
-
const verificationKey = this.getVerificationKeyData(circuit);
|
|
60
|
+
const verificationKey = this.getVerificationKeyData(circuit);
|
|
61
|
+
const flavor = getUltraHonkFlavorForCircuit(circuit);
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
this.logger.debug(`${circuit} Verifying with key: ${verificationKey.keyAsFields.hash.toString()}`);
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await fs.writeFile(verificationKeyPath, verificationKey.keyAsBytes);
|
|
65
|
+
// Split proof buffer into public input fields and proof fields (32-byte each)
|
|
66
|
+
const publicInputFields = splitBufferToFieldArrays(proof.buffer.subarray(0, proof.numPublicInputs * 32));
|
|
67
|
+
const proofFields = splitBufferToFieldArrays(proof.buffer.subarray(proof.numPublicInputs * 32));
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
await using instance = await this.bbJsFactory.getInstance();
|
|
70
|
+
const { verified, durationMs } = await instance.verifyProof(
|
|
71
|
+
proofFields,
|
|
72
|
+
verificationKey.keyAsBytes,
|
|
73
|
+
publicInputFields,
|
|
74
|
+
flavor,
|
|
75
|
+
);
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
77
|
+
if (!verified) {
|
|
78
|
+
throw new Error(`Failed to verify ${circuit} proof!`);
|
|
79
|
+
}
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
};
|
|
92
|
-
await runInDirectory(this.config.bbWorkingDirectory, operation, this.config.bbSkipCleanup, this.logger);
|
|
81
|
+
this.logger.debug(`${circuit} verification successful`, {
|
|
82
|
+
circuitName: mapProtocolArtifactNameToCircuitName(circuit),
|
|
83
|
+
duration: durationMs,
|
|
84
|
+
eventName: 'circuit-verification',
|
|
85
|
+
proofType: 'ultra-honk',
|
|
86
|
+
} satisfies CircuitVerificationStats);
|
|
93
87
|
}
|
|
94
88
|
|
|
89
|
+
/** Verify a Chonk (IVC) proof from a transaction via bb.js API. */
|
|
95
90
|
public async verifyProof(tx: Tx): Promise<IVCProofVerificationResult> {
|
|
96
91
|
const proofType = 'Chonk';
|
|
97
92
|
try {
|
|
98
93
|
const totalTimer = new Timer();
|
|
99
|
-
let verificationDuration = 0;
|
|
100
94
|
|
|
101
95
|
const circuit: ClientProtocolArtifact = tx.data.forPublic ? 'HidingKernelToPublic' : 'HidingKernelToRollup';
|
|
96
|
+
const verificationKey = this.getVerificationKeyData(circuit);
|
|
97
|
+
|
|
98
|
+
// Reconstruct the full proof with public inputs prepended, then convert Fr[] to Uint8Array[]
|
|
99
|
+
const proofWithPubInputs = tx.chonkProof.attachPublicInputs(tx.data.publicInputs().toFields());
|
|
100
|
+
const fieldsAsBuffers = proofWithPubInputs.fieldsWithPublicInputs.map(f => new Uint8Array(f.toBuffer()));
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.config.bbIVCConcurrency,
|
|
119
|
-
);
|
|
120
|
-
verificationDuration = timer.ms();
|
|
121
|
-
|
|
122
|
-
if (result.status === BB_RESULT.FAILURE) {
|
|
123
|
-
const errorMessage = `Failed to verify ${proofType} proof for ${circuit}!`;
|
|
124
|
-
throw new Error(errorMessage);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
this.logger.debug(`${proofType} verification successful`, {
|
|
128
|
-
circuitName: mapProtocolArtifactNameToCircuitName(circuit),
|
|
129
|
-
duration: result.durationMs,
|
|
130
|
-
eventName: 'circuit-verification',
|
|
131
|
-
proofType: 'chonk',
|
|
132
|
-
} satisfies CircuitVerificationStats);
|
|
133
|
-
};
|
|
134
|
-
await runInDirectory(this.config.bbWorkingDirectory, operation, this.config.bbSkipCleanup, this.logger);
|
|
135
|
-
return { valid: true, durationMs: verificationDuration, totalDurationMs: totalTimer.ms() };
|
|
102
|
+
await using instance = await this.bbJsFactory.getInstance();
|
|
103
|
+
const { verified, durationMs } = await instance.verifyChonkProof(fieldsAsBuffers, verificationKey.keyAsBytes);
|
|
104
|
+
|
|
105
|
+
if (!verified) {
|
|
106
|
+
throw new Error(`Failed to verify ${proofType} proof for ${circuit}!`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.logger.debug(`${proofType} verification successful`, {
|
|
110
|
+
circuitName: mapProtocolArtifactNameToCircuitName(circuit),
|
|
111
|
+
duration: durationMs,
|
|
112
|
+
eventName: 'circuit-verification',
|
|
113
|
+
proofType: 'chonk',
|
|
114
|
+
} satisfies CircuitVerificationStats);
|
|
115
|
+
|
|
116
|
+
return { valid: true, durationMs, totalDurationMs: totalTimer.ms() };
|
|
136
117
|
} catch (err) {
|
|
137
118
|
this.logger.warn(`Failed to verify ${proofType} proof for tx ${tx.getTxHash().toString()}: ${String(err)}`);
|
|
138
119
|
return { valid: false, durationMs: 0, totalDurationMs: 0 };
|
|
139
120
|
}
|
|
140
121
|
}
|
|
141
122
|
}
|
|
123
|
+
|
|
124
|
+
/** Split a buffer into 32-byte Uint8Array field elements. */
|
|
125
|
+
function splitBufferToFieldArrays(buffer: Buffer): Uint8Array[] {
|
|
126
|
+
const fields: Uint8Array[] = [];
|
|
127
|
+
for (let i = 0; i < buffer.length; i += 32) {
|
|
128
|
+
fields.push(new Uint8Array(buffer.subarray(i, i + 32)));
|
|
129
|
+
}
|
|
130
|
+
return fields;
|
|
131
|
+
}
|