Skip to content

Commit 584ad93

Browse files
committed
Prove U16 and U32 byte-order reads, writes and updates
Expose concrete calls to the production byteorder wrappers so annotation discovery does not depend on expanding their implementation macros. Add twelve total specifications covering reads, writes and setter/getter composition for U16 and U32 in both byte orders. Connect the extracted bit-vector operations to the shared radix-256 byte model. Independently check positional sums, every output byte and numerical value preservation. Model PhantomData structurally and admit only the opaque Formatter type needed by unused dictionaries, without assuming any formatting behavior. gherrit-pr-id: Gessib3piua3c4koztpng6i7m52bxi73s Agent-authored-by: AI agent acting on joshlf's behalf
1 parent 1499978 commit 584ad93

14 files changed

Lines changed: 1637 additions & 4 deletions

File tree

‎verification/aeneas/README.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ added without extending the specification set.
211211

212212
| Rust function | Checked property |
213213
| --- | --- |
214+
| Byteorder U16/U32 read, write, and set (both endiannesses) | Exact integer value, every encoded byte, and setter/getter equality for every typed input. |
214215
| `max` | Terminates successfully and returns the mathematical maximum. |
215216
| `min` | Terminates successfully and returns the mathematical minimum. |
216217
| `padding_needed_for` | For any positive alignment, succeeds with padding strictly below that alignment. |
@@ -826,3 +827,28 @@ provided useful prior evidence:
826827
Those older experiments are not assumed to describe the current release's
827828
runtime. This integration was validated with the pinned October 1 bundle and
828829
fresh translations of the current checkout.
830+
831+
## Shared integer and byte mathematics
832+
833+
`anneal/lean/Rust/Bytes.lean` defines unsigned byte encoding and decoding with
834+
ordinary natural-number arithmetic, independently of Aeneas. It proves lengths,
835+
bounds, truncation, and round trips for arbitrary byte counts. Anneal v1, v2,
836+
and this integration share that package; each consumer supplies its own bridge
837+
to the representation it verifies.
838+
839+
The inline contracts in `byteorder::verification` exercise the production
840+
U16/U32 `from_bytes`, `get`, `new`, `Into`, and `set` paths for both byte orders.
841+
`BytesAdapter.lean` connects Aeneas's bit-vector codecs with the shared radix-256
842+
model. Independent required contracts spell out positional byte sums and each
843+
output digit, so the same decoder helper cannot define both sides of the check.
844+
There are no extra preconditions on these fixed arrays or unsigned integers.
845+
This trial covers value computations; it does not verify signed conversions,
846+
floating-point operations, pointer access, or the full byteorder API.
847+
848+
The extracted ByteOrder trait dictionaries carry formatting methods even when
849+
these computations never format anything. Aeneas leaves `Formatter` as an
850+
opaque `Type`. The axiom audit admits that exact monomorphic data carrier after
851+
checking its signature; no formatting proposition or behavior is assumed. A
852+
change to a proposition or a function fails the audit. The PhantomData model
853+
likewise records its zero runtime fields, with a Lean shape theorem that fails
854+
if extraction adds fields; it makes no ownership or provenance claim.

‎verification/aeneas/golden/Funs.lean‎

Lines changed: 578 additions & 0 deletions
Large diffs are not rendered by default.

‎verification/aeneas/golden/Types.lean‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ set_option maxRecDepth 2048
2828

2929
namespace Zerocopy
3030

31+
/-- [core::marker::PhantomData]
32+
Source: '/rustc/library/core/src/marker.rs', lines 811:0-811:39
33+
Name pattern: [core::marker::PhantomData]
34+
Visibility: public -/
35+
@[rust_type "core::marker::PhantomData"]
36+
structure core.marker.PhantomData (T : Type) where
37+
3138
/-- Trait declaration: [core::num::nonzero::ZeroablePrimitive]
3239
Source: '/rustc/library/core/src/num/nonzero.rs', lines 34:0-34:59
3340
Name pattern: [core::num::nonzero::ZeroablePrimitive]
@@ -39,4 +46,57 @@ structure core.num.nonzero.ZeroablePrimitive (Self : Type) (Self_NonZeroInner :
3946
markerCopyInst : core.marker.Copy Self
4047
innerCopyInst : core.marker.Copy Self_NonZeroInner
4148

49+
/-- [zerocopy::byteorder::Order]
50+
Source: 'src/byteorder.rs', lines 103:0-108:1
51+
Visibility: public -/
52+
@[discriminant isize]
53+
inductive byteorder.Order where
54+
| BigEndian : byteorder.Order
55+
| LittleEndian : byteorder.Order
56+
57+
/-- Trait declaration: [zerocopy::byteorder::private::Sealed]
58+
Source: 'src/byteorder.rs', lines 95:4-95:23
59+
Visibility: public -/
60+
structure byteorder.private.Sealed (Self : Type) where
61+
62+
/-- Trait declaration: [zerocopy::byteorder::ByteOrder]
63+
Source: 'src/byteorder.rs', lines 87:0-92:1
64+
Visibility: public -/
65+
structure byteorder.ByteOrder (Self : Type) where
66+
ORDER : Result byteorder.Order
67+
coremarkerCopyInst : core.marker.Copy Self
68+
corecloneCloneInst : core.clone.Clone Self
69+
corefmtDebugInst : core.fmt.Debug Self
70+
corefmtDisplayInst : core.fmt.Display Self
71+
corecmpEqInst : core.cmp.Eq Self
72+
corecmpPartialEqInst : core.cmp.PartialEq Self Self
73+
corecmpOrdInst : core.cmp.Ord Self
74+
corecmpPartialOrdInst : core.cmp.PartialOrd Self Self
75+
corehashHashInst : core.hash.Hash Self
76+
privateSealedInst : byteorder.private.Sealed Self
77+
78+
/-- [zerocopy::byteorder::BigEndian]
79+
Source: 'src/byteorder.rs', lines 114:0-114:21
80+
Visibility: public -/
81+
inductive byteorder.BigEndian where
82+
83+
/-- [zerocopy::byteorder::LittleEndian]
84+
Source: 'src/byteorder.rs', lines 131:0-131:24
85+
Visibility: public -/
86+
inductive byteorder.LittleEndian where
87+
88+
/-- [zerocopy::byteorder::U16]
89+
Source: 'src/byteorder.rs', lines 528:12-528:62
90+
Visibility: public -/
91+
structure byteorder.U16 (O : Type) where
92+
_0 : Array Std.U8 2#usize
93+
_1 : core.marker.PhantomData O
94+
95+
/-- [zerocopy::byteorder::U32]
96+
Source: 'src/byteorder.rs', lines 528:12-528:62
97+
Visibility: public -/
98+
structure byteorder.U32 (O : Type) where
99+
_0 : Array Std.U8 4#usize
100+
_1 : core.marker.PhantomData O
101+
42102
end Zerocopy
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/- Copyright 2026 The Fuchsia Authors
2+
3+
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4+
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5+
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6+
This file may not be copied, modified, or distributed except according to
7+
those terms. -/
8+
9+
module
10+
public import RustAeneas.Bytes
11+
public import ModelLemmas
12+
@[expose] public section
13+
14+
/-!
15+
These adapters preserve byte values through zerocopy's scalar-array decoders.
16+
The generic numerical byte bridges live in the shared Aeneas companion package.
17+
Rust/compiler correspondence remains a separate obligation.
18+
-/
19+
20+
open Aeneas Aeneas.Std AeneasSpecs
21+
namespace Zerocopy.Proofs
22+
23+
/-- Scalar list decoding accepts every finite scalar and preserves its value. -/
24+
theorem decodeList_unsigned {ty : UScalarTy} (raw : List (UScalar ty)) :
25+
decodeList (modelUScalar ty).decode raw = some (raw.map unsignedWord) := by
26+
induction raw with
27+
| nil => rfl
28+
| cons head tail ih => simp [decodeList, ih]
29+
30+
/-- The mathematical witness for every raw scalar array, at its original length. -/
31+
def unsignedArray {ty : UScalarTy} {count : Usize} (raw : Aeneas.Std.Array (UScalar ty) count) :
32+
ArrayValue (UnsignedWord ty) count :=
33+
⟨raw.val.map unsignedWord, by simpa only [List.length_map] using raw.property⟩
34+
35+
theorem decodeArray_unsigned {ty : UScalarTy} {count : Usize}
36+
(raw : Aeneas.Std.Array (UScalar ty) count) :
37+
(modelArray count).decode raw = some (unsignedArray raw) := by
38+
apply (decodeArray_iff (modelUScalar ty) count raw (unsignedArray raw)).mpr
39+
exact decodeList_unsigned raw.val
40+
41+
theorem u8_ofNat_val (raw : U8) : BitVec.ofNat 8 raw.val = raw.bv := by
42+
apply BitVec.eq_of_toNat_eq
43+
simp only [BitVec.toNat_ofNat, UScalar.val]
44+
exact Nat.mod_eq_of_lt raw.bv.isLt
45+
46+
/-- The decoded array retains every byte used by the numerical specification. -/
47+
theorem decodedArray_bytes_eq {count : Usize} (raw : Aeneas.Std.Array U8 count)
48+
(value : ArrayValue (UnsignedWord .U8) count)
49+
(decoded : (modelArray count).decode raw = some value) :
50+
value.val.map (fun byte => BitVec.ofNat 8 (byte : Nat)) = raw.val.map U8.bv := by
51+
have same : raw.val.map unsignedWord = value.val :=
52+
Option.some.inj ((decodeList_unsigned raw.val).symm.trans
53+
((decodeArray_iff (modelUScalar .U8) count raw value).mp decoded))
54+
simp only [← same, bind_pure_comp]
55+
simp only [Functor.map, List.map_map]
56+
apply List.map_congr_left
57+
intro byte _
58+
exact u8_ofNat_val byte
59+
60+
end Zerocopy.Proofs

‎verification/aeneas/lean/Check.lean‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ run_elab do
3333
-- may have only the pointer-width size read and require no generic ABI inputs.
3434
let hasLayoutReads := env.contains `core.mem.align_of
3535
let modelInputs := if hasLayoutReads then layoutInputs else #[]
36+
-- ByteOrder carries formatting dictionaries even when a numerical method
37+
-- never formats a value. The backend leaves Formatter's representation
38+
-- abstract. Admit this exact opaque type, with no axiom about its values or
39+
-- behavior; changing it into a proposition or a function is an error.
40+
let opaqueCarriers := #[`Aeneas.Std.core.fmt.Formatter]
41+
let expectedCarrier ← Term.elabType (← `(Type))
42+
for carrier in opaqueCarriers do
43+
let some (.axiomInfo info) := env.find? carrier
44+
| throwError "Missing pinned opaque carrier {carrier}"
45+
unless info.levelParams.isEmpty && (← Meta.isDefEq info.type expectedCarrier) do
46+
throwError "Opaque carrier {carrier} changed its type-only signature"
3647
-- ABI inputs are functions returning data, with no proposition asserting
3748
-- alignment, size, or correctness. A changed type could smuggle a theorem
3849
-- into the allowed axiom list, so check their exact compiled signatures.
@@ -430,11 +441,11 @@ run_elab do
430441
let used ← collectAxioms declName
431442
for ax in used do
432443
-- These are Lean's permitted logical axioms. The only additional
433-
-- allowances are the ABI data inputs whose exact signatures were
434-
-- checked above. They supply no proposition;
444+
-- allowances are the ABI data inputs and opaque carrier whose exact
445+
-- signatures were checked above. Neither supplies a proposition;
435446
-- sorryAx and user-defined proof axioms remain errors.
436447
unless #[`propext, `Classical.choice, `Quot.sound].contains ax ||
437-
modelInputs.contains ax do
448+
modelInputs.contains ax || opaqueCarriers.contains ax do
438449
throwError "{declName} depends on unapproved axiom {ax}"
439450
audited := audited + 1
440451
IO.FS.writeFile "proof-dependencies.json" (Json.arr graph).pretty

‎verification/aeneas/lean/DeriveModels.lean‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ meta partial def boundModelProvider (raw : Expr) (params dicts : Array Expr) : M
104104
mkAppOptM n (xs.map some)
105105
if head.isConstOf ``UScalar then return ← app ``modelUScalar args
106106
if head.isConstOf ``IScalar then return ← app ``modelIScalar args
107+
-- PhantomData has no stored child value: select its fixed empty provider
108+
-- without demanding a model for the type named by its phantom parameter.
109+
if head.isConstOf ``Zerocopy.core.marker.PhantomData then
110+
return ← app ``modelPhantomData args
107111
if head.isConstOf ``Bool then return mkConst ``modelBool
108112
if head.isConstOf ``Nat then return mkConst ``modelNat
109113
if head.isConstOf ``Int then return mkConst ``modelInt

‎verification/aeneas/lean/ModelPrelude.lean‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ those terms. -/
88

99
module
1010
public import RustModel
11-
public import Zerocopy.TypesExternal
11+
public import Zerocopy.Types
1212
@[expose] public section
1313

1414
/-!
@@ -25,6 +25,20 @@ decoders test that condition and preserve its proof in the accepted value.
2525
open Aeneas.Std
2626
namespace AeneasSpecs
2727

28+
-- The imported PhantomData carrier has no runtime fields. Its value model
29+
-- records only that empty stored value; it makes no claim about ownership,
30+
-- variance, drop checking, or pointer provenance.
31+
@[reducible] instance modelPhantomData (T : Type) :
32+
RustModel (Zerocopy.core.marker.PhantomData T) :=
33+
{ Model := Unit, «decode» := fun _ => some () }
34+
35+
/-- This check fails if the pinned extracted carrier gains runtime fields. -/
36+
theorem phantomData_eq_mk (T : Type) (raw : Zerocopy.core.marker.PhantomData T) :
37+
raw = Zerocopy.core.marker.PhantomData.mk := by
38+
cases raw
39+
rfl
40+
41+
2842
-- The bounds are proofs stored beside the value. They contribute constraints
2943
-- to the mathematical type without another runtime field or admission test.
3044
structure UnsignedWord (ty : UScalarTy) where
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/- Copyright 2026 The Fuchsia Authors
2+
3+
Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4+
<LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5+
license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6+
This file may not be copied, modified, or distributed except according to
7+
those terms. -/
8+
9+
module
10+
public import Zerocopy
11+
@[expose] public section
12+
13+
open Aeneas Aeneas.Std
14+
namespace Zerocopy.Obligations
15+
16+
/- These byteorder expectations retain the raw input domain. Readers must
17+
return the positional base-256 value; writers must return every numeric byte,
18+
including zeros. They deliberately use explicit digit arithmetic rather than
19+
invoking the authored specification's shared decoder or encoder. -/
20+
21+
def read_u16_le_spec_contract (bytes : Aeneas.Std.Array U8 2#usize)
22+
(run : Result (U16)) : Prop :=
23+
run ⦃ output => output.val = bytes.val[0]!.val + 256 * (bytes.val[1]!.val) ⦄
24+
25+
def read_u16_le_spec : Prop :=
26+
∀ (bytes : Aeneas.Std.Array U8 2#usize), read_u16_le_spec_contract bytes
27+
(byteorder.verification.read_u16_le bytes)
28+
29+
def write_u16_le_spec_contract (n : U16)
30+
(run : Result (Aeneas.Std.Array U8 2#usize)) : Prop :=
31+
run ⦃ output => output.val.map UScalar.val = [n.val % 256, n.val / 256 % 256] ⦄
32+
33+
def write_u16_le_spec : Prop :=
34+
∀ (n : U16), write_u16_le_spec_contract n
35+
(byteorder.verification.write_u16_le n)
36+
37+
def set_u16_le_spec_contract (_bytes : Aeneas.Std.Array U8 2#usize) (n : U16)
38+
(run : Result (U16)) : Prop :=
39+
run ⦃ output => output = n ⦄
40+
41+
def set_u16_le_spec : Prop :=
42+
∀ (bytes : Aeneas.Std.Array U8 2#usize) (n : U16), set_u16_le_spec_contract bytes n
43+
(byteorder.verification.set_u16_le bytes n)
44+
45+
def read_u16_be_spec_contract (bytes : Aeneas.Std.Array U8 2#usize)
46+
(run : Result (U16)) : Prop :=
47+
run ⦃ output => output.val = bytes.val[1]!.val + 256 * (bytes.val[0]!.val) ⦄
48+
49+
def read_u16_be_spec : Prop :=
50+
∀ (bytes : Aeneas.Std.Array U8 2#usize), read_u16_be_spec_contract bytes
51+
(byteorder.verification.read_u16_be bytes)
52+
53+
def write_u16_be_spec_contract (n : U16)
54+
(run : Result (Aeneas.Std.Array U8 2#usize)) : Prop :=
55+
run ⦃ output => output.val.map UScalar.val = [n.val / 256 % 256, n.val % 256] ⦄
56+
57+
def write_u16_be_spec : Prop :=
58+
∀ (n : U16), write_u16_be_spec_contract n
59+
(byteorder.verification.write_u16_be n)
60+
61+
def set_u16_be_spec_contract (_bytes : Aeneas.Std.Array U8 2#usize) (n : U16)
62+
(run : Result (U16)) : Prop :=
63+
run ⦃ output => output = n ⦄
64+
65+
def set_u16_be_spec : Prop :=
66+
∀ (bytes : Aeneas.Std.Array U8 2#usize) (n : U16), set_u16_be_spec_contract bytes n
67+
(byteorder.verification.set_u16_be bytes n)
68+
69+
def read_u32_le_spec_contract (bytes : Aeneas.Std.Array U8 4#usize)
70+
(run : Result (U32)) : Prop :=
71+
run ⦃ output => output.val = bytes.val[0]!.val + 256 * (bytes.val[1]!.val + 256 * (bytes.val[2]!.val + 256 * (bytes.val[3]!.val))) ⦄
72+
73+
def read_u32_le_spec : Prop :=
74+
∀ (bytes : Aeneas.Std.Array U8 4#usize), read_u32_le_spec_contract bytes
75+
(byteorder.verification.read_u32_le bytes)
76+
77+
def write_u32_le_spec_contract (n : U32)
78+
(run : Result (Aeneas.Std.Array U8 4#usize)) : Prop :=
79+
run ⦃ output => output.val.map UScalar.val = [n.val % 256, n.val / 256 % 256, n.val / 65536 % 256, n.val / 16777216 % 256] ⦄
80+
81+
def write_u32_le_spec : Prop :=
82+
∀ (n : U32), write_u32_le_spec_contract n
83+
(byteorder.verification.write_u32_le n)
84+
85+
def set_u32_le_spec_contract (_bytes : Aeneas.Std.Array U8 4#usize) (n : U32)
86+
(run : Result (U32)) : Prop :=
87+
run ⦃ output => output = n ⦄
88+
89+
def set_u32_le_spec : Prop :=
90+
∀ (bytes : Aeneas.Std.Array U8 4#usize) (n : U32), set_u32_le_spec_contract bytes n
91+
(byteorder.verification.set_u32_le bytes n)
92+
93+
def read_u32_be_spec_contract (bytes : Aeneas.Std.Array U8 4#usize)
94+
(run : Result (U32)) : Prop :=
95+
run ⦃ output => output.val = bytes.val[3]!.val + 256 * (bytes.val[2]!.val + 256 * (bytes.val[1]!.val + 256 * (bytes.val[0]!.val))) ⦄
96+
97+
def read_u32_be_spec : Prop :=
98+
∀ (bytes : Aeneas.Std.Array U8 4#usize), read_u32_be_spec_contract bytes
99+
(byteorder.verification.read_u32_be bytes)
100+
101+
def write_u32_be_spec_contract (n : U32)
102+
(run : Result (Aeneas.Std.Array U8 4#usize)) : Prop :=
103+
run ⦃ output => output.val.map UScalar.val = [n.val / 16777216 % 256, n.val / 65536 % 256, n.val / 256 % 256, n.val % 256] ⦄
104+
105+
def write_u32_be_spec : Prop :=
106+
∀ (n : U32), write_u32_be_spec_contract n
107+
(byteorder.verification.write_u32_be n)
108+
109+
def set_u32_be_spec_contract (_bytes : Aeneas.Std.Array U8 4#usize) (n : U32)
110+
(run : Result (U32)) : Prop :=
111+
run ⦃ output => output = n ⦄
112+
113+
def set_u32_be_spec : Prop :=
114+
∀ (bytes : Aeneas.Std.Array U8 4#usize) (n : U32), set_u32_be_spec_contract bytes n
115+
(byteorder.verification.set_u32_be bytes n)
116+
117+
end Zerocopy.Obligations

0 commit comments

Comments
 (0)