Skip to main content

Proof Generation Requirements

This section is non-negotiable. Violating these rules will cause your application to fail.

Proof Architecture

ComponentResponsibilityLocation
ProverGenerates Groth16 proof from witnessBrowser (snarkjs) or Relayer
VerifierValidates proof on-chainProofVerifier contract
CircuitDefines what the proof provesCompiled .wasm + .zkey files

Who Generates Proofs

Production options:

  1. Browser-side (snarkjs): User generates proof locally. Requires ~10-30 seconds and ~100MB of circuit files.
  2. Relayer-assisted: Your backend calls the Ghost Protocol relayer. Relayer returns proof. User submits it.
Testnet Only - Mock Verifier

The current ProofVerifier at 0x2Bc0ac5508FF31A0Ad055A1F823C7653F48D37bE is a mock verifier that accepts any proof.

This is strictly for development. Mainnet will enforce full Groth16 verification.

Never assume a zero-proof will work outside testnet.

Public Input Ordering (CRITICAL)

The verifier expects public inputs in this exact order:

publicInputs[0] = root        // bytes32 as uint256
publicInputs[1] = nullifier // bytes32 as uint256
publicInputs[2] = dataHash // bytes32 as uint256
publicInputs[3] = recipient // address as uint256 (left-padded)
Wrong Order = Silent Failure

If you get this order wrong, proof verification will fail silently. The verifier will return false, and your transaction will revert with InvalidProof.

Proof Struct Format

struct Proof {
uint256[2] a; // G1 point
uint256[2][2] b; // G2 point (note: [2][2] not [2])
uint256[2] c; // G1 point
}

Converting from snarkjs

When converting from snarkjs output:

// snarkjs proof format
{
pi_a: [x, y, z], // Ignore z (always 1)
pi_b: [[x0, x1], [y0, y1], [z0, z1]], // Ignore z
pi_c: [x, y, z], // Ignore z
}

// Contract format
{
a: [pi_a[0], pi_a[1]],
b: [[pi_b[0][1], pi_b[0][0]], [pi_b[1][1], pi_b[1][0]]], // NOTE: indices swapped!
c: [pi_c[0], pi_c[1]],
}
Common Mistake

snarkjs outputs b in a different order than the contract expects. You must swap the inner array indices.

Relayer Dependency

The relayer provides two services:

ServiceRequired?Purpose
Root updatesYesUpdates Merkle roots on-chain
Proof generationNoCan self-host with snarkjs

If you self-host proof generation, you only depend on the relayer for root updates.

Trust model: The relayer is trusted for liveness, not correctness. Incorrect roots will cause proof verification to fail, not steal funds.

What Breaks If You Get This Wrong

MistakeSymptom
Wrong public input orderInvalidProof revert
Wrong proof array orderInvalidProof revert
Stale root (>100 updates ago)InvalidRoot revert
Wrong dataHashInvalidProof revert
Wrong nullifier computationInvalidProof revert
Value ≥ FIELD_SIZEUndefined behavior, likely revert

Testnet Mock Proof

For testnet development, use zero-filled proofs:

const mockProof = {
a: [0n, 0n],
b: [[0n, 0n], [0n, 0n]],
c: [0n, 0n],
};

This will be accepted by the mock verifier on testnet but will not work on mainnet.