From 9bae82c25ea21bbaad1e4f1580b49f0137efdc0f Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 6 Jul 2026 22:42:27 +0700 Subject: [PATCH 1/3] add timing, cach compile and method --- src/lib/proof-system/zkprogram.ts | 508 +++++++++++++++++++----------- 1 file changed, 318 insertions(+), 190 deletions(-) diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index d870bce517..c6c55be471 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -75,6 +75,46 @@ type MethodAnalysis = ConstraintSystemSummary & { proofs: ProofClass[]; }; +function o1jsTimingEnabled() { + let env = ( + globalThis as typeof globalThis & { + process?: { env?: Record }; + } + ).process?.env; + let value = env?.O1JS_TIMING; + return value === '1' || value === 'true'; +} + +function timingNow() { + return globalThis.performance?.now?.() ?? Date.now(); +} + +function reportO1jsTiming(label: string, start: number) { + if (!o1jsTimingEnabled()) return; + let ms = timingNow() - start; + console.error(`[o1js timing] ${label}: ${ms.toFixed(3)}ms`); +} + +function timeSync(label: string, run: () => T): T { + if (!o1jsTimingEnabled()) return run(); + let start = timingNow(); + try { + return run(); + } finally { + reportO1jsTiming(label, start); + } +} + +async function timeAsync(label: string, run: () => Promise): Promise { + if (!o1jsTimingEnabled()) return run(); + let start = timingNow(); + try { + return await run(); + } finally { + reportO1jsTiming(label, start); + } +} + function createProgramState() { let methodCache: Map = new Map(); return { @@ -344,6 +384,10 @@ function ZkProgram< let methodFunctions = methodKeys.map((key) => methods[key].method); let privateInputTypes = methodIntfs.map((m) => m.args); let maxProofsVerified: undefined | 0 | 1 | 2 = undefined; + type MethodsMeta = { + [I in keyof Methods]: MethodAnalysis; + }; + let methodsMetaCache: Promise | undefined = undefined; async function getMaxProofsVerified() { if (maxProofsVerified !== undefined) return maxProofsVerified; @@ -354,18 +398,27 @@ function ZkProgram< } async function analyzeMethods() { - let methodsMeta: Record = {}; - for (let i = 0; i < methodIntfs.length; i++) { - let methodEntry = methodIntfs[i]; - methodsMeta[methodEntry.methodName] = await analyzeMethod( - publicInputType, - methodEntry, - methodFunctions[i] - ); + if (methodsMetaCache !== undefined) { + return timeAsync(`ZkProgram.${selfTag.name}.analyzeMethods.cached`, () => methodsMetaCache!); + } + + methodsMetaCache = timeAsync(`ZkProgram.${selfTag.name}.analyzeMethods.total`, async () => { + let methodsMeta: Record = {}; + for (let i = 0; i < methodIntfs.length; i++) { + let methodEntry = methodIntfs[i]; + methodsMeta[methodEntry.methodName] = await timeAsync( + `ZkProgram.${selfTag.name}.analyzeMethods.${methodEntry.methodName}`, + () => analyzeMethod(publicInputType, methodEntry, methodFunctions[i]) + ); + } + return methodsMeta as MethodsMeta; + }); + try { + return await methodsMetaCache; + } catch (error) { + methodsMetaCache = undefined; + throw error; } - return methodsMeta as { - [I in keyof Methods]: MethodAnalysis; - }; } async function analyzeSingleMethod( @@ -376,16 +429,18 @@ function ZkProgram< return await analyzeMethod(publicInputType, methodIntf, methodImpl); } - let compileOutput: - | { - provers: Pickles.Prover[]; - maxProofsVerified: 0 | 1 | 2; - verify: ( - statement: Pickles.Statement, - proof: Pickles.Proof - ) => Promise; - } - | undefined; + type CompileOutput = { + provers: Pickles.Prover[]; + maxProofsVerified: 0 | 1 | 2; + verify: (statement: Pickles.Statement, proof: Pickles.Proof) => Promise; + }; + type CompileResult = { + verificationKey: { data: string; hash: Field }; + compileOutput?: CompileOutput; + }; + + let compileOutput: CompileOutput | undefined; + let compileCache = new Map>(); const programState = createProgramState(); @@ -396,38 +451,68 @@ function ZkProgram< withRuntimeTables = false, lazyMode = false, } = {}) { - doProving = proofsEnabled ?? doProving; + return timeAsync(`ZkProgram.${selfTag.name}.compile.total`, async () => { + let nextDoProving = proofsEnabled ?? doProving; + doProving = nextDoProving; + + let compileCacheKey = [ + `proofsEnabled:${nextDoProving}`, + `withRuntimeTables:${withRuntimeTables}`, + `lazyMode:${lazyMode}`, + ].join('|'); + let cachedCompile = compileCache.get(compileCacheKey); + if (!forceRecompile && cachedCompile !== undefined) { + let result = await timeAsync(`ZkProgram.${selfTag.name}.compile.cached`, () => cachedCompile); + if (result.compileOutput !== undefined) compileOutput = result.compileOutput; + return { verificationKey: result.verificationKey }; + } - if (doProving) { - let methodsMeta = await analyzeMethods(); - let gates = methodKeys.map((k) => methodsMeta[k].gates); - let proofs = methodKeys.map((k) => methodsMeta[k].proofs); - maxProofsVerified = computeMaxProofsVerified(proofs.map((p) => p.length)); + let compilePromise = (async (): Promise => { + if (!nextDoProving) { + return { + verificationKey: VerificationKey.empty(), + }; + } + let methodsMeta = await analyzeMethods(); + let gates = methodKeys.map((k) => methodsMeta[k].gates); + let proofs = methodKeys.map((k) => methodsMeta[k].proofs); + maxProofsVerified = computeMaxProofsVerified(proofs.map((p) => p.length)); + + let { provers, verify, verificationKey } = await compileProgram({ + publicInputType, + publicOutputType, + methodIntfs, + methods: methodFunctions, + gates, + proofs, + proofSystemTag: selfTag, + cache, + forceRecompile, + overrideWrapDomain: config.overrideWrapDomain, + numChunks: config.numChunks, + state: programState, + withRuntimeTables, + lazyMode, + }); - let { provers, verify, verificationKey } = await compileProgram({ - publicInputType, - publicOutputType, - methodIntfs, - methods: methodFunctions, - gates, - proofs, - proofSystemTag: selfTag, - cache, - forceRecompile, - overrideWrapDomain: config.overrideWrapDomain, - numChunks: config.numChunks, - state: programState, - withRuntimeTables, - lazyMode, - }); + return { + verificationKey, + compileOutput: { provers, verify, maxProofsVerified }, + }; + })(); - compileOutput = { provers, verify, maxProofsVerified }; - return { verificationKey }; - } else { - return { - verificationKey: VerificationKey.empty(), - }; - } + compileCache.set(compileCacheKey, compilePromise); + try { + let result = await compilePromise; + if (result.compileOutput !== undefined) compileOutput = result.compileOutput; + return { verificationKey: result.verificationKey }; + } catch (error) { + if (compileCache.get(compileCacheKey) === compilePromise) { + compileCache.delete(compileCacheKey); + } + throw error; + } + }); } // for each of the methods, create a prover function. @@ -749,17 +834,19 @@ async function compileProgram({ Try adding a method to your ZkProgram or SmartContract. If you are using a SmartContract, make sure you are using the @method decorator.`); - let rules = methodIntfs.map((methodEntry, i) => - picklesRuleFromFunction( - publicInputType, - publicOutputType, - methods[i], - proofSystemTag, - methodEntry, - gates[i], - proofs[i], - state, - withRuntimeTables + let rules = timeSync(`ZkProgram.${proofSystemTag.name}.compileProgram.ruleConstruction`, () => + methodIntfs.map((methodEntry, i) => + picklesRuleFromFunction( + publicInputType, + publicOutputType, + methods[i], + proofSystemTag, + methodEntry, + gates[i], + proofs[i], + state, + withRuntimeTables + ) ) ); @@ -786,41 +873,61 @@ If you are using a SmartContract, make sure you are using the @method decorator. MlBool(cache.canWrite), ]; - let { verificationKey, provers, verify, tag } = await prettifyStacktracePromise( - withThreadPool(async () => { - let result: ReturnType; - let id = snarkContext.enter({ inCompile: true }); - setSrsCache(cache); - try { - result = Pickles.compile(MlArray.to(rules), { - publicInputSize: publicInputType.sizeInFields(), - publicOutputSize: publicOutputType.sizeInFields(), - storable: picklesCache, - overrideWrapDomain, - numChunks: numChunks ?? 1, - lazyMode: lazyMode ?? false, - }); - let { getVerificationKey, provers, verify, tag } = result; - CompiledTag.store(proofSystemTag, tag); - let [, data, hash] = await getVerificationKey(); - let verificationKey = { data, hash: Field(hash) }; - return { - verificationKey, - provers: MlArray.from(provers), - verify, - tag, - }; - } finally { - snarkContext.leave(id); - unsetSrsCache(); - } - }) + let { verificationKey, provers, verify, tag } = await timeAsync( + `ZkProgram.${proofSystemTag.name}.compileProgram.threadPool`, + () => + prettifyStacktracePromise( + withThreadPool(async () => { + let result: ReturnType; + let id = snarkContext.enter({ inCompile: true }); + setSrsCache(cache); + try { + result = timeSync( + `ZkProgram.${proofSystemTag.name}.compileProgram.Pickles.compile`, + () => + Pickles.compile(MlArray.to(rules), { + publicInputSize: publicInputType.sizeInFields(), + publicOutputSize: publicOutputType.sizeInFields(), + storable: picklesCache, + overrideWrapDomain, + numChunks: numChunks ?? 1, + lazyMode: lazyMode ?? false, + }) + ); + let { getVerificationKey, provers, verify, tag } = result; + CompiledTag.store(proofSystemTag, tag); + let [, data, hash] = await timeAsync( + `ZkProgram.${proofSystemTag.name}.compileProgram.getVerificationKey`, + getVerificationKey + ); + let verificationKey = { data, hash: Field(hash) }; + return { + verificationKey, + provers: MlArray.from(provers), + verify, + tag, + }; + } finally { + snarkContext.leave(id); + unsetSrsCache(); + } + }) + ) ); // wrap provers let wrappedProvers = provers.map( - (prover): Pickles.Prover => + (prover, i): Pickles.Prover => async function picklesProver(publicInput: MlFieldConstArray) { - return prettifyStacktracePromise(withThreadPool(() => prover(publicInput))); + let methodName = methodIntfs[i]?.methodName ?? `method${i}`; + return timeAsync(`ZkProgram.${proofSystemTag.name}.${methodName}.prover.total`, () => + prettifyStacktracePromise( + withThreadPool(() => + timeAsync(`ZkProgram.${proofSystemTag.name}.${methodName}.prover.call`, () => + prover(publicInput) + ) + ) + ) + ); } ); // wrap verify @@ -888,111 +995,132 @@ function picklesRuleFromFunction( withRuntimeTables?: boolean ): Pickles.Rule { async function main(publicInput: MlFieldArray): ReturnType { - let { witnesses: argsWithoutPublicInput, inProver, auxInputData } = snarkContext.get(); - assert(!(inProver && argsWithoutPublicInput === undefined)); + return timeAsync(`ZkProgram.${proofSystemTag.name}.${methodName}.ruleMain.total`, async () => { + let { witnesses: argsWithoutPublicInput, inProver, auxInputData } = snarkContext.get(); + assert(!(inProver && argsWithoutPublicInput === undefined)); + + // witness private inputs and declare input proofs + let id = ZkProgramContext.enter(); + let finalArgs: unknown[] = []; + timeSync(`ZkProgram.${proofSystemTag.name}.${methodName}.ruleMain.witnessInputs`, () => { + for (let i = 0; i < args.length; i++) { + try { + let type = args[i]; + let value = Provable.witness(type, () => { + return argsWithoutPublicInput?.[i] ?? ProvableType.synthesize(type); + }); + finalArgs[i] = value; + + extractProofs(value).forEach((proof) => proof.declare()); + } catch (e: any) { + ZkProgramContext.leave(id); + e.message = `Error when witnessing in ${methodName}, argument ${i}: ${e.message}`; + throw e; + } + } + }); - // witness private inputs and declare input proofs - let id = ZkProgramContext.enter(); - let finalArgs = []; - for (let i = 0; i < args.length; i++) { - try { - let type = args[i]; - let value = Provable.witness(type, () => { - return argsWithoutPublicInput?.[i] ?? ProvableType.synthesize(type); - }); - finalArgs[i] = value; + // run the user circuit + let result!: { publicOutput?: any; auxiliaryOutput?: any }; + let proofs!: DeclaredProof[]; - extractProofs(value).forEach((proof) => proof.declare()); - } catch (e: any) { + try { + await timeAsync( + `ZkProgram.${proofSystemTag.name}.${methodName}.ruleMain.userCircuit`, + async () => { + if (publicInputType === Undefined || publicInputType === Void) { + result = (await func(...finalArgs)) as any; + } else { + let input = fromFieldVars(publicInputType, publicInput, auxInputData); + result = (await func(input, ...finalArgs)) as any; + } + proofs = ZkProgramContext.getDeclaredProofs(); + } + ); + } finally { ZkProgramContext.leave(id); - e.message = `Error when witnessing in ${methodName}, argument ${i}: ${e.message}`; - throw e; - } - } - - // run the user circuit - let result: { publicOutput?: any; auxiliaryOutput?: any }; - let proofs: DeclaredProof[]; - - try { - if (publicInputType === Undefined || publicInputType === Void) { - result = (await func(...finalArgs)) as any; - } else { - let input = fromFieldVars(publicInputType, publicInput, auxInputData); - result = (await func(input, ...finalArgs)) as any; - } - proofs = ZkProgramContext.getDeclaredProofs(); - } finally { - ZkProgramContext.leave(id); - } - - if (result?.publicOutput) { - // store the nonPure auxiliary data in program state cache if it exists - let nonPureOutput = publicOutputType.toAuxiliary(result.publicOutput); - state?.setNonPureOutput(nonPureOutput); - } - - // now all proofs are declared - check that we got as many as during compile time - assert( - proofs.length === verifiedProofs.length, - `Expected ${verifiedProofs.length} proofs, but got ${proofs.length}` - ); - - // extract proof statements for Pickles - let previousStatements = proofs.map(({ proofInstance }): Pickles.Statement => { - let fields = proofInstance.publicFields(); - let input = MlFieldArray.to(fields.input); - let output = MlFieldArray.to(fields.output); - return MlPair(input, output); - }); - - // handle dynamic proofs - proofs.forEach(({ ProofClass, proofInstance }) => { - if (!(proofInstance instanceof DynamicProof)) return; - - // Initialize side-loaded verification key - const tag = ProofClass.tag(); - const computedTag = SideloadedTag.get(tag.name); - const vk = proofInstance.usedVerificationKey; - - if (vk === undefined) { - throw new Error('proof.verify() not called, call it at least once in your circuit'); } - if (Provable.inProver()) { - Pickles.sideLoaded.inProver(computedTag, vk.data); - } - const circuitVk = Pickles.sideLoaded.vkToCircuit(() => vk.data); + return timeSync( + `ZkProgram.${proofSystemTag.name}.${methodName}.ruleMain.proofConversion`, + () => { + if (result?.publicOutput) { + // store the nonPure auxiliary data in program state cache if it exists + let nonPureOutput = publicOutputType.toAuxiliary(result.publicOutput); + state?.setNonPureOutput(nonPureOutput); + } + + // now all proofs are declared - check that we got as many as during compile time + assert( + proofs.length === verifiedProofs.length, + `Expected ${verifiedProofs.length} proofs, but got ${proofs.length}` + ); - // Assert the validity of the auxiliary vk-data by comparing the witnessed and computed hash - const hash = inCircuitVkHash(circuitVk); - Field(hash).assertEquals(vk.hash, 'Provided VerificationKey hash not correct'); - Pickles.sideLoaded.inCircuit(computedTag, circuitVk); + // extract proof statements for Pickles + let previousStatements = proofs.map(({ proofInstance }): Pickles.Statement => { + let fields = proofInstance.publicFields(); + let input = MlFieldArray.to(fields.input); + let output = MlFieldArray.to(fields.output); + return MlPair(input, output); + }); + + // handle dynamic proofs + proofs.forEach(({ ProofClass, proofInstance }) => { + if (!(proofInstance instanceof DynamicProof)) return; + + // Initialize side-loaded verification key + const tag = ProofClass.tag(); + const computedTag = SideloadedTag.get(tag.name); + const vk = proofInstance.usedVerificationKey; + + if (vk === undefined) { + throw new Error('proof.verify() not called, call it at least once in your circuit'); + } + + if (Provable.inProver()) { + Pickles.sideLoaded.inProver(computedTag, vk.data); + } + const circuitVk = Pickles.sideLoaded.vkToCircuit(() => vk.data); + + // Assert the validity of the auxiliary vk-data by comparing the witnessed and computed hash + const hash = inCircuitVkHash(circuitVk); + Field(hash).assertEquals(vk.hash, 'Provided VerificationKey hash not correct'); + Pickles.sideLoaded.inCircuit(computedTag, circuitVk); + }); + + // if the output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case + let hasPublicOutput = publicOutputType.sizeInFields() !== 0; + let publicOutput = hasPublicOutput ? publicOutputType.toFields(result.publicOutput) : []; + + if ( + state !== undefined && + auxiliaryType !== undefined && + auxiliaryType.sizeInFields() !== 0 + ) { + Provable.asProver(() => { + let { auxiliaryOutput } = result; + assert( + auxiliaryOutput !== undefined, + `${proofSystemTag.name}.${methodName}(): Auxiliary output is undefined even though the method declares it.` + ); + state.setAuxiliaryOutput( + Provable.toConstant(auxiliaryType, auxiliaryOutput), + methodName + ); + }); + } + + return { + publicOutput: MlFieldArray.to(publicOutput), + previousStatements: MlArray.to(previousStatements), + previousProofs: MlArray.to(proofs.map((p) => p.proofInstance.proof)), + shouldVerify: MlArray.to( + proofs.map((proof) => proof.proofInstance.shouldVerify.toField().value) + ), + }; + } + ); }); - - // if the output is empty, we don't evaluate `toFields(result)` to allow the function to return something else in that case - let hasPublicOutput = publicOutputType.sizeInFields() !== 0; - let publicOutput = hasPublicOutput ? publicOutputType.toFields(result.publicOutput) : []; - - if (state !== undefined && auxiliaryType !== undefined && auxiliaryType.sizeInFields() !== 0) { - Provable.asProver(() => { - let { auxiliaryOutput } = result; - assert( - auxiliaryOutput !== undefined, - `${proofSystemTag.name}.${methodName}(): Auxiliary output is undefined even though the method declares it.` - ); - state.setAuxiliaryOutput(Provable.toConstant(auxiliaryType, auxiliaryOutput), methodName); - }); - } - - return { - publicOutput: MlFieldArray.to(publicOutput), - previousStatements: MlArray.to(previousStatements), - previousProofs: MlArray.to(proofs.map((p) => p.proofInstance.proof)), - shouldVerify: MlArray.to( - proofs.map((proof) => proof.proofInstance.shouldVerify.toField().value) - ), - }; } if (verifiedProofs.length > 2) { From 8639181b1d55b3f51160584fb303bcb3f9748b43 Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 6 Jul 2026 23:03:41 +0700 Subject: [PATCH 2/3] fix merge conflict --- src/lib/proof-system/zkprogram.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index a3d17c0354..174a955f8e 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -441,16 +441,6 @@ function ZkProgram< let compileOutput: CompileOutput | undefined; let compileCache = new Map>(); - let compileOutput: - | { - provers: Pickles.Prover[]; - maxProofsVerified: 0 | 1 | 2; - verify: ( - statement: Pickles.Statement, - proof: Pickles.Proof - ) => Promise; - } - | undefined; const programState = createProgramState(); From d87ba8c3d6a684160e748c622a53396bc22aa111 Mon Sep 17 00:00:00 2001 From: Florian Date: Tue, 7 Jul 2026 00:11:31 +0700 Subject: [PATCH 3/3] pass cached step domains into repeated compiles --- src/bindings.d.ts | 2 ++ src/bindings/ocaml/lib/pickles_bindings.ml | 36 +++++++++++++++++++-- src/bindings/ocaml/lib/pickles_bindings.mli | 6 +++- src/lib/proof-system/zkprogram.ts | 23 +++++++++++-- src/mina | 2 +- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/bindings.d.ts b/src/bindings.d.ts index d99101b1d0..19ba7d3e5c 100644 --- a/src/bindings.d.ts +++ b/src/bindings.d.ts @@ -715,6 +715,7 @@ declare const Pickles: { overrideWrapDomain?: 0 | 1 | 2; numChunks?: number; lazyMode?: boolean; + stepDomains?: number[]; } ) => { provers: MlArray; @@ -729,6 +730,7 @@ declare const Pickles: { getVerificationKey: () => Promise< [_: 0, data: Base64VerificationKeyString, hash: FieldConst] >; + getStepDomains: () => Promise; }; verify( diff --git a/src/bindings/ocaml/lib/pickles_bindings.ml b/src/bindings/ocaml/lib/pickles_bindings.ml index c3eaadf727..85eb98a686 100644 --- a/src/bindings/ocaml/lib/pickles_bindings.ml +++ b/src/bindings/ocaml/lib/pickles_bindings.ml @@ -5,6 +5,8 @@ module Field = Impl.Field module Boolean = Impl.Boolean module Typ = Impl.Typ module Backend = Pickles.Backend +module Domain = Pickles__Import.Domain +module Domains = Pickles__Import.Domains module Public_input = struct type t = Field.t array @@ -619,7 +621,8 @@ let pickles_compile (choices : pickles_rule_js array) ; storable : Cache.js_storable Js.optdef_prop ; overrideWrapDomain : int Js.optdef_prop ; numChunks : int Js.optdef_prop - ; lazyMode : bool Js.optdef_prop > + ; lazyMode : bool Js.optdef_prop + ; stepDomains : int Js.js_array Js.t Js.optdef_prop > Js.t ) = (* translate number of branches and recursively verified proofs from JS *) let branches = Array.length choices in @@ -640,6 +643,16 @@ let pickles_compile (choices : pickles_rule_js array) in let num_chunks = Js.Optdef.get config##.numChunks (fun () -> 1) in let lazy_mode = Js.Optdef.get config##.lazyMode (fun () -> false) in + let known_step_domains = + Js.Optdef.to_option config##.stepDomains + |> Option.map ~f:(fun domains -> + Js.to_array domains + |> Array.map ~f:(fun log2 -> + { Domains.h = Domain.Pow_2_roots_of_unity log2 } ) + |> fun domains -> + Pickles_types.Vector.of_array_and_length_exn domains Branches.n ) + |> Option.map ~f:Obj.magic + in let (Choices choices) = Choices.of_js ~public_input_size ~public_output_size choices in @@ -662,7 +675,7 @@ let pickles_compile (choices : pickles_rule_js array) , public_input_typ public_output_size ) ) ~auxiliary_typ:Typ.unit ~max_proofs_verified:(module Max_proofs_verified) - ~name ~num_chunks ~lazy_mode ~choices () + ~name ~num_chunks ~lazy_mode ?known_step_domains ~choices () in (* translate returned prover and verify functions to JS *) @@ -705,6 +718,23 @@ let pickles_compile (choices : pickles_rule_js array) (data |> Js.string, hash) ) |> Promise_js_helpers.to_js in + let get_step_domains () = + let compiled = Pickles.Types_map.lookup_compiled tag.id in + let domains = + Promise.map + (Pickles_types.Vector.fold ~init:(Promise.return ()) + compiled.step_domains ~f:(fun acc domain -> + Promise.bind acc ~f:(fun () -> Promise.map domain ~f:ignore) ) ) + ~f:(fun () -> + Pickles_types.Vector.map compiled.step_domains ~f:(fun domain -> + Option.value_exn (Promise.peek domain) ) ) + in + Promise.map domains ~f:(fun domains -> + Pickles_types.Vector.to_array domains + |> Array.map ~f:(fun { Domains.h } -> Domain.log2_size h) + |> Js.array ) + |> Promise_js_helpers.to_js + in object%js val provers = Obj.magic provers @@ -713,6 +743,8 @@ let pickles_compile (choices : pickles_rule_js array) val tag = Obj.magic tag val getVerificationKey = get_vk + + val getStepDomains = get_step_domains end module Proof0 = Pickles.Proof.Make (Pickles_types.Nat.N0) diff --git a/src/bindings/ocaml/lib/pickles_bindings.mli b/src/bindings/ocaml/lib/pickles_bindings.mli index 1a606bc7c2..07750ede81 100644 --- a/src/bindings/ocaml/lib/pickles_bindings.mli +++ b/src/bindings/ocaml/lib/pickles_bindings.mli @@ -67,13 +67,17 @@ val pickles : ; storable : Cache.js_storable Js.optdef_prop ; overrideWrapDomain : int Js.optdef_prop ; numChunks : int Js.optdef_prop - ; lazyMode : bool Js.optdef_prop > + ; lazyMode : bool Js.optdef_prop + ; stepDomains : int Js.js_array Js.t Js.optdef_prop > Js.t -> < getVerificationKey : ( unit -> (Js.js_string Js.t * Impl.field) Promise_js_helpers.js_promise ) Js.readonly_prop + ; getStepDomains : + (unit -> int Js.js_array Js.t Promise_js_helpers.js_promise) + Js.readonly_prop ; provers : 'a Js.readonly_prop ; tag : 'b Js.readonly_prop ; verify : 'c Js.readonly_prop > diff --git a/src/lib/proof-system/zkprogram.ts b/src/lib/proof-system/zkprogram.ts index 174a955f8e..16f257bd97 100644 --- a/src/lib/proof-system/zkprogram.ts +++ b/src/lib/proof-system/zkprogram.ts @@ -436,11 +436,13 @@ function ZkProgram< }; type CompileResult = { verificationKey: { data: string; hash: Field }; + stepDomains?: number[]; compileOutput?: CompileOutput; }; let compileOutput: CompileOutput | undefined; let compileCache = new Map>(); + let stepDomainsCache = new Map(); const programState = createProgramState(); @@ -467,6 +469,8 @@ function ZkProgram< () => cachedCompile ); if (result.compileOutput !== undefined) compileOutput = result.compileOutput; + if (result.stepDomains !== undefined) + stepDomainsCache.set(compileCacheKey, result.stepDomains); return { verificationKey: result.verificationKey }; } @@ -481,7 +485,7 @@ function ZkProgram< let proofs = methodKeys.map((k) => methodsMeta[k].proofs); maxProofsVerified = computeMaxProofsVerified(proofs.map((p) => p.length)); - let { provers, verify, verificationKey } = await compileProgram({ + let { provers, verify, verificationKey, stepDomains } = await compileProgram({ publicInputType, publicOutputType, methodIntfs, @@ -496,10 +500,12 @@ function ZkProgram< state: programState, withRuntimeTables, lazyMode, + knownStepDomains: stepDomainsCache.get(compileCacheKey), }); return { verificationKey, + stepDomains, compileOutput: { provers, verify, maxProofsVerified }, }; })(); @@ -508,6 +514,8 @@ function ZkProgram< try { let result = await compilePromise; if (result.compileOutput !== undefined) compileOutput = result.compileOutput; + if (result.stepDomains !== undefined) + stepDomainsCache.set(compileCacheKey, result.stepDomains); return { verificationKey: result.verificationKey }; } catch (error) { if (compileCache.get(compileCacheKey) === compilePromise) { @@ -815,6 +823,7 @@ async function compileProgram({ state, withRuntimeTables, lazyMode, + knownStepDomains, }: { publicInputType: Provable; publicOutputType: Provable; @@ -830,6 +839,7 @@ async function compileProgram({ state?: ReturnType; withRuntimeTables?: boolean; lazyMode?: boolean; + knownStepDomains?: number[]; }) { await initializeBindings(); if (methodIntfs.length === 0) @@ -895,7 +905,7 @@ If you are using a SmartContract, make sure you are using the @method decorator. MlBool(cache.canWrite), ]; - let { verificationKey, provers, verify, tag } = await timeAsync( + let { verificationKey, stepDomains, provers, verify, tag } = await timeAsync( `ZkProgram.${proofSystemTag.name}.compileProgram.threadPool`, () => prettifyStacktracePromise( @@ -914,17 +924,23 @@ If you are using a SmartContract, make sure you are using the @method decorator. overrideWrapDomain, numChunks: numChunks ?? 1, lazyMode: lazyMode ?? false, + stepDomains: knownStepDomains, }) ); - let { getVerificationKey, provers, verify, tag } = result; + let { getVerificationKey, getStepDomains, provers, verify, tag } = result; CompiledTag.store(proofSystemTag, tag); let [, data, hash] = await timeAsync( `ZkProgram.${proofSystemTag.name}.compileProgram.getVerificationKey`, getVerificationKey ); let verificationKey = { data, hash: Field(hash) }; + let stepDomains = await timeAsync( + `ZkProgram.${proofSystemTag.name}.compileProgram.getStepDomains`, + getStepDomains + ); return { verificationKey, + stepDomains, provers: MlArray.from(provers), verify, tag, @@ -961,6 +977,7 @@ If you are using a SmartContract, make sure you are using the @method decorator. }; return { verificationKey, + stepDomains, provers: wrappedProvers, verify: wrappedVerify, tag, diff --git a/src/mina b/src/mina index b26d0f9530..33958c1357 160000 --- a/src/mina +++ b/src/mina @@ -1 +1 @@ -Subproject commit b26d0f95307bef300046435e598287d7784c2e47 +Subproject commit 33958c1357ffe82606215ddfe32facb64907c682