0010. Zero-knowledge modules standardize on Groth16 over BN254
- Status: Accepted
- Date: posed on the privacy track #10, ratified in #19 and scoped to both L1-viable systems in #20; built in #17 and #19 to #23 (2026-06-20 to 2026-06-21); recorded 2026-10-09
Context
#10 listed one open question before any ZK module: which proving system (Groth16 or PLONK/Halo2). It records no comparison; the outcome is recorded in #19 and #20. Lattice's privacy modules (anonymous membership, private voting, shielded transfers) verify proofs on chain, so verification gas matters, and the project rule is to integrate audited circuits and verifiers rather than write its own cryptography. A per-circuit trusted setup is a cost only for whoever runs the ceremony.
Options considered
- Groth16 on BN254 (Circom and snarkjs). The smallest proof and the cheapest on-chain verification, using the BN254 pairing precompiles. Audited circuits and verifiers exist for the target modules, with ceremonies already run upstream.
- PLONK on BN254. A universal setup, at a higher verification cost. It would win if Lattice wrote its own circuits, which the wrap-audited-circuits rule rules out.
- Halo2 or STARK systems. No trusted setup. Not adopted: #20 scopes the generic verifiers to "both L1-viable proving systems" (Groth16 and PLONK). That these cost more to verify on L1 and had no audited EVM verifier to wrap is the maintainer's stated rationale, not written in #10.
Decision
- Lattice's ZK modules use Groth16 over BN254 and wrap audited upstream circuits and verifiers:
Semaphorechecks proofs with the vendored, audited Semaphore v4 verifier, andPrivateVotingbuilds anonymous one-person-one-vote polls on it. - Lattice still ships generic, verifying-key-parameterized verifier facets for both L1-viable systems,
Groth16VerifierandPlonkVerifier, because integrators bring their own circuits. Both mirror the snarkjs verifier templates. - Merkle trees and nullifiers hash with Poseidon (the vendored
PoseidonT3and zk-kit's LeanIMT), because the circuits do; a keccak tree would not verify.
Consequences
- A generic verifier accepts whatever key the caller passes, and whoever generated a key can know its
trapdoor. Production use must pin the key;
HashPinnedGroth16VerifierandPinnedWithdrawVerifierinsrc/examples/privacy/show the two patterns. ShieldedPoolescrows funds, so it must be deployed only with an audited circuit and verifier and an honest trusted setup. The shielded-pool circuit intest/fixtures/shielded/is a test fixture only.- Token-weighted private voting needs a bespoke circuit and is not built.
PoseidonT3fits EIP-170 only on the legacy pipeline, so thevia_irsize gate exempts it.
Confirmation
- The privacy tests verify real proofs from
test/fixtures/: snarkjs proofs for the generic Groth16 and PLONK verifiers and the shielded-pool circuit, and Semaphore v4 proofs forSemaphoreandPrivateVoting. - lib/VENDORED.md records each vendored upstream with its commit and license.
References
Groth16VerifierLib.sol,PlonkVerifierLib.sol,SemaphoreLib.sol,IncrementalMerkleTreeLib.sol- lib/VENDORED.md
- #10 (the open question), #19 (Groth16 ratified), #20 (both L1-viable systems), #238 (verifying-key pinning)