How it works
Better than the state of the art
Bitcoin Cash's 2025/2026 upgrades ( big-integer script numbers, bounded loops, and reusable functions via OP_DEFINE/OP_INVOKE) give Script enough power to run a real Groth16 verifier on chain. Not just to run one, but to run it more efficiently than the state-of-the-art verifiers that exist today: those execute as single multi-megabyte transactions, while BCH's new opcodes let us compress the same work with loops and shared functions and spread it across cheap steps.
Why Groth16?
Zero-knowledge proof systems cluster into families by the cryptography they stand on. Hash-based systems (STARKs, built on FRI) need no trusted setup and stay secure against quantum computers, but their proofs run tens to hundreds of kilobytes. Inner-product arguments (Bulletproofs and Halo, the family behind Monero and Mina) also avoid a trusted setup, but verification cost grows with the size of the statement being proved. Pairing-based systems (Groth16, PLONK, KZG commitments) are the family behind Zcash's original shielded pool, Tornado Cash, and most production rollups, because their proofs are tiny and verification is cheap and constant-time.
Groth16 sits at the extreme of that third family: the smallest proofs (128–192 bytes compressed) and the cheapest verification of any deployed proof system, and the most battle-tested SNARK in production. When the verifier has to be written in Script with no precompiles, and every byte of it is scored, the system with the leanest verifier is the natural first target.
The tradeoffs are real and worth stating plainly. Groth16 needs a per-circuit trusted setup: a ceremony generates the proving and verification keys, and if every participant colluded, false proofs could be forged. In practice multi-party ceremonies make this workable: a single honest participant is enough for the setup to be sound. And its pairing-based cryptography is not post-quantum: a large quantum computer breaks it. That is a reason the hash-based family matters for the long run (why it matters covers what that opens up), not a reason to skip the family that is smallest and most deployed today.
What it takes
Bitcoin Cash has no pairing precompile, so the entire verifier (field-tower arithmetic up through F_p¹², the Miller loop, and the final exponentiation) is written in Script. The recent upgrades are exactly what make that tractable: big-integer numbers for 256-bit modular arithmetic, loops to collapse repeated work, and reusable functions so the field operations aren't re-inlined everywhere.
Two curves, two categories
Entries are split along two axes. The first is the curve: BN254 (32-byte field elements) or BLS12-381 (48-byte elements, a bigger curve that compiles to bigger scripts). Both are targets for the BCH-native work. Byte totals across curves aren't directly comparable, so the leaderboard has a curve filter.
The second axis is the category, and the two are genuinely different artifacts, not two scores for the same thing:
- a single, huge transaction that is correct but exceeds BCH's per-input limits, so it cannot run on chain; or
- a BCH-native verifier that stays under those limits and runs on chain as a chain of inputs.
The reference verifiers fall in the first category: the only Groth16 verifiers in the wild run on BSV, which removed Bitcoin's limits, so they execute as single multi-megabyte transactions (nChain, BLS12-381; sCrypt, BN254). Both are correct, and both exceed BCH's per-input limits.
A chain, not a singleton
BCH keeps a per-input budget: a 10 KB unlocking-bytecode cap, and an op-cost budget that scales with it and tops out at the byte cap, so the two limits are really one wall. The whole pairing computation is larger than one input's budget, so a BCH-native verifier splits the work across many inputs, each a sub-program that stays under the per-input limits.
There are two ways to wire those inputs together, and both are valid entries:
- Many inputs in one transaction. The inputs read each other's data and pass the running state forward, so the whole verification settles in a single transaction.
- A chain of transactions, one input each. Each step carries the running state forward in a CashToken NFT commitment. Because the commitment holds only 128 bytes, the step stores a
hash256of the full state, the spender re-supplies it in the next transaction, and the contract checks it against the stored hash before running its chunk.
Either way the unit that binds is the input, not the transaction, so the count that matters is inputs. Minimising the total footprint is the game.
The cost of splitting
What forces the split is primarily op-cost, and there is no single "the singleton" to compare against: the single-input builds trade bytes against compute along a Pareto frontier (charted in the score history's Pareto view). At one end sits the byte floor (4.7 KB), the fewest bytes any expression of the verifier has hit, but it buys those bytes with so much compute that it alone would need about 649 inputs' worth of op-cost budget, so no change of packaging could ever fit it. At the other end, the op-cost-optimized singleton spends 59 KB to get that down to about 9 inputs' worth.
Splitting has costs of its own: zero-padding each input to buy its op-cost budget, the per-chunk function prologues re-shipped in every input, and the re-provided, re-hashed state across every step. Shrinking that overhead is most of what a good submission does, and it has worked: the smallest fitting verifier now packs the whole computation into 54 KB across 7 inputs, below even the op-cost-optimized singleton, so splitting no longer carries a measurable byte premium over a comparable single input. The byte floor still leads the leaderboard (score is bytes, and it is the smallest expression of the verifier), and the one measure that remains is the ~12× gap against it: the full price of running under today's per-input limits, the floor's bytes-for-compute trade included.
The competition
- Goal: verify a fixed Groth16 statement on the BCH 2026 VM.
- Score = total on-chain bytes across all steps. Lower wins: bytes are what a user pays in fees.
- Correctness is a gate: a submission must accept the valid proof and reject tampered ones, or it isn't listed.
- Secondary metrics (op-cost, step count, BCH-compatibility) are shown but don't set the rank. That includes fitting BCH itself: even a single huge transaction that busts the per-input limits is listed and ranked, it just isn't BCH-native.
Other VMs
The competition proper runs on today's BCH 2026 VM, where an entry either fits standard relay policy or is consensus-valid but non-standard (miners can include it, but default nodes won't relay it). Alongside it, the benchmark grades two other Libauth VMs. The proposed bch-spec VM models the draft TXv5 upgrade (100,000-byte scripts and the op-cost budget that comes with them); entries built for it wear a bch-spec badge on the leaderboard, compete for their own crown, and never set the current-BCH frontier; how could we solve this? has the details. And a VM with the limits disabled grades the single-input builds that bust the per-input caps entirely: they chart the byte floor and the Pareto frontier, but cannot run on chain.
The code
The full stack is three repositories; the benchmark alone is only the grading harness:
- zk-verifier-bench: the harness that runs and grades each verifier on the BCH 2026 VM (this site reads its output).
- groth16_cashscript: the verifier contracts, the CashScript source and the chunk generators.
- cashscript (feat/reusable-functions): the custom compiler fork with reusable functions (
OP_DEFINE/OP_INVOKE) used to compile the contracts.
Submitting
Submissions (GitHub sign-in plus a compiled bytecode artifact graded by the harness) arrive in the next phase. For now this is a live read-out of the benchmark.