THE BLOG
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 the eq.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:

  1. proof-generator-multi-threaded
  2. 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.