ZK-SNARK Application Development
ZK-SNARK (Zero-Knowledge Succinct Non-interactive ARgument of Knowledge) — proof that you know a secret without revealing the secret. Sounds abstract until you face a concrete task: prove you're over 18 without sending your birthdate, confirm wallet balance without revealing your address, or verify program execution without re-running it.
Tornado Cash (before sanctions) moved ~7 billion dollars using ZK-SNARK to prove withdrawal rights without linking to the deposit address. Zcash protects transactions using the same technology. Polygon zkEVM proves correctness of thousands of transactions with one compact proof.
How ZK-SNARKs Work: Deep Enough to Build
From Task to Circuit
Any computational task that can be written as a set of arithmetic constraints can be proven via ZK-SNARK. A circuit is not a regular program. It's a description of computation as a system of equations over a finite field.
Take a simple task: prove I know x such that x² + x + 5 = y, where y is a public value. The circuit:
signal input x;
signal output y;
signal x_squared;
x_squared <== x * x;
y <== x_squared + x + 5;
This is Circom — the main language for describing ZK circuits. The compiler transforms the circuit into a set of R1CS constraints (Rank-1 Constraint System), then into QAP (Quadratic Arithmetic Program), which is the mathematical foundation for SNARK.
Groth16 vs PLONK vs FFLONK
Proof system choice determines all other parameters: proof size, generation time, trusted setup size, verification time in contract (and corresponding gas cost).
| System | Proof size | Verify gas | Trusted setup | Prover time |
|---|---|---|---|---|
| Groth16 | ~200 bytes | ~250k gas | Per-circuit | Fast |
| PLONK | ~800 bytes | ~450k gas | Universal | Slower |
| FFLONK | ~800 bytes | ~200k gas | Universal | Slower |
| STARKs | >40 KB | >1M gas | Not needed | Fast |
Groth16 — smallest proof and lowest gas for verification. Disadvantage: each circuit requires separate trusted setup ceremony. If the circuit changes — new ceremony needed. Used by: Tornado Cash, Zcash, most production ZK apps before 2022.
PLONK — universal trusted setup (Powers of Tau) suitable for any circuit up to a size limit. You can change the circuit without a new ceremony. Proof is larger but acceptable for most apps. Used by: zkSync Era, Aztec Protocol.
FFLONK — optimized PLONK version with lower gas for verification. Used in Polygon zkEVM.
We recommend Groth16 for production apps with fixed circuits and high transaction volume (minimal verification gas). PLONK — for prototypes and apps where circuit might change.
Trusted Setup and Why It Matters
Trusted setup is a cryptographic ceremony generating proof parameters. If someone keeps "toxic waste" (intermediate values) — they can generate fake proofs. This isn't theoretical: if setup is compromised, the entire trust system collapses.
Groth16 requires two-stage ceremony:
- Powers of Tau — universal part, circuit-independent. Public trusted setups exist from Ethereum Foundation (Hermez 1, 2) supported by thousands of participants. We use those, don't generate our own.
- Phase 2 — circuit-specific part. For production systems we organize ceremony with multiple participants via snarkjs.
Stack and Tools
Circom 2 — language for writing circuits. Rust compiler, significantly faster than v1. Supports templates for circuit reuse.
snarkjs — JavaScript library for proof generation and verification, conducting trusted setup, exporting verifier to Solidity.
circomlibjs — library of standard circuits: hash functions (Poseidon, MiMC, SHA256 in circuit), signatures (EdDSA, ECDSA), Merkle trees.
Noir (Aztec) — alternative language with higher abstraction level, compiles to PLONK. Easier for developers familiar with Rust syntax.
SnarkVM / Leo (Aleo) — for Aleo blockchain if task requires privacy-first L1.
Typical Project: ZK Age Verification
Task: user proves they're over 18 using verified credential data (e.g., from KYC provider). Provider signed the birthdate with their key. User doesn't reveal birthdate, only proves the fact.
Circuit (simplified):
template AgeVerification(merkleDepth) {
// Public inputs
signal input currentDate; // current date (public)
signal input issuerPubKeyHash; // hash of provider's pubkey (public)
// Private inputs (witness)
signal input birthDate; // birthdate (private)
signal input signature[2]; // provider's signature (private)
signal input issuerPubKey[2]; // provider's pubkey (private)
// Verify provider's signature
component sigVerifier = EdDSAVerifier();
sigVerifier.msg <== birthDate;
sigVerifier.pubKey <== issuerPubKey;
sigVerifier.sig <== signature;
// Verify pubKey matches public hash
component hasher = Poseidon(2);
hasher.inputs <== issuerPubKey;
issuerPubKeyHash === hasher.out;
// Verify age
signal age;
age <== currentDate - birthDate;
component ageCheck = GreaterThan(32);
ageCheck.in[0] <== age;
ageCheck.in[1] <== 18 * 365; // 18 years in days
ageCheck.out === 1;
}
Solidity verifier is generated automatically via snarkjs and contains precompile calls for elliptic curve pairing verification (EIP-197). Gas cost for verification — about 250k for Groth16.
Performance and Limitations
Proving time depends on circuit size (number of constraints). Benchmarks for Groth16 on modern hardware:
| Constraints in circuit | CPU prover time | GPU prover time |
|---|---|---|
| 100k | ~5 sec | ~0.5 sec |
| 1M | ~60 sec | ~5 sec |
| 10M | ~15 min | ~60 sec |
For web apps proving in browser is realistic for circuits up to 500k constraints (via WASM compilation). Heavier circuits require server prover or specialized prover service (Sindri, Succinct).
Poseidon hash in circuit is much more efficient than SHA256: Poseidon — ~250 constraints per hash, SHA256 — ~27000. That's why all ZK-friendly protocols use Poseidon.
Development Process
Research and circuit design (1-2 weeks). Convert business task to arithmetic constraints. Estimate circuit size and proving time. Choose proof system. This is the most important stage — error in circuit design may require complete redesign.
Circuit development in Circom (1-2 weeks). Write circuit, cover with unit tests via Jest + circomlibjs. Separately verify arithmetic correctness of constraints.
Trusted Setup. For prototype — use test entropy. For production — organize ceremony with multiple participants.
Verifier development and integration (1 week). Generate Solidity verifier via snarkjs. Integrate into main smart contract. Write TypeScript SDK for frontend.
Circuit audit. ZK circuits have specific vulnerabilities: underconstraining (circuit doesn't check all conditions), overconstraining (circuit makes legitimate proofs impossible), signal aliasing. This is a separate audit type requiring specialization.
Timeline: from 1 week (simple circuit, PLONK) to 3 months (complex zkApp with custom cryptographic primitives). Cost is calculated after detailed requirements analysis.







