Write and compile zero knowledge circuits with circom and snarkjs in the circom
workspace, develop smart contracts utilizing zero knowledge proofs with scaffold-eth.
yarn draft <CIRCUIT_NAME>
Creates a new file structure for <CIRCUIT_NAME>
under packages/circom/circuits/<CIRCUIT_NAME>
with a circuit.circom
file and and inputs
folder.
yarn compcir <CIRCUIT_NAME>
Compiles the circuit.circom
under packages/circom/circuits/<CIRCUIT_NAME>
and creates all the necessary files for the zero knowledge circuit. A solidity smart contract verifier will also be created in packages/hardhat/contracts
as <CIRCUIT_NAME>Verifier.sol
yarn verify <CIRCUIT_NAME> <INPUT_NAME>.json
Verifies whether a proof generated with the set of inputs found in packages/circom/circuits/<CIRCUIT_NAME>/inputs/<INPUT_NAME>.json
is valid for the <CIRCUIT_NAME>
zk circuit.
If no <INPUT_NAME>.json
is provided the command will default to input.json
.
yarn call <CIRCUIT_NAME> <INPUT_NAME>.json
Prints the calldata necessary to call the solidity smart contract verifier <CIRCUIT_NAME>Verifier.sol
with <INPUT_NAME>.json
into the terminal.
If no <INPUT_NAME>.json
is provided the command will default to input.json
.
-
You will find a default zk-circuit
hash
underpackages/circom/circuits/hash/circuit.circom
. Open this file and try to figure out what is going on inside. -
In your terminal use the
yarn compcir hash
command to compile thehash
zk-circuit. -
If you're on windows you will need to navigate to
packages/circom/node_modules/snarkjs/build/cli.cjs
and replace the contents of this file with a modified version found here before you run thecompcir
command. -
You should now see the message
[INFO] snarkJS: OK!
in your terminal after the compilation has completed. -
The contract found in
packages/hardhat/contracts/hashVerifier.sol
should now be updated as well.
-
Now navigate to
packages/circom/circuits/hash/inputs/
, you will find the two filesinput.json
andinvalid.json
. These files provide the input variables for your zk-circuit. Take a look at them. -
Now run
yarn verify hash
. Theverify
command will default to use a file namedinput.json
. You will receive this message:hash: Verification OK
, along with the proof for the inputs. -
Next run
yarn verify hash input.json
, you will receive the same output as the previous command, now tryyarn verify hash invalid.json
. You will receive this message:hash: Invalid proof
, along with the proof for the inputs.
-
Open two new terminals in the project root. in the first run
yarn chain
, in the second runyarn start
. Now go back to your original terminal and runyarn deploy
. -
After your front end has loaded grab funds from the faucet, find the
setPurpose
function and set the purpose toTesting ZK Proofs!!
. -
Now, if the purpose has changed without any issues, in your original terminal run
yarn call hash
.
You will receive the data you will need to call the solidity zk-proof verifier. This is the data generated by default for input.json
:
Generated hash_CALLDATA:
["0x21079367847c16de713f5051c84061999ac64e177086f14998144faf764f6303", "0x137f81c06d8947e111b3655dae2ba4070ded029b65f2975cba79d6b706d7b0b1"],[["0x18fb5e37e53ae119f97cdaeaf3bdb5c9d03e0a1b6a22a8ba25609dbd3d3a0ad7", "0x068b3498f67cb30fc2024029cc1d426078a450a3cef843c23734691eadd901a8"],["0x2b73f068229e65631a551706fa531902fc2b65627585e0896f49b8a189dd4fed", "0x007d5ebc3b10fbfee4bb2a8ae416b9e4407a79418947e6e91ed86ea00c98bc08"]],["0x2f2b5e54fdf9598cb9a46d5e2c4ca90059b02e10dce2bd98d915333d4073d70f", "0x0f28e58d95f8b6099a441a5c77beff93a3fa93d6236a73463a588c80c9285310"],["0x2323966c7385a437ec039864aa44a153587a402717f8bfe53741eb490f9935c8","0x2323966c7385a437ec039864aa44a153587a402717f8bfe53741eb490f9935c8"]
You will see two functions in the front end that take four arrays as inputs each: verifyProof
and testVerifyProof
.
verifyProof
will take your generated proof calldata and return a bool indicating whether or not the proof is valid. Let's test it!
- This time we'll do
yarn call hash input.json
to get used to calling specific inputs. You will need to copy each array individually:
uint256[2] a
["0x21079367847c16de713f5051c84061999ac64e177086f14998144faf764f6303", "0x137f81c06d8947e111b3655dae2ba4070ded029b65f2975cba79d6b706d7b0b1"]
uint256[2][2] b
[["0x18fb5e37e53ae119f97cdaeaf3bdb5c9d03e0a1b6a22a8ba25609dbd3d3a0ad7", "0x068b3498f67cb30fc2024029cc1d426078a450a3cef843c23734691eadd901a8"],["0x2b73f068229e65631a551706fa531902fc2b65627585e0896f49b8a189dd4fed", "0x007d5ebc3b10fbfee4bb2a8ae416b9e4407a79418947e6e91ed86ea00c98bc08"]]
uint256[2] c
["0x2f2b5e54fdf9598cb9a46d5e2c4ca90059b02e10dce2bd98d915333d4073d70f", "0x0f28e58d95f8b6099a441a5c77beff93a3fa93d6236a73463a588c80c9285310"]
uint256[2] input
["0x2323966c7385a437ec039864aa44a153587a402717f8bfe53741eb490f9935c8","0x2323966c7385a437ec039864aa44a153587a402717f8bfe53741eb490f9935c8"]
verifyProof
will return a true
boolean with these inputs (as long as the compilation steps were followed as above, as the circuit was generated deterministically).
Now let's get a false
return value!
yarn call hash invalid.json
uint256[2] a
["0x0b3d67fa93728d3273f36db464198780d2e04bf213d334baf3a3c1567ffa0fc1", "0x2b80d74fee22cb8d13ad9870d1939ab0e54a19065f88b46a1b1d75f59abfaeab"]
uint256[2][2] b
[["0x27d7123af104536e9f3088bcc4eebabd0757f6d9f209dad9dfa7ab228aaf8537", "0x233ffc479116d87ddc01c09839f77fcf7c3ef8543d0fc863e28e668c8030c6b8"],["0x0fb205a3a141851234396c6b943af8e5ad3e39b547cea2246f337e980fd02cec", "0x08c4eda51ccbe6bbf710873cf2ff61ba6660253d1d66dc51f2d6d41f67314567"]]
uint256[2] c
["0x1c446751482e590aa6eadda68c49f2b331ec276c1c4f4cf237e2a7f35e9da04d", "0x236afbafc261ea03035f2cda4850f48bf7b3cf9004aaa2930c0c0773c6d61528"]
uint256[2] input
["0x1b60fc4b486a8264116a0f412004c7d9c4c11879e80d71f42cadb0f869e58982","0x2323966c7385a437ec039864aa44a153587a402717f8bfe53741eb490f9935c8"]
These inputs will return a false
boolean as they are an intentionally invalid proof.
Okay, let's put these same inputs in the testVerifyProof
function.
Transaction Error
VM Exception while processing transaction: revert Invalid Proof
This is good! Let's navigate to packages/hardhat/contracts/YourContract.sol
to see what happened.
On line 32 we see this require statement:
require(verifyProof(a, b, c, input), "Invalid Proof");
This line calls the verifyProof
function we were playing with before and requires it to return a true
boolean for the transaction to be accepted.
Now let's use the solidity calldata for our valid proof:
uint256[2] a
["0x21079367847c16de713f5051c84061999ac64e177086f14998144faf764f6303", "0x137f81c06d8947e111b3655dae2ba4070ded029b65f2975cba79d6b706d7b0b1"]
uint256[2][2] b
[["0x18fb5e37e53ae119f97cdaeaf3bdb5c9d03e0a1b6a22a8ba25609dbd3d3a0ad7", "0x068b3498f67cb30fc2024029cc1d426078a450a3cef843c23734691eadd901a8"],["0x2b73f068229e65631a551706fa531902fc2b65627585e0896f49b8a189dd4fed", "0x007d5ebc3b10fbfee4bb2a8ae416b9e4407a79418947e6e91ed86ea00c98bc08"]]
uint256[2] c
["0x2f2b5e54fdf9598cb9a46d5e2c4ca90059b02e10dce2bd98d915333d4073d70f", "0x0f28e58d95f8b6099a441a5c77beff93a3fa93d6236a73463a588c80c9285310"]
uint256[2] input
["0x2323966c7385a437ec039864aa44a153587a402717f8bfe53741eb490f9935c8","0x2323966c7385a437ec039864aa44a153587a402717f8bfe53741eb490f9935c8"]
YES!! It went though this time. And we modified a state variable of our solidity contract in the process. The contract's verifiedHash
variable now has a value.
We just proved that we know the input value that hashes to verifiedHash
without revealing what that value actually is!
(Although, we did not do this securely, like at all)
Give the draft
command a try!
yarn draft <YOUR_CIRCUIT_NAME>
This will create a blank circuit template for you to try out!