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-circuit
This will create the files and folder
├── example_js
│ ├── example.wasm
│ ├── generate_witness.js
│ └── witness_calculator.js
├── example.r1cs
└── example.sym
Here, 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.json
To view more information on this R1CS, you can use,
snarkjs r1cs info circom/my-circuit/example.r1cs
In 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.zkey
To 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.json
Please 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.wtns
this 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.json
You 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.json
In 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-scripts
This 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 tx
which 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-testnet
In a new shell (a new nix develop
terminal), wait for it to make blocks via
cardano-cli query tip --testnet-magic 42
Then 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.signed
You can respin the network by purging it first via
purge-local-testnet
followed by
deploy-local-testnet
again.
The isolated evaluation of the plonk verifier can be benchmarked agains the test vectors in the /test-vectors
folder via
nix run .#bench-verifier
The 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
)