/ Cornell / Proofs.lean
Proofs.lean
  1  /-
  2    Cornell.Proofs - Verified Box Implementations
  3    
  4    This module contains Box definitions with COMPLETE proofs.
  5    No `sorry`, no `axiom`, no `partial`.
  6    
  7    The goal: real load-bearing proofs that guarantee roundtrip correctness.
  8  -/
  9  
 10  import Std.Tactic.BVDecide
 11  
 12  namespace Cornell.Proofs
 13  
 14  -- ═══════════════════════════════════════════════════════════════════════════════
 15  -- BYTES (same as Basic, but we'll use ByteArray directly for lemma access)
 16  -- ═══════════════════════════════════════════════════════════════════════════════
 17  
 18  abbrev Bytes := ByteArray
 19  
 20  -- ═══════════════════════════════════════════════════════════════════════════════
 21  -- PARSE RESULT
 22  -- ═══════════════════════════════════════════════════════════════════════════════
 23  
 24  /-- Parse result: either failure or (value, remaining bytes) -/
 25  inductive ParseResult (α : Type) where
 26    | ok : α → Bytes → ParseResult α
 27    | fail : ParseResult α
 28    deriving DecidableEq
 29  
 30  namespace ParseResult
 31  
 32  def map (f : α → β) : ParseResult α → ParseResult β
 33    | ok a rest => ok (f a) rest
 34    | fail => fail
 35  
 36  def bind (r : ParseResult α) (f : α → Bytes → ParseResult β) : ParseResult β :=
 37    match r with
 38    | ok a rest => f a rest
 39    | fail => fail
 40  
 41  -- Lemmas about ParseResult
 42  @[simp] theorem map_ok (f : α → β) (a : α) (rest : Bytes) : 
 43      map f (ok a rest) = ok (f a) rest := rfl
 44  
 45  @[simp] theorem map_fail (f : α → β) : 
 46      map f (fail : ParseResult α) = fail := rfl
 47  
 48  @[simp] theorem bind_ok (a : α) (rest : Bytes) (f : α → Bytes → ParseResult β) :
 49      bind (ok a rest) f = f a rest := rfl
 50  
 51  @[simp] theorem bind_fail (f : α → Bytes → ParseResult β) :
 52      bind (fail : ParseResult α) f = fail := rfl
 53  
 54  end ParseResult
 55  
 56  -- ═══════════════════════════════════════════════════════════════════════════════
 57  -- BOX - The verified codec structure
 58  -- ═══════════════════════════════════════════════════════════════════════════════
 59  
 60  /-- 
 61  A Box is a verified bidirectional codec.
 62  
 63  Key properties:
 64  - `roundtrip`: parsing what you serialized gives back the original value
 65  - `consumption`: parsing consumes exactly the serialized bytes, leaving extra untouched
 66  -/
 67  structure Box (α : Type) where
 68    parse : Bytes → ParseResult α
 69    serialize : α → Bytes
 70    roundtrip : ∀ a, parse (serialize a) = ParseResult.ok a ByteArray.empty
 71    consumption : ∀ a extra, parse (serialize a ++ extra) = ParseResult.ok a extra
 72  
 73  -- ═══════════════════════════════════════════════════════════════════════════════
 74  -- UNIT BOX - The trivial case (zero bytes)
 75  -- ═══════════════════════════════════════════════════════════════════════════════
 76  
 77  /-- The unit box: encodes Unit as zero bytes -/
 78  def unit : Box Unit where
 79    parse bs := .ok () bs
 80    serialize _ := ByteArray.empty
 81    roundtrip := by
 82      intro a
 83      -- parse (serialize ()) = parse ByteArray.empty = .ok () ByteArray.empty
 84      rfl
 85    consumption := by
 86      intro a extra
 87      -- parse (serialize () ++ extra) = parse (ByteArray.empty ++ extra) = parse extra = .ok () extra
 88      simp [ByteArray.empty_append]
 89  
 90  -- ═══════════════════════════════════════════════════════════════════════════════
 91  -- U8 BOX - Single byte
 92  -- ═══════════════════════════════════════════════════════════════════════════════
 93  
 94  /-- Parse a single byte -/
 95  def parseU8 (bs : Bytes) : ParseResult UInt8 :=
 96    if h : bs.size > 0 then
 97      .ok bs[0] (bs.extract 1 bs.size)
 98    else 
 99      .fail
100  
101  /-- Serialize a single byte -/
102  def serializeU8 (v : UInt8) : Bytes := 
103    [v].toByteArray
104  
105  -- Key lemma: size of a singleton ByteArray
106  theorem singleton_size (v : UInt8) : [v].toByteArray.size = 1 := by
107    simp [List.size_toByteArray]
108  
109  -- Key lemma: getting element 0 of singleton
110  theorem singleton_getElem (v : UInt8) : [v].toByteArray[0]'(by simp) = v := by
111    simp
112  
113  -- Key lemma: extracting from position 1 of a size-1 array gives empty
114  theorem singleton_extract_tail (v : UInt8) : 
115      [v].toByteArray.extract 1 [v].toByteArray.size = ByteArray.empty := by
116    simp
117  
118  /-- Single byte box with verified roundtrip -/
119  def u8 : Box UInt8 where
120    parse := parseU8
121    serialize := serializeU8
122    roundtrip := by
123      intro v
124      simp only [parseU8, serializeU8]
125      -- Need to show: if h : [v].toByteArray.size > 0 then ...
126      have hsize : [v].toByteArray.size > 0 := by simp
127      simp only [hsize, ↓reduceDIte]
128      -- Show equality by showing both components match
129      have hval : [v].toByteArray[0]'hsize = v := by simp
130      have hrest : [v].toByteArray.extract 1 [v].toByteArray.size = ByteArray.empty := by
131        simp
132      simp only [hval, hrest]
133    consumption := by
134      intro v extra
135      simp only [parseU8, serializeU8]
136      -- Size of [v].toByteArray ++ extra > 0
137      have hsize : ([v].toByteArray ++ extra).size > 0 := by
138        simp only [ByteArray.size_append, List.size_toByteArray, List.length_cons, List.length_nil]
139        omega
140      simp only [hsize, ↓reduceDIte]
141      -- Show value component: ([v].toByteArray ++ extra)[0] = v
142      have hval : ([v].toByteArray ++ extra)[0]'hsize = v := by
143        have h0 : (0 : Nat) < [v].toByteArray.size := by simp
144        rw [ByteArray.getElem_append_left h0]
145        simp
146      -- Show remaining component: extract 1 (size) = extra
147      have hrest : ([v].toByteArray ++ extra).extract 1 ([v].toByteArray ++ extra).size = extra := by
148        simp only [ByteArray.size_append]
149        -- extract 1 (1 + extra.size) from ([v].toByteArray ++ extra)
150        -- [v].toByteArray has size 1, so extract from position 1 gives us the second part
151        rw [ByteArray.extract_append_eq_right (by simp : 1 = [v].toByteArray.size)]
152        simp
153      simp only [hval, hrest]
154  
155  -- ═══════════════════════════════════════════════════════════════════════════════
156  -- SEQUENCE COMBINATOR
157  -- ═══════════════════════════════════════════════════════════════════════════════
158  
159  /-- 
160  Sequence two boxes: parse A then B, serialize A then B.
161  If both A and B have verified roundtrip, so does (A, B).
162  -/
163  def seq (boxA : Box α) (boxB : Box β) : Box (α × β) where
164    parse bs := 
165      boxA.parse bs |>.bind fun a rest =>
166        boxB.parse rest |>.map fun b => (a, b)
167    serialize ab := boxA.serialize ab.1 ++ boxB.serialize ab.2
168    roundtrip := by
169      intro ⟨a, b⟩
170      simp only [ParseResult.bind, ParseResult.map]
171      -- parse (serA a ++ serB b) 
172      -- = parseA (serA a ++ serB b) >>= ...
173      -- By consumption of A: parseA (serA a ++ serB b) = ok a (serB b)
174      rw [boxA.consumption a (boxB.serialize b)]
175      -- Simplify the match
176      simp only []
177      -- Now: parseB (serB b) = ok b empty
178      rw [boxB.roundtrip b]
179    consumption := by
180      intro ⟨a, b⟩ extra
181      simp only [ParseResult.bind, ParseResult.map]
182      -- parse ((serA a ++ serB b) ++ extra)
183      -- = parseA ((serA a ++ serB b) ++ extra) >>= ...
184      -- Rewrite: (serA a ++ serB b) ++ extra = serA a ++ (serB b ++ extra)
185      rw [ByteArray.append_assoc]
186      -- By consumption of A: parseA (serA a ++ (serB b ++ extra)) = ok a (serB b ++ extra)
187      rw [boxA.consumption a (boxB.serialize b ++ extra)]
188      -- Simplify the match
189      simp only []
190      -- By consumption of B: parseB (serB b ++ extra) = ok b extra
191      rw [boxB.consumption b extra]
192  
193  -- ═══════════════════════════════════════════════════════════════════════════════
194  -- ISO COMBINATOR (mapping through isomorphism)
195  -- ═══════════════════════════════════════════════════════════════════════════════
196  
197  /-- 
198  Map a box through an isomorphism.
199  Given Box α and bijection f : α ↔ β, get Box β.
200  -/
201  def isoBox (box : Box α) (f : α → β) (g : β → α) 
202      (fg : ∀ b, f (g b) = b) (_gf : ∀ a, g (f a) = a) : Box β where
203    parse bs := box.parse bs |>.map f
204    serialize b := box.serialize (g b)
205    roundtrip := by
206      intro b
207      simp only [ParseResult.map]
208      -- parse (serialize (g b)) = ok (g b) empty  by box.roundtrip
209      rw [box.roundtrip (g b)]
210      -- Simplify the match, then f (g b) = b by fg
211      simp only [fg]
212    consumption := by
213      intro b extra
214      simp only [ParseResult.map]
215      rw [box.consumption (g b) extra]
216      simp only [fg]
217  
218  -- ═══════════════════════════════════════════════════════════════════════════════
219  -- U64LE BOX - 64-bit little-endian using BitVec
220  -- ═══════════════════════════════════════════════════════════════════════════════
221  
222  /-- Extract byte i (0-indexed from LSB) from a 64-bit value using setWidth and shift -/
223  def extractByte (v : BitVec 64) (i : Nat) (_hi : i < 8 := by omega) : BitVec 8 :=
224    (v >>> (i * 8)).setWidth 8
225  
226  /-- Combine 8 bytes into a 64-bit value (little-endian) -/
227  def combineBytes (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) : BitVec 64 :=
228    -- Little-endian: b0 is LSB, b7 is MSB
229    -- We build from MSB to LSB using append
230    let v7 : BitVec 64 := b7.setWidth 64 <<< 56
231    let v6 : BitVec 64 := b6.setWidth 64 <<< 48
232    let v5 : BitVec 64 := b5.setWidth 64 <<< 40
233    let v4 : BitVec 64 := b4.setWidth 64 <<< 32
234    let v3 : BitVec 64 := b3.setWidth 64 <<< 24
235    let v2 : BitVec 64 := b2.setWidth 64 <<< 16
236    let v1 : BitVec 64 := b1.setWidth 64 <<< 8
237    let v0 : BitVec 64 := b0.setWidth 64
238    v7 ||| v6 ||| v5 ||| v4 ||| v3 ||| v2 ||| v1 ||| v0
239  
240  /-- Parse 8 bytes as little-endian u64 -/
241  def parseU64le (bs : Bytes) : ParseResult (BitVec 64) :=
242    if h : bs.size ≥ 8 then
243      have h0 : 0 < bs.size := by omega
244      have h1 : 1 < bs.size := by omega
245      have h2 : 2 < bs.size := by omega
246      have h3 : 3 < bs.size := by omega
247      have h4 : 4 < bs.size := by omega
248      have h5 : 5 < bs.size := by omega
249      have h6 : 6 < bs.size := by omega
250      have h7 : 7 < bs.size := by omega
251      let b0 : BitVec 8 := BitVec.ofNat 8 (bs[0]).toNat
252      let b1 : BitVec 8 := BitVec.ofNat 8 (bs[1]).toNat
253      let b2 : BitVec 8 := BitVec.ofNat 8 (bs[2]).toNat
254      let b3 : BitVec 8 := BitVec.ofNat 8 (bs[3]).toNat
255      let b4 : BitVec 8 := BitVec.ofNat 8 (bs[4]).toNat
256      let b5 : BitVec 8 := BitVec.ofNat 8 (bs[5]).toNat
257      let b6 : BitVec 8 := BitVec.ofNat 8 (bs[6]).toNat
258      let b7 : BitVec 8 := BitVec.ofNat 8 (bs[7]).toNat
259      .ok (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) (bs.extract 8 bs.size)
260    else 
261      .fail
262  
263  /-- Serialize u64 as 8 bytes little-endian -/
264  def serializeU64le (v : BitVec 64) : Bytes :=
265    let b0 := (extractByte v 0).toNat.toUInt8
266    let b1 := (extractByte v 1).toNat.toUInt8
267    let b2 := (extractByte v 2).toNat.toUInt8
268    let b3 := (extractByte v 3).toNat.toUInt8
269    let b4 := (extractByte v 4).toNat.toUInt8
270    let b5 := (extractByte v 5).toNat.toUInt8
271    let b6 := (extractByte v 6).toNat.toUInt8
272    let b7 := (extractByte v 7).toNat.toUInt8
273    [b0, b1, b2, b3, b4, b5, b6, b7].toByteArray
274  
275  -- Key lemma: extractByte extracts the correct byte
276  theorem extractByte_toNat (v : BitVec 64) (i : Nat) (hi : i < 8) :
277      (extractByte v i hi).toNat = (v.toNat >>> (i * 8)) % 256 := by
278    simp only [extractByte, BitVec.toNat_setWidth, BitVec.toNat_ushiftRight]
279  
280  -- Key lemma: size of serialized u64
281  theorem serializeU64le_size (v : BitVec 64) : (serializeU64le v).size = 8 := by
282    simp [serializeU64le, List.size_toByteArray]
283  
284  -- Helper: List of 8 elements has length 8
285  theorem list8_length {α : Type} (a b c d e f g h : α) : 
286      [a, b, c, d, e, f, g, h].length = 8 := rfl
287  
288  -- The key insight: combineBytes and extractByte are inverses.
289  -- This is the fundamental theorem we need for u64le roundtrip.
290  
291  -- Lemma: combining bytes then extracting gives back the original bytes
292  -- This follows from the structure of little-endian encoding
293  -- We prove each case using bv_decide
294  
295  theorem combineBytes_extractByte_0 (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) :
296      extractByte (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) 0 = b0 := by
297    simp only [extractByte, combineBytes]
298    bv_decide
299  
300  theorem combineBytes_extractByte_1 (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) :
301      extractByte (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) 1 = b1 := by
302    simp only [extractByte, combineBytes]
303    bv_decide
304  
305  theorem combineBytes_extractByte_2 (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) :
306      extractByte (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) 2 = b2 := by
307    simp only [extractByte, combineBytes]
308    bv_decide
309  
310  theorem combineBytes_extractByte_3 (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) :
311      extractByte (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) 3 = b3 := by
312    simp only [extractByte, combineBytes]
313    bv_decide
314  
315  theorem combineBytes_extractByte_4 (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) :
316      extractByte (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) 4 = b4 := by
317    simp only [extractByte, combineBytes]
318    bv_decide
319  
320  theorem combineBytes_extractByte_5 (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) :
321      extractByte (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) 5 = b5 := by
322    simp only [extractByte, combineBytes]
323    bv_decide
324  
325  theorem combineBytes_extractByte_6 (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) :
326      extractByte (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) 6 = b6 := by
327    simp only [extractByte, combineBytes]
328    bv_decide
329  
330  theorem combineBytes_extractByte_7 (b0 b1 b2 b3 b4 b5 b6 b7 : BitVec 8) :
331      extractByte (combineBytes b0 b1 b2 b3 b4 b5 b6 b7) 7 = b7 := by
332    simp only [extractByte, combineBytes]
333    bv_decide
334  
335  -- The converse: extracting then combining reconstructs the original value
336  -- This is the key theorem for the serialize->parse direction
337  theorem extractBytes_combineBytes (v : BitVec 64) :
338      combineBytes (extractByte v 0) (extractByte v 1) (extractByte v 2) (extractByte v 3)
339                   (extractByte v 4) (extractByte v 5) (extractByte v 6) (extractByte v 7) = v := by
340    simp only [extractByte, combineBytes]
341    bv_decide
342  
343  -- ═══════════════════════════════════════════════════════════════════════════════
344  -- U64LE BOX (complete with proofs)
345  -- ═══════════════════════════════════════════════════════════════════════════════
346  
347  -- Helper: UInt8 ↔ BitVec 8 roundtrip
348  theorem uint8_IsLt (x : UInt8) : x.toNat < 256 := by
349    unfold UInt8.toNat
350    exact x.toBitVec.isLt
351  
352  theorem bitvec8_IsLt (b : BitVec 8) : b.toNat < 256 := b.isLt
353  
354  -- Key: BitVec.ofNat 8 (x.toNat) where x : UInt8 preserves the value
355  theorem ofNat8_uint8_toNat (x : UInt8) : 
356      (BitVec.ofNat 8 x.toNat).toNat = x.toNat := by
357    simp only [BitVec.toNat_ofNat]
358    exact Nat.mod_eq_of_lt (uint8_IsLt x)
359  
360  -- Key: extractByte then toUInt8 then back to BitVec = extractByte
361  theorem extractByte_uint8_roundtrip (v : BitVec 64) (i : Nat) (hi : i < 8) :
362      BitVec.ofNat 8 ((extractByte v i hi).toNat.toUInt8.toNat) = extractByte v i hi := by
363    apply BitVec.eq_of_toNat_eq
364    simp only [BitVec.toNat_ofNat]
365    have hlt : (extractByte v i hi).toNat < 256 := bitvec8_IsLt _
366    -- toUInt8.toNat of a value < 256 is identity
367    have h1 : (extractByte v i hi).toNat.toUInt8.toNat = (extractByte v i hi).toNat := by
368      unfold Nat.toUInt8 UInt8.ofNat UInt8.toNat
369      simp only [BitVec.toNat_ofNat, Nat.mod_eq_of_lt hlt]
370    rw [h1]
371    exact Nat.mod_eq_of_lt hlt
372  
373  -- Serialize then get byte i gives extractByte v i
374  theorem serializeU64le_getElem (v : BitVec 64) (i : Nat) (hi : i < 8) :
375      (serializeU64le v)[i]'(by simp [serializeU64le_size]; exact hi) = 
376      (extractByte v i hi).toNat.toUInt8 := by
377    simp only [serializeU64le]
378    -- The serialized bytes are [b0, b1, ..., b7].toByteArray
379    -- We need to show that index i gives the i-th element
380    match i with
381    | 0 => simp
382    | 1 => simp
383    | 2 => simp
384    | 3 => simp
385    | 4 => simp
386    | 5 => simp
387    | 6 => simp
388    | 7 => simp
389  
390  -- Combined lemma: BitVec.ofNat 8 (serializeU64le v)[i].toNat = extractByte v i _
391  -- This allows simp to work instead of conv
392  -- We prove individual instances for each index to make simp work
393  @[simp] theorem serializeU64le_bitvec_0 (v : BitVec 64) (h : 0 < (serializeU64le v).size := by simp) :
394      BitVec.ofNat 8 (serializeU64le v)[0].toNat = extractByte v 0 (by omega) := by
395    rw [serializeU64le_getElem, extractByte_uint8_roundtrip]
396  
397  @[simp] theorem serializeU64le_bitvec_1 (v : BitVec 64) (h : 1 < (serializeU64le v).size := by simp) :
398      BitVec.ofNat 8 (serializeU64le v)[1].toNat = extractByte v 1 (by omega) := by
399    rw [serializeU64le_getElem, extractByte_uint8_roundtrip]
400  
401  @[simp] theorem serializeU64le_bitvec_2 (v : BitVec 64) (h : 2 < (serializeU64le v).size := by simp) :
402      BitVec.ofNat 8 (serializeU64le v)[2].toNat = extractByte v 2 (by omega) := by
403    rw [serializeU64le_getElem, extractByte_uint8_roundtrip]
404  
405  @[simp] theorem serializeU64le_bitvec_3 (v : BitVec 64) (h : 3 < (serializeU64le v).size := by simp) :
406      BitVec.ofNat 8 (serializeU64le v)[3].toNat = extractByte v 3 (by omega) := by
407    rw [serializeU64le_getElem, extractByte_uint8_roundtrip]
408  
409  @[simp] theorem serializeU64le_bitvec_4 (v : BitVec 64) (h : 4 < (serializeU64le v).size := by simp) :
410      BitVec.ofNat 8 (serializeU64le v)[4].toNat = extractByte v 4 (by omega) := by
411    rw [serializeU64le_getElem, extractByte_uint8_roundtrip]
412  
413  @[simp] theorem serializeU64le_bitvec_5 (v : BitVec 64) (h : 5 < (serializeU64le v).size := by simp) :
414      BitVec.ofNat 8 (serializeU64le v)[5].toNat = extractByte v 5 (by omega) := by
415    rw [serializeU64le_getElem, extractByte_uint8_roundtrip]
416  
417  @[simp] theorem serializeU64le_bitvec_6 (v : BitVec 64) (h : 6 < (serializeU64le v).size := by simp) :
418      BitVec.ofNat 8 (serializeU64le v)[6].toNat = extractByte v 6 (by omega) := by
419    rw [serializeU64le_getElem, extractByte_uint8_roundtrip]
420  
421  @[simp] theorem serializeU64le_bitvec_7 (v : BitVec 64) (h : 7 < (serializeU64le v).size := by simp) :
422      BitVec.ofNat 8 (serializeU64le v)[7].toNat = extractByte v 7 (by omega) := by
423    rw [serializeU64le_getElem, extractByte_uint8_roundtrip]
424  
425  -- The big theorem: parsing serialized bytes reconstructs the original value
426  theorem parseU64le_serializeU64le (v : BitVec 64) :
427      parseU64le (serializeU64le v) = ParseResult.ok v ByteArray.empty := by
428    simp only [parseU64le]
429    have hsize : (serializeU64le v).size ≥ 8 := by simp [serializeU64le_size]
430    simp only [hsize, ↓reduceDIte]
431    -- Extract goes from 8 to 8, giving empty
432    have hextract : (serializeU64le v).extract 8 (serializeU64le v).size = ByteArray.empty := by
433      simp [serializeU64le_size]
434    -- Now show the combined value equals v and rest is empty
435    -- Goal: ParseResult.ok (combineBytes ...) (extract ...) = ParseResult.ok v empty
436    -- Use congr to split into showing combineBytes ... = v and extract ... = empty
437    -- hextract proves the second component, so we use simp with it first
438    simp only [hextract]
439    -- Now just need to show combineBytes ... = v
440    congr 1
441    simp only [serializeU64le_bitvec_0, serializeU64le_bitvec_1, serializeU64le_bitvec_2,
442               serializeU64le_bitvec_3, serializeU64le_bitvec_4, serializeU64le_bitvec_5,
443               serializeU64le_bitvec_6, serializeU64le_bitvec_7]
444    exact extractBytes_combineBytes v
445  
446  -- Consumption: parsing serialized bytes ++ extra leaves extra
447  theorem parseU64le_serializeU64le_append (v : BitVec 64) (extra : Bytes) :
448      parseU64le (serializeU64le v ++ extra) = ParseResult.ok v extra := by
449    simp only [parseU64le]
450    have hsize : (serializeU64le v ++ extra).size ≥ 8 := by 
451      simp [ByteArray.size_append, serializeU64le_size]
452    simp only [hsize, ↓reduceDIte]
453    -- Extract from position 8 gives extra
454    have hextract : (serializeU64le v ++ extra).extract 8 (serializeU64le v ++ extra).size = extra := by
455      have hi : 8 = (serializeU64le v).size := by simp [serializeU64le_size]
456      have hj : (serializeU64le v ++ extra).size = (serializeU64le v).size + extra.size := by
457        simp [ByteArray.size_append]
458      rw [hj, ByteArray.extract_append_eq_right hi rfl]
459    -- Use hextract to simplify the second component
460    simp only [hextract]
461    -- Now just need to show combineBytes ... = v
462    congr 1
463    -- Same as above but with append
464    have h0 : (0 : Nat) < (serializeU64le v).size := by simp [serializeU64le_size]
465    have h1 : (1 : Nat) < (serializeU64le v).size := by simp [serializeU64le_size]
466    have h2 : (2 : Nat) < (serializeU64le v).size := by simp [serializeU64le_size]
467    have h3 : (3 : Nat) < (serializeU64le v).size := by simp [serializeU64le_size]
468    have h4 : (4 : Nat) < (serializeU64le v).size := by simp [serializeU64le_size]
469    have h5 : (5 : Nat) < (serializeU64le v).size := by simp [serializeU64le_size]
470    have h6 : (6 : Nat) < (serializeU64le v).size := by simp [serializeU64le_size]
471    have h7 : (7 : Nat) < (serializeU64le v).size := by simp [serializeU64le_size]
472    simp only [ByteArray.getElem_append_left h0, ByteArray.getElem_append_left h1,
473               ByteArray.getElem_append_left h2, ByteArray.getElem_append_left h3,
474               ByteArray.getElem_append_left h4, ByteArray.getElem_append_left h5,
475               ByteArray.getElem_append_left h6, ByteArray.getElem_append_left h7]
476    simp only [serializeU64le_bitvec_0, serializeU64le_bitvec_1, serializeU64le_bitvec_2,
477               serializeU64le_bitvec_3, serializeU64le_bitvec_4, serializeU64le_bitvec_5,
478               serializeU64le_bitvec_6, serializeU64le_bitvec_7]
479    exact extractBytes_combineBytes v
480  
481  /-- The verified u64le box -/
482  def u64leBitVec : Box (BitVec 64) where
483    parse := parseU64le
484    serialize := serializeU64le
485    roundtrip := parseU64le_serializeU64le
486    consumption := parseU64le_serializeU64le_append
487  
488  -- ═══════════════════════════════════════════════════════════════════════════════
489  -- U32LE BOX (32-bit little-endian, for Protobuf fixed32)
490  -- ═══════════════════════════════════════════════════════════════════════════════
491  
492  /-- Extract byte i from a 32-bit value (little-endian) -/
493  def extractByte32 (v : BitVec 32) (i : Nat) (_ : i < 4 := by omega) : BitVec 8 :=
494    (v >>> (i * 8)).setWidth 8
495  
496  /-- Combine 4 bytes into a 32-bit value (little-endian) -/
497  def combineBytes32 (b0 b1 b2 b3 : BitVec 8) : BitVec 32 :=
498    b0.setWidth 32 |||
499    (b1.setWidth 32 <<< 8) |||
500    (b2.setWidth 32 <<< 16) |||
501    (b3.setWidth 32 <<< 24)
502  
503  /-- Serialize a 32-bit value to 4 bytes -/
504  def serializeU32le (v : BitVec 32) : Bytes :=
505    let b0 : UInt8 := (extractByte32 v 0).toNat.toUInt8
506    let b1 : UInt8 := (extractByte32 v 1).toNat.toUInt8
507    let b2 : UInt8 := (extractByte32 v 2).toNat.toUInt8
508    let b3 : UInt8 := (extractByte32 v 3).toNat.toUInt8
509    [b0, b1, b2, b3].toByteArray
510  
511  /-- Parse 4 bytes as a 32-bit value -/
512  def parseU32le (bs : Bytes) : ParseResult (BitVec 32) :=
513    if h : bs.size ≥ 4 then
514      let b0 := BitVec.ofNat 8 bs[0].toNat
515      let b1 := BitVec.ofNat 8 bs[1].toNat
516      let b2 := BitVec.ofNat 8 bs[2].toNat
517      let b3 := BitVec.ofNat 8 bs[3].toNat
518      .ok (combineBytes32 b0 b1 b2 b3) (bs.extract 4 bs.size)
519    else .fail
520  
521  @[simp] theorem serializeU32le_size (v : BitVec 32) : (serializeU32le v).size = 4 := by
522    simp [serializeU32le, List.size_toByteArray]
523  
524  -- Byte extraction lemmas (bv_decide)
525  theorem combineBytes32_extractByte_0 (b0 b1 b2 b3 : BitVec 8) :
526      extractByte32 (combineBytes32 b0 b1 b2 b3) 0 = b0 := by
527    simp only [extractByte32, combineBytes32]
528    bv_decide
529  
530  theorem combineBytes32_extractByte_1 (b0 b1 b2 b3 : BitVec 8) :
531      extractByte32 (combineBytes32 b0 b1 b2 b3) 1 = b1 := by
532    simp only [extractByte32, combineBytes32]
533    bv_decide
534  
535  theorem combineBytes32_extractByte_2 (b0 b1 b2 b3 : BitVec 8) :
536      extractByte32 (combineBytes32 b0 b1 b2 b3) 2 = b2 := by
537    simp only [extractByte32, combineBytes32]
538    bv_decide
539  
540  theorem combineBytes32_extractByte_3 (b0 b1 b2 b3 : BitVec 8) :
541      extractByte32 (combineBytes32 b0 b1 b2 b3) 3 = b3 := by
542    simp only [extractByte32, combineBytes32]
543    bv_decide
544  
545  -- The converse: extracting all bytes and combining gives back original
546  theorem extractBytes32_combineBytes (v : BitVec 32) :
547      combineBytes32 (extractByte32 v 0) (extractByte32 v 1) 
548                     (extractByte32 v 2) (extractByte32 v 3) = v := by
549    simp only [extractByte32, combineBytes32]
550    bv_decide
551  
552  -- UInt8 roundtrip for 32-bit
553  theorem extractByte32_uint8_roundtrip (v : BitVec 32) (i : Nat) (hi : i < 4) :
554      BitVec.ofNat 8 ((extractByte32 v i hi).toNat.toUInt8.toNat) = extractByte32 v i hi := by
555    apply BitVec.eq_of_toNat_eq
556    simp only [BitVec.toNat_ofNat]
557    have hlt : (extractByte32 v i hi).toNat < 256 := bitvec8_IsLt _
558    have h1 : (extractByte32 v i hi).toNat.toUInt8.toNat = (extractByte32 v i hi).toNat := by
559      unfold Nat.toUInt8 UInt8.ofNat UInt8.toNat
560      simp only [BitVec.toNat_ofNat, Nat.mod_eq_of_lt hlt]
561    rw [h1]
562    exact Nat.mod_eq_of_lt hlt
563  
564  theorem serializeU32le_getElem (v : BitVec 32) (i : Nat) (hi : i < 4) :
565      (serializeU32le v)[i]'(by simp [serializeU32le_size]; exact hi) = 
566      (extractByte32 v i hi).toNat.toUInt8 := by
567    simp only [serializeU32le]
568    match i with
569    | 0 => simp
570    | 1 => simp
571    | 2 => simp
572    | 3 => simp
573  
574  -- Combined simp lemmas for the proof
575  @[simp] theorem serializeU32le_bitvec_0 (v : BitVec 32) (h : 0 < (serializeU32le v).size := by simp) :
576      BitVec.ofNat 8 (serializeU32le v)[0].toNat = extractByte32 v 0 (by omega) := by
577    rw [serializeU32le_getElem, extractByte32_uint8_roundtrip]
578  
579  @[simp] theorem serializeU32le_bitvec_1 (v : BitVec 32) (h : 1 < (serializeU32le v).size := by simp) :
580      BitVec.ofNat 8 (serializeU32le v)[1].toNat = extractByte32 v 1 (by omega) := by
581    rw [serializeU32le_getElem, extractByte32_uint8_roundtrip]
582  
583  @[simp] theorem serializeU32le_bitvec_2 (v : BitVec 32) (h : 2 < (serializeU32le v).size := by simp) :
584      BitVec.ofNat 8 (serializeU32le v)[2].toNat = extractByte32 v 2 (by omega) := by
585    rw [serializeU32le_getElem, extractByte32_uint8_roundtrip]
586  
587  @[simp] theorem serializeU32le_bitvec_3 (v : BitVec 32) (h : 3 < (serializeU32le v).size := by simp) :
588      BitVec.ofNat 8 (serializeU32le v)[3].toNat = extractByte32 v 3 (by omega) := by
589    rw [serializeU32le_getElem, extractByte32_uint8_roundtrip]
590  
591  -- Main roundtrip theorem
592  theorem parseU32le_serializeU32le (v : BitVec 32) :
593      parseU32le (serializeU32le v) = ParseResult.ok v ByteArray.empty := by
594    simp only [parseU32le]
595    have hsize : (serializeU32le v).size ≥ 4 := by simp [serializeU32le_size]
596    simp only [hsize, ↓reduceDIte]
597    have hextract : (serializeU32le v).extract 4 (serializeU32le v).size = ByteArray.empty := by
598      simp [serializeU32le_size]
599    simp only [hextract]
600    congr 1
601    simp only [serializeU32le_bitvec_0, serializeU32le_bitvec_1, 
602               serializeU32le_bitvec_2, serializeU32le_bitvec_3]
603    exact extractBytes32_combineBytes v
604  
605  -- Consumption theorem
606  theorem parseU32le_serializeU32le_append (v : BitVec 32) (extra : Bytes) :
607      parseU32le (serializeU32le v ++ extra) = ParseResult.ok v extra := by
608    simp only [parseU32le]
609    have hsize : (serializeU32le v ++ extra).size ≥ 4 := by 
610      simp [ByteArray.size_append, serializeU32le_size]
611    simp only [hsize, ↓reduceDIte]
612    have hextract : (serializeU32le v ++ extra).extract 4 (serializeU32le v ++ extra).size = extra := by
613      have hi : 4 = (serializeU32le v).size := by simp [serializeU32le_size]
614      have hj : (serializeU32le v ++ extra).size = (serializeU32le v).size + extra.size := by
615        simp [ByteArray.size_append]
616      rw [hj, ByteArray.extract_append_eq_right hi rfl]
617    simp only [hextract]
618    congr 1
619    have h0 : (0 : Nat) < (serializeU32le v).size := by simp [serializeU32le_size]
620    have h1 : (1 : Nat) < (serializeU32le v).size := by simp [serializeU32le_size]
621    have h2 : (2 : Nat) < (serializeU32le v).size := by simp [serializeU32le_size]
622    have h3 : (3 : Nat) < (serializeU32le v).size := by simp [serializeU32le_size]
623    simp only [ByteArray.getElem_append_left h0, ByteArray.getElem_append_left h1,
624               ByteArray.getElem_append_left h2, ByteArray.getElem_append_left h3]
625    simp only [serializeU32le_bitvec_0, serializeU32le_bitvec_1,
626               serializeU32le_bitvec_2, serializeU32le_bitvec_3]
627    exact extractBytes32_combineBytes v
628  
629  /-- The verified u32le box -/
630  def u32leBitVec : Box (BitVec 32) where
631    parse := parseU32le
632    serialize := serializeU32le
633    roundtrip := parseU32le_serializeU32le
634    consumption := parseU32le_serializeU32le_append
635  
636  -- ═══════════════════════════════════════════════════════════════════════════════
637  -- DERIVED BOXES (built from primitives)
638  -- ═══════════════════════════════════════════════════════════════════════════════
639  
640  /-- Two bytes (u8, u8) -/
641  def u8pair : Box (UInt8 × UInt8) := seq u8 u8
642  
643  /-- Three bytes -/
644  def u8triple : Box ((UInt8 × UInt8) × UInt8) := seq u8pair u8
645  
646  /-- Four bytes -/
647  def u8quad : Box (((UInt8 × UInt8) × UInt8) × UInt8) := seq u8triple u8
648  
649  -- ═══════════════════════════════════════════════════════════════════════════════
650  -- EXAMPLE: Using isoBox to create a wrapped type
651  -- ═══════════════════════════════════════════════════════════════════════════════
652  
653  /-- A wrapper type for demonstration -/
654  structure WrappedByte where
655    val : UInt8
656    deriving DecidableEq
657  
658  /-- Box for WrappedByte -/
659  def wrappedByte : Box WrappedByte := 
660    isoBox u8 
661      WrappedByte.mk 
662      WrappedByte.val
663      (fun _ => rfl)
664      (fun ⟨_⟩ => rfl)
665  
666  -- ═══════════════════════════════════════════════════════════════════════════════
667  -- STATISTICS
668  -- ═══════════════════════════════════════════════════════════════════════════════
669  
670  /-!
671  ## Verification Status
672  
673  ### Fully Verified Boxes (0 sorry):
674  
675  | Box/Combinator | Roundtrip | Consumption | Status |
676  |----------------|-----------|-------------|--------|
677  | unit           | ✓ proven  | ✓ proven    | DONE   |
678  | u8             | ✓ proven  | ✓ proven    | DONE   |
679  | u32leBitVec    | ✓ proven  | ✓ proven    | DONE   |
680  | u64leBitVec    | ✓ proven  | ✓ proven    | DONE   |
681  | seq            | ✓ proven  | ✓ proven    | DONE   |
682  | isoBox         | ✓ proven  | ✓ proven    | DONE   |
683  
684  ### BitVec Lemmas (bv_decide, 0 sorry):
685  
686  | Lemma | What's Proven |
687  |-------|---------------|
688  | combineBytes_extractByte_0..7 | extractByte(combine(b0..b7), i) = bi |
689  | combineBytes32_extractByte_0..3 | extractByte32(combine32(b0..b3), i) = bi |
690  | extractBytes_combineBytes | combine(extractByte(v, 0..7)) = v |
691  | extractBytes32_combineBytes | combine32(extractByte32(v, 0..3)) = v |
692  | parseU64le_serializeU64le | parse(serialize(v)) = ok v empty |
693  | parseU32le_serializeU32le | parse(serialize(v)) = ok v empty |
694  
695  ### Derived (compositionally verified):
696  
697  | Box | Built From |
698  |-----|------------|
699  | u8pair | seq u8 u8 |
700  | u8triple | seq u8pair u8 |  
701  | u8quad | seq u8triple u8 |
702  | wrappedByte | isoBox u8 |
703  | u64le (UInt64) | isoBox u64leBitVec (via Basic.lean) |
704  | u32le (UInt32) | isoBox u32leBitVec (via Basic.lean) |
705  | bool64 | isoBox u64leBitVec (via Basic.lean) |
706  
707  **Total: 6 primitive boxes, 50+ theorems, 0 sorry in Proofs.lean**
708  **Compositionally: infinite verified boxes constructible**
709  -/
710  
711  end Cornell.Proofs