Electron Labs
  • Quantum: Proof Aggregation for Ethereum
    • Introduction
    • Proof Lifecycle and Architecture
    • Quantum Protocol Specifications
    • Integration
    • Developer tutorial for integrating Quantum
    • Gas Cost Analysis
    • Developer Chat
Powered by GitBook
On this page
  • Step 1: Setup Nodejs repo
  • Step 2: Retrive auth key
  • Step 3: Circuit registration with Quantum
  • Step 3.1: Check if registration is completed
  • Step 4: Proof submission to Quantum
  • Step 4.1: Check if proof is verified
  • Step 5: Retrieve inclusion proof from Quantum
  • Step 6: Verifying public inputs in contract
  1. Quantum: Proof Aggregation for Ethereum

Developer tutorial for integrating Quantum

PreviousIntegrationNextGas Cost Analysis

Last updated 2 months ago

In the following page, we detail all the steps required for a protocol to integrate with Quantum. We use the circuit as an example throughout the demo.

Step 1: Setup Nodejs repo

Firstly, install quantum-sdk in your project:

npm i quantum-sdk
npm i axios

Step 2: Retrive auth key

Now, we have to retrieve an auth key to get access to quantum testnet.

import axios from "axios";

const RPC_ENDPOINT = "xx.xxx.xx";

async function main() {
    let requestData = { "protocol_name": "your_protocol_name" }
    let resp = await axios.post(`${RPC_ENDPOINT}/auth/protocol`, requestData);
    const authKey = resp.data.auth_token;
    
    console.log(authKey);
}

Step 3: Circuit registration with Quantum

import { Quantum } from "quantum-sdk";
import { ProofType } from "quantum-sdk/dist/src/enum/proof_type";

const RPC_ENDPOINT = "xx.xxx.xx";
const AUTH_KEY = "xx-xxx";
const quantum = new Quantum(RPC_ENDPOINT, AUTH_KEY);

async function main() {
  const vkey_path = "path/to/verification_key.json";
  const num_pis = 12;

  let connection = await quantum.checkServerConnection();
  if (!connection) {
    throw Error("Not connected with Quantum server");
  }

  let circuit_hash = (
    await quantum.registerSnarkJSGroth16Circuit(vkey_path)
  ).circuitHash.asString();

  console.log(circuit_hash);
}

The circuit_hash received above is basically an identifier for this circuit, and here onward any interactions for this circuit will be done using this circuit_hash.

Step 3.1: Check if registration is completed

Next, we check if the circuit registration is complete.

import { Quantum } from "quantum-sdk";
import { CircuitRegistrationStatus } from "quantum-sdk/dist/src/enum/circuit_registration_status";

const RPC_ENDPOINT = "xx.xxx.xx";
const AUTH_KEY = "xx-xxx";
const quantum = new Quantum(RPC_ENDPOINT, AUTH_KEY);

async function main() {
  let connection = await quantum.checkServerConnection();
  if (!connection) {
    throw Error("Not connected with Quantum server");
  }

  const circuit_hash = "xxxxxxxxxx"; // circuit hash that we received during registration

  let registered_response = await quantum.isCircuitRegistered(circuit_hash);
  let status =
    registered_response.isCircuitRegistered.circuitRegistrationStatus;
  if (status != CircuitRegistrationStatus.Completed) {
    throw Error("Circuit not registered yet");
  }
}

Step 4: Proof submission to Quantum

import { Quantum } from "quantum-sdk";
import { ProofType } from "quantum-sdk/dist/src/enum/proof_type";

const RPC_ENDPOINT = "xx.xxx.xx";
const AUTH_KEY = "xx-xxx";
const quantum = new Quantum(RPC_ENDPOINT, AUTH_KEY);

async function main() {
  const proof_path = "path/to/proof.json";
  const public_path = "path/to/public.json";

  let connection = await quantum.checkServerConnection();
  if (!connection) {
    throw Error("Not connected with Quantum server");
  }

  const circuit_hash = "xxxxxxxxxx"; // circuit hash that we received during registration

  let proof_hash = (
    await quantum.submitSnarkJSGroth16Proof(
      proof_path,
      public_path,
      circuit_hash,
    )
  ).proofHash.asString();

  console.log(proof_hash);
}

The proof_hash received above will be used to check the status of the proof.

Step 4.1: Check if proof is verified

To check if a proof has been aggregated and submitted on chain:

import { Quantum } from "quantum-sdk";
import { ProofStatus } from "quantum-sdk/dist/src/enum/proof_status";

const RPC_ENDPOINT = "xx.xxx.xx";
const AUTH_KEY = "xx-xxx";
const quantum = new Quantum(RPC_ENDPOINT, AUTH_KEY);

async function main() {
  let connection = await quantum.checkServerConnection();
  if (!connection) {
    throw Error("Not connected with Quantum server");
  }

  let proof_hash = "xxxxxxxxxx"; // proof hash that we received during proof submission

  let proof_status = (await quantum.getProofData(proof_hash)).proofData.status;
  if (proof_status != ProofStatus.VERIFIED) {
    throw Error("Not verified on chain yet");
  }
}

Step 5: Retrieve inclusion proof from Quantum

When an aggregated proof is submitted on chain, all the individual proofs’ public inputs are arranged in a merkle tree and it's root is stored on chain. The merkle proof for the same is used to verify validity of public inputs on-chain. It can be retrieved as follows:

import { Quantum } from "quantum-sdk";

const RPC_ENDPOINT = "xx.xxx.xx";
const AUTH_KEY = "xx-xxx";
const quantum = new Quantum(RPC_ENDPOINT, AUTH_KEY);

async function main() {
  let connection = await quantum.checkServerConnection();
  if (!connection) {
    throw Error("Not connected with Quantum server");
  }

  let proof_hash = "xxxxxxxxxx"; // proof hash that we received during proof submission

  let protocol_proof = (await quantum.getProtocolProof(proof_hash)).protocolProof;
  let merkle_proof = protocol_proof.merkleProof;
  let merkle_proof_position = protocol_proof.merkleProofPosition;
}

Step 6: Verifying public inputs in contract

/* other imports */

// 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 VenmoSendProcessorV2 is ISendProcessor, BaseProcessorV2 {
    
    /* othercontract state */
    
    bytes32 circuitHash; // the same circuit_hash that we received from quantum
    address constant QUANTUM; // address of quantum contract

    constructor(
        /* other fields */,
        bytes32 circuitHash_,
        address quantum_
    )
        /* other constructors */
    {
        circuitHash = circuitHash_;
        QUANTUM = quantum_;
    }

    /* other functions */

    function processProof(
        uint256[12] calldata _signals,
        uint256 merkleProofPosition,
        bytes32[] calldata merkleProof
    )
        /* function keywords and return type */
    {
        ProtocolVerifier_12.verifyPubInputs(
            _signals,
            merkleProofPosition,
            merkleProof,
            circuitHash,
            QUANTUM,
        );

        /* remaining function logic */
    }
}

With this, we have completed the integration of Quantum with this protocol. If you are still facing any problems in integrating Quantum with your protocol, please reach out to us.

Now, assuming that the circuit's proving key, and verification key files have been generated following the steps detailed in , the next step is to register the verification key file.

Next, we will submit a proof to Quantum for aggregation. After the protocol has generated proof.json and public.json files through either or , they can submit the proof to quantum as follows:

To verify public inputs on chain, the protocol needs to make slight changes to their contract, which are summarised below. We demonstrate by making changes to contract.

ZKP2P's VenmoOnRamp2
Circom's documentation
Snarkjs
Rapidsnark
ZKP2P's VenmoSendProcessorV2