Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
dbc387e
Author production wrapping adder in Clean with checked GF(2) export
kevaundray Oct 10, 2026
3436a54
Export and prove production 32-bit and carry-in adder families
kevaundray Oct 10, 2026
03a8032
WIP: BLAKE2s word operations and defined-wire lowering in Clean
kevaundray Oct 10, 2026
6874953
Author and prove the complete production RV shift circuit in Clean
kevaundray Oct 10, 2026
7f2393a
Merge remote-tracking branch 'upstream/riscv-exploration' into kw/cle…
kevaundray Oct 10, 2026
c091cef
WIP: BLAKE2s compression circuit in Clean, sound against RFC 7693
kevaundray Oct 10, 2026
5788748
Require each exact library object before stock kernel replay
kevaundray Oct 10, 2026
163e953
Skip mobile benchmarks on pull requests while circuits are in progress
kevaundray Oct 10, 2026
382f5e3
WIP: closed-form lowering of the Clean ripple adder
kevaundray Oct 10, 2026
0ef5159
WIP: closed-form lowering of every BLAKE2s word operation
kevaundray Oct 10, 2026
cb8c6e1
WIP: prove the exact lowered artifact of the Clean BLAKE2s compression
kevaundray Oct 10, 2026
0728332
WIP: export soundness, completeness and adder call theorems for BLAKE2s
kevaundray Oct 10, 2026
26d9b76
Merge remote-tracking branch 'origin/kw/clean-circuits-adder-20261010…
kevaundray Oct 10, 2026
1d724ee
Author the BLAKE2s compression in Clean and generate the production h…
kevaundray Oct 10, 2026
9c098af
Merge Clean-authored BLAKE2s compression circuits
kevaundray Oct 10, 2026
805e6d7
WIP: recursion base field K with a kernel-checked irreducibility proof
kevaundray Oct 10, 2026
b8bb21c
WIP: K as a Clean FiniteField with its machine word representation
kevaundray Oct 10, 2026
6362faf
WIP: recursion table forms over K and E with their semantics
kevaundray Oct 10, 2026
22bd15c
Prove the recursion tables' slot forms over K and E and generate them…
kevaundray Oct 10, 2026
7560912
Merge Clean-authored recursion field and table identities
kevaundray Oct 10, 2026
d2ceb99
State what the recursion builder's row patterns mean
kevaundray Oct 10, 2026
e077414
Join hash rows to their compression and pin the builder's row shapes …
kevaundray Oct 10, 2026
a94258e
Merge Clean recursion builder row identities
kevaundray Oct 10, 2026
a9d8097
Merge upstream duplex Fiat-Shamir transcript
kevaundray Oct 10, 2026
6870abb
Prove the recursion circuit's hash chains, Merkle paths, tree and sta…
kevaundray Oct 10, 2026
ab58ccd
Distinguish hash row views from buffered duplex challenges
kevaundray Oct 10, 2026
6c7957c
Write the recursion rows at slot indices generated from the names the…
kevaundray Oct 10, 2026
c05e465
Merge the duplex Fiat-Shamir base into kw/clean-hashrec-20261010
kevaundray Oct 10, 2026
ef3bc83
Prove the recursion rows replay the native Fiat-Shamir duplex
kevaundray Oct 10, 2026
d38d7f3
Merge Clean recursion hash, Merkle and duplex row models
kevaundray Oct 10, 2026
404c8fd
Prove the bus copy argument and that E is a field
kevaundray Oct 10, 2026
ee40747
Clarify mathematical duplex row model trust boundary
kevaundray Oct 10, 2026
aa34c36
Model the recursion circuit builder in Lean and check it builds the t…
kevaundray Oct 10, 2026
6eca235
Merge remote-tracking branch 'origin/kw/clean-circuits-adder-20261010…
kevaundray Oct 10, 2026
a024c12
Prove the recursion builder's method contracts, bound the duplex curs…
kevaundray Oct 10, 2026
a164656
Check the tracked circuits' bus links are each wire class's cycle
kevaundray Oct 10, 2026
c62c3e7
Author and prove complete load and store circuits in Clean
kevaundray Oct 10, 2026
b723cd0
Merge proved recursion builder and copy bus models
kevaundray Oct 10, 2026
3acf5fe
Merge upstream PCS overhaul and current verifier changes
kevaundray Oct 10, 2026
2db90c1
Use canonical native witness types and format memory circuit regressions
kevaundray Oct 10, 2026
5d9ea90
WIP: prove the CSA multiplier's plan and gates compute the product mo…
kevaundray Oct 10, 2026
5da8d25
Merge remote-tracking branch 'origin/kw/clean-circuits-adder-20261010…
kevaundray Oct 10, 2026
05becd2
Prove the bus theorems over Clean channels with integer multiplicities
kevaundray Oct 10, 2026
660ba70
Merge Clean integer-multiplicity bus and memory proofs
kevaundray Oct 10, 2026
076446b
Derive the memory theorem's clock hypothesis from the state walk, and…
kevaundray Oct 10, 2026
288a21d
Merge shared RAM and advice memory consistency corollaries
kevaundray Oct 10, 2026
fbc07f6
Prove the end-to-end bus theorem over an abstract row type with the c…
kevaundray Oct 10, 2026
499bd1c
Merge conditional lookup state and memory machine correctness
kevaundray Oct 11, 2026
178ab64
Author and prove the complete production ALU in Clean with integer bu…
kevaundray Oct 11, 2026
a529a96
Add the clock circuit's word-level reference
kevaundray Oct 11, 2026
6874c5f
Derive the clock facts from the clock circuit's reference and tie the…
kevaundray Oct 11, 2026
81d172a
Model the instruction tables' flushes, check them against the Rust ta…
kevaundray Oct 11, 2026
a936287
Compose the table flush model with the bus theorem for the instructio…
kevaundray Oct 11, 2026
8efee21
Write the clock circuit in Clean, parametric in the slot list, with i…
kevaundray Oct 11, 2026
e331b8c
Author the Ext clock extras in Clean and generate them into Ext::cloc…
kevaundray Oct 11, 2026
66d204e
WIP: Clean Mul, Mulh and Div circuits proved against RV64IM
kevaundray Oct 11, 2026
5198c07
Export the clock circuit for every production slot list, prove its ro…
kevaundray Oct 11, 2026
d63fae0
Remove the handwritten Ext clock extras now that the generated gate l…
kevaundray Oct 11, 2026
3d570f3
Merge the generated Ext clock extras and close the tables' bus theore…
kevaundray Oct 11, 2026
9d27553
Drop the test-only handwritten clock builder, check every table's clo…
kevaundray Oct 11, 2026
82a1f63
Merge Clean-authored clock circuits and checked table bus linkage
kevaundray Oct 11, 2026
16b6167
Generate Mul, Mulh and Div from Clean circuits proved against RV64IM;…
kevaundray Oct 11, 2026
1e644bf
Merge Clean ALU, clock and bus circuits; reuse BooleanAny and drop wo…
kevaundray Oct 11, 2026
be3dc5b
Document checked clock and table linkage with explicit trust boundaries
kevaundray Oct 11, 2026
2d38d6f
Merge Clean-authored RV64 multiply and divide circuits
kevaundray Oct 11, 2026
4efdd12
Record independently checked RV64 multiply and divide proof smoke
kevaundray Oct 11, 2026
07549dc
Build large multiplier proofs before concurrent circuit exports
kevaundray Oct 11, 2026
ffb68c7
Author and prove EXT field identities in Clean for production tables
kevaundray Oct 11, 2026
484bc01
Merge remote-tracking branch 'upstream/riscv-exploration' into kw/cle…
kevaundray Oct 11, 2026
afd7952
Model the spongefish transcript of #607 in Rec.Duplex: the session se…
kevaundray Oct 11, 2026
e3b8a30
Migrate circuit regressions to spongefish transcripts
kevaundray Oct 11, 2026
02bb568
Audit the deployed spongefish nonce trial
kevaundray Oct 11, 2026
1d1ef6b
Export instruction table layouts and bus plans from Clean
kevaundray Oct 11, 2026
7af129d
Merge commit 'b014f1a3' into kw/clean-circuits-shift-20261010
kevaundray Oct 11, 2026
e6dec79
Trace #613's Builder::block and Builder::d_to_halves and model them: …
kevaundray Oct 11, 2026
d677770
Format generated Rust with the workspace edition
kevaundray Oct 11, 2026
dc57267
Document the tracing-gated table dump checker
kevaundray Oct 11, 2026
25db6ab
Merge commit 'b094d006' into kw/clean-merge-ready-20261012
kevaundray Oct 11, 2026
3ec6048
Replay positional claim-set and tier circuits against Clean
kevaundray Oct 11, 2026
7cca49c
Re-enable mobile CI and document positional recursion replay
kevaundray Oct 12, 2026
7f785cf
Merge riscv-exploration (#627, #609) into kw/clean-circuits-adder-202…
kevaundray Oct 12, 2026
e74fdd8
circuits: absent places at every claim-set node replay with the exist…
kevaundray Oct 12, 2026
f8ecd41
Merge riscv-exploration (#630) into kw/clean-circuits-adder-20261010
kevaundray Oct 12, 2026
793fbc1
circuits: the reordered verifiers replay with the existing model; LVM…
kevaundray Oct 12, 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
80 changes: 80 additions & 0 deletions .github/workflows/clean-circuits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Clean circuits

on:
pull_request:
paths:
- verification/circuits/**
- crates/flock/src/clean.rs
- crates/leanvm_core/src/rec/clean.rs
- crates/leanvm_core/src/tables/clean.rs
- .github/workflows/clean-circuits.yml
- rustfmt.toml
push:
branches: [riscv-exploration, 'kw/clean-circuits-*']
paths:
- verification/circuits/**
- crates/flock/src/clean.rs
- crates/leanvm_core/src/rec/clean.rs
- crates/leanvm_core/src/tables/clean.rs
- .github/workflows/clean-circuits.yml
- rustfmt.toml
workflow_dispatch:

permissions:
contents: read

jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.97.0
components: rustfmt
- uses: leanprover/lean-action@v1
with:
lake-package-directory: verification/circuits
build: 'false'
test: 'false'
lint: 'false'
use-mathlib-cache: 'false'
- name: Check source, lowering, artifact, and theorem axioms
working-directory: verification/circuits
run: |
lake exe cache get Mathlib.Tactic Mathlib.FieldTheory.Finite.Extension Mathlib.RingTheory.AdjoinRoot Mathlib.Algebra.Polynomial.SpecificDegree Mathlib.Algebra.MvPolynomial.Eval Mathlib.Algebra.Polynomial.Roots
# Keep the kernel-reduced multiplier plan and its export out of concurrent heavy builds.
lake build LeanVMCircuits.Multiply.Checked
lake build LeanVMCircuits.MulDivExport
lake build
lake env lean Audit.lean
test -f .lake/build/lib/lean/LeanVMCircuits.olean
# Stock leanchecker replays every matching module concurrently.
# The root has no declarations; reject any future declaration there.
if grep -nEv '^[[:space:]]*(module|public import LeanVMCircuits[.][A-Za-z0-9_.]+)?[[:space:]]*$' LeanVMCircuits.lean; then
echo "::error::LeanVMCircuits.lean must contain only its module header and public imports"
exit 1
fi
shopt -s globstar nullglob
sources=(LeanVMCircuits/**/*.lean)
if (( ${#sources[@]} == 0 )); then
echo "::error::No library source modules found for kernel checking"
exit 1
fi
for source in "${sources[@]}"; do
module="${source%.lean}"
if [[ ! -f ".lake/build/lib/lean/${module}.olean" ]]; then
echo "::error::Missing compiled library module ${module}"
exit 1
fi
lake env leanchecker "${module//\//.}"
done
- name: Check deterministic production export
working-directory: verification/circuits
run: |
lake env lean --run Generate.lean /tmp/clean-generated.rs
diff -u ../../crates/flock/src/clean.rs /tmp/clean-generated.rs
lake env lean --run GenerateRec.lean /tmp/rec-generated.rs
diff -u ../../crates/leanvm_core/src/rec/clean.rs /tmp/rec-generated.rs
lake env lean --run GenerateTables.lean /tmp/tables-generated.rs
diff -u ../../crates/leanvm_core/src/tables/clean.rs /tmp/tables-generated.rs
47 changes: 47 additions & 0 deletions .github/workflows/rec-builder-model.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Recursion builder model

# The Lean model of the recursion circuit's builder (verification/circuits/LeanVMCircuits/Rec/Builder.lean)
# replays the builder calls that make each tracked aggregation tree's circuits and checks it builds the same
# circuits. Any Rust change can change what the verifier in rows calls, so every one runs it.

on:
pull_request:
paths:
- crates/**
- bins/leanvm/**
- programs/**
- Cargo.toml
- Cargo.lock
- verification/circuits/**
- .github/workflows/rec-builder-model.yml
push:
branches: [riscv-exploration, 'kw/clean-circuits-*']
workflow_dispatch:

permissions:
contents: read

jobs:
replay:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.97.0
- uses: leanprover/lean-action@v1
with:
lake-package-directory: verification/circuits
build: 'false'
test: 'false'
lint: 'false'
use-mathlib-cache: 'false'
- name: Dump the tracked circuits and instruction table bindings
run: cargo run --release -p leanvm-cli --features circuits -- circuits /tmp/circuits
- name: Replay the builders and check native table bindings
working-directory: verification/circuits
run: |
lake exe cache get Mathlib.Tactic Mathlib.FieldTheory.Finite.Extension Mathlib.RingTheory.AdjoinRoot Mathlib.Algebra.Polynomial.SpecificDegree Mathlib.Algebra.MvPolynomial.Eval Mathlib.Algebra.Polynomial.Roots
lake build checkrec checkflushes
lake exe checkrec /tmp/circuits/*.txt
lake exe checkflushes /tmp/circuits/table-flushes.dump
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ target
__pycache__/
formal/
conformance/act4/elf/
verification/circuits/.lake/
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
A RISC-V (rv64im) virtual machine and the SNARK that proves its execution. Proofs are not zero knowledge. Every rv64im instruction is proven, plus custom ones: the BLAKE2s compression (`blake2s rs1, rs2`) and the multiplication in the extension field `E = GF(2^192)` (`extmul`, `extmac`, `extmulk`, `extmack`, each `rd, rs1, rs2` with every register an address). A program is a guest's ELF file (`programs/<name>/guest`, Rust built for `riscv64im-unknown-none-elf`, loaded by `rv::Guest::from_elf`) or a text assembled by hand with `leanvm::asm`; a run is proven on an advice (a region of memory the prover fills), and its statement is the program's digest and the output (`a0..a3` at `exit`); there is no public input. One run is one proof: a run whose witness exceeds one commitment (`pcs::MAX_MU`) is refused up front with `ProveError::TooLong` (re-exported as `leanvm::ProveError::TooLong`), continuations being unimplemented.

- `doc/leanvm/` is the LaTeX project describing the machine ISA and the snark that proves it. Its root is `doc/leanvm/main.tex`; build it with `cd doc/leanvm && latexmk -pdf main.tex`, which writes to the gitignored `doc/leanvm/.build/`. Sections live in `doc/leanvm/body/`, numbered `01`..`08` plus the lettered annexes `a` (ring switching), `b` (the PCS), `c` (Flock), `d` (novel basis and additive NTT), and `e` (recursion), and every symbol is defined once in `doc/leanvm/preamble/macros.tex`. If latexmk fails oddly (a bibtex error, or a missing `main.log`) right after inputs are renamed or `refs.bib` is edited, remove `doc/leanvm/.build` and rerun; it has not reproduced on unchanged inputs. **Drafting one section:** each section file carries a `% !TeX root` comment pointing at its generated driver in `doc/leanvm/drafts/`, so the LaTeX build key (`F5`, or the extension's `cmd+alt+b`) compiles only that section, numbered as in the full document and with cross-references and citations resolved against `.build/main.aux`; in `main.tex` the same key builds everything. Run `doc/leanvm/make-drafts.sh` after adding, renaming or renumbering a section.
- The one hash function is BLAKE2s, in `primitives::hash`: scalar, streaming, and a lane-transposed batched form for the PCS Merkle tree. The VM's compression instruction is the generic gate-list circuit in `rv::semantics::hash` (`Class::Hash.circuit()`), proven like every other class; its witness is word arithmetic in `rv::semantics::hash` (exported as `rv::circuits::blake2s_witness`), pinned to the walk of the gate list by a test.
- The one hash function is BLAKE2s, in `primitives::hash`: scalar, streaming, and a lane-transposed batched form for the PCS Merkle tree. The VM's compression instruction is the generic gate-list circuit in `rv::semantics::hash` (`Class::Hash.circuit()`), proven like every other class; its gate list is `flock::clean::blake2s`, generated from the Clean circuit `LeanVMCircuits.Blake2s.Compress` (proven to be RFC 7693's compression, `verification/circuits/README.md`), and its witness is word arithmetic in `rv::semantics::hash` (exported as `rv::circuits::blake2s_witness`), pinned to the walk of the gate list by a test.

## Layout

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ leanVM is designed for security:

**warning**: Formal verification is [in progress](https://github.com/Verified-zkEVM/leanerVM). leanVM is not (yet) production ready.

The production wrapping and carry adders and the complete RV shift circuit are authored and proved in [Clean](./verification/circuits/README.md), then exported as checked-in Rust gates. Real instruction and BLAKE2s circuits consume the adder family; real RV shifts consume the complete shift artifact. The proofs cover arbitrary-witness Boolean constraint soundness and honest completeness, with the shift flag domain made explicit. The Rust interpretation and the separate cryptographic composition retain the trust boundaries documented there.

## work in progress

Expect leanVM to change significantly:
Expand Down
2 changes: 2 additions & 0 deletions bins/leanvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ workspace = true
[features]
# The system allocator in place of `bench::Jemalloc`, still under `bench::Counting`, for heap profilers that hook `malloc` (heaptrack). Slower: for memory questions only.
system-alloc = []
# `circuits`: the tracked trees' recursion circuits with the builder calls that make them, for `checkrec`.
circuits = ["leanvm/circuit-trace"]

[dependencies]
leanvm.workspace = true
Expand Down
13 changes: 11 additions & 2 deletions bins/leanvm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ enum Command {
#[arg(long, conflicts_with = "cycles_only")]
only: Option<String>,
},
/// Write the tracked aggregation circuits and instruction table bindings (with `--features circuits`) for the Lean checkers.
Circuits {
/// The directory to write them to.
out: PathBuf,
},
}

fn parse_rate(log_inv_rate: &str) -> Result<Rate, Box<dyn Error + Send + Sync>> {
Expand Down Expand Up @@ -179,15 +184,15 @@ fn refuse(what: Arguments) -> ! {
fn main() {
let cli = Cli::parse();
let fixed_threads = match &cli.command {
Command::Bench { cycles_only: true, .. } => false,
Command::Bench { cycles_only: true, .. } | Command::Circuits { .. } => false,
Command::Bench { only, .. } => only.as_deref().is_some_and(|name| name.ends_with("-16thread")),
_ => std::env::var_os("LEANVM_NUM_THREADS").is_none(),
};
if fixed_threads {
parallel::init_with_threads(NonZeroUsize::new(16).unwrap())
.unwrap_or_else(|actual| refuse(format_args!("cannot configure 16 benchmark threads: {actual:?}")));
}
if !matches!(cli.command, Command::Bench { .. }) || fixed_threads {
if !matches!(cli.command, Command::Bench { .. } | Command::Circuits { .. }) || fixed_threads {
let topology = parallel::topology();
eprintln!(
"Benchmark pool: {} threads ({} performance, {} efficiency)",
Expand Down Expand Up @@ -232,5 +237,9 @@ fn main() {
&prover,
plan,
),
#[cfg(feature = "circuits")]
Command::Circuits { out } => tracked::dump(&out, leaf_prover.rate(), prover.rate()),
#[cfg(not(feature = "circuits"))]
Command::Circuits { .. } => refuse(format_args!("`circuits` needs `--features circuits`")),
}
}
56 changes: 56 additions & 0 deletions bins/leanvm/src/tracked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use crate::refuse;
use crate::workload::{Items, Workload};
use bench::{Heap, Metric, Plan, Timing, bencher_json};
#[cfg(feature = "circuits")]
use leanvm::aggregate::{Capacity, ClaimShape, Tier};
use leanvm::aggregate::{CircuitStats, Kind, Leaf, LeafShape, Tree, TreeError, TreeProof, TreeShape};
use leanvm::{Output, ProvenRun, Prover, Rate, Stats};
use primitives::pretty_integer;
Expand Down Expand Up @@ -340,6 +342,60 @@ fn circuits(tree: &Aggregation, leaf_rate: Rate, rate: Rate) -> [(Kind, CircuitS
Kind::ALL.map(|kind| (kind, built.stats(kind)))
}

/// Write every tracked output-tree circuit and claim-set tier position, plus native instruction table bindings.
///
/// CI's `rec-builder-model` workflow replays the recorded builders and checks the table slot and clock-port bindings against their Lean sources.
#[cfg(feature = "circuits")]
pub fn dump(out: &Path, leaf_rate: Rate, rate: Rate) {
let proven = [
Aggregation::new("aggregate-leanxmss-100-2to1", Workload::leanxmss(100), 2, 2),
Aggregation::new("aggregate-leanxmss-100-4to1", Workload::leanxmss(100), 4, 4),
];
std::fs::create_dir_all(out).unwrap_or_else(|e| refuse(format_args!("{}: {e}", out.display())));
let path = out.join("table-flushes.dump");
std::fs::write(&path, leanvm::table_flushes()).unwrap_or_else(|e| refuse(format_args!("{}: {e}", path.display())));
for tree in counted_trees().into_iter().chain(proven) {
let shape = LeafShape::measured(&tree.leaf.measure(), leaf_rate);
let built = tree.tree(shape, rate);
for position in 0..built.circuit_count() {
let kind = if position == 0 { Kind::First } else { Kind::Node };
let path = out.join(format!("{}.txt", tree.node(kind).0));
std::fs::write(&path, built.circuit_dump(position))
.unwrap_or_else(|e| refuse(format_args!("{}: {e}", path.display())));
}
}
let leaf = Workload::leanxmss(100);
let measured = LeafShape::measured(&leaf.measure(), leaf_rate);
let capacity = Capacity { claims: 12, groups: 4 };
for (name, claims) in [
("claimset-single-tier", ClaimShape::new(capacity)),
(
"claimset-two-tier",
ClaimShape {
capacity,
small: Some(Tier {
arity: 2,
capacity: Capacity { claims: 5, groups: 2 },
}),
},
),
] {
let shape = TreeShape {
leaf: measured,
arity_0: 1,
arity: 3,
rate,
claims: Some(claims),
};
let built = Tree::new(&leaf.program, shape).unwrap_or_else(|e| refuse(format_args!("{name}: {e}")));
for position in 0..built.circuit_count() {
let path = out.join(format!("{name}-circuit-{position}.txt"));
std::fs::write(&path, built.circuit_dump(position))
.unwrap_or_else(|e| refuse(format_args!("{}: {e}", path.display())));
}
}
}

/// `rows` (the circuit's own), `proven-rows` (the tables' heights, powers of two) and
/// `committed` (the witness words): exact, the same on every machine.
fn circuit_counts(stats: &CircuitStats) -> Vec<(&'static str, Metric)> {
Expand Down
Loading
Loading