This repository is not audited and should be used only for educational purposes. Use at your own risk.
This is an e2e example of a plonk ZKP written in Circom, besides that, this repo uses a custom fork of SnarkJS to generate proofs. To run this example, enter a shell via nix develop, this will bring all the necessary tools in path. The goal of this example is to show that it is not difficult to develop and set up a script that uses zero knowledge on Cardano.
In the circom/example.circom file you will find the logic of what we prove in zero knowledge. This circuit has two private inputs in[2] and one public output out, the idea is that this simple circuit proves that one knows a preimage of size two to the poseidon hash digest out. To compile this circuit over the BLS12-381 curve to a rank one constraint system (R1CS), you can use
circom circom/my-circuit/example.circom --r1cs --wasm --sym -p bls12381 -o circom/my-circuitThis will create the files and folder
├── example_js
│ ├── example.wasm
│ ├── generate_witness.js
│ └── witness_calculator.js
├── example.r1cs
└── example.symHere, the example.r1cs can also be converted to a readable form via
snarkjs r1cs export json circom/my-circuit/example.r1cs circom/my-circuit/example.r1cs.jsonTo view more information on this R1CS, you can use,
snarkjs r1cs info circom/my-circuit/example.r1csIn the test-vector directory, you will find the files that SnarkJS uses to convert this R1CS to something we can use to generate proofs. First note that in the test-vector/setup/ folder, we have the power of tau files (these are generated by me, and are not to be trusted, as this is an example, see the SnarkJS repo for more info). Besides that, the verification key (which is the mapping of the R1CS over the power of tau) is given, to calculate this yourself, you can use,
snarkjs plonk setup circom/my-circuit/example.r1cs test-vectors/setup/pot_final.ptau test-vectors/setup/example_final.zkeyTo export this example_final.zkey to the more human-readable verification_key.json, you can use,
snarkjs zkey export verificationkey test-vectors/setup/example_final.zkey test-vectors/setup/verification_key.jsonPlease note that if you did these steps, no file changed (see git status), showing that the verification key that was in the repo, was the correct one.
In the test-vector/example folder, I create a private-input.json. This holds the secret values a=3 and b=11 (you can pick different values) which, together with the wasm files generated in the Circom step above, can be used to create a witness via,
node circom/my-circuit/example_js/generate_witness.js circom/my-circuit/example_js/example.wasm test-vectors/example/private-input.json test-vectors/example/witness.wtnsthis witness can be used to create a proof and deduce what the public output should be via,
snarkjs plonk prove test-vectors/setup/example_final.zkey test-vectors/example/witness.wtns test-vectors/example/proof.json test-vectors/example/public-input.jsonYou can verify this proof against the verification key via
snarkjs plonk verify test-vectors/setup/verification_key.json test-vectors/example/public-input.json test-vectors/example/proof.jsonIn this repo, you will find a plutus library called plutus-plonk that implements the verifier logic needed to verify a proof and public input given a fixed verification key. Besides that, there is a basic minting script implemented in the plutus-scripts folder that uses this to verify the circuit we compiled and setup above. To compile this script (which hard-codes test-vectors/setup/verification_key.json in the minting script) and convert both the public-input.json and proof.json to plutus data, you can use
nix run .#write-scriptsThis will write the script to assets/V3/zkMintingScript.plutus and the redeemer (which contains the combination of your public input and proof) to assets/redeemers/mintRedeemer.json. These two together should allow you to mint an asset with any name under the policy ID via
cardano-cli conway transaction build --testnet-magic 4 \
--tx-in $(cardano-cli query utxo --address $(cat payment.addr) --output-json --testnet-magic 42 | jq -r 'keys[0]') \
--tx-in-collateral $(cardano-cli query utxo --address $(cat payment.addr) --output-json --testnet-magic 4 | jq -r 'keys[0]') \
--mint "1 $(cardano-cli transaction policyid --script-file assets/V3/zkMintingScript.plutus).eeeeee" \
--tx-out $(cat payment.addr)+10000000+"1 $(cardano-cli transaction policyid --script-file assets/V3/zkMintingScript.plutus).eeeeee" \
--change-address $(cat payment.addr) \
--mint-script-file assets/V3/zkMintingScript.plutus --mint-redeemer-file assets/redeemers/mintRedeemer.json \
--out-file txwhich should work on sanchonet, given that the payment.addr can cover for the tx.
If you would like to test this script on a local testnet, you can deploy one via
deploy-local-testnetIn a new shell (a new nix develop terminal), wait for it to make blocks via
cardano-cli query tip --testnet-magic 42Then you can mint via
cp local-testnet/example/utxo-keys/utxo1.skey ./payment.skey
cp local-testnet/example/utxo-keys/utxo1.vkey ./payment.vkey
cardano-cli address build --testnet-magic 42 --payment-verification-key-file payment.vkey > payment.addr
cardano-cli conway transaction build --testnet-magic 42 \
--tx-in $(cardano-cli query utxo --address $(cat payment.addr) --output-json --testnet-magic 42 | jq -r 'keys[0]') \
--tx-in-collateral $(cardano-cli query utxo --address $(cat payment.addr) --output-json --testnet-magic 42 | jq -r 'keys[0]') \
--mint "1 $(cardano-cli transaction policyid --script-file assets/V3/zkMintingScript.plutus).eeeeee" \
--tx-out $(cat payment.addr)+10000000+"1 $(cardano-cli transaction policyid --script-file assets/V3/zkMintingScript.plutus).eeeeee" \
--change-address $(cat payment.addr) \
--mint-script-file assets/V3/zkMintingScript.plutus --mint-redeemer-file assets/redeemers/mintRedeemer.json \
--out-file tx
cardano-cli transaction sign --testnet-magic 42 --signing-key-file payment.skey --tx-body-file tx --out-file tx.signed
cardano-cli transaction submit --testnet-magic 42 --tx-file tx.signedYou can respin the network by purging it first via
purge-local-testnetfollowed by
deploy-local-testnetagain.
The isolated evaluation of the plonk verifier can be benchmarked agains the test vectors in the /test-vectors folder via
nix run .#bench-verifierThe current test circuit and public inputs run with
Run fast snarkJS plonk verifier with public inputs [.......]
n Script size CPU usage Memory usage
----------------------------------------------------------------------
- 6274 (38.3%) 3585086348 (35.9%) 668618 (4.8%) Here the results are relative to the mainnet paramaters (see /plutus-benchmark/common/PlutusBenchmark/ProtocolParameters.hs)