A ZK light client is not a bridge feature. It is a reimplementation of the source chain's consensus rules inside an arithmetic circuit — and every hard problem we hit building the NEAR→Ethereum client in plonky2 was a consensus problem wearing a cryptography costume. This is the engineering account: the finality scheme, the circuits, and what we would build differently today.
- Verifying NEAR on Ethereum means re-executing NEAR's consensus checks — ed25519 approvals from ≥2/3 of staked block producers, epoch rotation via
next_bp_hash— inside a proof, not trusting a relayer. - Our scheme proves finality of
Block_i+2from a window of five consecutive blocks (Block_i…Block_i+4): three consecutive heights above the target give BFT finality under NEAR's rules. - ed25519 is the cost center: curve arithmetic over a foreign field inside the circuit. plonky2 recursion aggregates the signature checks — a full finality proof proves in ≈8 minutes on one 32-core machine and verifies on Ethereum for ≈310k gas.
- Work done as a member of Wormhole's decentralized contributor set, under a Wormhole Foundation contributor grant. Code public.
- Today we would evaluate zkVM provers before hand-building circuits — the tradeoff moved between 2023 and 2026.
Wormhole Foundation — contributor grant announcement (Feb 2024)
NEAR light client specification (nomicon.io)
Original write-up on Medium (superseded by this post)
Why NEAR→Ethereum was the expensive direction
Bridging NEAR and Ethereum has an asymmetry. Ethereum→NEAR is comparatively cheap: NEAR contracts can afford to verify Ethereum's consensus artifacts. The reverse direction is where designs historically bent. NEAR block producers sign with ed25519, and Ethereum has no ed25519 precompile — the EIP that proposed one (EIP-665) was rejected years ago. Verifying a single ed25519 signature in EVM bytecode costs on the order of hundreds of thousands of gas; verifying approvals from a full validator set, every epoch, is not a product.
The first-generation answer was optimistic: post NEAR headers to Ethereum unverified, let watchdogs challenge invalid ones, and make users wait out a challenge window measured in hours. That works, but the trust model is “someone is watching” and the latency is the window. The ZK answer replaces the window with a proof: the contract on Ethereum verifies one succinct proof that the consensus checks were executed correctly — for any number of signatures.
The scheme: five blocks, one finality proof
The client proves two things about a chosen block: computational integrity of the block itself, and BFT finality. NEAR's finality rule (Doomslug under the hood) lets a block be treated as final once it is followed by two blocks at consecutive heights. Our scheme takes a window of five consecutive blocks, Block_i through Block_i+4, and proves finality of Block_i+2 when the heights of Block_i+2, Block_i+3, and Block_i+4 are consecutive — the two extra blocks give the circuit the material to check the linkage instead of assuming it.
Epoch rotation is the part naive designs miss. The validator set that signs epoch i was committed in epoch i−1 via next_bp_hash, and the circuit carries the final block hash of epoch i−2 as its anchor. By proving that next_bp_hash — derived from the validator list — is part of the current block's data, which in turn is part of the block hash being signed, the chain of custody from “validators we already trust” to “block we are being asked to accept” closes with no gap a relayer could exploit. Get this wrong and a light client will happily accept a block signed by a validator set nobody elected.
The circuit is NEAR's consensus rules, restated
Everything the NEAR protocol specifies in prose, the circuit must specify in constraints: parse the header layout exactly, hash it exactly as NEAR core does, check that approvals carry ≥2/3 of the epoch's stake, and recompute the Merkle commitments for any state slot being proven. For state proofs we reused code from NEAR core to construct the trees — the fastest way to be bug-compatible with the chain you are verifying is to be code-compatible with it.
The cost center is ed25519. Curve25519 arithmetic lives over the field 2255−19; plonky2's native field (Goldilocks) is 64-bit. Every group operation is foreign-field arithmetic, emulated limb by limb inside the circuit. There is no way to make one signature cheap — the way out is to make many signatures aggregate.
plonky2 and the recursion strategy
We chose plonky2 for two properties that mattered in production: no trusted setup (FRI-based), and recursion fast enough to use as an engineering primitive rather than a stunt. The signature workload parallelizes naturally — prove batches of approval verifications independently, then fold the batch proofs into one block proof, then chain block proofs across the five-block window. Recursion is what turns “hundreds of expensive checks” into “one proof whose size does not care.”
The last hop is the EVM. Verifying a FRI-based proof directly on Ethereum is calldata- and hash-heavy, so the standard production pattern is to wrap the final plonky2 proof in a pairing-based proof (BN254) that the EVM verifies in a few hundred thousand gas. The wrapping stage and its exact on-chain cost belong in the measured table below.
Measured results
The numbers a light-client write-up owes its readers, from our proving runs — hardware stated:
| Metric | Value | Context |
|---|---|---|
| Proving time, one block proof | ≈150 s | 32-core AMD EPYC, 256 GB; signature batches proved in parallel, then folded |
| Proving time, five-block finality window | ≈8 min | end-to-end on the same machine; chained recursion across the window |
| Signature circuit size | ≈218 rows | per ed25519 verification; foreign-field arithmetic dominates. Recursion nodes: 212–213 |
| Final proof size on the wire | ≈130 KB → 192 B | plonky2 proof, wrapped to a BN254 Groth16 proof plus public inputs for the EVM |
| Ethereum verification gas | ≈310k gas | pairing check + calldata, per finality proof |
Against the optimistic-era baseline it replaces: a challenge window of hours and a watchdog assumption, versus ≈10 minutes of proof latency and no window at all. The comparison worth publishing is cost per finalized header at equal trust assumptions — that is the number protocol teams actually budget against, and we work through it in a working cost model for ZK light clients.
What we'd do differently in 2026
The design held up. The toolchain moved. Three things change on a rebuild. First, we would benchmark zkVM provers — writing NEAR's consensus checks as a Rust program and letting the VM prove it — against hand-built circuits before committing; in 2023 the hand-built circuit won on proving cost by a wide margin, and the margin has been shrinking every year since. Second, plonky2's successors (smaller fields, faster hashing) reprice the recursion tree. Third, the precompile landscape is still against ed25519 — RIP-7212 standardized secp256r1, not curve25519 — so the wrap-for-EVM stage stays, and engineering effort put into cheaper wrapping pays for itself across every proof submitted.
What does not change: the framing. If a team sells you a “light client” without talking about epoch rotation, stake thresholds, and the exact finality rule of the source chain, they are selling you a signature checker. The consensus is the work — the ZK part is how you afford it. That framing is also why this work sits in our applied cryptography & ZK practice next to consensus engineering, and how it came to run under a Wormhole Foundation contributor grant, as part of Wormhole's decentralized contributor set.
If you're working on something similar, we'd like to hear about it. Talk to a founder — 30 minutes.