The detailed reference of what CompStark implements and proves, component by component: files, theorem names, assumptions and benchmark commands. The README gives the overview.
-
Poseidon2 (
CompStark/Poseidon2): the specificationpermuteruns on any field. A field's implementation of an instance is a permutation with a proof that it is the specification (Poseidon2.Impl,CompStark/Poseidon2/Impl.lean), andPoseidon2Field Fgives Plonky3's instances of widths 16 and 24 with their implementations, on which Plonky3's hashes and challenger are built; the specification itself is the default implementation (Poseidon2Field.ofSpec). Plonky3's default constants over KoalaBear (S-boxx ^ 3,CompStark/Poseidon2/KoalaBear.lean, the round constants Plonky3 regenerated with the Grain LFSR) and BabyBear (S-boxx ^ 7,CompStark/Poseidon2/BabyBear.lean); tests check Plonky3's outputs.permute_maptransports a run along any ring homomorphism, so the permutation on CompPoly's Montgomery field is the specification (KoalaBear.fast_width16,BabyBear.fast_width16, …). KoalaBear's implementation is the packed kernels below, one state in lane 0 of a group, for any round constants of KoalaBear's shape (CompStark/KoalaBear/Poseidon2.lean:Instancesholds both widths' constants with their shape proofs,Instances.fieldandInstances.packedgive theirPoseidon2Fieldand packed kernels,Instances.perm16_eq,Instances.perm24_eq; the default instances are Plonky3's, andfixtures/CompStarkFixtures/Poseidon2Example.leanholds the ones Plonky3's exampleprove_prime_field_31draws withnew_from_rng_128fromSmallRngseeded with1, generated bycompstark-bench keccak-example-constants); BabyBear's is the specification on its Montgomery field (CompStark/BabyBear/Poseidon2.lean). The interpreter cannot run the packed kernels (CompPoly's inline C stores), so the known answers that use them run compiled (tests/CompStarkNativeTests.lean, the test driver); the prover and the benchmarks run them too. The specification is ArkLib's Poseidon2, a translation of leanSpec's, at both widths (permute_arkLib16,permute_arkLib24,CompStark/Poseidon2/ArkLib.lean), at Plonky3's current KoalaBear constants, which ArkLib's parameters predate; proving it found two indexing bugs in ArkLib's round constants, fixed in ArkLib#1316. -
Packed Poseidon2 (
CompStark/Poseidon2/Packed.lean): eight states at a time, one per vector lane, with macro-generated round kernels on Montgomery words.permute16_width16andpermute24_width24(PackedCorrectness.lean,PackedCorrectness24.lean, on the width-genericPackedLanes.lean) prove that on every group of eight states they compute Plonky3's width-16 and width-24 permutations, read in the spec field, and leave the rest of the buffer unchanged. Every kernel first writes eight words of its group back unchanged, so the compiler knows the buffer is exclusive and interleaves loads with stores instead of holding all loaded words until the first exclusivity check. The multiplier-bound S-box kernel leaves its cubes below2 p(cubeLazy) and themat4kernel reduces them; a partial round's sum is a tree beside its S-box, and coordinate0isrest - y. The round loops carry a buffer of fixed size and check the group and the constant tables once per permutation. Single thread, on an 8-core Ryzen 7 3700X: about 157 ns per width-16 permutation and 255 ns per width-24 one, against 118 ns and 193 ns for Plonky3's AVX2-packedPoseidon2KoalaBearand 676 ns for its scalar width-16 one (CompStarkBench poseidon2-packed 14,CompStarkBench perm24 14,bench/rust: compstark-bench bench 14). -
Hashing and Merkle commitments (
CompStark/Hash/Sponge.lean,CompStark/Merkle/Tree.lean,CompStark/Plonky3/Hash.lean): Plonky3'sPaddingFreeSpongeandTruncatedPermutation, andMerkleTree::newover lists of matrices of mixed heights, with the Poseidon2 instances of Plonky3'sPoseidon2MerkleMmcs(width-24 sponge for leaves, width-16 compression). Tests check digests and roots against Plonky3, including odd heights and injected smaller matrices. For matrices of one height, the tree is VCVio's verified Merkle tree (CompStark/Merkle/VCVio.lean): the root isbuildMerkleTreeWithHash's, openings aregenerateProof's paths read from the leaves up, honest openings verify, and two accepted openings of different rows at one index give a collision of the compression or of the leaf hash (verifyBatch_binding). VCVio is pinned at ArkLib's revision with small fixes for Lean v4.35's Mathlib. Commitments are Merkle caps, Plonky3'sMerkleTreeMmcscap_height(Fri.Params.capHeight,Stark.Cap): the2 ^ hdigests of the layerhlevels below the root,hcapped at the tree's levels, the root alone forh = 0(Merkle.capOf); the transcript observes a cap's digests in order (capElems), and a multi-opening is checked against the cap entry above each index (Merkle.verifyMultiM, which rejects a cap of another size and matrices injected above the cap;verifyBatch_of_verifyMultiandverifyBatch_of_verifyMulti_mixedgive every query an accepted opening of the subtree under its cap entry). -
Packed Merkle commitment (
CompStark/Merkle/Packed.lean): the root of one matrix given as chunks of rows, leaves hashed eight rows at a time by the packed width-24 sponge in parallel chunk tasks (the fast prover hashes its extension in place, in tasks over ranges of 512 rows,leavesIn), layers compressed eight pairs at a time by the packed width-16 permutation in parallel range tasks.root_eq(RootCorrectness.lean, onPackedCorrectness.lean) proves it isMerkle.Poseidon2.rootof that matrix. For$2^{14}$ rows of 64 elements: about 3.8 ms on 16 chunks and 25.6 ms on one core, against 2.1 ms and 15.6 ms for Plonky3'sMerkleTreeMmcs::commiton its default rayon pool and on one thread (CompStarkBench merkle 14 64 16,taskset -c 5 CompStarkBench merkle 14 64 1,bench/rust: compstark-bench merkle 14 64,RAYON_NUM_THREADS=1). -
STARK prover and verifier (
CompStark/Stark/Prover.lean,CompStark/Stark/Verifier.lean, onCompStark/Challenger.lean,CompStark/Merkle/Mmcs.lean,CompStark/Fri/Prover.leanandCompStark/Fri/Verifier.lean): Plonky3'suni-starkproveandverifywith the two-adic FRI commitment scheme at Plonky3 revisiona4982ec, including FRI folds of higher arity, over any base field with aBaseFieldinstance and any extension field of it with aChallengeinstance (CompStark/Stark/Challenge.lean: coordinates, base embedding and a ring isomorphism with CompPoly's spec field over the base field), with the Poseidon2 Merkle commitment above and the width-24 duplex challenger. The fast backend is quartic only. AIRs are lists of constraint expressions (CompStark/Stark/Air.lean); as in Plonky3 (main_next_row_columns), an AIR that does not read the trace's next row (Air.mainNextRow) has its trace opened atζonly, and the verifier rejects a next row opened for it. The transcript is Plonky3's typed Fiat–Shamir layer: the STARK, the commitment scheme and FRI each observe a domain separator first (CompStark/DomainSeparator.lean: the protocol's name, the Keccak-256 hash of its interaction pattern and the instance's shape), the duplex challenger tags each absorb with its length, and besides the fold and query rounds the rounds ofζand of the batching challenge may grind too. FRI opens its queries as Merkle multi-openings with pruned paths (Merkle.verifyMultiM,CompStark/Merkle/Multi.lean: an accepted multi-opening gives every query an accepted single opening, and honest pruned paths verify). Preprocessed columns follow Plonky3'ssetup_preprocessedandverify_with_preprocessed: an AIR carries its preprocessed trace (Air.preprocessed,Air.preprocessedWidth), constraints read its current and next rows (Expr.prepCur,Expr.prepNext), andsetupcomputes its verifying key (PreprocessedKey: the width, the trace's log height and the Merkle cap of its low-degree extension). The transcript observes the preprocessed cap after the trace cap; the commitment scheme opens the preprocessed trace atζ, and atζ ωonly if the AIR reads its next row (Air.preprocessedNextRow); the verifier rejects a key or openings that do not match the AIR's shape. It also rejects an empty quotient cap, and FRI an empty commit-phase cap, before observing them: observing nothing would merge two duplexing rounds of the challenger, and Plonky3 rejects such a cap when it checks the openings against it. Periodic columns follow Plonky3'speriodic_columns: an AIR lists one period per column (Air.periodic, read byExpr.periodic), of a length that is a power of two dividing the trace's height; as a polynomial a column of periodpisf(Xⁿᐟᵖ),fits period's interpolant (periodicPoly). Periodic columns are neither committed nor opened: the domain separator carries their number, the verifier interpolates each atζ ^ (n / p)(periodicAt), and the prover extends each period onto the quotient domain (periodicOnQuotient). With periodic columns Plonky3 sizes the quotient by the constraints' polynomial degrees on the trace (Expr.degBound, a periodic column of degreen - n / p) rather than bydegree_multiple, so the chunk count depends on the trace length (Air.logQuotientChunks). On Plonky3's proofs of a Fibonacci AIR, of a degree-3 AIR with a two-chunk quotient, of an AIR with preprocessed columns, with and without next-row reads, and of AIRs with periodic columns (Plonky3'sPeriodicAirand a degree-3 AIR whose quotient the periodic sizing halves), for several FRI parameter sets (tests/data), grinding at every site included, the verifier accepts them and the prover reproduces them exactly, grinding witnesses included. Both are reference implementations: the prover's speed comes from the fast backend below. No zero knowledge yet. The verifier is written once over its hash functions (CompStark/Primitives.lean: a challenger that observes and samples base elements, the Merkle leaf hash and compression, all in a monad):verifyisverifyWith (poseidon2 F), and the same code runs on random oracles. FRI folds by Plonky3's schedule for the input heights (foldSchedule), which proofs no longer carry. -
Base field (
CompStark/BaseField.lean): the verifier, the specification prover and the whole analysis (soundness, the verifier on random oracles, the spec field and its error terms) are generic over a prime fieldFof odd characteristic with aBaseFieldinstance: finiteness as a proposition (Finite F, so no executable enumerates the field; the analysis counts it byNat.card F), a prime numberp = |F|of elements (card_prime, so the characteristic isp,ringChar_eq_card), a canonical valueF → ℕ, a bijection onto[0, p)forp = |F|(CompPoly'sCanonicalNat, of whichsampleBitskeeps the low bits); two-adic generatorstwoAdicGen kof order2 ^ kfork ≤ twoAdicity, each the square of the next, so that2 ^ twoAdicity ∣ p - 1; and a coset shiftgen ≠ 0withgen ^ 2 ^ twoAdicity ≠ 1. The domains of a configuration must be two-adic subgroups:logN + logBlowup ≤ twoAdicity(ValidConfig). The bias ofsampleBitsis bounded for2 ^ b ∣ p - 1(CompStark/Sound/Sampling.lean), which the query indices satisfy, and the grinding probability ispowFraction F bits = ((p - 1) / 2 ^ bits + 1) / pfor anyp. CompPoly's fast Montgomery fieldFastField pof any prime with aMont32Field pinstance is a base field given its domain data,Mont32BaseField p: the two-adicity, the two-adic generators and the shift with their proofs (CompStark/Mont32/BaseField.lean); its field structure is CompPoly's, so CompPoly's fast quartic extensionExt4 pover anyBinomial4 pis a challenge field (CompStark/Mont32/Challenge.lean). KoalaBear (CompStark/KoalaBear/Field.lean: Plonky3's shift3andTWO_ADIC_GENERATORS, two-adicity 24) and BabyBear (CompStark/BabyBear/Field.lean: Plonky3's shift31andTWO_ADIC_GENERATORS, two-adicity 27) are instances, with their quartic extensions; KoalaBear's quintic one is a challenge field too (CompStark/KoalaBear/Challenge.lean). Plonky3's primitives over any base field with Poseidon2 instances, the duplex challenger, the Poseidon2 Merkle hashes,poseidon2,verifyand the specification proverprove, live inCompStark/Plonky3/, and the JSON proof format of Montgomery fields inCompStark/Mont32/Json.lean; Plonky3's proofs over BabyBear verify and are reproduced like KoalaBear's (tests/CompStarkTests/BabyBear.lean). The fast backend and the completeness proof are generic too (below); the concrete numbers (CompStark/Calculator/Security.lean) are KoalaBear's. -
Fast prover backend (
CompStark/Stark/Fast.lean), over any 32-bit Montgomery fieldFastField pwith its domains (Mont32BaseField p), the quartic extensionExt4 p(Binomial4 p), Poseidon2 instances (Poseidon2Field) with permutations of packed groups of eight states (PackedPoseidon2 p,CompStark/Poseidon2/PackedField.lean; KoalaBear's vectorized kernels, or any field's implementation state by state,PackedPoseidon2.ofLanes) and CompPoly's compiled coset LDE kernels. Each field instantiates it in the CPU-targeted native library, where the compiler specializes the generic code with the field's Montgomery constants as immediates:CompStark/KoalaBear/Fast.leanandCompStark/BabyBear/Fast.lean(backendFor; over KoalaBear on any Poseidon2 instancesInstances.backendFor, correct byInstances.prove_fast).proveWithruns on aBackendof kernels: the transcript's permutation and grinding, low-degree extension, the Merkle digest layers, row access, the quotient values, opened values and rows batched by powers ofα. The fast backend keeps matrices as packed Montgomery words by columns, in blocks of the trace's height (PMat), uses the packed Poseidon2 (eight grinding candidates per permutation as Plonky3, in parallel waves), CompPoly's coset LDE with plans and quotient tables prepared per instance shape (backendFor logBlowup shapes), column by column (ldeColumns: ranges of 256 rows of the trace transposed into columns, every column extended into its blocks, no transpose or join of the output), the packed Merkle tree, which commits to matrices of mixed heights by hashing the tallest ones' rows side by side, eight rows per packed group loaded from the columns (Merkle.Packed.hashColumns,CompStark/Merkle/PackedColumns.lean), and injecting each smaller height into the layer of its height with one more packed compression (commitLayers;commitLayers_eqproves it is the specification's tree), vectorized word kernels (CompStark/Stark/Words.lean, four products summed in 64 bits and reduced once per word,Words.mulAdd4: the opened values dot every column with the barycentric weights, and the batched rows multiply-add four columns at a time into the four coordinate planes, every column word read once,Words.mulAdd4x4Into), FRI's folds of any arity as one kernel (Backend.fold,Fri.foldOnce: arity 2 on planes, larger arities group by group in parallel ranges,lagrangecompiled with one inversion per point by thecsimplemmalagrange_eq_lagrangeFast), extension vectors as coordinate planes with inverse denominators by the norm (CompStark/Stark/ExtWords.lean) and parallel tasks throughout, and reproduces Plonky3's proofs like the specification backend (CompStarkBench prove fast AIR … JSON). On the representative configuration,wide 64(64 columns, degree-3 transitions, two quotient chunks) with blowup 2, 100 queries and 16-bit query grinding, it is on par with Plonky3 on 16 threads: about 333 ms against 345 ms for$2^{16}$ rows, 99 ms against 88 ms for$2^{14}$ , 1.14 s against 1.20 s for$2^{18}$ ; with one worker thread about 1.32 s against Plonky3's 1.96 s on one thread for$2^{16}$ , 0.36 s against 0.49 s for$2^{14}$ and 4.8 s against 7.9 s for$2^{18}$ (medians of three interleaved rounds on a shared 16-core host under load) (CompStarkBench prove fast wide 16 1 0 1 100 0 16,bench/rust: compstark-bench prove-bench wide 16 1 0 1 100 0 16,LEAN_NUM_THREADS=1,RAYON_NUM_THREADS=1;CompStarkBench phases 16andcompstark-bench prove-trace …break the time down by phase). The fast prover produces the specification's proof:prove_fast(CompStark/Stark/Fast/Refines.lean) provesproveWith (backendFor …) params air logN words publicValues = prove (Ext4 p) params air logN trace publicValuesfor a trace given to the fast prover as row chunks of packed Montgomery wordswordsthat encodetrace(encoded : RT logN air.width words trace,CompStark/Stark/Fast/Rel.lean), under explicit assumptions, for a field with a fourth root of unity (2 ≤ twoAdicity, for the norm) whose largest domains of extension elements are addressable (DomainsAddressable);KoalaBear.prove_fastdischarges both. On the input (InputOk,CompStark/Stark/Refines.lean): the trace has2 ^ logNrows of the AIR's positive width, the AIR reads only its own columns,logN + logBlowup ≤ twoAdicityso that the extension stays within the two-adic subgroups, the quotient's chunks fit the blowup, and the extended trace and quotient are addressable by machine indices. On the run: the specification's out-of-domain pointζ(outOfDomainPoint) lies outside the base field. The last one is the interesting one: forζin the base field, which happens over KoalaBear with probability about$2^{-93}$ , someζ - xvanishes, and the two backends compute different garbage from batch inversion and the norm.proveWith_refinesreduces the theorem to laws of the backend's kernels (Fri.Refines), each stating the shapes it needs (CompStark/Fri/Assumptions.lean) and proved inCompStark/Stark/Fast/: the transcript, with the smallest grinding witness from the parallel waves, CompPoly's LDE, every Merkle layer and opening, the quotient, the opened values with inverses by the norm, the batched reduction and the folds; the generic proof carries the shapes through the FRI commit phase (CompStark/Fri/Shapes.lean). Packed values relate to the specification's by their encoding. A backend takes the main trace in its own representation (Backend.Trace, read back bytraceValues): the fast one takes row chunks of packed words, the form a parallel trace generator writes, and extends them column by column without joining them (ldeWordsWith_rel). The quotient's constraints compile once per AIR into a straight-line program (Prog.compile,CompStark/Stark/Prog.lean): a hash-consed DAG with constant folding, scheduled and allocated to registers by liveness, accumulating every constraint with the coordinates of its weight. The backend runs it on blocks of up to 256 rows in the extension's bit-reversed order on the group machine (Prog.groupMachine,CompStark/Stark/ProgGroup.lean): the block's four accumulators and registers in one buffer, every instruction one call of a kernel that runs sixteen words at a time over the block in place, reading the block's rows copied from the extension's columns, and its next rows copied in one piece when they are consecutive, every accumulation adding the operand times the weight's four words to the four accumulators in one pass, multiplications with Plonky3's form of the Montgomery reduction (Prog.mulM: the high words of the product and of its quotient timesp, subtracted;mulM_eqproves it is the field's multiplication); blocks of fewer than sixteen rows, or not a multiple of sixteen, run on the block interpreter (Prog.runBlock,CompStark/Stark/ProgWords.lean). The compiler is proved (compile_run: on a row, the program accumulates the constraints' values with their weights) and so are the group machine (runGroup_rel,CompStark/Stark/Fast/ProgGroup.lean: on every row of a block, it runs the program on the field's values) and the block interpreter (runBlock_rel), which give the quotient's law (quotientWith_rel). A program can also run as straight-line code generated for it (CompStark/Stark/ProgGen.lean):#gen_group_chunksevaluates the program during elaboration, cuts its code into chunks of about 2000 instructions, each one compiled function of kernel calls with the field's constants as immediates (group_kernels p,CompStark/KoalaBear/Group.lean), and checks each by the kernel'srflequal to the code's run on the group machine: both reduce to the same kernel calls, so no kernel and no field arithmetic unfolds; chunks of one program can be generated in separate modules built in parallel, and#gen_group_kernelassembles them into aProg.GroupKernel, with the programProg.compile csof the constraintscsit was generated from (Fast.AirKernel, evaluated once).backendFor kernelschecks once that each kernel's code is its program's (checkedKernels), takes an AIR's program from the kernel generated for its constraints instead of compiling them, and runs any other program by the kernel whose code it is (kernelFor), the proofs unchanged (prove_fastandproveBatch_fasthold for anykernels). The Keccak fixture's kernel (fixtures/CompStarkFixtures/KeccakKernel.lean, 19 chunks in four modules) is checked against the AIR's program and used by the Keccak example's proof test and benchmarks.verifyFast(CompStark/Stark/Fast/Verify.lean;KoalaBear.verifyFast,KoalaBear.Instances.verifyFast) is Plonky3's verifier on a 32-bit Montgomery field with packed Poseidon2 kernels, computed faster and proved equal toverify(verifyFast_eq): the leaves of every Merkle multi-opening are hashed eight at a time in packed groups, and a query's batched quotient∑ αⁱ (pᵢ(z) - pᵢ(x)) / (z - x)of a matrix is(∑ αⁱ pᵢ(z) - ∑ αⁱ pᵢ(x)) / (z - x), the opened values' sums computed once for all queries and the row's by multiplications by base elements. -
AIR fingerprints, the Keccak fixture and Plonky3's Keccak example.
Air.fingerprint(CompStark/Stark/Fingerprint.lean) evaluates every constraint and every lookup of an AIR at one point of the quartic extension drawn from SplitMix64 and hashes them with the AIR's shape by Plonky3's Poseidon2 sponge, with a JSON report of one constraint per line;compstark-bench fingerprint AIR LOG_N(bench/rust/src/fingerprint.rs) computes the same from any Plonky3Airthrough upstream's symbolic builder, and the tests match the example AIRs and Plonky3'sKeccakAir(2633 columns, 3182 constraints) line for line. Plonky3's Keccak AIR is a fixture of the tests and benchmarks (fixtures/CompStarkFixtures/Keccak.lean), with a parallel trace generator writing row chunks of packed words, checked row by row against upstream'sgenerate_trace_rows. Plonky3's exampleprove_prime_field_31for Keccak-f over KoalaBear with Poseidon2 Merkle hashes uses caps of height 3 and the Poseidon2 constantsnew_from_rng_128draws fromSmallRngseeded with1; the example binary does not write its proof, socompstark-bench keccak-example L DIRreproduces its configuration from the same upstream functions and writes the proof, whose postcard size equals the binary's reportedProof size(1027222 bytes atL = 6). The verifier accepts it, and the fast prover from the generated trace and the specification prover reproduce it (tests/CompStarkTests/Keccak.lean). End to end at the example's configuration (blowup 2, arity up to 8, 87 queries, 16 bits of query grinding and 10 of batching grinding, caps of height 3), whole proofs from the generated trace, medians of three interleaved rounds on a shared Ryzen 7 3700X (8 cores, 16 threads; load averages 3 to 11), against Plonky3 built for the native CPU: on 16 threads 0.99 s against Plonky3's 0.84 s for$2^{15}$ rows, 1.96 s against 1.82 s for$2^{16}$ , 3.85 s against 4.52 s for$2^{17}$ , and 8.2 s against 10.9 s for$2^{18}$ (one round); on one thread 3.43 s against 2.05 s for$2^{14}$ and 6.93 s against 4.07 s for$2^{15}$ (two rounds). Trace generation takes 56 ms, 110 ms and 234 ms against Plonky3's 23 ms, 43 ms and 85 ms on 16 threads. By phase on 16 threads at$2^{16}$ (CompStarkBench keccak-phases 16): the trace's extension 0.52–0.56 s, its commitment 0.83 s, the quotient 0.33 s and the openings 0.28–0.29 s (opened values 0.10 s, batched rows 0.09–0.10 s, commit phase 0.04 s), against Plonky3's 1.43 s to commit to the trace, 0.25 s for the quotient and 0.13 s to open. The commitment's cost is the width-24 Poseidon2 permutation, about 1.3 times Plonky3's: the commitment alone takes about 0.44 s and 0.86 s on 16 threads for$2^{15}$ and$2^{16}$ rows and 3.0 s on one thread for$2^{15}$ , against about 0.30 s, 0.62 s and 2.15 s for Plonky3's Merkle tree on the same extensions (compstark-bench merkle LOG_H 2633). The extension's cost is CompPoly's NTT. The proof peaks at 1.3 GB, 2.2 GB, 3.9 GB and 6.8 GB of memory on$2^{15}$ to$2^{18}$ rows, the trace transposed for its extension in ranges of 256 columns that are freed as they are extended, against Plonky3's 0.7 GB, 1.4 GB, 2.8 GB and 5.5 GB, within a 12 GB limit at$2^{18}$ . The fast verifier takes about 50 ms against Plonky3's 11 ms, the specification verifier about 155 ms. The benchmarks:CompStarkBench keccak LOG_N REPS,CompStarkBench keccak-prove LOG_N(one proof alone in the process, for its peak memory, and the verifiers),CompStarkBench keccak-phases LOG_N,CompStarkBench openings-phase LOG_N REPS(the values at the out-of-domain points and the batched reduction of the trace's extension),bench/rust: compstark-bench keccak-bench LOG_N REPS, andcompstark-bench keccak-prove-trace LOG_Nfor Plonky3's phases. -
Completeness (
CompStark/Complete/Stark.lean): over any base field with Poseidon2 instances, if a trace satisfies the AIR, the honest prover's proof verifies (KoalaBear.completenessinstantiates it):theorem completeness (config : ValidConfig params air logN) (luck : NoBadLuck EF params air logN trace publicValues) : air.Satisfied logN trace publicValues → publicValues.length = air.numPublicValues → verify params air (setup params air logN) (prove EF params air logN trace publicValues) publicValues = .ok ()
The verifier checks that there are as many public values as the AIR declares (
Air.numPublicValues), as Plonky3 does.air.Satisfied(CompStark/Stark/Satisfied.lean) says the trace is a2 ^ logN × widthmatrix on whose rows every constraint vanishes, with Plonky3's selector values there.ValidConfigcollects the conditions on the configuration, checkable before proving: the constraints read only the AIR's columns (a next row only if the AIR declares it reads it), the preprocessed trace has2 ^ logNrows of the AIR's preprocessed width, the periodic columns' periods are powers of two dividing2 ^ logN, the constraints' degrees fit the quotient's chunks (Air.DegreeFits, which counts the transition selector as degree 1 where Plonky3'sdegree_multiplecounts 0), the extended trace domain is a two-adic subgroup of the base field, the chunks fit the blowup, which is positive, FRI folds at least once per round down to a final polynomial no longer than the trace, and makes at least one query.NoBadLuckcollects the two unlikely events in which the honest run fails: the out-of-domain pointζlands in the base field (probability$p^{1-d}$ for an extension of degree$d$ , about$2^{-93}$ for the quartic and$2^{-124}$ for the quintic one), or a proof-of-work search finds no witness, which the transcript records (proveTranscript, where Plonky3's grinder would panic). The proof (CompStark/Complete/) replays the prover's transcript in the verifier; the honest Merkle openings verify through the VCVio bridge; the batched DEEP quotients of the committed low-degree extensions are low degree and the verifier recomputes them at each query; FRI's folds keep low degree, for inputs of mixed heights, and each query's folds replay the prover's down to the final polynomial; and the quotient's chunks recompose atζto the folded constraints overZ_H(ζ). The theorem holds for every challenge fieldEFwith aChallengeinstance (CompStark/Stark/Challenge.lean), such asKoalaBear.Fast.Ext4andKoalaBear.Fast.Ext5. Soundness: see below.On the interaction layer (
CompStark/Iop/StarkCompleteness.lean), the honest prover of a satisfying trace (Iop.Setup.honestProver: the trace's and the quotient chunks' extensions ontoLand their polynomials' values atζandζ ω_n) is accepted by the oracle protocol on every execution whose out-of-domain pointζavoids the base field (Iop.Setup.completeness, for aValidConfig): the honest quotient makes the check atζhold, and DEEP quotients of codewords are codewords (deepWord_mem_code). FRI runs in Plonky3's coordinate, on the subgroup⟨ω⟩whose cosetLis (Setup.friDomain); its codes there are those onL. -
Knowledge soundness (
CompStark/Iop/StarkSoundness.lean), of the STARK as an oracle protocol on ArkLib's interaction layer (CompStark/Iop/Stark.lean, on the setups and checks ofCompStark/Iop/StarkSetup.lean): the protocol behindverifywith oracles in place of the Merkle commitments and uniform challenges in place of Fiat–Shamir. The prover's first message is the trace length, a public move the verifier admits (Statement.Length), so that every later shape is a function of it; then the trace oracle,α, the quotient oracle,ζ, the claimed values in the clear, the batching challenge, and ArkLib's FRI on the batched DEEP word along Plonky3's schedule (Fri.Interaction.protocol,Setup.friSchedule). The verifier holds the extension of the AIR's preprocessed trace as words of its own (Setup.preprocessedWord), reads the DEEP word at a point through a query program on the trace and quotient oracles (Setup.readDeep), and accepts if FRI accepts and the constraints hold atζ. The prover is any strategy; anIop.Prover, words and messages as functions of the challenges, plays with FRI's honest prover (Prover.strategy) and is accepted on every run where its claimed values pass the check and its DEEP word has low degree (Prover.strategy_accepts). The extractor list-decodes the committed trace:closeTracesare the traces whose columns are polynomials of degree at mostnagreeing with the committed words on a common set of(1 - δ') Npoints. FRI tests proximityδ, and the extractor list-decodes atδ' ≥ δ.Statement.farAcceptanceis the probability that the verifier accepts while no trace close to the committed one satisfies the AIR at the claimed length:theorem knowledgeSoundness (C : Statement F) (publicValues : List F) {OutP : (C.protocol EF).tree.ExecutionPath → Type} (prover : Interaction.Oracle.Prover.Strategy unifSpec (C.protocol EF).tree (C.protocol EF).roles OutP) (δ δ' : ℕ → NNReal) (hδ' : ∀ n, C.Admits n → (δ' n : ℝ) < 1 - C.rate) : C.farAcceptance EF publicValues δ' prover ≤ C.knowledgeError EF δ δ'
The statement (
Statement: FRI parameters and an AIR reading only its columns) has no trace length: the prover claims one the verifier admits (Statement.Admits:SoundConfig, the verifier's checks on the length and the parameters, and the preprocessed trace's length) and the protocol is the STARK there (Statement.setupAt), and the bound is the largest knowledge error over the admitted lengths (Statement.knowledgeError), every admitted length covered, FRI's unfolded one included. The proof is a round-by-round knowledge certificate (CompStark/Iop/StarkKnowledge.lean): the doomed-transcript states ofCompStark/Sound/Rbr.leanforα,ζanda(AlphaBad,ZetaBad,BatchBad, each with its error term below), with the trace oracle mapped to the extracted trace, appended withKnowledgeCertificate.appendto ArkLib's FRI certificate on the DEEP word (Fri.Interaction.certificate), gated by the constraint check atζ(gateCertificate); ArkLib'sexecuteStrategies_extraction_leturns it into the bound at each length (Setup.extraction_le_knowledgeError), and whatever length the prover sends, the certificate there bounds the rest. FRI's error is ArkLib's (friErrorWith_eq). The knowledge error at a length (CompStark/Iop/StarkError.lean) has one term per challenge, withℓthe list sizes of the committed words' interleaved codes atδ'(Setup.listSize):ℓ (#constraints - 1) / |𝕂|forα,(ℓ · identityDegree + n + N) / |𝕂|forζ(Schwartz–Zippel on the constraint identity of each candidate of the committed words, andζlanding on the trace domain or onL, where the DEEP quotients divide by zero, and so then doesζ ω_n,Lbeing a coset of a group holdingω_n; decoding finds the trace, permutation trace and quotient polynomials on one common set, so the candidates are those of the committed words as one family,Setup.Committed, and the term is linear in the list size, as in Haböck's DEEP-ALI analysis, https://eprint.iacr.org/2022/1216, Theorem 8), the correlated-agreement error of the powers of the batching challenge (the batched wordδ-close although the words are not on a common set atδ'; forδ ≤ δ'at most the mutual correlated-agreement error atδ,batchingError_le_mcaError), and FRI's soundness error atδ(FriRounds.error, the error of ArkLib's interaction-layer FRI certificate,Fri.Interaction.error, from ArkLib's FRI query and folding analysis of ArkLib#992, after Garreta–Mohnblatt–Wagner, with injected inputs). FRI's tree is Plonky3's commit phase: each fold round is the prover's word at the layer, the first being the batched DEEP word, then the folding challenge; the last layer has no word, only the final polynomial; and each query checks every committed word against the verifier's word there, the DEEP word computed from the trace and quotient openings at the first layer and the fold of the previous committed word after. The theorem holds for everyδand everyδ'below1 - rate; the error is small up to the Johnson bound1 - √rate, where the list sizes are, and below the unique-decoding radiusℓ ≤ 1(listSize_le_johnson,listSize_le_one,CompStark/Sound/Johnson.lean). Concrete security (CompStark/Calculator/Security.lean): for the Fibonacci AIR with blowup 16, FRI folding by arity 2 and 110 queries, at every trace length the verifier admits (up to$2^{20}$ rows), the knowledge error with FRI atδ = 15/32 - 2^{-15}and the extractor atδ' = 15/32 - 2^{-19}, below the unique-decoding radius$15/32$ , is at most$2^{-98}$ (fibonacci_knowledgeError_le, about$2^{-98.95}$ exactly on$2^{20}$ rows,$2^{-100.37}$ on$2^{10}$ ). Batching all opened words by powers of one challenge needs correlated agreement for curves: ArkLib bounds it byk · N / |𝕂|below the unique-decoding radius (ProximityGap.exists_exactPowerAgreement_uniquewithProximityGap.mcaError_powers_le_of_exactPowerAgreement, on [BCIKS20] Theorem 6.1 for curves;McaBound.uniqueDecoding_holds), and with a proximity lossδ < δ'the error is independent ofN(batchingError_le_curve,CompStark/Sound/CurveCA.lean). FRI's folds use the same argument through lossy correlated agreement, at any arity:friErroris the lossy FRI error at the best proximity loss, and a fold by$2^k$ onto a domainL'costs(2^k - 1) min(B, |L'|) / |𝕂|(one more word if it injects an input), withB = max(T₁, δ_int / loss)independent of|L'|forδ ≤ (1 - rate) / 2 - 1/|L'|and the powers MCA bound|L'|otherwise (friErrorWith_le,lossyLevel_le,CompStark/Sound/LossyMca.lean), at the price of#folds · lossin the query term. With it,wide 64(64 columns, degree-3 constraints, 136 opened words) with blowup 4, arity 2 and 150 queries has knowledge error at most$2^{-98}$ at every admitted length, up to$2^{22}$ rows, with FRI atδ = 3/8 - 2^{-12}and the extractor atδ' = 3/8 - 2^{-14}(wide_knowledgeError_le, about$2^{-98.71}$ exactly on$2^{22}$ rows,$2^{-100.17}$ on$2^{20}$ ), dominated by the out-of-domain term(identityDegree + n + N) / |𝕂|, about$2^{-98.95}$ on$2^{22}$ rows, and FRI's queries. Since that term grows with the list size, the extractor stays below the unique-decoding radius at this size. Every term but the queries is an integer over$|\mathbb{K}|$ , so the quartic extension ($p^4 \approx 2^{124}$ ) caps every configuration near 100 bits at these sizes (the interactive error cannot credit grinding, which only costs a prover that pays for its oracle calls; on Plonky3's verifier below, grinding lifts the cap to about 118 bits per duplex call); the analysis is generic in the extension field, and over the quintic one ($p^5 \approx 2^{155}$ )wide 64with blowup 4 and 190 queries has knowledge error at most$2^{-128}$ at every admitted length (wide5i_knowledgeError_le, about$2^{-128.22}$ on$2^{22}$ rows). The non-interactive verifier is covered in the random-oracle model, next. State restoration (CompStark/Iop/StarkStateRestoration.lean,CompStark/Iop/BatchStateRestoration.lean): the STARK and the batch STARK are public-coin (Statement.publicCoin: the lengths the prover's first move, every challenge with one continuation, FRI's presentationFri.Interaction.publicCoinappended), their certificates with uniform query positions sample every challenge uniformly, and the verifier's checks are a program of the branch (Statement.terminal). ArkLib'sKnowledgeCertificate.stateRestoration_extraction_lethen bounds the state-restoration game, where the adversary queries a random oracle for each challenge at its erased prefix and chooses the public values and every message: the checks pass while no trace of the claimed length close to the committed one satisfies the AIR with probability at mostQ · maxError + sumErrorof the node errors (Statement.stateRestoration_le): at each length the folding, out-of-domain and batching errors, each FRI fold's error and FRI's query error at the length's proximity loss, whose path sum is the interactive error at that loss (Statement.pathError_nodeErrors). For the batch, a node's error is the largest over the instances (Batch.Setup.nodeErrors). The random-oracle theorems below are proved directly on Plonky3's verifier and do not go through this game. -
Knowledge soundness of Plonky3's verifier as it runs (
CompStark/Fs/StarkBcs.lean), from ArkLib's BCS theorem (KnowledgeCertificate.bcs_knowledge_soundness):verifyWithitself, with Merkle commitments, Fiat–Shamir and grinding, run on random oracles in place of Poseidon2's three roles (CompStark/Fs/RandomOracle.lean): the duplex challenger, which takes the capacity and the elements observed since the last squeeze and answers a new capacity andKsamples; the Merkle leaf hash; and the compression. The statement is aStatement, FRI parameters and an AIR with no trace length: the verifier takes the length from the proof, as Plonky3's does, and the theorem covers every length it admits (Statement.Admits), with the STARK at each (Statement.setupAt). The game (Statement.starkGame) computes the verifying key of the AIR's preprocessed trace on the oracles, runs an adversary that outputs public values and a proof, and runs Plonky3's verifier on them, all against one logged lazy random oracle (ArkLib'sBcs.realImpl, the verifier's oracles read as a duplex chain and two Merkle hashes bytranslate,CompStark/Fs/HashChain.lean). The extractor reads the trace off the log's hash answers under the proof's trace commitment (traceMessageOf,merkleCache,hashCacheOf):theorem knowledgeSoundness_bcs (h8 : 2 ^ 8 ≤ Nat.card F) (h64 : twoAdicity F < 2 ^ 64) (hK : 0 < K) (δ δ' : ℕ → ℝ≥0) (hδ' : ∀ n, C.Admits n → (δ' n : ℝ) < 1 - C.rate) (loss : C.Length → ℝ≥0) (adversary : OracleComp (spec F K) (List F × Proof F EF)) {Q q : ℕ} (hQ : IsQueryBoundP adversary (fun t => isDuplex t = true) Q) (hq : IsTotalQueryBound adversary q) : Pr{let z ← (simulateQ Bcs.realImpl (simulateQ translate (C.starkGame EF K adversary))).run []}[ z.1.2.2.2 = .ok () ∧ ∀ h : C.Admits z.1.2.2.1.degreeBits, ∀ trace ∈ closeTraces (C.setupAt EF _ h) (δ' z.1.2.2.1.degreeBits) ((C.setupAt EF _ h).traceWords ((C.setupAt EF _ h).traceMessageOf (merkleCache (Bcs.hashCacheOf z.2)) z.1.2.2.1.traceCommit)), ¬ C.air.Satisfied z.1.2.2.1.degreeBits trace z.1.2.1] ≤ chainError (Digest F) (Q + C.verifierQueries EF + 3 * C.verifierChainQueries EF K) + hashBadError F (2 ^ C.params.capHeight) (C.capOffsets EF).length (q + C.verifierQueries EF) + (maxError (fun (_ : C.Input) node ↦ C.ratio EF node * C.nodeErrors EF δ δ' loss (1 / Nat.card F) node) * (Q + C.verifierQueries EF + 3 * C.verifierChainQueries EF K : ℕ) + sumError (fun (_ : C.Input) node ↦ C.ratio EF node * C.nodeErrors EF δ δ' loss (1 / Nat.card F) node))
The protocol is the STARK's public-coin presentation with every challenge drawn as Plonky3's verifier draws it (
Fs/Draws.lean:extDrawfor an extension element, after a grinding sample if the round grinds and with no witness at0bits,queryDrawfor the query positions, each the low bits of one sample), from the answers of the challenge's squeezes,Ksamples each: a challenge needing more samples than one squeeze holds squeezes again with the empty input. Its layout (Setup.layout,Statement.layout,CompStark/Fs/Stark.lean) is what the verifier observes before each challenge, as Plonky3 observes it: the domain separators, the public values, the claimed values, the final polynomial, the grinding witnesses, and each committed word's Merkle cap,2 ^ min capHeight depthdigests, opened to the word read off a snapshot of the hash answers under it (merkleCache,StarkWords.lean). The observations are prefix-free (Statement.layout_prefixFree) through the domain separator's length bytes (DomainSeparator.starkSeedOf_inj, which needs the challenger's byte chunks of⌊log₂ p / 8⌋ ≥ 1bytes and lengths within the domain separator's eight bytes:h8,h64). Each draw is bounded by its ratio against the law the knowledge certificate samples at its node (Statement.bounded_decoder): the grinding'spowFraction F bits, with FRI's query positions drawn from the law Plonky3's verifier draws them from, the low bits of uniform base elements (Setup.queryPositions, slack1/p), so the positions' bias is paid inside the query error,(1 - δ + #folds · loss + 1/p) ^ numQueries. The node errors are the state-restoration theorem's (Statement.nodeErrors), each times its draw's ratio.The BCS theorem's adversary runs the game with its own log, reads the proof's selection off it (
Statement.parse,CompStark/Fs/StarkParse.lean) and fills its snapshots from the log (Statement.bcsAdversary). On a run without digest clashes (HashBad: a hash answer equal to a digest known before it), extraction under an absorbed cap is the same from the cap's snapshot and from the whole log (extractRow_snapshotAt,CompStark/Fs/MerkleLog.lean,opensStable_parse), and an accepted opening opens the extracted rows (capBinding_of_log), so the verifier's acceptance gives the STARK's terminal on the extracted words (Setup.terminal_of_verifyWith,CompStark/Fs/StarkTerminal.lean) while a far trace makes the extraction fail (Statement.bcsEvent_of_mem_support). Digest clashes costhashBadError F capLen m q = (2 + m capLen) q² / |F|⁸(prEvent_hashBad_le),mthe offsets at which the verifier's inputs carry caps (capOffsets,twoAdicity F + 3of them) andcapLen = 2 ^ capHeight; the capacities' collisions and predictions cost ArkLib'schainError (Digest F) Q' = ⌊Q' (Q' + 1) / 2⌋ / |F|⁸.verifierQueriescounts the setup's and the verifier's oracle calls on a proof of any length (verifierCallsAt,isTotalQueryBound_verifyWith,CompStark/Fs/StarkCalls.lean), the setup's2 · |L| - 1calls none without preprocessed columns, andverifierChainQueriesthe duplex calls of the verifier's completion, at mostmaxChallenges = twoAdicity F + 4challenges (Setup.challengesLE_publicCoin) of at mostcoinCountsqueezes each, which the BCS theorem's adversary runs once and ArkLib's simulation charges twice more, henceQ' = Q + verifierQueries + 3 verifierChainQueries. The theorem does not cover Poseidon2 itself or Plonky3's sponge and duplex constructions (rate/capacity chaining, length-tagged absorption, no padding): it idealizes each role of the hash as a random oracle.Each node error times its ratio is one of the security calculator's round terms (below;
Setup.ratio_mul_nodeErrors_le,CompStark/Calculator/FiatShamir.lean), so the round error's bound (Statement.roundErrorBound) is the error per duplex call, and a path of at mostmaxChallengeschallenges sums to at mostmaxChallengestimes it: the calculator'sC.SecureRo EF p B A bitsgiveschainError Q' + hashBadError + (Q' + maxChallenges) · 2 ^ (-bits)(Statement.knowledgeSoundness_bcs_of_secureRo), whichStatement.bcsBoundevaluates with the round error's bound in place of2 ^ (-bits). The verifier's duplex calls count both inQ'and in the path sum. Per duplex call, Plonky3's Keccak example has52.11bits on2 ^ 23rows,wide 64withwideGrindParams117.37, and with SP1's parameters98.95at its longest admitted length; atQ = q = 2 ^ 64calls andK = 8, the bounds are about$2^{-53.37}$ forwideGrindStatement,$2^{-34.95}$ for SP1's parameters and$2^{-64.49}$ forwide5Statement,Q'times the per-call error dominating the capacities'$2^{-120.91}$ and the digest clashes'$2^{-115.05}$ .tests/CompStarkTests/Security.leaninstantiates the theorem at117bits (wideGrind_knowledgeSoundness_bcs), andtests/CompStarkTests/KeccakSecurity.leanfor Keccak at128and, beyond the unique-decoding radius,100bits (knowledgeSoundness_bcs_quintic,johnson_knowledgeSoundness_bcs_quintic). -
Batch-STARK prover and verifier (
CompStark/Batch/,CompStark/Plonky3/Batch.lean): Plonky3'sbatch-starkprove_batchandverify_batchat revisiona4982ec, LogUp lookups included: several AIR instances (Batch.Instance: an AIR, its trace length and its lookups, packed at keygen,Instance.ofAir) of mixed heights, with preprocessed and periodic columns, under one main commitment, one quotient commitment and one opening argument over matrices of mixed heights. The common data (Batch.Common,setupBatch, Plonky3'sCommonData) is the cap of one commitment to all preprocessed traces with every instance's keygen height and packed lookups. The transcript is Plonky3's typed one forp3-batch-stark(DomainSeparator.batchSeed: every instance's degree bits observed as extension elements, the out-of-domain grinding step present at zero difficulty too). The verifier (verifyBatchWith,CompStark/Batch/Verifier.lean) takes the AIRs and the common data, and, as Plonky3's, every instance's trace length from the proof (instancesOf): it checks that an instance with preprocessed columns has its keygen height (keyOk, Plonky3's preprocessed metadata), then every instance asuni-starkdoes at the proof's heights, against oneαand oneζ, with Plonky3's shape checks (verifyInstancesWith). LogUp lookups follow Plonky3's single-terminal gadget (CompStark/Lookup/): an AIR declares its lookups (Air.lookups,Lookup: tuples of expressions with signed multiplicities, each with its weight (Count), local or on a named bus), and keygen fixes the batch's layout in the common data: same-bus lookups packed into shared fraction columns within the quotient's degree budget (Air.packedLookups,pack_same_bus_with_degree), one tuple width per lookup and per bus and the multiplicity bound∑ᵢ wᵢ nᵢ < pat the proof's heights checked (widthsOk,heightBoundOk), and one challenge pair(α_L, β_L)for the batch, busbseparated by the baseα_L + (b + 1) β_Lᵂ(challengeLayout). After lookup grinding and the pair, the prover commits to every permutation trace, an accumulator and a fraction column per lookup over the challenge field in base coordinates (permutationTrace), and observes the terminals; each instance's LogUp constraints (logUpConstraints, expressions over the traces, the permutation columns, the challenges and the terminal,ExtExpr) follow its own in Horner's order, its quotient chunks sized by both (Instance.logQuotientChunks), the permutation traces open atζandζ ω, and the verifier checks that the terminals sum to zero. On Plonky3's batch proofs of the test AIRs ofbatch-stark/tests/simple.rs(three instances of mixed heights, mixed preprocessed columns, a periodic AIR, a single instance, and preprocessed traces of mixed heights with and without next-row reads, and an AIR that does not read the next row; with lookups,MulAirLookups,FibAirLookupsandSingleTableLocalLookupAir: local lookups, global buses across instances of mixed heights, packed same-bus lookups, lookup grinding, and two unbalanced batches; and a range table with its queries;tests/data/batch_*.json), the verifier accepts them, rejects them tampered, with other degree bits, with reordered instances, against other preprocessed traces, with a tampered, missing or spurious terminal or permutation opening, and the unbalanced ones by the terminals' sum, and the specification proverproveBatchreproduces them exactly. The fast prover reproduces them too, over KoalaBear and over BabyBear (tests/data/babybear_batch_*.json), andKoalaBear.proveBatch_fastproves it is the specification:proveBatchWith (backendFor params.logBlowup (Batch.shapes instances)) params instances traces publicValues = proveBatch (Ext4 p) params instances traces publicValuesfor one list of public values per instance, whenζ(Batch.batchOutOfDomainPoint) lies outside the base field, on batches of the prover's shapes (Batch.InputOk,CompStark/Batch/Refines.lean: at least one instance, every trace'sInputOk, every instance's lookups reading only its AIR's columns, and the extensions addressable side by side at the tallest height);Batch.proveBatchWith_refinesreduces it to the same backend laws asuni-stark, every instance's quotient byuni-stark's argument (batchQuotientChunks_refines), plus two LogUp kernels. The fast backend generates every permutation trace block by block (Fast.permutationWords), the lookups' expressions one compiled program (Prog.compileOuts) and the fractions on planes of extension coordinates: every denominatorα - ∑ⱼ tⱼ βᵏ⁻¹⁻ʲinverted through the normN(d) = g₀² - W g₁²with one zero-safe batch inversion per tuple and block (invPlanes_rel), the rows written in parallel ranges of blocks with the accumulator from the range's start and then shifted by the sums before it (assembleRanges_eq); it extends the words with CompPoly's coset LDE, andpermutationLdeWith_rel(CompStark/Stark/Fast/Permutation.lean) proves the extension and terminal are the specification's (permutationLdeOf). The LogUp constraints compile like the AIR's into a program over the extension's base coordinates, every extension node four coordinate nodes and products overX⁴ = W(Prog.compileExt), every coordinatejof constraintkaccumulated with the weightXʲ α^(K-1-k), and run on the same blocks (Fast.lookupQuotientWith,lookupQuotientWith_relinCompStark/Stark/Fast/LookupQuotient.lean). On threewide 64tables of$2^{20}$ ,$2^{19}$ and$2^{16}$ rows with blowup 2, 100 queries and 16-bit query grinding, the fast batch prover takes about 6.0 s against 6.3 s for Plonky3'sprove_batchon 16 threads, and 26.7 s against 42.0 s on one thread; on a lookup batch with the same parameters (mulAirLookups 8on$2^{18}$ rows with local lookups and eight sends on a global bus,fibonacciLookupsreceiving them on$2^{18}$ rows, andmulAirLookups 8with local lookups on$2^{16}$ rows), it takes about 2.1 s against 1.7 s on 16 threads, and 10.9 s against 12.7 s on one thread (medians of six interleaved rounds on 16 threads and three on one thread, each run started on a shared 16-core host with a one-minute load below 4, single wide rounds on 16 threads ranging 5.5–6.5 s and 5.8–11.1 s;CompStarkBench prove-batch 1 0 1 100 0 16,CompStarkBench prove-batch-lookup 1 0 1 100 0 16,bench/rust: compstark-bench batch-bench wide 1 0 1 100 0 16andbatch-bench lookup-bench 1 0 1 100 0 16,LEAN_NUM_THREADS=1,RAYON_NUM_THREADS=1). -
Completeness of the batch STARK (
CompStark/Complete/Batch.lean): over any base field with Poseidon2 instances, if every instance's trace satisfies its AIR, every bus balances and the multiplicities stay below the characteristic, the honest prover's batch proof verifies against the batch's common data (KoalaBear.batchCompletenessinstantiates it):theorem Batch.completeness (config : Batch.ValidConfig params instances) (luck : Batch.NoBadLuck EF params instances traces publicValues) : Batch.Satisfied instances traces publicValues → verifyBatch params (instances.map (·.air)) (setupBatch params instances) (proveBatch EF params instances traces publicValues) publicValues = .ok ()
Batch.Satisfiedgives one trace and one list of public values per instance, every trace satisfying its AIR (Air.Satisfied) with as many public values as the AIR declares, every bus balanced (Batch.Balanced: for every bus and tuple of values, the multiplicities of the batch's messages,Batch.messages, sum to zero in the field) and the verifier's multiplicity bound.Batch.ValidConfigasks for at least one instance and aValidConfigfor every instance, every instance's quotient, LogUp constraints included, within the blowup and its constraints within the chunks, row-local lookups reading the AIR's columns (Lookup.Fits) and one tuple width per lookup and per bus; as the instances share FRI's parameters, FRI's final polynomial is then no longer than the shortest trace; and no instance's extension lies above another's Merkle cap (capFits), since the verifier rejects matrices injected above the cap.Batch.NoBadLuckisNoBadLuckfor the batch's run (batchOutOfDomainPoint,proveBatchTranscript), plus no pole: under the run's lookup challenges no tuple of non-zero multiplicity has a zero denominator (NoPoleAt, probability1/|EF|per bus and tuple). The proof (CompStark/Complete/Batch.lean) replays the transcript and reads every instance's opened values asuni-stark's honest openings, so each instance's constraint check passes as incompleteness; the opening argument over matrices of mixed heights is complete for any honest rounds (verifyPcs_openPcs,CompStark/Complete/Pcs.lean): the Merkle trees with injected matrices open honestly (verifyMulti_prune_mixed,CompStark/Merkle/Mixed.lean), the batched quotients at a query are recomputed with every matrix's row at its own height, and FRI rolls each shorter input in when its folds reach that height (verifyFri_proveFri,Sched,foldSchedule_sched). For lookups (CompStark/Complete/LogUp.lean,CompStark/Complete/LogUpQuotient.lean), the honest permutation trace satisfies the LogUp constraints on every row (logUpConstraints_honest: a fraction pinU f - Vvanishes onf = ∑ m / dwhen no tuple of non-zero multiplicity hasd = 0), its column polynomials over the spec extension make every LogUp constraintZ_Htimes a quotient (extPoly_eq), the prover's quotient values are the constraints' folded quotient timesα ^ Lplus the LogUp constraints' (toSpec_batchQuotientValues), so every instance's check passes (instanceCheck_honest), and the terminals sum to the batch's messages weighted by their denominators, zero when every bus balances (terminals_sum_eq_zero).On the interaction layer (
CompStark/Iop/BatchCompleteness.lean), a batch prover's strategy is accepted whenever the checks in the clear pass and every domain's batched DEEP word is a codeword on that domaing ⟨ω_m⟩(Iop.Batch.Prover.strategy_accepts): a codeword on a coset is one on every coset of the same subgroup, so FRI injects each shorter word in its code (ArkLib'sinputsInCode_plonky3). The honest prover of a batch's traces (Iop.Batch.Setup.honestProver: every instance's trace, permutation trace and quotient chunks extended onto its domain, its terminal, and their polynomials' values atζandζ ω_n, over the specification's layout of the lookup challenges) meets both conditions: for aBatch.ValidConfigand traces satisfying the batch (Iop.Batch.Setup.Satisfied), it is accepted on every execution whoseζavoids the base field and under whose lookup challenges no denominator vanishes (Iop.Batch.Setup.completeness,Setup.NoPole). The terminals sum to zero byterminals_sum_eq_zero, every instance's check atζpasses byinstanceCheck_honest, and every word opened on a domain is a polynomial of degree below its trace length claimed at its value (OpensLow), so the domain's batched DEEP word is a codeword (deepQuotient_mem_code). The multiplicity bound belongs to the lengths the verifier admits (Statement.Admits), not to its checks, so the theorem does not assume it. -
Knowledge soundness of the batch STARK (
CompStark/Iop/BatchSoundness.lean), as an oracle protocol on ArkLib's interaction layer (CompStark/Iop/Batch.lean, onCompStark/Iop/BatchSetup.lean):k ≥ 1AIRs, instanceion2 ^ logN irows (Batch.Setup), each a STARK of its own (Setup.instanceSetup) on its own domain, under oneα, oneζand one batching challenge. The prover sends the lengths, every instance's trace as one oracle, the permutation traces as one oracle and the terminals of the instances with lookups (Setup.Terminals, as Plonky3's proof carries them) after the lookup challenges, the quotients afterαand the claimed values afterζ. As Plonky3's commitment scheme reduces the openings by height, the DEEP quotients of the instances of each domain size are batched by the powers of the batching challenge, starting over at each size, in Plonky3's order (the main traces, the quotients, the preprocessed traces;groupDeepWord), and ArkLib's FRI runs on the tallest batched word and injects each shorter one when its folds reach that size (Setup.friHeights); the verifier reads a batched word at a point through the values of the instances of that size (Setup.readGroupDeep). The extractor outputs one trace per instance, each list-decoded from its committed trace (Batch.closeTraces), andBatch.Statement.farAcceptanceis the probability that the verifier accepts while no close tuple satisfies a relationR:theorem batchKnowledgeSoundness (C : Batch.Statement F) (publicValues : Fin C.k → List F) {OutP : (C.protocol EF).tree.ExecutionPath → Type} (prover : Interaction.Oracle.Prover.Strategy unifSpec (C.protocol EF).tree (C.protocol EF).roles OutP) (δ δ' : (Fin C.k → ℕ) → NNReal) (hδ' : ∀ n, C.Admits n → (δ' n : ℝ) < 1 - C.rate) : C.farAcceptance EF publicValues δ' (fun n traces ↦ (C.setup EF n).Satisfied traces publicValues) prover ≤ C.knowledgeError EF δ δ'
As for the STARK, the batch statement (
Batch.Statement: FRI parameters, the AIRs and their keygen heights, below) has no trace lengths: the prover claims a vector the verifier admits, and the bound is the largest knowledge error over the admitted vectors (Batch.Statement.knowledgeError). The certificate (CompStark/Iop/BatchKnowledge.lean) appends the doomed-transcript states ofCompStark/Sound/BatchRbr.lean(LookupBad, then the failing instance'sAlphaBad,ZetaBadandBatchBadwith that instance's errors, so that each path's errors sum to its instance error) to ArkLib's FRI certificate on the batched words, gated by the terminal sum and the constraint checks.Setup.Satisfiedasks every trace to satisfy its AIR and every bus to balance in the field (Setup.Balanced, over the batch's messagesSetup.messages); the extractor outputs the main traces only, the permutation traces are auxiliary. The verifier first draws the lookup challenges(α_L, β_L)and accepts only if the instances' terminals sum to zero. If every close tuple satisfying every AIR leaves a bus unbalanced, the pair is bad with probability at mostlookupError: for each such tuple the messages' fractions, with every denominatorα_L + (b + 1) β_Lᵂ - ∑ⱼ tⱼ β_Lʲcleared, give a nonzero polynomial of degree at mostT + (T - 1) Win(α_L, β_L)over theTinteractions of width at mostW, and a pole of any tuple, of zero multiplicity or not, counts as bad too (Schwartz–Zippel,CompStark/Sound/LogUp.lean,CompStark/Sound/BatchLogUp.lean); Plonky3'sp3-securityestimatesN (W + 2) / |EF|. Otherwise the terminals of close permutation traces satisfying their LogUp constraints telescope to the messages' nonzero fraction sum (CompStark/Sound/Telescope.lean), so the verifier rejects, or some instance has no close trace and permutation trace satisfying all its constraints, and the proof runs the single STARK's argument on it among the instances of its domain's size: the DEEP quotients of that size near on a common set make that instance's near too. So the knowledge error (CompStark/Iop/BatchError.lean) islookupErrorplus the worst instance'sfoldingError + outOfDomainError, with its own list sizes andidentityDegree, and batching of its size's opened words (batchingErrorOf, decoding losing two points of its domain), plus FRI's error with the shorter words injected (friErrorWith). FRI with inputs injected mid-folding is ArkLib's interaction-layer FRI certificate: a fold that injects a word is bad when the powers of its challenge over its2 ^ kcoefficient words and the injected word make them agree with the code although they do not on a common set (the lossy correlated-agreement event of2 ^ k + 1words), and FRI rejects when any input is far; CompStark's rounds with injected inputs (CompStark/Sound/FriRounds.lean,CompStark/Sound/FriInject.lean) carry the same error (friErrorWith_eq). This covers every schedule Plonky3's verifier accepts: an instance at FRI's final length is injected by the last fold, at the final height, and with every instance there FRI does not fold and the queries check the batched words against the final polynomial (FriRounds.Rounds.Fits,FriRounds.Rounds.schedule). The STARK's FRI is this FRI without injected inputs. A lookup's tuples are queries, of positive weight, or provided table entries, of weight0and any multiplicity (Plonky3'sCount::boundedandCount::provided). Balance in the base fieldℤ/pmakes the queries included in what is provided: with the verifier's multiplicity bound∑ᵢ wᵢ 2 ^ logNᵢ < p(Setup.HeightBounded), one of its checks on every length vector it admits (Statement.Admits), and AIRs keeping every row's query multiplicities within their weights (Air.CountsBounded, Plonky3'scount_weight, an obligation of the AIR the verifier does not check), for every bus and tuple of values some row of some instance provides it with a nonzero multiplicity (Setup.Provides, exposing the instance and row so the provider's AIR applies there), or its queries' multiplicities, read as the integers of magnitude belowp / 2they stand for (intValue), sum to zero overℤ(Setup.Included,Sound.Batch.included_of_balanced,CompStark/Sound/Inclusion.lean): without a provider, balance makes the queries' sum vanish modulop, and the height bound keeps it belowpin magnitude. So the knowledge error also bounds acceptance while no close tuple satisfies every AIR with lookup inclusion (Setup.SatisfiedWithInclusion):theorem batchKnowledgeSoundness_inclusion (C : Batch.Statement F) (publicValues : Fin C.k → List F) {OutP : (C.protocol EF).tree.ExecutionPath → Type} (prover : Interaction.Oracle.Prover.Strategy unifSpec (C.protocol EF).tree (C.protocol EF).roles OutP) (δ δ' : (Fin C.k → ℕ) → NNReal) (hδ' : ∀ n, C.Admits n → (δ' n : ℝ) < 1 - C.rate) (hcounts : ∀ n, C.Admits n → ∀ i, (C.air i).CountsBounded (n i)) : C.farAcceptance EF publicValues δ' (fun n traces ↦ (C.setup EF n).SatisfiedWithInclusion traces publicValues) prover ≤ C.knowledgeError EF δ δ'
A decidable check discharges
Air.CountsBoundedfor any AIR (Air.countsChecked, sound bycountsBounded_of_countsChecked,CompStark/Sound/Counts.lean): every lookup's query multiplicities have syntactic bounds summing to at most its count weight, a bound being1for a row selector or a boolean column,kfor a constant±k, and sums and products of bounds; a column is boolean when the AIR assertsx (x - 1)for it (assert_bool) on every row, or on transitions and on the last row, since a transition constraint leaves the last row free. ThewideBus 64batches pass it bydecide(wideBus_countsChecked,CompStark/Iop/LookupInclusion.lean). A range check shows inclusion at work: range tables (rangeTable, a preprocessed row index provided on the busrangewith an unconstrained multiplicity column) and range queries (rangeQuery, a value queried with a boolean multiplicitys) pass the check, and lookup inclusion puts every query withs = 1in some table's range (rangeChecked_of_satisfiedWithInclusion,CompStark/Sound/Range.lean), so acceptance is bounded while no close tuple satisfies the AIRs with every real query in range (rangeKnowledgeSoundness, and on Plonky3's batch verifierBatch.Statement.rangeKnowledgeSoundness_bcs). Plonky3 proves such a batch (compstark-bench batch range,tests/data/batch_range_*.json), and the verifier and both provers agree with it.Concretely, three
wideBus 64instances (wide 64exchanging two tuples of width 4 per row on one global bus) of keygen heights$2^{20}$ ,$2^{19}$ and$2^{16}$ over the quintic extension, with blowup 4 and 190 queries, have knowledge error at most$2^{-127}$ at every admitted vector of lengths (wideBatch_knowledgeError_le, about$2^{-127.11}$ , the largest per-vector bound, with every instance on$2^{22}$ rows and the lookup challenges about$2^{-128.04}$ ; at the keygen heights alone the batch reached$2^{-128.34}$ ). The random-oracle model of the batch follows. -
Knowledge soundness of Plonky3's batch verifier as it runs (
CompStark/Fs/BatchBcs.lean), LogUp lookups included:Batch.Statement.knowledgeSoundness_bcs, the same bound as the STARK's forverifyBatchWithitself on the same three oracles (CompStark/Fs/BatchRandomOracle.lean), against an adversary that outputs public values and a batch proof. The statement is a batchStatement(CompStark/Iop/BatchSetup.lean): FRI parameters, the AIRs and their keygen heights, at which their lookups are packed, with the lookups' fit to their AIRs' columns andBusesValid, properties of the keygen data; it has no trace lengths. The verifier takes every instance's length from the proof's degree bits, as Plonky3's does, and the theorem covers every vector of lengths it admits (Statement.Admits: per instanceSoundConfig, the quotient domain with the lookups' LogUp constraints within the two-adic subgroups, the keygen height for an instance with preprocessed columns, and the multiplicity bound, all checked by an accepting verifier,Statement.admits_of_ok, FRI's final height included: every query folds every height down to it), with the batch at each (Statement.setupAt). The game (Statement.batchGame) computes the common data, the commitment to the instances' preprocessed traces at their keygen heights, on the oracles (Batch.commonWith), and runsverifyBatchWithon the adversary's proof against it; the extractor reads every instance's trace off the log under the main commitment, at the instance's offset among the matrices of its height (Merkle.matColumn), at the proof's lengths (Statement.lengthsOf). The lengths are told apart by the degree bits the verifier observes right after the batch's domain separator, extension elements whose first coordinate is the length, and an admitted length is at most the two-adicity, belowp, so the batch needs no assumption on the field. Plonky3's verifier clamps every cap to its tree, and an accepted opening has every committed matrix at least as tall as its cap (cap_le_of_verifyMulti_mixed); the commitments bind the trees of mixed heights the prover committed to (capBindingM_of_log,Fs/MixedBinding.lean), and the verifier's checks hold for the extracted words (Fs/BatchExtract.lean, FRI with the shorter heights injected inFs/FriInjected.lean), which give the batch's terminal (Setup.terminal_of_verifyWith,CompStark/Fs/BatchTerminal.lean).verifierQueriescounts the setup's calls (at most four per leaf of the preprocessed traces' tree,isTotalQueryBound_commonWith) and the verifier's on a proof of any lengths, the largest over the vectors of lengths up to the two-adicity (verifierCallsOf: a leaf hash, a compression per level and a hash and a compression per injected height for every opening of a commitment);verifierCalls_lebounds it in closed form, checked bydecide +kernel. The batch has at mosttwoAdicity F + 5challenges, the lookup challenges included (Batch.Setup.challengesLE_publicCoin). The calculator's certificate (Batch.Statement.SecureRo, implying the batch'sSecureRoat every admitted vector,secureRo_setupAt) bounds every node (Batch.Setup.ratio_mul_nodeErrors_le; without lookups, LogUp's error is0), soBatch.Statement.knowledgeSoundness_bcs_of_secureRogiveschainError Q' + hashBadError + (Q' + maxChallenges) · 2 ^ (-bits). Concretely, the threewideBus 64instances above, of keygen heights$2^{20}$ ,$2^{19}$ and$2^{16}$ , with zkDTVM's FRI over the quintic extension (160 queries, 20 bits of query grinding) have every round term at most$2^{-128}$ at every admitted vector (wideBatch5Statement_secureRo,verifierQueries ≤ 134253): the worst vector puts every instance on$2^{22}$ rows, the lookup challenges at$2^{-128.04}$ and batching all 474 opened words on one domain size at$2^{-128.06}$ . Over the quartic extension with 144 queries and grinding at every site, aswideGrindSetup, and 18 bits before the lookup challenges, the bound per duplex call is$2^{-115}$ (wideBatchGrindStatement_secureRo,verifierQueries ≤ 120816,wideBatchGrind_knowledgeSoundness_bcsintests/CompStarkTests/Security.lean, about$2^{-51.05}$ at$2^{64}$ calls), the lookup challenges at$2^{-115.05}$ on$2^{22}$ rows per instance ($2^{-117.99}$ at the keygen heights alone). A fold by$2^k$ that injects a word batches$2^k$ words besides the first instead of$2^k - 1$ . With lookup inclusion,Batch.Statement.knowledgeSoundness_bcs_inclusion(CompStark/Iop/LookupInclusion.lean) gives the same bound withSatisfiedWithInclusionin place ofSatisfied, for AIRs that bound their query multiplicities at every admitted vector (hcounts), andBatch.Statement.rangeKnowledgeSoundness_bcsfor range checks; the multiplicity bound needs no hypothesis here, since an accepting verifier checked it. -
Security calculator (
CompStark/Calculator/Bounds.lean): every error term above depends on the AIR only through a few numbers, itsShape(its constraint count andidentityDegree, its opened words, whether it has lookups, its lookups' interactionsTand widest tupleW), and on the heights and FRI's parameters. The calculator computes, for a setup, a base field ofpelements, an MCA boundBand anAnalysis(FRI's radiusδ, the extractor'sδ' ≥ δbelow the Johnson radius1 - √rate, and a proximity loss, all rationals), a rational bound on each term of the knowledge error and of the round error, at any FRI arity: the bounds ofCompStark/Calculator/Numbers.lean, with the list sizes atδ'at most1below the unique-decoding radius and the Johnson bound⌊(1 - δ' - rate) / ((1 - δ')² - rate)⌋above it (listSize_le_listBound), batching at the better ofB's count and the curve bound, and FRI's folds read off Plonky3's schedule as a plan of folds (FriRounds.Rounds.plan: each fold's arity, the domain it folds onto, and whether it injects a word), each at the better ofB's count and the lossy bound. The MCA bound (McaBound,CompStark/Calculator/McaBound.lean) is computable data: where it admits a Reed–Solomon code of degree belowkonNpoints at radiusδ, a count of the exceptional seeds for power curves of degreeM;B.Holdssays that the counts bound the mutual correlated-agreement error of the powers over every finite field and domain.McaBound.uniqueDecodingcountsM Nwithin the unique-decoding radius,2 ⌊δ N⌋ ≤ N - k(uniqueDecoding_holds),McaBound.johnsoncountsMtimes the Dao–Kominers–Thaler exception count for lines ([DKT26] Theorem 5.12) up to the Johnson radius1 - √ρ₋,ρ₋ = (k - 1) / N, exactly as ArkLib'sjohnsonExceptionCount(johnsonLineCount:(2μ - 1) h + θ (h + μ + 4 D μ h) + (n - D - 1) μwithμ = ⌈t / √ρ₋⌉ - 1,h = ⌈t² / (3 ρ₋)⌉ - 1,θ = (n - D) / (A - D)at the agreement thresholdA = ⌈(1 - δ) N⌉and the multiplicitym = t - 1/2 = max(⌈r / (2 (1 - δ - r))⌉, 3)for a dyadicr ≥ √ρ₋), inNatandℚarithmetic with⌈√·⌉throughNat.sqrt, andMcaBound.uniqueOrJohnsoncombines the two.S.Secure p B A bits(andS.SecureRo p B A bitsper oracle answer, and the batch's) says that the analysis fits the setup,Badmitting its batching and its folds, and the bound is at most2 ^ (-bits); it is decidable inNatandℚarithmetic, sodecide +kernelchecks it with the kernel evaluating the AIR's constraints, widths and lookups (awide 4096table, 4096 constraints, takes about seven seconds, withmaxRecDepthraised for its list of constraints). The analysis need not be picked by hand:S.bestAnalysis p B(andbestAnalysisRo, and the batch's) searches FRI's radius, the extractor's and the loss by coordinate descent over a grid for the smallest bound; it is computation only, so soundness rests on the check of what it finds, and every analysis ofCompStark/Calculator/Security.leanis the search's. For a large AIR,secure_of_shape(andsecureRo_of_shape) takes the shape as a fact proved once, andshape_eq_of_profileFitsproves it from a one-pass kernel check of the constraints' degree profiles (Air.ProfileFits), so that each certificate evaluates only rationals. One theorem per headline turns a certificate into the bound (CompStark/Calculator/Security.lean), given that the MCA bound holds:theorem knowledgeSoundness_of_secure (C : Statement F) (hp : Nat.card F = p) {B : McaBound} (hB : B.Holds) (A : Analysis) {bits : ℕ} (h : C.Secure EF p B A bits) (δ' : NNReal) (hδ' : (δ' : ℝ) = A.δ') (publicValues : List F) {OutP : (C.protocol EF).tree.ExecutionPath → Type} (prover : Interaction.Oracle.Prover.Strategy unifSpec (C.protocol EF).tree (C.protocol EF).roles OutP) : C.farAcceptance EF publicValues (fun _ ↦ δ') prover ≤ (2 ^ bits)⁻¹
and likewise
batchKnowledgeSoundness_of_secure(withStatement.knowledgeError_le_of_secureand the batch statement's for the errors themselves), and on Plonky3's verifiersStatement.knowledgeSoundness_bcs_of_secureRoandBatch.Statement.knowledgeSoundness_bcs_of_secureRo(CompStark/Calculator/FiatShamir.lean, above). A statement's certificate (Statement.Secure,Statement.SecureRo) checks every admitted length below the two-adicity; a batch statement's (Batch.Statement.Secure,Batch.Statement.SecureRo) bounds every admitted vector's per-vector bound, the vectors being too many to enumerate (secure_setupAt,secureRo_setupAt). Up to an input, FRI's plan is the plan without it down to at mostmaxLogArityabove the input's height (FriRounds.Rounds.plan_cons_split), so each input adds at most a per-height amount to the folds' bound, the folds and the largest fold, exactly that at arity 2. The round error's bound is the largest term, so its largest over the vectors is each term's: LogUp at every instance's longest admitted length, the instance terms at every instance's shape at every admitted length, batching on each domain size with every instance admitting it there, FRI's folds from every top length and at every input, and the queries at the most folds. The knowledge error sums its terms: at each top length every instance takes the length of the largest share, its LogUp interactions and, below the top, what its input adds to FRI's folds and queries, with the instance terms at their worst; that bounds every vector of that top length and is the per-vector bound of the vector it picks when the instance terms are worst there and its inputs sit at distinct heights, as for thewideBusbatches, whose worst vector has every instance at the longest length. Every concrete bound above is such a check with the unique-decoding bound (fibonacci_secure,wide_secure, …,wideBatchGrindStatement_secureRo).What is proven and what is not: the list sizes up to the Johnson radius are proven (
CompStark/Sound/Johnson.lean), so the extractor'sδ'may go beyond the unique-decoding radius, with the folding and out-of-domain terms multiplied by the list sizes. FRI's soundness holds at every radiusδ(ArkLib'sFri.Interaction.acceptance_le, Garreta–Mohnblatt–Wagner's round-by-round argument: each fold's MCA error, a curve of degree2^k - 1, or2^kwhen it injects an input, and(1 - δ)^tfor the queries), and so does batching; the radius is limited by the MCA bound the calculator uses. Within the unique-decoding radius it is proven (uniqueDecoding_holds, from ArkLib's unique-decoding curve bound, with the lossy and curve bounds ofCurveCA.leanandLossyMca.lean), so each query is worth at most$\log_2 (4/3) \approx 0.415$ bits at blowup 2 and$\log_2 (8/5) \approx 0.678$ at blowup 4. Up to the Johnson radius it is proven too (johnson_holds,uniqueOrJohnson_holds): ArkLib's Johnson-regime mutual correlated agreement for power curves (ReedSolomon.exists_johnson_curve_exactPowerAgreement_line_mul_of_fintype, Dao–Kominers–Thaler's line count with the curve degree as a factor, in any characteristic and on any domain, branchfri-interactionof the ArkLib fork, on ArkLib main), which brings a query up to$\log_2 (1/(1 - \delta))$ bits forδup to1 - √rate, about 0.5 bits at blowup 2 and 1 at blowup 4.Plonky3's Keccak example (
tests/CompStarkTests/KeccakSecurity.lean):KeccakAir, 3182 constraints of degree 3 over 2633 columns, every column opened at the next row, as a statement whose proofs may claim any trace length the verifier admits,$2^0$ to$2^{23}$ rows at blowup 2 and to$2^{22}$ at blowup 4, every certificate covering all of them at one analysis (secureRo_of_lengths,secure_of_lengths). With the example's FRI (blowup 2, arity 8, 87 queries, 16 bits of query grinding and 10 of batching grinding, caps of height 3) over KoalaBear's quartic extension it has 52 proven bits per duplex call and 36 interactively (example_secureRo,example_secure). Over the quartic extension the out-of-domain term caps the bits near 98.4 on$2^{23}$ rows and 99.0 on$2^{22}$ , so 203 and 244 queries at blowup 2 and 124 and 149 at blowup 4 give 98 bits per duplex call and interactively; over the quintic extension 270 and 310 queries at blowup 2 give 128 and 127 bits, 166 and 190 at blowup 4 give 128 and 128. Up to the Johnson radius (McaBound.uniqueOrJohnson), over the quintic extension with Plonky3's grinding, 169 queries give 100 bits per duplex call and 205 give 98 interactively at blowup 2, 85 and 102 give 100 and 99 at blowup 4 (johnson_knowledgeSoundness_bcs_quinticon Plonky3's verifier); the batching curve's degree 5273 multiplies the Johnson count, which grows with the trace, so with 0, 30 and 12 bits of grinding beforeζ, the batching challenge and each fold, 230 and 115 queries give 125 and 126 bits per duplex call over the quintic extension, and with 1, 30 and 16 bits over the quartic one 172 and 86 queries give 96 and 97.
The Rust side lives in bench/rust, built for the native CPU (bench/rust/.cargo/config.toml), without which Plonky3's fields lose their vectorized arithmetic; cargo run --release prints the Poseidon2 known answers used by the tests, compstark-bench challenger the challenger's, compstark-bench stark AIR LOG_N LOG_BLOWUP LOG_FINAL MAX_LOG_ARITY QUERIES COMMIT_POW QUERY_POW [BATCH_POW OOD_POW] a proof of the fib, cube, square (no next row read), wide, prep or prep0 (preprocessed columns, read at the next row or not), periodic, periodic-wide, periodic-tiny or pcube (periodic columns) AIR as JSON, and compstark-bench seeds Plonky3's interaction patterns and domain separators of the tests' configurations; compstark-bench babybear and compstark-bench stark-babybear AIR … print the same over BabyBear, and compstark-bench batch CASE LOG_BLOWUP LOG_FINAL MAX_LOG_ARITY QUERIES COMMIT_POW QUERY_POW [BATCH_POW OOD_POW [LOOKUP_POW]] a batch-stark proof of the batch three, mixed, periodic, single, prepmul, square, or with lookups local1, local, global, both, lookups, single-local, heights, failed-global or failed-local as JSON (batch-babybear CASE … over BabyBear; batch-bench wide … times prove_batch on the benchmark's three wide tables, batch-bench lookup-bench … on the lookup benchmark's batch). Run the challengers and proofs with RAYON_NUM_THREADS=1, so that grinding returns the smallest witness. lake test builds the #guard library CompStarkTests and runs the compiled test driver CompStarkNativeTests: the known answers on the packed kernels, and Plonky3's proofs verified, rejected when tampered, and reproduced by the specification prover and the fast prover (KoalaBear and BabyBear). Lake does not track tests/data: after replacing a proof there, rerun lake exe CompStarkNativeTests (or lake env lean tests/CompStarkTests/BabyBear.lean).
For the Keccak fixture, compstark-bench fingerprint AIR LOG_N prints an AIR's fingerprint report (tests/data/fingerprint), compstark-bench keccak-trace INPUTS the trace known answers, compstark-bench keccak-example-constants the module of the example's Poseidon2 constants, RAYON_NUM_THREADS=1 compstark-bench keccak-example L DIR the example's proof and inputs, and compstark-bench lde LOG_N WIDTH times Plonky3's trace extension.
- Custom externs for the Poseidon2 kernels. Every
ByteArraystore in Lean carries an exclusivity check, so a loop over the eight lanes stays scalar, and clang's SLP vectorizer only handles one round at a time. Written as one loop over the lanes, the whole width-24 permutation keeps its state in registers and vectorizes cleanly: 211 ns in a C model of the Lean kernels, against 289 ns for the split Lean kernels and 179 ns for Plonky3. A check-free store primitive would unlock that shape, at the price of trusting an extern.