|
| 1 | +# Formal-verification proof targets |
| 2 | + |
| 3 | +A menu of proof targets across Etheorem, the SSZ layer, the consensus containers, |
| 4 | +and the fork framework. Every target points at code already in the tree and says |
| 5 | +whether one of the two prior beacon-chain efforts left a template for it, or whether |
| 6 | +it's new ground. They're grouped into four tiers by area and tagged by how novel and |
| 7 | +how hard I think each one is. |
| 8 | + |
| 9 | +This is a working doc. I'll reshuffle targets as work lands. |
| 10 | + |
| 11 | +## Prior art |
| 12 | + |
| 13 | +Two earlier efforts verified chunks of the beacon chain, both outside Lean 4. What |
| 14 | +they closed tells us what's tractable. What they punted on is where the interesting |
| 15 | +work is. |
| 16 | + |
| 17 | +### ConsenSys eth2.0-dafny (Dafny, Phase 0, archived) |
| 18 | + |
| 19 | +- SSZ round-trip proved for the non-composite shapes: `seDesInvolutive` |
| 20 | + (`deserialise(serialise s) = s`), `serialiseIsInjective`, and the per-type SeDes |
| 21 | + lemmas, bitlist and bitvector included. Both central lemmas carry |
| 22 | + `requires !(s.Container? || s.List? || s.Vector?)`, so containers, lists, and |
| 23 | + vectors sit outside them. Composite round-trip is open ground for everyone. |
| 24 | +- Merkleization: only the chunk-count and length bookkeeping proved. Root-equals-spec |
| 25 | + stays differential-tested. `hash()` is uninterpreted. |
| 26 | +- State transition proved as refinement. Every `process_*` method carries |
| 27 | + `ensures s' == update*(s)`, with slot monotonicity and validator/balance length |
| 28 | + sync. |
| 29 | +- Overflow bounds assumed, not proved: balance sum `< 2^64`, deposit index |
| 30 | + `+1 < 2^64`, registry `<= VALIDATOR_REGISTRY_LIMIT` all ride on `{:axiom}` |
| 31 | + `Assume*Overflow` lemmas ("This proof is assumed", per their docstrings). The |
| 32 | + transition proofs hold modulo those assumptions; discharging them is open. |
| 33 | +- Committee-size bounds (`ActiveValidatorBounds`). |
| 34 | +- Fork-choice store invariants: `aValidStoreIsAChain`, slot-monotone ancestry, |
| 35 | + accepted-block immutability. No liveness. `filter_block_tree` unimplemented. |
| 36 | +- Casper FFG accountable safety (`lemma5`, on the `goal1` branch, never merged to |
| 37 | + master): conflicting finalized and justified checkpoints imply a `1/3` slashable |
| 38 | + set, under a fixed validator set. |
| 39 | +- Never built `is_valid_merkle_branch`. Shuffling stubbed to identity, so no |
| 40 | + permutation proof. BLS unimplemented. |
| 41 | + |
| 42 | +### Runtime Verification (K + Coq) |
| 43 | + |
| 44 | +- Deposit-contract incremental Merkle tree: a full proof that the incremental root |
| 45 | + equals the naive full-tree root, plus KEVM bytecode refinement against an untrusted |
| 46 | + compiler, which surfaced real bugs. This is the canonical incremental-Merkle result. |
| 47 | +- Phase 0 state transition: an executable K model, conformance-tested against the |
| 48 | + official vectors, not proved. |
| 49 | +- Gasper in Coq: accountable safety, plausible liveness, and the slashable bound, with |
| 50 | + dynamic validator sets, on an abstract model rather than the executable spec. |
| 51 | +- The bridge from the K state-transition model up to Gasper: ongoing, incomplete. |
| 52 | +- Shuffling and rewards/penalties: not independently proved. |
| 53 | + |
| 54 | +### Adjacent efforts |
| 55 | + |
| 56 | +- Apalache / TLA+: bounded model-checking of 3SF (three-slot-finality) accountable |
| 57 | + safety (Konnov et al. 2025). It checks the property up to a bound, so it finds |
| 58 | + counterexamples rather than proving the general case, complementary to a deductive |
| 59 | + D2 proof. |
| 60 | +- Nyx Foundation `formal-leanSpec`: a parallel Lean 4 formalization, with its own SSZ |
| 61 | + layer, of the post-quantum leanSpec (the Beam-chain minimal spec). It tracks the |
| 62 | + future PQ spec where Etheorem tracks the production consensus spec, so the two run |
| 63 | + alongside each other. |
| 64 | + |
| 65 | +### Takeaways |
| 66 | + |
| 67 | +1. SSZ round-trip on the non-composite shapes and refinement invariants are |
| 68 | + known-tractable; Dafny closed both, so we can lean on their shape. Their overflow |
| 69 | + bounds were assumed via axiom lemmas, so actually proving those is still open. |
| 70 | +2. `is_valid_merkle_branch` soundness and shuffle-is-a-permutation are open ground. |
| 71 | + Neither effort proved either, so that's where the novelty is. |
| 72 | +3. Our one real edge over both: a verified SHA-256 (`LeanSha256` plus the |
| 73 | + FFI-equivalence axioms) and the Poseidon2 proofs. Where Dafny and K could |
| 74 | + only differential-test merkleization soundness, we can close it against a |
| 75 | + concrete hash. |
| 76 | + |
| 77 | +## Current proof surface |
| 78 | + |
| 79 | +The crypto layer is well proved. Roughly 65 SizzLean theorems (round-trip over |
| 80 | +`BasicSupported`, size bounds, injectivity, bit packing, wide-integer codec), 48 |
| 81 | +Poseidon2, and 23 SHA-256, with zero `sorry`. The axiom footprint is small and named: two |
| 82 | +field-primality axioms (`bn254FrModulus_prime`, `blsFrModulus_prime`, LeanPoseidonProofs) and |
| 83 | +the three-axiom SHA-256 bridge (`sha256Hash_eq_spec`, `sha256Combine_eq_spec`, |
| 84 | +`sha256BatchCombine_eq_spec`, SizzLean), each visible through `#axioms` on any dependent proof. |
| 85 | +The Merkle-branch completeness work is symbolic, so it adds none. |
| 86 | +The narrow `uintN` arms (8 through 64) close by `bv_decide`, so `Lean.ofReduceBool` |
| 87 | +(the compiler axiom) is in that footprint. The wide 128 and 256 arms (PR #18) close by |
| 88 | +`Nat`-digit induction and add no axiom. |
| 89 | + |
| 90 | +The consensus layer is mostly bare. Two proof sites sit on main: `uint64ModOfNatToNatLt`, |
| 91 | +and the three `bv_decide` builder-index round-trip theorems PR #16 landed |
| 92 | +(`EthCLSpecs/Proofs/BuilderIndex.lean`). Alongside them, off main, the in-progress |
| 93 | +Merkle-branch stack (roughly 21 theorems in EthCLLib), around 40 `#guard` and |
| 94 | +`native_decide` property checks, and roughly 60 `forkdef` state functions with no proofs |
| 95 | +attached. A curated function-level candidates list for the Gloas surface lives in |
| 96 | +[`CONSENSUS_PROOF_CANDIDATES.md`](../packages/EthCLSpecs/docs/CONSENSUS_PROOF_CANDIDATES.md); |
| 97 | +it complements this roadmap one altitude down, per-function where this doc is |
| 98 | +per-tier. |
| 99 | + |
| 100 | +A4 is the worked example, already started. The `isValidMerkleBranch` completeness proof |
| 101 | +shows a passing verifier accepts the honest opening over a real Merkle tree, down to the |
| 102 | +pure-Lean `LeanSha256`, symbolically. Its extension to the mix-in-length root that `processDeposit` |
| 103 | +checks is in progress, the prerequisite for **C1**'s deposit arm. |
| 104 | + |
| 105 | +A structural fact shapes the SSZ tier. `SizzLean.Proofs.decode_encode` is |
| 106 | +proved over `SSZType.BasicSupported`, which covers every fixed-size shape. PR #18 closed |
| 107 | +the last integer widths (`uintN 128/256`, `Proofs/UIntWide.lean`), and `Proofs/BitPack.lean` |
| 108 | +folded in the two bit shapes: `packBitsLE_unpackBitsLEAux_inverse` is proved and the |
| 109 | +`bitvector`/`bitlist` arms of the central theorems are closed. Variable-size containers |
| 110 | +and variable-size lists sit outside `Supported` (the decoder returns `.error` for them); |
| 111 | +fixed-element lists are covered and proved (`decode_encode_listFixed`), and `Union` is |
| 112 | +not an `SSZType` constructor at all, so it cannot even be stated yet. |
| 113 | + |
| 114 | +## Targets |
| 115 | + |
| 116 | +Legend. Prior art: ✅ template exists, ⚠ partial, ★ greenfield. Difficulty (a guess): L, M, H. |
| 117 | + |
| 118 | +### Tier A, SSZ and merkleization |
| 119 | + |
| 120 | +- **A4 `is_valid_merkle_branch` completeness** ★ M. An honest inclusion proof is always |
| 121 | + accepted by the verifier, over a real Merkle tree down to the pure-Lean `LeanSha256`. Started (see the |
| 122 | + status paragraph above), symbolic and axiom-clean. The worked Tier A example. |
| 123 | +- **A2 bit-packing round-trip** ★ M. Done for the proved direction: |
| 124 | + `packBitsLE_unpackBitsLEAux_inverse` (PR #10, `Proofs/BitPack.lean`) recovers |
| 125 | + the input bits from their packed bytes, up to false-padding (bits→bytes→bits). |
| 126 | + The byte-side composition (`packBitsLE` after unpacking arbitrary bytes) has |
| 127 | + no standalone identity and `unpackBitsLE` exists only as the `Aux` form. |
| 128 | +- **A1 round-trip on bitvector and bitlist** ⚠ M. Done: PR #10 closed both arms of |
| 129 | + `decode_encode` and folded the bit shapes into `BasicSupported`. |
| 130 | +- **A5 `merkleRootWithCache ≡ hashTreeRoot`** ⚠ M. The cached tree agrees with the spec |
| 131 | + merkleization. |
| 132 | +- **A6 zero-hashes tower** and **A7 chunk and length plumbing** ⚠ L–M. The padding and |
| 133 | + chunking bookkeeping Dafny proved. |
| 134 | +- **A9 generalized-index library** ★ M. Decompose `get_generalized_index` and |
| 135 | + `get_subtree_index` into the `(depth, index)` pair the merkleization theorems take, and |
| 136 | + bridge a gindex opening to that pair. Greenfield: neither prior effort built it. In |
| 137 | + progress. The `(depth, index)` decomposition (`gindex_decompose`, |
| 138 | + `getSubtreeIndex`) and `getPowerOfTwoCeil` are proved and axiom-clean; |
| 139 | + `getGeneralizedIndex` over `SSZType` is modeled but still miscomputes the chunk position |
| 140 | + for packed vectors and lists (it reads the raw element index), so that arm is still |
| 141 | + unfinished. |
| 142 | + This is the enabling dependency for every Merkle-proof consumer past deposits: the |
| 143 | + light-client header branches and the blob and data-column sidecar inclusion proofs all |
| 144 | + address leaves by generalized index rather than raw tree position, so each of them waits |
| 145 | + on A9. Deposits skip it, their index is a plain tree position. |
| 146 | + |
| 147 | +### Tier B, shuffling and committees |
| 148 | + |
| 149 | +Greenfield, high novelty. Neither prior effort proved a real shuffle. Determinism |
| 150 | +needs no target here: the shuffle is a pure function, so same seed, same permutation |
| 151 | +holds definitionally, where the informal efforts had to state it as a property. |
| 152 | + |
| 153 | +- **B1 `computeShuffledPermutation` is a bijection** on `[0, count)`. |
| 154 | + `EthCLSpecs/Fulu/Committees.lean`. ★ M–H. The flagship result. Dafny stubbed the |
| 155 | + shuffle to identity, so no one has proved this. |
| 156 | +- **B2 committee partition**: the union of committees is the active set, pairwise |
| 157 | + disjoint. Follows B1. ★ M. |
| 158 | +- **B3 sampler termination**: the balance-weighted 10M-fuel bound always suffices. ★ M. |
| 159 | + |
| 160 | +### Tier C, state-transition invariants and bounds |
| 161 | + |
| 162 | +The Dafny-proven pattern: templates exist for the statements, but C1's proofs were |
| 163 | +assumed there (`{:axiom}`), not carried out. |
| 164 | + |
| 165 | +- **C1 overflow safety**: `increaseBalance` never wraps, total balance `< 2^64`, deposit |
| 166 | + index `+1 < 2^64`, registry `<= VALIDATOR_REGISTRY_LIMIT`. |
| 167 | + `EthCLSpecs/Fulu/{Balances,Operations}.lean`. ⚠ M. Dafny stated these bounds but |
| 168 | + assumed them via `{:axiom}` lemmas, so the template covers the statements, and the proofs |
| 169 | + themselves are new work. |
| 170 | +- **C2 slot/epoch round-trip and monotonicity**: `computeEpochAtSlot |
| 171 | + (computeStartSlotAtEpoch e) = e` for `e < 2^59` (the raw `UInt64` multiply |
| 172 | + wraps above that, so the identity is false unbounded), plus slot |
| 173 | + monotonicity. `EthCLSpecs/Fulu/Time.lean`. ✅ M; the bound hypothesis is where the |
| 174 | + work hides. |
| 175 | +- **C3 length invariant**: `|validators| = |balances|` preserved across transitions. ✅ M. |
| 176 | +- **C4 committee-size bounds**: an active count in `[32, 2^22]` implies sizes in `(0, MAX]`. |
| 177 | + `EthCLSpecs/Fulu/Committees.lean`. ✅ (Dafny `ActiveValidatorBounds`). M. |
| 178 | +- **C5 `isSlashableAttestationData` = spec** (double-vote or surround), with the |
| 179 | + `strictlySorted` well-formedness. `EthCLSpecs/Fulu/Operations.lean`. ⚠ M. |
| 180 | +- **C6 registry-update legality**: a validator flows active to exited at most once, churn |
| 181 | + `<=` churn_limit. `EthCLSpecs/Fulu/EpochProcessing.lean`. ⚠ H. |
| 182 | + |
| 183 | +### Tier D, deep consensus |
| 184 | + |
| 185 | +Research-scale. |
| 186 | + |
| 187 | +- **D1 fork-choice store invariants**: a valid store is a chain, ancestry slot-monotone, |
| 188 | + accepted blocks immutable. `EthCLSpecs/Fulu/ForkChoice.lean`. |
| 189 | + ✅ (Dafny `aValidStoreIsAChain`). M. The best entry into Tier D. |
| 190 | +- **D2 Casper FFG accountable safety**: conflicting finalized and justified checkpoints |
| 191 | + imply a `1/3` slashable set. Needs an abstract FFG layer over |
| 192 | + `EthCLSpecs/Fulu/EpochProcessing.lean`. |
| 193 | + ✅✅ (Dafny `goal1` and RV Coq). H. Both prior efforts closed it, Dafny under a fixed |
| 194 | + validator set and RV Coq under dynamic sets, so it is hard yet demonstrably reachable. |
| 195 | + Apalache also bounded-model-checked the 3SF form (Konnov et al. 2025), up to a bound. A |
| 196 | + Lean version would be a headline result. One approach: extract an abstract |
| 197 | + checkpoint/justification-link model from the epoch-processing functions, prove the |
| 198 | + two-quorum intersection argument on that, then bridge the abstract model back to |
| 199 | + `EthCLSpecs/Fulu/EpochProcessing.lean` by refinement. |
| 200 | +- **D3 plausible liveness**: with `>= 2/3` honest, new finalization stays possible. ✅ |
| 201 | + (RV Coq, dynamic sets). Very H. Sits behind D2 and shares the abstract FFG model D2 |
| 202 | + builds. |
| 203 | +- **D4 Heze/FOCIL inclusion-list validity**: a satisfied inclusion list forces the required |
| 204 | + transactions into the block. ★ M–H. |
| 205 | + |
| 206 | +## By function |
| 207 | + |
| 208 | +The same targets, resolved to the concrete definitions they're about, in the layout |
| 209 | +[`CONSENSUS_PROOF_CANDIDATES.md`](../packages/EthCLSpecs/docs/CONSENSUS_PROOF_CANDIDATES.md) |
| 210 | +uses, so the two docs compare side by side. Line numbers are as of this commit and will drift. |
| 211 | + |
| 212 | +Several candidate rows over there are the same work as targets here: the churn |
| 213 | +no-underflow candidates (`computeExitEpochAndUpdateChurn`, `reserveChurn`) are C6, the |
| 214 | +fork-choice monotonicity and base-case candidates (`updateCheckpoints`, |
| 215 | +`onAttesterSlashing`, `getForkchoiceStore`, `getHead`/`getWeight` determinism) sit |
| 216 | +inside D1, and `initiateBuilderExit`'s unguarded exit-epoch arithmetic is a |
| 217 | +C1-shaped overflow statement on the Gloas side. |
| 218 | + |
| 219 | +### Tier A |
| 220 | + |
| 221 | +| Target | Function(s) | Location | Status | |
| 222 | +| ------ | ----------- | -------- | ------ | |
| 223 | +| A4 | `isValidMerkleBranch` | `EthCLLib/Spec/SigningRoot.lean:68` (the verifier) | started; the completeness proof is not yet in-tree | |
| 224 | +| A2 | `packBitsLE`, `unpackBitsLEAux` | `SizzLean/Spec/Serialize.lean:223`, `SizzLean/Spec/Deserialize.lean:178`, proof in `SizzLean/Proofs/BitPack.lean` | done | |
| 225 | +| A1 | `decode_encode`, bit arms | `SizzLean/Proofs/Roundtrip.lean`, arms in `SizzLean/Proofs/BitPack.lean` | done | |
| 226 | +| A5 | `hashTreeRoot` vs `Node.merkleRootWithCache` / `Node.merkleRoot`, `hashTreeRootCached` | `SizzLean/Spec/HashTreeRoot.lean:504`, `SizzLean/Cache/MerkleTree/Merkle.lean:52`, `SizzLean/Cache/TreeBacked.lean:306` | open; reuses the perfect-tree kit shared with the A4 stack | |
| 227 | +| A6 | `zeroHashes` (private) | `SizzLean/Cache/MerkleTree/Zero.lean:107` | open | |
| 228 | +| A7 | `padToChunk`, `chunkDepth`, `mixInLength` (private) | `SizzLean/Spec/HashTreeRoot.lean:100,288,302` | open | |
| 229 | +| A9 | `getGeneralizedIndex`, `getSubtreeIndex`, `floorLog2`, `getPowerOfTwoCeil` | not yet in-tree | in progress; decomposition + `getPowerOfTwoCeil` proved, `getGeneralizedIndex` packed-position fix pending | |
| 230 | + |
| 231 | +### Tier B |
| 232 | + |
| 233 | +All in `EthCLSpecs/Fulu/Committees.lean`. |
| 234 | + |
| 235 | +| Target | Function(s) | Location | Status | |
| 236 | +| ------ | ----------- | -------- | ------ | |
| 237 | +| B1 | `computeShuffledPermutation` | `Committees.lean:32` | open | |
| 238 | +| B2 | `getBeaconCommittee`, on top of B1 | `Committees.lean:83` | open | |
| 239 | +| B3 | `computeBalanceWeightedSelection` (the 10M-fuel call is at `:121`) | `Committees.lean:114` | open | |
| 240 | + |
| 241 | +### Tier C |
| 242 | + |
| 243 | +| Target | Function(s) | Location | Status | |
| 244 | +| ------ | ----------- | -------- | ------ | |
| 245 | +| C1 | `increaseBalance`, `processDeposit`, `processRegistryUpdates` | `Fulu/Balances.lean:29`, `Fulu/Operations.lean:214`, `Fulu/EpochProcessing.lean:165` | open; its deposit-arm Merkle prerequisite (the mix-in-length extension) is in progress, the overflow bounds are not started | |
| 246 | +| C2 | `computeEpochAtSlot`, `computeStartSlotAtEpoch` | `Fulu/Time.lean:25,28` | open | |
| 247 | +| C3 | `processDeposit` (the append site), then transition-wide preservation | `Fulu/Operations.lean:214` | open; rests on the in-progress mix-in extension, not started | |
| 248 | +| C4 | `getBeaconCommittee` | `Fulu/Committees.lean:83` | open | |
| 249 | +| C5 | `isSlashableAttestationData`, `strictlySorted` | `Fulu/Operations.lean:38,30` | open | |
| 250 | +| C6 | `processRegistryUpdates`, `initiateValidatorExit`, `computeExitEpochAndUpdateChurn` | `Fulu/EpochProcessing.lean:165`, `Fulu/RegistryUpdates.lean:112,78` | open | |
| 251 | + |
| 252 | +### Tier D |
| 253 | + |
| 254 | +| Target | Function(s) | Location | Status | |
| 255 | +| ------ | ----------- | -------- | ------ | |
| 256 | +| D1 | `getAncestor`, `getHead`, `onBlock` (Gloas twins at `Gloas/ForkChoice.lean:156,446,594`) | `Fulu/ForkChoice.lean:105,200,387` | open | |
| 257 | +| D2, D3 | `processJustificationAndFinalization`, behind an abstract FFG layer | `Fulu/EpochProcessing.lean:69` | open | |
| 258 | +| D4 | `processInclusionList`, `isPayloadInclusionListSatisfied` | `Heze/ForkChoice.lean` (PR #6, in review) | open; workable now on the in-flight Heze layer, against the current alpha | |
| 259 | + |
| 260 | +## Dependencies and entry points |
| 261 | + |
| 262 | +No strict order here. Some targets rest on others, some are gentler first steps. |
| 263 | + |
| 264 | +- A4 shows the merkleization-completeness shape end to end (status above). A1 and A2 have |
| 265 | + since landed on main, which leaves A5 as the natural next SSZ extension. |
| 266 | +- A4 is load-bearing beyond Tier A. `processDeposit` accepts a deposit only behind the |
| 267 | + `isValidMerkleBranch` assert (`Fulu/Operations.lean:216`), |
| 268 | + so C1's deposit arm and C3 lean on the completeness theorem *plus a mix-in-length |
| 269 | + extension*: the deposit root mixes in the list length, so it isn't a plain perfect tree |
| 270 | + and the base theorem doesn't cover it. That extension is in progress, stated general over |
| 271 | + depth so the sidecar inclusion proofs reuse it, a blob or data-column commitment opens a |
| 272 | + `List` element that crosses the same length-mix-in node. A5 reuses the same perfect-tree |
| 273 | + kit. The `DataColumnSidecar.kzgCommitmentsInclusionProof` field (`Fulu/Blocks.lean:63`) |
| 274 | + is the other branch-proof consumer, not modeled yet. |
| 275 | +- Tier C holds the quick wins. C1 and C2 are small, self-contained, and follow the Dafny |
| 276 | + template closely. |
| 277 | +- B1 is the flagship shuffle result. B2 and B3 cluster around it, and B2 follows B1 |
| 278 | + directly. |
| 279 | +- D1 is the doorway into deep consensus and ties into the fork-choice work. D2 is the |
| 280 | + long-arc goal of accountable safety, and D3 shares the abstract FFG model D2 builds. D4 |
| 281 | + follows the FOCIL work. |
| 282 | + |
| 283 | +Both prior efforts closed accountable safety in their own settings, so I'm keeping D2 and |
| 284 | +D3 on the list as reachable, although it's arguably deeper research. |
0 commit comments