Files
zeekayandHanzo Dev 3626b96b68 lean: prove the ten files that did not compile
Every library is a default target, so any one of these failing takes the
whole build down. What each was:

Compute/Billing.lean, Gateway/RateLimit.lean, CRDT/Anchor.lean — `simp [f]
at h` already turns the guard into a conjunction, so the `split at h` after
it had nothing left to split. Destructure the conjunction and state the
reduced goal for omega. In `rate_limited` the simp closed the goal, so the
line after it ran on none.

Compute/ConfidentialCompute.lean — `a == b = true` does not parse: `==` and
`=` are both precedence 50 and non-associative. State the propositional
equality, which is the stronger claim anyway.

Compute/PoAI.lean — `reward_bounded` was false as stated. qualityScore is
unbounded in the type, so `rate ≤ 1000` alone does not bound
computeUnits * rate * quality / 1000000 by computeUnits; quality = 10^9
breaks it. Added the range hypothesis on qualityScore that the 1000000
divisor normalizes against, and proved it. `canonical_root` was false of an
arbitrary AISupply — nothing stops a record from teleporting more than was
minted — so the bridge invariant it needs is now stated as `wellFormed`.
`low_quality_no_mining` closed at the simp.

Compute/Swarm.lean, KMS/Secrets.lean — trailing `omega` after a simp that
had already closed the goal. AccessPolicy also needed `deriving Repr`,
which SecretMeta's own Repr derivation requires of it.

CRDT/Privacy.lean — `seal ` is a Lean command token, so a structure field
named `seal` cannot parse. Backend fields take the names the Go interface
uses (encrypt / decrypt) and the envelope layer is wrap / unwrap.
`wrong_tag` needed the tag inequality in the direction the guard tests it,
and both message theorems needed mapM' so the goal did not unfold into
List.mapM.loop.

Gateway/Auth.lean — checkAuth calls the axiomatized verifyToken, so it has
no code to compile: noncomputable.

Platform/SBOM.lean — simp had already pushed the negation inward, leaving
push_neg nothing to do, and `List.all_cons` left a goal in ∀-form that the
Bool-form hypothesis does not close.

Three statements compiled while asserting nothing, and now carry a claim:
enclave_isolation and attestation_constant_size stated `True`, and
derived_key_isolation returned its own hypothesis. Isolation is now
non-interference over a host view — two attested tasks differing only in
enclave payload look identical from outside — and derived_key_isolation
derives the master's confinement from hsm_boundary, which until now no
theorem used.

lake build exits 0. vacuity.py: 0 vacuous declarations, down from 2, and no
theorem rests on sorryAx.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-29 11:47:14 -07:00

129 lines
5.1 KiB
Lean4

/-
CRDT Privacy
Every operation is wrapped before it leaves the process and unwrapped
when it arrives. The wire format carries only wrapped envelopes —
there is no plaintext field, so a downgrade injection cannot be
expressed as a valid message.
Each envelope binds the document ID into the authenticated payload.
An envelope captured from document A cannot be replayed against
document B even when both share a key, because the opener checks
the embedded ID against its own.
`seal` and `open` are Lean commands, so the backend operations here
carry the names the Go interface uses (EncryptOp / DecryptOp) and the
envelope layer is wrap / unwrap (Go: SealOps / OpenOps).
Maps to:
- hanzo/base/crdt/privacy.go (Privacy interface: Name, EncryptOp, DecryptOp)
- hanzo/base/crdt/document.go (SealOps / OpenOps with docID wrapping)
- hanzo/base/crdt/sync.go (SyncMessage has Envelopes only, resolveOps is one line)
-/
import Mathlib.Data.Finset.Basic
import Mathlib.Tactic
namespace CRDT.Privacy
abbrev Op := Nat
abbrev Tag := String
abbrev DocID := String
abbrev Ciphertext := Nat
/-- A privacy backend encrypts and decrypts operations. The `recovers`
field is the contract: decrypt always undoes encrypt. -/
structure Backend where
name : Tag
encrypt : Op → Ciphertext
decrypt : Ciphertext → Option Op
recovers : ∀ op, decrypt (encrypt op) = some op
/-- An envelope pairs the backend's tag with the ciphertext.
The tag lets the receiver verify the op was encrypted by a
compatible backend before attempting to decrypt. -/
structure Envelope where
tag : Tag
ct : Ciphertext
def wrap (b : Backend) (op : Op) : Envelope :=
{ tag := b.name, ct := b.encrypt op }
def unwrap (b : Backend) (env : Envelope) : Option Op :=
if env.tag = b.name then b.decrypt env.ct else none
/-- Wrap then unwrap gives back the original op. -/
theorem wrap_then_unwrap (b : Backend) (op : Op) :
unwrap b (wrap b op) = some op := by
simp [unwrap, wrap, b.recovers]
/-- An envelope wrapped by one backend cannot be unwrapped by another.
This is the tag-mismatch check that prevents a peer from
swapping backends mid-stream. -/
theorem wrong_tag (b₁ b₂ : Backend) (h : b₁.name ≠ b₂.name) (op : Op) :
unwrap b₂ (wrap b₁ op) = none := by
simp [unwrap, wrap, h]
-- ── DocID binding ────────────────────────────────────────────
/-- A bound envelope extends Envelope with the document ID.
In the Go code this ID is prepended inside the ciphertext
via wrapDocID; here we model it as a field for clarity. -/
structure BoundEnvelope extends Envelope where
docID : DocID
def wrapFor (b : Backend) (doc : DocID) (op : Op) : BoundEnvelope :=
{ tag := b.name, ct := b.encrypt op, docID := doc }
def unwrapFor (b : Backend) (doc : DocID) (env : BoundEnvelope) : Option Op :=
if env.tag = b.name ∧ env.docID = doc then b.decrypt env.ct else none
/-- Wrap then unwrap works when the document matches. -/
theorem same_doc (b : Backend) (doc : DocID) (op : Op) :
unwrapFor b doc (wrapFor b doc op) = some op := by
simp [unwrapFor, wrapFor, b.recovers]
/-- An envelope wrapped for one document fails on a different document.
This is the cross-document replay defence: the attacker has a valid
ciphertext from doc A, but doc B's opener sees the wrong docID
inside the payload and rejects it. -/
theorem wrong_doc (b : Backend) (d₁ d₂ : DocID) (h : d₁ ≠ d₂) (op : Op) :
unwrapFor b d₂ (wrapFor b d₁ op) = none := by
simp [unwrapFor, wrapFor, h]
-- ── Wire format ──────────────────────────────────────────────
/-- SyncMessage carries bound envelopes only. There is no bare-ops
field — the downgrade injection that existed when the message had
both Ops and Envelopes is structurally impossible in this type. -/
structure Message where
docID : DocID
envelopes : List BoundEnvelope
/-- Unwrap every envelope in a message. If any one fails the whole
message is rejected — no partial application. -/
def resolve (b : Backend) (doc : DocID) (msg : Message) : Option (List Op) :=
msg.envelopes.mapM (unwrapFor b doc)
/-- A message where every envelope was wrapped for the right doc and
the right backend resolves to the original ops. -/
theorem good_message (b : Backend) (doc : DocID) (ops : List Op) :
resolve b doc { docID := doc, envelopes := ops.map (wrapFor b doc) } = some ops := by
simp only [resolve, ← List.mapM'_eq_mapM]
induction ops with
| nil => rfl
| cons hd tl ih => simp [same_doc, ih]
/-- A message wrapped for the wrong document is rejected entirely.
Even one mismatched docID poisons the mapM chain. -/
theorem bad_message (b : Backend) (d₁ d₂ : DocID) (h : d₁ ≠ d₂)
(ops : List Op) (hne : ops ≠ []) :
resolve b d₂ { docID := d₁, envelopes := ops.map (wrapFor b d₁) } = none := by
cases ops with
| nil => exact absurd rfl hne
| cons hd tl =>
simp only [resolve, ← List.mapM'_eq_mapM]
simp [unwrapFor, wrapFor, h]
end CRDT.Privacy