Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
a310fff
Added cvc5 dependency
May 26, 2026
203bed2
Test run of cvc5 expression building
May 26, 2026
e6f8e1c
lmao
May 27, 2026
d02582c
First pass of WP calculator is done, but memory issues
May 27, 2026
0b6e96c
oops
May 28, 2026
9f9508f
ugh
May 28, 2026
3f42d7b
Trying without StringMap
May 28, 2026
3040840
Fixed stupid lifetimes issue
May 28, 2026
1e88de7
fixed memory errors, and (a -> (b -> c)) to (a && b) -> c
May 28, 2026
ce20c74
Reducing mod p now, still need to fix field inference
May 28, 2026
3924fe6
Refactor with TermBuilder
May 28, 2026
a930ab8
Bump LLZK, clean up field detection
May 28, 2026
a907c77
update comment
May 29, 2026
6bfbcd2
Merge remote-tracking branch 'origin' into raghav/weakest-precondition
May 29, 2026
38451d3
Updated tests for optimized SMT encoding
Jun 1, 2026
30f63a1
Bump LLZK
Jun 1, 2026
f0ac14b
Don't spuriously require field
Jun 1, 2026
9de175e
Rename
Jun 1, 2026
a7e98a0
Properly handle WP for arrays
Jun 2, 2026
9ca279e
Made array flattening optional
Jun 2, 2026
ab4d657
marked regression
Jun 2, 2026
47ca8f5
Emitting extra declarations, added some TODOs
Jun 2, 2026
9d74420
Array equality works after adding bounds
Jun 4, 2026
54ea022
Some refactoring, whatever
Jun 4, 2026
88786a9
Emit extra [0, p-1] bounds
Jun 4, 2026
3cc4806
Add readwrite array test
Jun 8, 2026
aef14fe
Really stupid handling of subcomponents, not emitting extra uninterpr…
Jun 8, 2026
f605017
Always doing write-once semantics for signal arrays, still need to ma…
Jun 8, 2026
e051c39
Some better subcomponent support in WP
Jun 11, 2026
7dec5d4
Fixed testcases
Jun 12, 2026
84cf9cd
syntax error in test I guess
Jun 12, 2026
b42f2c1
Subcomponent store
raghav198 Jun 12, 2026
5be597b
Loop invariants (#43)
raghav198 Jun 24, 2026
3170dd3
Raghav/public release (#50)
raghav198 Jun 25, 2026
75574f7
Revert "Raghav/public release (#50)"
Jun 25, 2026
6f6d829
Raghav/public release (#55)
raghav198 Jun 30, 2026
e44c79b
Correctly read from a subcomponent member
Jun 30, 2026
6782c4c
Correctly read from a subcomponent member
Jun 30, 2026
671df15
Implemented solver portfolio
Jul 1, 2026
d9c8227
bn128
Jul 2, 2026
02e6765
git module
Jul 2, 2026
12180d3
Fixed portfolio issues, added benchmark importing
Jul 2, 2026
933157c
Remove hardcoded benchmarks
Jul 2, 2026
fe1dbdc
Remove unused file
Jul 7, 2026
a282a5c
Documentation
Jul 7, 2026
7b02d16
Veridise Inc -> Project LLZK
Jul 7, 2026
201a78c
Comments
Jul 7, 2026
30f3f55
Comments, got rid of missing file from cmake
Jul 7, 2026
81acba7
Code review comments
Jul 7, 2026
d910d3d
Veridise Inc -> Project LLZK
Jul 7, 2026
9ae3cdb
Build instructions clearer
Jul 7, 2026
150fba3
Reword
Jul 7, 2026
9139dda
Reword
Jul 7, 2026
85c12a5
Veridise Inc -> Project LLZK
Jul 7, 2026
4da703a
Updated report syntax
Jul 7, 2026
82dbeb7
Updated report syntax
Jul 7, 2026
4c48099
Bump LLZK
Jul 8, 2026
130f29a
default to release build
Jul 8, 2026
19463dc
Code review comments
Jul 8, 2026
1fb7557
Code review comments
Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "circom-benchmarks"]
path = circom-benchmarks
url = git@github.com:project-llzk/circom-benchmarks
125 changes: 123 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,123 @@
# LLZK-Verifier
Porting [ZEQUAL](https://www.cs.utexas.edu/~isil/zequal.pdf) to LLZK
# LLEQ

LLEQ is an equivalence verifier for zero-knowledge circuits in LLZK IR, based on [Zequal](https://veridise.com/wp-content/uploads/2025/08/zequal.pdf). 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](https://github.com/project-llzk/.github/blob/main/profile/README.md)), then run LLEQ on the resulting `.llzk` file

## Quick Links

- [Build](#build)
- [Usage](#usage)
- [Examples](#examples)
- [Capabilities and Limitations](#capabilities-and-limitations)

## Build

LLEQ can be built with Nix, or manually with CMake.

### Nix (Recommended)

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](https://nix.dev/manual/nix/2.31/installation/index.html)), 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.

### Manual

For a manual build, you will need the following:

- LLVM
- MLIR
- LLZK
- a C++23-capable compiler
- `cvc5`
- `z3`
- 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](https://github.com/project-llzk/llzk-lib/blob/main/doc/doxygen/01_setup.md#manual-build-setup) 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
```

#### Build with CMake

Configure:

```bash
cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release
```

If 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:

```bash
cmake --build build
```

### Run the Test Suite

To run the LIT regression tests after a manual build:

```bash
cmake --build build --target check
```

The Nix package build also runs the test suite.

## Usage

LLEQ operates on an input `.llzk` file and a selected struct:

```bash
lleq <subcommand> --struct <StructName> [options] input.llzk
```

Available subcommands:

- `dump-store`: print the symbolic store constructed for the selected struct
- `verify`: prove equivalence of signal members
- `dump-smt`: print the SMT-LIB query generated for deductive verification
- `wp`: 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 for `verify` and `dump-smt`; adds extra symbolic-store facts

### Verify Output

`lleq verify` reports one line per signal member:

- `+ @Struct::member`: proven equivalent
- `- @Struct::member`: proven inequivalent
- `* @Struct::member`: unknown
Comment thread
iangneal marked this conversation as resolved.

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.

### Weakest-Precondition Queries

`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:

```bash
lleq wp --struct DecomposeProduct_1 --field babybear examples/circom-examples/DecomposeProduct.llzk | z3 -in
```

## Examples

See [Examples](examples/README.md)

## Capabilities and Limitations

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.
1 change: 1 addition & 0 deletions circom-benchmarks
Submodule circom-benchmarks added at f6b9cb
54 changes: 54 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Examples

This directory collects example circuits and benchmark artifacts used for LLEQ
development, demos, and evaluation.

## `circom-examples`

To import and generate benchmarks from [`project-llzk/circom-benchmarks`](https://github.com/project-llzk/circom-benchmarks), run:

```bash
python3 scripts/import_circom_demo_examples.py --circom-frontend <circom> --llzk <llzk-opt>
```

Each imported benchmark is lowered to LLZK and then normalized with the
following pass pipeline before being checked into this repository:

```text
--llzk-while-to-for
--llzk-compute-constrain-to-product=root-struct=<RootStruct>
--llzk-fuse-product-loops
--canonicalize
```

Here, `<RootStruct>` is the benchmark's `llzk.main` root struct. The resulting
normalized `.llzk` files are stored flat in [`circom-examples`](./circom-examples).

The subdirectory also includes `verification_results.csv`, which records LLEQ
verification outcomes for the imported set.

## Collecting Verification Data

Run:

```bash
python3 scripts/collect_circom_demo_results.py --lleq-bin <lleq> --z3-bin <z3>
```

For each imported benchmark, the script currently runs:

```text
lleq wp --struct <RootStruct> | z3 -in
```

Each run uses a 120-second timeout and reports one of:

- `verified`
- `counterexample`
- `partial`
- `timeout`
- `error`

`wp` is classified as `partial` when z3 returns `unknown`.

Results are saved in `examples/verification_results.csv`.
12 changes: 6 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@

# Default lleq build uses the default compiler for the system (usually gcc for Linux and clang for Macos)
lleq = (final.callPackage ./nix/lleq.nix { clang = final.clang_20; llzk = final.llzk; mlir = final.mlir; }).overrideAttrs(attr: {
cmakeBuildType = "RelWithDebInfo";
});
lleqDebug = (final.callPackage ./nix/lleq.nix { clang = final.clang_20; llzk = final.llzk-debug; mlir = final.mlir-debug; }).overrideAttrs(attr: {
cmakeBuildType = "Debug";
});
# Build in release with symbols mode with a particular compiler and sanitizers enabled.
Expand Down
23 changes: 18 additions & 5 deletions include/Analysis/ScalarSymbolAnalysis.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand All @@ -20,6 +20,7 @@
#include <mlir/Analysis/DataFlowFramework.h>
#include <mlir/Dialect/SCF/IR/SCF.h>
#include <mlir/IR/Value.h>
#include <mlir/Interfaces/CallInterfaces.h>

namespace lleq {

Expand Down Expand Up @@ -135,6 +136,11 @@ class ScalarSymbolAnalysis
mlir::LogicalResult
visitOperation(mlir::Operation *op, llvm::ArrayRef<const Lattice *> operands,
llvm::ArrayRef<Lattice *> results) override;

void visitExternalCall(mlir::CallOpInterface call,
llvm::ArrayRef<const Lattice *> arguments,
llvm::ArrayRef<Lattice *> results) override;

void setToEntryState(Lattice *lattice) override {
lattice->getValue().initPool(&pool.get());
mlir::Value anchor = lattice->getAnchor();
Expand All @@ -143,19 +149,21 @@ class ScalarSymbolAnalysis
// since all the symbolic expressions are relative to these
if (llvm::isa<llzk::function::FuncDefOp>(
blockArg.getOwner()->getParentOp())) {
propagateIfChanged(
return propagateIfChanged(
lattice, lattice->join(pool.get().index(lattice->getAnchor(), {})));
return;
}
// Apparently setToEntryState gets called on loop induction vars too, but
// these should just be set to Unknown because `i` and `i + step` will
// never antiunify to anything interesting
if (llvm::isa<mlir::scf::ForOp>(blockArg.getOwner()->getParentOp())) {
propagateIfChanged(lattice, lattice->join(pool.get().fresh_unknown()));
return propagateIfChanged(lattice,
lattice->join(pool.get().fresh_unknown()));
}
}

// If nothing else is known, a default of `uninitialized` is fine
propagateIfChanged(lattice, lattice->join(pool.get().uninitialized()));
return propagateIfChanged(lattice,
lattice->join(pool.get().uninitialized()));
}
};

Expand All @@ -175,4 +183,9 @@ inline bool sourceMatchesOp(mlir::Operation *op, Signal::Source source) {
(source == Signal::Source::Witness && isWitnessOp(op));
}

inline bool isSubcmpRead(llzk::component::MemberReadOp read) {
return read.getComponent().getType() !=
read->getParentOfType<llzk::component::StructDefOp>().getType();
}

} // namespace lleq
2 changes: 1 addition & 1 deletion include/Analysis/Store.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Analysis/SymbolExpr.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2025 Veridise Inc.
* Copyright 2025 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Analysis/SymbolImpls.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
5 changes: 3 additions & 2 deletions include/Analysis/SymbolicStore.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2025 Veridise Inc.
* Copyright 2025 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand All @@ -19,6 +19,7 @@
#include <llzk/Dialect/Struct/IR/Ops.h>
#include <mlir/Analysis/DataFlow/ConstantPropagationAnalysis.h>
#include <mlir/Analysis/DataFlow/DeadCodeAnalysis.h>
#include <mlir/Analysis/DataFlowFramework.h>
#include <mlir/IR/Block.h>
#include <mlir/IR/Operation.h>
#include <mlir/IR/Value.h>
Expand All @@ -31,7 +32,7 @@ namespace lleq {
/// prove equivalence between pairs of witness/constraint signals.
class SymbolicStore {
std::unique_ptr<SymbolPool> pool = std::make_unique<SymbolPool>();
mlir::DataFlowSolver solver;
mlir::DataFlowSolver solver{mlir::DataFlowConfig{}.setInterprocedural(false)};

llzk::component::StructDefOp component;
SignalStore *signalStore;
Expand Down
2 changes: 1 addition & 1 deletion include/Analysis/SymbolicStoreAnalysis.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Analysis/Unification.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2025 Veridise Inc.
* Copyright 2025 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Transforms/LLEQIfToIfElse.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Transforms/LLEQWhileToFor.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Verification/DeductiveVerifier.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Verification/FixpointVerifier.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Verification/SMTLIBEquivalenceEmitter.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
2 changes: 1 addition & 1 deletion include/Verification/SymbolicVerifier.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2026 Veridise Inc.
* Copyright 2026 Project LLZK
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down
Loading
Loading