- Published on
How to Verify zkLLVM Circuits On-Chain
Introduction
The primary objective of this tutorial is to guide through the process of writing circuit, providing it with specific input parameters, and then employing a combination of three tools—zkLLVM
, proof-producer
, and evm-placeholder-verification
—to compile, build, and verify the circuit. Specifically, we will:
- Use
zkLLVM
to compile and build the circuit - Use
proof-producer
as a standalone binary to generate the necessary proofs for our circuit - Use
evm-placeholder-verification
to deploy a verifier contract on a local (EVM) network and verify the circuit with the given inputs
By the end of this tutorial, you will have a understanding of how to seamlessly integrate these tools
zkllvm:
Before diving into zkLLVM, ensure that you've cloned the repository along with all its submodules to set up the environment properly (install all dependencies https://github.com/NilFoundation/zkLLVM?tab=readme-ov-file#install-dependencies)
Step 1: Modify the Circuit Example
- Navigate to
examples/cpp/compare
in the zkLLVM repository. - We will use and modify the
eq.cpp
example, which checks if two inputs are equal. This simple circuit will be enhanced to include a built-in function enforcing a condition check. Replace theeq.cpp
code with the following:
#include <cstdint>
[[circuit]] bool is_even(uint32_t a, uint32_t b) {
bool res = a == b;
__builtin_assigner_exit_check(res);
return res;
}
This modification ensures the verification fails if the result (res
) is not true
Step 2: Adjust Input Values
Adjust the input values in examples/inputs/compare/eq.inp
to ensure both numbers are the same, which is necessary due to the auto-enabled check. Update the file to:
[
{"int": 11},
{"int": 11}
]
Step 3: Configure and Build
Configure CMake to enable EVM verification with the following command:
cmake -G "Ninja" -B ${ZKLLVM_BUILD:-build} -DCMAKE_BUILD_TYPE=Release -DCIRCUIT_ASSEMBLY_OUTPUT=TRUE -DBUILD_TESTS=True -DGENERATE_EVM_VERIFIER=TRUE .
Build the C++ compiler components (assigner
, clang
, transpiler
) using Ninja:
ninja -C ${ZKLLVM_BUILD:-build} assigner clang -j$(nproc)
Step 4: Identify and Build the Circuit
To list all targets for the compare_eq_cpp
example, execute (in the build dir):
ninja -t targets | grep compare_eq_cpp
You should see compare_eq_cpp_evm_verifier
among the targets.
Build the circuit with the command:
ninja -C build compare_eq_cpp_evm_verifier
This step generates a new directory inside build/examples/cpp
named transpiler_output_compare_eq_cpp
, preparing the circuit as instantiated contracts ready for blockchain deployment and verification. In this directory you will find all required solidity contract, circuit_params, proof and inputs
evm-placeholder-verification:
After setting up zkLLVM
and preparing your circuit, the next phase involves deploying and verifying it using evm-placeholder-verification
.
Step 1: Clone and Prepare the Repository
Begin by cloning the evm-placeholder-verification
repository. Navigate to its root directory and install all dependencies with npm install
. After which we need to compile hardhat project
npx hardhat compile
Step 2: Integrate the Circuit Output
Find the transpiler_output_compare_eq_cpp
directory generated by zkLLVM
. This contains the Solidity contract and other components necessary for verifying your circuit on the blockchain.
Copy this entire directory into contracts/zkllvm
within the evm-placeholder-verification
repository.
Step 3: Deploy and Verify the Circuit
Deploy all circuits, including yours, by executing npx hardhat deploy
. This command deploys the necessary verification contracts to a local EVM network, setting the stage for actual verification.
To verify your specific circuit with its inputs, use:
npx hardhat verify-circuit-proof --test circuit-name
For a broader verification that includes all circuits in the contracts/zkllvm
directory, run:
npx hardhat verify-circuit-proof-all
Given that the inputs are correctly set to equal values, the verification should pass
====================================
Verify : ../contracts/zkllvm/transpiler_output_compare_eq_cpp/proof.bin
⛽Gas used: 5071711
Events received:
✅ProofVerified
====================================
Testing with Different Inputs
Altering the inputs in transpiler_output_compare_eq_cpp
to unequal values
[
{"int": 11},
{"int": 12}
]
and rerunning the verification simulates a failed proof condition. The expected outcome in this scenario includes a WrongPublicInput
and a ProofVerificationFailed
event
====================================
Verify : ../contracts/zkllvm/transpiler_output_compare_eq_cpp/proof.bin
⛽Gas used: 5072502
Events received:
🤔WrongPublicInput
🛑ProofVerificationFailed
====================================
proof-producer:
The final component of our tutorial involves using proof-producer
to generate a proof, ensuring it aligns with the proof generated using zkLLVM
.
Clone the proof-producer
repository recursively to include all submodules.
Install the necessary dependencies by running the following command:
sudo apt-get install \
build-essential \
libsctp-dev \
libssl-dev \
libicu-dev \
lsb-release \
gnutls-dev \
pkg-config
Step 1: Build proof-generator
Create a build directory and navigate into it:
mkdir build && cd build
Configure the build with CMake:
cmake .. -DCMAKE_BUILD_TYPE=Release -DZK_PLACEHOLDER_PROFILING_ENABLED=TRUE -DBLUEPRINT_PLACEHOLDER_PROOF_GEN=True
Compile the proof-generator
:
make -j $(nproc)
Upon completion, you'll find two versions of the proof-generator in build/bin
:
proof-generator-multi-threaded
proof-generator-single-threaded
For this example, we'll use proof-generator-single-threaded
to generate the proof.
Step 2: Generate Assignment Table and Constraint System
Before generating the proof, we need the assignment table and constraint system from our circuit. Return to the zkLLVM
directory and execute:
cmake --build build -t cpp_examples_generate_crct
cmake --build build -t cpp_examples_generate_tbl
These commands generate the assignment tables (*.tbl
) and constraint system (*.crct
) for all examples, located in zkLLVM/build/examples/cpp
. Copy the absolute paths for our specific circuit's .tbl
and .crct
files.
Step 2: Generate the Proof
With the assignment table and constraint system ready, navigate to proof-generator
binaries in build/bin/proof-generator
and run the proof generator:
./proof-generator-single-threaded -t /path/to/assignment_compare_eq_cpp.tbl --circuit /path/to/circuit_compare_eq_cpp.crct
This command produces a proof.bin
file in the same directory, representing the generated proof.
Step 3: Validate the Proof
Finally, to validate the generated proof, replace the proof.bin
file in evm-placeholder-verification
under contracts/zkllvm/transpiler_output_compare_eq_cpp
with the new proof.bin
from proof-producer
.
If the proof generators in zkLLVM
and proof-producer
are compatible, the verification process should succeed as before. If a mismatch occurs, verify the versions of the proof generators used in each tool to ensure consistency.