Proof Generation Requirements
This section is non-negotiable. Violating these rules will cause your application to fail.
Proof Architecture
| Component | Responsibility | Location |
|---|---|---|
| Prover | Generates Groth16 proof from witness | Browser (snarkjs) or Relayer |
| Verifier | Validates proof on-chain | ProofVerifier contract |
| Circuit | Defines what the proof proves | Compiled .wasm + .zkey files |
Who Generates Proofs
Production options:
- Browser-side (snarkjs): User generates proof locally. Requires ~10-30 seconds and ~100MB of circuit files.
- Relayer-assisted: Your backend calls the Ghost Protocol relayer. Relayer returns proof. User submits it.
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)
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]],
}
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:
| Service | Required? | Purpose |
|---|---|---|
| Root updates | Yes | Updates Merkle roots on-chain |
| Proof generation | No | Can 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
| Mistake | Symptom |
|---|---|
| Wrong public input order | InvalidProof revert |
| Wrong proof array order | InvalidProof revert |
| Stale root (>100 updates ago) | InvalidRoot revert |
| Wrong dataHash | InvalidProof revert |
| Wrong nullifier computation | InvalidProof revert |
| Value ≥ FIELD_SIZE | Undefined 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.