|
| 1 | +||| SPDX-License-Identifier: MPL-2.0 |
| 2 | +||| |
| 3 | +||| Ochrance.Filesystem.MerkleBuild - buildMerkleTree / getLeafHash correctness. |
| 4 | +||| |
| 5 | +||| Proves `buildGetLeaf`: reading leaf `i` back out of `buildMerkleTree hs` |
| 6 | +||| returns exactly `index i hs`. This is the round-trip soundness of the |
| 7 | +||| balanced Merkle constructor against the by-index leaf accessor. |
| 8 | + |
| 9 | +module Ochrance.Filesystem.MerkleBuild |
| 10 | + |
| 11 | +import Data.Vect |
| 12 | +import Data.Fin |
| 13 | +import Data.Fin.Split |
| 14 | +import Data.Fin.Properties |
| 15 | +import Data.Nat |
| 16 | + |
| 17 | +import Ochrance.A2ML.Types |
| 18 | +import Ochrance.Filesystem.Merkle |
| 19 | +import Ochrance.Util.VectLemmas |
| 20 | + |
| 21 | +%default total |
| 22 | + |
| 23 | +-------------------------------------------------------------------------------- |
| 24 | +-- Bridging the Bool `idx < power 2 k` to the splitSum case |
| 25 | +-- |
| 26 | +-- For Nat, `(<)` is the Ord-interface default: `x < y = compare x y == LT`, |
| 27 | +-- and `compare = compareNat`. So `idx < m` is `compareNat idx m == LT`. |
| 28 | +-------------------------------------------------------------------------------- |
| 29 | + |
| 30 | +||| `compareNat a b` is `LT` whenever `a` is strictly below `b`. |
| 31 | +compareNatLT : (a, b : Nat) -> LT a b -> compareNat a b = LT |
| 32 | +compareNatLT Z (S _) _ = Refl |
| 33 | +compareNatLT (S a) (S b) (LTESucc p) = compareNatLT a b p |
| 34 | + |
| 35 | +||| Any `Fin m` index is `< m` as a Bool. (`<` here is the Nat Ord default.) |
| 36 | +finBoundLT : (a : Fin m) -> (finToNat a < m) = True |
| 37 | +finBoundLT a = |
| 38 | + rewrite compareNatLT (finToNat a) m (elemSmallerThanBound a) in Refl |
| 39 | + |
| 40 | +||| `compareNat (p + x) p` is never `LT`: `p + x` is at least `p`. |
| 41 | +compareNatPlusNotLT : (p, x : Nat) -> |
| 42 | + Either (compareNat (p + x) p = EQ) (compareNat (p + x) p = GT) |
| 43 | +compareNatPlusNotLT Z Z = Left Refl |
| 44 | +compareNatPlusNotLT Z (S _) = Right Refl |
| 45 | +compareNatPlusNotLT (S p) x = compareNatPlusNotLT p x |
| 46 | + |
| 47 | +||| `(p + x) < p` is `False`: a left-padded index never falls in the left half. |
| 48 | +plusNotLT : (p, x : Nat) -> ((p + x) < p) = False |
| 49 | +plusNotLT p x = case compareNatPlusNotLT p x of |
| 50 | + Left eq => rewrite eq in Refl |
| 51 | + Right eq => rewrite eq in Refl |
| 52 | + |
| 53 | +-------------------------------------------------------------------------------- |
| 54 | +-- buildMerkleTree / getLeafHash round-trip soundness |
| 55 | +-------------------------------------------------------------------------------- |
| 56 | + |
| 57 | +||| Core of the inductive step at height `S k`, stated on the *already split* |
| 58 | +||| halves so the `splitAt`/`splitSum` reasoning is decoupled from the transport. |
| 59 | +||| |
| 60 | +||| `left ++ right` is the (transported) leaf vector and `j` is the (transported) |
| 61 | +||| index into it; the two IH calls discharge each subtree. |
| 62 | +buildGetLeafSplit : |
| 63 | + {k : Nat} -> |
| 64 | + (left, right : Vect (power 2 k) HashBytes) -> |
| 65 | + (j : Fin (power 2 k + power 2 k)) -> |
| 66 | + ((bl : Vect (power 2 k) HashBytes) -> (a : Fin (power 2 k)) -> |
| 67 | + getLeafHash (buildMerkleTree {n = k} bl) (finToNat a) = Just (index a bl)) -> |
| 68 | + getLeafHash (Node (buildMerkleTree {n = k} left) (buildMerkleTree {n = k} right)) |
| 69 | + (finToNat j) |
| 70 | + = Just (index j (left ++ right)) |
| 71 | +buildGetLeafSplit {k} left right j ih with (splitSum {m = power 2 k} {n = power 2 k} j) |
| 72 | + proof splitEq |
| 73 | + _ | Left a = |
| 74 | + -- j = weakenN (power 2 k) a ; index lands in `left`, THEN branch taken. |
| 75 | + let jIs : (j = weakenN (power 2 k) a) |
| 76 | + jIs = trans (sym (indexOfSplitSumInverse {m = power 2 k} {n = power 2 k} j)) |
| 77 | + (cong indexSum splitEq) |
| 78 | + finEq : (finToNat j = finToNat a) |
| 79 | + finEq = trans (cong finToNat jIs) (finToNatWeakenNNeutral (power 2 k) a) |
| 80 | + idxEq : (index j (left ++ right) = index a left) |
| 81 | + idxEq = trans (cong (\z => index z (left ++ right)) jIs) |
| 82 | + (indexAppendLeft a left right) |
| 83 | + in rewrite finEq in |
| 84 | + rewrite finBoundLT a in |
| 85 | + rewrite idxEq in |
| 86 | + ih left a |
| 87 | + _ | Right b = |
| 88 | + -- j = shift (power 2 k) b ; index lands in `right`, ELSE branch taken. |
| 89 | + let jIs : (j = shift (power 2 k) b) |
| 90 | + jIs = trans (sym (indexOfSplitSumInverse {m = power 2 k} {n = power 2 k} j)) |
| 91 | + (cong indexSum splitEq) |
| 92 | + finEq : (finToNat j = power 2 k + finToNat b) |
| 93 | + finEq = trans (cong finToNat jIs) (finToNatShift (power 2 k) b) |
| 94 | + idxEq : (index j (left ++ right) = index b right) |
| 95 | + idxEq = trans (cong (\z => index z (left ++ right)) jIs) |
| 96 | + (indexAppendRight b left right) |
| 97 | + in rewrite finEq in |
| 98 | + rewrite plusNotLT (power 2 k) (finToNat b) in |
| 99 | + rewrite minusPlus {n = finToNat b} (power 2 k) in |
| 100 | + rewrite idxEq in |
| 101 | + ih right b |
| 102 | + |
| 103 | +||| Reading leaf `i` back out of `buildMerkleTree hs` returns `index i hs`. |
| 104 | +||| The constructor places leaf `i` at exactly position `finToNat i`. |
| 105 | +export |
| 106 | +buildGetLeaf : {n : Nat} -> (hs : Vect (power 2 n) HashBytes) -> (i : Fin (power 2 n)) -> |
| 107 | + getLeafHash (buildMerkleTree {n} hs) (finToNat i) = Just (index i hs) |
| 108 | +buildGetLeaf {n = Z} [h] FZ = Refl |
| 109 | +buildGetLeaf {n = S k} hs i with (splitAt (power 2 k) |
| 110 | + (replace {p = \x => Vect x HashBytes} (powerTwoSucc k) hs)) |
| 111 | + proof splitPrf |
| 112 | + _ | (left, right) = |
| 113 | + let i' : Fin (power 2 k + power 2 k) |
| 114 | + i' = replace {p = Fin} (powerTwoSucc k) i |
| 115 | + hs' : Vect (power 2 k + power 2 k) HashBytes |
| 116 | + hs' = replace {p = \x => Vect x HashBytes} (powerTwoSucc k) hs |
| 117 | + -- The two split halves re-append to the transported vector. |
| 118 | + catEq : (left ++ right = hs') |
| 119 | + catEq = trans (sym (cong (\z => fst z ++ snd z) splitPrf)) |
| 120 | + (splitAtConcat (power 2 k) hs') |
| 121 | + -- index/finToNat are preserved by the transport. |
| 122 | + finEq : (finToNat i = finToNat i') |
| 123 | + finEq = sym (finToNatReplace (powerTwoSucc k) i) |
| 124 | + idxEq : (index i hs = index i' (left ++ right)) |
| 125 | + idxEq = trans (sym (indexReplace (powerTwoSucc k) i hs)) |
| 126 | + (sym (cong (index i') catEq)) |
| 127 | + in rewrite finEq in |
| 128 | + rewrite idxEq in |
| 129 | + buildGetLeafSplit left right i' buildGetLeaf |
0 commit comments