https://github.com/imdea-software/leanfraudproofs
L2 Optimistic rollups fraud proofs mechanization
https://github.com/imdea-software/leanfraudproofs
Last synced: 9 months ago
JSON representation
L2 Optimistic rollups fraud proofs mechanization
- Host: GitHub
- URL: https://github.com/imdea-software/leanfraudproofs
- Owner: imdea-software
- License: mit
- Created: 2025-08-23T11:09:07.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2025-08-23T12:31:59.000Z (10 months ago)
- Last Synced: 2025-08-23T18:49:43.288Z (10 months ago)
- Language: JavaScript
- Size: 11.7 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.org
- License: LICENSE
Awesome Lists containing this project
README
* [[https://imdea-software.github.io/LeanFraudProofs/][Clickable Documentation]]
* CCS'25
Main artifact proofs are in [[./FraudProof/L2.lean][L2]] .
Zenode badge:
[[https://doi.org/10.5281/zenodo.16993258][https://zenodo.org/badge/1043206342.svg]]
** Main proof is ~honest_chooser_valid~ proving our main validity claim.
#+begin_src lean4
theorem honest_chooser_valid {α ℍ}
[BEq ℍ][LawfulBEq ℍ][DecidableEq α]
[o : Hash α ℍ][m : HashMagma ℍ][InjectiveHash α ℍ][InjectiveMagma ℍ]
(val_fun : α -> Bool)
(p1 : P1_Actions α ℍ)
: linear_l2_protocol val_fun p1 ( fun (t, mt) => honest_chooser val_fun t mt)
↔ local_valid p1.da val_fun
#+end_src
** Players
*** Player One, proposing DAs, is defined as a new batch with all possible moves in all possible games.
#+begin_src lean4
structure P1_Actions (α ℍ : Type) : Type
where
da : BTree α × ℍ
dac_str : ABTree (Option α) (Option (ℍ × ℍ))
gen_elem_str : {n : Nat} -> ISkeleton n -> (Sequence n (Option (ℍ × ℍ)) × Option α)
#+end_src
*** Player Two, the one (possibly) challenging, is defined as
#+begin_src lean4
inductive P2_Actions (α ℍ : Type) : Type
where
| DAC (str : ABTree Unit ((ℍ × ℍ × ℍ) -> Option ChooserMoves))
| Invalid {n : Nat} (p : α) (seq : ISkeleton n) (str : Sequence n ((ℍ × ℍ × ℍ) -> Option ChooserSmp))
| Duplicate (n m : Nat)
-- There are two paths
(path_p : ISkeleton n) (path_q : ISkeleton m)
-- Strategies to force proposer to show elements.
(str_p : Sequence n ((ℍ × ℍ × ℍ) -> Option ChooserSmp))
(str_q : Sequence m ((ℍ × ℍ × ℍ) -> Option ChooserSmp))
| Ok
#+end_src
** Validity of Batches.
Notion of (local) batch validity is defined exactly as the article says:
#+begin_src lean4
def local_valid {α ℍ : Type} [DecidableEq α][Hash α ℍ][HashMagma ℍ]
(da : BTree α × ℍ)(val_fun : α -> Bool) : Prop
-- Merkle Tree is correct
:= da.fst.hash_BTree = da.snd
-- All elements are |val_fun| valid
∧ (da.fst.fold val_fun and)
-- There are no duplicated elements.
∧ List.Nodup da.fst.toList
#+end_src
** Winning Lemmas
To see how we prove that honest players win games see file [[./FraudProof/Games/ElementInTree.lean][ElementInTree]] .
+ Lemma ~elem_back_rev_honest_two~ stating that knowing the data, honest
choosers lead proposers either to quit or to prove that an element is in a
tree.
+ We define the notion of winning conditions in file [[file:./FraudProof/Game/GenericTree.lean][GenericTree]] and prove that
proposers win ~winning_proposer_wins~.
** Adding History.
To complete the CSS'25 article proofs, we needed to check that there are no
duplicates elements across history.
Local validity is local to each block, but history non-duplicity is contextual
and depends on the previous elements added. It is effectful in a way.
We defined a new protocol in [[./FraudProof/History_L2.lean][History L2]] on top of the previous one ( [[./FraudProof/L2.lean][L2]] ).
The new protocol has a history of added blocks and checks that all blocks are
local valid plus they do not duplicate elements.
#+begin_src lean4 :noeval
def historical_valid
{α ℍ : Type} [DecidableEq α][Hash α ℍ][HashMagma ℍ]
-- History of commited blocks and their hashes.
(Hist : List (BTree α × ℍ))
-- Validity Function
(val_fun : α -> Bool)
: Prop
:=
-- Each epoch is `local_valid`
(∀ e ∈ Hist, local_valid e val_fun)
∧ -- Plus there are no duplicated elements. This one subsumes nodup in local_valid.
(List.Nodup (Hist.map (BTree.toList $ ·.fst)).flatten)
#+end_src
We add a new move for player two, now the player two (the chooser) can detect
duplicate elements across history and build witnesses and fraudproofs
accordingly.
Player one (the proposer), now has to provide strategies defending their claim
for all elements across history too. Since we do not have IO.
Same as before, our main theorem is that the protocol equipped with an honest
player only accepts valid blocks.
#+begin_src lean4 :noeval
theorem HistoryProtocol {α ℍ}
[BEq ℍ][LawfulBEq ℍ][DecidableEq α]
[o : Hash α ℍ][m : HashMagma ℍ][InjectiveHash α ℍ][InjectiveMagma ℍ]
(val_fun : α -> Bool)
(p1 : P1_History_Actions α ℍ)
--
(hist : List (BTree α × ℍ))
: (linear_l2_historical_protocol val_fun hist p1
(fun h (t, mt) => historical_honest_algorith val_fun h t mt)
∧
historical_valid hist val_fun)
↔ historical_valid (hist ++ [p1.local_str.da]) val_fun
#+end_src
* Compilation
** Lake Compilation (Lean native)
Get lean first, https://leanprover-community.github.io/get_started.html .
To build the whole project should suffice with a simple lake build command
#+begin_src bash
lake update
lake build
#+end_src
Follow Makefile:
#+begin_src bash
make help
#+end_src
** Nix Compilation
Get Nix, follow make file.
#+begin_src bash
make help
#+end_src
** Compile Documentation
See Makefile help.
** Added Pages
* Project Structure:
** Data Structures
Here I programmed useful data structures:
+ BTree :: Binary Trees
+ Hash :: Hash stuff, classes defining hash function from one type to another.
Collision Free notions.
+ MTree :: Merkle Trees. (BTree + Hashes).
+ Sequences :: Fin lists (Sequences,Arrays?,n-Tuples), in Lean
#+begin_src lean4 :noeval
def Sequence (a : Type) (n : Nat) := { t : List a // t.lenght = n }
#+end_src
+ SeqBTree :: Sequences to BTrees. Balance Complete Trees, so we can show log
search of fraud-proofs.
+ TreeAccess :: Main star when formalizing L2 (Arranger) games. Main lesson
here, we need to characterize the algorithm in a complete way. We need errors
and enough information to build fraudproofs. This reminds me a lot of 'Z'
charts characterizing good and bad behaviours.
- Find invalid elements returning extra metadata when an invalid element is
found and proof-carrying data when no element is found. We need extra data
to build strategies and to eventually prove that correct agents always win.
- Find duplicated elements. Find_inv ; Find_inv when invalid is membership?
Several proofs are needed here.
** Extras
Don't really remember here, files here may be obsolete.
** Games
+ ElemInTree :: Arbitration games proving element membership in merkle trees.
+ FMBC :: Fmbc stuff. I did it to guide reviewrs, idk.
+ FromBtoMTree :: DAC game, merkle tree is correctly built arbitration game.
+ GameDef :: definitions of games.
+ GenericTree :: Generic Games played on top of Binary Trees arenas.
+ LogProof :: logarithmic games are logarithmic.
+ PathToTreeGames :: similar to ElemInTree?
+ ReverseLinearGames :: linear games can be played bottom-up ant top-down.
+ Simultaneous :: Arbitration Games where both players provide their strategies
before playing. It is not their complete strategies but a commitment element.
The Merkle Tree root of their strategies.
** Proofs
Again, not so sure.
** L2.Lean
Root file implementing Arranger games and strategies.
Using everything we defined, we prove that honest players always win.
** HistoryL2.Lean
Root file implementing Arrangers with History.
Using everything we defined, we prove that honest players always win and
they accept honest claims.
* FraudProof
Here we aim to implement and formalize FraudProof ideas placed in L2 Setchain.
** Implementation List
*** DONE Basic Data Structures
**** DONE [[file:FraudProof/DataStructures/BTree.lean][Binary Tree]]
Small binary tree implementation plus some important definitions:
Sibilings path from an element to /the root/.
#+begin_src lean :noeval
abbrev TreePath (α : Type ):= List (Sum (BTree α) (BTree α))
#+end_src
When an element is in a tree, and moreover, we can compute its proof. In this
case, a path from the element (value) to the root.
#+begin_src lean :noeval
def valueIn [BEq α] (v : α) ( bt : BTree α ) : Bool
def valueInProof [BEq α](v : α) (bt : BTree α) : Option ( TreePath α )
#+end_src
**** DONE [[file:FraudProof/DataStructures/Value.lean][Value]]
Opaque type representing values in our data structure.
Requisites: an equilvalence relation.
**** DONE [[file:FraudProof/DataStructures/Hash.lean][ Hash]]
Hash are strings (maybe we can change that, there is a hash notion already in Lean)
It defines a hash function ~H : Value -> Hash~ and an operator ~\oplus: Hash -> Hash -> Hash~.
Moreover, we assume perfect hashing (no collisions).
#+begin_src lean :noeval
axiom hash_prop (v1 v2 : Value) : v1 ≠ v2 → H v1 ≠ H v2
#+end_src
**** DONE Merkle Tree simple data structure [[file:FraudProof/MTree.lean][MTree Implementation]]
Merkle Trees are nothing but the hash of the root of the markle tree they
represent.
Here we define important notions as hash paths. For example, a Hash is in a
Merkle tree, if we provide the /evidence/, a list of hashes and positions,
leading to the root.
#+begin_src lean :noeval
def nodeIn (h : Hash) (path : Path) (t : MTree) : Bool
:= match t with
| MTree.node hT => listPathHashes h path == hT
#+end_src
*** DONE Games data structures
**** DONE Games
There is only one game: membership game.
Given a value (or a hash), it is an element in a given Merkle Tree.
***** DONE General Game Definitions -- [[file:FraudProof/Games/GameDef.lean][GameDefs]]
Who wins. Maybe as we define more adv games we may have more stuff here?
***** DONE One Step Game -- [[file:FraudProof/Games/OneStepGame.lean][OneStepGame]]
One step game are games over a path of length 1. That is we have
two hashes ~hb ht : Hash~ and proposers win if they can produce a /valid/
sibling hash ~hb' : Hash~, that is ~opHash hb hb' = ht~.
Other games eventually lead to this game.
***** DONE [[file:FraudProof/Games/LinearGame.lean][Linear Game]] -- [[file:FraudProof/LinearGame.lean][LinearGame]]
Linear games consist on one player proposing hashes along the one, one at a
time, and the chooser deciding if a hash is incorrect, challenging that claim.
The chooser either chooses between 'this hash is incorrect' or 'continue with
the next'. If the chooser challenges a correct hash, the chooser loses.
It can be player from the root to the leaf or the other way around.
***** DONE Log Game -- [[file:FraudProof/Games/LogGame.lean][LogGame]]
Similar to the Linear one but instead of going through the list offering one by
one, the Proposer produces the hash in the middle of the path between the leaf
and the root.
The chooser then chooses on which half the game should continue to.
It is called /Log Game/ since it halves the path at every instance. Leading to a one step game.
**** DONE Players -- [[file:FraudProof/Players.lean][Players]]
Here we define two players.
***** DONE Proposer
Proposers propose hashes along the way.
Since eventually they need to provide siblings too, proposer strategies are compose of two
arrays of hashes:
#+begin_src lean :noeval
structure HC (n : Nat) where
-- Hashes along the way
pathNode : Fin ( n + 1 ) -> Hash
-- Path elem knows how to hash.
pathSib : Fin n -> PathElem
#+end_src
Proposers are indexed on the length of the path.
Plus some operations over proposers.
****** IDEA Maybe. Min Proposer
I guess we can implement a /minimal proposer/ using just an array of hashes as
long as the path itself.
It is just taking ~pathSib~ and computing ~pathNode~. But that only works for
the ~correct player~.
***** DONE Chooser
Choosers are somewhat simpler, but I haven't proved anything on them yet.
They take three hashes and choose which side (Left or Right) they want to
continue playing in.
*** DONE Winning Players -- [[file:FraudProof/Winning/Proposer.lean][WinningDefinitions]]
The goal here was to prove that /good proposers/ always win. That is that a player with some information, in particular, the original binary tree, can compute a winning strategy.
Two main concepts:
+ What's the definition of a winning strategy?
+ How to build a winning strategy from the information /honest/ player have?
**** DONE Winning Proposer
Winning proposers are path of a given length /connecting/ two hashes.
By connecting, I mean that the proposer propose hashes (nodes and siblings) that hash correct from one hsah to the other.
We can see it better in this three props:
#+begin_src lean :noeval
@[simp]
def GoodInit (h : Hash) := Player.pathNode 0 = h
@[simp]
def GoodRoot (h : Hash ) := Player.pathNode ⟨ n , by simp ⟩ = h
@[simp]
def GoodMid :=
forall (m : Nat) (mLtn : m < n ),
Player.pathNode ⟨ (m + 1) , by apply Nat.succ_lt_succ;assumption⟩ =
opHash ( Player.pathNode ⟨ m , by apply Nat.lt_add_one_of_lt; assumption ⟩) ( Player.pathSib ⟨ m , mLtn ⟩ )
#+end_src
We also lift operations from strategies to /winning proposers/ (we'll need them
when proving.)
*** DONE Fraud Proof Games -- [[file:FraudProof.lean][FraudProofs]]
Main file proving that:
**** DONE Winning Proposers win Linear Game
**** TODO Winning Proposers win Log Game
*** TODO Chooser Guarantees
If someone wrongly challenges a posted Merkle Tree, we can defend it and win.
It is a similar theorem to winning strategies when challenged. But on the other side.
**** DONE Main Idea: Path Skeletons
We need path skeletons to prove that choosers have winning strategies.
Theorem is like:
#+begin_quote
Following the same path provided by proposers, correct choosers know the how to
fill the same path with corrects hashes. Knowing that something is wrong, i.e.
the last hash proposed is wrong but the first is right (it is the hash of the
root assumed correct.), choosers can choose wisely when to challenge.
#+end_quote
**** DONE Linear games
Found a bug here. My bad when defining hash props.
Fixed, but I made a bug in proof evident.
**** TODO Log Games
**** TODO Multicut games
*** DONE Model Merkle Tree chain?
Should we model the idea of posting Merkle Trees and the possibility of challenges.
**** DONE Computing Hashes?
#+begin_src bash :noeval
python3 -m venv venv
source ./venv/bin/activate.fish
python3 -m pip install web3
#+end_src
Delegating this to Python.
#+begin_src python :noeval
from web3 import web3 # hashfunctions.
print(web3.solidity_keccak(['string'],['testing']))
#+end_src
**** DONE Removing opaque types.
See 'Hash Classes'
*** TODO Validity Proofs
*** DONE Hash Classes
#+begin_src Lean :noeval
-- Hash function
@[class] structure Hash (α ℍ : Type) where
mhash : α -> ℍ
-- Hash comb function
@[class] structure HashMagma (ℍ : Type) where
comb : ℍ -> ℍ -> ℍ
-- Laws
-- Collision resistant?
@[class] structure CollResistant (α ℍ : Type)[op : Hash α ℍ] where
-- Collision resistant? It should be hard to find these guys.
noCollisions : forall (a b : α), a ≠ b -> op.mhash a ≠ op.mhash b
-- Similar but for magma op.
@[class] structure SLawFulHash (ℍ : Type)[m : HashMagma ℍ] where
-- Combine diff hashes are diff.
neqLeft : forall (a1 a2 b1 b2 : ℍ), a1 ≠ a2 -> m.comb a1 b1 ≠ m.comb a2 b2
neqRight : forall (a1 a2 b1 b2 : ℍ), b1 ≠ b2 -> m.comb a1 b1 ≠ m.comb a2 b2
#+end_src
*** DONE Hash Injective
Injective prop is stronger than collision resistant and lawful.
*** DONE IO Interactions
**** DONE From Opaque to Classes
**** DONE Keccak256 is a valid IO Hash?
**** DONE IO Merkle Tree generation.
*** TODO A bit more general games
**** DONE DAs
DAs are weird computational data.
\(\{ a : \alpha , b : \beta \}\) and a process \(f\) such that \( f(a) = b\).
**** DONE Skeletons in BTree/Tree computations.
**** DONE Implementation of winning Defensive strategy and challenging strategies.
**** TODO Proving the above?
***** DONE DA : BTree -> Merkle Tree
***** TODO DA : (BTree -> MTree) and Valid
Depending on what the DA is, we may need different stuff.
1. DA : \(\langle e , path , ha \rangle\) -- Tree is implicit and hashes to \(ha\)
2. DA : \(\langle h(e) , path , ha \rangle\) -- Tree is implicit and hashes to \(ha\)
3. DA : \(\langle tree , _ , path , ha \rangle\) -- Tree is |tree| and hashes to \(ha\)
****** TODO Elements are f-Valid
****** TODO No repeated elements
** DONE Sequences to Vectors: Move on from Fin to Finite list
Good things, we will not need \(funext\).
#+begin_src lean4
def Sequence (n : Nat) (a : Type) := { ls : [ a ] // ls.length = n } -- Vector n a
#+end_src
** TODO Linear to Log using Generic Trees
*** DONE Define Game transformations
*** DONE Sequence Linear to Tree Linear
*** TODO Sequence Linear to /Log/ Tree Linear
I have been fighting with this one. I fall into the first model I actually
proved correct. Tried to define some wierd transformations, nothing worked but I
have an idea.
Same as before, I have two ways of seeing the sequence of hashes I have, as a
sequence of siblings plus side or just computing each hash.
I tried the sibling path, but it gets a bit fuzzy and it is not exactly what I
need when going though the logarithmic game. The logarithmic game is played only
observing the resulting intermediary hashes. Next step is to try to program that.
I took the first path because it was an easy transformation, forming a tree game
arena with exactly what I had while computing intermediary hashes along the way.
But it turned out to be a bit complex, maybe I can come back to this idea after
gaining some intuition about this.
** Hash Function
Implicit assumptions.
Hash functions are:
+ Collision resistants (from RDoC)
I did not require it to prove strategies are correct when proving Merkle trees are
correct.
** Chooser Strategy.
When an invalid hash tree is detected, we can invoke a choosers strategy to
debunk the block.
The strategy operates under the assumption the top hash is wrong, otherwise
there is no way to know if the agent proposing the block is wrong. For example,
the agent can front-run another and post what it seems to be a valid block
without knowing the tree.
*** TODO Optimization: We can build players choosing shorters paths when possible.
We know the whole tree and it is not complete.
*** TODO Chooser generation stategy game.
We can define a game using generation strategies. If choosers provide inside
knowledge of how they created their strategies, we can perform useful
transformation.
Honest choosers know the data and thus, are part of this family.
** Simultaneous games
Since we have data behind Choosers now (and we generate functions based on that), we can play a simultaneous game.
At each step, both players reveal information and based on that the game progresses.
I think they are equivalent, but I am leaving the proof of that to after FMBC.
*** Simulatneous games -- Always sectioning games
The BoLD paper describes an optimization over the k-sectioning arbitration game.
It says that when the player choosing a side on a k-sectioning, it also provides
the ranges and the next sectioning, and roles are swapped.
I do not fully understand how it works, but the idea is that sectioning happens at each move.
The only similar game I have is what I call simultaneous games, but I am not sure what they do yet.
**** DONE Ask Marga what's her take
* To build the whole project
#+begin_src sh :noeval
lake build
#+end_src
* L2Setchain FraudProofs
Data = batch tag , \(\langle id , h , \sigma \rangle \) implicitly assigned to \(b\)
where:
+ symbol \(\sigma\) seems to be a structure containing at least \(f + 1\) signatures
+ symbol \( h \) is a hash??
+ symbol \( id\) is an identification tag
+ symbol \( b \) is a batch defined as a list/sequence of transactions?
Batch tags are valid iff they hold 4 props (additional to the above) over the same 'da'?
DA: This is the next valid batch tag corresponding to batch \(b\): \(\langle id, h , \sigma \rangle\)
** DONE Data availability Challenge ::
Data is unknown to a part of the network. It is not challenging the validity of
the DA. This challenge challenges missing data? and it makes sense because of economic rules.
Challenge is over a specific node, notation here is very high level.
It is more on the lines of information retrieval than challenging results.
The way I was thinking about DAs was \( data_{rep} , res \) with two implicit
computations \( F(data) = data_{rep} \wedge C(data) = res\).
Here the first part is the missing one, \(F(data) = data_{rep}\).
After posting the tag, another agent ask for data indicating a node
#+begin_src Lean :noeval
pathToN : Skeleton
#+end_src
The original proposer then provides the information
#+begin_src Lean :noeval
data ! pathToN = info
#+end_src
And then there is a challenge game to play. The DA the proposer just did is the following
Data hashes to \(h\) (already proposed) and such path goes to \(info\).
#+begin_src Lean :noeval
(data ! pathToN = info) \and C(data) = h
#+end_src
If information provided by the proposer, the challenger can challenge the whole
subtree. In Lean, we have a game just for that.
** DONE Signature Challenge is just invoking a checker, one shoot game.
** DONE Validity Challenge ::
Challenger knows there is an invalid transaction \(e\) in batch \(t\).
Game consists then on showing that \(e \in t\) assuming \(t\) is the batch
corresponding to current batch tag \(C(t) = h\).
One player plays to prove \(e \in t\), the other to prove the opposite.
** DONE Integrity Challenge 1 ::
Two paths leading to the same element. similar to the above but with one extra
step, the defender should choose a path (the one that it thinks is wrong) and
play the ElementInTree game.
** DONE Integrity Challenge 2 ::
Element \(e\) appears in two batches. Same as before but involving one paths in
each batch.