How to Use Zero Knowledge Dapp Boilerplate

Written by heypran | Published 2022/05/16
Tech Story Tags: blockchain | tatum_io | blockchain-writing-contest | zksnarks | zero-knowledge-proofs | nextjs | typescript | dapps | web-monetization

TLDRWe will be walking through a simple zk-dapp for age verification, that will allow users to prove that they are below a certain age without revealing their actual age. This is a fairly trivial problem to solve and may not necessarily require zero-knowledge proofs but to keep things simple and easy to understand we will go ahead with this idea. The dapp will be using a nextjs app to interact with the smart contract and take user input ( age) which is meant to be private and will not be published on-chain. The proof generation occurs on the frontend using [snarkjs].via the TL;DR App

Building zk based web3 apps

We will be doing walk through of a simple zk-dapp for age verification, that will allow users to prove that they are below a certain age without revealing their actual age. This is a fairly trivial problem to solve and may not necessarily require zero-knowledge proofs but to keep things simple and easy to understand we will go ahead with this idea.

Subscribe here for all the cool zk-tech and web3 stuff!

The overview of the architecture looks something like the below.

We will be using a nextjs app to interact with the smart contract and take user input ( age) which is meant to be private and will not be published on-chain. The proof generation occurs on the frontend using snarkjs. There are two smart contracts for this dapp, AgeCheck and Verifier:

  • AgeCheck.sol takes proof inputs from the frontend app and maintains the mapping of addresses if they are verified or not.
  • Verifier.sol has one purpose i.e. to verify the proof.

There are lots of different ways to generate zero-knowledge proofs and these are continuously evolving. There is a lot of advancement going in the zero-knowledge space. For the purpose of this dapp, we will be using SNARK proofs, more specifically Groth16.

To generate zero-knowledge proof for our use case we need to define the problem statement in terms of circuits. In this case, our problem statement is verifying that the user's age ( age is private information) should be below a certain age limit ( age limit is public information). To create circuits we will be using circom library.

The purpose of the circuit is to take the user’s age as input and check if that is above a certain age limit ( for eg. 18). The circuit is then compiled to create wasm which then can be used in the browser to generate proofs. The input to the circuit includes private and public inputs - in this case, user’s age is private input ( as it doesn’t need to be revealed) and ageLimit is public input ( known to everyone). These inputs are used in generating the witness ( which is a set of signals that satisfies the circuit) you can read more about them here.

To summarize in short**, zk-SNARK proofs are a specific type of zero-knowledge proofs that allow you to prove that you know a set of signals i.e. witness that matches all the constraints of a circuit without revealing any of the signals except the public inputs and the outputs.**

I know it sounds a bit confusing, but may in a later post I will simplify some math behind it and maybe a circuit primer. For the purpose of this dapp, let's proceed in order to understand how everything works in tandem.

Now let’s dive into the code, we will be using zk-block boilerplate for zero-knowledge dapps, refer to this repo.

Clone the repository.

git clone https://github.com/heypran/zk-block.git

Note: Following along with code requires familiarity with react, ethers, and solidity.

We will start from the backend ( or if you look at the above diagram, from top to bottom). Here’s what we gonna do:

  1. Compile the circuit built using circom and auto-generate the verifier contract using snarkjs

cd zk-block
cd backend
yarn install
yarn compile:circuits

This will use the circuit present in the backend/circuits folder and compile it using the script in scripts/compile-circuits.ts If you know about zero-knowledge proofs, you will know that Groth16 protocol requires performing a trusted setup. This is done in two phases. Phase 1 is independent of the circuit and phase 2 is circuit dependent. You can read about them here.

Caution: Please note that you need to perform MPC to generate trusted setup, do not use the setup in compile-circuits.ts as-is for production.

When you compile the circuit, it will create the necessary files in the build/snarks folder which can be used in the frontend repo to generate proofs. The above compilation also generates the Verifier.sol in the contracts folder. The Verifier.sol is used to verify the proof on-chain.

  1. Compile the contracts and deploy the smart contract to be able to use with our nextjs app

Execute the following command inside the background folder to compile the contracts

yarn compile

This will compile the contracts in backend/contracts folder, which contains AgeCheck.sol and Verifier.sol ( which is generated in the above step). The compilation is done using hardhat configuration and the selected network in the hardhat.config.ts , the compiled code is generated in the backend/build folder.

  1. (Optional) Deploy contracts to the network of your own choice

Note: This step is not required, if you don’t want to deploy, you can simply use the existing testnet contract address, which is already configured inside ui/src/config/constants.tsand also mentioned in the github readme.

In order to deploy the compiled contract, you will have to create a backend/private.json

with your private key inside it ( refer private.example.json ) and change the selected network in hardhat.config.ts to a network of your choice ( refer chain.ts to view the networks).

Execute the below command to deploy-

yarn deploy:agecheck --network testnet

The deployed contract address will be displayed on the terminal, configure it inside ui/src/config/constants.ts

  1. Generating Proof from the frontend

Now let’s move to the frontend, to understand how the proof is generated on the frontend.

Execute the below commands.

cd ..
cd frontend
yarn install
yarn dev

The above will run the zk-block frontend locally, visit http://localhost:3000/dapp to see the age verification in action. You may connect to network, Polygon, or Harmony ( or where you deployed the smart contract), using Metamask.

Input the age in the box, and click verify. This will verify your age as above 18 using the smart contract without publishing your age on the blockchain.

Behind the scenes, when the user inputs the age, it will generate the witness using public and private inputs ( refer to the below snippet from ui/src/utils/zk/zk-witness.ts).

The code is commented on for understanding.

  // read the wasm generated by compiling the circuit in STEP 1
  const buffer = await getBinaryPromise(circuitWasm);

  // generate witness using calculator file generated from circom
  // and then passing the public and private input
  const witnessCalculator = await generateWitnessJs(buffer);
  const buff = await witnessCalculator.calculateWTNSBin(params, 0);

  const provingKey = await fetch(zkey);
  const provingKeyBuffer = await provingKey.arrayBuffer();

  // Generate groth proof
  const { proof, publicSignals } = await snarkjs.groth16.prove(
    new Uint8Array(provingKeyBuffer),
    buff,
    null,
  );

  // required to generate solidity call params
  const editedPublicSignals = unstringifyBigInts(publicSignals);
  const editedProof = unstringifyBigInts(proof);

  // Generate solidity compatible params for Verifier.sol 
  const callData = await snarkjs.groth16.exportSolidityCallData(
    editedProof,
    editedPublicSignals,
  );

The calldata are proof parameters generated to be able to verify the proof ( i.e. the age is indeed above a limit, in this case 18) defined using our Verifier.sol deployed on-chain.

You can play around with zk age verification here. Now if you go through the architecture diagram, you will have a better understanding :).

Please note: This zkblock boilerplate is updated on a regular basis if you find that anything is not working as it suppose to be or you want to suggest any improvement, feel free to create an issue. Also, you can go through the readme. Any contributions are welcome.

Subscribe here for all the cool zk-tech and web3 stuff!

Happy Hacking!

References:

https://github.com/heypran/zk-block

https://github.com/iden3/circom


Written by heypran | Curiosity killed the Schrodinger's cat? or did it?
Published by HackerNoon on 2022/05/16