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.

May 20, 2026 ·12 min read
#ZK#cryptography#Flow#Cadence#EVM#Groth16#benchmarks
327k gas
End-to-end ConfidentialToken transfer — ~$0.005–0.012 on Flow EVM
vs $5–15 on Ethereum mainnet — 3 orders of magnitude cheaper

Zero published benchmarks for ZK in Cadence. Not for Pedersen commitments. Not for Groth16. Not for Poseidon. I measured them. Here are the numbers.

~16 CU
Pedersen addCommits
Cadence native
~270k gas
Groth16 verify
CrossVM EVM
~327k gas
ConfidentialToken transfer
CrossVM full
<$0.01
Per private transfer
At Flow EVM prices

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

DimensionLayerBudgetNotes
Computation Units (CU)Cadence9,999 CU/txCharged for script + tx execution
EVM gasEVM (cross-VM)No Cadence limitPaid 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

OperationCU CostEVM GasLayerDollar cost (est.)
BN254Field — fieldAdd / fieldMul<10 CUCadence native~$0.000001
PedersenBabyJub — addCommits~16 CUCadence native~$0.000002
PedersenBabyJub — createCommit~18 CUCadence native~$0.000002
BeaconBound commit (VRF)~25 CUCadence native~$0.000003
Poseidon 2-input (native Cadence)~45 CUCadence native~$0.000005
Poseidon 2-input (EVM Yul bridge)~120 CU~35k gasCrossVM~$0.002
PedersenBabyJub — commitScalar~180 CUCadence native~$0.00002
Groth16 verify (EVM verifier)~200 CU~270k gasCrossVM~$0.004–0.010
PedersenBabyJub — verifyCommitment~340 CUCadence native~$0.00004
ConfidentialToken transfer (full)~350 CU~327k gasCrossVM~$0.005–0.012
Mixer deposit (Poseidon Merkle)~280 CU~210k gasCrossVM~$0.003–0.008
Mixer withdraw (nullifier + proof)~320 CU~290k gasCrossVM~$0.004–0.010
FROST BLS12-381 sign (partial)~800 CUCadence native~$0.0001
FROST BLS12-381 verify (EIP-2537)~900 CU~185k gasCrossVM~$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

scale: CU per call (Cadence native, no EVM gas)
addCommits point addition
~16 CU
createCommit point add + scalar mul
~18 CU
commitScalar full scalar mul
~180 CU
verifyCommit scalar mul + equality
~340 CU

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.

  1. 1
    circuit.circom → witness client
    Off-chain (client). The circuit defines what's being proven.
  2. 2
    snarkjs proves ~1-3s CPU
    Off-chain (client). Generates the (π_a, π_b, π_c) + public signals.
  3. 3
    ABI-encode the proof + signals Cadence
    Inside the Cadence transaction. Trivial Cadence-side work.
  4. 4
    EVM.run() → verifier contract ~270k–327k gas
    Cross-VM bridge call. Cadence calls into the EVM verifier deployed on Flow EVM. Most of the gas budget lives here.
  5. 5
    Decode UInt8 → Bool, assert(verified) Cadence
    Back in Cadence. If proof fails, the whole transaction reverts atomically.
  6. 6
    vault.withdraw() + nullifier.insert() ~200–350 CU
    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 Poseidon
EVM Yul bridge Poseidon

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

Feasible — ship it
Not feasible / expensive

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

flowchart TD subgraph CAD ["Cadence side (FLOW)"] C1["~350 CU\n≈ negligible FLOW cost"] end subgraph EVM ["EVM side (ETH gas on Flow EVM)"] E1["~327,000 gas × ~10 Gwei\n≈ $0.002–0.008 USD"] end subgraph CMP ["Comparison"] F1["Flow total: < $0.01 per transfer"] F2["Tornado Cash (Ethereum): $0.50–5.00"] end CAD --> F1 EVM --> F1 F1 -.- F2

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.

Available for contracts