|
| 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