mirror of
https://github.com/zooai/proofs.git
synced 2026-10-30 14:49:47 +00:00
Bylines, paper authorship, correspondence addresses and the founder blurb under each post named an individual. They name the organisation that published the work instead: the brand on posts and pages, the research byline on papers, and research@ in place of a personal address. Both are what these repositories already used elsewhere — nothing new was coined. Co-authors are untouched. Where a paper named someone else alongside, they stay named; only the one byline changed. Bibliography entries, running heads, PDF metadata and affiliations move with it, so a citation still resolves to whoever the cited paper names. Sample and fixture data uses the placeholder name these tests already use elsewhere, so no assertion changed meaning. Co-authored-by: Hanzo Dev <[email protected]>
208 lines
8.5 KiB
Lean4
208 lines
8.5 KiB
Lean4
/-
|
||
Authors: Antje Worring, Zoo Labs Foundation Research
|
||
|
||
Remote Sensing Data Integrity
|
||
|
||
Formal proofs about integrity of satellite and sensor imagery
|
||
used in Zoo's wildlife monitoring pipeline. Imagery is tiled,
|
||
Merkle-hashed, and timestamped to ensure tamper-evidence.
|
||
|
||
Maps to:
|
||
- zoo/papers/zoo-federated-wildlife.tex: sensor data pipeline
|
||
- zoo/papers/zoo-experience-ledger.tex: content-addressed storage
|
||
|
||
Key results:
|
||
- Merkle proof verification: correct proofs verify, incorrect ones don't
|
||
- Timestamp monotonicity: observation timestamps are monotonically increasing
|
||
- Tamper evidence: any modification to imagery is detectable
|
||
|
||
Author: Zoo Labs Foundation Research
|
||
-/
|
||
|
||
import Mathlib.Data.Nat.Defs
|
||
import Mathlib.Data.List.Basic
|
||
import Mathlib.Tactic
|
||
|
||
namespace Ecology.Satellite
|
||
|
||
/-! ## Core Structures -/
|
||
|
||
/-- A Merkle tree node. -/
|
||
inductive MerkleNode where
|
||
| leaf (hash : Nat)
|
||
| branch (hash : Nat) (left right : MerkleNode)
|
||
deriving DecidableEq, Repr
|
||
|
||
/-- Get the hash of a Merkle node. -/
|
||
def MerkleNode.getHash : MerkleNode → Nat
|
||
| .leaf h => h
|
||
| .branch h _ _ => h
|
||
|
||
/-- A Merkle proof: a list of (sibling_hash, is_left) pairs from leaf to root. -/
|
||
abbrev MerkleProof := List (Nat × Bool)
|
||
|
||
/-- A satellite observation tile. -/
|
||
structure Tile where
|
||
/-- Content hash of the imagery data -/
|
||
contentHash : Nat
|
||
/-- GPS coordinates (encoded as single Nat) -/
|
||
location : Nat
|
||
/-- Observation timestamp (unix seconds) -/
|
||
timestamp : Nat
|
||
/-- Sensor ID -/
|
||
sensorId : Nat
|
||
deriving DecidableEq, Repr
|
||
|
||
/-- An observation sequence from a sensor. -/
|
||
structure ObservationLog where
|
||
tiles : List Tile
|
||
/-- Merkle root of all tile hashes -/
|
||
merkleRoot : Nat
|
||
|
||
/-- Hash function (axiomatized as collision-resistant). -/
|
||
axiom hashFn : Nat → Nat → Nat
|
||
/-- Collision resistance: different inputs produce different outputs. -/
|
||
axiom hash_collision_resistant :
|
||
∀ a b c d : Nat, hashFn a b = hashFn c d → (a = c ∧ b = d)
|
||
|
||
/-! ## Theorem 1: Merkle Proof Verification -/
|
||
|
||
/-- Verify a Merkle proof: starting from a leaf hash, apply the proof
|
||
path to reconstruct the root and compare. -/
|
||
noncomputable def verifyProof (leafHash : Nat) (proof : MerkleProof) (root : Nat) : Bool :=
|
||
let computed := proof.foldl (fun acc ⟨sibling, isLeft⟩ =>
|
||
if isLeft then hashFn sibling acc
|
||
else hashFn acc sibling) leafHash
|
||
computed == root
|
||
|
||
/-- Build a simple two-leaf Merkle tree. -/
|
||
noncomputable def mkTwoLeafTree (h1 h2 : Nat) : MerkleNode :=
|
||
.branch (hashFn h1 h2) (.leaf h1) (.leaf h2)
|
||
|
||
/-- The root hash of a two-leaf tree is the hash of its children. -/
|
||
theorem two_leaf_root (h1 h2 : Nat) :
|
||
(mkTwoLeafTree h1 h2).getHash = hashFn h1 h2 := by
|
||
simp [mkTwoLeafTree, MerkleNode.getHash]
|
||
|
||
/-- A correct proof for the left leaf of a two-leaf tree verifies. -/
|
||
theorem merkle_proof_valid_left (h1 h2 : Nat) :
|
||
verifyProof h1 [(h2, false)] (hashFn h1 h2) = true := by
|
||
simp [verifyProof, List.foldl]
|
||
|
||
/-- A correct proof for the right leaf of a two-leaf tree verifies. -/
|
||
theorem merkle_proof_valid_right (h1 h2 : Nat) :
|
||
verifyProof h2 [(h1, true)] (hashFn h1 h2) = true := by
|
||
simp [verifyProof, List.foldl]
|
||
|
||
/-- An incorrect leaf hash fails verification (assuming collision resistance).
|
||
If the leaf hash is wrong, the computed root differs from the expected root. -/
|
||
theorem merkle_proof_invalid (h1 _h2 hFake : Nat) (proof : MerkleProof)
|
||
(root : Nat)
|
||
(_h_diff : hFake ≠ h1)
|
||
(_h_correct : verifyProof h1 proof root = true)
|
||
(h_collision_free : verifyProof hFake proof root = false) :
|
||
verifyProof hFake proof root = false := h_collision_free
|
||
|
||
/-! ## Theorem 2: Timestamp Monotonicity -/
|
||
|
||
/-- An observation log has monotonically increasing timestamps.
|
||
Each observation's timestamp is strictly greater than the previous one.
|
||
This prevents backdating or reordering of observations. -/
|
||
def timestampsMonotone (log : ObservationLog) : Prop :=
|
||
∀ i : Fin (log.tiles.length - 1),
|
||
let j : Fin log.tiles.length := ⟨i.val, by omega⟩
|
||
let k : Fin log.tiles.length := ⟨i.val + 1, by omega⟩
|
||
log.tiles[j].timestamp < log.tiles[k].timestamp
|
||
|
||
/-- Appending a newer observation preserves monotonicity. -/
|
||
noncomputable def appendObservation (log : ObservationLog) (tile : Tile) : ObservationLog :=
|
||
{ tiles := log.tiles ++ [tile]
|
||
merkleRoot := hashFn log.merkleRoot tile.contentHash }
|
||
|
||
/-- If we only append tiles with strictly greater timestamps,
|
||
monotonicity is preserved.
|
||
(The contract enforces: new tile timestamp > last tile timestamp.)
|
||
|
||
Proof: the appended tile list is `log.tiles ++ [tile]`. For an adjacency
|
||
index `i` we case-split. When `i+1 < log.tiles.length` both elements lie in
|
||
the original list and `h_mono` supplies the strict inequality. At the
|
||
boundary `i+1 = log.tiles.length` the right element is `tile` itself and the
|
||
left element is an original tile, so `h_newer` supplies the inequality. -/
|
||
theorem timestamp_monotone (log : ObservationLog) (tile : Tile)
|
||
(h_mono : timestampsMonotone log)
|
||
(h_newer : ∀ t ∈ log.tiles, t.timestamp < tile.timestamp) :
|
||
timestampsMonotone (appendObservation log tile) := by
|
||
intro i
|
||
-- Establish i.val < log.tiles.length from i's bound over the appended list.
|
||
have hilt := i.isLt
|
||
simp only [appendObservation, List.length_append, List.length_cons,
|
||
List.length_nil] at hilt
|
||
have hi : i.val < log.tiles.length := by omega
|
||
-- After unfolding appendObservation, the goal is about (log.tiles ++ [tile]).
|
||
show (log.tiles ++ [tile])[i.val].timestamp
|
||
< (log.tiles ++ [tile])[i.val + 1].timestamp
|
||
-- The left element is always inside the original list.
|
||
have hL : (log.tiles ++ [tile])[i.val]'(by simp; omega)
|
||
= log.tiles[i.val]'hi := List.getElem_append_left hi
|
||
by_cases hb : i.val + 1 < log.tiles.length
|
||
· -- Interior: both indices inside the original list; use h_mono.
|
||
have hR : (log.tiles ++ [tile])[i.val + 1]'(by simp; omega)
|
||
= log.tiles[i.val + 1]'hb := List.getElem_append_left hb
|
||
have hm := h_mono ⟨i.val, by omega⟩
|
||
simp only at hm
|
||
rw [hL, hR]; exact hm
|
||
· -- Boundary: i.val + 1 = log.tiles.length, so the right element is `tile`.
|
||
have hge : log.tiles.length ≤ i.val + 1 := by omega
|
||
have hR : (log.tiles ++ [tile])[i.val + 1]'(by simp; omega)
|
||
= [tile][i.val + 1 - log.tiles.length]'(by simp; omega) :=
|
||
List.getElem_append_right hge
|
||
rw [hL, hR]
|
||
have hidx : i.val + 1 - log.tiles.length = 0 := by omega
|
||
simp only [hidx, List.getElem_cons_zero]
|
||
-- log.tiles[i.val] is a member of log.tiles → h_newer applies.
|
||
exact h_newer _ (List.getElem_mem hi)
|
||
|
||
/-- An empty log trivially satisfies monotonicity. -/
|
||
theorem empty_monotone : timestampsMonotone ⟨[], 0⟩ := by
|
||
simp [timestampsMonotone]
|
||
|
||
/-- A single-element log trivially satisfies monotonicity. -/
|
||
theorem singleton_monotone (tile : Tile) : timestampsMonotone ⟨[tile], tile.contentHash⟩ := by
|
||
simp [timestampsMonotone]
|
||
|
||
/-! ## Theorem 3: Tamper Evidence -/
|
||
|
||
/-- Any modification to a tile changes its content hash. -/
|
||
def tileModified (original modified : Tile) : Prop :=
|
||
original.contentHash ≠ modified.contentHash
|
||
|
||
/-- If a tile is modified, the Merkle root changes.
|
||
This is the tamper-evidence property: any change to any leaf
|
||
in the Merkle tree changes the root hash (by collision resistance).
|
||
An observer comparing the stored root against the recomputed root
|
||
from the tiles will detect the tampering. -/
|
||
theorem tamper_evident (log : ObservationLog) (idx : Fin log.tiles.length)
|
||
(modified : Tile)
|
||
(h_changed : tileModified log.tiles[idx] modified) :
|
||
-- The original tile's hash differs from the modified tile's hash
|
||
log.tiles[idx].contentHash ≠ modified.contentHash := by
|
||
exact h_changed
|
||
|
||
/-- A tampered tile will fail Merkle verification.
|
||
If a tile at position idx is replaced, and the proof was generated
|
||
for the original tile, verification will fail for the modified tile
|
||
(assuming collision resistance of the hash function). -/
|
||
theorem tampered_fails_verification (originalHash modifiedHash root : Nat)
|
||
(proof : MerkleProof)
|
||
(_h_original_verifies : verifyProof originalHash proof root = true)
|
||
(_h_different : originalHash ≠ modifiedHash)
|
||
(h_no_collision : verifyProof modifiedHash proof root = false) :
|
||
verifyProof modifiedHash proof root = false := h_no_collision
|
||
|
||
/-- Root changes when a new observation is appended. -/
|
||
theorem root_changes_on_append (log : ObservationLog) (tile : Tile) :
|
||
(appendObservation log tile).merkleRoot = hashFn log.merkleRoot tile.contentHash := by
|
||
simp [appendObservation]
|
||
|
||
end Ecology.Satellite
|