Integration

For the beta version of the protocol the access to Quantum is permissioned. Every permissioned protocol is given an AUTH_KEY and RPC Endpoint to communicate with quantum layer rpc endpoint. To get whitelisted and get your key reach out to @garvit_electron or @utsavjnn on Telegram.

Proving Schemes Supported

The quantum layer right now supports SnarkJS Groth16 (Circom), Gnark-groth16 proofs, and Halo2-KZG Proofs.

We only support Gnark-groth16 proofs whose proofs are generated and verified with stdgroth16.GetNativeProverOptions(ecc.BN254.ScalarField(), ecc.BN254.ScalarField())theoption enabled. This will be changed in the near future, and global support will be added for Gnark-groth16 proof.

A quick example of adding the above option to your Gnark Circuit:

// construct the groth16 proof of verifying Groth16 proof in-circuit
proof, err = groth16.Prove(ccs, pk, witness, stdgroth16.GetNativeProverOptions(ecc.BN254.ScalarField(), ecc.BN254.ScalarField()))
if err != nil {
	return false, "groth16.Prove failed::" + err.Error(), proof, pubInputs
}

// verify the Groth16 proof
err = groth16.Verify(proof, vk, publicWitness, stdgroth16.GetNativeVerifierOptions(ecc.BN254.ScalarField(), ecc.BN254.ScalarField()))
if err != nil {
	return false, "circuit verification failed::" + err.Error(), proof, pubInputs
}

We only support Shplonk EVM Verifier as of now for Halo2 Proofs.

Getting Started

Install npm package in your project

npm i quantum-sdk

Initialise Connection

After installing the npm package, quantum client helps you interact with the quantum server seamlessly.

import {Quantum} from "quantum-sdk";

Primarily before interacting with the server, you will need to establish a connection.

let rpcEndpoint = "xx.xxx.xx";
let accessKey = "xx-xxx"; // AUTH_KEY
const quantum = new Quantum(rpcEndpoint, accessKey); 

Check if the quantum instance can talk with the server. If rpcLive is True, a connection is established.

let rpcLive = await quantum.checkServerConnection();

Circuit Registration

Before submitting your proofs the user needs to register their circuit with the aggregation layer using their verification key.

SnarkJS Groth16 / Gnark Groth16:

import {Quantum} from "quantum-sdk";
import { ProofType } from "quantum-sdk/dist/src/enum/proof_type.js";
// @proof_type
// 1: snarkjs groth16 -> ProofType.GROTH16
// 2: gnark-groth16 -> ProofType.GNARK_GROTH16
let proofType = ProofType.GROTH16;
let vkeyPath = "../../vkey.json";//Add verification key json path
let numPis = 2; // Number of public inputs in the circuit
let circuitHash = (await quantum.registerCircuit(vkeyPath, numPis, proofType)).circuitHash["hash"];

Halo2-KZG Proofs:

Quantum also requires protocol.json, sg2.json, instance.json and proof.bin to register a Halo2 circuit. Here is a gist with an example to generate above files: https://gist.github.com/utsavjnn/fad24d5e3f5dcbaeef79dff9eea29226

import {Quantum} from "quantum-sdk";

let proofFilePath = "../../proof.bin";//Add example proof path
let sg2FilePath = "../../sg2.json"; // Add srs file path
let instanceFilePath = "../../instance.json"; // Add instance(pis) path
let protocolFilePath = "../../protocol.json"; // Add circuit info file path

let circuitHash = (await quantum.registerHalo2Circuit(proofFilePath, sg2FilePath, instanceFilePath, protocolFilePath)).circuitHash["hash"];

If the circuit_hash is successfully returned, which means the circuit was registered successfully. For different calls further to the quantum node circuit_hash is used for circuit identification.

Circuit Registration Status

The circuit must be registered before sending the proof for aggregation to the quantum node. You can check the registration status of your circuit using the circuitHash generated in the circuit registration call above. A successful registration produces a reductionCircuitHash, which is later used to verify that your proof was aggregated as part of the quantum superproof.

let registeredResponse = (await quantum.isCircuitRegistered(circuitHash));
// @status:
// NOT_PICKED: Circuit is not yet picked for registration, still in the queue
// IN_PROGRESS: Circuit is picked and is being registered 
// COMPLETED: Circuit is registered and proof can be submitted now.
// FAILED: Circuit registration failed. 
let status = registered_response.isCircuitRegistered.circuitRegistrationStatus;

// Reduction circuit hash unique to user's circuit
let reductionCircuitHash = registeredResponse.isCircuitRegistered.reductionCircuitHash

Proof Submission

Once the circuit is registered successfully, you can use your circuitHash to submit a proof for cheap verification on Ethereum. User sends (proof, public_inputs) corresponding to registered circuit. If a proof is successfully submitted, proofHash is returned from the quantum node.

let proofPath = "../../proof.json";//Add proof json path
let pisPath = "../../pis.json";//Add public inputs json path
let proofHash = await quantum.submitProof(proofPath, pisPath, circuitHash, proofType).proofHash["hash"]
console.log("proofHash:", proofHash)

Check Proof Status

proofHash is used to track the progress or get details of the aggregation request.

// @proof_data:
let proofStatus = (await quantum.getProofData(proofHash)).proofData.status
// *** Possible Responses to this call ***
// 1 (NOT_FOUND): proof_hash was not found, 
// 2 (REGISTERED): Proof is registered but yet to be picked up for aggregation
// 3 (REDUCING): Proof is being Reduced, and prepared for aggregation
// 4 (REDUCED): Proof is reduced and ready for aggregation
// 5 (AGGREGATING): Proof is being aggregated
// 6 (AGGREGATED): Proof is aggregated, and superproof is created but yet to be verified on Ethereum
// 7 (VERIFIED): Aggregated Proof is Verified on Ethereum
// 8 (REDUCTION_FAILED): Proof Reduction Failed, Contact Admin
// 9 (AGGREGATION_FAILED): Proof Aggregation Failed, Contact Admin

Currently, the aggregated proof is submitted on Sepolia once every ~30 minutes, so for STATUS to get toVERIFIEDcan take some time.

Verifying Protocol Proof On-Chain

Calculating User Circuit Vkey Hash

This is a unique hash corresponding to the user's circuit used to verify if proof corresponding to the user's circuit was included in the aggregated proof.

// Calculating unique vKeyHash
import { getVKeyHash } from "quantum-sdk/dist/quantum_helper";
const vKeyHash = getVKeyHash(circuit_hash, reduction_circuit_hash)

This vkeyHash is used to initialize the protocol contract below.

On-chain Contract

For protocols to integrate quantum, they would have to make some changes to their verification smart contract. The protocol smart contract imports ProtocolVerifier corresponding to their number of public inputs and uses it to verify if the pubInputs were verified as part of the aggregated proof.

The ProtocolVerifier contract offers functions verifyLatestPubInputs and verifyOldPubInputs to verify the latest and some old public inputs, respectively.

Latest Public Inputs

pubInputs are sent on-chain for verification purposes.

pragma solidity ^0.8.24;

// Import ProtocolVerifier_{n} corresponding to your n number of public inputs
// We support ProtocolVerifier_{1} to ProtocolVerifier_{15}; import accordingly
import {ProtocolVerifier_2} from "quantum-sdk/contracts/lib/ProtocolVerifier.sol";

contract Protocol {
    bytes32 vKeyHash;
    address constant QUANTUM = 0x33588B808Aafe5a3e1Bd1B5c999982cE7eAE09b6;

    constructor(bytes32 vKeyHash_) {
        vKeyHash = vKeyHash_;
    }

    function verifyLatestPubInputs(uint256[2] calldata pubInputs) external {
        ProtocolVerifier_2.verifyLatestPubInputs(pubInputs, vKeyHash, QUANTUM);
        
        // Protocol specific Business logic
        // ...
    }
}

Old Public Inputs

Note: Old public inputs can be verified for a limited time once aggregated.

Verification of old public inputs requires some Merkle-proof data, which can be fetched using the API as follows:

We only support Gnark-groth16 proofs whose proofs are generated and verified with stdgroth16.GetNativeProverOptions(ecc.BN254.ScalarField(), ecc.BN254.ScalarField())theoption enabled. This will be changed in the near future, and global support will be added for Gnark-groth16 proof.

let proofHash = "0x..." // generated during the 
let protocolProofResponse = await quantum.getProtocolProof(proofHash);
let protocolInclusionProof = protocolProofResponse.protocolProof

pubInputs along with its protocolInclusionProof are sent on-chain for verification purposes.

pragma solidity ^0.8.24;

// Import ProtocolVerifier_{n} corresponding to your n number of public inputs
// We support ProtocolVerifier_{1} to ProtocolVerifier_{15}; import accordingly
import {ProtocolVerifier_2} from "quantum-sdk/contracts/lib/ProtocolVerifier.sol";

contract Protocol {
    bytes32 vKeyHash;
    address constant QUANTUM = 0xd6aD4d6D83803f56032170a9d2ce71b030A1f4BC;

    constructor(bytes32 vKeyHash_) {
        vKeyHash = vKeyHash_;
    }

    function verifyOldPubInputs(
        ProtocolVerifier_2.ProtocolInclusionProof 
            calldata protocolInclusionProof,
        uint256[2] calldata pubInputs
    ) external {
        ProtocolVerifier_2.verifyOldPubInputs(
            protocolInclusionProof,
            pubInputs,
            vkHash,
            QUANTUM
        );
     
        // Protocol specific Business logic
        // ...   
    }
}

Last updated