THE BLOG
Published on

My First Zero Knowledge Circuit

Introduction

If you're reading this article, you probably already know about Zero Knowledge Proofs. If you do, feel free to jump to the next section. If not, let me explain it simply.

Zero knowledge proofs are a method where someone can prove a fact is true to another person without giving away secret details. In this setup, we have two roles: the prover, who creates the proof, and the verifier, whose role is to assess the proof's validity without learning any secret information

But how does this work?

There are many examples to explain this. A famous one is the Ali Baba cave story, which you can read about here. Another clear and illustrative way to understand zero-knowledge proofs is through the example of coloured balls and a colour-blind friend. This scenario is both simple and effective in demonstrating the key principles of zero-knowledge proofs.

Coloured balls and a colour-blind friend

Imagine you have two balls that look the same, but one is red and the other green. Your friend Victor, who can't tell red from green, thinks these balls are identical. Victor is doubtful that the balls are actually different colors. You want to show Victor they are different without telling him which is red and which is green.

Here's how you do it: You give Victor the balls. He hides them and shows you one. Then he either switches it with the other or shows the same one again. He asks if he switched the ball. This process is repeated many times.

You can tell if Victor switched the balls by their color. If the balls were the same color, you couldn't consistently guess right more than half the time. If you keep identifying the switches correctly over several tries, Victor will believe the balls are different colors.

This proof is a zero-knowledge proof because Victor doesn't learn which ball is which color; he gains no extra information about the balls, yet he becomes certain that the balls are of different colors.

Exploring =nil; Foundation's Role in Zero-Knowledge Technology

The =nil; Foundation is a leader in zero-knowledge technology, focused on making zkProofs and trustless data management easy for developers. They provide tools for scalable, secure computing and data access.

=nil; Foundation Toolchain Highlights:

=nil; zkSharding: A powerful zkRollup solution that boosts Ethereum's throughput to over 60,000 transactions per second. It's a game-changer for web3 developers, enabling them to create scalable and secure applications with ease.

Proof Market: This decentralized marketplace revolutionizes the way zero-knowledge proofs are handled. It automatically generates high-performance circuits suitable for various protocols, significantly streamlining the development process.

zkLLVM: A unique compiler that transforms C++ or Rust code into zero-knowledge circuits. It simplifies and accelerates the generation of zkProofs, making the implementation process both time and cost-efficient.

Placeholder & Crypto3 Suite: This is a flexible and robust zero-knowledge proof system paired with a comprehensive cryptography suite. It's perfect for developers looking for quick, secure prototyping in zero-knowledge technology.

In summary, the =nil; Foundation's toolchain provides a comprehensive, easy-to-use suite of tools essential for modern zero-knowledge application development.

Starting with Zero Knowledge Circuits

To test out the =nil; Foundation toolchain, we will use zkllvm-template. This repository serves as both a tutorial and a template project for creating an application based on the zkLLVM toolchain.

Prerequisites

  1. Linux based OS (I am using Ubuntu 22.04, CCX43 16 AMD 64 GB 360 GB)
  2. Follow the instructions to set up repository and to pull docker image (1, 2)

Writing the circuit

For this tutorial, we will be creating a simple circuit that will check if a number is even or odd. The circuit will take a number as input and output a boolean value.

Zkllvm-template already provides an example circuit which you can find in "src/" directory. But we will be creating a new circuit from scratch.

Note: Examples of many different circuits, along with their inputs, can be found in the zkllvm-examples repository

Circuit code (main.cpp):

#include <cstdint>

[[circuit]] bool is_even(uint32_t a) {
    bool res = (a % 2 == 0);

    __builtin_assigner_exit_check(res);

    return res;
}

In this code, we define a circuit function is_even to determine if a given unsigned 32-bit integer (uint32_t a) is even. The function calculates if the number is even using (a % 2 == 0) and stores the result in bool res.

The __builtin_assigner_exit_check(res) is a built-in function that enforces a condition check within the circuit. If res is not true, it stops the circuit and rejects the proof. This ensures that the circuit only proceeds when the condition is met, which in this case, is when the number is even.

Input (main-input.json):

[
  {"int": 12}
]

In the is_even function, as indicated by its signature, the required input is a single unsigned 32-bit integer (uint32_t). This input specification is crucial for understanding how to structure the input data.

In the context of the zkllvm-template, inputs are provided in a JSON format, encapsulated within an array structure. Each input value is represented as a field within this array.

Compiling, Running, and Verifying the Circuit with zkLLVM

Step 1: Compile the Circuit

First, we focus on the ./src/main.cpp file, where our is_even circuit is. This circuit is marked with [[circuit]]. We need to compile it using the zkLLVM compiler. Here’s how:

  1. In the project's root directory, run the command:
cmake -G "Ninja" -B ${ZKLLVM_BUILD:-build} -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=clang-zkllvm .
  1. Then compile it with:
ninja -C ${ZKLLVM_BUILD:-build} template.

This will create a file template.ll in the build/src directory. This file is the compiled version of our circuit.

Step 2: Create the Circuit File and Assignment Table

Now we need to turn our compiled circuit into a format that can be used, and get our input data ready.

Run this command:

assigner -b build/src/template.ll \
         -i src/main-input.json \
         --circuit template.crct \
         --assignment-table template.tbl \
         -e pallas

This command makes two things: a file template.crct (our circuit in binary format) and template.tbl (which has our input data ready for the next steps).

Step 3: Make and Check the Proof Locally

Now, let's create and verify our proof. We do this using the proof-generator-single-threaded tool:

proof-generator-single-threaded \
--circuit="template.crct" \
--assignment-table="template.tbl" \
--proof="proof.bin"

During this process, you should see messages indicating the successful generation and verification of the proof. Here's how the successful log output will look:

Proof generated
Verifying proof...
Proof is verified
Writing proof to "proof.bin"
Proof written

These messages confirm that the proof has been generated and verified correctly. The final proof file will be named ./proof.bin

Verifying Proof with Invalid Input

In the previous section, we successfully generated and verified a proof using an even number as input. But what happens if we use an odd number? Let's find out by changing our input to an odd number and repeating the same steps. This will demonstrate how our circuit responds to inputs that don't meet the criteria

Adjusting the Input

First, we modify the input in our main-input.json file to an odd number, such as 11:

[
  {"int": 11}
]

Repeating the Steps

Now, we repeat the compilation and assignment table creation steps as before. Then, we run the proof-generator-single-threaded tool again with this adjusted input:

proof-generator-single-threaded \
--circuit="template.crct" \
--assignment-table="template.tbl" \
--proof="proof.bin"

This time, the messages during the proof generation and verification process will be different. Here's what you should see:

Proof generated
Verifying proof...
Error: Something went wrong - proof is not verified

This output shows that although the proof was generated, it failed during the verification step. This is because our circuit logic specifically checks for even numbers, and our input (11) is odd. It's important to note that in this case, the proof.bin file will not be produced, as the proof did not pass the verification.

Conclusion

That's a wrap on our tutorial for creating your first zero-knowledge circuit using the =nil; Foundation toolchain. We've walked through the steps of writing, compiling, and verifying a circuit - both with valid and invalid inputs

This article marks the start of a series where we'll dive into more details about the =nil; Foundation's toolchain. We'll explore more advanced topics and showcase how to leverage their toolchain for sophisticated zero-knowledge proof applications.

Happy coding!