in this section we gonna create a Minimalistic zero knowledge proof
system using Groth16 snark
-
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 theproof
and theverification
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.
2οΈβ£ after implementing the circuit .we'll generate the setup for the Groth16.
-
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
To install circom
, follow the instructions at installing circom.
Now let's implement the circuit
-
this is the implementation
circuit
of the MIMC function in thecircom
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
- to create a groth16 setup we gonna do the following steps :
-
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 azkey
file .
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 randomnessin 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 .
- keep generation a new
-
now we need to prepare the phase2 by running :
snarkjs powersoftau prepare phase2 lastrandom.ptau final.ptau
-
now compile the
circuit
to anr1cs
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 thezkey
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 :
- 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 }
- circuit.wasm : the
wasm
format of the circuit . - final.zkey : the
zkey
file that we generated in the setup.
-
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.
- this will create for you a new directory named : mimc_js, and in this directory you will find the
-
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 thezkey
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
.
- this will create a solidity file named
now we have our verification contract let's deploy it in the sepolia network using foundry.
- copy the file to the
src
directory in the repo. - 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
- created a circuit .
- create a setup.
- generate proof.
- deployed the verfier contract on blockchain.
β‘οΈ 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 fileverifier.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 theproof
which is theproof.json
that we get earlier.
but we need a proof that is accepted by solidity π
to get theproof
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
- so the function