# Tutorial: How to develop a Zero Knowledge App using Groth16

This tutorial will outline the process of developing a Zero Knowledge(zk) Decentralized Application (DApp) from inception to deployment. The objective is to demonstrate the workflow of a zkDApp and its deployment for users to use it.

In this tutorial, we'll develop a zkDApp to demonstrate that someone knows how to solve a Sudoku puzzle without revealing the answer. Utilizing Circom for circuits, Solidity for smart contracts, and JavaScript for frontend development, this guide encompasses essential aspects such as circuit and smart contract testing. It also offers practical tips for the efficient organization and construction of projects of this nature.

The code used in this tutorial is part of a project called zkArcade which can be found here:

* [github.com/DeograciousAggrey/ZkArcade](https://github.com/DeograciousAggrey/zkArcade)
    

The Sudoku contract and verifier are both deployed on the Polygon Mumbai network and the frontend is hosted on vercel at [https://zk-arcade.vercel.app/sudoku](https://zk-arcade.vercel.app/sudoku)

## Dependencies

Follow the guides provided to install the following dependencies

1. npm : [https://docs.npmjs.com/downloading-and-installing-node-js-and-npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
    
2. yarn : [https://yarnpkg.com/getting-started/install](https://yarnpkg.com/getting-started/install)
    
3. circom : [https://docs.circom.io/getting-started/installation/](https://docs.circom.io/getting-started/installation/)
    
4. snarkjs : [https://github.com/iden3/snarkjs#install-snarkjs](https://github.com/iden3/snarkjs#install-snarkjs)
    

## Circuits

### a) Creating the Circuit

Create a <mark>Projects </mark> folder and inside the folder create a circuits folder

```bash
$ mkdir Projects
$ cd Projects
$ mkdir circuits
$ cd circuits
```

Run the following command to create a <mark>package.json</mark> file inside the <mark>circuits</mark> folder.

```bash
yarn init -y
```

Add the <mark>circomlib</mark> library to use circuits from there: [circomlib docs](https://docs.circom.io/)

```bash
yarn add circomlib
```

Create the <mark>sudoku</mark> folder and open Visual Studio while inside the folder: ( **Note**: You can use any code editor you are comfortable with).

```bash
$ mkdir sudoku
$ cd sudoku
$ code .
```

Create a <mark>sudoku.circom</mark> file and write the following circom code :

```cpp
pragma circom 2.0.0;

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

template Sudoku() {
    signal input unsolved[9][9];
    signal input solved[9][9];


    // Check if the numbers of the solved sudoku are >=1 and <=9
    // Each number in the solved sudoku is checked to see if it is >=1 and <=9

    component getone[9][9];
    component letnine[9][9];

    
    for (var i = 0; i < 9; i++) {
       for (var j = 0; j < 9; j++) {
           letnine[i][j] = LessEqThan(32);
           letnine[i][j].in[0] <== solved[i][j];
           letnine[i][j].in[1] <== 9;

           getone[i][j] = GreaterEqThan(32);
           getone[i][j].in[0] <== solved[i][j];
           getone[i][j].in[1] <== 1;
           
           letnine[i][j].out ===  getone[i][j].out;
        }
    }
    

    // Check if unsolved is the initial state of solved
    // If unsolved[i][j] is not zero, it means that solved[i][j] is equal to unsolved[i][j]
    // If unsolved[i][j] is zero, it means that solved [i][j] is different from unsolved[i][j]

    component ieBoard[9][9];
    component izBoard[9][9];

    for (var i = 0; i < 9; i++) {
       for (var j = 0; j < 9; j++) {
            ieBoard[i][j] = IsEqual();
            ieBoard[i][j].in[0] <== solved[i][j];
            ieBoard[i][j].in[1] <== unsolved[i][j];

            izBoard[i][j] = IsZero();
            izBoard[i][j].in <== unsolved[i][j];

            ieBoard[i][j].out === 1 - izBoard[i][j].out;
        }
    }


    // Check if each row in solved has all the numbers from 1 to 9, both included 
    // For each element in solved, check that this element is not equal 
    // to previous elements in the same row

    component ieRow[324];

    var indexRow = 0;


    for (var i = 0; i < 9; i++) {
       for (var j = 0; j < 9; j++) {
            for (var k = 0; k < j; k++) {
                ieRow[indexRow] = IsEqual();
                ieRow[indexRow].in[0] <== solved[i][k];
                ieRow[indexRow].in[1] <== solved[i][j];
                ieRow[indexRow].out === 0;
                indexRow ++;
            }
        }
    }


    // Check if each column in solved has all the numbers from 1 to 9, both included
    // For each element in solved, check that this element is not equal 
    // to previous elements in the same column

    component ieCol[324];

    var indexCol = 0;


    for (var i = 0; i < 9; i++) {
       for (var j = 0; j < 9; j++) {
            for (var k = 0; k < i; k++) {
                ieCol[indexCol] = IsEqual();
                ieCol[indexCol].in[0] <== solved[k][j];
                ieCol[indexCol].in[1] <== solved[i][j];
                ieCol[indexCol].out === 0;
                indexCol ++;
            }
        }
    }


    // Check if each square in solved has all the numbers from 1 to 9, both included
    // For each square and for each element in each square, check that the 
    // element is not equal to previous elements in the same square

    component ieSquare[324];

    var indexSquare = 0;

    for (var i = 0; i < 9; i+=3) {
       for (var j = 0; j < 9; j+=3) {
            for (var k = i; k < i+3; k++) {
                for (var l = j; l < j+3; l++) {
                    for (var m = i; m <= k; m++) {
                        for (var n = j; n < l; n++){
                            ieSquare[indexSquare] = IsEqual();
                            ieSquare[indexSquare].in[0] <== solved[m][n];
                            ieSquare[indexSquare].in[1] <== solved[k][l];
                            ieSquare[indexSquare].out === 0;
                            indexSquare ++;
                        }
                    }
                }
            }
        }
    }
    
}

// unsolved is a public input signal. It is the unsolved sudoku
component main {public [unsolved]} = Sudoku();
```

### b) Compiling the circuit

After writing our circuit using circom, its now time to compile it to get a system of arithmetic equations representing it. As a result of the compilation, we will also obtain programs to compute the witness. To make our work easier, lets create some bash files that are generic and that we'll use every time we want to compile our circuits.

To make use of the `compile.sh` script, run the file and specify the circuit name, such as `./compile.sh` sudoku. Alternatively, you can edit the `CIRCUIT` variable within the `compile.sh` file with your circuit's name and then run `./compile.sh`. Don't forget to run `chmod u+x compile.sh` the first time you execute the script.

```bash
#!/bin/bash

# Variable to store the name of the circuit
CIRCUIT=sudoku

# In case there is a circuit name as input
if [ "$1" ]; then
    CIRCUIT=$1
fi

# Compile the circuit
circom ${CIRCUIT}.circom --r1cs --wasm --sym --c
```

Next we run the file:

If running for the first time use this command:

```bash
chmod u+x compile.sh
```

After that, use this command:

```bash
./compile.sh
```

Running this produces an output like the one shown below:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700650860125/c24f579a-72fe-4b86-b680-d5a5a0b10487.png align="center")

### c) Creating the input file

Create an <mark>input.json</mark> file and in it write the following code:

```json
{
  "unsolved": [
    [0, 0, 0, 0, 0, 6, 0, 0, 0],
    [0, 0, 7, 2, 0, 0, 8, 0, 0],
    [9, 0, 6, 8, 0, 0, 0, 1, 0],
    [3, 0, 0, 7, 0, 0, 0, 2, 9],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [4, 0, 0, 5, 0, 0, 0, 7, 0],
    [6, 5, 0, 1, 0, 0, 0, 0, 0],
    [8, 0, 1, 0, 5, 0, 3, 0, 0],
    [7, 9, 2, 0, 0, 0, 0, 0, 4]
  ],
  "solved": [
    [1, 8, 4, 3, 7, 6, 2, 9, 5],
    [5, 3, 7, 2, 9, 1, 8, 4, 6],
    [9, 2, 6, 8, 4, 5, 7, 1, 3],
    [3, 6, 5, 7, 1, 8, 4, 2, 9],
    [2, 7, 8, 4, 6, 9, 5, 3, 1],
    [4, 1, 9, 5, 3, 2, 6, 7, 8],
    [6, 5, 3, 1, 2, 4, 9, 8, 7],
    [8, 4, 1, 9, 5, 7, 3, 6, 2],
    [7, 9, 2, 6, 8, 3, 1, 5, 4]
  ]
}
```

### d) Computing the witness

Before creating the proof, we need to calculate all the signals of the circuit that match all the constraints of the circuit. For that, we will use the `Wasm` module generated by`circom` that helps to do this job.

Let us start with the `Wasm` code. Using the generated `Wasm` binary and three JavaScript files, we simply need to provide a file with the inputs and the module will execute the circuit and calculate all the intermediate signals and the output. The set of inputs, intermediate signals and output is called [witness](https://docs.circom.io/background/background#witness).

Create the file `executeGroth16.sh` and append the following:

This file is versatile and compatible with any circuit (utilizing groth16). For instance, to execute a circuit named `circuit.circom` with `ptau` set to 12, run the script as follows: `./executeGroth16.sh circuit 12`. Alternatively, customize the circuit and `ptau` variables like so: `CIRCUIT=circuit` and `PTAU=12.`

For detailed information refer to the [snarkjs documentation](https://github.com/iden3/snarkjs).

```bash
#!/bin/bash

# Variable to store the name of the circuit
CIRCUIT=sudoku

# Variable to store the number of the ptau file
PTAU=14

# In case there is a circuit name as an input
if [ "$1" ]; then
    CIRCUIT=$1
fi

# In case there is a ptau file number as an input
if [ "$2" ]; then
    PTAU=$2
fi

# Check if the necessary ptau file already exists. If it does not exist, it will be downloaded from the data center
if [ -f ./ptau/powersOfTau28_hez_final_${PTAU}.ptau ]; then
    echo "----- powersOfTau28_hez_final_${PTAU}.ptau already exists -----"
else
    echo "----- Download powersOfTau28_hez_final_${PTAU}.ptau -----"
    wget -P ./ptau https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_${PTAU}.ptau
fi

# Compile the circuit
circom ${CIRCUIT}.circom --r1cs --wasm --sym --c

# Generate the witness.wtns
node ${CIRCUIT}_js/generate_witness.js ${CIRCUIT}_js/${CIRCUIT}.wasm input.json ${CIRCUIT}_js/witness.wtns

echo "----- Generate .zkey file -----"
# Generate a .zkey file that will contain the proving and verification keys together with all phase 2 contributions
snarkjs groth16 setup ${CIRCUIT}.r1cs ptau/powersOfTau28_hez_final_${PTAU}.ptau ${CIRCUIT}_0000.zkey

echo "----- Contribute to the phase 2 of the ceremony -----"
# Contribute to the phase 2 of the ceremony
snarkjs zkey contribute ${CIRCUIT}_0000.zkey ${CIRCUIT}_final.zkey --name="1st Contributor Name" -v -e="some random text"

echo "----- Export the verification key -----"
# Export the verification key
snarkjs zkey export verificationkey ${CIRCUIT}_final.zkey verification_key.json

echo "----- Generate zk-proof -----"
# Generate a zk-proof associated to the circuit and the witness. This generates proof.json and public.json
snarkjs groth16 prove ${CIRCUIT}_final.zkey ${CIRCUIT}_js/witness.wtns proof.json public.json

echo "----- Verify the proof -----"
# Verify the proof
snarkjs groth16 verify verification_key.json public.json proof.json

echo "----- Generate Solidity verifier -----"
# Generate a Solidity verifier that allows verifying proofs on Ethereum blockchain
snarkjs zkey export solidityverifier ${CIRCUIT}_final.zkey ${CIRCUIT}Verifier.sol
# Update the solidity version in the Solidity verifier
sed -i 's/0.6.11;/0.8.4;/g' ${CIRCUIT}Verifier.sol
# Update the contract name in the Solidity verifier
sed -i "s/contract Verifier/contract ${CIRCUIT^}Verifier/g" ${CIRCUIT}Verifier.sol

echo "----- Generate and print parameters of call -----"
# Generate and print parameters of call
snarkjs generatecall | tee parameters.txt
```

Next we run the file: (**Note: This process takes a lot of time so patience is advised)**

If running for the first time use this command:

```bash
chmod u+x executeGroth16.sh
```

After that, use this command

```bash
./executeGroth16.sh
```

Running this produces an output like the one shown below, with success message `[INFO] snarkJS: OK!`:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700652888443/866ff8cf-5c40-4ce4-a906-6789ef99b1d2.png align="center")

### e) Testing the Circuits

Inside the <mark>circuits</mark> folder, install [chai](https://www.chaijs.com/) and install [circom\_tester](https://github.com/iden3/circom_tester):

```bash
$yarn add -D chai
$yarn add circom_tester
```

Create a test folder and inside the test folder create a circuit.js file and add to it:

```javascript
const { assert } = require("chai");
const wasm_tester = require("circom_tester").wasm;

describe("Sudoku circuit", function () {
  let sudokuCircuit;

  before(async function () {
    sudokuCircuit = await wasm_tester("sudoku/sudoku.circom");
  });

  it("Should generate the witness successfully", async function () {
    let input = {
      unsolved: [
        [0, 0, 0, 0, 0, 6, 0, 0, 0],
        [0, 0, 7, 2, 0, 0, 8, 0, 0],
        [9, 0, 6, 8, 0, 0, 0, 1, 0],
        [3, 0, 0, 7, 0, 0, 0, 2, 9],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [4, 0, 0, 5, 0, 0, 0, 7, 0],
        [6, 5, 0, 1, 0, 0, 0, 0, 0],
        [8, 0, 1, 0, 5, 0, 3, 0, 0],
        [7, 9, 2, 0, 0, 0, 0, 0, 4],
      ],
      solved: [
        [1, 8, 4, 3, 7, 6, 2, 9, 5],
        [5, 3, 7, 2, 9, 1, 8, 4, 6],
        [9, 2, 6, 8, 4, 5, 7, 1, 3],
        [3, 6, 5, 7, 1, 8, 4, 2, 9],
        [2, 7, 8, 4, 6, 9, 5, 3, 1],
        [4, 1, 9, 5, 3, 2, 6, 7, 8],
        [6, 5, 3, 1, 2, 4, 9, 8, 7],
        [8, 4, 1, 9, 5, 7, 3, 6, 2],
        [7, 9, 2, 6, 8, 3, 1, 5, 4],
      ],
    };
    const witness = await sudokuCircuit.calculateWitness(input);
    await sudokuCircuit.assertOut(witness, {});
  });
  it("Should fail because there is a number out of bounds", async function () {
    // The number 10 in the first row of solved is > 9
    let input = {
      unsolved: [
        [0, 0, 0, 0, 0, 6, 0, 0, 0],
        [0, 0, 7, 2, 0, 0, 8, 0, 0],
        [9, 0, 6, 8, 0, 0, 0, 1, 0],
        [3, 0, 0, 7, 0, 0, 0, 2, 9],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [4, 0, 0, 5, 0, 0, 0, 7, 0],
        [6, 5, 0, 1, 0, 0, 0, 0, 0],
        [8, 0, 1, 0, 5, 0, 3, 0, 0],
        [7, 9, 2, 0, 0, 0, 0, 0, 4],
      ],
      solved: [
        [1, 8, 4, 3, 7, 6, 2, 9, 10],
        [5, 3, 7, 2, 9, 1, 8, 4, 6],
        [9, 2, 6, 8, 4, 5, 7, 1, 3],
        [3, 6, 5, 7, 1, 8, 4, 2, 9],
        [2, 7, 8, 4, 6, 9, 5, 3, 1],
        [4, 1, 9, 5, 3, 2, 6, 7, 8],
        [6, 5, 3, 1, 2, 4, 9, 8, 7],
        [8, 4, 1, 9, 5, 7, 3, 6, 2],
        [7, 9, 2, 6, 8, 3, 1, 5, 4],
      ],
    };
    try {
      await sudokuCircuit.calculateWitness(input);
    } catch (err) {
      // console.log(err);
      assert(err.message.includes("Assert Failed"));
    }
  });
  it("Should fail because unsolved is not the initial state of solved", async function () {
    // unsolved is not the initial state of solved
    let input = {
      unsolved: [
        [0, 0, 0, 0, 0, 6, 0, 0, 0],
        [0, 0, 7, 2, 0, 0, 8, 0, 0],
        [9, 0, 6, 8, 0, 0, 0, 1, 0],
        [3, 0, 0, 7, 0, 0, 0, 2, 9],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [4, 0, 0, 5, 0, 0, 0, 7, 0],
        [6, 5, 0, 1, 0, 0, 0, 0, 0],
        [8, 0, 1, 0, 5, 0, 3, 0, 0],
        [7, 9, 2, 0, 0, 0, 0, 0, 4],
      ],
      solved: [
        [1, 2, 7, 5, 8, 4, 6, 9, 3],
        [8, 5, 6, 3, 7, 9, 1, 2, 4],
        [3, 4, 9, 6, 2, 1, 8, 7, 5],
        [4, 7, 1, 9, 5, 8, 2, 3, 6],
        [2, 6, 8, 7, 1, 3, 5, 4, 9],
        [9, 3, 5, 4, 6, 2, 7, 1, 8],
        [5, 8, 3, 2, 9, 7, 4, 6, 1],
        [7, 1, 4, 8, 3, 6, 9, 5, 2],
        [6, 9, 2, 1, 4, 5, 3, 8, 7],
      ],
    };
    try {
      await sudokuCircuit.calculateWitness(input);
    } catch (err) {
      // console.log(err);
      assert(err.message.includes("Assert Failed"));
    }
  });
  it("Should fail due to repeated numbers in a row", async function () {
    // The number 1 in the first row of solved is twice
    let input = {
      unsolved: [
        [0, 0, 0, 0, 0, 6, 0, 0, 0],
        [0, 0, 7, 2, 0, 0, 8, 0, 0],
        [9, 0, 6, 8, 0, 0, 0, 1, 0],
        [3, 0, 0, 7, 0, 0, 0, 2, 9],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [4, 0, 0, 5, 0, 0, 0, 7, 0],
        [6, 5, 0, 1, 0, 0, 0, 0, 0],
        [8, 0, 1, 0, 5, 0, 3, 0, 0],
        [7, 9, 2, 0, 0, 0, 0, 0, 4],
      ],
      solved: [
        [1, 8, 4, 3, 7, 6, 2, 9, 1],
        [5, 3, 7, 2, 9, 1, 8, 4, 6],
        [9, 2, 6, 8, 4, 5, 7, 1, 3],
        [3, 6, 5, 7, 1, 8, 4, 2, 9],
        [2, 7, 8, 4, 6, 9, 5, 3, 1],
        [4, 1, 9, 5, 3, 2, 6, 7, 8],
        [6, 5, 3, 1, 2, 4, 9, 8, 7],
        [8, 4, 1, 9, 5, 7, 3, 6, 2],
        [7, 9, 2, 6, 8, 3, 1, 5, 4],
      ],
    };
    try {
      await sudokuCircuit.calculateWitness(input);
    } catch (err) {
      // console.log(err);
      assert(err.message.includes("Assert Failed"));
    }
  });
  it("Should fail due to repeated numbers in a column", async function () {
    // The number 4 in the first column of solved is twice and the number 7 in the last column of solved is twice too
    let input = {
      unsolved: [
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
      ],
      solved: [
        [1, 8, 4, 3, 7, 6, 2, 9, 5],
        [5, 3, 7, 2, 9, 1, 8, 4, 6],
        [9, 2, 6, 8, 4, 5, 7, 1, 3],
        [3, 6, 5, 7, 1, 8, 4, 2, 9],
        [2, 7, 8, 4, 6, 9, 5, 3, 1],
        [4, 1, 9, 5, 3, 2, 6, 7, 8],
        [6, 5, 3, 1, 2, 4, 9, 8, 7],
        [8, 4, 1, 9, 5, 7, 3, 6, 2],
        [4, 9, 2, 6, 8, 3, 1, 5, 7],
      ],
    };
    try {
      await sudokuCircuit.calculateWitness(input);
    } catch (err) {
      // console.log(err);
      assert(err.message.includes("Assert Failed"));
    }
  });
  it("Should fail due to repeated numbers in a square", async function () {
    // The number 1 in the first square (top-left) of solved is twice
    let input = {
      unsolved: [
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
      ],
      solved: [
        [1, 8, 4, 3, 7, 6, 2, 9, 5],
        [5, 3, 7, 2, 9, 1, 8, 4, 6],
        [9, 2, 1, 8, 4, 5, 7, 6, 3],
        [3, 6, 5, 7, 1, 8, 4, 2, 9],
        [2, 7, 8, 4, 6, 9, 5, 3, 1],
        [4, 1, 9, 5, 3, 2, 6, 7, 8],
        [6, 5, 3, 1, 2, 4, 9, 8, 7],
        [8, 4, 6, 9, 5, 7, 3, 1, 2],
        [7, 9, 2, 6, 8, 3, 1, 5, 4],
      ],
    };
    try {
      await sudokuCircuit.calculateWitness(input);
    } catch (err) {
      // console.log(err);
      assert(err.message.includes("Assert Failed"));
    }
  });
});
```

Add the `mocha` test script to the <mark>package.json</mark> file which will look like this:

```json
{
  "name": "circuits",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "test": "mocha"
  },
  "dependencies": {
    "circom_tester": "^0.0.11",
    "circomlib": "^2.0.3"
  },
  "devDependencies": {
    "chai": "^4.3.6"
  }
}
```

Run the tests using yarn:

```bash
yarn test
```

## Smart Contracts

### a) Initializing

We are going to use the `hardhat` framework to work with smart contracts. You can read more about hardhat in the [hardhat documentation](https://hardhat.org/hardhat-runner/docs/getting-started#overview).

Inside the <mark>projects</mark> folder, create a <mark>contracts</mark> folder and inside the contracts folder create a <mark>package.json</mark> file:

```bash
$ mkdir contracts
$ cd contracts
$yarn init -y
```

Add the `hardhat` library and initialize the default hardhat project(`create a basic sample project`).

```bash
$yarn add -D hardhat
$npx hardhat
```

You can delete the sample files that have been provided, don't delete the folders.

### b) Creating the smart contracts

Run the following command to copy the `sudokuVerifier.sol` contract generated before into the <mark>contracts</mark> folder:

```bash
cp ../circuits/sudoku/sudokuVerifier.sol contracts
```

Inside the <mark>Projects/contracts/contracts</mark> folder create the Sudoku.sol file and add to it:

```solidity
//SPDX-License-Identifier: Unlicense

pragma solidity ^0.8.4;

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

contract Sudoku {
    address public verifierAddr;

    uint8[9][9][3] sudokuBoardList = [
        [
            [1, 2, 7, 5, 8, 4, 6, 9, 3],
            [8, 5, 6, 3, 7, 9, 1, 2, 4],
            [3, 4, 9, 6, 2, 1, 8, 7, 5],
            [4, 7, 1, 9, 5, 8, 2, 3, 6],
            [2, 6, 8, 7, 1, 3, 5, 4, 9],
            [9, 3, 5, 4, 6, 2, 7, 1, 8],
            [5, 8, 3, 2, 9, 7, 4, 6, 1],
            [7, 1, 4, 8, 3, 6, 9, 5, 2],
            [6, 9, 2, 1, 4, 5, 3, 0, 7]
        ],
        [
            [0, 2, 7, 5, 0, 4, 0, 0, 0],
            [0, 0, 0, 3, 7, 0, 0, 0, 4],
            [3, 0, 0, 0, 0, 0, 8, 0, 0],
            [4, 7, 0, 9, 5, 8, 0, 3, 6],
            [2, 6, 8, 7, 1, 0, 0, 4, 9],
            [0, 0, 0, 0, 0, 2, 0, 1, 8],
            [0, 8, 3, 0, 9, 0, 4, 0, 0],
            [7, 1, 0, 0, 0, 0, 9, 0, 2],
            [0, 0, 0, 0, 0, 5, 0, 0, 7]
        ],
        [
            [0, 0, 0, 0, 0, 6, 0, 0, 0],
            [0, 0, 7, 2, 0, 0, 8, 0, 0],
            [9, 0, 6, 8, 0, 0, 0, 1, 0],
            [3, 0, 0, 7, 0, 0, 0, 2, 9],
            [0, 0, 0, 0, 0, 0, 0, 0, 0],
            [4, 0, 0, 5, 0, 0, 0, 7, 0],
            [6, 5, 0, 1, 0, 0, 0, 0, 0],
            [8, 0, 1, 0, 5, 0, 3, 0, 0],
            [7, 9, 2, 0, 0, 0, 0, 0, 4]
        ]
    ];

    constructor(address _verifierAddr) {
        verifierAddr = _verifierAddr;
    }

    function verifyProof(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256[81] memory input
    ) public view returns (bool) {
        return IVerifier(verifierAddr).verifyProof(a, b, c, input);
    }

    function verifySudokuBoard(uint256[81] memory board)
        private
        view
        returns (bool)
    {
        bool isEqual = true;
        for (uint256 i = 0; i < sudokuBoardList.length; ++i) {
            isEqual = true;
            for (uint256 j = 0; j < sudokuBoardList[i].length; ++j) {
                for (uint256 k = 0; k < sudokuBoardList[i][j].length; ++k) {
                    if (board[9 * j + k] != sudokuBoardList[i][j][k]) {
                        isEqual = false;
                        break;
                    }
                }
            }
            if (isEqual == true) {
                return isEqual;
            }
        }
        return isEqual;
    }

    function verifySudoku(
        uint256[2] memory a,
        uint256[2][2] memory b,
        uint256[2] memory c,
        uint256[81] memory input
    ) public view returns (bool) {
        require(verifySudokuBoard(input), "This board does not exist");
        require(verifyProof(a, b, c, input), "Filed proof check");
        return true;
    }

    function pickRandomBoard(string memory stringTime)
        private
        view
        returns (uint8[9][9] memory)
    {
        uint256 randPosition = uint256(
            keccak256(
                abi.encodePacked(
                    block.difficulty,
                    block.timestamp,
                    msg.sender,
                    stringTime
                )
            )
        ) % sudokuBoardList.length;
        return sudokuBoardList[randPosition];
    }

    function generateSudokuBoard(string memory stringTime)
        public
        view
        returns (uint8[9][9] memory)
    {
        return pickRandomBoard(stringTime);
    }
}
```

### c) Testing the smart contract

Add the same version of the snarkjs library to test the computing of the proof:

**Note**: Ensure that the global and the local version of snarkjs are the same

```bash
$yarn add snarkjs
```

Create a <mark>zkProof</mark> folder and copy the `sudoku.wasm` and `sudoku_final.zkey` files into the <mark>zkProof </mark> folder.

```bash
 cp ../circuits/sudoku/sudoku_js/sudoku.wasm zkproof
 cp ../circuits/sudoku/sudoku_final.zkey zkproof
```

Create a `test.js` in the <mark>test</mark> folder and add to it:

```javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { exportCallDataGroth16 } = require("./utils/utils");

describe("Sudoku", function () {
  let SudokuVerifier, sudokuVerifier, Sudoku, sudoku;

  before(async function () {
    SudokuVerifier = await ethers.getContractFactory("SudokuVerifier");
    sudokuVerifier = await SudokuVerifier.deploy();
    await sudokuVerifier.deployed();

    Sudoku = await ethers.getContractFactory("Sudoku");
    sudoku = await Sudoku.deploy(sudokuVerifier.address);
    await sudoku.deployed();
  });

  it("Should generate a board", async function () {
    let board = await sudoku.generateSudokuBoard(new Date().toString());
    expect(board.length).to.equal(9);
  });

  it("Should return true for valid proof on-chain", async function () {
    const unsolved = [
      [1, 2, 7, 5, 8, 4, 6, 9, 3],
      [8, 5, 6, 3, 7, 9, 1, 2, 4],
      [3, 4, 9, 6, 2, 1, 8, 7, 5],
      [4, 7, 1, 9, 5, 8, 2, 3, 6],
      [2, 6, 8, 7, 1, 3, 5, 4, 9],
      [9, 3, 5, 4, 6, 2, 7, 1, 8],
      [5, 8, 3, 2, 9, 7, 4, 6, 1],
      [7, 1, 4, 8, 3, 6, 9, 5, 2],
      [6, 9, 2, 1, 4, 5, 3, 0, 7],
    ];

    const solved = [
      [1, 2, 7, 5, 8, 4, 6, 9, 3],
      [8, 5, 6, 3, 7, 9, 1, 2, 4],
      [3, 4, 9, 6, 2, 1, 8, 7, 5],
      [4, 7, 1, 9, 5, 8, 2, 3, 6],
      [2, 6, 8, 7, 1, 3, 5, 4, 9],
      [9, 3, 5, 4, 6, 2, 7, 1, 8],
      [5, 8, 3, 2, 9, 7, 4, 6, 1],
      [7, 1, 4, 8, 3, 6, 9, 5, 2],
      [6, 9, 2, 1, 4, 5, 3, 8, 7],
    ];

    const input = {
      unsolved: unsolved,
      solved: solved,
    };

    let dataResult = await exportCallDataGroth16(
      input,
      "./zkproof/sudoku.wasm",
      "./zkproof/sudoku_final.zkey"
    );

    // Call the function.
    let result = await sudokuVerifier.verifyProof(
      dataResult.a,
      dataResult.b,
      dataResult.c,
      dataResult.Input
    );
    expect(result).to.equal(true);
  });

  it("Should return false for invalid proof on-chain", async function () {
    let a = [0, 0];
    let b = [
      [0, 0],
      [0, 0],
    ];
    let c = [0, 0];
    let Input = new Array(81).fill(0);

    let dataResult = { a, b, c, Input };

    // Call the function.
    let result = await sudokuVerifier.verifyProof(
      dataResult.a,
      dataResult.b,
      dataResult.c,
      dataResult.Input
    );
    expect(result).to.equal(false);
  });
  it("Should verify Sudoku successfully", async function () {
    const unsolved = [
      [1, 2, 7, 5, 8, 4, 6, 9, 3],
      [8, 5, 6, 3, 7, 9, 1, 2, 4],
      [3, 4, 9, 6, 2, 1, 8, 7, 5],
      [4, 7, 1, 9, 5, 8, 2, 3, 6],
      [2, 6, 8, 7, 1, 3, 5, 4, 9],
      [9, 3, 5, 4, 6, 2, 7, 1, 8],
      [5, 8, 3, 2, 9, 7, 4, 6, 1],
      [7, 1, 4, 8, 3, 6, 9, 5, 2],
      [6, 9, 2, 1, 4, 5, 3, 0, 7],
    ];

    const solved = [
      [1, 2, 7, 5, 8, 4, 6, 9, 3],
      [8, 5, 6, 3, 7, 9, 1, 2, 4],
      [3, 4, 9, 6, 2, 1, 8, 7, 5],
      [4, 7, 1, 9, 5, 8, 2, 3, 6],
      [2, 6, 8, 7, 1, 3, 5, 4, 9],
      [9, 3, 5, 4, 6, 2, 7, 1, 8],
      [5, 8, 3, 2, 9, 7, 4, 6, 1],
      [7, 1, 4, 8, 3, 6, 9, 5, 2],
      [6, 9, 2, 1, 4, 5, 3, 8, 7],
    ];

    const input = {
      unsolved: unsolved,
      solved: solved,
    };

    let dataResult = await exportCallDataGroth16(
      input,
      "./zkproof/sudoku.wasm",
      "./zkproof/sudoku_final.zkey"
    );

    // Call the function.
    let result = await sudoku.verifySudoku(
      dataResult.a,
      dataResult.b,
      dataResult.c,
      dataResult.Input
    );
    expect(result).to.equal(true);
  });
  it("Should be reverted on Sudoku verification because the board is not in the board list", async function () {
    const unsolved = [
      [1, 2, 7, 5, 8, 4, 6, 9, 3],
      [8, 5, 6, 3, 7, 9, 1, 2, 4],
      [3, 4, 9, 6, 2, 1, 8, 7, 5],
      [4, 7, 1, 9, 5, 8, 2, 3, 6],
      [2, 6, 8, 7, 1, 3, 5, 4, 9],
      [9, 3, 5, 4, 6, 2, 7, 1, 8],
      [5, 8, 3, 2, 9, 7, 4, 6, 1],
      [7, 1, 4, 8, 3, 6, 9, 5, 2],
      [6, 9, 2, 1, 4, 5, 3, 8, 0],
    ];

    const solved = [
      [1, 2, 7, 5, 8, 4, 6, 9, 3],
      [8, 5, 6, 3, 7, 9, 1, 2, 4],
      [3, 4, 9, 6, 2, 1, 8, 7, 5],
      [4, 7, 1, 9, 5, 8, 2, 3, 6],
      [2, 6, 8, 7, 1, 3, 5, 4, 9],
      [9, 3, 5, 4, 6, 2, 7, 1, 8],
      [5, 8, 3, 2, 9, 7, 4, 6, 1],
      [7, 1, 4, 8, 3, 6, 9, 5, 2],
      [6, 9, 2, 1, 4, 5, 3, 8, 7],
    ];

    const input = {
      unsolved: unsolved,
      solved: solved,
    };

    let dataResult = await exportCallDataGroth16(
      input,
      "./zkproof/sudoku.wasm",
      "./zkproof/sudoku_final.zkey"
    );

    await expect(
      sudoku.verifySudoku(
        dataResult.a,
        dataResult.b,
        dataResult.c,
        dataResult.Input
      )
    ).to.be.reverted;
  });
});
```

Create a <mark>utils</mark> folder inside the <mark>test</mark> folder. Create a `utils.js` file in the <mark>utils</mark> folder and add to it:

```javascript
const { groth16 } = require("snarkjs");

async function exportCallDataGroth16(input, wasmPath, zkeyPath) {
  const { proof: _proof, publicSignals: _publicSignals } =
    await groth16.fullProve(input, wasmPath, zkeyPath);
  const calldata = await groth16.exportSolidityCallData(_proof, _publicSignals);

  const argv = calldata
    .replace(/["[\]\s]/g, "")
    .split(",")
    .map((x) => BigInt(x).toString());

  const a = [argv[0], argv[1]];
  const b = [
    [argv[2], argv[3]],
    [argv[4], argv[5]],
  ];
  const c = [argv[6], argv[7]];
  const Input = [];

  for (let i = 8; i < argv.length; i++) {
    Input.push(argv[i]);
  }

  return { a, b, c, Input };
}

module.exports = {
  exportCallDataGroth16,
};
```

Some additional libraries and packages that will be useful in testing the contracts could be added as shown below:

hardhat-gas-reporter:

```bash
$yarn add -D hardhat-gas-reporter
```

Add it to the `hardhat.config.js` file after the last `require :`

```javascript
require("hardhat-gas-reporter");
```

Optimizer: Modify the `hardhat.config.js` file:

```javascript
solidity: {
    version: "0.8.4",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
```

To test the contracts run the following command:

```bash
$npx hardhat test
```

You will see an output similar to this: (**Note**: This is inclusive of the whole zkArcade project so yours might look a bit different)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700658395480/e62a1ff5-22eb-423d-82c7-18ec732d9519.png align="center")

### d) Run Smart Contracts

Inside the <mark>scripts</mark> folder, create a `run.js` file and add to it:

* ```javascript
      const main = async () => {
        const SudokuVerifier = await hre.ethers.getContractFactory("SudokuVerifier");
        const sudokuVerifier = await SudokuVerifier.deploy();
        await sudokuVerifier.deployed();
        console.log("SudokuVerifier Contract deployed to:", sudokuVerifier.address);
      
        const Sudoku = await hre.ethers.getContractFactory("Sudoku");
        const sudoku = await Sudoku.deploy(sudokuVerifier.address);
        await sudoku.deployed();
        console.log("Sudoku Contract deployed to:", sudoku.address);
      
        let board = await sudoku.generateSudokuBoard(new Date().toString());
        console.log(board);
      
        let callDataSudoku = [
          [
            "0x2c5defbc1706b51a941bff57cc1e40f2c941fccbbf13c587de8bf36dc1017b56",
            "0x10c1184d623f5f8efa15052e3c59613a80507c11f0605c998c36a0336bb4012f",
          ],
          [
            [
              "0x21fcd189100631f163579f405875def48a41c0f6b1dae436a71feebcc84af110",
              "0x0ac4517b8693891029159dd4d5b15e17fbe0ac27068cf14a9781e3a347352cd6",
            ],
            [
              "0x0f43d6e3ace2228a67578fcb7778d248b274f6342ed14828454d343c0a933110",
              "0x075df9f66bb65d47e04caa80d6f8e99fbd8db5217d259a3aac80709b5cb37347",
            ],
          ],
          [
            "0x05f56ad16658e304882d42230c634626848f981c51d7a34f5f9833f7272447fb",
            "0x1405302f7a377aa913513f026ea72e3544484d66ce27204e72787ce4bf3229b4",
          ],
          [
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000006",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000007",
            "0x0000000000000000000000000000000000000000000000000000000000000002",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000008",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000009",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000006",
            "0x0000000000000000000000000000000000000000000000000000000000000008",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000001",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000003",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000007",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000002",
            "0x0000000000000000000000000000000000000000000000000000000000000009",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000004",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000005",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000007",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000006",
            "0x0000000000000000000000000000000000000000000000000000000000000005",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000001",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000008",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000001",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000005",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000003",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000007",
            "0x0000000000000000000000000000000000000000000000000000000000000009",
            "0x0000000000000000000000000000000000000000000000000000000000000002",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000000",
            "0x0000000000000000000000000000000000000000000000000000000000000004",
          ],
        ];
      
        // Call the function.
        let result = await sudokuVerifier.verifyProof(
          callDataSudoku[0],
          callDataSudoku[1],
          callDataSudoku[2],
          callDataSudoku[3]
        );
      
        console.log("Result", result);
      };
      
      const runMain = async () => {
        try {
          await main();
          process.exit(0);
        } catch (error) {
          console.log(error);
          process.exit(1);
        }
      };
      
      runMain();
    ```
    
    `callDataSudoku` is the data of the `parameters.txt` file generated before, the `verifyProof` function will return true. If you change an element of the `callDataSudoku` variable, the `verify Proof` function will return false.
    

Run the `run.js` file

```bash
$npx hardhat run scripts/run.js
```

### e) Deploying the Smart contracts

We are going to deploy the contracts on [Polygon Mumbai](https://mumbai.polygonscan.com/). Ensure you have [Metamask](https://metamask.io/) installed and have [added the polygon mumbai network to Metamask](https://wiki.polygon.technology/docs/tools/wallets/metamask/config-polygon-on-metamask/).

Inside the <mark>scripts</mark> folder, create a `deploy.js` file and add to it:

```javascript
const main = async () => {
  const SudokuVerifier = await hre.ethers.getContractFactory("SudokuVerifier");
  const sudokuVerifier = await SudokuVerifier.deploy();
  await sudokuVerifier.deployed();
  console.log("SudokuVerifier Contract deployed to:", sudokuVerifier.address);

  const Sudoku = await hre.ethers.getContractFactory("Sudoku");
  const sudoku = await Sudoku.deploy(sudokuVerifier.address);
  await sudoku.deployed();
  console.log("Sudoku Contract deployed to:", sudoku.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();
```

Create a `.env` file and add to it. Add your private key

**<mark>Warning: Ensure that the </mark>** `.env` **<mark>file is in the </mark>** `.gitignore` **<mark>file and don't use any real crypto in this wallet account</mark>**

```javascript
PRIVATE_KEY=
```

Add `.env` file to the `.gitignore` file

```makefile
node_modules
.env
coverage
coverage.json
typechain

#Hardhat files
cache
artifacts
```

Install the [dotenv](https://github.com/motdotla/dotenv) library and edit the `hardhatconfig.js` file to read the private key from `.env`

```bash
$yarn add dotenv
```

hardhat.config.js

```javascript
require("@nomiclabs/hardhat-waffle");
require("hardhat-gas-reporter");
require("dotenv").config();

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: {
    version: "0.8.4",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  networks: {
    sepolia: {
      url: "https://rpc.sepolia.org/",
      accounts: [process.env.PRIVATE_KEY],
    },
    mumbai: {
      url: "https://rpc-mumbai.maticvigil.com",
      accounts: [process.env.PRIVATE_KEY],
    },
  },
};
```

Get MATIC from the Mumbai faucet: [https://faucet.polygon.technology/](https://faucet.polygon.technology/)

Run the `deploy.js` file:

```bash
$npx hardhat run scripts/deploy.js --network mumbai
```

You can see the transactions on the mumbai explorer: [https://mumbai.polygonscan.com/](https://mumbai.polygonscan.com/)

## Creating the Frontend

### a) Initializing the project

Now lets create the frontend to interact with our zkDApp.

We are going to use Next.js. More information about it can be found here: [https://nextjs.org/](https://nextjs.org/)

Inside the <mark>Projects</mark> folder run this command:

```bash
yarn create next-app frontend
```

Go inside the <mark>frontend</mark> folder and start a server to check that everything is installed correctly:

```bash
cd frontend
$yarn dev
```

Open a browser and go to http://localhost:3000/

Something similar should appear

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700660092690/6a7914ab-ee9b-491e-a686-e081089bf353.avif align="center")

### b) Adding Libraries

Add Tailwind for page styling: [https://tailwindcss.com/docs/guides/nextjs](https://tailwindcss.com/docs/guides/nextjs)  
Add the following libraries

```bash
yarn add wagmi ethers
yarn add snarkjs
```

Import the snarkjs to the client side using either of these methods

1. Use snark.min.js file
    
    ```bash
    cp ./node_modules/snarkjs/build/snarkjs.min.js ./public
    ```
    
    Add `<Script id="snarkjs" src="/snarkjs.min.js" />` in `layout.js`
    
    You can access the library using `window.snarkjs`.
    

2\. As a package using yarn as shown above.

Configure webpack in the `next.config.js` file to use `snarkjs`:

```javascript
const nextConfig = {
  reactStrictMode: true,
  webpack: function (config, options) {
    if (!options.isServer) {
      config.resolve.fallback.fs = false;
    }
    config.experiments = { asyncWebAssembly: true };
    return config;
  },
};

module.exports = nextConfig;
```

**Note:** The `config.experiments = { asyncWebAssembly: true };` line is for using wasm files.

### c) Adding configuration

To generate the proof, create a `.babelrc` file and add to it

```javascript
{
  "presets": [
    [
      "next/babel",
      {
        "preset-env": {
          // "debug": true,
          "targets": [
            "last 2 Edge versions",
            "last 2 Opera versions",
            "last 2 Safari versions",
            "last 2 Chrome versions",
            "last 2 Firefox versions"
          ]
        }
      }
    ]
  ]
}
```

### d) Adding Zero Knowledge to the frontend

Inside the <mark>public</mark> folder, create a <mark>zkProof</mark> folder and copy the `sudoku.wasm` and `sudoku_final.key` files there:

```bash
 cp ../../circuits/sudoku/sudoku_js/sudoku.wasm ./public/zkproof
 cp ../../circuits/sudoku/sudoku_final.zkey ./public/zkproof
```

In the <mark>frontend</mark> folder, create a <mark>zkproof</mark> folder and inside the <mark>zkproof</mark> folder create the `snarkjsZkproof.js` file and add to it:

```javascript
import { groth16 } from "snarkjs";

export async function exportCallDataGroth16(input, wasmPath, zkeyPath) {
  const { proof: _proof, publicSignals: _publicSignals } =
    await groth16.fullProve(input, wasmPath, zkeyPath);

  const calldata = await groth16.exportSolidityCallData(_proof, _publicSignals);

  const argv = calldata
    .replace(/["[\]\s]/g, "")
    .split(",")
    .map((x) => BigInt(x).toString());

  const a = [argv[0], argv[1]];
  const b = [
    [argv[2], argv[3]],
    [argv[4], argv[5]],
  ];
  const c = [argv[6], argv[7]];
  const Input = [];

  for (let i = 8; i < argv.length; i++) {
    Input.push(argv[i]);
  }

  return { a, b, c, Input };
}
```

Some errors may occur.

If you get an error importing `groth16` from `snarkjs` that says:

```javascript
./node_modules/fastfile/src/fastfile.js
Can't import the named export 'O_TRUNC' (imported as 'O_TRUNC') from default-exporting module (only default export is available)
```

This is solve by importing groth16 this way:

```javascript
const groth16 = require("snarkjs").groth16;
```

or you can try this way

```javascript
const { groth16 } = require("snarkjs");
```

Another error may be like this:

```javascript
./node_modules/snarkjs/build/main.cjs:8:0
Module not found: Can't resolve 'readline'
```

Then add the `config.resolve.fallback.readline = false;` line in the `next.config.js`

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack: function (config, options) {
    if (!options.isServer) {
      config.resolve.fallback.fs = false;
      config.resolve.fallback.readline = false;
    }
    config.experiments = { asyncWebAssembly: true };
    return config;
  },
};

module.exports = nextConfig;
```

Okay, now away from troubleshooting errors, inside the <mark>frontend</mark> folder create the `sudoku` folder and inside this new folder created, create the `snarkjsSudoku.js` file and add to it:

```javascript
import { exportCallDataGroth16 } from "../snarkjsZkproof";

export async function sudokuCalldata(unsolved, solved) {
  const input = {
    unsolved: unsolved,
    solved: solved,
  };

  let dataResult;

  try {
    dataResult = await exportCallDataGroth16(
      input,
      "/zkproof/sudoku.wasm",
      "/zkproof/sudoku_final.zkey"
    );
  } catch (error) {
    // console.log(error);
    window.alert("Wrong answer");
  }

  return dataResult;
}
```

### e) Connecting the smart contracts to verify the proof

Connection of the contracts using ABI

```javascript
const contract = useContract({
  addressOrName: contractAddress.sudokuContract,
  contractInterface: sudokuContractAbi.abi,
  signerOrProvider: signer || provider,
});
```

Now to use the `verifySudoku` function

```javascript
result = await contract.verifySudoku(
  calldata.a,
  calldata.b,
  calldata.c,
  calldata.Input
);
```

### f) Adding utility files

Create a <mark>utils</mark> folder inside the <mark>frontend</mark> folder. Inside the utils folder add;

1. The `abiFiles` folder which contains all the abi files needed for the frontend application. You can find the abi file here: `Projects/contracts/artifacts/contracts/Sudoku.sol/Sudoku.json`. The abi file is a file generated when the smart contract is compiled.
    
2. The `contractsaddress.json` file that contains all the necessary smart contracts addresses, in this case, the `Sudoku` contract:
    

```json
    {
      "sudokuContract": "0xD0276C2f1353157A562400309560C9cdCBA47212"
    }
```

3\. The `networks.json` file which contains all the chains that we can use in the app and the `selectedChain` which is the network used in this project (Mumbai):

```json
    {
      "selectedChain": "11155111",
      "1337": {
        "chainId": "1337",
        "chainName": "Localhost 8545",
        "rpcUrls": ["http://localhost:8545"],
        "nativeCurrency": {
          "symbol": "ETH"
        },
        "blockExplorerUrls": []
      },
      "11155111": {
        "chainId": "11155111",
        "chainName": "Sepolia",
        "rpcUrls": ["https://rpc.sepolia.org"],
        "nativeCurrency": {
          "symbol": "ETH"
        },
        "blockExplorerUrls": ["https://sepolia.etherscan.io/"]
      },
      "80001": {
        "chainId": "80001",
        "chainName": "Mumbai",
        "rpcUrls": ["https://rpc-mumbai.maticvigil.com"],
        "nativeCurrency": {
          "symbol": "MATIC"
        },
        "blockExplorerUrls": ["https://mumbai.polygonscan.com/"]
      }
    }
```

* The `switchNetwork.js` file to switch to the network used in the projects if necessary. (In this project we are using Mumbai)
    
* ```javascript
          import networks from "../utils/networks.json";
      
          export const switchNetwork = async () => {
            if (window.ethereum) {
              try {
                // Try to switch to the chain
                await ethereum.request({
                  method: "wallet_switchEthereumChain",
                  params: [
                    { chainId: `0x${parseInt(networks.selectedChain).toString(16)}` },
                  ],
                });
              } catch (switchError) {
                // This error code indicates that the chain has not been added to MetaMask.
                if (switchError.code === 4902) {
                  try {
                    await ethereum.request({
                      method: "wallet_addEthereumChain",
                      params: [
                        {
                          chainId: `0x${parseInt(networks.selectedChain).toString(16)}`,
                          chainName: networks[networks.selectedChain].chainName,
                          rpcUrls: networks[networks.selectedChain].rpcUrls,
                          nativeCurrency: {
                            symbol:
                              networks[networks.selectedChain].nativeCurrency.symbol,
                            decimals: 18,
                          },
                          blockExplorerUrls:
                            networks[networks.selectedChain].blockExplorerUrls,
                        },
                      ],
                    });
                  } catch (addError) {
                    console.log(addError);
                  }
                }
                // handle other "switch" errors
              }
            } else {
              // If window.ethereum is not found then MetaMask is not installed
              alert(
                "MetaMask is not installed. Please install it to use this app: https://metamask.io/download/"
              );
            }
          };
    ```
    

### g) Some Code cleanup

* Delete:
    
    * The `api` folder inside the `pages` folder.
        
    * The `favicon.ico` file, inside the `public` folder.
        
    * The `vercel.svg` file, inside the `public` folder.
        
* Create:
    
    * The `assets` folder and add the image inside it.
        
    * The `components` folder and copy all the code inside that folder.
        
* Copy:
    
    * All the files inside the `pages` folder.
        
    * All the files inside the `styles` folder.
        
    * The `favicon.ico` and `socialMedia.png` files, inside the `public` folder.
        

### h) Deploying the frontend

In this tutorial, we are going to deploy the frontend on vercel

* Create a new [**Github**](https://github.com/) repository and push the project into it.
    
* Go to Vercel:
    
    * Create a new project.
        
    * Import the Github repo created before.
        
    * Configure the project (select `Next.js` as FRAMEWORK PRESET and `frontend` as ROOT DIRECTORY) and click `Deploy`:
        

You can read more about vercel here: [https://vercel.com/docs](https://vercel.com/docs)

### [zkDApp DEMO](http://zk-arcade.vercel.app/)

The deployed website of the of this tutorial alongside 2 other games can be found here: [http://zk-arcade.vercel.app/](http://zk-arcade.vercel.app/)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700663802323/d969b842-48fd-4383-81e2-b9586de8a77d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1700663817212/9b926ee8-195f-4351-84d7-543b572578ef.png align="center")

## Tips and Conclusion

* If you change the circuit, you should update:
    
    * `sudokuVerifier.sol` inside `contracts/contracts`.
        
    * `sudoku.wasm` and `sudoku_final.zkey` files inside `contracts/zkproof`.
        
    * `sudoku.wasm` and `sudoku_final.zkey` files inside `frontend/public/zkproof`.
        
* If you change smart contracts you should update if necessary:
    
    * The `Sudoku.json` abi file inside `frontend/utils/abiFiles`.
        
    * The `contractaddress.json` file inside `frontend/utils/`(with the new Sudoku smart contract address in case smart contracts are deployed again).
        
* If you change the network used to deploy you should update `selectedChain` inside `frontend/utils/networks.json`.
    
* To copy or update all the zk elements to use and test smart contracts, you can create the [`copyZkFiles.sh`](http://copyZkFiles.sh) file inside `Projects/contracts/contracts/scripts` and add to it:
    
* ```bash
      #!/bin/bash
      
      # Copy the verifier
      cp ../circuits/sudoku/sudokuVerifier.sol contracts
      
      # Create the zkproof folder if it does not exist
      mkdir -p zkproof
      
      # Copy the wasm file to test smart contracts
      cp ../circuits/sudoku/sudoku_js/sudoku.wasm zkproof
      
      # Copy the final zkey file to test smart contracts
      cp ../circuits/sudoku/sudoku_final.zkey zkproof
    ```
    
    Running it for the first time
    
* ```bash
      chmod u+x scripts/copyZkFiles.sh
    ```
    
    Subsequent times
    
* ```bash
      ./scripts/copyZkFiles.sh
    ```
    
    * To copy or update all the zk elements to use in the frontend, you can create the [`copyZkFiles.sh`](http://copyZkFiles.sh) file inside `Projects/frontend/scripts` and add to it:
        
    * ```bash
          #!/bin/bash
          
          # Create the zkproof folder inside the public folder if it does not exist
          mkdir -p public/zkproof
          
          # Copy the wasm file
          cp ../circuits/sudoku/sudoku_js/sudoku.wasm public/zkproof
          
          # Copy the final zkey
          cp ../circuits/sudoku/sudoku_final.zkey public/zkproof
          
          # Create the abiFiles folder inside the utils folder if it does not exist
          mkdir -p utils/abiFiles
          
          # Copy the abi file
          cp ../contracts/artifacts/contracts/Sudoku.sol/Sudoku.json utils/abiFiles
        ```
        
    * Running it for the first time
        
    * ```bash
          chmod u+x scripts/copyZkFiles.sh
        ```
        
        Subsequent times
        
    * ```bash
          ./scripts/copyZkFiles.sh
        ```
        
    
    This completes the tutorial on how to create a fullstack zkDApp from start to finish using Groth16. Another way to have achieved this would have been to use Plonk instead of Groth16. Using Plonk, we can avoid a trusted ceremony for every circuit but it is slower than GRoth16 because the zkey file is larger .
