Proving the Circuit: Formal Verification for Zero-Knowledge Proofs

Morgan Thomas and Ventali Tan · September 2022, revised September 2026

In 2024 a systematization-of-knowledge paper collected 141 vulnerabilities from audits, disclosures and bug trackers of deployed zero-knowledge proof systems and sorted them by where in the stack they lived.1 Most were not in the cryptography. They were in the circuits: the systems of polynomial equations over a finite field that encode the computation being proven. The most common root cause was a circuit that accepted more solutions than its author intended, so that a prover could satisfy it with a false statement.

That is the failure mode this post is about. We wrote the first version in September 2022, when the question "how do we formally verify a circuit?" had a handful of research prototypes behind it and no established method. The question has not changed. How do we formally verify that an arithmetic circuit, as used by a zero-knowledge proof system, has the desired characteristics: soundness, completeness, and zero knowledge?

The answer has a shape that is worth stating up front. The proof system is a general theorem, proven once on paper, that holds for every circuit in a class. The circuit is a one-off, and its correctness is a purely mathematical statement about a finite system of polynomial equations. Such a statement can be known only by proof, or by construction from something already proven. Testing does not reach it, because an under-constrained circuit passes every test an honest prover can write. The rest of this post makes that precise: what the statement is, what a constraint system is, which techniques can establish the statement and at what cost, a way of thinking that makes the obligations compose, who was doing what in 2022, and what has happened in the four years since.

1. The leading problem

Fix a finite field F, in practice the scalar field of a pairing-friendly curve with p near 2254, or a small FFT-friendly field near 264 or 231 for STARKs. A constraint system C over F is a finite list of polynomials c1, …, ck in variables partitioned into public inputs x ∈ Fn and witness variables w ∈ Fm. We write C(x, w) = 0 to mean that every ci vanishes at (x, w). The public inputs are the statement being proven. The witness is everything the prover supplies to make the statement check out, secret inputs and intermediate wires alike.

A circuit denotes a set: the set of statements for which some witness exists.

⟦C⟧ = { x ∈ Fn : ∃ w ∈ Fm. C(x, w) = 0 }

A specification denotes a set as well, the set S ⊆ Fn of statements that are true in the intended semantics. For a hash-verification circuit, x is a pair (d, h) and S is the set of pairs where h is the hash of d. With both sets in hand, the two properties of interest are inclusions.

sound(C, S) ⇔ ⟦C⟧ ⊆ S       complete(C, S) ⇔ S ⊆ ⟦C⟧

Soundness of a circuit says it is unsatisfiable on every false statement: no witness, however adversarially chosen, makes the polynomials vanish when x ∉ S. Completeness says it is satisfiable on every true statement. The conjunction, ⟦C⟧ = S, says the circuit denotes the intended relation, and that is the whole of circuit correctness. Table 1 lays out the four cases for a single statement.

Table 1. The four cases for one statement x. Circuit correctness is the absence of the off-diagonal cells for every x.
x ∈ S (statement true)x ∉ S (statement false)
C(x, ·) satisfiablecorrect: accepted, as intendedsoundness bug (under-constrained): a false statement has a witness
C(x, ·) unsatisfiablecompleteness bug (over-constrained): a true statement has no witnesscorrect: rejected, as intended

The two kinds of bug have names in practice. An under-constrained circuit is one that is unsound: the constraints admit assignments the specification forbids, so the denotation is too large. An over-constrained circuit is one that is incomplete: the constraints exclude assignments the specification requires, so the denotation is too small. Under-constrainedness is the dangerous direction, because an honest prover never notices it. The honest prover computes the intended witness, the circuit accepts it, every test passes, and the extra solutions sit unused until someone goes looking for them.

An under-constrained circuit passes every test an honest prover can write. The extra solutions sit unused until someone goes looking for them.

Most specifications are relations rather than sets. The spec says which meaningful witnesses y ∈ W go with which statements, R ⊆ Fn × W, and the circuit's witness w contains an encoding of y together with auxiliary wires. The set-level statement ⟦C⟧ = { x : ∃ y. R(x, y) } is then the minimum. Applications that need knowledge soundness need the stronger, witness-level form, with a projection π from circuit witnesses to meaningful ones:

C(x, w) = 0 ⇒ R(x, π(w))       R(x, y) ⇒ ∃ w. π(w) = y ∧ C(x, w) = 0

The first implication is soundness at the witness level, the second completeness. Verification efforts differ in which of these they prove, and it is worth asking of any result which one it establishes.

Zero knowledge is a property of the proof system rather than of the circuit, and we mostly set it aside here.2 It says that a proof reveals nothing beyond the statement: for every verifier strategy there is a simulator that, given only x, produces a transcript indistinguishable from a real interaction with a prover who knows w. What the circuit contributes is the boundary of "nothing beyond the statement". Any fact implied by x together with ⟦C⟧ is fair game, so if the relation lets w be recovered from x, because the function is easy to invert or the domain is small, no proof system can hide it. A circuit that is meant to hide its witness must encode a hard-to-invert function of that witness.

Two subproblems

A proof system Π takes a circuit C, a statement x and a witness w and produces a proof; its verifier takes C, x and the proof and accepts or rejects. Π is complete if honest proofs of true statements are accepted, sound if no prover can get a false statement accepted except with negligible probability ε, and zero knowledge as above. These properties are stated relative to ⟦C⟧, and they are proven once, for all C in the class the system supports.

x ∈ ⟦C⟧, C(x, w) = 0  ⇒  Pr[ V(C, x, P(C, x, w)) = 1 ] = 1
x ∉ ⟦C⟧  ⇒  ∀ P*. Pr[ V(C, x, P*(C, x)) = 1 ] ≤ ε

Put the two obligations together and the application-level guarantee follows. If ⟦C⟧ = S and Π is sound and complete, then the application-specific proving system, Π instantiated with C, accepts exactly the true statements: honest proofs of x ∈ S always verify, and no proof of x ∉ S verifies except with probability ε. The leading problem therefore splits cleanly. One subproblem is verifying the proof system, which is hard, has been done informally for the major systems, and in 2022 had not been done formally for any significant one. The other is verifying the circuit, and that is a problem about systems of polynomial equations, independent of which proof system will consume them.3 This post is mostly about the second.

Formal verification of a circuit needs five things: a formal specification of the relation the circuit is intended to encode, a formal model of the semantics of circuits, the circuit itself, a theorem prover, and a mechanized proof, checked by the prover, of a theorem relating the first three. Figure 1 shows how they fit, and how the result composes with the proof system.

Five ingredients and two obligations Three boxes in a row, a formal specification, a semantics of circuits, and the circuit, feed a mechanized proof that the circuit denotes the specification, checked by a theorem prover. That proof, together with an assumed sound and complete proof system, yields an application-specific proving system that accepts exactly the specified statements. What a circuit proof needs, and what it buys (1) formal specification S ⊆ Fⁿ, or a relation R(x, y) (2) semantics of circuits ⟦C⟧ = { x : ∃w. C(x, w) = 0 } (3) the circuit C R1CS · AIR · PLONKish (5) mechanized proof that ⟦C⟧ = S checked by (4) a theorem prover proof system Π sound, complete, ZK: assumed application-specific proving system (Π, C) accepts exactly S, up to soundness error ε circuit theorem
Figure 1. The five ingredients of a circuit proof and the composition with the proof system. Items (1) to (3) are inputs, (4) is the checker, (5) is the deliverable, and the dashed box is what this post assumes rather than proves.

2. What a constraint system is

The polynomial systems that proof systems consume come in a few standard shapes, and the shape matters to verification because it determines what the semantics model in Figure 1 has to say. Three cover almost everything deployed.

Rank-1 constraint systems

An R1CS with k constraints over a vector z = (1, x, w) of length 1 + n + m is a triple of matrices A, B, C ∈ Fk × (1+n+m), satisfied when

(A z) ∘ (B z) = C z

where ∘ is the entrywise product. Each row is one constraint of the form (Σ ai zi)(Σ bi zi) = Σ ci zi: a product of two linear forms equals a third. The constant wire 1 lets constants appear. A boolean wire b is constrained by the single row b · (b − 1) = 0, that is, a = eb, b = eb − e1, c = 0. Any polynomial system can be flattened to this shape by introducing a wire for each intermediate product, which is how a compiler like circom gets from a program to an R1CS. The pairing-based SNARKs consume R1CS after interpolating each column into a polynomial: pick k distinct points r1, …, rk, let Ai(X) be the polynomial that passes through the entries of column i of A at those points, and likewise for B and C. The R1CS is then satisfied if and only if the polynomial identity

( Σi zi Ai(X) ) · ( Σi zi Bi(X) ) − Σi zi Ci(X) = H(X) · Z(X),    Z(X) = ∏j (X − rj)

holds for some quotient H. That is the quadratic arithmetic program, and it is the identity a pairing-based SNARK such as Groth16 checks in the exponent.4

Algebraic intermediate representations

A STARK works from an execution trace T with L rows and c columns, one row per step of a machine and one column per register. An AIR is a list of transition polynomials Pi in 2c variables that must vanish on every pair of consecutive rows, plus boundary constraints pinning particular cells. On a multiplicative subgroup H = ⟨ω⟩ of F of order L, each column interpolates to a polynomial Tj(X) with Tj(ωt) = T[t][j], and the transition constraint becomes divisibility:

Pi( T(ωt), T(ωt+1) ) = 0 for all t  ⇔  ZH(X) divides Pi( T(X), T(ωX) ),    ZH(X) = XL − 1

The prover commits to the Tj and to the quotients, and a low-degree test does the rest.5 For verification the point is that the AIR is a small object, a few dozen polynomials of low degree, while the trace is huge, and the correctness question is about the AIR: does every trace satisfying it correspond to a valid execution of the machine, and does every valid execution produce a satisfying trace? That is ⟦C⟧ = S again, with S the set of statements a correct machine can reach.

PLONKish systems

PLONK and its descendants lay wires out in a table and attach a gate equation to each row, selected by fixed columns:

qL · a + qR · b + qO · c + qM · a b + qC = 0

Setting qM = 1, qO = −1 and the rest to zero makes a multiplication gate; other selector patterns make addition or constants. Wires that must be equal across rows are tied by a copy constraint, expressed as a permutation σ on cells with the requirement that the table is invariant under σ. Halo 2 generalizes the gate to arbitrary custom polynomials over cells at fixed row offsets, and adds lookup arguments, which constrain a tuple of cells to lie in a fixed table.6 The verification question is unchanged, but the semantics model has to account for three kinds of constraint, gate, copy and lookup, rather than one.

Table 2. The three constraint-system shapes in use. Each is a finite polynomial system over F; they differ in where the structure lives, which is what a semantics model has to capture.
FormConstraint shapeWhere the structure isConsumed by
R1CS(Az) ∘ (Bz) = Cz: one bilinear equation per row, degree 2none between rows; wires are shared by indexGroth16, Marlin, Spartan
AIRPi(Tt, Tt+1) = 0 at every row, plus boundary constraints; low degreethe same constraints repeat at every row; the structure is in the traceSTARKs (StarkWare, Plonky3)
PLONKishselector-weighted gate polynomial per row; copy constraints as a permutation; lookups into tablespermutation ties cells across rows; tables constrain tuplesPLONK, Halo 2, Plonky2

What the three shapes share is that they are finite systems of polynomial equations over a finite field. Satisfiability of such a system is decidable, because the domain is finite, and intractable in general, because deciding whether a quadratic system over Fp has a solution is NP-complete. Verification therefore cannot be brute search, and the techniques in Section 3 are all ways of exploiting structure: the constraints are sparse, most are linear or bilinear, and most gadgets are meant to compute a function of their inputs.

A worked example

The gadget that appears most often in discussions of circuit bugs is the one that tests whether a wire is zero. In circomlib it is written as follows: introduce a witness wire inv, which the honest prover sets to the inverse of in when in ≠ 0 and to 0 otherwise, and impose two constraints.7

(i)  out = 1 − in · inv       (ii)  in · out = 0

Take the cases. If in = 0, constraint (i) forces out = 1, and (ii) holds trivially. If in ≠ 0, constraint (ii) forces out = 0, and then (i) forces inv = in−1, which exists. So for every in there is exactly one out that satisfies both, and it is the intended one: the circuit denotes the relation out = [in = 0], and it is sound and complete.

Now drop constraint (ii). When in = 0 nothing changes. When in ≠ 0, the prover may still set inv = in−1 and get out = 0, but may equally set inv = 0 and get out = 1. The circuit accepts the false statement "in is zero" for every nonzero in. Nothing about the honest prover's behavior has changed, every test the honest prover writes still passes, and the denotation has doubled. Add instead the extra constraint in · inv = 1 and the opposite happens: for in = 0 no assignment satisfies it, and the circuit rejects the true statement "0 is zero". Figure 2 draws the first two relations.

The IsZero gadget with and without its second constraint Two panels. On the left, with both constraints, in equals zero maps to out equals one and in nonzero maps to out equals zero, a function. On the right, with the second constraint dropped, in nonzero maps to both out equals zero and, by choosing inv equals zero, out equals one, so a false statement is accepted. (a) both constraints out = 1 − in·inv  and  in·out = 0 in = 0 in ≠ 0 out = 1 out = 0 inv = in⁻¹ a function: sound and complete (b) second constraint dropped out = 1 − in·inv only in = 0 in ≠ 0 out = 1 out = 0 inv = 0 a relation: unsound, every test still passes
Figure 2. The IsZero gadget as a relation between input and output. With both constraints (a) each input has exactly one output. With the second constraint dropped (b) a nonzero input also admits out = 1, the dashed edge, which is a proof of a false statement.

The dropped-constraint version violates a property worth naming, because it is the one most automatic tools check. A circuit is properly constrained, or has the uniqueness property, with respect to a designated set of output wires if the inputs determine the outputs:

∀ x, w, w′.  C(x, w) = 0 ∧ C(x, w′) = 0  ⇒  out(w) = out(w′)

Uniqueness needs no specification. It is a property of the polynomial system alone, which is what makes it automatable, and the tools Ecne and Picus discussed below check exactly this.8 It is also strictly weaker than soundness. A circuit that computes a function deterministically may compute the wrong function; uniqueness says the prover has no freedom, not that the prover's forced answer is right. In practice the two come apart less often than one might expect, because the commonest way to be unsound is to leave a wire free, and that is exactly what uniqueness catches.

3. Techniques

To know a general statement with certainty, it has to be a mathematical statement, knowable by reasoning from consistent assumptions through truth-preserving inferences, without running anything. Given that a bachelor is an unmarried man and that a married man has a spouse, no bachelor has a spouse; no survey of bachelors is required. A proof theory codifies which inferences are truth-preserving, and with pencil and paper one can construct proofs in it. A proof assistant is a program that helps construct such proofs and checks them mechanically, which removes human error from the checking and reduces the labor of the construction. Coq (renamed Rocq in 2025), Agda, Isabelle, ACL2, PVS, Imandra and Lean are the ones that appear in this post.

With a proof assistant

There are two ways to prove a statement about a program with a proof assistant. One is to write the program in the proof assistant's own language and apply its proving facilities directly. The other is to translate a program written in some other language into an object the proof assistant can reason about, and prove things about that object. The second is post hoc verification, and the translation is its weak point: the proof is about the translator's output, and the assumption that the output means the same as the source is unproven unless the translator is itself verified.9 The first approach gives more confidence, because the theorem is about exactly the program being executed. What motivates the second is that the proof assistant's language is often not a language one can build the application in.10

Without a proof assistant

A verified compiler is a compiler with a theorem, checked in a proof assistant, that its output has the same meaning as its input. CompCert is the canonical example: a C compiler proven to emit assembly semantically equivalent to the C.11 A verifying compiler is different: it emits, alongside each compiled program, a proof that this particular output matches this particular input, which can be checked independently. Either way the output is correct by construction, provided the input was correct. Verified compilers do not find bugs in source programs; they promise not to add any.

An automatic proof search algorithm takes statements and returns proofs when it finds them. A static analyzer takes a program and recognizes patterns that always or often indicate a problem, without understanding what the program means. Each has a ceiling. A verified compiler proves only that meaning is preserved, and nothing about whether the meaning was intended. A proof search algorithm is limited by its sophistication and its compute, and no algorithm can prove every true statement, for reasons the footnote spells out with the care a reviewer of the original version insisted on.12 A static analyzer cannot tell whether a program is correct, only whether it resembles programs that were wrong.

What is different about circuits

For ordinary software these limits bite hard. For circuits, automatic methods reach further than usual, for the reason already given: the objects are finite polynomial systems over Fp, not programs with loops and heaps. Satisfiability modulo a theory of finite fields, with Gröbner-basis and propagation reasoning inside the solver, decides bounded questions about a circuit outright, and uniqueness in particular reduces to a single query: find x, w, w′ with C(x, w) = 0, C(x, w′) = 0 and out(w) ≠ out(w′). If the solver returns unsatisfiable, the circuit is properly constrained. If it returns a model, the model is a concrete exploit. What the automatic route cannot do is supply the specification. Uniqueness and its relatives are spec-free, and once they are exhausted, everything left needs S written down formally, and then either an interactive proof against it or a compiler proven to produce circuits from it. Figure 3 places the techniques and the tools on those two axes.

Verification techniques by specification requirement and degree of automation A two-by-two chart. Horizontal axis from spec-free to specification required; vertical axis from interactive to automatic. Top-left: linters such as Circomspect and uniqueness checkers Ecne and Picus. Straddling the middle at the top: SMT with a finite-field theory. Top-right: verifying compilers such as the proof-producing Cairo compiler and verified compilers such as the Sigma-1-1 compiler to Halo 2 and Leo. Middle-right: refinement types, Coda. Bottom-right: proof assistants, ACL2 with Axe at Kestrel and Lean at StarkWare, and the Clean circuit DSL. Bottom-left: manual audit. Where the techniques sit automatic interactive spec-free: properties of the polynomial system alone specification required proves the circuit means what S says linters: Circomspect uniqueness checkers: Ecne, Picus SMT with a finite-field theory: cvc5 verifying compilers: Cairo, proof-producing verified compilers: Σ¹₁ to Halo 2, Leo correct by construction: the spec is the program refinement types: Coda (Coq) proof assistants: ACL2 with Axe (Kestrel),Lean (StarkWare, Bailey and Miller) circuit DSL with proofs: Clean (Lean 4) manual audit the 2022 default
Figure 3. Techniques by whether they need a specification and how much of the work is automatic. Everything in the left column checks the polynomial system against itself. Everything in the right column checks it against a statement of intent, and the price of the right column is writing that statement.

4. Denotational design

The observation that a circuit denotes a set, and a specification denotes a set, is more useful than it looks, because it turns every verification obligation into an equation between denotations, and equations compose. The methodology has a name, denotational design, and it applies to both subproblems.

Suppose specifications are written in a language Spec and compiled to circuits by a function comp. Each language has a denotation function into subsets of Fn. The compiler is correct when the square in Figure 4 commutes:

⟦ comp(s) ⟧Circ = ⟦ s ⟧Spec   for every s ∈ Spec

Soundness of the compiler is the inclusion ⊆, completeness the inclusion ⊇, and commutation is both. The same square with a single circuit in place of comp(s) is the post hoc obligation of Section 1: a proof that one particular C denotes one particular S.

The commutative square of denotational design Top left, a specification s. An arrow labeled comp leads right to the circuit comp of s. Downward arrows labeled with the denotation brackets lead from each to a subset of F to the n. The two bottom nodes are joined by an equals sign. The square commutes when the denotation of the compiled circuit equals the denotation of the specification. The compiler is correct when the square commutes specification s ∈ Spec circuit comp(s) ∈ Circ ⟦s⟧Spec ⊆ Fⁿ ⟦comp(s)⟧Circ ⊆ Fⁿ comp verified once, for all s ⟦·⟧Spec ⟦·⟧Circ Π is verified against this, once sound: ⊆    complete: ⊇ commutes: ⟦comp(s)⟧ = ⟦s⟧, so no application-specific proof is needed
Figure 4. Denotational design as a commutative square. A formal version appears in the Σ¹₁ arithmetization paper. Soundness and completeness of a circuit compiler are both the statement that all paths between the same corners are equal.

The proof system's properties are stated against the same denotation. Completeness: for every C and every x ∈ ⟦C⟧ with witness w, the honest prover's proof is accepted. Soundness: for every C and every x ∉ ⟦C⟧, no prover strategy is accepted except with probability ε. Knowledge soundness strengthens this with an extractor E that, given access to any prover P* that convinces the verifier, produces a witness:

Pr[ V(C, x, π) = 1  ∧  C(x, EP*(C, x)) ≠ 0 ] ≤ εks

None of these mention S. That is the point. The proof system is verified once against ⟦·⟧Circ, for all circuits. The compiler is verified once against both denotations, for all specifications. An application then consists of writing s, and its correctness follows without an application-specific proof: ⟦comp(s)⟧ = ⟦s⟧ by the compiler theorem, and Π is sound and complete for ⟦comp(s)⟧ by the proof-system theorem. This is the correct-by-construction route, and it is the only route that scales to more circuits than there are people able to write mechanized proofs.

The proof system is verified once against the denotation of circuits. The compiler is verified once against the denotation of specifications. The application is verified by writing it down.

A concrete instance existed in 2022. Morgan Thomas's arithmetization of Σ11 relations takes formulas of the shape

∃ f1 … ∃ fk . φ( f1, …, fk, x )

where the fi range over functions on bounded finite domains and φ is first order, and compiles them to Halo 2 circuits whose denotation is the set of x for which the formula holds. The Orbis Specification Language is a typed front end that compiles to such formulas.13 Σ11 is a natural target because it has exactly the strength of an NP relation: the existential function quantifiers are the witness, the first-order body is the check. A specification written in OSL needs no circuit-level reasoning at all once the compiler is proven.

The terminology, stated once. If you write a specification in a high-level formal language and compile it with a verified or verifying compiler, the result is correct by construction. If you take an existing constraint system and prove properties of it, up to and including ⟦C⟧ = S, against a high-level specification, that is post hoc verification.

5. From theorem to executable

There is usually a gap between the theorem one can prove and the code one can run. One can prove a theorem about a specification and write code for it, and not be able to prove the code implements the specification. One can prove a theorem about code and not be able to compile it efficiently. One can compile it and not be able to prove the machine code means what the source meant. Every approach to closing the gap decides which of these to prove and which to trust, and Table 3 records the trusted core for each: the components whose bugs would invalidate the theorem without being caught by it.

Table 3. What each approach proves and what it leaves trusted. The prover's kernel and the hardware appear in every row; the rows differ in what else they add.
ApproachWhat is provenWhat is trusted
Program written in the prover's own languagea theorem about the source itselfprover kernel; extraction or compilation to machine code; hardware
Post hoc: translate, then provea theorem about the translated modelall of the above, plus the translator and the semantics model it targets
Verified compilermeaning preserved from source to output, for all inputsthe compiler's proof, once; the source specification; kernel; hardware
Verifying compilermeaning preserved for this input, with a proof emitted per compilationthe proof checker; the source specification; kernel; hardware
Automatic proof search, SMTthe specific query, such as uniquenessthe solver unless it produces checkable proofs; the encoding of the circuit into the solver's logic. No specification exists to be wrong
Static analysisnothingeverything; the output is a heuristic

The ecosystem in 2022 offered several partial bridges. Coq and Agda extract programs to OCaml or Haskell, to be compiled conventionally, with the extraction trusted and the efficiency of the result often disappointing. ATS combines a proof system with a language that aims at C-level efficiency. Fiat and CertiCoq synthesize efficient binaries inside Coq with the synthesis proven. ACL2 defines a subset of Common Lisp with a full formal logic, so that an executable definition is also a logical one: the same text is compiled to efficient code and reasoned about, and a verifying compiler, ATC, carries ACL2 to C.14 Imandra does the same for a subset of OCaml. Transpilers from mainstream languages into a proof assistant allow theorems about an extracted model of the source, with the transpiler trusted.15

Hardware

Proof generation is dominated by number-theoretic transforms, O(n log n) field operations, and multi-scalar multiplications, O(n) group operations with large constants, both of them embarrassingly parallel. Provers are most efficient on GPUs, FPGAs and ASICs, and are mostly written for CPUs, because CPUs are easier to program and the programmers are easier to hire. For a formally verified prover that is also fast, the right target is the specialized hardware, and in 2022 tools for compiling formally verified programs to GPUs, FPGAs or ASICs were, as far as we knew, nonexistent. That has not materially changed. The verification story for provers still stops at the CPU, and the vectorized kernels that do the work sit outside every proof.

6. Where things stood in 2022

Two older bodies of work were adjacent. Decades of research on formally verifying arithmetic circuits exist in the hardware community, with some industrial use, but a hardware arithmetic circuit is a netlist of bit-level gates and a SNARK arithmetic circuit is a polynomial system over a large prime field, and the methods do not transfer directly.16 Decades of research also exist on proving soundness, completeness and zero knowledge for probabilistic proof systems, on paper: Groth16, Bulletproofs and PLONK each come with such proofs, and they are the starting point for doing the same formally.17

The efforts directed at circuits themselves were few, and each is best described along four axes: the language used for the specification, the language used to model the circuit, the theorem prover, and the theorems proven, whether soundness, completeness, functional correctness or zero knowledge. Two further questions bear on how much confidence a result deserves: whether the tooling and proofs are open source, since a theorem whose inputs cannot be inspected is hard to trust, and what the trusted core is. Table 4 applies the rubric.

Table 4. The 2022 efforts along the axes of the rubric: specification language, circuit model and prover, and theorems proven. Open-source status and trusted core varied and are worth checking per project.
EffortSpecificationCircuit model; proverTheorems
Kestrel InstituteACL2 functions: parts of Zcash Sapling and Ethereum SemaphoreR1CS formalized in ACL2, lifted from Bellman by Axe; ACL2functional correctness of gadgets, hence soundness and completeness
Aleo, LeoLeo programsR1CS; ACL2correct compilation by a verifying compiler (in progress); post hoc gadget proofs via Axe
Orbis LabsOSL, compiled to Σ¹₁ formulasHalo 2; Coqcompiler correctness: denotation preserved
Bailey and MillerSNARK constructions in the algebraic group modelthe proof system rather than circuits; Leansoundness of Groth16 and other linear-PCP SNARKs
VeridiseV, a spec language; Coda's DSL typesR1CS (Picus), Cairo (Medjai), Coq DSL (Coda); Coq and custom symbolic enginesuniqueness (Picus); functional correctness (Coda)
Ecne (0xPARC)noneR1CS; custom solveruniqueness: the system defines a partial or total function
StarkWareLean specificationsCairo programs; Leanconformance of programs to specifications
Ozdemir; Vella and AltSMT-LIB queriespolynomial systems over Fp; cvc5's finite-field theory, a polynomial solversatisfiability of bounded queries

In prose. Kestrel Institute had proved functional correctness of R1CS gadgets in ACL2: they formalized R1CS in ACL2, fetched gadgets from the Bellman implementation with an extraction tool, lifted them into logic with the Axe toolkit, and proved them equivalent to specifications of parts of the Zcash Sapling protocol and of Ethereum's Semaphore, which implies both soundness and completeness of those gadgets.18 Aleo was building Leo, a language compiling to R1CS, with the aim of a verifying compiler whose per-compilation theorems are generated and checked in ACL2, and had done post hoc verification of gadgets with Axe.19 Orbis Labs was building the Σ11 compiler and OSL of Section 4, and a toolchain, Miya, aimed at hardware-accelerated verified provers, with a theory of interaction-combinator arithmetization and parsimonious definitions of proof system, soundness and zero knowledge. Bolton Bailey and Andrew Miller were formalizing soundness of a range of SNARKs in Lean, in the algebraic group model.20 Veridise had Medjai, a symbolic evaluator for Cairo; Picus, a symbolic virtual machine for R1CS aimed at automatic proof search; V, a specification language; and Coda, a Coq framework for certifying circuits through a functional DSL.21 Ecne, from 0xPARC, was a special-purpose solver that proves an R1CS defines a function, total or partial, and had found real bugs that way.8 StarkWare was writing Lean proofs that Cairo programs conform to specifications.22 Alex Ozdemir was adding a finite-field theory to the cvc5 SMT solver, and Lucas Clemente Vella and Leonardo Alt were building a solver for polynomial systems over finite fields.23 Nomadic Labs had brought Zcash Sapling to Tezos, with some of its R1CS verified by Kestrel, and Anoma was building Juvix as a language-level route to more reliable contracts.24

7. What has happened since

Four years is a long time in a field that had almost nothing. Read against the rubric, the movement sorts into five lines.

Spec-free checking became a product. Picus was published in 2023 as an automated checker for the uniqueness property, combining an SMT solver over Fp with a propagation procedure tuned to the linear and bilinear structure most gadgets have, and it found under-constrained circuits in circomlib and in deployed projects.25 Trail of Bits released Circomspect, a static analyzer for circom that flags the patterns, such as a signal assigned with <-- but never constrained, that produce the IsZero bug of Section 2.26 These are the top-left of Figure 3, and it is now reasonable to expect any serious circuit to have been through them.

The solvers arrived. cvc5 shipped a theory of finite fields in 2023, and a proposal for a standard SMT-LIB theory of finite fields followed in 2024, so the polynomial-solver work that was a research prototype in 2022 is now a standard interface any verification tool can target.27

Specifications got cheaper. Coda, published in 2024, writes circuits in a DSL with refinement types: the type of a gadget carries the relation it is meant to compute, and Coq discharges the resulting obligations, many of them automatically, with the remainder proved by hand. Its authors certified a substantial part of circomlib this way.28 Kestrel wrote up the ACL2 method in 2023, with a formalization of prime-field constraint systems, the lifting of gadgets into logic, and compositional proofs that let a verified gadget be reused inside a larger one without reproving it.29 Both are the bottom-right of Figure 3 moving upward: the same spec-required guarantees at less labor per gadget.

The proof-system subproblem got real work. Bailey and Miller's Lean formalization of soundness for a family of pairing-based SNARKs in the algebraic group model was completed and published, the first mechanized soundness proofs for deployed SNARK constructions.20 In the other direction, Dao, Miller, Wright and Grubbs showed that many deployed proof systems had implemented Fiat–Shamir weakly, omitting the statement from the hash, and that the omission was exploitable. It is a reminder that "assume the proof system is sound" is an assumption about an implementation, not only about a paper.30 The Verified zkEVM effort, started in 2024, is building ArkLib, a Lean 4 library for stating and proving the security of interactive oracle proofs and polynomial commitment schemes, which is the reusable verified abstraction the 2022 list of directions asked for.31

Correct by construction at scale. StarkWare and Avigad's group published a proof-producing compiler for Cairo in 2025: the compiler emits, with the compiled program, a Lean proof that the program meets its specification, which is the verifying-compiler pattern of Section 3 applied to circuits that run in production.32 The Verified zkEVM effort maintains Clean, a circuit DSL embedded in Lean in which each gadget is written together with its soundness and completeness proofs and gadgets compose with their proofs, so that the square of Figure 4 commutes by construction for everything written in it.31

Table 5 sets the 2022 list of directions against this.

Table 5. The directions proposed in 2022 and where they stand in 2026.
Direction proposed in 2022Status in 2026
Foundations for verifying proof systems: generalizable proof techniques, reusable verified abstractions such as a polynomial-commitment libraryMoved. Bailey and Miller's Lean soundness proofs; ArkLib's oracle-proof and polynomial-commitment library in Lean 4.
Better specification languages, and verified translators between themMoved on the first half: refinement types in Coda, OSL to Σ¹₁, Clean's Lean-embedded DSL. Verified translators between specification languages remain rare.
Formally verified programs on vectorized hardware: FPGA, GPU, ASICNot moved.
Verifying systems that optimize circuits automatically, such as re-arithmetization for parallel setup or partial evaluation for a prover who knows part of the witnessLittle movement. Circuit optimizers are still trusted.
K semantics for circom and Cairo; K's Rust semantics applied to arkworksNot pursued as far as we know. The need was met differently: circom got Circomspect and Picus, Cairo got a proof-producing compiler in Lean.
Not on the 2022 list: spec-free automatic checkingBecame the most widely used technique in the field: Ecne, Picus, Circomspect.

8. What is still open

Three things have not moved, and one new bottleneck has appeared.

The composition of Section 1, circuit theorem plus proof-system theorem plus compiler theorem yielding an application guarantee, has been proven in pieces and, as far as we know, never assembled end to end for a deployed system with a single trusted core. The pieces now live in the same proof assistant more often than not, which was not true in 2022, so the assembly is closer to an engineering project than a research one.

Hardware is untouched. Provers run on GPUs and the proofs stop at the CPU.

Zero knowledge is still the property everyone defers. The simulator-based definition is well understood on paper, and formalizing it for a particular circuit and proof system, so that "the proof reveals nothing beyond x" is a checked theorem, remains rare.

The new bottleneck is specification. When the tools were the limit, the proof was the expensive step. With uniqueness checkers automatic, solvers standard and refinement types discharging much of the rest, the expensive step is writing S: saying, in a formal language, what the circuit is for. Spec-free properties are exhausted at uniqueness. Everything beyond it needs a specification, and specifications are written by people who understand both the application and the logic, which is a small set. Two things help. Executable specifications, in the ACL2 style, can be tested before they are proven, which catches the specification bugs that would otherwise be proven faithfully. And the proof, unlike the specification, no longer has to be written by a person: a proof generated by any means, including a language model, is checked by the same kernel, so the untrusted part of the pipeline can be as unreliable as it likes.

The 2022 version of this post ended by saying that not enough emphasis is placed on formal verification in the security industry. That is less true now, and the reason is the one this post opened with. The bugs are in the circuits, the circuits are polynomial systems, and the correctness of a polynomial system is a theorem or it is nothing.

References

  1. Stefanos Chaliasos, Jens Ernstberger, David Theodore, David Wong, Mohammad Jahanara and Benjamin Livshits, "SoK: What don't we know? Understanding Security Vulnerabilities in SNARKs", USENIX Security 2024. ↩
  2. The original post deferred the zero-knowledge property to a later article. To state it one needs private inputs; the correct computation of a hash from a hidden preimage is the standard example. The property is how hard it is to determine the private inputs from the public information, and it is generally a probabilistic measure. If the computation is too simple and the output is known, the input may be recoverable with certainty, and no proof system can prevent that. ↩
  3. The circuit subproblem can be solved independently of the ambient proof system: it is a problem about systems of polynomial equations, and the same proof serves whichever system consumes the circuit. ↩
  4. Rosario Gennaro, Craig Gentry, Bryan Parno and Mariana Raykova, "Quadratic Span Programs and Succinct NIZKs without PCPs", EUROCRYPT 2013; Jens Groth, "On the Size of Pairing-based Non-interactive Arguments", EUROCRYPT 2016. ↩
  5. Eli Ben-Sasson, Iddo Bentov, Yinon Horesh and Michael Riabzev, "Scalable, transparent, and post-quantum secure computational integrity", 2018. ↩
  6. Ariel Gabizon, Zachary J. Williamson and Oana Ciobotaru, "PLONK: Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge", 2019; The halo2 Book, Zcash. ↩
  7. The IsZero template in circomlib's comparators.circom. In circom's notation the inverse is assigned with <--, which creates no constraint, and the two constraints are written with <== and ===. ↩
  8. Franklyn Wang, "Ecne: Automated Verification of ZK Circuits", 0xPARC, 2022, code at github.com/franklynwang/EcneProject; Veridise, Picus. ↩
  9. The transpiler idea makes the most sense when there is a formally defined DSL close to the language you want to verify, so that the transpiler is small. It should still itself be formally verified. ↩
  10. Two observations from formal verification of systems with a cryptographic component. First, it is extremely difficult to reason formally about the security of the whole system: the design has many dependencies of different kinds, and even a component that is simple at a high level, such as an encryption scheme or a MAC, is implemented with failure handling, recovery and backup, so any formal conclusion ends up being about a small part of the whole. Second, proof-assistant languages have limited expressivity for protocols: there are few tools that prove theorems directly about a protocol written in C or Rust, and developers must handle threads and timing, which Gallina, for instance, has no constructs for. Formalizing existing abstractions for concurrency, such as Robin Milner's π-calculus, may help. ↩
  11. Xavier Leroy, "Formal verification of a realistic compiler", Communications of the ACM, 2009. ↩
  12. The original draft attributed the limit to Gödel's incompleteness theorem. Alessandro Coglio suggested "the undecidability of first-order logic" instead, since first-order logic is complete by Gödel's completeness theorem and yet undecidable, while higher-order logic is incomplete as well as undecidable. Morgan Thomas objected that undecidability alone does not rule out a complete proof search: first-order consequence is semidecidable, so an algorithm can find a proof of any provable statement, and what rules out a complete search for first-order arithmetic is that no computably enumerable set of true axioms proves every true statement, which is incompleteness. Coglio's reply was that both are correct and that in practice undecidability is the obstacle in complete and incomplete logics alike: provability is undecidable in first-order and higher-order logic, and even decidable theories can take impractically long. The accurate sentence is therefore that no algorithm can establish, for every formula, whether it is a theorem. ↩
  13. Morgan Thomas, "Arithmetization of Σ¹₁ relations in Halo 2", 2022, and "Arithmetization of Σ¹₁ relations with polynomial bounds in Halo 2", 2022; Orbis Labs, "Orbis Specification Language: a type theory for zk-SNARK programming", 2022; code at github.com/Orbis-Tertius/coq-arithmetization. ↩
  14. Common Lisp compiles to quite efficient code, close to C or Rust, though not to GPU efficiency, and concurrency, while available in Common Lisp, is outside the ACL2 subset that has a formal logic. An executable formal specification can usually be compiled to fairly efficient code entirely within the theorem prover's language. ATC is documented in the ACL2 manual. ↩
  15. Fiat; CertiCoq; ATS; Imandra; ACL2. ↩
  16. Rolf Drechsler et al., on formal verification of arithmetic circuits, 2022; Cunxi Yu et al., "Formal Verification of Arithmetic Circuits by Function Extraction", 2016; Christoph Kern and Mark R. Greenstreet, "Formal Verification in Hardware Design: A Survey", 1999, which describes the AAMP5 verification. ↩
  17. Groth16 as above; Benedikt Bünz et al., "Bulletproofs: Short Proofs for Confidential Transactions and More", 2017; PLONK as above. ↩
  18. Kestrel Institute's ACL2 formalizations of Zcash, Semaphore and R1CS; the Axe toolkit. Kestrel also has the APT toolkit for correct-by-construction synthesis from specifications. ↩
  19. Aleo and the Leo language. ↩
  20. Bolton Bailey and Andrew Miller, "Formalizing Soundness Proofs of SNARKs", USENIX Security 2024; code at github.com/BoltonBailey/formal-snarks-project. ↩
  21. Veridise: Medjai, Picus, V, Coda. ↩
  22. StarkWare, formal-proofs: Lean verification of Cairo programs. ↩
  23. Alex Ozdemir's finite-field theory for cvc5; Lucas Clemente Vella and Leonardo Alt, an SMT solver for polynomial equations over finite fields, SMT workshop 2022. ↩
  24. Nomadic Labs; Anoma's Juvix. ↩
  25. Shankara Pailoor, Yanju Chen, Franklyn Wang, Clara Rodríguez, Jacob Van Geffen, Jason Morton, Michael Chu, Brian Gu, Yu Feng and Işıl Dillig, "Automated Detection of Under-Constrained Circuits in Zero-Knowledge Proofs", PLDI 2023. ↩
  26. Trail of Bits, Circomspect, 2022. ↩
  27. Alex Ozdemir, Gereon Kremer, Cesare Tinelli and Clark Barrett, "Satisfiability Modulo Finite Fields", CAV 2023; Thomas Hader and Alex Ozdemir, "An SMT-LIB Theory of Finite Fields", 2024. ↩
  28. Junrui Liu, Ian Kretz, Hanzhi Liu, Bryan Tan, Jonathan Wang, Yi Sun, Luke Pearson, Anders Miltner, Işıl Dillig and Yu Feng, "Certifying Zero-Knowledge Circuits with Refinement Types", IEEE Symposium on Security and Privacy 2024. ↩
  29. Alessandro Coglio, Eric McCarthy and Eric W. Smith, "Formal Verification of Zero-Knowledge Circuits", 2023. ↩
  30. Quang Dao, Jim Miller, Opal Wright and Paul Grubbs, "Weak Fiat-Shamir Attacks on Modern Proof Systems", IEEE Symposium on Security and Privacy 2023. ↩
  31. Verified zkEVM: ArkLib, formally verified arguments of knowledge in Lean, and Clean, a Lean circuit DSL. ↩
  32. Jeremy Avigad, Lior Goldberg, David Levit, Yoav Seginer and Alon Titelman, "A Proof-Producing Compiler for Blockchain Applications", 2025. ↩