Integrating ZKP for Private Transactions
Ethereum is a public ledger. Every transfer() is visible to everyone: sender, receiver, amount. For most DeFi protocols this is fine. But certain application classes — corporate payments, confidential voting, private auctions, payroll on blockchain — where transparency becomes a problem. Zero-Knowledge Proof allows proving transaction correctness without revealing its contents.
What ZKP Means for Transactions
ZKP is a cryptographic construction where prover convinces verifier of a statement's truth without revealing the underlying information. For private transactions this means:
- "I transferred 100 tokens and my balance is non-negative after" — without revealing balance, amount, or addresses
- "I voted in this proposal" — without revealing which option
- "This is a legitimate transaction per protocol rules" — without revealing details
The blockchain primarily uses zk-SNARKs (Groth16, PLONK) and zk-STARKs. The difference isn't just technical — it impacts architecture choice.
Key Proof Systems
| System | Proof Size | Verifier Gas | Trusted Setup | Post-Quantum |
|---|---|---|---|---|
| Groth16 | ~200 bytes | ~300K gas | Yes (per-circuit) | No |
| PLONK | ~400 bytes | ~500K gas | Universal | No |
| STARKs | 40-200 KB | High | No | Yes |
| Noir (Barretenberg) | ~500 bytes | ~400K gas | Universal | No |
For on-chain verification Groth16 is most gas-efficient. But circuit-specific trusted setup creates operational overhead: each new circuit requires a separate ceremony (or using universal SRS).
PLONK with universal trusted setup is simpler to operate. Aztec Protocol uses UltraPLONK. Scroll and the zkSync stack use custom variants.
Architectural Patterns for Private Transactions
UTXO-based Model (Zcash Approach)
Funds are stored as notes — encrypted UTXOs. Each transaction spends old notes and creates new ones. On-chain, only the commitment (note hash) and nullifier (prevents double-spend) are stored.
spend(note) → proof(note exists in tree, note not spent, balance >= amount)
→ reveal nullifier (add to nullifier set)
→ create new note commitments
This is Tornado Cash, Aztec v1, Zcash's approach. The Solidity verifier checks the ZK-proof, verifies the nullifier in a mapping, updates merkle tree commitments.
Weakness — Tornado Cash showed that even with ZKP, metadata analysis (timing attacks, amounts) can deanonymize participants. ZKP hides transaction relationships but not behavioral patterns.
State Encryption via FHE (Fully Homomorphic Encryption)
Fhenix and Inco use FHE to encrypt state on-chain. Smart contracts work with encrypted values directly. Conceptually different from ZKP: not "prove transaction is correct" but "execute operation on encrypted data". Technology is immature — FHE computational overhead is huge — but actively developing.
Tools for ZKP Integration
Circom + SnarkJS
Most common stack for custom circuits:
circuit.circom → compile → R1CS → Powers of Tau → proving key + verification key
→ verifier.sol (Solidity)
Circom is a language for describing arithmetic circuits. The compiler transforms the circuit into R1CS (Rank-1 Constraint System). SnarkJS generates keys and Solidity verifier.
Example simple circuit for range checking:
pragma circom 2.1.0;
include "circomlib/circuits/comparators.circom";
template RangeProof(bits) {
signal input value; // private
signal input maxValue; // public
component lt = LessThan(bits);
lt.in[0] <== value;
lt.in[1] <== maxValue;
lt.out === 1;
}
component main {public [maxValue]} = RangeProof(64);
This proves that private value is less than public maxValue, without revealing the value itself.
Noir (Aztec)
Higher-level language, Rust-like. Abstracts from arithmetic circuits. Use for projects where the team doesn't want to dive into R1CS.
fn main(x: Field, y: pub Field) {
assert(x != y);
// x remains private, y is public input
}
Noir compiles to ACIR → Barretenberg backend → Solidity verifier.
ZK-email, Semaphore, WorldCoin: Ready Primitives
Semaphore is a library for anonymous signals (voting, whitelist membership). Proves group membership without revealing identity. Often used for governance:
// User proves they are in the voter group
// without revealing their address
semaphore.verifyProof(
merkleTreeRoot,
nullifierHash, // prevents re-voting
signal, // vote hash
proof
);
Compliance and Privacy: Not Mutually Exclusive
Regulators don't worry about privacy itself, but money laundering risk. ZKP enables selective disclosure — transactions are private to observers, but the owner can reveal details to the regulator with cryptographic proof of correctness.
Aztec v3 (Noir) and Aleo build compliance layers on top of ZKP: viewing keys let auditors see a specific user's transactions without breaking privacy for others.
ZKP Integration Process
Analytics. Determine what data needs to stay hidden. Amounts? Addresses? Fact of participation? This determines architecture: UTXO model, commitment scheme, or selective disclosure.
Circuit design. Formalize the constraint system. Which inputs are private (witness), which public? What statements do we prove? Check for soundness — is there a path to generate valid proof for incorrect statement?
Development. Write circuit in Circom or Noir, test on test vectors. Generate Solidity verifier. Integrate into smart contract. Develop prover service (ZK proofs are computed off-chain, take seconds-to-minutes of CPU).
Audit. Circuit soundness is separate from Solidity audit. Vulnerability can be in constraint system, not smart contract. Use Circomspect for static Circom analysis.
Performance. Proof generation on server: Groth16 — 1-5 seconds, PLONK — 5-30 seconds. For mobile/browser clients — WebAssembly prover (SnarkJS) or backend delegation.
Timeline Estimates
Integrating an existing ZKP primitive (Semaphore, Tornado fork): 2-4 weeks. Developing custom circuit from scratch (range proof, private transfer): 4-8 weeks. Full ZKP protocol with compliance layer, prover service, and frontend: 2-3 months.
Timelines significantly depend on circuit complexity and proving system choice — PLONK is easier to maintain, Groth16 is faster and cheaper on-chain.







