Introduction
Zero-knowledge proofs (ZKPs) enable privacy-preserving verification of information, and Circom is a popular tool for building ZK circuits. In this tutorial, we’ll create a simple circuit that takes a number as input and output the four bit binary representation of the number . This hands-on approach will introduce you to key concepts like defining inputs, constraints, and generating proofs. By the end, you’ll have a solid foundation to start building more complex zero-knowledge circuits using Circom.
Revisiting Key Concepts
- Prover and Verifier: The prover generates a proof that the verifier can use to confirm a statement’s validity without learning any details.
- Proof: A piece of cryptographic evidence that convinces the verifier.
- Witness: Hidden input data used to construct the proof.
- Circuit: A set of logical steps used to create a proof. It defines how the inputs relate to the outputs.
- Constraint System: Rules combining inputs using mathematical and cryptographic operations.
- Trusted Setup: A one-time process to generate cryptographic keys for the prover and verifier to use.
The combination of these elements allows zero-knowledge proofs to provide privacy, security, and verification without revealing sensitive information.
To learn more, read this introduction
Introduction to Zero-Knowledge Proofs
Tools and Environment Setups.
To build and run ZKPs, you’ll need the following tools installed on your system:
Note: you can use zkREPL as a faster and easier web based editor with built in circom compiler.
- Node.js: JavaScript runtime to run SnarkJS scripts.
- Rust: Used to compile Circom.
- Circom: A specialized programming language for ZK circuits.
- SnarkJS: A JavaScript library for generating and verifying ZKP proofs.
Step 1: Install Prerequisites
For installation and project setup, check out Part1 of this section in the series.
Building the Circuit
Create a file called num2fourbits in the circuits/ folder.
num2fourbits.circom
pragma circom 2.1.6;
template Num2FourBits () {
// declaratio of signals
signal input x;
signal output b0;
signal output b1;
signal output b2;
signal output b3;
b0 <-- x % 2;
b1 <-- (x - b0) / 2 % 2;
b2 <-- (x - 2 * b1 - b0) / 2 % 2;
b3 <-- (x - 4 * b2 - 2 * b1 - b0) / 2 % 2;
b0 * (b0 - 1) === 0;
b1 * (b1 - 1) === 0;
b2 * (b2 - 1) === 0;
b3 * (b3 - 1) === 0;
1*b0 + 2*b1 + 4*b2 + 8*b3 === x;
}
component main { public [ x ] } = Num2FourBits();
Explanation of Key Elements
In this circuit, we define a template called Num2FourBits
, which is used to convert an input number x
into a 4-bit binary representation. Here’s an explanation of the key components:
1. Signals Declaration:
signal input x;
: This is the input signalx
, which is the number that will be converted into binary.signal output b0, b1, b2, b3;
: These are the output signals representing the 4 bits of the binary representation ofx
. Each of these corresponds to one bit in the 4-bit binary number.
2. Binary Conversion Logic:
The goal of the circuit is to represent the number x
as a sum of powers of 2. The circuit breaks down the number x
into its binary digits (b0
, b1
, b2
, and b3
):
b0 <-- x % 2;
: This computes the least significant bit (LSB) by taking the remainder ofx
when divided by 2 (x % 2
).b1 <-- (x - b0) / 2 % 2;
: This calculates the second bit by subtractingb0
fromx
, then dividing by 2, and taking the remainder when divided by 2. This isolates the second bit.b2 <-- (x - 2 * b1 - b0) / 2 % 2;
: This isolates the third bit by subtracting2 * b1 + b0
fromx
, then dividing by 2 and taking the remainder when divided by 2.b3 <-- (x - 4 * b2 - 2 * b1 - b0) / 2 % 2;
: This isolates the fourth bit by subtracting4 * b2 + 2 * b1 + b0
fromx
, then dividing by 2 and taking the remainder when divided by 2.
3. Constraints on the Binary Values:
The following constraints ensure that the outputs b0
, b1
, b2
, and b3
are binary values (either 0 or 1):
b0 * (b0 - 1) === 0;
: This ensures thatb0
is either 0 or 1.- Similarly, the other lines ensure that
b1
,b2
, andb3
are also either 0 or 1.
4. Reconstruction of x
:
1 * b0 + 2 * b1 + 4 * b2 + 8 * b3 === x;
: This constraint ensures that the sum of the binary digits (each multiplied by its corresponding power of 2) exactly reconstructs the original numberx
. This is a key check to verify that the binary decomposition is correct.
Compiling the Circuit
Run the following command to compile the circuit:
circom circuits/num2fourbits.circom --r1cs --wasm --sym -o output/
Let’s look at each file:
-
–r1cs: Generates the Rank 1 Constraint System (R1CS) file.
-
–wasm: Outputs a WebAssembly file for computation.
-
–sym: Outputs a symbol file for debugging.
-
-o output/: Specifies the output directory.
Check for the following files in output/:
-
num2fourbits.r1cs: The constraint system file.
-
num2fourbits.wasm: The WebAssembly file.
-
num2fourbits.sym: The symbol file for debugging.
Generating Trusted Setup
Generate the keys for the prover and verifier:
wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_10.ptau -O outputs/pot10.ptau
snarkjs groth16 setup outputs/num2fourbits.r1cs outputs/pot10.ptau outputs/num2fourbits_final.zkey
Export the Verification Key
snarkjs zkey export verificationkey outputs/num2fourbits_final.zkey outputs/verification_key.json
Creating Inputs
Create an input file input/input.json
with the following content:
{
"x": "3"
}
These Inputs will be used to generate the proof.
Generating the Proof
snarkjs groth16 prove outputs/num2fourbits_final.zkey outputs/witness.wtns outputs/proof.json outputs/public.json
snarkjs groth16 prove outputs/num2fourbits_final.zkey outputs/witness.wtns outputs/proof.json outputs/public.json
snarkjs groth16 verify outputs/verification_key.json outputs/public.json outputs/proof.json
If everything is setup correctly, you should see:
snarkJS: OK!
Summary
-
Design the Circuit: Create the logic of the proof in a Circom file.
-
Compile the Circuit: Convert the logic to constraint systems and WebAssembly files.
-
Generate Trusted Setup: Produce keys for the prover and verifier.
-
Input Data: Provide the inputs for the computation.
-
Generate Witness: Produce intermediate computation results.
-
Generate Proof: Create a proof from the witness.
-
Verify Proof: Use the verifier to check the proof’s validity.
Conclusion
Congratulations! You’ve completed a hands-on implementation of zero-knowledge proofs. This process may seem complex initially, but as you create more advanced circuits, you’ll see how useful ZKPs can be for privacy-preserving computation.
We encourage you to continue your exploration of topics on zk proofs. If you’re interested in diving deeper, you can follow up on the pathway here Zero-Knowledge Proofs on the Celo Blockchain: A Comprehensive Tutorial Series - Pathways - Celo Academy
Resources
Circom docs
SnarkJS
0xparc Circom Workshop
About the author
I’m Jonathan Iheme, A full stack block-chain Developer from Nigeria. With a great passion for Zero Knowledge Technology.