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:
| System | Status | Backend | Setup | Proof Size | Verification Gas |
|---|---|---|---|---|---|
| Groth16 | Live (production) | Circom + snarkjs | Trusted setup (completed) | 256 bytes (constant) | ~220k gas |
| SP1 | Planned | Rust + SP1 zkVM | Transparent (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:
- No trusted setup. SP1 uses a transparent proof system (STARK-based), eliminating the trusted setup assumption entirely.
- 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).
- Rust expressiveness. Writing circuit logic in Rust rather than Circom enables more complex proving statements, better tooling, and easier auditing.
- 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:
| Precompile | Address | Operation | Gas Cost |
|---|---|---|---|
ecAdd | 0x06 | G1 point addition | 150 |
ecMul | 0x07 | G1 scalar multiplication | 6,000 |
ecPairing | 0x08 | Pairing 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:
| Variant | In-Circuit Name | Inputs | Uses |
|---|---|---|---|
| Poseidon2 | PoseidonT3 | 2 | Merkle tree internal nodes, nullifier derivation, access tags, token IDs |
| Poseidon4 | PoseidonT5 | 4 | OpenGhost data commitments |
| Poseidon7 | PoseidonT8 | 7 | CommitRevealVault 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.
| Property | Value |
|---|---|
| Public inputs | 8 (root, nullifier, withdrawAmount, recipient, changeCommitment, tokenId, policyId, policyParamsHash) |
| Private inputs | 7 (secret, nullifierSecret, amount, blinding, pathElements[20], pathIndices[20], newBlinding) |
| Key constraints | Commitment preimage, Merkle membership, nullifier derivation, amount conservation, change commitment, recipient/policy binding |
| Used by | CommitRevealVault |
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.
| Property | Value |
|---|---|
| Public inputs | 4 (root, dataHash, sessionNonce, accessTag) |
| Private inputs | 5 (secret, nullifierSecret, blinding, pathElements[20], pathIndices[20]) |
| Key constraints | Commitment preimage (Poseidon4), Merkle membership, access tag derivation |
| Used by | PersistentKeyVault, 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.
| Property | Value |
|---|---|
| Public inputs | Root, nullifier, dataHash |
| Private inputs | secret, nullifierSecret, blinding, pathElements[20], pathIndices[20] |
| Key constraints | Poseidon4 commitment preimage, Merkle membership, nullifier derivation |
| Used by | OpenGhostReveal |
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):
| Circuit | Constraints | Proof Time | Memory |
|---|---|---|---|
| GhostRedemption(20) | ~45,000 | 3-8 seconds | ~200 MB |
| Access Proof | ~12,000 | 1-3 seconds | ~80 MB |
| OpenGhost | ~15,000 | 1-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:
| Artifact | Size | Description |
|---|---|---|
circuit.wasm | ~2-5 MB | Compiled circuit (constraint system as WASM) |
circuit.zkey | ~10-50 MB | Proving 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)
| Stage | Tool | Output |
|---|---|---|
| Compile | circom | .r1cs (constraint system), .wasm (witness generator), .sym (debug symbols) |
| Trusted setup | snarkjs | .zkey (proving key) |
| Export VK | snarkjs | verification_key.json |
| Export verifier | snarkjs | Verifier.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.