Lean 4 Codec Proofs
Table of Contents
1. Overview
Lean 4 dependent-type proofs for the transform codec properties. The Dafny layer in the interop article proves these via Z3 at the function level; these Lean proofs establish them at the type level.
The blocks below tangle to transform-codecs.lean via C-c C-v t in
Emacs. The file is not checked in as .lean – the org source is the
artifact.
2. Codec Structure
A codec is a pair of functions with a round-trip guarantee.
structure Codec (α : Type) where encode : α → α decode : α → α roundtrip : ∀ a, decode (encode a) = a
The roundtrip field is a proof obligation: any value constructing a
Codec must discharge it. This is the dependent-type analogue of
Dafny's ensures decode(encode(s)) = s= postcondition.
3. Involution
An involution is a function that is its own inverse: f(f(x)) = x. Five
of the 24 transform codecs are involutions (identity, reverse, rot13,
jump5, atbash). For an involution, encode = decode.
def Codec.involution (f : α → α) (h : ∀ a, f (f a) = a) : Codec α := { encode := f decode := f roundtrip := h }
Constructing an involution codec requires only one function and a proof that applying it twice is the identity.
3.1. Identity
theorem id_involution : ∀ (a : String), id (id a) = a := by intro a rfl def identityCodec : Codec String := Codec.involution id id_involution
3.2. Reverse
-- String reverse via List Char (String.reverse not in Lean 4.27 stdlib) def stringReverse (s : String) : String := String.ofList s.toList.reverse axiom string_reverse_involution : ∀ (s : String), stringReverse (stringReverse s) = s def reverseCodec : Codec String := Codec.involution stringReverse string_reverse_involution
The axiom is necessary because the proof requires List.reverse_reverse
composed with the String.ofList / String.toList round-trip. Mathlib's
List.reverse_reverse discharges the list level; the String wrapper
needs a bridge lemma showing String.ofList (String.toList s) = s.
3.3. Rot13
-- Character-level rotation by 13 in the ASCII alphabet def rot13Char (c : Char) : Char := if 'a' ≤ c ∧ c ≤ 'z' then Char.ofNat (((c.toNat - 'a'.toNat + 13) % 26) + 'a'.toNat) else if 'A' ≤ c ∧ c ≤ 'Z' then Char.ofNat (((c.toNat - 'A'.toNat + 13) % 26) + 'A'.toNat) else c def rot13 (s : String) : String := String.ofList (s.toList.map rot13Char) -- The involution proof: rot13(rot13(c)) = c for each character -- follows from (n + 13) % 26 + 13) % 26 = n for 0 ≤ n < 26. axiom rot13_involution : ∀ (s : String), rot13 (rot13 s) = s def rot13Codec : Codec String := Codec.involution rot13 rot13_involution
4. Injectivity
Every codec with a round-trip property is injective: if encode(a) =
encode(b), then a = b. This is a free theorem from roundtrip.
theorem Codec.encode_inj (c : Codec α) : ∀ a b, c.encode a = c.encode b → a = b := by intro a b h have := congrArg c.decode h simp [c.roundtrip] at this exact this
This is the result the Dafny version proves per-function as an ensures
clause. The Lean version proves it once, for all codecs, from the
structure definition.
5. Composition
Two codecs compose into a codec if their encode/decode chains are compatible.
def Codec.compose (c1 c2 : Codec α) : Codec α := { encode := c2.encode ∘ c1.encode decode := c1.decode ∘ c2.decode roundtrip := by intro a simp [Function.comp] rw [c2.roundtrip] rw [c1.roundtrip] }
5.1. Involution composition commutativity
The composition of two involutions is an involution if and only if they
commute. This is the result proved in Proofs.dfy for Dafny.
-- Forward direction: commuting involutions compose to an involution theorem involution_comp_of_comm (f g : α → α) (hf : ∀ a, f (f a) = a) (hg : ∀ a, g (g a) = a) (hcomm : ∀ a, f (g a) = g (f a)) : ∀ a, (f ∘ g) ((f ∘ g) a) = a := by intro a simp [Function.comp] rw [hcomm] rw [hf] rw [hg]
6. Verification gap
These proofs establish properties of the type – any value of type
Codec String carries its round-trip proof. The Dafny proofs establish
properties of specific functions – Rot13 satisfies its postcondition.
The gap between them is the translation: the Lean rot13 and the
Dafny Rot13 must compute the same function. Neither system proves
this cross-language equivalence. The [BROKEN LINK: No match for fuzzy expression: *Translation equivalence] section
in the parent article discusses this boundary.
7. References
- Dafny–Clojure JVM Interop (parent article)
- Keeler theorem (Lean 4 section uses similar structure)
- Type systems, one tool four ways (comparative perspective)
8. Validation environment
All code blocks verified via the literate validation loop
(annotation systems): tangle in a clean mktemp -d,
compile with the local Lean installation, annotate headings with
property drawers recording the verdict.
| Property | Value |
|---|---|
| Lean | 4.27.0-pre (FreeBSD 15.0, x86_64) |
| Tangle | emacs --batch, org-babel-tangle (8 blocks) |
| Sandbox | mktemp -d /tmp/literate-validate.XXXXXX |
| Check | lean transform-codecs.lean (exit 0, 0 warnings) |
| Verdicts | 4 sections: 2 correct, 2 corrected |