# Zero Knowledge App using SNARKs

*Originally published on November 11, 2023 on* [*my previous blog*](https://somesh-banerjee.github.io/blogs/blog/10)*.*

---
In this blog, we will create a simple zero knowledge application where a user can prove that he is above 18 years old without revealing his age. To achieve this we will use CIRCOM and snarkjs to create a circuit and generate a proof. Then we will create a solidity contract which will verify the proofs and store the result on chain. Finally, we will create a simple react app to interact with the contract.

You can find the complete code [here](https://github.com/somesh-banerjee/zero-knowledge-sample-contract).

# Zero Knowledge

Zero knowledge is a way of proving that you know something without revealing what you know. For example, if you wanted to prove that you know the password to a door without revealing the password, you could do so by opening the door. Someone watching you would know that you know the password, but they would not learn what the password is. This is the basic idea behind zero knowledge proofs. You can find more examples [here](https://www.circularise.com/blogs/zero-knowledge-proofs-explained-in-3-examples#example-proof-of-membership-2).

# zk-SNARKs

zk-SNARKs stands for Zero-Knowledge Succinct Non-Interactive Argument of Knowledge. One popular implementation of zk-SNARKs is [Zcash](https://z.cash/). It is a type of zero-knowledge proof that allows one party to prove to another that a statement is true, without revealing any information beyond the validity of the statement itself, and without any interaction between the prover and the verifier. The non-interactive nature of zk-SNARKS make it suitable for scenarios where real-time interaction is not possible. In our project, the age verification proofs will be verified on-chain, which can be later used by other users to verify the age of the user.

# CIRCOM

[Circom](https://docs.circom.io/) is a domain-specific language for writing arithmetic circuits for zero-knowledge proof systems. It is used to create circuits which can be used to generate proofs. In Circom circuits, we have signals which acts like inputs and outputs. We will also use the [circomlib](https://github.com/iden3/circomlib) library which contains some useful circuits like comparators, hashers, etc. You can find more about the language [here](https://docs.circom.io/circom-language).

# Initialising the project

Since we will be creating a smart contract at the end, we will use the [hardhat](https://hardhat.org/) framework to create the project. This will help us to compile, deploy and test the contract. So we can follow the [hardhat installation guide](https://hardhat.org/getting-started/#installation) to create the project. After setting up the project, we will install snarksjs and circomlib npm packages to create the circuit.

```bash
npm install snarksjs circomlib
```

After this you can create a separate directory for the zk related codes to keep things organised.

# Creating the circuit

We will create a simple circuit which will take the age of the user as input and output a boolean value which will be true if the user is above 18 years old.

First we will define the signals. In our case, we will have one input signal which will be the age of the user and one output signal which will be true if the user is above 18 years old.

```js
signal input age;
signal output out;
```

Now we need to compare the age with 18. For this, we will use the `GreaterThan` component from the circomlib library. This component takes two inputs and outputs 1 if the first input is greater than the second input. We will connect the age signal to the first input and 18 to the second input. Then we will connect the output of the `GreaterThan` component to the output signal. You can find more about the `GreaterThan` component [here](https://github.com/iden3/circomlib/blob/cff5ab6288b55ef23602221694a6a38a0239dcc0/circuits/comparators.circom#L131).

```js
component greaterThan = GreaterThan(8); // 8 denotes number of bits
greaterThan.in[0] <== age;
greaterThan.in[1] <== 17; // 18 is considered 18+ so 17 is the limit

out <-- greaterThan.out;
out === 1;
```

Before using the comparator, we need to include the circomlib library that we installed with npm. So include the correct path to the library in the code. The overall circuit will look like this:

```plaintext
pragma circom 2.0.0;

include "../../node_modules/circomlib/circuits/comparators.circom";

template AgeAbove18() {
    signal input age;

    signal output out;

    component greaterThan = GreaterThan(8); // 8 denotes number of bits
    greaterThan.in[0] <== age;
    greaterThan.in[1] <== 17; // 18 is considered 18+ so 17 is the limit

    out <-- greaterThan.out;
    out === 1;
}

component main = AgeAbove18();
```

Though I have said the code returns true or false based on age, it actually always returns true, and if age is less than 18 then it gives Assert Error.

Next we will compile the circuit and export the solidity code. This process is same as mentioned in the [circom documentation](https://docs.circom.io/getting-started/installation/). You can follow the steps mentioned there to compile the circuit and export the solidity code.

# Creating the solidity contract

After exporting the solidity code, we will create a solidity contract which will verify the proofs and store the result on chain. We will copy the exported solidity code to `contracts` directory of hardhat project and name it `verifier.sol`.Also, create a new contract `AgeVerifier.sol` which will call the verifier contract and store result. The `AgeVerifier.sol` contract will look like this:

```js
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IVerifier {
  function verifyProof(
    uint256[2] memory a,
    uint256[2][2] memory b,
    uint256[2] memory c,
    uint256[2] memory input
  ) external view returns (bool);
}

contract AgeVerifier {

    IVerifier public verifier;
    mapping(address => bool) public ageChecked;
    mapping(address => address) public approvedViewer;

    constructor(address _verifier) {
        verifier = IVerifier(_verifier);
    }

    function verifyProof(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256[2] memory input
    ) external {
        require(
            verifier.verifyProof(a, b, c, input),
            "Below 18 years old"
        );
        ageChecked[msg.sender] = true;
    }

    function CheckProof(address _w) external view returns (bool) {
        require(_w == msg.sender || approvedViewer[_w] == msg.sender, "Not approved to check");
        return ageChecked[_w];
    }

    function Approve(address _w) external {
        approvedViewer[msg.sender] = _w;
    }
}
```

The `verifyProof` function is used to verify the proof and store the result on chain.

I have added a few extra features to the contract. The `Approve` function can be used to approve another user to check the age. The `CheckProof` function can be used to check if the user has verified his age or not.

Now to call the `verifyProof` function, we need to pass the proof as arguments. We will use the `snarkjs` library to generate the proof. The code to generate proof will look like this:

```js
const { groth16 } = require('snarkjs')

const witness = {
  age: 20,
}

const { proof, publicSignals } = await groth16.fullProve(witness, wasmFilePath, finalZkeyPath, null)
```

Here pass the age as the witness. The `wasmFilePath` is the path to the `circuit.wasm` file which is generated when we compiled the circuit. The `finalZkeyPath` is the path to the `final.zkey` file which is generated in phase 2 of Groth16 Trusted setup. The `proof` and `publicSignals` are the outputs of the `groth16.fullProve` function. We will pass these two as arguments to the `verifyProof` function of the contract. The code to call the `verifyProof` function will look like this:

```js
const transaction = verifier.verifyProof(
  [proof.pi_a[0], proof.pi_a[1]],
  [
    [proof.pi_b[0][1], proof.pi_b[0][0]],
    [proof.pi_b[1][1], proof.pi_b[1][0]],
  ],
  [proof.pi_c[0], proof.pi_c[1]],
  publicSignals,
)
```

Now yo can write tests in the `test` directory to test the contract. You can find the complete code [here](https://github.com/somesh-banerjee/zero-knowledge-sample-contract/blob/main/test/age_ab18.test.ts).

# Creating the react app

Now we can create a basic react app to interact with the contract. We will take age as input and then generate the proof. We can use wagmi now to connect wallet and call the `verifyProof` function of the contract. This will store if the user is above 18 or not in the contract. Remember that the contract will only store the result if the user is above 18. If the user is below 18, then error will be raised at the proof generation step.

The whole react app is available in the `frontend` directory of the github [repo](https://github.com/somesh-banerjee/zero-knowledge-sample-contract).
