@aztec/bb-prover 0.0.1-commit.96bb3f7 → 0.0.1-commit.993d240
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 +13 -8
- package/dest/avm_proving_tests/avm_proving_tester.d.ts.map +1 -1
- package/dest/avm_proving_tests/avm_proving_tester.js +152 -107
- 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 +17 -1
- 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/instrumentation.d.ts +1 -1
- package/dest/instrumentation.d.ts.map +1 -1
- package/dest/instrumentation.js +12 -4
- 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 +43 -7
- 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 +208 -79
- package/dest/test/delay_values.js +1 -1
- package/dest/verification_key/verification_key_data.js +1 -1
- package/dest/verifier/batch_chonk_verifier.d.ts +45 -0
- package/dest/verifier/batch_chonk_verifier.d.ts.map +1 -0
- package/dest/verifier/batch_chonk_verifier.js +232 -0
- 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/dest/verifier/index.d.ts +2 -1
- package/dest/verifier/index.d.ts.map +1 -1
- package/dest/verifier/index.js +1 -0
- package/dest/verifier/queued_chonk_verifier.d.ts +2 -3
- package/dest/verifier/queued_chonk_verifier.d.ts.map +1 -1
- package/dest/verifier/queued_chonk_verifier.js +8 -7
- package/package.json +20 -18
- package/src/avm_proving_tests/avm_proving_tester.ts +53 -122
- 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 +16 -0
- package/src/index.ts +2 -1
- package/src/instrumentation.ts +12 -4
- package/src/prover/client/bb_private_kernel_prover.ts +118 -5
- package/src/prover/proof_utils.ts +41 -1
- package/src/prover/server/bb_prover.ts +133 -137
- package/src/test/delay_values.ts +1 -1
- package/src/verification_key/verification_key_data.ts +1 -1
- package/src/verifier/batch_chonk_verifier.ts +276 -0
- package/src/verifier/bb_verifier.ts +66 -76
- package/src/verifier/index.ts +1 -0
- package/src/verifier/queued_chonk_verifier.ts +8 -8
- package/dest/bb/execute.d.ts +0 -107
- package/dest/bb/execute.d.ts.map +0 -1
- package/dest/bb/execute.js +0 -647
- package/src/bb/execute.ts +0 -678
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvmStat } from '@aztec/bb.js';
|
|
2
2
|
import { Timer } from '@aztec/foundation/timer';
|
|
3
3
|
import {
|
|
4
4
|
PublicTxSimulationTester,
|
|
@@ -10,77 +10,17 @@ import {
|
|
|
10
10
|
import type { PublicTxResult } from '@aztec/simulator/server';
|
|
11
11
|
import { AvmCircuitInputs, AvmCircuitPublicInputs, PublicSimulatorConfig } from '@aztec/stdlib/avm';
|
|
12
12
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
13
|
+
import type { Gas } from '@aztec/stdlib/gas';
|
|
13
14
|
import type { MerkleTreeWriteOperations } from '@aztec/stdlib/interfaces/server';
|
|
14
15
|
import type { GlobalVariables } from '@aztec/stdlib/tx';
|
|
15
16
|
import { NativeWorldStateService } from '@aztec/world-state';
|
|
16
17
|
|
|
17
|
-
import fs from 'node:fs/promises';
|
|
18
|
-
import { tmpdir } from 'node:os';
|
|
19
18
|
import path from 'path';
|
|
20
19
|
|
|
21
|
-
import {
|
|
20
|
+
import { BBJsFactory } from '../bb/bb_js_backend.js';
|
|
22
21
|
|
|
23
22
|
const BB_PATH = path.resolve('../../barretenberg/cpp/build/bin/bb-avm');
|
|
24
23
|
|
|
25
|
-
// An InterceptingLogger that records all log messages and forwards them to a wrapped logger.
|
|
26
|
-
class InterceptingLogger implements Logger {
|
|
27
|
-
public readonly logs: string[] = [];
|
|
28
|
-
public level: LogLevel;
|
|
29
|
-
public module: string;
|
|
30
|
-
|
|
31
|
-
private logger: Logger;
|
|
32
|
-
|
|
33
|
-
constructor(logger: Logger) {
|
|
34
|
-
this.logger = logger;
|
|
35
|
-
this.level = logger.level;
|
|
36
|
-
this.module = logger.module;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
isLevelEnabled(level: LogLevel): boolean {
|
|
40
|
-
return this.logger.isLevelEnabled(level);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
createChild(_childModule: string): Logger {
|
|
44
|
-
throw new Error('Not implemented');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private intercept(level: LogLevel, msg: string, ...args: any[]) {
|
|
48
|
-
this.logs.push(...msg.split('\n'));
|
|
49
|
-
// Forward to the wrapped logger
|
|
50
|
-
(this.logger[level] as LogFn)(msg, ...args);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Log methods for each level
|
|
54
|
-
silent(msg: string, ...args: any[]) {
|
|
55
|
-
this.intercept('silent', msg, ...args);
|
|
56
|
-
}
|
|
57
|
-
fatal(msg: string, ...args: any[]) {
|
|
58
|
-
this.intercept('fatal', msg, ...args);
|
|
59
|
-
}
|
|
60
|
-
warn(msg: string, ...args: any[]) {
|
|
61
|
-
this.intercept('warn', msg, ...args);
|
|
62
|
-
}
|
|
63
|
-
info(msg: string, ...args: any[]) {
|
|
64
|
-
this.intercept('info', msg, ...args);
|
|
65
|
-
}
|
|
66
|
-
verbose(msg: string, ...args: any[]) {
|
|
67
|
-
this.intercept('verbose', msg, ...args);
|
|
68
|
-
}
|
|
69
|
-
debug(msg: string, ...args: any[]) {
|
|
70
|
-
this.intercept('debug', msg, ...args);
|
|
71
|
-
}
|
|
72
|
-
trace(msg: string, ...args: any[]) {
|
|
73
|
-
this.intercept('trace', msg, ...args);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Error log function can be string or Error
|
|
77
|
-
error(err: Error | string, ...args: any[]) {
|
|
78
|
-
const msg = typeof err === 'string' ? err : err.message;
|
|
79
|
-
this.logs.push(msg);
|
|
80
|
-
this.logger.error(msg, err, ...args);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
24
|
// Config with collectHints enabled for proving tests
|
|
85
25
|
const provingConfig: PublicSimulatorConfig = PublicSimulatorConfig.from({
|
|
86
26
|
skipFeeEnforcement: false,
|
|
@@ -92,7 +32,7 @@ const provingConfig: PublicSimulatorConfig = PublicSimulatorConfig.from({
|
|
|
92
32
|
});
|
|
93
33
|
|
|
94
34
|
export class AvmProvingTester extends PublicTxSimulationTester {
|
|
95
|
-
private
|
|
35
|
+
private readonly bbJsFactory = new BBJsFactory(BB_PATH);
|
|
96
36
|
|
|
97
37
|
constructor(
|
|
98
38
|
private checkCircuitOnly: boolean,
|
|
@@ -116,53 +56,57 @@ export class AvmProvingTester extends PublicTxSimulationTester {
|
|
|
116
56
|
return new AvmProvingTester(checkCircuitOnly, contractDataSource, merkleTrees, globals, metrics);
|
|
117
57
|
}
|
|
118
58
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Generate an AVM proof (or run check-circuit if configured). Records per-stage timings in the test metrics.
|
|
61
|
+
* Returns the in-memory proof fields on success; throws via jest expect() on failure.
|
|
62
|
+
*/
|
|
63
|
+
async prove(avmCircuitInputs: AvmCircuitInputs, txLabel: string = 'unlabeledTx'): Promise<Uint8Array[]> {
|
|
64
|
+
const inputsBuffer = avmCircuitInputs.serializeWithMessagePack();
|
|
122
65
|
|
|
123
|
-
|
|
66
|
+
if (this.checkCircuitOnly) {
|
|
67
|
+
await using instance = await this.bbJsFactory.getInstance();
|
|
68
|
+
const { passed, stats } = await instance.checkAvmCircuit(inputsBuffer);
|
|
69
|
+
this.recordProverMetrics(stats, txLabel);
|
|
70
|
+
expect(passed).toBe(true);
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
124
73
|
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
|
|
74
|
+
await using instance = await this.bbJsFactory.getInstance();
|
|
75
|
+
const { proof, stats } = await instance.generateAvmProof(inputsBuffer);
|
|
76
|
+
this.recordProverMetrics(stats, txLabel);
|
|
77
|
+
return proof;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async verify(proof: Uint8Array[], publicInputs: AvmCircuitPublicInputs): Promise<void> {
|
|
81
|
+
if (this.checkCircuitOnly) {
|
|
82
|
+
// Check-circuit did not generate a proof; nothing to verify.
|
|
83
|
+
return;
|
|
135
84
|
}
|
|
136
|
-
|
|
85
|
+
const piBuffer = publicInputs.serializeWithMessagePack();
|
|
86
|
+
await using instance = await this.bbJsFactory.getInstance();
|
|
87
|
+
const { verified } = await instance.verifyAvmProof(proof, piBuffer);
|
|
88
|
+
expect(verified).toBe(true);
|
|
89
|
+
}
|
|
137
90
|
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// const match = log.match(/\b(\w+): (\d+) \(~2/);
|
|
143
|
-
// if (match) {
|
|
144
|
-
// traceSizes.push({
|
|
145
|
-
// name: match[1],
|
|
146
|
-
// size: parseInt(match[2]),
|
|
147
|
-
// });
|
|
148
|
-
// }
|
|
149
|
-
// });
|
|
150
|
-
const times: { [key: string]: number } = {};
|
|
151
|
-
logs.forEach(log => {
|
|
152
|
-
const match = log.match(/\b([\w/]+)_ms: (\d+)/);
|
|
153
|
-
if (match) {
|
|
154
|
-
times[match[1]] = parseInt(match[2]);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
91
|
+
public async proveVerify(avmCircuitInputs: AvmCircuitInputs, txLabel: string = 'unlabeledTx') {
|
|
92
|
+
const proof = await this.prove(avmCircuitInputs, txLabel);
|
|
93
|
+
await this.verify(proof, avmCircuitInputs.publicInputs);
|
|
94
|
+
}
|
|
157
95
|
|
|
158
|
-
|
|
96
|
+
private recordProverMetrics(stats: AvmStat[], txLabel: string) {
|
|
97
|
+
// Build a lookup keyed on the stage name with the `_ms` suffix stripped, matching the legacy
|
|
98
|
+
// stdout-scraped shape. bb::avm2::Stats::time() stores keys with `_ms` appended.
|
|
99
|
+
const times: { [key: string]: number } = {};
|
|
100
|
+
for (const { name, valueMs } of stats) {
|
|
101
|
+
const key = name.endsWith('_ms') ? name.slice(0, -'_ms'.length) : name;
|
|
102
|
+
times[key] = valueMs;
|
|
103
|
+
}
|
|
159
104
|
if (Object.keys(times).length === 0) {
|
|
160
|
-
throw new Error('AVM
|
|
105
|
+
throw new Error('AVM response did not contain any proving-stage timings!');
|
|
161
106
|
}
|
|
162
|
-
|
|
163
107
|
// Hack to make labels match.
|
|
164
108
|
const txLabelWithCount = `${txLabel}/${this.txCount - 1}`;
|
|
165
|
-
//
|
|
109
|
+
// Cast because TS doesn't realize `metrics` is protected, not private on the parent class.
|
|
166
110
|
(this as any).metrics?.recordProverMetrics(txLabelWithCount, {
|
|
167
111
|
proverSimulationStepMs: times['simulation/all'],
|
|
168
112
|
proverProvingStepMs: times['proving/all'],
|
|
@@ -175,26 +119,6 @@ export class AvmProvingTester extends PublicTxSimulationTester {
|
|
|
175
119
|
provingLogDerivativeInverseCommitmentsMs: times['prove/log_derivative_inverse_commitments_round'],
|
|
176
120
|
provingWireCommitmentsMs: times['prove/wire_commitments_round'],
|
|
177
121
|
});
|
|
178
|
-
|
|
179
|
-
return proofRes as BBSuccess;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async verify(proofRes: BBSuccess, publicInputs: AvmCircuitPublicInputs): Promise<BBResult> {
|
|
183
|
-
if (this.checkCircuitOnly) {
|
|
184
|
-
// Skip verification if we are only checking the circuit.
|
|
185
|
-
// Check-circuit does not generate a proof to verify.
|
|
186
|
-
return proofRes;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return await verifyAvmProof(BB_PATH, this.bbWorkingDirectory, proofRes.proofPath!, publicInputs, this.logger);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
public async proveVerify(avmCircuitInputs: AvmCircuitInputs, txLabel: string = 'unlabeledTx') {
|
|
193
|
-
const provingRes = await this.prove(avmCircuitInputs, txLabel);
|
|
194
|
-
expect(provingRes.status).toEqual(BB_RESULT.SUCCESS);
|
|
195
|
-
|
|
196
|
-
const verificationRes = await this.verify(provingRes as BBSuccess, avmCircuitInputs.publicInputs);
|
|
197
|
-
expect(verificationRes.status).toBe(BB_RESULT.SUCCESS);
|
|
198
122
|
}
|
|
199
123
|
|
|
200
124
|
public async simProveVerify(
|
|
@@ -207,6 +131,7 @@ export class AvmProvingTester extends PublicTxSimulationTester {
|
|
|
207
131
|
privateInsertions?: TestPrivateInsertions,
|
|
208
132
|
txLabel: string = 'unlabeledTx',
|
|
209
133
|
disableRevertCheck: boolean = false,
|
|
134
|
+
gasLimits?: Gas,
|
|
210
135
|
): Promise<PublicTxResult> {
|
|
211
136
|
const simTimer = new Timer();
|
|
212
137
|
const simRes = await this.simulateTx(
|
|
@@ -217,6 +142,7 @@ export class AvmProvingTester extends PublicTxSimulationTester {
|
|
|
217
142
|
feePayer,
|
|
218
143
|
privateInsertions,
|
|
219
144
|
txLabel,
|
|
145
|
+
gasLimits,
|
|
220
146
|
);
|
|
221
147
|
const simDuration = simTimer.ms();
|
|
222
148
|
this.logger.info(`Simulation took ${simDuration} ms for tx ${txLabel}`);
|
|
@@ -243,6 +169,7 @@ export class AvmProvingTester extends PublicTxSimulationTester {
|
|
|
243
169
|
teardownCall?: TestEnqueuedCall,
|
|
244
170
|
feePayer?: AztecAddress,
|
|
245
171
|
privateInsertions?: TestPrivateInsertions,
|
|
172
|
+
gasLimits?: Gas,
|
|
246
173
|
) {
|
|
247
174
|
return await this.simProveVerify(
|
|
248
175
|
sender,
|
|
@@ -254,6 +181,7 @@ export class AvmProvingTester extends PublicTxSimulationTester {
|
|
|
254
181
|
privateInsertions,
|
|
255
182
|
txLabel,
|
|
256
183
|
true,
|
|
184
|
+
gasLimits,
|
|
257
185
|
);
|
|
258
186
|
}
|
|
259
187
|
|
|
@@ -261,6 +189,7 @@ export class AvmProvingTester extends PublicTxSimulationTester {
|
|
|
261
189
|
appCall: TestEnqueuedCall,
|
|
262
190
|
expectRevert?: boolean,
|
|
263
191
|
txLabel: string = 'unlabeledTx',
|
|
192
|
+
gasLimits?: Gas,
|
|
264
193
|
) {
|
|
265
194
|
await this.simProveVerify(
|
|
266
195
|
/*sender=*/ AztecAddress.fromNumber(42),
|
|
@@ -271,6 +200,8 @@ export class AvmProvingTester extends PublicTxSimulationTester {
|
|
|
271
200
|
/*feePayer=*/ undefined,
|
|
272
201
|
/*privateInsertions=*/ undefined,
|
|
273
202
|
txLabel,
|
|
203
|
+
/*disableRevertCheck=*/ false,
|
|
204
|
+
gasLimits,
|
|
274
205
|
);
|
|
275
206
|
}
|
|
276
207
|
}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import { type AvmStat, type BackendOptions, BackendType, Barretenberg } from '@aztec/bb.js';
|
|
2
|
+
import type { LogFn, Logger } from '@aztec/foundation/log';
|
|
3
|
+
import { FifoMemoryQueue } from '@aztec/foundation/queue';
|
|
4
|
+
import { Timer } from '@aztec/foundation/timer';
|
|
5
|
+
|
|
6
|
+
import type { UltraHonkFlavor } from '../honk.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Maps UltraHonkFlavor to the bb.js ProofSystemSettings.
|
|
10
|
+
* All server-side proofs use disableZk: true.
|
|
11
|
+
*/
|
|
12
|
+
function getProofSettings(flavor: UltraHonkFlavor) {
|
|
13
|
+
const base = { disableZk: true, optimizedSolidityVerifier: false };
|
|
14
|
+
switch (flavor) {
|
|
15
|
+
case 'ultra_honk':
|
|
16
|
+
return { ...base, oracleHashType: 'poseidon2' as const, ipaAccumulation: false };
|
|
17
|
+
case 'ultra_keccak_honk':
|
|
18
|
+
return { ...base, oracleHashType: 'keccak' as const, ipaAccumulation: false };
|
|
19
|
+
case 'ultra_starknet_honk':
|
|
20
|
+
return { ...base, oracleHashType: 'starknet' as const, ipaAccumulation: false };
|
|
21
|
+
case 'ultra_rollup_honk':
|
|
22
|
+
return { ...base, oracleHashType: 'poseidon2' as const, ipaAccumulation: true };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Result of a successful proof generation via bb.js. */
|
|
27
|
+
export type BBJsProofResult = {
|
|
28
|
+
/** Proof fields as 32-byte Uint8Arrays. */
|
|
29
|
+
proofFields: Uint8Array[];
|
|
30
|
+
/** Public input fields as 32-byte Uint8Arrays. */
|
|
31
|
+
publicInputFields: Uint8Array[];
|
|
32
|
+
/** Duration of the proving operation in ms. */
|
|
33
|
+
durationMs: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Public API surface of a bb.js instance, used by the factory and debug wrapper. */
|
|
37
|
+
export interface BBJsApi {
|
|
38
|
+
generateProof(
|
|
39
|
+
circuitName: string,
|
|
40
|
+
bytecode: Uint8Array,
|
|
41
|
+
verificationKey: Uint8Array,
|
|
42
|
+
witness: Uint8Array,
|
|
43
|
+
flavor: UltraHonkFlavor,
|
|
44
|
+
): Promise<BBJsProofResult>;
|
|
45
|
+
verifyProof(
|
|
46
|
+
proofFields: Uint8Array[],
|
|
47
|
+
verificationKey: Uint8Array,
|
|
48
|
+
publicInputFields: Uint8Array[],
|
|
49
|
+
flavor: UltraHonkFlavor,
|
|
50
|
+
): Promise<{ verified: boolean; durationMs: number }>;
|
|
51
|
+
verifyChonkProof(
|
|
52
|
+
fieldsWithPublicInputs: Uint8Array[],
|
|
53
|
+
verificationKey: Uint8Array,
|
|
54
|
+
): Promise<{ verified: boolean; durationMs: number }>;
|
|
55
|
+
computeGateCount(
|
|
56
|
+
circuitName: string,
|
|
57
|
+
bytecode: Uint8Array,
|
|
58
|
+
flavor: UltraHonkFlavor | 'mega_honk',
|
|
59
|
+
): Promise<{ circuitSize: number; durationMs: number }>;
|
|
60
|
+
generateContract(verificationKey: Uint8Array): Promise<{ solidityCode: string; durationMs: number }>;
|
|
61
|
+
/** Generate an AVM proof from serialized inputs. Callers should call verifyAvmProof separately. */
|
|
62
|
+
generateAvmProof(inputs: Uint8Array): Promise<{ proof: Uint8Array[]; stats: AvmStat[]; durationMs: number }>;
|
|
63
|
+
/** Verify an AVM proof against serialized public inputs. */
|
|
64
|
+
verifyAvmProof(proof: Uint8Array[], publicInputs: Uint8Array): Promise<{ verified: boolean; durationMs: number }>;
|
|
65
|
+
/** Check the AVM circuit from serialized inputs. Returns pass/fail and per-stage timings. */
|
|
66
|
+
checkAvmCircuit(inputs: Uint8Array): Promise<{ passed: boolean; stats: AvmStat[]; durationMs: number }>;
|
|
67
|
+
destroy(): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Thin wrapper around a single Barretenberg instance.
|
|
72
|
+
* Each instance spawns its own bb process via the NativeUnixSocket backend.
|
|
73
|
+
*/
|
|
74
|
+
export class BBJsInstance implements BBJsApi {
|
|
75
|
+
private constructor(private api: Barretenberg) {}
|
|
76
|
+
|
|
77
|
+
/** Creates a new Barretenberg instance connected to a fresh bb process. */
|
|
78
|
+
static async create(bbPath: string, logger?: LogFn, threads?: number): Promise<BBJsInstance> {
|
|
79
|
+
const options: BackendOptions = {
|
|
80
|
+
bbPath,
|
|
81
|
+
backend: BackendType.NativeUnixSocket,
|
|
82
|
+
logger,
|
|
83
|
+
};
|
|
84
|
+
if (threads !== undefined) {
|
|
85
|
+
options.threads = threads;
|
|
86
|
+
}
|
|
87
|
+
const api = await Barretenberg.new(options);
|
|
88
|
+
return new BBJsInstance(api);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generate an UltraHonk proof for a circuit.
|
|
93
|
+
* @param circuitName - Identifier for the circuit (used by bb internally).
|
|
94
|
+
* @param bytecode - Uncompressed ACIR bytecode.
|
|
95
|
+
* @param verificationKey - The circuit's verification key bytes.
|
|
96
|
+
* @param witness - Uncompressed witness bytes.
|
|
97
|
+
* @param flavor - The UltraHonk flavor to use.
|
|
98
|
+
*/
|
|
99
|
+
async generateProof(
|
|
100
|
+
circuitName: string,
|
|
101
|
+
bytecode: Uint8Array,
|
|
102
|
+
verificationKey: Uint8Array,
|
|
103
|
+
witness: Uint8Array,
|
|
104
|
+
flavor: UltraHonkFlavor,
|
|
105
|
+
): Promise<BBJsProofResult> {
|
|
106
|
+
const timer = new Timer();
|
|
107
|
+
const result = await this.api.circuitProve({
|
|
108
|
+
circuit: {
|
|
109
|
+
name: circuitName,
|
|
110
|
+
bytecode,
|
|
111
|
+
verificationKey,
|
|
112
|
+
},
|
|
113
|
+
witness,
|
|
114
|
+
settings: getProofSettings(flavor),
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
proofFields: result.proof,
|
|
118
|
+
publicInputFields: result.publicInputs,
|
|
119
|
+
durationMs: timer.ms(),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Verify an UltraHonk proof.
|
|
125
|
+
* @param proofFields - Proof fields as 32-byte Uint8Arrays.
|
|
126
|
+
* @param verificationKey - The VK bytes.
|
|
127
|
+
* @param publicInputFields - Public input fields as 32-byte Uint8Arrays.
|
|
128
|
+
* @param flavor - The UltraHonk flavor.
|
|
129
|
+
* @returns Whether the proof is valid.
|
|
130
|
+
*/
|
|
131
|
+
async verifyProof(
|
|
132
|
+
proofFields: Uint8Array[],
|
|
133
|
+
verificationKey: Uint8Array,
|
|
134
|
+
publicInputFields: Uint8Array[],
|
|
135
|
+
flavor: UltraHonkFlavor,
|
|
136
|
+
): Promise<{ verified: boolean; durationMs: number }> {
|
|
137
|
+
const timer = new Timer();
|
|
138
|
+
const result = await this.api.circuitVerify({
|
|
139
|
+
verificationKey,
|
|
140
|
+
publicInputs: publicInputFields,
|
|
141
|
+
proof: proofFields,
|
|
142
|
+
settings: getProofSettings(flavor),
|
|
143
|
+
});
|
|
144
|
+
return { verified: result.verified, durationMs: timer.ms() };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Compute circuit gate count / circuit size.
|
|
149
|
+
* @param circuitName - Identifier for the circuit.
|
|
150
|
+
* @param bytecode - Uncompressed ACIR bytecode.
|
|
151
|
+
* @param flavor - 'mega_honk' for chonk circuits, or an UltraHonk flavor.
|
|
152
|
+
* @returns The dyadic circuit size (next power of 2 of gate count).
|
|
153
|
+
*/
|
|
154
|
+
async computeGateCount(
|
|
155
|
+
circuitName: string,
|
|
156
|
+
bytecode: Uint8Array,
|
|
157
|
+
flavor: UltraHonkFlavor | 'mega_honk',
|
|
158
|
+
): Promise<{ circuitSize: number; durationMs: number }> {
|
|
159
|
+
const timer = new Timer();
|
|
160
|
+
if (flavor === 'mega_honk') {
|
|
161
|
+
const result = await this.api.chonkStats({
|
|
162
|
+
circuit: { name: circuitName, bytecode },
|
|
163
|
+
includeGatesPerOpcode: false,
|
|
164
|
+
});
|
|
165
|
+
return { circuitSize: result.circuitSize, durationMs: timer.ms() };
|
|
166
|
+
}
|
|
167
|
+
const result = await this.api.circuitStats({
|
|
168
|
+
circuit: { name: circuitName, bytecode, verificationKey: new Uint8Array(0) },
|
|
169
|
+
includeGatesPerOpcode: false,
|
|
170
|
+
settings: getProofSettings(flavor),
|
|
171
|
+
});
|
|
172
|
+
return { circuitSize: result.numGatesDyadic, durationMs: timer.ms() };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate a Solidity verifier contract from a verification key.
|
|
177
|
+
* @param verificationKey - The VK bytes.
|
|
178
|
+
* @returns The Solidity source code.
|
|
179
|
+
*/
|
|
180
|
+
async generateContract(verificationKey: Uint8Array): Promise<{ solidityCode: string; durationMs: number }> {
|
|
181
|
+
const timer = new Timer();
|
|
182
|
+
const result = await this.api.circuitWriteSolidityVerifier({
|
|
183
|
+
verificationKey,
|
|
184
|
+
settings: {
|
|
185
|
+
ipaAccumulation: false,
|
|
186
|
+
oracleHashType: 'poseidon2',
|
|
187
|
+
disableZk: true,
|
|
188
|
+
optimizedSolidityVerifier: false,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
return { solidityCode: result.solidityCode, durationMs: timer.ms() };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Verify a Chonk (IVC) proof passed as flat field elements (with public inputs prepended).
|
|
196
|
+
* The split into structured sub-proofs happens server-side in `ChonkProof::from_field_elements`,
|
|
197
|
+
* so this layer doesn't need to know per-component sub-proof sizes.
|
|
198
|
+
* @param fieldsWithPublicInputs - Flat proof fields as 32-byte Uint8Arrays (public inputs prepended).
|
|
199
|
+
* @param verificationKey - The VK bytes.
|
|
200
|
+
*/
|
|
201
|
+
async verifyChonkProof(
|
|
202
|
+
fieldsWithPublicInputs: Uint8Array[],
|
|
203
|
+
verificationKey: Uint8Array,
|
|
204
|
+
): Promise<{ verified: boolean; durationMs: number }> {
|
|
205
|
+
const timer = new Timer();
|
|
206
|
+
const result = await this.api.chonkVerifyFromFields({ proof: fieldsWithPublicInputs, vk: verificationKey });
|
|
207
|
+
return { verified: result.valid, durationMs: timer.ms() };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Generate an AVM proof from serialized inputs. */
|
|
211
|
+
async generateAvmProof(inputs: Uint8Array): Promise<{ proof: Uint8Array[]; stats: AvmStat[]; durationMs: number }> {
|
|
212
|
+
const timer = new Timer();
|
|
213
|
+
const result = await this.api.avmProve({ inputs });
|
|
214
|
+
return { proof: result.proof, stats: result.stats, durationMs: timer.ms() };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Verify an AVM proof against serialized public inputs. */
|
|
218
|
+
async verifyAvmProof(
|
|
219
|
+
proof: Uint8Array[],
|
|
220
|
+
publicInputs: Uint8Array,
|
|
221
|
+
): Promise<{ verified: boolean; durationMs: number }> {
|
|
222
|
+
const timer = new Timer();
|
|
223
|
+
const result = await this.api.avmVerify({ proof, publicInputs });
|
|
224
|
+
return { verified: result.verified, durationMs: timer.ms() };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Check the AVM circuit from serialized inputs. */
|
|
228
|
+
async checkAvmCircuit(inputs: Uint8Array): Promise<{ passed: boolean; stats: AvmStat[]; durationMs: number }> {
|
|
229
|
+
const timer = new Timer();
|
|
230
|
+
const result = await this.api.avmCheckCircuit({ inputs });
|
|
231
|
+
return { passed: result.passed, stats: result.stats, durationMs: timer.ms() };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Destroy this instance and kill the underlying bb process. */
|
|
235
|
+
async destroy(): Promise<void> {
|
|
236
|
+
await this.api.destroy();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Options for {@link BBJsFactory}. */
|
|
241
|
+
export interface BBJsFactoryOptions {
|
|
242
|
+
/**
|
|
243
|
+
* Number of long-lived bb processes to keep in the pool.
|
|
244
|
+
* If omitted, every `getInstance()` call spawns a fresh bb that is destroyed on dispose.
|
|
245
|
+
*/
|
|
246
|
+
poolSize?: number;
|
|
247
|
+
logger?: Logger;
|
|
248
|
+
threads?: number;
|
|
249
|
+
debugDir?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Manages bb.js instance lifecycle. By default every `getInstance()` call spawns a fresh
|
|
254
|
+
* bb process that is destroyed when the borrow is disposed. Pass `poolSize` to keep a fixed
|
|
255
|
+
* set of long-lived bb processes that are reused across calls — useful when the per-call
|
|
256
|
+
* bb startup cost dominates the workload (e.g. high-rate IVC verification).
|
|
257
|
+
*
|
|
258
|
+
* Idiomatic usage:
|
|
259
|
+
* ```
|
|
260
|
+
* await using inst = await factory.getInstance();
|
|
261
|
+
* await inst.someMethod(...);
|
|
262
|
+
* // disposed automatically when `inst` goes out of scope
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
export class BBJsFactory {
|
|
266
|
+
private readonly poolSize?: number;
|
|
267
|
+
private readonly logger?: Logger;
|
|
268
|
+
private readonly threads?: number;
|
|
269
|
+
private readonly debugDir?: string;
|
|
270
|
+
|
|
271
|
+
/** Available pooled instances when poolSize is set; otherwise undefined. */
|
|
272
|
+
private pool?: FifoMemoryQueue<BBJsApi>;
|
|
273
|
+
/** Lazily-resolved on first `getInstance()` call to prevent racing pool initialization. */
|
|
274
|
+
private initPromise?: Promise<void>;
|
|
275
|
+
private destroyed = false;
|
|
276
|
+
|
|
277
|
+
constructor(
|
|
278
|
+
private bbPath: string,
|
|
279
|
+
options: BBJsFactoryOptions = {},
|
|
280
|
+
) {
|
|
281
|
+
this.poolSize = options.poolSize;
|
|
282
|
+
this.logger = options.logger;
|
|
283
|
+
this.threads = options.threads;
|
|
284
|
+
this.debugDir = options.debugDir;
|
|
285
|
+
if (this.poolSize !== undefined && this.poolSize < 1) {
|
|
286
|
+
throw new Error(`BBJsFactory poolSize must be >= 1, got ${this.poolSize}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Acquire a bb instance. The returned object implements `BBJsApi` and `AsyncDisposable`.
|
|
292
|
+
* With no pool: spawns a fresh bb that is destroyed on dispose. With a pool: borrows from
|
|
293
|
+
* the pool and returns to it on dispose.
|
|
294
|
+
*/
|
|
295
|
+
async getInstance(): Promise<BBJsApi & AsyncDisposable> {
|
|
296
|
+
if (this.destroyed) {
|
|
297
|
+
throw new Error('BBJsFactory has been destroyed');
|
|
298
|
+
}
|
|
299
|
+
if (this.poolSize === undefined) {
|
|
300
|
+
// No pool: fresh-per-call, dispose destroys.
|
|
301
|
+
const instance = await this.createInstance();
|
|
302
|
+
return this.makeOwned(instance);
|
|
303
|
+
}
|
|
304
|
+
if (!this.initPromise) {
|
|
305
|
+
this.initPromise = this.initPool();
|
|
306
|
+
}
|
|
307
|
+
await this.initPromise;
|
|
308
|
+
const pool = this.pool;
|
|
309
|
+
if (!pool) {
|
|
310
|
+
throw new Error('BBJsFactory has been destroyed');
|
|
311
|
+
}
|
|
312
|
+
const instance = await pool.get();
|
|
313
|
+
if (!instance) {
|
|
314
|
+
throw new Error('BBJsFactory was destroyed while waiting for an instance');
|
|
315
|
+
}
|
|
316
|
+
return this.makeBorrowed(instance);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Tear down all pooled instances. Idempotent. No-op when no pool is configured (fresh-per-call
|
|
321
|
+
* instances are destroyed by their own dispose callbacks). Instances currently held by an
|
|
322
|
+
* in-flight pooled borrow are destroyed by their dispose callback when released.
|
|
323
|
+
*/
|
|
324
|
+
async destroy(): Promise<void> {
|
|
325
|
+
if (this.destroyed) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
this.destroyed = true;
|
|
329
|
+
const pool = this.pool;
|
|
330
|
+
this.pool = undefined;
|
|
331
|
+
if (!pool) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const idle: BBJsApi[] = [];
|
|
335
|
+
while (pool.length() > 0) {
|
|
336
|
+
const item = pool.getImmediate();
|
|
337
|
+
if (item) {
|
|
338
|
+
idle.push(item);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
pool.cancel();
|
|
342
|
+
// Aggregate teardown failures so a single bb child that fails to shut down doesn't mask others.
|
|
343
|
+
const results = await Promise.allSettled(idle.map(item => item.destroy()));
|
|
344
|
+
const errors = results.filter((r): r is PromiseRejectedResult => r.status === 'rejected').map(r => r.reason);
|
|
345
|
+
if (errors.length > 0) {
|
|
346
|
+
throw new AggregateError(errors, `BBJsFactory.destroy: ${errors.length} bb instance(s) failed to shut down`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private async initPool(): Promise<void> {
|
|
351
|
+
// Use allSettled so that if any createInstance() rejects we can destroy the rest instead of
|
|
352
|
+
// leaking bb child processes whose creation succeeded.
|
|
353
|
+
const results = await Promise.allSettled(Array.from({ length: this.poolSize! }, () => this.createInstance()));
|
|
354
|
+
const items: BBJsApi[] = [];
|
|
355
|
+
const errors: unknown[] = [];
|
|
356
|
+
for (const result of results) {
|
|
357
|
+
if (result.status === 'fulfilled') {
|
|
358
|
+
items.push(result.value);
|
|
359
|
+
} else {
|
|
360
|
+
errors.push(result.reason);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (errors.length > 0 || this.destroyed) {
|
|
364
|
+
// Either creation failed or destroy() raced ahead — clean up everything we successfully spawned.
|
|
365
|
+
await Promise.all(items.map(item => item.destroy()));
|
|
366
|
+
if (errors.length > 0) {
|
|
367
|
+
throw errors[0];
|
|
368
|
+
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const pool = new FifoMemoryQueue<BBJsApi>();
|
|
372
|
+
for (const item of items) {
|
|
373
|
+
pool.put(item);
|
|
374
|
+
}
|
|
375
|
+
this.pool = pool;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private async createInstance(): Promise<BBJsApi> {
|
|
379
|
+
const logFn = this.logger ? (msg: string) => this.logger!.verbose(`bb.js - ${msg}`) : undefined;
|
|
380
|
+
const raw = await BBJsInstance.create(this.bbPath, logFn, this.threads);
|
|
381
|
+
return this.maybeWrapDebug(raw);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Wrap the instance in a debug wrapper if debugDir is configured. */
|
|
385
|
+
private async maybeWrapDebug(instance: BBJsInstance): Promise<BBJsApi> {
|
|
386
|
+
if (this.debugDir && this.logger) {
|
|
387
|
+
const { DebugBBJsInstance } = await import('./bb_js_debug.js');
|
|
388
|
+
return new DebugBBJsInstance(instance, this.debugDir, this.bbPath, this.logger);
|
|
389
|
+
}
|
|
390
|
+
return instance;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Wrap a fresh instance with an `AsyncDisposable` that destroys it on dispose. Used when no
|
|
395
|
+
* pool is configured. Destroy errors are propagated so that a teardown failure (e.g. a bb child
|
|
396
|
+
* that didn't shut down cleanly) surfaces instead of being silently swallowed.
|
|
397
|
+
*/
|
|
398
|
+
private makeOwned(instance: BBJsApi): BBJsApi & AsyncDisposable {
|
|
399
|
+
return this.makeDisposable(instance, () => instance.destroy());
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Wrap a pooled instance with an `AsyncDisposable` that returns it to the pool (or destroys it
|
|
404
|
+
* if the factory was destroyed in the meantime). Destroy errors are propagated.
|
|
405
|
+
*/
|
|
406
|
+
private makeBorrowed(instance: BBJsApi): BBJsApi & AsyncDisposable {
|
|
407
|
+
return this.makeDisposable(instance, async () => {
|
|
408
|
+
const pool = this.pool;
|
|
409
|
+
if (pool && !this.destroyed) {
|
|
410
|
+
pool.put(instance);
|
|
411
|
+
} else {
|
|
412
|
+
await instance.destroy();
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private makeDisposable(instance: BBJsApi, onDispose: () => void | Promise<void>): BBJsApi & AsyncDisposable {
|
|
418
|
+
let disposed = false;
|
|
419
|
+
const dispose = async (): Promise<void> => {
|
|
420
|
+
if (disposed) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
disposed = true;
|
|
424
|
+
await onDispose();
|
|
425
|
+
};
|
|
426
|
+
return new Proxy(instance as BBJsApi & AsyncDisposable, {
|
|
427
|
+
get(target, prop, receiver) {
|
|
428
|
+
if (prop === Symbol.asyncDispose) {
|
|
429
|
+
return dispose;
|
|
430
|
+
}
|
|
431
|
+
return Reflect.get(target, prop, receiver);
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|