Quantum Protocol Specifications

User Journey Overview

This section outlines the complete user journey with the Quantum Protocol, basically highlighting all the user interactions with Quantum.

Circuit Registration

Users begin by registering the quantum circuits they wish to generate and aggregate proofs for. This is accomplished seamlessly using the quantum-sdk. During the Circuit Registration phase, users submit circuit-specific data (e.g., verification keys) to the Quantum Node. In return, they receive a unique identifier called the circuitKey, which will be used in subsequent interactions with the protocol.

Proof Submission

Once a circuit is registered, users can submit their proof (π\pi) along with their public inputs (pispis) to the Quantum Node. Upon submission, the user receives a proofHash, a unique identifier for their proof. The proofHash allows users to track the status of their proof during the aggregation process.

Inclusion Check

Within a designated time window, the Quantum Node aggregates all user-submitted proofs into an Aggregated Proof (πagg\pi_{agg}). The aggregation is encoded in a binary Merkle tree, whose root, known as the Superproof Root, is submitted and then verified on the QuantumVerifier contract deployed on Ethereum. Post this users can verify that their individual proof is included in the aggregated proof. This process, referred to as the Inclusion Check, involves obtaining an inclusion_proof from the Quantum Node. The inclusion_proof is a Merkle proof that allows the user to independently verify that their proof was verified and included as part of the aggregated proof (πagg\pi_{agg}) verified on-chain.

Aggregation Backend Overview

This section provides a high-level overview of the aggregation backend architecture. Details regarding constraints, public inputs, and their linkages across different stages are covered in the Specifications

The aggregation backend operates in four distinct stages: Reduction, Aggregation, SNARKification, and Consolidation. Each stage leverages difference prover: Risc0 zkVM for the first two stages, Circom for SNARKification, and Gnark for Consolidation.

Reduction:

In this stage, each user-submitted proof (π\pi) and its corresponding public inputs (pispis) are individually verified within a Risc0 zkVM circuit. This circuit implements the verification algorithm for the proving scheme and some hashing. The result of this process is a compressed proof (πreduced\pi_{reduced}) along with new reduced public inputs (pisreducedpis_{reduced}).

Aggregation:

The reduced proofs (πreduced\pi_{reduced}) and public inputs (pisreducedpis_{reduced}) are then recursively verified and encoded into a Merkle tree within another Risc0 zkVM circuit. This produces the aggregated proof (πagg\pi_{agg}) and its corresponding aggregated public inputs (pisaggpis_{agg}).

SNARKification:

The aggregated proof (πagg\pi_{agg}) and corresponding public inputs (pisaggpis_{agg}) undergo a further transformation at this stage. Using a Circom circuit, the proof is recursively verified and converted into a Groth16 proof (πsnark\pi_{snark}) with its associated public inputs (pissnarkpis_{snark}).

Consolidation:

In the final step, the Groth16 proof (πsnark\pi_{snark}) and its public inputs (pissnarkpis_{snark}) are recursively verified inside a Gnark circuit. This produces the final proof (πfinal\pi_{final}) and its public inputs (pisfinalpis_{final}). While this step may seem redundant, it ensures the system is future-proof by enabling seamless integration with additional VMs for reduction and aggregation, requiring minimal modifications.

Specifications

This section goes into depth of each component and inner working of the Quantum Protocol.

1. Circuit Registration

In this stage, the user submits circuit-specific data, which primarily includes the information necessary to verify proofs generated for the circuit. Depending on the proving scheme, either all or a subset of this data is used in formation of circuitKey. This data is combined and hashed with the Risc0 Image ID associated with the verifier (reduction) circuit for the selected proving scheme. The resulting hash, known as the circuitKey, is returned to the user as a unique identifier for their circuit.

The circuitKey also plays a critical role in later stages, such as during the User Inclusion Check.

Formula for Computing circuitKey:

circuitKey=Keccak(user_circuit_datarisc0_image_id)circuitKey=Keccak(user\_circuit\_data ∣∣ risc0\_image\_id)
  • : The circuit-specific data, which varies based on the proving scheme.

  • risc0_image_idrisc0\_image\_id: The unique identifier for the reduction circuit used in the protocol.

This hashing mechanism ensures that each circuit is uniquely identifiable and securely tied to its associated verification logic.

  • Gnark Groth16/Plonk

    For Gnark Groth16/Plonk, the user's Verification Key is serialized into bytes using the Borsh serialization format. The serialized bytes are then hashed using the Keccak Hash algorithm to compute user_circuit_datauser\_circuit\_data.

    user_circuit_data=Keccak(user_vkey_bytes)user\_circuit\_data = Keccak(user\_vkey\_bytes)
  • SnarkJS Groth16

    For SnarkJS Groth16, the user’s Verification Key is converted into *PreparedVerifyingKey,* and then serialised into bytes using Canonical Serialize.

    user_circuit_data=Keccak(prepared_vkey_bytes)user\_circuit\_data = Keccak(prepared\_vkey\_bytes)
  • Risc0 Compressed

    For Risc0 Compressed Proofs, just the user’s risc0 Image Id is used.

    user_circuit_data=Keccak(user_circuit_image_id_bytes)user\_circuit\_data = Keccak(user\_circuit\_image\_id\_bytes)
  • SP1 Succinct

    For SP1 Succinct Proofs, user_circuit_datauser\_circuit\_data is computed from SP1VerifyingKey using this function.

    user_circuit_data=keccak(sp1_verifying_key)user\_circuit\_data = keccak(sp1\_verifying\_key)
  • Plonky2

    For Plonky2, the user sends VerifierOnlyCircuitData and CommonCircuitData during Circuit Registration as both of them are required to successfully verify a Plonky2 proofs.

    user_circuit_data=keccak(vkeyu  common_datau)user\_circuit\_data = keccak(vkey_{u} \ || \ common\_data_{u})
  • Halo2 KZG/KZG-EVM

    For Halo2 KZG/KZG-EVM, the user sends PlonkProtocol and s_g2 as both are required to successfully verify Halo2 KZG or KZG-EVM proofs.

    user_circuit_data=keccak(protocol_datau  s_g2u)user\_circuit\_data = keccak(protocol\_data_{u} \ || \ s\_g2_{u})

2. Proof Submission

In this stage, the user submits the proof πu\pi_{u} they want aggregated, along with the corresponding public inputs pisupis_{u}. In return, the user receives a unique proofId. This proofId is computed as follows:

proofId=Keccak(circuitKey  Keccak(πu)  Keccak(pisu))proofId = Keccak(circuitKey \ || \ Keccak(\pi_{u}) \ || \ Keccak(pis_{u}))

The proofHash allows users to track the status of their proof during the aggregation process and also serves as the leaf node in the binary Merkle tree that corresponds to the Superproof Root.

3. Reduction

The reduction stage validates the user's proof within a Risc0 circuit, which executes the verifier logic specific to the corresponding proving scheme. Each proving scheme is associated with a distinct reduction circuit, identified by a unique Risc0 Image ID.

This step produces a Risc0-compressed proof of proof (πred\pi_{red}), unifying proofs from different proving schemes into a standardized format. The reduction circuit accepts all the necessary data for verifying a proof as private input, while the public input for the reduction circuit is typically as follows:

pisred==( keccak(user_circuit_data  keccak(πu)  keccak(pisu)))pis_{red} == ( \ keccak(user\_circuit\_data \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u})) )
  • Gnark Groth16/Plonk , SnarkJS Groth16

    Private Inputs (Reduction circuit private inputs):

    1. vkeyuvkey_{u} : User circuit verification key

    2. πu\pi_{u} : Proof user wants to get aggregated corresponding to vkeyuvkey_{u}

    3. pisupis_{u}: Public inputs corresponding to πu\pi_{u}

    Public Input ( pisredpis_{red} : Reduction Circuit Public Input):

    1. pisredpis_{red} ( keccak(keccak(vkeyu)  keccak(πu)  keccak(pisu)))( \ keccak(keccak(vkey_{u} ) \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u})))

    Reduction Circuit Constraints:

    1. snark_verify(vkeyu,πu,pisu)==Truesnark\_verify(vkey_{u}, \pi_{u}, pis_{u}) == True

    2. pisred==( keccak(keccak(vkeyu)  keccak(πu)  keccak(pisu)))pis_{red} == ( \ keccak(keccak(vkey_{u}) \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u})) )

  • Risc0 Compressed

    Private Inputs (Reduction circuit private inputs):

    1. image_iduimage\_id_{u} : User risc0 circuit Image Id

    2. journalujournal_{u} : User generated risc0 journal

    Public Inputs ( pisredpis_{red} : Reduction Circuit Public Input):

    1. pisredpis_{red}(keccak(image_idu  keccak(journalu)))(keccak(image\_id_{u} \ || \ keccak(journal_{u}) ))

    Reduction Circuit Constraints:

    1. risc0_verify_compressed(image_idu,journalu)==Truerisc0\_verify\_compressed(image\_id_{u}, journal_{u}) == True

    2. pisred==(keccak(image_idu  keccak(journalu)))pis_{red} ==(keccak(image\_id_{u} \ || \ keccak(journal_{u})))

  • SP1 Succinct

    Private Inputs (Reduction Circuit Private Inputs):

    1. vkeyuvkey_{u} : User circuit verification key

    2. πu\pi_{u} : Succinct sp1 proof user wants to get aggregated corresponding to vkeyuvkey_{u}

    3. pisupis_{u}: Public inputs corresponding to πu\pi_{u}

    Public Inputs ( pisredpis_{red} : Reduction Circuit Public Input):

    1. pisred( keccak(keccak(vkeyu)  keccak(πu)  keccak(pisu)))pis_{red}( \ keccak(keccak(vkey_{u}) \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u})))

    Constraints:

    1. sp1_verify_succinct(vkeyu,πu,pisu)==Truesp1\_verify\_succinct(vkey_{u}, \pi_{u}, pis_{u})==True

    2. pisred==( keccak(keccak(vkeyu)  keccak(πu)  keccak(pisu)))pis_{red} == ( \ keccak(keccak(vkey_{u}) \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u})))

  • Plonky2

    Private Inputs (Reduction Circuit Private Inputs):

    1. common_dataucommon\_data_{u}: User plonky2 circuit common_data

    2. vkeyuvkey_{u}: User circuit verification_only data

    3. πu\pi_{u}: Plonky2 proof user wants to get aggregated corresponding to vkeyuvkey_{u}

    4. pisupis_{u} : User circuit public inputs

    Public Inputs:

    1. pisredpis_{red}(keccak(keccak(vkeyu  common_datau)  keccak(πu)  keccak(pisu))keccak(keccak(vkey_{u} \ || \ common\_data_{u}) \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u})))

    Constraints:

    1. plonky2_verify_proof(common_datau,vkeyu,pisu,πu)plonky2\_verify\_proof(common\_data_{u}, vkey_{u}, pis_{u}, \pi_{u})

    2. pisred==keccak(keccak(vkeyu  common_datau)  keccak(πu)  keccak(pisu))pis_{red} == keccak(keccak(vkey_{u} \ || \ common\_data_{u}) \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u}))

  • Halo2 KZG/EVM

    Private Inputs:

    1. protocol_datauprotocol\_data_{u}

    2. s_g2us\_g2_{u}

    3. pisupis_{u}

    4. πu\pi_{u}

    Public Inputs:

    1. pisredpis_{red}(keccak(protocol_datau  s_g2u)  keccak(πu)  keccak(pisu))(keccak(protocol\_data_{u} \ || \ s\_g2_{u}) \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u}))

    Constraints:

    1. halo2_verify(protocol_datau,pisu,s_g2u,πu)halo2\_verify(protocol\_data_{u}, pis_{u}, s\_g2_{u}, \pi_{u})

    2. pisred==(keccak(protocol_datau  s_g2u)  keccak(πu)  keccak(pisu))pis_{red} == (keccak(protocol\_data_{u} \ || \ s\_g2_{u}) \ || \ keccak(\pi_{u}) \ || \ keccak(pis_{u}))

4. Aggregation

In aggregation stage, all the reduced risc0 proofs and public input pairs are recursively verified inside a single risc0 circuit and encoded as a binary merkle tree. Each individual proof is verified against its Reduction circuit’s Risc0 Image Id . These Image Id’s are hardcoded in the aggregation circuit corresponding to their proof_types.

Public Input (pisaggpis_{agg}): superproof_rootsuperproof\_root

Private Inputs:

  1. Reduced Proofs: (πredi)i=0i=batch_size(\pi_{red_{i}})_{i=0}^{i=batch\_size}

  2. Reduced Public Inputs: (pisredi)i=0i=batch_size(pis_{red_{i}})_{i=0}^{i=batch\_size}

  3. User Proof Types: (proof_typei)i=0i=batch_size(proof\_type_{i})_{i=0}^{i=batch\_size}

  4. User Proof Hashes: (π_hashi)i=0i=batch_size(\pi\_hash_{i})_{i=0}^{i=batch\_size}

  5. User Public Input Hashes: (pis_hashi)i=0i=batch_size(pis\_hash_{i})_{i=0}^{i=batch\_size}

  6. User Circuit Datas: (ckt_data)i=0i=batch_size(ckt\_data)_{i=0}^{i=batch\_size}

Constraints:

  1. (verify_risc0_proof(πredi,pisredi,redn_image_idi)==True)i=0i=batch_size(verify\_risc0\_proof(\pi_{red_{i}}, pis_{red_{i}}, redn\_image\_id_{i}) == True)_{i=0}^{i=batch\_size}

  2. (pisredi==keccak(ckt_datai  π_hashi  pis_hashi))i=0i=batch_size(pis_{red_{i}} == keccak(ckt\_data_{i} \ || \ \pi\_hash_{i} \ || \ pis\_hash_{i} ))_{i=0}^{i=batch\_size}

  3. superproof_root==construct_tree(leaves);leaves=[keccak(keccak(ckt_datai  redn_image_idi)  π_hashi  pis_hashi)]i=0i=batch_sizesuperproof\_root == construct\_tree(leaves); \\leaves=[keccak(keccak(ckt\_data_{i} \ || \ redn\_image\_id_{i}) \ || \ \pi\_hash_{i} \ || \ pis\_hash_{i})]_{i=0}^{i=batch\_size}

5. Snarkification

This stage converts risc0 compressed proof (πagg\pi_{agg}) to circom groth16 proof (πsnark\pi_{snark}) using this circuit. The only reason of this step is to reduce on-chain verification cost on Ethereum. The public inputs in previous stage and this stage which is the superproof_rootsuperproof\_root stays exactly the same i.e. pisagg==pissnarkpis_{agg} == pis_{snark}

7. On-chain Superproof Verification

Once the Snarkification step is complete, the Groth16 proof (πsnark\pi_{snark}) is submitted to the QuantumVerifier contract on Ethereum for on-chain verification. The corresponding public input (pissnarkpis_{snark}), which is the Superproof Root, is also passed to the contract.

The QuantumVerifier contract validates the proof against the public input using the Groth16 verifier logic. If the verification is successful, the Superproof Root (pissnarkpis_{snark}) is stored in the Quantum contract's state as a record of successful aggregation and proof verification.

8. User Inclusion Check

After the Superproof Root (pissnarkpis_{snark}) is verified and stored on-chain, individual users can perform an Inclusion Check to verify that their proof (πu\pi_u) was correctly included in the aggregated proof.

Steps for Inclusion Check:

  1. Retrieve the inclusion proof :

    • The user queries the Quantum Node for an inclusion_proof associated with their submitted proof.

    • The inclusion_proof is a Merkle proof that demonstrates that the user’s proof (πu\pi_{u}) was correctly verified using the appropriate verifier circuit identified by redn_image_idproving_schemeredn\_image\_id_{proving\_scheme}, corresponding to the public inputs (pisupis_{u}) for the circuit with circuit data ckt_datauckt\_data_{u}. It also confirms that this verified proof was included in the Merkle tree whose root is the Superproof Root.

  2. Validate their public inputs on-chain using the Inclusion Proof :

    i. Pre-storing the circuit_key:

    • The user first registers their circuit with the protocol and stores the circuit_key in their protocol-specific smart contract.

    • The circuit_key uniquely identifies the user’s circuit and is derived during the Circuit Registration phase as: circuitKey=Keccak(user_circuit_datarisc0_image_id)circuitKey=Keccak(user\_circuit\_data ∣∣ risc0\_image\_id)

    • This serves as the foundation for reconstructing and verifying the inclusion proof.

    ii. Sending Proof Details to the Contract:

    • The user submits the following to their protocol smart contract:

      • Keccak(πu)Keccak(\pi_{u}): The hash of the user’s individual proof.

      • pisupis_{u}: The public inputs corresponding to the proof.

      • inclusion_proofinclusion\_proof: A Merkle proof that links the user’s proof to the Superproof Root stored on-chain.

    iii. Reconstructing the Leaf:

    • Inside the protocol smart contract, the submitted data is used to reconstruct the Merkle leaf as: leaf=Keccak(circuitKeyKeccak(πu)Keccak(pisu))leaf=Keccak(circuitKey ∣∣ Keccak(π_u) ∣∣ Keccak(pis_u))

    • This leaf represents the hashed representation of the user’s proof and its associated public inputs, tied to the specific circuit.

    iv. Verifying the Inclusion Proof:

    • The reconstructed leaf is verified against the Merkle tree using the submitted inclusion_proof.

    • The smart contract calls the Quantum Verifier contract to cross-check the inclusion_proof against the Superproof Root stored on-chain during the On-Chain Superproof Verification step.

    v. Validation Outcome:

    • If the inclusion_proof is valid and the Merkle proof matches the Superproof Root, the user’s public inputs (pisupis_{u}) are confirmed as part of the aggregated proof.

    • This ensures that the user’s proof was correctly included, verified, and aggregated within the protocol.

Last updated