Skip to main content

Generating Proofs

ZK proofs can be generated client-side (in the browser or Node.js using snarkjs) or server-side via the proof relayer API.

Client-side with snarkjs

Installation

npm install snarkjs circomlibjs

Generating a redemption proof

import * as snarkjs from 'snarkjs';
import { buildPoseidon } from 'circomlibjs';

// Prepare circuit inputs
const input = {
// Public inputs
root: merkleRoot,
nullifier: computedNullifier,
withdrawAmount: withdrawAmount.toString(),
recipient: BigInt(recipientAddress).toString(),
changeCommitment: changeCommitment || '0',
tokenId: tokenIdHash.toString(),
policyId: '0',
policyParamsHash: '0',

// Private inputs
secret: secret.toString(),
nullifierSecret: nullifierSecret.toString(),
amount: amount.toString(),
blinding: blinding.toString(),
pathElements: merkleProof.pathElements,
pathIndices: merkleProof.pathIndices,
newBlinding: newBlinding.toString(),
};

// Generate proof
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
input,
'/path/to/redemption.wasm', // compiled circuit
'/path/to/redemption_final.zkey' // proving key
);

// Format proof for on-chain submission (8 uint256 values)
const calldata = await snarkjs.groth16.exportSolidityCallData(proof, publicSignals);

Circuit artifacts

Proof generation requires two files from the trusted setup:

FileDescriptionTypical size
redemption.wasmCompiled circuit (WebAssembly)~5 MB
redemption_final.zkeyProving key~50 MB

These files should be hosted on a CDN and fetched by the client. They are not secret — only the private inputs are.

Server-side via proof relayer

The proof relayer at https://relayer.specterchain.com generates proofs server-side. This is useful when:

  • Client devices lack the compute power for proof generation
  • You want consistent proof generation times
  • The WASM circuit files are too large for client download

API endpoint

POST https://relayer.specterchain.com/api/proof/generate

Request

{
"secret": "123456789",
"nullifierSecret": "987654321",
"amount": "1000000000000000000",
"blinding": "111222333",
"tokenIdHash": "0",
"recipient": "0xRecipientAddress",
"withdrawAmount": "1000000000000000000",
"newBlinding": "0"
}

Response

{
"proof": [
"0x...", "0x...", "0x...", "0x...",
"0x...", "0x...", "0x...", "0x..."
],
"publicInputs": [
"0x...", "0x...", "0x...", "0x...",
"0x...", "0x...", "0x...", "0x..."
]
}

Rate limits

LimitValue
Requests per minute per IP5
Max concurrent proofs2
warning

Proof generation is CPU-intensive. The proof relayer has strict rate limits to prevent denial of service. For high-throughput applications, generate proofs client-side.

Authentication

The proof relayer requires HMAC authentication. Include the signature in the request headers:

X-HMAC-Signature: <computed-hmac>

Proof format for on-chain submission

The Groth16 proof consists of 3 elliptic curve points (A, B, C) encoded as 8 uint256 values:

proof[0], proof[1]  → Point A (G1)
proof[2], proof[3] → Point B x-coordinate (G2)
proof[4], proof[5] → Point B y-coordinate (G2)
proof[6], proof[7] → Point C (G1)

These map directly to the uint256[8] proof parameter in the reveal() function.