Skip to content

Commit 9a06cc5

Browse files
authored
feat: Inductive Merkle Trees with completeness theorem (#44)
* add file * progress on proof * formulate parts of completeness proof * progress on completeness * progress on completeness * push sorries down to mem_buildMerkleTree_support_iff * rewrite to push sorries into monad code * clean up and document * clean up * runWithOracle_bind * reorganize tree * clean up comments and improve readability in randomOracle theorems * mk_all
1 parent 16bd10d commit 9a06cc5

6 files changed

Lines changed: 1299 additions & 0 deletions

File tree

ArkLib.lean

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ArkLib.AGM.Basic
22
import ArkLib.CommitmentScheme.Basic
33
import ArkLib.CommitmentScheme.Fold
4+
import ArkLib.CommitmentScheme.InductiveMerkleTree
45
import ArkLib.CommitmentScheme.KZG
56
import ArkLib.CommitmentScheme.MerkleTree
67
import ArkLib.CommitmentScheme.SimpleRO
@@ -159,6 +160,9 @@ import ArkLib.ProofSystem.Whir.OutofDomainSmpl
159160
import ArkLib.ProofSystem.Whir.ProximityGap
160161
import ArkLib.ProofSystem.Whir.ProximityGen
161162
import ArkLib.ToMathlib.BigOperators.Fin
163+
import ArkLib.ToMathlib.Data.IndexedBinaryTree.Basic
164+
import ArkLib.ToMathlib.Data.IndexedBinaryTree.Equiv
165+
import ArkLib.ToMathlib.Data.IndexedBinaryTree.Lemmas
162166
import ArkLib.ToMathlib.Finset.Basic
163167
import ArkLib.ToMathlib.Finsupp.Fin
164168
import ArkLib.ToMathlib.MvPolynomial.Equiv
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
/-
2+
Copyright (c) 2024 ArkLib Contributors. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Quang Dao
5+
-/
6+
7+
import VCVio
8+
import ArkLib.ToMathlib.Data.IndexedBinaryTree.Basic
9+
import ArkLib.CommitmentScheme.Basic
10+
import Mathlib.Data.Vector.Snoc
11+
import ArkLib.ToVCVio.Oracle
12+
13+
/-!
14+
# Inductive Merkle Trees
15+
16+
This file implements Merkle Trees. In contrast to the other Merkle tree implementation in
17+
`ArkLib.CommitmentScheme.MerkleTree`, this one is defined inductively.
18+
19+
## Implementation Notes
20+
21+
This works with trees that are indexed inductive binary trees,
22+
(i.e. indexed in that their definitions and methods carry parameters regarding their structure)
23+
as defined in `ArkLib.Data.IndexedBinaryTree`.
24+
25+
* We found that the inductive definition seems likely to be convenient for a few reasons:
26+
* It allows us to handle non-perfect trees.
27+
* It can allow us to use trees of arbitrary structure in the extractor.
28+
* I considered the indexed type useful because the completeness theorem and extractibility theorems
29+
take indices or sets of indices as parameters,
30+
and because we are working with trees of arbitrary structure,
31+
this lets us avoid having to check that these indices are valid.
32+
33+
## Plan/TODOs
34+
35+
- [x] Basic Merkle tree API
36+
- [x] `buildMerkleTree`
37+
- [x] `generateProof`
38+
- [x] `getPutativeRoot`
39+
- [x] `verifyProof`
40+
- [x] Completeness theorem
41+
- [ ] Collision Lemma (See SNARGs book 18.3)
42+
- (this is really not a lemma about oracles, so it could go with the binary tree API)
43+
- [ ] Extractibility (See SNARGs book 18.5)
44+
- [ ] Multi-leaf proofs
45+
- [ ] Arbirary arity trees
46+
- [ ] Multi-instance
47+
48+
-/
49+
50+
51+
namespace InductiveMerkleTree
52+
53+
open List OracleSpec OracleComp BinaryTree
54+
55+
section spec
56+
57+
variable (α : Type)
58+
59+
/-- Define the domain & range of the (single) oracle needed for constructing a Merkle tree with
60+
elements from some type `α`.
61+
62+
We may instantiate `α` with `BitVec n` or `Fin (2 ^ n)` to construct a Merkle tree for boolean
63+
vectors of length `n`. -/
64+
@[reducible]
65+
def spec : OracleSpec Unit := fun _ => (α × α, α)
66+
67+
@[simp]
68+
lemma domain_def : (spec α).domain () = (α × α) := rfl
69+
70+
@[simp]
71+
lemma range_def : (spec α).range () = α := rfl
72+
73+
end spec
74+
75+
76+
variable {α : Type}
77+
78+
/-- Example: a single hash computation -/
79+
def singleHash (left : α) (right : α) : OracleComp (spec α) α := do
80+
let out ← query (spec := spec α) () ⟨left, right⟩
81+
return out
82+
83+
/-- Build the full Merkle tree, returning the tree populated with data on all its nodes -/
84+
def buildMerkleTree {s} (leaf_tree : LeafData α s) : OracleComp (spec α) (FullData α s) :=
85+
match leaf_tree with
86+
| LeafData.leaf a => do return (FullData.leaf a)
87+
| LeafData.internal left right => do
88+
let leftTree ← buildMerkleTree left
89+
let rightTree ← buildMerkleTree right
90+
let rootHash ← singleHash leftTree.getRootValue rightTree.getRootValue
91+
return FullData.internal rootHash leftTree rightTree
92+
93+
/--
94+
A functional form of merkle tree construction, that doesn't depend on the monad.
95+
This receives an explicit hash function
96+
-/
97+
def buildMerkleTree_with_hash {s} (leaf_tree : LeafData α s) (hashFn : α → α → α) :
98+
(FullData α s) :=
99+
match leaf_tree with
100+
| LeafData.leaf a => FullData.leaf a
101+
| LeafData.internal left right =>
102+
let leftTree := buildMerkleTree_with_hash left hashFn
103+
let rightTree := buildMerkleTree_with_hash right hashFn
104+
let rootHash := hashFn (leftTree.getRootValue) (rightTree.getRootValue)
105+
FullData.internal rootHash leftTree rightTree
106+
107+
/--
108+
Running the monadic version of `buildMerkleTree` with an oracle function `f`
109+
is equivalent to running the functional version of `buildMerkleTree_with_hash`
110+
with the same oracle function.
111+
-/
112+
lemma runWithOracle_buildMerkleTree {s} (leaf_data_tree : LeafData α s) (f) :
113+
(runWithOracle f (buildMerkleTree leaf_data_tree))
114+
= buildMerkleTree_with_hash leaf_data_tree fun (left right : α) =>
115+
(f () ⟨left, right⟩) := by
116+
induction s with
117+
| leaf =>
118+
match leaf_data_tree with
119+
| LeafData.leaf a =>
120+
unfold buildMerkleTree
121+
simp only [runWithOracle_pure, buildMerkleTree_with_hash]
122+
| internal s_left s_right left_ih right_ih =>
123+
match leaf_data_tree with
124+
| LeafData.internal left right =>
125+
unfold buildMerkleTree
126+
simp [left_ih, right_ih, runWithOracle_bind]
127+
rfl
128+
129+
/--
130+
Generate a Merkle proof for a leaf at a given idx
131+
The proof consists of the sibling hashes needed to recompute the root.
132+
133+
TODO rename this to copath and move to BinaryTree?
134+
-/
135+
def generateProof {s} (cache_tree : FullData α s) :
136+
BinaryTree.SkeletonLeafIndex s → List α
137+
| .ofLeaf => []
138+
| .ofLeft idxLeft =>
139+
(cache_tree.rightSubtree).getRootValue ::
140+
(generateProof cache_tree.leftSubtree idxLeft)
141+
| .ofRight idxRight =>
142+
(cache_tree.leftSubtree).getRootValue ::
143+
(generateProof cache_tree.rightSubtree idxRight)
144+
145+
@[simp]
146+
theorem generateProof_leaf (a : α) (idx) :
147+
generateProof (FullData.leaf a) idx = [] := by
148+
cases idx with
149+
| ofLeaf => rfl
150+
151+
@[simp]
152+
theorem generateProof_ofLeft {sleft sright : Skeleton}
153+
(cache_tree : FullData α (Skeleton.internal sleft sright))
154+
(idxLeft : SkeletonLeafIndex sleft) :
155+
generateProof cache_tree (BinaryTree.SkeletonLeafIndex.ofLeft idxLeft) =
156+
(cache_tree.rightSubtree).getRootValue ::
157+
(generateProof cache_tree.leftSubtree idxLeft) := by
158+
rfl
159+
160+
@[simp]
161+
theorem generateProof_ofRight {sleft sright : Skeleton}
162+
(cache_tree : FullData α (Skeleton.internal sleft sright))
163+
(idxRight : SkeletonLeafIndex sright) :
164+
generateProof cache_tree (BinaryTree.SkeletonLeafIndex.ofRight idxRight) =
165+
(cache_tree.leftSubtree).getRootValue ::
166+
(generateProof cache_tree.rightSubtree idxRight) := by
167+
rfl
168+
169+
/--
170+
Given a leaf index, a leaf value at that index, and putative proof,
171+
returns the hash that would be the root of the tree if the proof was valid.
172+
i.e. the hash obtained by combining the leaf in sequence with each member of the proof,
173+
according to its index.
174+
-/
175+
def getPutativeRoot {s} (idx : BinaryTree.SkeletonLeafIndex s) (leafValue : α)
176+
(proof : List α) : OracleComp (spec α) α :=
177+
match proof with
178+
| [] => return leafValue -- If no proof, the root is just the leaf value
179+
| siblingBelowRootHash :: restProof => do
180+
match idx with
181+
| BinaryTree.SkeletonLeafIndex.ofLeaf =>
182+
-- This indicates that the proof is longer than the depth of the tree, which is invalid.
183+
-- A more well-typed version using `Vector` might prevent this.
184+
-- For now, we just return the leaf value.
185+
return leafValue
186+
| BinaryTree.SkeletonLeafIndex.ofLeft idxLeft =>
187+
-- Recursively get the hash of the ancestor of the leaf which is just below the root
188+
let ancestorBelowRootHash ← getPutativeRoot idxLeft leafValue restProof
189+
singleHash ancestorBelowRootHash siblingBelowRootHash
190+
| BinaryTree.SkeletonLeafIndex.ofRight idxRight =>
191+
-- Recursively get the hash of the ancestor of the leaf which is just below the root
192+
let ancestorBelowRootHash ← getPutativeRoot idxRight leafValue restProof
193+
singleHash siblingBelowRootHash ancestorBelowRootHash
194+
195+
/--
196+
A functional version of `getPutativeRoot` that does not depend on the monad.
197+
It receives an explicit hash function `hashFn` that combines two hashes into one.
198+
And recursively calls itself down the tree.
199+
-/
200+
def getPutativeRoot_with_hash {s} (idx : BinaryTree.SkeletonLeafIndex s)
201+
(leafValue : α) (proof : List α) (hashFn : α → α → α) : α :=
202+
match proof with
203+
| [] => leafValue -- If no proof, the root is just the leaf value
204+
| siblingBelowRootHash :: restProof =>
205+
match idx with
206+
| BinaryTree.SkeletonLeafIndex.ofLeaf =>
207+
-- This indicates that the proof is longer than the depth of the tree, which is invalid.
208+
-- A more well-typed version using `Vector` might prevent this.
209+
-- For now, we just return the leaf value.
210+
leafValue
211+
| BinaryTree.SkeletonLeafIndex.ofLeft idxLeft =>
212+
-- Recursively get the hash of the ancestor of the leaf which is just below the root
213+
hashFn (getPutativeRoot_with_hash idxLeft leafValue restProof hashFn) siblingBelowRootHash
214+
| BinaryTree.SkeletonLeafIndex.ofRight idxRight =>
215+
-- Recursively get the hash of the ancestor of the leaf which is just below the root
216+
hashFn siblingBelowRootHash (getPutativeRoot_with_hash idxRight leafValue restProof hashFn)
217+
218+
/--
219+
Running the monadic version of `getPutativeRoot` with an oracle function `f`,
220+
it is equivalent to running the functional version of `getPutativeRoot_with_hash`
221+
-/
222+
lemma runWithOracle_getPutativeRoot {s} (idx : BinaryTree.SkeletonLeafIndex s)
223+
(leafValue : α) (proof : List α) (f) :
224+
(runWithOracle f (getPutativeRoot idx leafValue proof))
225+
=
226+
getPutativeRoot_with_hash idx leafValue proof fun (left right : α) => (f () ⟨left, right⟩) := by
227+
induction proof generalizing s with
228+
| nil =>
229+
unfold getPutativeRoot
230+
simp only [runWithOracle_pure, getPutativeRoot_with_hash]
231+
| cons siblingBelowRootHash restProof ih =>
232+
unfold getPutativeRoot
233+
cases s with
234+
| leaf =>
235+
cases idx with
236+
| ofLeaf =>
237+
rfl
238+
| internal s_left s_right =>
239+
cases idx with
240+
| ofLeft idxLeft =>
241+
simp [runWithOracle_bind, ih]
242+
rfl
243+
| ofRight idxRight =>
244+
simp only [runWithOracle_bind, ih]
245+
rfl
246+
247+
/--
248+
Verify a Merkle proof `proof` that a given `leaf` at index `i` is in the Merkle tree with given
249+
`root`.
250+
Works by computing the putative root based on the branch, and comparing that to the actual root.
251+
Outputs `failure` if the proof is invalid.
252+
-/
253+
def verifyProof {α} [DecidableEq α] {s}
254+
(idx : BinaryTree.SkeletonLeafIndex s) (leafValue : α) (rootValue : α)
255+
(proof : List α) : OracleComp (spec α) Unit := do
256+
let putative_root ← getPutativeRoot idx leafValue proof
257+
guard (putative_root = rootValue)
258+
259+
/--
260+
A functional form of the completeness theorem for Merkle trees.
261+
This references the functional versions of `getPutativeRoot` and `buildMerkleTree_with_hash`
262+
-/
263+
theorem functional_completeness (α : Type) {s : Skeleton}
264+
(idx : SkeletonLeafIndex s)
265+
(leaf_data_tree : LeafData α s)
266+
(hash : α → α → α) :
267+
(getPutativeRoot_with_hash
268+
idx
269+
(leaf_data_tree.get idx)
270+
(generateProof
271+
(buildMerkleTree_with_hash leaf_data_tree hash) idx)
272+
(hash)) =
273+
(buildMerkleTree_with_hash leaf_data_tree hash).getRootValue := by
274+
induction s with
275+
| leaf =>
276+
match leaf_data_tree with
277+
| LeafData.leaf a =>
278+
cases idx with
279+
| ofLeaf =>
280+
simp [buildMerkleTree_with_hash, getPutativeRoot_with_hash]
281+
| internal s_left s_right left_ih right_ih =>
282+
match leaf_data_tree with
283+
| LeafData.internal left right =>
284+
cases idx with
285+
| ofLeft idxLeft =>
286+
simp_rw [LeafData.get_ofLeft, LeafData.leftSubtree_internal, buildMerkleTree_with_hash,
287+
generateProof_ofLeft, FullData.rightSubtree, FullData.leftSubtree,
288+
getPutativeRoot_with_hash, left_ih, FullData.internal_getRootValue]
289+
| ofRight idxRight =>
290+
simp_rw [LeafData.get_ofRight, LeafData.rightSubtree_internal, buildMerkleTree_with_hash,
291+
generateProof_ofRight, FullData.leftSubtree, FullData.rightSubtree,
292+
getPutativeRoot_with_hash, right_ih, FullData.internal_getRootValue]
293+
294+
295+
/--
296+
Completeness theorem for Merkle trees.
297+
298+
The proof proceeds by reducing to the functional completeness theorem by a theorem about
299+
the OracleComp monad,
300+
and then applying the functional version of the completeness theorem.
301+
-/
302+
theorem completeness [DecidableEq α] [SelectableType α] {s}
303+
(leaf_data_tree : LeafData α s) (idx : BinaryTree.SkeletonLeafIndex s)
304+
(preexisting_cache : (spec α).QueryCache) :
305+
(((do
306+
let cache ← buildMerkleTree leaf_data_tree
307+
let proof := generateProof cache idx
308+
let _ ← verifyProof idx (leaf_data_tree.get idx) (cache.getRootValue) proof
309+
).simulateQ (randomOracle)).run preexisting_cache).neverFails := by
310+
-- An OracleComp is never failing on any preexisting cache
311+
-- if it never fails when run with any oracle function.
312+
revert preexisting_cache
313+
rw [randomOracle_neverFails_iff_runWithOracle_neverFails]
314+
-- Call this hash function `f`
315+
intro f
316+
-- Simplify
317+
simp_rw [verifyProof, guard_eq, bind_pure_comp, id_map', runWithOracle_bind,
318+
runWithOracle_buildMerkleTree, runWithOracle_getPutativeRoot]
319+
simp only [apply_ite, runWithOracle_pure, runWithOracle_failure, Option.bind_eq_bind,
320+
Option.bind_some, Option.isSome_some, Option.isSome_none, Bool.if_false_right, Bool.and_true,
321+
decide_eq_true_eq]
322+
exact functional_completeness α idx leaf_data_tree fun left right ↦ f () (left, right)
323+
324+
end InductiveMerkleTree

0 commit comments

Comments
 (0)