Inherits all rules from root AGENTS.md. Only overrides and additions below.
This is the ZK prover for Linea, written in Go and built on top of gnark.
It implements the Vortex polynomial commitment scheme and the Consensys zkEVM.
The proving pipeline works as follows. The go-corset library supplies the
circuit description and witness assignments for the zkEVM arithmetization.
The prover extends this circuit with constraints for precompiles and public
inputs, then compiles it using the custom proving framework in
./protocol/wizard to produce an inner proof system. Inner proofs are then
wrapped via proof composition into a Plonk circuit — the execution proof —
defined in ./circuits. A separate data-availability proof, also in
./circuits, proves in Plonk that the execution data across a group of
execution proofs is consistent with the data published on L1. Batches of
execution and data-availability proofs are then aggregated by the aggregation
circuit, which uses the BW6-761 / BLS12-377 curve pair to enable efficient
recursive verification. A pi-interconnection subproof, embedded in the
aggregation proof, checks consistency of the sub-proofs' public inputs.
Finally, an emulation proof converts the aggregation proof into a proof over
the BN254 scalar field, which is what the L1 verifier contract checks.
# Build
cd prover && make build
# Run tests (excludes corset, includes fuzz-light)
cd prover && go test ./... -tags nocorset,fuzzlight -timeout 30m
# Static checks
cd prover && gofmt -l .
cd prover && golangci-lint run
# Build Docker image
docker build -f prover/Dockerfile -t consensys/linea-prover:local .- Go version: 1.25.7 (see
go.mod) - Formatting:
gofmt(standard Go formatting, tabs) - Linting:
golangci-lint - Indentation: tabs (per
.editorconfigGo section) - Build tags:
nocorsetandfuzzlightfor standard test runs - Cross-compilation: Targets Linux AMD64 (musl) and Darwin ARM64
| Library | Purpose |
|---|---|
github.com/consensys/gnark |
ZK circuit compiler |
github.com/consensys/gnark-crypto |
Cryptographic primitives |
github.com/consensys/go-corset |
Constraint system |
github.com/crate-crypto/go-kzg-4844 |
KZG polynomial commitments |
github.com/sirupsen/logrus |
Logging |
github.com/spf13/cobra |
CLI framework |
github.com/prometheus/client_golang |
Metrics |
backend/ Backend logic
circuits/ Gnarks circuits (for the execution-proof, data-availability proof and aggregation proof)
zkevm/ ZK-EVM implementation
symbolic/ Symbolic calculus library. Used ubiquitously in the protocol and zkevm packages.
crypto/ Low-level cryptographic utilities
maths Low-level maths functions, finite field arithmetic.
protocol/ Main framework for the description of the ZK-EVM and the implementation of the proof system.
public-input/ Circuit's public inputs specifications and hashing
config/ Configuration
- Never commit proving keys or large binary assets (checked via
.gitignore) - Cryptographic code changes require careful review — affects proof validity
- Test timeouts are 30 minutes due to proof generation complexity
To test the package:
- CI workflow:
.github/workflows/prover-testing.yml - Static checks run first (gofmt, golangci-lint)
- Compressor tests run separately from main test suite
- 30-minute timeout for test suite
- Naming:
TestFoofor the happy path,TestFoo_Scenariofor sub-cases (e.g.TestVerify_InvalidSignature). - Table-driven: Use
t.Runwith a case slice when there are 3 or more input variants. - One concern per test: Each test should verify a single behavior. If a test needs multiple independent scenarios, split it into sub-tests.
- Failure messages: Include a description in assertions:
require.Equal(t, want, got, "after padding, slice length should be n"). A failing test must say what it expected, not just that it failed. - Test independence: Tests must not share mutable state. Each test must
be runnable in isolation with
go test -run TestFoo. - Package naming: Use
package foo_test(black-box) by default. Switch topackage foo(white-box) only when the behavior to test is unreachable through the public API. - Negative tests: Every non-trivial function must have at least one failure case that asserts the expected error.
- Circuit soundness tests are mandatory. For every circuit, write at least one test that provides an invalid witness and asserts proof generation fails. A circuit tested only with valid witnesses is untested against its core security property.
- Regression tests: When a bug is fixed, add a test named
TestFoo_Regression_ShortDescriptionthat would have caught it. - Benchmarks: Add
BenchmarkFoofor any function on a hot path or where algorithmic choice matters. Run withgo test -bench=. -benchmem. - Assertions: Use
require(stops the test immediately) for setup and preconditions. Useassertfor independent checks within the same test.
- For mathematical functions, validate properties over random inputs rather
than hand-crafted cases only. Use a deterministic seed (e.g.
chacha8randwith a fixed seed) so failures are reproducible. - Add
FuzzFootests for serialization and deserialization entry points — these are common sources of bugs at input boundaries. Use thefuzzlightbuild tag for corpus-driven fuzz tests that run in CI. - Every known edge case found in production should have a corresponding fuzz corpus entry.
- Tests that run the full prover or generate proofs are slow. Guard them so under build-tags like fuzzlight so that they do not block fast feedback:
- Always run
gofmtandgolangci-lintbefore proposing Go changes - Do not touch circuit code without explicit user directive (see Circuit Rules below)
- Binary assets in
prover-assets/are version-controlled selectively — check.gitignoreexceptions
-
Fewer packages is better. Before creating a new package, propose the structure to the user — do not define new packages without consent.
-
./utilsis for domain-agnostic functionality — an extension of the standard library or key dependencies. -
It is not a dumping ground for orphan functions.
May go in: assertion helpers (
utils.Require), parallel execution primitives, generic slice operations (RightPadWith,SortedKeysOf), safe numeric conversions (DivCeil,NextMultipleOf), iterator combinators (ChainIterators) — anything explainable without domain knowledge.Cannot go in:
- Functions with fewer than 3 call sites in the codebase.
- Functions already available in the standard library or key dependencies, or that are trivial one-liners.
- Functions that require domain knowledge to explain — if understanding it requires knowing what a ZK-EVM, a Vortex polynomial, or a Compiled-IOP is, it does not belong here.
| Source | Policy |
|---|---|
| Go standard library | Always allowed |
golang.org/x packages |
Allowed; notify the user |
Packages already in go.mod |
Allowed; notify the user |
| New external dependencies | Requires user consent |
New external dependencies are discouraged but not prohibited. When proposing one, explain why it is preferable to a local implementation.
- Prefer extending existing types and interfaces over creating new ones.
- Only introduce a new struct or interface if it reduces maintenance cost or complexity.
- Prefer names that make comments unnecessary. If a name requires a comment to be understood, the name is wrong.
| Unit | Target | Hard limit |
|---|---|---|
| Line length | ≤ 80 chars | 120 chars |
| File length | — | 1000 lines |
| Function length | ≤ 50 lines | — |
Regarding function length, prefer smaller functions. If you can factor out a common functionality from several overlapping functions, do it but the behavior of the function should be easy to explain.
- Return errors when failures are expected or recoverable at the call site.
- Panic on invariant violations — use
panicorutils.Requirewhen a condition should never be false given correct usage. - Use hard assertions liberally in internal APIs. Particularly,
- For function or structure invariants.
- For function pre-conditions
- Pay attention to nil-ness checks
- Length consistency checks
The documentation philosophy of the project follows a 4-tier system:
-
docs.go (godoc): Package introduction - what you'll find here.
- Entry point for users browsing docs.
- May contain examples, especially if they are user-facing.
-
godoc comments:
- What the function does,
- Inputs/expectations, preconditions, invariants.
- Avoid obvious statement (like "Sum adds a and b") if possible
-
Inline comments:
- Giving context ("this is a non-trivial bug fix")
- Instructions to maintainers ("don't reorder these")
- Design decisions ("implemented this way not that way because...") Avoid using them for
- Stating TODOS, issues are the right tools for TODOs - not comments.
- Paraphrasing the code
-
Readme:
- Design rationale
- Algorithms
- Workflows
- Security analysis.
- NOT about code/API - about context
- Are located in the same package as the code implementing them
- Should be kept reasonably concise
- If more content is needed, split the Readmes in smaller file, each focusing on their own topic.
Generally speaking, all of these should embrace conciseness and try to maximize signal-to-noise ratio. Favor good naming and simpler design when possible.
Circuit definitions encode security-critical constraints. They are off-limits without an explicit directive from the user.
- Do not modify, or generate
Define()implementations or constraint expressions without explicit user instruction. This applies to any function that takes in a gnark'sfrontend.APIin their parameter. - Do not add, remove, or reorder fields in circuit structs without explicit user instruction — this changes the verification key and invalidates existing proofs.
- Explaining circuit code is always fine.
- Be proactive in raising concerns about soundness of a circuits.
- Do not insert, modify or remove items in a Compiled-IOP without explicit user instruction.
- Do not modify any method with the signature
Check(ifaces.Runtime) error.- But be proactive in raising concern regarding potentially missing checks.
- Do not remove any statement registering a constraints or a query in the wizard protocol.
Every circuit struct must carry a comment documenting:
- What it proves. One or two sentences in plain language, written for an auditor who is unfamiliar with the codebase.
- Public vs private. Each field must be annotated
// publicor// private. Public inputs are visible to the verifier; private fields are known only to the prover. - Constraint budget. Approximate constraint count and the dominant cost (e.g. "~500k constraints, dominated by Keccak permutations").
- Profile before optimizing:
go test -cpuprofile cpu.profthengo tool pproffor CPU,-memprofile mem.proffor heap. - New algorithmic choices on hot paths require a benchmark comparison
(
go test -bench=. -benchmem) before committing. - Use
go tool traceto diagnose goroutine scheduling and GC pauses.
- Pre-allocate slices when the size is known:
make([]T, 0, n). - Pass buffers as parameters rather than allocating and returning them to lets callers reuse allocations across calls.
- Avoid
interface{}/anyboxing in hot loops — boxing forces a heap allocation for the value. - Prefer arrays over slices for fixed-size data — no pointer, no GC pressure, better cache behaviour.
- Avoid
big.Intin hot paths — every arithmetic operation allocates. Use the field element types from gnark-crypto instead. - Measure with
-benchmemto track allocation pressure.
- Order struct fields largest-to-smallest to minimize padding.
- Avoid
fmt.Sprintfin hot paths; usestrings.Builderor direct byte manipulation. - For data-parallel loops over large arrays, prefer struct-of-arrays (SoA) layout over array-of-structs (AoS) for cache locality.
- Use
parallel.Executefor parallel computation. Assume 100+ CPUs. - Avoid channels and locks for single-task dispatch — the scheduling overhead
dominates. Prepare uniform-shaped work chunks for
parallel.Executeinstead; useruntime.GOMAXPROCS(0)for sizing.