Off-chain Proof Generation

Privacy by Design

The core axiom of our privacy protocol is that secret data must never leave the user's device. Therefore, the heavy lifting of Zero-Knowledge Proof (ZKP) generation is performed entirely client-side (within the user's browser or mobile application).

Unlike standard blockchain transactions where the wallet simply signs a message, this protocol requires the client to perform a cryptographic computation to prove validity without revealing the underlying secrets (such as the private key or the specific note being spent).

The Generation Pipeline

The proof generation process transforms a user's intent (e.g., "Transfer 10 tokens") into a succinct cryptographic proof. This pipeline executes in a strictly linear fashion:

  1. Input Collection: Gathering public data (Merkle roots) and private data (Private keys, path indices).

  2. Witness Calculation: Computing the internal state of the arithmetic circuit.

  3. Proving: Generating the Groth16 proof points () using the Proving Key.

  4. Serialization: Formatting the proof for Solidity ingestion.


Circuit Artifacts

To generate a proof, the client requires two static artifacts derived from the protocol's circuit compilation and Trusted Setup. These are fetched dynamically to minimize the initial application bundle size.

1. The Circuit Logic (.wasm)

The WebAssembly binary contains the compiled arithmetic circuit. It defines the constraints of the system (e.g., "The hash of the private key must match the public commitment"). The client uses this to calculate the Witness.

2. The Proving Key (.zkey)

This file contains the proving key generated during the Phase 2 Trusted Setup. It is specific to the circuit and is required to mathematically sign the proof. It ensures that the proof was generated using the correct circuit logic.


Phase 1: Witness Calculation

Before a proof can be generated, the client must calculate the Witness.

In the context of Rank-1 Constraint Systems (R1CS), the witness is the complete assignment of values to every "wire" in the circuit. This includes:

  • Public Inputs: Known to everyone (e.g., the Merkle Root).

  • Private Inputs: Known only to the user (e.g., the Secret Key).

  • Intermediate Wires: The results of every internal calculation (e.g., hashes, additions) inside the circuit.

The "Constraint Satisfaction" Check:

The WASM binary executes the circuit logic using the user's inputs. If the inputs are valid (e.g., the private key generates the correct commitment), the circuit is "satisfied," and a valid witness is produced. If the inputs are invalid, the calculation fails immediately, preventing the creation of an invalid proof.


Phase 2: Groth16 Proving

Once the witness is calculated, the client uses the .zkey to transform the witness into a succinct proof. This relies on the Groth16 algorithm.

The algorithm maps the satisfying assignment (the witness) onto the Elliptic Curve pairing-friendly groups ( and ). The result is a proof consisting of only three elements, regardless of the complexity of the transaction:

  1. : A point on curve .

  2. : A point on curve (twisted curve).

  3. : A point on curve .

Performance Note: This step is CPU-intensive. While optimized for WASM, generating a proof for complex circuits (like 16-level Merkle Trees) typically takes 1–3 seconds on a modern consumer device.


Output Serialization

The final step is formatting the mathematical output for the Ethereum Virtual Machine (EVM). The raw coordinates of the elliptic curve points are serialized into a standard Solidity format.

The client outputs a JSON object containing:

  • The Proof: The vectors a, b, and c.

  • Public Signals: An array containing the public inputs (e.g., [root, nullifierHash, recipient]).

This output acts as the payload for the smart contract call. Importantly, the original witness and private inputs are discarded immediately after proof generation and are never transmitted over the network.


Security Verification (Local)

Before submitting the transaction to the blockchain, the client performs a "Sanity Check." It runs the verification algorithm locally using the corresponding Verification Key.

This ensures:

  1. The proof was generated correctly.

  2. The public signals match the intended transaction parameters.

  3. The user does not waste gas fees submitting a proof that is guaranteed to revert on-chain.

Last updated