11/**
22 * Regression testing framework for individual ZkProgram examples.
33 *
4- * Stores and compares metadata such as compile & proving times.
4+ * Stores and compares metadata such as compile, proving, and verifying times.
55 * Can run in two modes:
66 * - **Dump**: write baseline results into
77 * {@link tests/perf-regression/perf-regression.json}
@@ -34,6 +34,7 @@ type MethodsInfo = Record<
3434 rows : number ;
3535 digest : string ;
3636 proveTime ?: number ;
37+ verifyTime ?: number ;
3738 }
3839> ;
3940
@@ -45,10 +46,10 @@ type PerfRegressionEntry = {
4546
4647type PerfStack = {
4748 start : number ;
48- label ?: 'compile' | 'prove' | string ;
49+ label ?: 'compile' | 'prove' | 'verify' | string ;
4950 programName ?: string ;
5051 methodsSummary ?: Record < string , ConstraintSystemSummary > ;
51- methodName ?: string ; // required for prove; optional for compile
52+ methodName ?: string ; // required for prove/verify ; optional for compile
5253} ;
5354
5455const argv = minimist ( process . argv . slice ( 2 ) , {
@@ -72,10 +73,10 @@ if (DUMP && CHECK) {
7273}
7374
7475const FILE_PATH = path . isAbsolute ( argv . file ?? '' )
75- ? argv . file
76+ ? ( argv . file as string )
7677 : path . join (
7778 process . cwd ( ) ,
78- argv . file ? argv . file : './tests/perf-regression/perf-regression.json'
79+ argv . file ? ( argv . file as string ) : './tests/perf-regression/perf-regression.json'
7980 ) ;
8081
8182// Create directory & file if missing (only on dump)
@@ -89,7 +90,7 @@ if (DUMP) {
8990 * Create a new performance tracking session for a program.
9091 *
9192 * @param programName Name of the program (key in perf-regression.json)
92- * @param methodsSummary Optional methods analysis (required for prove checks)
93+ * @param methodsSummary Optional methods analysis (required for prove/verify checks)
9394 * @param log Optional boolean (default: true). If `--silent` is passed via CLI,
9495 * it overrides this and disables all logs.
9596 * @returns An object with `start()` and `end()` methods
@@ -106,10 +107,10 @@ function createPerformanceSession(
106107 /**
107108 * Start measuring performance for a given phase.
108109 *
109- * @param label The phase label: `'compile' | 'prove' | string`
110- * @param methodName Method name (required for `prove`)
110+ * @param label The phase label: `'compile' | 'prove' | 'verify' | string`
111+ * @param methodName Method name (required for `prove` and `verify` )
111112 */
112- start ( label ?: 'compile' | 'prove' | string , methodName ?: string ) {
113+ start ( label ?: 'compile' | 'prove' | 'verify' | string , methodName ?: string ) {
113114 perfStack . push ( {
114115 label,
115116 start : performance . now ( ) ,
@@ -134,75 +135,56 @@ function createPerformanceSession(
134135 const time = ( performance . now ( ) - start ) / 1000 ;
135136
136137 // Base logging — only if log is enabled
137- // — shows contract.method for prove
138+ // — shows contract.method for prove/verify
138139 if ( shouldLog && label ) {
139140 console . log (
140141 `${ label } ${ programName ?? '' } ${
141- label === 'prove' && methodName ? '.' + methodName : ''
142+ ( label === 'prove' || label === 'verify' ) && methodName ? '.' + methodName : ''
142143 } ... ${ time . toFixed ( 3 ) } sec`
143144 ) ;
144145 }
145146
146- // If neither --dump nor --check, just optionally log
147+ // If neither --dump nor --check, we’re done.
147148 if ( ! DUMP && ! CHECK ) return ;
148149
149- // Only act for compile/prove with required context
150- if ( ! programName || ( label !== 'compile' && label !== 'prove' ) ) return ;
150+ // Only act for compile/prove/verify with required context
151+ if ( ! programName || ( label !== 'compile' && label !== 'prove' && label !== 'verify' ) ) return ;
151152
152153 // Load the baseline JSON used for both DUMP and CHECK modes.
153- // - In DUMP mode: merge new data with existing entries so multiple methods remain grouped.
154- // - In CHECK mode: compare current results against stored baselines.
155154 const raw = fs . readFileSync ( FILE_PATH , 'utf8' ) ;
156155 const perfRegressionJson : Record < string , PerfRegressionEntry > = JSON . parse ( raw ) ;
157156
157+ // --- compile ---
158158 if ( label === 'compile' ) {
159- // DUMP: update only contract-level compileTime
160159 if ( DUMP ) {
161160 dumpCompile ( perfRegressionJson , programName , time ) ;
162161 return ;
163162 }
164-
165- // CHECK: validate against baseline (no writes)
166163 if ( CHECK ) {
167164 checkCompile ( perfRegressionJson , programName , time ) ;
168165 return ;
169166 }
170167 }
171168
172- if ( label === 'prove' ) {
173- // Require analyzed methods summary when proving
174- if ( ! cs ) {
175- throw new Error (
176- 'methodsSummary is required for "prove". Pass it to Performance.create(programName, methodsSummary).'
177- ) ;
178- }
179-
180- // Require the specific method name
181- if ( ! methodName ) {
182- throw new Error (
183- 'Please provide the method name you are proving (start("prove", methodName)).'
184- ) ;
185- }
169+ // --- prove / verify (shared validation + separate actions) ---
170+ if ( label === 'prove' || label === 'verify' ) {
171+ const info = validateMethodContext ( label , cs , methodName , programName ) ;
186172
187- // Look up the method; error if missing (also covers empty methodsSummary)
188- const info = cs [ methodName as keyof typeof cs ] ;
189- if ( ! info ) {
190- const available = Object . keys ( cs ) ;
191- throw new Error (
192- `The method "${ methodName } " does not exist in the analyzed constraint system for "${ programName } ". ` +
193- `Available: ${ available . length ? available . join ( ', ' ) : '(none)' } `
194- ) ;
195- }
196-
197- // DUMP: update per-method rows/digest and proveTime; leave compileTime untouched
198173 if ( DUMP ) {
199- dumpProve ( perfRegressionJson , programName , methodName , info , time ) ;
174+ if ( label === 'prove' ) {
175+ dumpProve ( perfRegressionJson , programName , methodName ! , info , time ) ;
176+ } else {
177+ dumpVerify ( perfRegressionJson , programName , methodName ! , info , time ) ;
178+ }
200179 return ;
201180 }
202181
203- // CHECK: validate only, no writes
204182 if ( CHECK ) {
205- checkProve ( perfRegressionJson , programName , methodName , info . digest , time ) ;
183+ if ( label === 'prove' ) {
184+ checkProve ( perfRegressionJson , programName , methodName ! , info . digest , time ) ;
185+ } else {
186+ checkVerify ( perfRegressionJson , programName , methodName ! , info . digest , time ) ;
187+ }
206188 return ;
207189 }
208190 }
@@ -215,17 +197,16 @@ const Performance = {
215197 * Initialize a new performance session.
216198 *
217199 * @param programName Optional identifier for the program or label.
218- * - When provided with a ZkProgram name and its `methodsSummary`, the session
219- * benchmarks compile and prove phases, storing or checking results against
200+ * - With a ZkProgram name and its `methodsSummary`, the session benchmarks
201+ * compile, prove, and verify phases, storing or checking results against
220202 * `perf-regression.json`.
221- * - When used without a ZkProgram, `programName` acts as a freeform label and
222- * the session can be used like `console.time` / `console.timeEnd` to log
223- * timestamps for arbitrary phases.
203+ * - Without a ZkProgram, `programName` acts as a freeform label and the session
204+ * can be used like `console.time` / `console.timeEnd` to log timestamps.
224205 * @param methodsSummary Optional analysis of ZkProgram methods, required when
225- * measuring prove performance.
206+ * measuring prove/verify performance.
226207 * @param log Optional boolean flag (default: `true`).
227208 * - When set to `false`, disables all console output for both general labels
228- * and compile/prove phase logs.
209+ * and compile/prove/verify phase logs.
229210 * - When the `--silent` flag is provided, it overrides this setting and disables
230211 * all logging regardless of the `log` value.
231212 */
@@ -238,7 +219,7 @@ const Performance = {
238219 } ,
239220} ;
240221
241- // HELPERS
222+ /// HELPERS (dump/check)
242223
243224function dumpCompile (
244225 perfRegressionJson : Record < string , PerfRegressionEntry > ,
@@ -269,13 +250,39 @@ function dumpProve(
269250 merged . methods [ methodName ] = {
270251 rows : info . rows ,
271252 digest : info . digest ,
253+ // keep any existing verifyTime if present
254+ verifyTime : merged . methods [ methodName ] ?. verifyTime ,
272255 proveTime : time ,
273256 } ;
274257
275258 perfRegressionJson [ programName ] = merged ;
276259 fs . writeFileSync ( FILE_PATH , JSON . stringify ( perfRegressionJson , null , 2 ) ) ;
277260}
278261
262+ function dumpVerify (
263+ perfRegressionJson : Record < string , PerfRegressionEntry > ,
264+ programName : string ,
265+ methodName : string ,
266+ info : ConstraintSystemSummary ,
267+ time : number
268+ ) {
269+ const prev = perfRegressionJson [ programName ] ;
270+ const merged : PerfRegressionEntry = prev
271+ ? { ...prev , methods : { ...prev . methods } }
272+ : { methods : { } } ;
273+
274+ merged . methods [ methodName ] = {
275+ rows : info . rows ,
276+ digest : info . digest ,
277+ // keep any existing proveTime if present
278+ proveTime : merged . methods [ methodName ] ?. proveTime ,
279+ verifyTime : time ,
280+ } ;
281+
282+ perfRegressionJson [ programName ] = merged ;
283+ fs . writeFileSync ( FILE_PATH , JSON . stringify ( perfRegressionJson , null , 2 ) ) ;
284+ }
285+
279286function checkCompile (
280287 perfRegressionJson : Record < string , PerfRegressionEntry > ,
281288 programName : string ,
@@ -284,7 +291,7 @@ function checkCompile(
284291 checkAgainstBaseline ( {
285292 perfRegressionJson,
286293 programName,
287- label : 'compile' , // compile checks don't use method/digest; pass empty strings
294+ label : 'compile' ,
288295 methodName : '' ,
289296 digest : '' ,
290297 actualTime,
@@ -308,14 +315,60 @@ function checkProve(
308315 } ) ;
309316}
310317
318+ function checkVerify (
319+ perfRegressionJson : Record < string , PerfRegressionEntry > ,
320+ programName : string ,
321+ methodName : string ,
322+ digest : string ,
323+ actualTime : number
324+ ) {
325+ checkAgainstBaseline ( {
326+ perfRegressionJson,
327+ programName,
328+ label : 'verify' ,
329+ methodName,
330+ digest,
331+ actualTime,
332+ } ) ;
333+ }
334+
335+ // -------------------------
336+ // HELPERS (validation + baselines)
337+ // -------------------------
338+
339+ function validateMethodContext (
340+ label : string ,
341+ cs : Record < string , ConstraintSystemSummary > | undefined ,
342+ methodName : string | undefined ,
343+ programName ?: string
344+ ) : ConstraintSystemSummary {
345+ if ( ! cs || typeof cs !== 'object' ) {
346+ throw new Error (
347+ `methodsSummary is required for this label: ${ label } . Pass it to Performance.create(programName, methodsSummary).`
348+ ) ;
349+ }
350+ if ( ! methodName ) {
351+ throw new Error ( `Please provide the method name (start(${ label } , methodName)).` ) ;
352+ }
353+ const info = cs [ methodName ] ;
354+ if ( ! info ) {
355+ const available = Object . keys ( cs ) ;
356+ throw new Error (
357+ `The method "${ methodName } " does not exist in the analyzed constraint system for "${ programName } ". ` +
358+ `Available: ${ available . length ? available . join ( ', ' ) : '(none)' } `
359+ ) ;
360+ }
361+ return info ;
362+ }
363+
311364/**
312365 * Compare a measured time/digest against stored baselines.
313366 * Throws an error if regression exceeds tolerance.
314367 */
315368function checkAgainstBaseline ( params : {
316369 perfRegressionJson : Record < string , PerfRegressionEntry > ;
317370 programName : string ;
318- label : 'compile' | 'prove' ;
371+ label : 'compile' | 'prove' | 'verify' ;
319372 methodName : string ;
320373 digest : string ;
321374 actualTime : number ;
@@ -327,11 +380,11 @@ function checkAgainstBaseline(params: {
327380 throw new Error ( `No baseline for "${ programName } ". Seed it with --dump first.` ) ;
328381 }
329382
330- // tolerances (same as other file)
383+ // tolerances
331384 const compileTol = 1.05 ; // 5%
332385 const compileTiny = 1.08 ; // for near-zero baselines
333- const proveTolDefault = 1.1 ; // 10%
334- const proveTolSmall = 1.25 ; // 25% for very small times (<0.2s)
386+ const timeTolDefault = 1.1 ; // 10% for prove/verify
387+ const timeTolSmall = 1.25 ; // 25% for very small times (<0.2s)
335388
336389 if ( label === 'compile' ) {
337390 const expected = baseline . compileTime ;
@@ -354,11 +407,11 @@ function checkAgainstBaseline(params: {
354407 return ;
355408 }
356409
357- // prove checks
410+ // prove/verify checks
358411 const baseMethod = baseline . methods ?. [ methodName ] ;
359412 if ( ! baseMethod ) {
360413 throw new Error (
361- `No baseline method entry for ${ programName } .${ methodName } . Run --dump (prove ) to add it.`
414+ `No baseline method entry for ${ programName } .${ methodName } . Run --dump (${ label } ) to add it.`
362415 ) ;
363416 }
364417 if ( baseMethod . digest !== digest ) {
@@ -368,19 +421,21 @@ function checkAgainstBaseline(params: {
368421 ` Expected: ${ baseMethod . digest } \n`
369422 ) ;
370423 }
371- const expected = baseMethod . proveTime ;
424+
425+ const expected = label === 'prove' ? baseMethod . proveTime : baseMethod . verifyTime ;
426+ const labelPretty = label === 'prove' ? 'Prove' : 'Verify' ;
372427 if ( expected == null ) {
373428 throw new Error (
374- `No baseline proveTime for ${ programName } .${ methodName } . Run --dump (prove ) to set it.`
429+ `No baseline ${ label } Time for ${ programName } .${ methodName } . Run --dump (${ label } ) to set it.`
375430 ) ;
376431 }
377- const tol = expected < 0.2 ? proveTolSmall : proveTolDefault ;
432+ const tol = expected < 0.2 ? timeTolSmall : timeTolDefault ;
378433 const allowedPct = ( tol - 1 ) * 100 ;
379434
380435 if ( actualTime > expected * tol ) {
381436 const regressionPct = ( ( actualTime - expected ) / expected ) * 100 ;
382437 throw new Error (
383- `Prove regression for ${ programName } .${ methodName } \n` +
438+ `${ labelPretty } regression for ${ programName } .${ methodName } \n` +
384439 ` Actual: ${ actualTime . toFixed ( 3 ) } s\n` +
385440 ` Regression: +${ regressionPct . toFixed ( 2 ) } % (allowed +${ allowedPct . toFixed ( 0 ) } %)`
386441 ) ;
0 commit comments