This is the artifact for the paper ZEE200: Zero Knowledge for Everything and Everyone @ 200 KHz to be presented on ACM CCS 2026.
eprint link: https://eprint.iacr.org/2026/828.pdf
ZEE200 is an instruction-based zero-knowledge virtual machine built on a tight ZK CPU and VOLE-based ZK. Proof costs scale with the number of executed instructions, not the entire program size, and memory is modeled via a black-box ZK RAM (IZKRAM).
This repository contains:
- The ZEE200 backend (implemented in this repo).
- A ZEE baseline implementation (
zee-processor). - An LLVM/Newlib frontend toolchain (via Docker) for compiling C → ZEE assembly → ZK programs.
- Benchmarks and scripts to reproduce the evaluation from the paper.
The original paper describes the design and evaluation.
Example workloads and scripts live under benchmarks/ and scripts/.
The CCS artifact appendix source is available at docs/artifact_appendix.tex.
- CMake (>=3.10), a C++17 compiler, and
make. - Docker (for the LLVM/Newlib toolchain; the zkVM itself does not require Docker).
On a fresh Ubuntu-like system, run:
./setup.shThis installs pinned EMP toolkit dependencies and other libraries used by the ZEE baseline and by some development tooling. It uses sudo to install system packages and EMP libraries.
From the repo root:
mkdir -p build
cd build
cmake ..
make -jThis builds test_zkvm_generic_asm_test under build/bin/.
To build the ZEE reference prover/verifier (ZKMachine) used by the ZEE scripts:
cd zee-processor
cmake .
make -jThis produces a zee-processor/ZKMachine binary, which is what the scripts/zee/run_*.sh helpers expect.
Some experiments in the paper assume a bounded bandwidth and non-zero latency between prover and verifier. The helper script set_net.sh configures this using Linux tc on a single machine.
- Script location:
set_net.sh(repo root) - Usage:
IF=<iface> ./set_net.sh <rate_mbit:100|500|1000> <one_way_delay_ms>- By default
IF=lo(loopback). - The delay is one-way; RTT is roughly
2 × one_way_delay_ms.
- Example: approximate 1 Gbit/s bandwidth and 2 ms RTT on loopback:
./set_net.sh 1000 1
- To clear the shaping rules:
sudo tc qdisc del dev $IF root(replace$IFwith your interface, e.g.,lo)
The frontend lives under toolchain/ and is intended to run inside a Docker image (zee-toolchain:20.04) so it uses a consistent LLVM 10 / newlib environment. On first use, the Docker scripts will build the zee-toolchain:20.04 image if it is missing; this can take a while, but subsequent runs reuse the image and are much faster.
From the repo root:
# 1) Compile C to ZEE assembly (.s) via Docker
# This will build the zee-toolchain:20.04 image automatically if missing.
./toolchain/compile_c_docker.sh \
benchmarks/fib_stdin/fib_stdin.c \
benchmarks/fib_stdin/fib_stdin.s
# 2) Convert ZEE .s to a ZK program (.zk) for zee-processor
./toolchain/s_to_zk_docker.sh \
benchmarks/fib_stdin/fib_stdin.s \
benchmarks/fib_stdin/fib_stdin.zkYou can reuse these commands for other C benchmarks under benchmarks/ by adjusting the input and output paths. The first build of the zee-toolchain:20.04 image can consume a significant amount of disk space (on the order of ~16 GB for the image and build artefacts), so make sure you have enough free space.
If Docker requires sudo on your system:
-
The simplest option is to prefix the commands with
sudo. -
On Linux with Docker Engine, you can instead allow your user to run Docker without
sudo:# Create the docker group if it does not exist getent group docker >/dev/null || sudo groupadd docker # Add the current user to the docker group sudo usermod -aG docker "$USER" # Apply the new group membership (or log out and log back in) newgrp docker
Membership in the
dockergroup effectively grants root-equivalent privileges on the host. -
On macOS (Docker Desktop),
usermod/newgrpand thedockerUnix group are not used; just install and run Docker Desktop and ensuredockeris on yourPATH.
The scripts/ directory contains small, explicit scripts for each benchmark and backend. All of them are meant to be run from the repo root.
For artifact evaluation, run the prover and verifier in two separate terminals.
Start the prover first, then start the matching verifier. A successful run
prints tight zk cpu test passed on both sides. For benchmarks that read stdin
(fib_stdin and sha256), enter the inputs manually in the prover terminal;
do not pipe stdin into the prover.
Each benchmark has:
- A top-level
run_*_prover.shandrun_*_verifier.shthat select the backend (zee200orzee) and print any relevant input instructions. - Backend-specific implementations under:
scripts/zkvm/for the ZEE200 backend.scripts/zee/for the ZEE baseline.
Examples:
# ZEE200 backend
./scripts/run_sed_prover.sh zee200
./scripts/run_sed_verifier.sh zee200
# ZEE baseline
./scripts/run_sed_prover.sh zee
./scripts/run_sed_verifier.sh zeeThese use the benchmarks/fib_stdin/ program, which reads from stdin.
# Prover (ZEE200)
./scripts/run_fib_prover.sh zee200
# Input pattern in prover terminal:
# 1) Dummy integer (e.g., 12345)
# 2) Fibonacci index n (e.g., 20 or 23)
# Verifier (ZEE200)
./scripts/run_fib_verifier.sh zee200Swap zee200 → zee to run the ZEE baseline instead.
# Prover (ZEE200)
./scripts/run_sha_prover.sh zee200
# Input pattern in prover terminal:
# 1) Dummy integer (e.g., 12345)
# 2) Iteration count (e.g., 10 or 200)
# Verifier
./scripts/run_sha_verifier.sh zee200Other benchmarks follow the same naming pattern:
-
Mergesort:
./scripts/run_mergesort500_prover.sh <zee200|zee>./scripts/run_mergesort500_verifier.sh <zee200|zee>./scripts/run_mergesort5000_prover.sh <zee200|zee>./scripts/run_mergesort5000_verifier.sh <zee200|zee>
-
gzip (bug trigger and decompression):
./scripts/run_gzip_bug_prover.sh <zee200|zee>./scripts/run_gzip_bug_verifier.sh <zee200|zee>./scripts/run_gzip_decompress_prover.sh <zee200|zee>./scripts/run_gzip_decompress_verifier.sh <zee200|zee>
-
Empty-program RAM sweeps (Table on ZK RAM usage):
- ZEE200:
./scripts/run_empty_ram_sweep_zee200.sh - ZEE:
./scripts/run_empty_ram_sweep_zee.sh
- ZEE200:
These scripts set up temporary working directories, copy only the necessary binaries and inputs, and log results under benchmarks/*/ so the raw data is easy to inspect.
ZEE200 project code is released under the MIT license. Vendored third-party
components retain their own licenses; see THIRD_PARTY_NOTICES.md.
Citation metadata for this artifact is provided in CITATION.cff.
At a high level:
ZEE200/
├── benchmarks/ # C sources, ZEE .s/.zk, inputs, and eval helpers
│ ├── empty/ # Empty program + RAM sweep logs
│ ├── fib_stdin/ # Recursive Fibonacci benchmark
│ ├── gzip/ # gzip bug + decompression benchmarks
│ ├── mergesort/ # mergesort benchmarks (n=500, 5000)
│ ├── sed/ # sed vulnerability benchmark
│ └── sha256/ # SHA-256 benchmark
├── emp-zk/ # EMP toolkit ZK library
├── scripts/ # Top-level and backend-specific runners
│ ├── run_*_prover.sh # One script per benchmark (backend selector)
│ ├── run_*_verifier.sh # Matching verifier scripts
│ ├── zkvm/ # ZEE200-specific runners
│ └── zee/ # ZEE baseline-specific runners
├── toolchain/ # Frontend toolchain (LLVM, newlib, Docker scripts)
│ ├── zk-llvm-backend/ # ZEE LLVM backend + newlib sources
│ ├── zknewlib/ # Newlib build directory for zk target
│ ├── zee-cleartext/ # Cleartext ISA→ZK ISA tools (isaconverter, zkassembler)
│ ├── compile_c.sh # C → ZEE .s (host)
│ ├── compile_c_docker.sh # C → ZEE .s (Docker; preferred)
│ └── s_to_zk_docker.sh # ZEE .s → .zk (Docker)
├── zk-ram/ # IZKRAM / ZKSet implementations
├── zkvm/ # Core ZEE200 backend implementation
│ ├── generic_asm_test.cpp # Main driver (test_zkvm_generic_asm_test)
│ ├── common_test_base.h # ZK protocol harness
│ ├── instruction.h # Circuit representation
│ ├── zkcpu.h # VM coordinator
│ ├── gates/ # Gate(Gadget) implementations
│ ├── parsing/ # Assembly parser
│ ├── translation/ # ASM→circuit lowering
│ └── cleartext_vm/ # Cleartext reference VM
├── zee-processor/ # ZEE baseline prover/verifier (ZKMachine)
└── build/ # Local build directory (not committed)
└── bin/ # Built binaries (e.g., test_zkvm_generic_asm_test)