/- ──────────────────────────────────────────────────────────────────────────── Proofs/Basic.lean — pin the hand-written external models + first sanity facts about the generated code ──────────────────────────────────────────────────────────────────────────── First proofs over the Aeneas-generated field model (CurveField). BACKGROUND. The Rust field code of curve25519/solana-ed25519 (src/field.rs + src/backend/serial/u64/field.rs) was transpiled mechanically to Lean 4 with Charon + Aeneas into gen/CurveField/Types.lean and Funs.lean; fallible machine operations live in the `Result` monad (`ok x` = success, `fail` = panic/overflow). Items OUTSIDE the crate — essentially the `subtle` crate (v2.6.1, constant-time primitives) — are not transpiled; they are modeled BY HAND in gen/CurveField/FunsExternal.lean and form the trusted base of the verification (see ../README.md, "External-model policy"). These establish the proof pattern for this workspace: - pin down the semantics of the hand-written external models (gen/CurveField/FunsExternal.lean) as reusable simp lemmas, and - prove first sanity facts about the generated code itself. Every external-model lemma below is proved by `rfl` (definitional equality): it adds NO trust beyond the model itself. Its value is (a) restating the model in the `= ok …` shape that simp / symbolic execution consumes, and (b) regression-pinning: if someone edits a model in FunsExternal.lean to mean something else, this file stops compiling. PLACE IN THE PROOF GRAPH. Standalone: imports the generated model (CurveField.Funs) and is compiled first by ../check.sh, but is NOT imported by the Denote → … → FieldMain chain that proves the main theorem (CurveFieldProofs.fieldImplementation). Proofs/ConstSpecs.lean proves the stronger, denotational versions of the constant facts below (it even reuses the name `zero_spec`, which is harmless precisely because this file is not imported there). The real verification targets (limb-bound invariants, add/sub/mul/square correctness vs. ℤ/(2²⁵⁵-19), panic-freedom of the carry chains, and the sqrt_ratio_i specification) build on these — see ../README.md. -/ import CurveField.Funs open Aeneas Aeneas.Std Result ControlFlow Error open curve25519 namespace CurveFieldProofs /-! ## Semantics of the external models (subtle) `subtle.Choice` is the subtle crate's constant-time boolean: a `u8` whose documented invariant is value ∈ {0, 1} (1 = true). The model declares `subtle.Choice := U8` (gen/CurveField/TypesExternal.lean), so `c.val` below is that u8's numeric value. Source spans cite subtle-2.6.1/src/lib.rs, copied from the model docstrings in gen/CurveField/FunsExternal.lean. -/ /-- `Choice::from(u8)` is the identity (the Rust `black_box` is a barrier). subtle crate: `impl From for Choice`, subtle-2.6.1/src/lib.rs:238. MATH: from b = ok b — total, value unchanged. The Rust body is `Choice(black_box(input))`; `black_box` is a volatile read that only defeats compiler optimization, semantically the identity. WHY NEEDED: the transpiled field code builds `Choice`s through this conversion (e.g. in `sqrt_ratio_i`); the simp lemma lets proofs step over those calls. -/ @[simp] theorem choice_from_u8_spec (b : Std.U8) : subtle.Choice.Insts.CoreConvertFromU8.from b = ok b := rfl /-- `bool::from(Choice)` tests non-zeroness. subtle crate: `impl From for bool`, subtle-2.6.1/src/lib.rs:153. MATH: from c = ok (c ≠ 0); on the documented {0,1} invariant this is exactly "c = 1". WHY NEEDED: the field API exposes results of constant-time comparisons as `bool` through this conversion. -/ @[simp] theorem bool_from_choice_spec (c : subtle.Choice) : Bool.Insts.CoreConvertFromChoice.from c = ok (c.val != 0) := rfl /-- `u64::conditional_select(a, b, c)` keeps `a` iff `c = 0`. subtle crate: `impl ConditionallySelectable for u64`, subtle-2.6.1/src/lib.rs:513. MATH: conditional_select a b c = ok (if c = 0 then a else b). The Rust mask trick `a ^ ((-(c as i64) as u64) & (a ^ b))` agrees with this if-then-else on the {0,1} Choice invariant (mask = 0 or all-ones). WHY NEEDED: limbwise constant-time selection is the building block of `FieldElement51`'s `ConditionallySelectable` impl, used by the decompression path (`sqrt_ratio_i`). -/ @[simp] theorem u64_conditional_select_spec (a b : Std.U64) (c : subtle.Choice) : U64.Insts.SubtleConditionallySelectable.conditional_select a b c = ok (if c.val = 0 then a else b) := rfl /-- `u64::conditional_assign(self, other, c)` keeps `self` iff `c = 0`. subtle crate: `impl ConditionallySelectable for u64`, subtle-2.6.1/src/lib.rs:521. MATH: conditional_assign self other c = ok (if c = 0 then self else other) — same mask trick as `conditional_select`, in-place flavor (Aeneas turns `&mut self` into returning the new value). WHY NEEDED: `sqrt_ratio_i` conditionally overwrites its candidate root through this operation. -/ @[simp] theorem u64_conditional_assign_spec (a b : Std.U64) (c : subtle.Choice) : U64.Insts.SubtleConditionallySelectable.conditional_assign a b c = ok (if c.val = 0 then a else b) := rfl /-- `u8::ct_eq` decides equality. subtle crate: `impl ConstantTimeEq for u8`, subtle-2.6.1/src/lib.rs:348. MATH: ct_eq a b = ok (if a = b then 1 else 0) — the specification the Rust xor/shift bit trick implements for ALL inputs (not just {0,1}). WHY NEEDED: byte-wise constant-time equality underlies the slice `ct_eq` used by the field API's equality test. -/ @[simp] theorem u8_ct_eq_spec (a b : Std.U8) : U8.Insts.SubtleConstantTimeEq.ct_eq a b = ok (if a = b then 1#u8 else 0#u8) := rfl /-! ## First facts about the generated field code Sanity checks that the TRANSPILED definitions compute what the Rust constants say — first uses of the `unfold`-then-`rfl`/`simp` pattern the *Spec files build on. Path abbreviation below: u64/field.rs = curve25519/solana-ed25519/src/backend/serial/u64/field.rs -/ /-- `FieldElement51::ZERO` is the all-zero limb array. Rust: `FieldElement51::ZERO = FieldElement51::from_limbs([0,0,0,0,0])`, u64/field.rs:263. MATH: ZERO = ok [0, 0, 0, 0, 0] — the constant evaluates without panic to the all-zero limb vector (`Array.repeat 5 0` = five copies of 0#u64). Its denotation ⟪·⟫ = 0 is proved later in Proofs/ConstSpecs.lean. WHY NEEDED: sanity fact; the additive identity of the field must exist and be panic-free. -/ theorem zero_spec : backend.serial.u64.field.FieldElement51.ZERO = ok (Array.repeat 5#usize 0#u64) := by -- Unfold the generated constant and its `from_limbs` constructor; -- both sides are then the same literal term. unfold backend.serial.u64.field.FieldElement51.ZERO backend.serial.u64.field.FieldElement51.from_limbs rfl /-- `::default()` is `ZERO`. Rust: `impl Default for FieldElement` (the serial-u64 alias of `FieldElement51`), curve25519/solana-ed25519/src/field.rs:66-70. MATH: default = ZERO (equal as `Result`-valued constants). WHY NEEDED: checks the transpiler's `Default`-trait plumbing dispatches to the right constant. -/ theorem default_eq_zero : backend.serial.u64.field.FieldElement51.Insts.CoreDefaultDefault.default = backend.serial.u64.field.FieldElement51.ZERO := by -- Unfolding the Default-impl wrapper exposes ZERO itself. unfold backend.serial.u64.field.FieldElement51.Insts.CoreDefaultDefault.default rfl /-- `FieldElement51::from_limbs` never fails. Rust: `pub(crate) const fn from_limbs(limbs: [u64; 5])`, u64/field.rs:258-260 — a plain constructor wrapping the array. MATH: forall limbs, from_limbs limbs = ok limbs — total identity. WHY NEEDED: every transpiled constant (ZERO/ONE/MINUS_ONE/SQRT_M1) goes through this constructor; the `@[simp]` lemma erases it during symbolic execution. -/ @[simp] theorem from_limbs_spec (limbs : Array Std.U64 5#usize) : backend.serial.u64.field.FieldElement51.from_limbs limbs = ok limbs := rfl end CurveFieldProofs