Skip to main content

Plonky2

For users of Plonky2.

info

This tutorial corresponds to the v0.2.2 version of Plonky2.

Introduction​

We will walk through the process of how to create a circuit using the Plonky2 library that proves a correct Merkle tree inclusion proof. The complete and finished code for the circuit can be found here along with other example projects in our sindri-resources repository. You can also check out the Plonky2 overview from Polymer Labs for a general overview of how to use Plonky2. An overview of the role that Merkle trees and Merkle inclusion proofs serve in the blockchain space can be found here.

Merkle Tree Circuit​

The Merkle tree circuit implementation is based on the sample Plonky2 circuits provided by Hashcloak. Using this implementation requires the following steps:

  1. Importing the leaves for the leaf node layer of the Merkle tree and selecting the index of the leaf for which we want to prove inclusion. Along with the leaf nodes, this leaf index and corresponding Merkle path will be included as public inputs to the circuit.
  2. Constructing a full Merkle tree from the leaf nodes. We use the merkle_tree.rs file to generate the hashes for every node in the Merkle tree and also to generate a Merkle path for the leaf whose inclusion is being proved. This file is based on the on Hashcloak's implementation.
  3. Importing the node hashes and the the Merkle path indices as public inputs to the circuit. The Merkle root hash is the last public input to the circuit and is comprised of four 64-bit words.
  4. Proving the inclusion of the leaf in the Merkle tree using the Plonky2 Merkle verification circuit.

For this tutorial, we will use a 1024 leaf Merkle tree and prove membership for the first leaf. Note that the input values are all modulo the Goldilocks 64-bit prime, which defines the prime field for Plonky2.

Circuit Configuration​

The circuit is organized in a directory containing the following files:

β”œβ”€β”€ πŸ“‚circuit
β”‚ β”œβ”€β”€ πŸ“œCargo.toml
β”‚ β”œβ”€β”€ πŸ“œrust-toolchain
β”‚ β”œβ”€β”€ πŸ“œsindri.json
β”‚ └── πŸ“‚src
β”‚ β”œβ”€β”€ πŸ“œlib.rs
β”‚ └── πŸ“œmerkle_tree.rs

The src/lib.rs contains a prove method for the circuit function. The Plonky2 circuit construction needs to be defined inside of this prove method. The prove method is responsible for constructing the arithmetic circuit, loading any input data, configuring the partial witness, and proving the circuit. Helper methods can be defined in the lib.rs or in a separate module and imported into the lib.rs file. For instance, the merkle_tree.rs module contains methods to generate the root hash of a merkle tree along with along with a merkle path and required hashes to verify a Merkle proof. The helper function verify_merkle_proof_circuit creates the arithmetic circuit for verifying a Merkle tree proof using the Plonky2 CircuitBuilder struct.

For detailed descriptions of the other files, you can click through the different tabs here.

For any circuit using a Sindri supported framework, you will need to include a sindri.json file defining certain metadata about your circuit. There are a few key pieces of information here that are required:

  1. The packageName and plonky2Version must be included and match the values in your Cargo.toml file. We only support Plonky2 versions v0.2, v0.2.1, and v0.2.2.
  2. The provingScheme and circuitType must both be set to plonky2.
  3. The structName must be the path to the circuit struct in your lib.rs file which implements the prove method. To meet the requirements of the interface, the structName in your sindri.json must match the name of your circuit struct.
circuit_tutorials/plonky2/merkle_tree/circuit/sindri.json
loading...

Circuit explanation​

Sindri API​

We'll walk through the steps to upload your Plonky2 circuit to Sindri, compile it, generate proofs, and verify proofs. We will use a Rust script to interact with the Sindri API.

Setup​

Clone the Sindri resources repository and move into the merkle_tree tutorial.

git clone https://github.com/Sindri-Labs/sindri-resources.git
cd sindri-resources/circuit_tutorials/plonky2/merkle_tree

Make sure that the merkle_tree project directory contains the following files:

πŸ“¦ merkle_tree
β”œβ”€β”€ πŸ“œCargo.toml
β”œβ”€β”€ πŸ“œinput_1024.json
β”œβ”€β”€ πŸ“‚circuit
β”‚ β”œβ”€β”€ πŸ“œCargo.toml
β”‚ β”œβ”€β”€ πŸ“œrust-toolchain
β”‚ β”œβ”€β”€ πŸ“œsindri.json
β”‚ └── πŸ“‚src
β”‚ β”œβ”€β”€ πŸ“œlib.rs
β”‚ └── πŸ“œmerkle_tree.rs
β”œβ”€β”€ πŸ“œREADME.md
β”œβ”€β”€ πŸ“œrust-toolchain
β”œβ”€β”€ πŸ“œsample.env
└── πŸ“‚src
└── πŸ“œmain.rs

The circuit described above is contained in the merkle_tree_circuit directory.

You'll also need your Sindri API key to authenticate all requests to the API, so make sure you have this ready (see #api-authentication). Modify the sample.env file to include your API key and rename it to .env.

Upload & Compile​

In your main function, we have three methods:

  1. 'compile_circuit' to upload the circuit to Sindri and compile it.
  2. 'prove_circuit' which generate a proof for the circuit.
  3. 'verify_proof' to verify the proof.

Compile​

Running compile will upload the merkle_tree_circuit files to Sindri. If the compile checks pass, a json file will be outputted to data/compile_out.json.

{
"circuit_id": "bacaa9ca-f1c2-4c28-84c8-117f2548d60e",
"circuit_name": "merkle_tree_circuit",
"circuit_type": "plonky2",
"compute_time": "P0DT00H00M59.673416S",
"compute_time_sec": 59.673416,
"compute_times": {
"clean_up": 4.49532,
"compile": 22.33991,
"precompilation_checks": 37.0868,
"save_results": 0.24671,
"total": 64.16873
},
"date_created": "2024-08-13T19:00:04.989Z",
"error": null,
"file_size": 2992277,
"has_smart_contract_verifier": false,
"has_verification_key": false,
"meta": {},
"num_proofs": 0,
"plonky2_version": "0.2.2",
"project_name": "merkle_tree_circuit",
"proving_scheme": "plonky2",
"public": false,
"queue_time": "P0DT00H00M01.002722S",
"queue_time_sec": 1.002722,
"status": "Ready",
"struct_name": "merkle_tree::MerkleTreeCircuit",
"tags": ["latest"],
"team": "****",
"team_avatar_url": "****",
"team_slug": "****",
"uploaded_file_name": "filename.filetype",
"verification_key": null,
"warnings": null
}

Prove​

The prove_circuit method requires a path to a valid input file. The input_1024.json file contains 1024 leaf values and the index of the desired leaf node for which to prove inclusion.

{"inputs":[17385365412,13508786373,...,14749629153,3531383147], "index": 0}

The prove_circuit method will pull the circuit ID from the compile_out.json file. The proof data is returned to the user in the data/proof_out.json file. The proofs field of this JSON file contains everything that we need to verify the proof locally.

For Plonky2 proofs, we need three Rust structs to verify the proof:

  1. The ProofWithPublicInputs struct.
  2. The CommonCircuitData struct.
  3. The VerifierOnlyCircuitData struct.

These three structs are encoded as base64 strings. The base64 strings are quite large for Plonky2 circuits, so they've been replaced with "****" in the sample proof_out.json fiel shown below.

  "circuit_id": "66d36825-3e72-4444-84d4-430ff402ffc1",
"circuit_name": "merkle_tree_circuit",
"circuit_team": "roy-sindri",
"circuit_team_avatar_url": "https://gravatar.com/avatar/758874998f5bd0c393da094e1967a72b?s=400&d=identicon&r=x",
"circuit_team_slug": "roy-sindri",
"circuit_type": "plonky2",
"compute_time": "P0DT00H00M01.203837S",
"compute_time_sec": 1.203837,
"compute_times": {
"clean_up": 0.18932,
"file_setup": 0.46645,
"prove": 0.04134,
"save_results": 0.50672,
"total": 1.20384
},
"date_created": "2024-08-30T08:50:40.932Z",
"error": null,
"file_size": 100671,
"has_smart_contract_calldata": false,
"has_verification_key": false,
"meta": {},
"perform_verify": false,
"project_name": "merkle_tree_circuit",
"proof": {
"common": "****"
"proof": "****"
"verifier_data": "****"
},
"proof_id": "8695040a-b1d8-4e74-95c1-1cb8441e8150",
"public": null,
"queue_time": "P0DT00H00M01.486846S",
"queue_time_sec": 1.486846,
"smart_contract_calldata": null,
"status": "Ready",
"team": "roy-sindri",
"team_avatar_url": "https://gravatar.com/avatar/758874998f5bd0c393da094e1967a72b?s=400&d=identicon&r=x",
"team_slug": "roy-sindri",
"verification_key": null,
"warnings": null
}

Verify​

The verify_proof method comprises several steps:

  1. Extracts the proof field from the proof_out.json file.
  2. Deserializes the base64 strings to the serialized Plonky2 types byte arrays.
  3. Defines a Gate Serializer for the Plonky2 circuit used for generating the proof. This is necessary to deserialize the CommonCircuitData struct.
  4. Deserialzies the byte arrays to their respective Plonky2 types. will take the proof data from the proof_out.json file, deserialize the base64 strings, to their Plonky2 typeserify the proof locally.
  5. Verifies the proof using the verify method from the Plonky2 API.

Running the verify_proof method will show that the proof was verified successfully.

In order to run the code for the Merkle tree circuit, run the following command from the root of the merkle_tree directory:

cargo run --release