LLEQ is an equivalence verifier for zero-knowledge circuits in LLZK IR, based on Zequal. LLEQ consumes LLZK IR, not high-level circuit languages directly. If your circuit is written in another language, first lower it to LLZK using an appropriate frontend (see Frontends), then run LLEQ on the resulting .llzk file
LLEQ can be built with Nix, or manually with CMake.
This repository is configured with a Nix flakes environment. The flake was built using Nix version 2.31; older/newer versions may or may not.
Once you have a Nix installation (See Installation), you can run nix build .#lleq from the repository root, or nix develop to enter a developer shell. Inside the dev shell, the built lleq binary is added to PATH for
convenience.
For a manual build, you will need the following:
- LLVM
- MLIR
- LLZK
- a C++23-capable compiler
cvc5z3- Python 3
Both LLZK and MLIR must be built and installed in a way that CMake can discover with find_package(LLZK) (See project-llzk/llzk-lib for instructions on how to build LLVM/MLIR/LLZK).
At runtime, LLEQ expects both cvc5 and z3 to be available on PATH. Solver
discovery can also be overridden by setting the following environment variables:
LLEQ_CVC5=/path/to/cvc5
LLEQ_Z3=/path/to/z3
Configure:
cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=ReleaseIf LLVM, MLIR, or LLZK are installed in nonstandard locations, point CMake at
their package configuration directories with -DLLVM_DIR=...,
-DMLIR_DIR=..., and -DLLZK_DIR=....
Build:
cmake --build buildTo run the LIT regression tests after a manual build:
cmake --build build --target checkThe Nix package build also runs the test suite.
LLEQ operates on an input .llzk file and a selected struct:
lleq <subcommand> --struct <StructName> [options] input.llzkAvailable subcommands:
dump-store: print the symbolic store constructed for the selected structverify: prove equivalence of signal membersdump-smt: print the SMT-LIB query generated for deductive verificationwp: infer loop invariants and emit weakest-precondition-style verification conditions as SMT-LIB
Common options:
--struct <name>: required; selects the struct to analyze--field <field-name>: selects the prime field when it cannot be inferred--flatten: run LLZK flattening and array-to-scalar lowering before analysis--enable-store: available forverifyanddump-smt; adds extra symbolic-store facts
lleq verify reports one line per signal member:
+ @Struct::member: proven equivalent- @Struct::member: proven inequivalent* @Struct::member: unknown
For inequivalent members, LLEQ also prints a witness/constraint counterexample model.
Note that lleq verify does not perform loop invariant inference. To verify a struct containing loops or arrays, pass the --flatten argument to first unroll and scalarize the struct.
lleq wp emits an SMT-LIB query that can be sent directly to an SMT solver to
check the verification condition and, when it fails, obtain a counterexample.
For example:
lleq wp --struct DecomposeProduct_1 --field babybear examples/circom-examples/DecomposeProduct.llzk | z3 -inSee Examples
Analysis of structs with multidimensional arrays or subcomponent calls is not currently well-supported. Verification may also fail for structs where LLZK's product alignment produces poor alignments.