TLDR
We have a working proof, verified on Ethereum mainnet, of Lido’s exact registered validator set and its consensus-layer balance at a single finalized slot: every key across all four staking modules, counted exactly once, joined to those balances under one beacon anchor.
This is the property LIP-23 set aside: “ZK-proved values can indicate only the upper limit at this stage.” No prior attempt tried to close it. This proof does, and verifies on mainnet for 350,140 gas.
We know the ZK-oracle effort is paused, and this proposal is written to fit inside the pause: no Lido engineering time, no EIP-4788 anchoring, and the accounting rewrite the pause pointed to shipped in late July. Approving it doesn’t restart the paused effort; it buys the council measured answers to the pause’s own open questions. Details in “The pause” below.
Verification transaction: Ethereum Transaction Hash: 0x9d41453bd1... | Etherscan
Verifier contract: 0x0D661adc1616652ef20E027e4d448E5A7E546d28
Code: GitHub - willow-network/lido-validator-set-proof: Zero-knowledge proof of Lido's exact registered validator set and CL balance, verified on Ethereum mainnet · GitHub
We’re asking LEGO for $30,000, partially retroactive: $15K covering work already delivered and inspectable on mainnet, $15K on completion of Milestone 3 below. Milestone 4, a standing fourteen-day shadow run, we deliver at our own cost. The prior ZK-oracle grants were $50K each; we’ve sized this one to the pause.
Context
Lido’s accounting oracle reports consensus-layer balance from an off-chain index, ratified by a 5-of-9 committee; secondOpinionOracle() remains address(0). Four grant-funded teams have worked on ZK oracles for the balance sum, and every attempt derives the validator set the same way: filter the consensus layer by withdrawal credentials, then prove sums over the filtered set. LIP-23 states the limit: anyone can point a validator at Lido’s credentials, so credential-matching yields an upper bound. The protocol permits 0.5% of disagreement against that uncertainty, roughly 48,700 ETH of slack; an exact set removes the reason for it. What hasn’t been done is the other direction: enumerate the set Lido actually registered, from the staking modules’ own storage, closed by Lido’s own counters, and join it to the consensus layer. That’s the direction a per-module reallocation attack requires, and it’s what this proof does.
On July 25 the oracle underreported, and the August 6 post-mortem by @Izzy attributes it to “the missed inclusion of a single validator with a pending deposit,” with the root cause not yet established. The core issue was an off-chain view of the data that disagreed with the actual chain state. That’s exactly the kind of thing this construction rules out: every quantity is proven against one finalized anchor (a beacon state and the execution-layer block it commits to), so there’s no second source that can drift out of sync. And because the count is exact, a second opinion built on it compares at full resolution, not at 0.5% tolerance.
What the proof establishes
At beacon slot 14,952,064, one Groth16 proof establishes:
The set is complete. Every key registered through all four staking modules (Curated, SimpleDVT, Community Staking, Curated v2), enumerated from the modules’ own storage and closed against Lido’s on-chain counters: per-operator counters sum to each module’s total, module totals sum to depositedValidators, and the proven key list has exactly that length. Omission has no valid form: remove one key and the sums stop closing.
The balances are complete. The full beacon registry, 2,332,970 validators, scanned and summed, with the registry’s length mixed into the anchored root so a truncated scan matches nothing.
The two halves are joined. Each registered key maps to at most one validator and lands in exactly one bucket: matched on the consensus layer, stranded (credential is not Lido’s), pending (proven against the beacon state’s own pending-deposits queue, not asserted), or unaccounted. The buckets sum to the registered count, and unaccounted is a published output; a prover can’t silently zero it. The comparison runs the other way too: the credential scan’s totals are statement fields, so a validator pointing at Lido’s credential that no registered key explains (a mistaken registration, a donation) shows up as a proven rogue count and balance, still countable toward TVL, instead of a discrepancy to chase by hand.
Both halves bind to a single beacon state root, so “whose validators” and “what they hold” can’t drift apart. There’s no new committee anywhere in this, and anyone can produce a report.
At the pinned slot: m = 488,374 = 467,856 matched + 0 stranded + 20,518 pending + 0 unaccounted. Proven consensus-layer balance: 8,793,794 ETH, with zero rogue validators (credential-matched but never registered).
Cost and latency. These are first-run numbers with no optimization pass behind them: a ceiling, not a floor. Producing the proof took about two hours on rented GPUs and about $30 of compute, measured. A daily report is about $15 at a similar wall clock: balances change every slot, so the scan, the join, and the final wrap recur each day, while the sorted index that closes the set chains forward at about 400 new validators a day. Each line is measured cycles priced at the run’s measured cost per cycle, the proving parallelizes across as many GPUs as you rent, and the fourteen-day shadow run publishes where the numbers land.
The pause
In April, @TheDZhon paused the ZK-oracle effort, for reasons we think were right. We’re not asking the council to reverse that call, and since the pause covers the initiative broadly, we’re not claiming an exemption.
Engineering focus. The engineers who’d integrate a ZK oracle are committed to the 0x02 migration; nothing here needs them. Every acceptance test below runs without a Lido contributor, and we run and pay for the proving. Integration waits for the pause to lift, on your schedule.
Fork churn. Every fork since Altair has moved beacon fields and broken circuits, and each break costs re-development, re-audit, and re-testing. Three things make that cheaper here: beacon field positions come from the consensus spec at compile time, so a fork that moves a field fails the build and names it; builds are reproducible in Docker, so what needs review after a fork is a source diff; and the registry half reads execution-layer storage, which consensus forks don’t touch. We’d rather measure this than argue it: we’ll ship each fork’s updated prover and re-pinned key before that fork reaches mainnet, at no extra cost, starting with Glamsterdam. The post-quantum changes further out would be a real rewrite for any beacon-state circuit, ours included.
Anchoring under ePBS. We don’t use EIP-4788. The anchor is a beacon state root checked against the sync committee, so payload-less slots under EIP-7732 break nothing. Today that check sits with the consumer; Milestone 2 moves it inside the proof.
The pause post said the effort would be revisited once its conditions are met or drastically improve. One has moved: the post said the ZK oracle “cannot be finalized until this new architecture stabilizes,” and that architecture is now live; the artifact above is built against the post-SRv3 layout. Whether it has stabilized is Lido’s call, and nothing here waits on it.
Team
Willow Labs. Paul DeLucia (Founder, CEO & CTO) built the protocol; Eric Gleeson (Co-Founder, COO) runs partnerships and operations. Pavel Burylichev (Founding Engineer) supports development. Samuel Westrich, CTO of Dash Core Group, advises.
Willow is a verifiable indexing protocol: the product is a cryptographic proof that a query result is complete, not merely present. We have completeness proofs verified on Ethereum mainnet today: a window of blocks proven indexed completely and correctly, verified on-chain for about $0.20 in gas. Lido’s accounting is the hardest instance of the general problem, which is why we went straight at it.
Prior open-source work includes upstream contributions to the Binius proof system.
Roadmap and deliverables
Milestone 1: Reproducible report. Documentation and a published toolchain digest pinning the verifying key, on the public prover and verifier repo (the guest programs run on SP1), sufficient to reproduce a report independently. Acceptance: anyone outside our team produces a byte-identical statement, and the same verifying key, without our involvement.
Milestone 2: The contract verifies its own anchor. Today a consumer must independently confirm the beacon state root is canonical Ethereum. We move the sync-committee signature check inside the proof, so the verifier establishes its own anchor, still with no EIP-4788 dependency. Acceptance: a report with a fabricated but internally consistent beacon state root, or a mislabeled slot, is rejected on-chain.
Milestone 3: Per-module totals. Module attribution is already proven; this exposes per-module balance and count as statement fields, which is what Staking Router v3’s per-module reporting needs, and where a quorum moving stake between modules would show up. Acceptance: per-module figures match the oracle’s own report for the same reference slot, or we publish a per-validator reconciliation accounting for the difference.
Milestone 4: A standing second opinion, ready for the slot. An interface compatible with the sanity checker’s ZK slot, plus recurring proving that covers each day’s registry changes instead of rebuilding from scratch; each report chains from the last accepted root, so a prover can never supply its own prior. We operate it as a shadow run at our own cost, published alongside the committee’s reports, so when the pause lifts the integration decision starts from running code, not a proposal. Acceptance: fourteen consecutive daily reports verified on-chain, measured cost and latency published, and a sanity-checker testnet deployment we stand up ourselves accepting our report path end to end.
Milestone 1 within two weeks of approval. Milestone 2 within six weeks; SP1 carries BLS12-381 precompiles and an existing light-client implementation of exactly this workload. Milestone 3 two weeks after that. Milestone 4 concludes with the fourteen-day run. The artifact above went from design to verified on mainnet in days; these dates have room in them.
Scope and limits
The contract doesn’t yet verify its own anchor; that’s Milestone 2. It checks the proof, the pins, and the conservation identity, but takes the pair (beaconStateRoot, slot) as given. A consumer today must match both against an authenticated anchor before treating a report as a statement about Ethereum.
“Registered” means every key ever deposited through the staking modules. Exited and withdrawn keys stay in the set, so the count exceeds the active validator count. That’s deliberate: filtering to “active” means trusting a definition or proving a filter, and a filter is somewhere to hide a validator. The balance total is unaffected; withdrawn validators hold nothing.
A deployment pins Lido’s storage layout, and fails open if that layout changes. If Lido re-lays-out storage (as it did with Staking Router v3 on July 24), the old slots can read zero and an empty-set report would verify against the stale deployment. A re-layout must retire the deployment, and a consumer should cross-check the proven count against an independently read depositedValidators. This artifact post-dates July 24.
V3 stVaults are outside the enumerated set. They keep validators in unindexed mappings, about 0.07% of the protocol today.
No external audit yet. The adversarial test suite ships with the repository, Milestone 1 makes the build independently reproducible, and the construction is open to review from day one.
ePBS can’t be tested against mainnet yet. Glamsterdam is on public testnets now, with mainnet later this year; the fork is the first test of the maintenance commitment above.
Funding
We’re asking for $30,000: $15,000 on approval, covering work already delivered and live on mainnet, and $15,000 on completion of Milestone 3. Milestone 4, the shadow run, we deliver regardless at our own cost, so nothing LEGO pays for touches the integration question.
The retroactive half covers only work anyone can inspect before a vote. Whatever milestone this reaches, LEGO has paid for delivered, verified work and nothing else. Proving costs, infrastructure, the shadow run, and fork maintenance are ours. Exact-set completeness is Willow’s core product, not a side project; Lido gets a maintainer whose company depends on this working.
This is a starting point, not a fixed structure. Scope, staging, and milestone boundaries are open to shaping with the contributors closest to the oracle. Everything ships open-source under a permissive license; anyone should be able to compete on the implementation.
Next
The secondOpinionOracle slot has been open since LIP-23 and is still set to zero. Whoever fills it, and when, is Lido’s call. Our job is to make the exact-set standard available to whoever does, ourselves included.
Questions on the construction (the counter closure, the pending-queue binding, the four-bucket identity) are very welcome, here or in a call. The adversarial test suite is in the repository, and if you find the case we missed, we want to hear it.