/Groth16

Minimalistic Growth16 snark system.

Primary LanguageSolidity

πŸ’Ž πŸ’Ž πŸ’Ž Elhaj πŸ’Ž πŸ’Ž πŸ’Ž

Groth16 snark :

in this section we gonna create a Minimalistic zero knowledge proof system using Groth16 snark

Getting start:

  • to generate the system we'll use :

  • there are some steps that we will follow to generate this system :
    1️⃣ create the circuit that we gonna use to generate the proof and the verification system.

    • this circuit is gonna implement the MIMC hash funciton. the reason of using this function cause it's a lightweight function and it will not cost alot of gas like the sha256 for example when we apply it on the blockchain.

    • we gonna implement this circuit using circom language.

    2️⃣ after implementing the circuit .we'll generate the setup for the Groth16.

Dependency:

Install node
First off, make sure you have a recent version of Node.js installed. While any version after v12 should work fine, we recommend you install v16 or later.

If you’re not sure which version of Node you have installed, you can run:

node -v

To download the latest version of Node, see here.

Install snarkjs

To install snarkjs run:

npm install -g snarkjs@latest

If you're seeing an error, try prefixing both commands with sudo and running them again.

Understand the help command

To see a list of all snarkjs commands, as well as descriptions about their inputs and outputs, run:

snarkjs --help

Install circom

To install circom, follow the instructions at installing circom.

Now let's implement the circuit

1. circuit:

  • this is the implementation circuit of the MIMC function in the circom language :

        /_
        --> the implementation of the MIMC hash function as a circuit :
        --> function : F(x) = (x + k + Ci)^3
        while : 1. (x) : the value that we gonna hash 2. (k) : a random constant for each round 3. (Ci): constant values that are diffrent in each round , and they are constant for each circuit
        NOTE: we choose (i)rounds , and run this function (i) times , in each round the (x) and (k) are constant and
        only the (Ci) get changed .
        NOTE: in our case we gonna use the power 5 ,for more randomness ;
        _/
      pragma circom 2.0.0;
      template mimc5() {
          // inputs and outputs :
          signal input x;
          signal input k;
          signal output out;
          var rounds = 15;
          var c[rounds] = [
              0,
              21469745217645236226405533686231592324177671190346326883245530828586381403876,
              50297292308054131785073003188640636012765603289604974664717077154647718767691,
              106253950757591248575183329665927832654891498741470681534742234294971120334749,
              16562112986402259118419179721668484133429300227020801196120440207549964854140,
              57306670214488528918457646459609688072645567370160016749464560944657195907328,
              108800175491146374658636808924848899594398629303837051145484851272960953126700,
              52091995457855965380176529746846521763751311625573037022759665111626306997253,
              4647715852037907467884498874870960430435996725635089920518875461648844420543,
              19720773146379732435540009001854231484085729453524050584265326241021328895041,
              2468135790246813579024681357902468135790246813579024681357902468,
              1357924680135792468013579246801357924680135792468013579246801357,
              8642097531864209753186420975318642097531864209753186420975318642,
              3141592653589793238462643383279502884197169399375105820974944592,
              2718281828459045235360287471352662497757247093699959574966967627
              ];//this is a hardcoded random numbers
    
              var base[rounds];
              signal lastOutput[16];
              signal base2[rounds];
              signal base4[rounds];
              lastOutput[0]<==x;
    
              for (var i = 0;i<rounds;i++){
                  // calculate the first base which is x (f(x) = x^5) ;
                  base[i] = lastOutput[i] + k+ c[i];
                  base2[i] <== base[i] * base[i];
                  base4[i] <== base2[i] * base2[i];
                  lastOutput[i + 1] <== base4[i] * base[i];
              }
              out <== lastOutput[rounds] + k;
    
          }
          component main = mimc5();
    

    Now let's set the setup

2. Setup :

  • 1️⃣ Create a new ceremony with the elliptic curve bn128.

  • 2️⃣ Second you pass the ceremony file to N number of contributors that add additional randomness each time and you keep the last generated file.

  • you just need to feed this final ceremony file to the circuit, and it will give you a zkey file .

Coding part:

in our case snarkjs make it easy for us to generate random ceremony files, and contribute with randmness.

  • creat a ceremony file with snark js :

    snarkjs powersoftau new bn128 12 ceremony_0.ptau
  • contribute with random data by runnig :

    snarkjs powersoftau contribute ceremony_0.ptau random_1.ptau -v

    by running the obove command you'll asked to put an input. put random input (numbers,characters ,symbols ..)

    Notice: in real world senario alot of random people are contributes in this part passing alot of random inputs. and if only one of the contributers are honest. we ensure the randomness

    in the real cases it's always recommended to verify the ceremony file with snarkjs running the command:

    snarkjs powersoftau verify <fileName>.ptau

    and you should see msg like this : "snarkJS: ZKey Ok!"

    • keep generation a new ceremony file from the last one you generated and delete the last one , as many as you want, then keep the last file.ptau .
  • now we need to prepare the phase2 by running :

    snarkjs powersoftau prepare phase2 lastrandom.ptau final.ptau
  • now compile the circuit to an r1cs format so we can feed it to the final ceremony file that we generated randomlly (πŸ™ˆ πŸ™‰ πŸ™Š I hope soπŸ™ˆ πŸ™‰ πŸ™Š) :

    circom mimc.circom --r1cs
  • Finally let's set up the Groth16 :

    snarkjs groth16 setup mimc.r1cs final.ptau outputZkey.zkey
    • Additional step : to make sure all this is random. we could add more randomness to the zkey file that we computed. by running : (this is optional)

      snarkjs zkey contribute outputZkey.zkey finalZkey.zkey
    • to make sure that the zkey file is generated Correctly run:

      snarkjs zkey verify r1cs [circuit.r1cs] [powersoftau.ptau] [circuit_final.zkey]

      snarkjs zkey verify mimc.r1cs final.ptau finalZkey.zkey

      and you should see in the last line the msg : "snarkJS: ZKey Ok!"


  • But how to generate a proof β“πŸ˜•

To generate the proof from this setup we need :

  1. input.json : we need to provide the inputs that we wanna proof knowledge of to the verifier as a json format .
    • example :
      {
        "x": 73249023423490833,
        "y": 324235235342342423423
      }
  2. circuit.wasm : the wasm format of the circuit .
  3. final.zkey : the zkey file that we generated in the setup.

let's continue with code :

  • first let's generate the input that we wanna proof the knowledge of as a json file:

    {
      "x":1258847665265646546465,
      "k":8923410096358576854354354
    }
  • second lets get the web assembly format (wasm) of the circuit by running :

    circom mimc.circom --wasm
    • this will create for you a new directory named : mimc_js, and in this directory you will find the .wasm file that we need.
  • finally run the command that generate the proof :

    snarkjs groth16 fullprove [input.json] [circuit_final.wasm] [circuit_final.zkey] [proof.json] [public.json]

    snarkjs groth16 fullprove input.json mimc_js/mimc.wasm finalZkey.zkey proof.json public.json
    • this cammand will generate two .json files .

      • public.json : this contains the circuit output,in our case this will be the hash that we generated.
      • proof.json : this is the proof that you will submit to the verifier._

  • Now how the Verification ability will be implemented in the blockchain β“πŸ˜•

    Well snarkjs have this very cool method that will generate the smart contract for you from the zkey file.
    you just need to run the command :

    snarkjs zkey export solidityverifier finalZkey.zkey Verifier.sol

    • this will create a solidity file named verifier.sol.

Deploy the contract:

now we have our verification contract let's deploy it in the sepolia network using foundry.

  1. copy the file to the src directory in the repo.
  2. deploy the contract by running the command :
    $ export pk=<your private key>
    $ export url=https://eth-sepolia.g.alchemy.com/v2/<your apikey>
    $ export etherscan=<etherscan apikey>
    $ forge create --rpc-url $url --private-key $pk --etherscan-api-key $etherscan --verify src/Verifier.sol:Groth16Verifier

Alright now we've :

  • created a circuit .
  • create a setup.
  • generate proof.
  • deployed the verfier contract on blockchain.

πŸ§ͺπŸ§ͺπŸ§ͺ it's time to test it πŸ§ͺπŸ§ͺπŸ§ͺπŸ§ͺ

➑️ you may already notice that our contract have only one view funciton (verifyProof()).
➑️this function takes a proof, and if the proof is valid it returns true, and if not it reutrns false
➑️ now let's test it with a valid and non valid proof:

  • in directory test create anew file verifier.t.sol and write this part of code :

    //SPDX-License-Identifier: MIT
    pragma solidity >=0.7.0 <0.9.0;
    
    import "forge-std/Test.sol";
    import "forge-std/console.sol";
    import {Groth16Verifier} from "../src/Verifier.sol";
    
    contract groth16Test is Test{
        Groth16Verifier verifier = Groth16Verifier(0x6C77d5Fb53212e3206691bFACBB96a0874cCa1D3);
        string sepolia = vm.envString("sepolia_url");
        uint fork;
        function setUp()public  {
            // we need to create a fork :
          fork =  vm.creatFork(sepolia);
            vm.selectFork(fork);
    
    }}
    • so the function verifyProof() in our deployed contract takes the proof which is the proof.json that we get earlier.
      but we need a proof that is accepted by solidity πŸ˜•
      to get the proof That is compatible with our solidity function run the command:
     snarkjs zkey export soliditycalldata public.json proof.json
    • copy the out put and pass it in to the test function like (see ‡️)

      notice : make sure to remove the quotation.

    • now add the test funciton with your output calldata :

        function test__verifyProof() public {
          // check that the fork is active :
          assertEq(vm.activeFork(),fork,"fork not active");
          // the valid data from zksnarks:
          bool responseValid = verifier.verifyProof(["0x07d95b6cc9c96b0d02ee0c9c40fc25b03b554054374b49e8abc0d8430ca07ab7",
           0x19fc21f573e36f456a7217e1fe7295b5446777127446d15c4d2b480c1769fcec],
           [[0x2d7dc8fa823020b03d695adf4c7ff3ab372555f28bc89f5308d8e2a0b43d29a9,
           0x12521440e46c2094217c76789aa9f68cc41c4ed6921f0ca2261f9a4ee7420794],
           [0x155ffec1720ce460aa7948185e52649f5acf7c4f14d0efcbc68619c62c300bba,
           "0x1e3f1af20679f80675959abf85961cca057cc58522061cf6f09f935e8420039e]],
           [0x1d08fdd7f4b9e1e35eadf5a6c8c8b6475eeda0a59f8c9f65dfc9ad8026553086,
            0x0ddbbaf6dd4649a5edccea28e539f368db829ba57b5325c9bce6a5dce4e2d8e3],
            [0x0718e749c63e0d5c73460a7ad9e6fd8670811ef8c132c662394051320e50b62f]);
      
          assertTrue(responseValid,response not valid but it should. i don't know WTF is wrong);
      }
    • then run the command :

        forge test -vvv