Pedersen commitments + Groth16 in Cadence: the first real benchmarks
Zero published benchmarks for ZK in Cadence. I measured them. PedersenBabyJub addCommits ~16 CU, ConfidentialToken transfer ~327k gas, mixer Poseidon cross-VM, and more. What's feasible, what's not.
Zero published benchmarks for ZK in Cadence. Not for Pedersen commitments. Not for Groth16. Not for Poseidon. I measured them. Here are the numbers.
When I started building ZK cryptography primitives on Flow, I couldn’t find a single published benchmark. The academic papers benchmarked EVM. The Flow documentation benchmarked basic transactions. Nobody had measured what it actually costs to do ZK math on Flow.
So I measured it myself. These benchmarks come from the work that produced openjanus/primitives — the public library of Pedersen, Groth16, BabyJub, and ECIES primitives published via @claucondor/sdk.
Flow’s execution model: two cost dimensions
| Dimension | Layer | Budget | Notes |
|---|---|---|---|
| Computation Units (CU) | Cadence | 9,999 CU/tx | Charged for script + tx execution |
| EVM gas | EVM (cross-VM) | No Cadence limit | Paid when Cadence calls EVM.run() |
The implication: some ZK operations are cheap in CU but expensive in EVM gas (Groth16 via EVM precompile), while others are native Cadence math (Pedersen scalar multiplication) and cost pure CU.
Full benchmark table
| Operation | CU Cost | EVM Gas | Layer | Dollar cost (est.) |
|---|---|---|---|---|
| BN254Field — fieldAdd / fieldMul | <10 CU | — | Cadence native | ~$0.000001 |
| PedersenBabyJub — addCommits | ~16 CU | — | Cadence native | ~$0.000002 |
| PedersenBabyJub — createCommit | ~18 CU | — | Cadence native | ~$0.000002 |
| BeaconBound commit (VRF) | ~25 CU | — | Cadence native | ~$0.000003 |
| Poseidon 2-input (native Cadence) | ~45 CU | — | Cadence native | ~$0.000005 |
| Poseidon 2-input (EVM Yul bridge) | ~120 CU | ~35k gas | CrossVM | ~$0.002 |
| PedersenBabyJub — commitScalar | ~180 CU | — | Cadence native | ~$0.00002 |
| Groth16 verify (EVM verifier) | ~200 CU | ~270k gas | CrossVM | ~$0.004–0.010 |
| PedersenBabyJub — verifyCommitment | ~340 CU | — | Cadence native | ~$0.00004 |
| ConfidentialToken transfer (full) | ~350 CU | ~327k gas | CrossVM | ~$0.005–0.012 |
| Mixer deposit (Poseidon Merkle) | ~280 CU | ~210k gas | CrossVM | ~$0.003–0.008 |
| Mixer withdraw (nullifier + proof) | ~320 CU | ~290k gas | CrossVM | ~$0.004–0.010 |
| FROST BLS12-381 sign (partial) | ~800 CU | — | Cadence native | ~$0.0001 |
| FROST BLS12-381 verify (EIP-2537) | ~900 CU | ~185k gas | CrossVM | ~$0.002–0.005 |
Dollar cost estimates at Flow EVM current gas prices. CU cost per dollar is negligible at current FLOW price — the EVM gas side dominates economics.
Pedersen commitments: native Cadence
PedersenBabyJub is my implementation of Pedersen commitments on the Baby Jubjub elliptic curve, implemented natively in Cadence. Baby Jubjub is the curve used in Circom/snarkjs — the standard toolchain for ZK proofs in the Ethereum ecosystem.
The Pedersen commitment scheme:
Commitment formula:
C = r·G + v·H
where:
G, H = fixed generator points on Baby Jubjub
r = random blinding factor (secret)
v = value to commit to (secret)
C = commitment (public, on-chain)
Homomorphic property:
C1 + C2 = (r1+r2)·G + (v1+v2)·H
→ addCommits costs ~16 CU (two point additions)
The homomorphic property is what makes Pedersen useful for confidential balances: you can add commitments without revealing the underlying values.
Operation costs breakdown
All well within the 9,999 CU transaction limit. All comfortably production-viable.
Groth16 verification: cross-VM strategy
Groth16 proof verification does an elliptic curve pairing check on BN254. Cadence doesn’t have native BN254 pairing operations, but Flow’s EVM layer does — via the EIP-197 precompile at address 0x08.
- 1circuit.circom → witnessOff-chain (client). The circuit defines what's being proven.
- 2snarkjs provesOff-chain (client). Generates the (π_a, π_b, π_c) + public signals.
- 3ABI-encode the proof + signalsInside the Cadence transaction. Trivial Cadence-side work.
- 4EVM.run() → verifier contractCross-VM bridge call. Cadence calls into the EVM verifier deployed on Flow EVM. Most of the gas budget lives here.
- 5Decode UInt8 → Bool, assert(verified)Back in Cadence. If proof fails, the whole transaction reverts atomically.
- 6vault.withdraw() + nullifier.insert()Cadence state mutation. The actual privacy-preserving effect.
All within the 9,999 CU per-transaction ceiling. One Cadence tx, atomic.
Poseidon hash: native vs. EVM strategy comparison
Poseidon is the ZK-friendly hash function used in most modern ZK applications (Circom, Iden3).
Native Cadence
- ~45 CU per 2-input hash
- No EVM gas — pure Cadence math
- ~$0.000005 per hash
- Best for: high-frequency hashing, Merkle trees ≤ depth 20
- Merkle depth 20 = ~900 CU total — tight but feasible
EVM Yul bridge
- ~120 CU Cadence overhead
- ~35k EVM gas per hash
- ~$0.002 per hash
- Best for: when EVM side needs the same hash (avoids double-hashing)
- The tornado mixer uses this: EVM verifier + Cadence commitment share the same leaf hash
What’s feasible now vs. what isn’t
Production-viable now
- Pedersen all ops (<400 CU) — ship it
- Poseidon native (~45 CU) — ship it
- Groth16 verify ≤100 constraints (~200 CU + ~270k gas) — ship it
- ConfidentialToken transfer (~350 CU + ~327k gas) — ship it
- FROST BLS12-381 (~900 CU + ~185k gas) — ship for custody
Expensive / not yet feasible
- Groth16 for large circuits (>500 constraints): >1M gas — use sparingly
- Merkle depth 20 (~900 CU): feasible but tight
- Recursive SNARKs / STARKs: missing primitives
- zkEVM-style general computation: far beyond current limits
Cross-VM cost breakdown: who pays for what
The privacy wallet thesis
If you squint at these numbers, a pattern emerges: amount-hiding (Pedersen commitments) is very cheap. Identity-hiding (stealth addresses) requires Poseidon hashing — affordable. Arbitrary private computation (zkVM) is not yet practical.
This maps to a specific product: a privacy wallet on Flow that hides amounts and can generate stealth addresses, but doesn’t try to hide sender/recipient identities. That’s PrivateTip’s design.
The benchmarks in this post are what ground that design decision. Without knowing the actual CU and gas costs, you can’t make an informed trade-off between privacy properties and economic feasibility.
Limitations
These are measurements on Flow testnet. Gas prices and CU costs can change with protocol upgrades. The benchmarks reflect circuit sizes specific to my use cases — different circuits will have different costs. Benchmark methodology is documented in openjanus/primitives.