Skip to main content

Circuits Overview

Specter's privacy guarantees are enforced by zero-knowledge circuits -- mathematical programs that define exactly what a prover must demonstrate without revealing any private data. Every commit/reveal operation in Ghost Protocol requires a valid ZK proof generated from one of these circuits. This page covers the proof system architecture, the two proving backends (Groth16 and SP1), and how circuits fit into the broader protocol.

Two Proof Systems

Specter employs a dual proof system strategy:

SystemStatusBackendSetupProof SizeVerification Gas
Groth16Live (production)Circom + snarkjsTrusted setup (completed)256 bytes (constant)~220k gas
SP1PlannedRust + SP1 zkVMTransparent (no trusted setup)~1-10 KB~300-500k gas (estimated)

Why Two Systems?

Groth16 is the current production system. It offers the smallest proof size and lowest verification gas cost of any practical ZK proof system. The tradeoff is that it requires a trusted setup ceremony -- a one-time procedure that generates the proving and verification keys. If the secret randomness from the ceremony is not properly destroyed, an attacker could forge proofs. Specter mitigated this risk through a multi-party ceremony where only one honest participant is needed for security.

SP1 is Succinct's zkVM, which executes arbitrary Rust programs inside a zero-knowledge virtual machine (RISC-V based). SP1 is planned as a second proving backend because:

  1. No trusted setup. SP1 uses a transparent proof system (STARK-based), eliminating the trusted setup assumption entirely.
  2. Post-quantum path. SP1's STARK-based proofs are not tied to elliptic curve assumptions. By integrating post-quantum signature schemes within SP1 programs, Specter can achieve quantum resistance for the proof layer itself (complementing the existing keccak256 quantum defense layer).
  3. Rust expressiveness. Writing circuit logic in Rust rather than Circom enables more complex proving statements, better tooling, and easier auditing.
  4. Upgradability. New proving logic can be deployed without a new trusted setup ceremony.

The tradeoff is cost: SP1 proofs are larger and more expensive to verify on-chain. The plan is for both systems to coexist -- Groth16 for gas-sensitive operations and SP1 for users who prefer stronger security assumptions.

Groth16 on BN254

The production proof system uses Groth16 over the BN254 (alt_bn128) elliptic curve. This combination was chosen for its EVM compatibility.

EVM Precompile Support

The Ethereum Virtual Machine includes three precompiled contracts for BN254 operations, defined in EIP-196 and EIP-197:

PrecompileAddressOperationGas Cost
ecAdd0x06G1 point addition150
ecMul0x07G1 scalar multiplication6,000
ecPairing0x08Pairing check (bilinear map)45,000 + 34,000/pair

These precompiles make Groth16 verification over BN254 roughly 10-100x cheaper than verifying proofs over other curves that would require pure Solidity elliptic curve arithmetic. Specter's EVM inherits these precompiles.

Trusted Setup

The Groth16 trusted setup was completed for all production circuits. The ceremony followed the standard two-phase process:

Phase 1 (Powers of Tau): A universal setup that produces structured reference string (SRS) elements. This phase is circuit-agnostic and can be reused across different circuits.

Phase 2 (Circuit-Specific): Each circuit contributes additional randomness to specialize the SRS for its specific constraint system. The output is a proving key and verification key for each circuit.

Powers of Tau → Phase 2 (GhostRedemption) → proving_key.zkey + verification_key.json
→ Phase 2 (AccessProof) → proving_key.zkey + verification_key.json
→ Phase 2 (OpenGhost) → proving_key.zkey + verification_key.json

The verification keys are embedded as constants in the on-chain verifier contracts. The proving keys are distributed to clients (webapp, mobile, proof relayer) for proof generation.

Security Assumption

Groth16 is secure under the knowledge-of-exponent assumption on BN254. Additionally, the trusted setup must have been performed honestly by at least one participant. If the toxic waste (secret randomness) from the setup is compromised, an attacker can forge proofs for arbitrary statements, which would allow draining all committed assets.

Circuit Architecture

All Specter circuits share common design principles:

Tree Depth

Every circuit that performs Merkle membership proofs uses a fixed tree depth of 20:

Tree capacity = 2^20 = 1,048,576 leaves

This depth is hardcoded in the circuit templates as a compile-time parameter. A depth of 20 provides over one million leaf slots -- sufficient for the foreseeable future -- while keeping the Merkle proof path short enough for efficient proving (20 Poseidon2 hashes per proof).

Hash Functions

All in-circuit hashing uses Poseidon, an algebraic hash function designed for arithmetic circuits:

VariantIn-Circuit NameInputsUses
Poseidon2PoseidonT32Merkle tree internal nodes, nullifier derivation, access tags, token IDs
Poseidon4PoseidonT54OpenGhost data commitments
Poseidon7PoseidonT87CommitRevealVault token commitments (with policy binding)

Poseidon is orders of magnitude cheaper than SHA-256 or keccak256 inside arithmetic circuits. A single Poseidon2 hash adds roughly 250 R1CS constraints, compared to ~25,000 for SHA-256.

Signal Types

Circom circuits distinguish between public and private signals (inputs):

  • Public signals are visible to the verifier (the on-chain contract). They are included in the proof's public inputs and checked by the verifier.
  • Private signals are known only to the prover. The ZK proof guarantees the prover knows valid private inputs satisfying the circuit's constraints, without revealing what those inputs are.
template ExampleCircuit() {
// Public inputs -- visible on-chain
signal input root;
signal input nullifier;

// Private inputs -- known only to prover
signal input secret;
signal input pathElements[20];
signal input pathIndices[20];

// Circuit constraints verify relationships between public and private inputs
// without revealing private values
}

Circuit Catalog

Specter currently defines three circuits:

1. GhostRedemption(20)

The primary circuit for token privacy. Proves that the prover owns a commitment in the Merkle tree and is authorized to withdraw a specified amount.

PropertyValue
Public inputs8 (root, nullifier, withdrawAmount, recipient, changeCommitment, tokenId, policyId, policyParamsHash)
Private inputs7 (secret, nullifierSecret, amount, blinding, pathElements[20], pathIndices[20], newBlinding)
Key constraintsCommitment preimage, Merkle membership, nullifier derivation, amount conservation, change commitment, recipient/policy binding
Used byCommitRevealVault

See Redemption Circuit for the full breakdown.

2. Access Proof Circuit

A lightweight circuit for proving access to a committed key without spending it. Used by PersistentKeyVault for reusable, unlinkable authentication.

PropertyValue
Public inputs4 (root, dataHash, sessionNonce, accessTag)
Private inputs5 (secret, nullifierSecret, blinding, pathElements[20], pathIndices[20])
Key constraintsCommitment preimage (Poseidon4), Merkle membership, access tag derivation
Used byPersistentKeyVault, OpenGhostReveal

See Access Proof Circuit for the full breakdown.

3. OpenGhost Circuit

The reveal circuit for OpenGhostVault data commitments. Structurally similar to GhostRedemption but without amount handling, change commitments, or policy binding.

PropertyValue
Public inputsRoot, nullifier, dataHash
Private inputssecret, nullifierSecret, blinding, pathElements[20], pathIndices[20]
Key constraintsPoseidon4 commitment preimage, Merkle membership, nullifier derivation
Used byOpenGhostReveal

Client-Side Proof Generation

All ZK proofs are generated on the client side -- the prover's secret inputs never need to leave their device (except when offloaded to the Proof Relayer for constrained devices).

Browser (snarkjs + WASM)

The webapp generates proofs entirely in the browser using snarkjs compiled to WebAssembly:

import * as snarkjs from "snarkjs";

// Load circuit artifacts (fetched once, cached in browser)
const wasmPath = "/circuits/GhostRedemption.wasm";
const zkeyPath = "/circuits/GhostRedemption.zkey";

// Generate proof with private inputs
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
privateInputs, // Object with all circuit input values
wasmPath, // Compiled circuit (WASM)
zkeyPath // Proving key from trusted setup
);

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

Performance (typical desktop browser):

CircuitConstraintsProof TimeMemory
GhostRedemption(20)~45,0003-8 seconds~200 MB
Access Proof~12,0001-3 seconds~80 MB
OpenGhost~15,0001-4 seconds~100 MB

Mobile (Proof Relayer)

Mobile devices (React Native / Expo) typically cannot generate proofs in-browser due to memory and compute constraints. Instead, the mobile app sends the private inputs to the Proof Relayer over TLS:

Mobile App → HTTPS/TLS → Proof Relayer → snarkjs → proof + publicSignals → Mobile App

The Proof Relayer is a trusted service operated by the Specter team. The private inputs are processed in memory and not persisted. For users requiring maximum privacy, the webapp's in-browser proving is recommended.

Proof Artifacts

Each circuit requires two artifacts for proof generation:

ArtifactSizeDescription
circuit.wasm~2-5 MBCompiled circuit (constraint system as WASM)
circuit.zkey~10-50 MBProving key from trusted setup (contains SRS elements)

These artifacts are loaded once and cached. The .zkey file is the largest artifact and dominates the initial load time.

Verification Flow

Circuit Compilation Pipeline

Circuits are written in Circom 2 and compiled through the following pipeline:

circuit.circom
↓ circom compiler
circuit.r1cs + circuit.wasm + circuit.sym
↓ snarkjs (Phase 2 trusted setup)
circuit.zkey (proving key)
↓ snarkjs (export verification key)
verification_key.json
↓ snarkjs (export Solidity verifier)
Verifier.sol (on-chain contract)
StageToolOutput
Compilecircom.r1cs (constraint system), .wasm (witness generator), .sym (debug symbols)
Trusted setupsnarkjs.zkey (proving key)
Export VKsnarkjsverification_key.json
Export verifiersnarkjsVerifier.sol (deployable Solidity contract)

The Verifier.sol output is the basis for the on-chain SP1ProofVerifier contract, with the verification key constants embedded directly in the contract bytecode.