An end-to-end zksnark app on Ethereum
Koh Wei Jie
twitter.com/weijie_eth
December 2021 / March 2022
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);
Basic terminology
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;
How a zk-snark solves the problem
Circuit:
Private input: preimage
Public inputs: hashedPreimage, address
Constraints: hash([preimage]) === hashedPreimage
Output: nullifier = hash([address, preimage])
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.
Demo
Developer tooling for snarkjs and circom
Instead of:
https://fvictorio.notion.site/Circom-and-snarkjs-diagram-2ae9f63de55e46a49eadc23021c9fc43
circom-starter
circom-helper
zkey-manager
Eases compilation and zkey generation for multiple circuits.
Code walkthrough
Feel free to copy & paste anything!
circom-helper
zkey-manager
Deploy contracts