Seyed Masoud Hosseini · Overview · Study log · Ideas · Transcript · RSS feed
Zero Knowledge Proofs · Lecture 13 of 14 · 1:07:05
Lecture 15: Securing ZK Circuits with Formal Methods
Study guide
What this lecture covers
This lecture, given by a program-verification researcher, addresses a problem specific to zero-knowledge circuits: because a circuit consists of both witness-generation code and a separate constraint system, the two can silently diverge, letting a prover convince a verifier of something false. The lecture answers how such bugs arise, how to classify them, and how two complementary tools (an abstract-interpretation-based static analyzer and an SMT-based formal verifier) can find and eliminate them.
It sits alongside the course's other applied-security material, building on general formal-methods background from smart-contract auditing and applying it specifically to circuit correctness. After watching, you should be able to describe the difference between over-constrained and under-constrained bugs, recognize common ZK bug patterns, and understand how static analysis and SMT solving can be combined to verify a circuit is properly constrained.
Key ideas
- Abstract interpretation: a static analysis technique that computes conservative over-approximations of a program's possible states to prove the absence of certain bad behaviors, at the cost of possible false positives.
- Formal verification: proving a program matches a precise mathematical specification by reducing the question to a validity check, typically delegated to an SMT solver such as Z3.
- Witness generation vs. constraint system: a ZK circuit has two parts, the code that computes witness values and the polynomial equations that constrain them; for the circuit to be correct these two must be equivalent.
- Over-constrained bug: a valid witness computation fails to satisfy the constraints; rare in practice because it triggers an assertion failure during proving, so it is caught early.
- Under-constrained bug: some input/output pair satisfies the constraints without being a valid output of the witness computation, letting a prover fabricate accepted proofs of false statements; the more dangerous and common class.
- Circuit dependence graph: a representation of a circuit as signals (nodes) connected by data-dependency edges and constraint edges, used to query for known under-constrained bug patterns.
- Picus: a verification tool combining static analysis and SMT solving in a loop, each supplying information the other cannot derive alone, to prove a circuit is well-constrained, find a counterexample, or report unknown.
Walkthrough
Why ZK bugs matter (0:01)
The lecture opens with examples of costly blockchain bugs, including a smart-contract exploit and a bug in the original Zcash protocol that allowed counterfeit coins to be minted. It frames formal methods, ranging from testing to abstract interpretation to full formal verification, as a spectrum trading manual effort for stronger correctness guarantees.
Abstract interpretation fundamentals (3:03)
The lecture explains how abstract interpretation reasons about programs by computing over-approximations of possible states, using interval abstraction as a running example, and shows how imprecise abstractions cause false positives or false negatives. It then applies this to a classic reentrancy attack pattern in smart contracts, showing how the vulnerability can be encoded as a pattern over external calls followed by storage updates.
Formal verification fundamentals (16:14)
The lecture introduces formal verification as checking code against an explicit specification, using a verification condition generator to turn code and specification into a logical formula that an SMT solver checks for validity. It notes the tradeoffs: stronger guarantees than static analysis, but a need for manual specifications and solvers that struggle with the non-linear, finite-field arithmetic common in ZK circuits.
A taxonomy of ZK circuit bugs (26:24)
Drawing on a large-scale study of open-source ZK projects, the lecture defines the equivalence property a circuit must satisfy and categorizes real bugs found in the wild: under-constrained output signals (such as an off-by-one loop bound that leaves an output array element unconstrained), unsafe component usage where a subcircuit's output is not constrained by its caller, and mismatches between constraints and witness computations, such as a modular-inverse gadget that fails to reject division by zero.
Detecting bugs with the Vanguard static analyzer (36:35)
The lecture describes representing a circuit as a circuit dependence graph and encoding bug patterns as queries over that graph, implemented in the Vanguard framework for Circom and Halo2. Run on 258 circuits from 17 open-source projects, the tool found 32 previously unknown vulnerabilities, most acknowledged by the projects, with under-constrained signals being the most common category.
Formally verifying circuits with Picus (54:49)
The lecture presents Picus, which combines static analysis (scalable but prone to false positives) with SMT solving (precise but hard to scale) in an iterative loop: each phase grows a set of signals proven well-constrained and passes progress to the other phase until the circuit is proven safe, proven under-constrained, or the result is inconclusive. Evaluated on 163 Circom circuits, Picus outperformed either technique alone, especially as circuit complexity grew.
Open problems (1:04:57)
The lecture closes on open challenges: specifying properties involving complex cryptographic primitives, building solvers that reason efficiently about finite fields, generating concrete counterexamples when verification fails, and extending these techniques beyond Circom and Halo2 to other proof systems.
Before you watch
- Familiarity with how ZK-SNARKs turn a program into a prover and verifier, from earlier lectures in this course.
- Basic exposure to smart-contract vulnerabilities such as reentrancy attacks is helpful for the abstract-interpretation examples.
- No prior formal-methods background is assumed; the lecture defines its terms as it goes.
Check your understanding
- What is the difference between an over-constrained bug and an under-constrained bug, and why is the latter more dangerous?
- Why do over-constrained bugs tend to get caught early, while under-constrained bugs can go undetected until exploited?
- How does a circuit dependence graph help detect an under-constrained output signal?
- Why does Picus combine static analysis with SMT solving instead of relying on just one technique?
- Why do existing SMT solvers struggle with verifying ZK circuit constraints compared to typical program verification tasks?
Chapters
- 0:00 Introduction to ZK Security
- 2:29 Formal methods overview
- 5:10 Abstract interpretation
- 9:07 Finding bugs with abstract
- 16:13 Formal verification
- 23:08 Verification tools and trade-offs
- 26:09 ZK circuit components
- 31:59 ZK bug taxonomy
- 37:08 Detecting bugs with static analysis
- 45:44 Vanguard framework
- 53:46 Formal verification of ZK circuits
- 57:54 Picas tool and evaluation
- 1:04:51 Conclusion and future directions
From the YouTube description
Yu Feng, Zero Knowledge Proofs MOOC
← Lecture 12: zkEVM Design, Optimization and Applications · Lecture 16: Hardware Acceleration of ZKP →
