1 of 16

An end-to-end zksnark app on Ethereum

Koh Wei Jie

twitter.com/weijie_eth

December 2021 / March 2022

2 of 16

Problem statement

I want a smart contract to do some action (e.g. mint an NFT) only to users who know a secret password.

If I store the hash of the password on-chain, and verify the it in a contract function, anyone can see the preimage as the contract performs, defeating the purpose of the contract:

require(hashedPassword == hash(preimage);

3 of 16

Basic terminology

  • Preimage: the input to a hash function
    • aka “message” or “input”
    • Is often meant to be secret
    • Has nothing to do with images (e.g. JPEGs)
  • Hash: the output of a hash function
    • aka “hash value”, “message digest”

4 of 16

How a zk-snark solves the problem

Contract function mintWithProof(

uint256 _nullifier,

uint256[8] memory _proof

):

require(nullifiers[_nullifier] == false);

require(isValid(_proof, [_nullifier, hashedPreimage]));

nullifiers[_nullifier] = true;

5 of 16

How a zk-snark solves the problem

Circuit:

Private input: preimage

Public inputs: hashedPreimage, address

Constraints: hash([preimage]) === hashedPreimage

Output: nullifier = hash([address, preimage])

6 of 16

Preventing replay attacks

We use a nullifier to prevent an attacker from submitting another user’s proof.

The contract has:

mapping(uint256 => bool) public nullifiers;

mintWithProof() requires the nullifier to not have been seen previously.

7 of 16

Demo

8 of 16

Developer tooling for snarkjs and circom

Instead of:

https://fvictorio.notion.site/Circom-and-snarkjs-diagram-2ae9f63de55e46a49eadc23021c9fc43

9 of 16

circom-starter

Developed by Blaine & Brian:

https://github.com/0xPARC/circom-starter

10 of 16

circom-helper

  • Useful for unit tests for projects with many circuits
  • Exposes a JSON-RPC server with two endpoints:
    1. gen_witness: given a circuit name and inputs, generate the witness (using the C++ witness generator). The witness is an array of values.
    2. get_signal_index: given a circuit and a signal name, get the index at which one can retrieve the value of said signal from the witness.

11 of 16

zkey-manager

Eases compilation and zkey generation for multiple circuits.

  • Developers can specify the desired parameters and public inputs in a config file, instead of having to manage multiple main component files.
  • Encapsulates the retrieval of the necessary ptau file from iden3.
  • zkey generation (no phase 2)

12 of 16

Code walkthrough

  • Lerna
  • Contracts
    • Solidity and Hardhat
  • Circuit code
    • circom v2
  • Circuit unit tests
    • circom-helper v0.3.3
  • Circuit compilation
    • zkey-manager v0.1.2
  • Proof generation in the browser
    • parcel
    • snarkjs.min.js
    • witness_calculator.js (note that it is modified)
    • Also see https://github.com/akinovak/circom2-example

13 of 16

Feel free to copy & paste anything!

  • Groth16 verifier template for Solidity 0.8.x
  • Tweaked witness generation code for circom2 circuits
  • Unit test for the circuit
  • etc

14 of 16

circom-helper

  1. Install circom v2
  2. Configure circom-helper
  3. Write the circuit and main file
  4. Write the unit test
  5. Run the unit test

15 of 16

zkey-manager

  1. Configure zkey-manager
  2. Compile
  3. Download ptau
  4. Generate zkey file
  5. Export verification key
  6. Generate verifier contract

16 of 16

Deploy contracts

  1. Get Goerli ETH
  2. Compile contracts
  3. Deploy and verify