/- ─────────────────────────────────────────────────────────────────────────── Proofs/EdDenote.lean — POINT DENOTATION layer: from transpiled limb structures to coordinates in 𝔽_p, p = 2²⁵⁵ − 19. CONTEXT. The Rust crate curve25519/solana-ed25519 represents points of the twisted Edwards curve −x² + y² = 1 + d·x²y² (d = −121665/121666) in four internal models (src/backend/serial/curve_models.rs module docs, following Hisil–Wong–Carter–Dawson 2008 and the ℙ¹×ℙ¹ picture of Costello–Smith 2017): * `EdwardsPoint` (X:Y:Z:T) — "extended" ℙ³ coordinates with x = X/Z, y = Y/Z and the Segre coherence X·Y = Z·T (equivalently T = XY/Z); * `ProjectivePoint` (X:Y:Z) — ℙ² coordinates, x = X/Z, y = Y/Z; * `CompletedPoint` ((X:Z),(Y:T)) ∈ ℙ¹×ℙ¹ — x = X/Z, y = Y/T (NOTE: here T is the SECOND DENOMINATOR, a completely different role from the extended T); * `ProjectiveNielsPoint` (Y+X, Y−X, Z, T·2d) — a CACHE of readily-added combinations of an extended point; * `AffineNielsPoint` (y+x, y−x, 2d·x·y) — the same cache for an affine point (Z = 1). All coordinates are `FieldElement51` (= `Fe`) limb vectors; Charon+Aeneas transpiled the structures to gen/CurveField/Types.lean and the code to gen/CurveField/Funs.lean. Proofs/FieldMain.lean already established that the FIELD layer is correct: the denotation ⟪·⟫ : Fe → 𝔽_p, the limb-bound invariant `Bnd`, and one `run_*` theorem per field operation. THIS FILE builds the corresponding layer for POINTS, in four parts: 1. VALIDITY PREDICATES + DENOTATIONS for each representation (`ExtValid`/`edX`/`edY`, `ProjValid`/`projX`/`projY`, `ComplValid`/`complX`/`complY`, `ProjNielsValid`/`IsNielsOf`, `AffNielsValid`/`IsAffNielsOf`). ⚠ The Z ≠ 0 (resp. Z ≠ 0 ∧ T ≠ 0) side conditions are carried EXPLICITLY: the Rust code NEVER checks them (projective division by zero cannot panic — it is simply never performed; the code only manipulates numerators/denominators), so they must live in the specification layer. Every honest production of a point (identity, decompression, the add/double formulas on valid inputs) maintains them, and the op-spec phase will thread them through. 2. CONSTANT SPECS for the precomputed curve constants `EDWARDS_D` and `EDWARDS_D2` (gen/CurveField/Funs.lean). To keep this file independent of the (not-yet-existing) math layer Proofs/EdCurve.lean — which will define the canonical `edD : Fp := -(121665:Fp)/121666` — the d-constant is specified in CHARACTERIZATION FORM: 121666 · ⟪D⟫ = −121665 (d = −121665/121666) 121666 · ⟪D2⟫ = −243330 (2d = −243330/121666), which pins the same field element without naming any quotient. `edwards_d2_eq_two_d` then proves ⟪D2⟫ = 2·⟪D⟫ outright. 3. IDENTITY-CONSTANT SPECS: the generated `Identity` trait implementations for `ProjectivePoint` (0:1:1), `AffineNielsPoint` (1,1,0), `ProjectiveNielsPoint` (1,1,1,0) and `EdwardsPoint` (0:1:1:0) all RUN (no panic), are valid, and denote the neutral affine point (0, 1). 4. SMALL FIELD HELPERS the op-spec phase needs: 2 ≠ 0 and 121666 ≠ 0 in 𝔽_p, division-equation rewrites (`fp_div_eq_iff` etc.), the determinism extractor `ok_ext`, and the constructor-projection (`mk_*`) conveniences for the point structures. PROOF TECHNIQUE for the constants: identical to `sqrt_m1_spec` (Proofs/ConstSpecs.lean). Each constant is `from_limbs` of 5 literal limbs; `simp` evaluates the denotation to a concrete ~255-bit natural number N, and the characterization becomes the ℕ-congruence (121666·N) mod p = p − 121665, which `norm_num` checks by kernel-verified literal arithmetic. (Sanity-checked externally: N_d = 37095705934669439343138083508754565189542113879843219016388785533085940283555, N_d2 = 16295367250680780974490674513165176452449235426866156013048779062215315747161, 121666·N_d ≡ −121665 and 121666·N_d2 ≡ −243330 (mod p), N_d2 ≡ 2·N_d.) Nothing in gen/ (the transpiled code) is modified; we only define predicates ABOUT it and run it. Imports: Proofs/FieldMain.lean (denotation, Bnd, run_* runners, Field 𝔽_p). Imported by: the forthcoming point-operation spec files. ─────────────────────────────────────────────────────────────────────── -/ import Proofs.FieldMain open Aeneas Aeneas.Std Result open curve25519_dalek set_option maxHeartbeats 4000000 namespace CurveFieldProofs /-! ## Short aliases for the transpiled point types Definitionally the generated structures (`rfl`-equal), mirroring the `Fe`/`fe_*` aliases of Proofs/Denote.lean. The Rust source lines cited are from the generated docstrings in gen/CurveField/Types.lean. -/ /-- Rust: `pub struct EdwardsPoint { X, Y, Z, T: FieldElement51 }`, src/edwards.rs:390-395 — extended (ℙ³ / "extended twisted Edwards") coordinates. -/ abbrev EdPoint := edwards.EdwardsPoint /-- Rust: `pub(crate) struct ProjectivePoint { X, Y, Z }`, src/backend/serial/curve_models.rs:154-158 — ℙ² coordinates. -/ abbrev ProjPoint := backend.serial.curve_models.ProjectivePoint /-- Rust: `pub(crate) struct CompletedPoint { X, Y, Z, T }`, src/backend/serial/curve_models.rs:169-174 — ℙ¹×ℙ¹ coordinates ((X:Z),(Y:T)); the output type of the add/double kernels. -/ abbrev ComplPoint := backend.serial.curve_models.CompletedPoint /-- Rust: `pub struct ProjectiveNielsPoint { Y_plus_X, Y_minus_X, Z, T2d }`, src/backend/serial/curve_models.rs:206-211 — readily-addable cache of an `EdwardsPoint`. -/ abbrev ProjNiels := backend.serial.curve_models.ProjectiveNielsPoint /-- Rust: `pub(crate) struct AffineNielsPoint { y_plus_x, y_minus_x, xy2d }`, src/backend/serial/curve_models.rs:184-188 — readily-addable cache of an affine point (Z = 1). -/ abbrev AffNiels := backend.serial.curve_models.AffineNielsPoint /-! ## Small 𝔽_p facts the point layer relies on The point denotations divide by Z (and T), and the d-constant characterization divides (conceptually) by 121666 and 2; these lemmas make those denominators usable. -/ /-- No Rust analog — arithmetic helper generalizing `natCast_P_sub_one` (Proofs/ConstSpecs.lean) from k = 1 to arbitrary k ≤ p. MATH: ((p − k : ℕ) : 𝔽_p) = −(k : 𝔽_p) (since p ≡ 0 in 𝔽_p). WHY NEEDED: the constant specs below compute (121666·N) mod p to the ℕ literal p − 121665 (resp. p − 243330); this lemma converts that natural number into the field element −121665 (resp. −243330). -/ theorem natCast_P_sub (k : ℕ) (hk : k ≤ P) : ((P - k : ℕ) : Fp) = -(k : Fp) := by -- k ≤ p lets Nat.cast_sub distribute the truncated subtraction rw [Nat.cast_sub hk, ZMod.natCast_self] ring /-- No Rust analog — arithmetic helper. MATH: 0 < k < p ==> (k : 𝔽_p) ≠ 0. A positive natural number below the modulus does not vanish mod p: (k : 𝔽_p) = 0 would mean p ∣ k, forcing p ≤ k. WHY NEEDED: the two specific instances below (k = 2 and k = 121666); stated generally so the op-spec phase can produce further nonzero numerals (e.g. 121665, 486664) without repeating the argument. -/ theorem natCast_ne_zero_of_lt_P {k : ℕ} (h0 : 0 < k) (hk : k < P) : ((k : ℕ) : Fp) ≠ 0 := by intro h -- (k : ZMod p) = 0 ↔ p ∣ k, and a divisor of a positive number is ≤ it have hdvd : P ∣ k := (ZMod.natCast_eq_zero_iff k P).mp h have := Nat.le_of_dvd h0 hdvd omega /-- MATH: (2 : 𝔽_p) ≠ 0 — p = 2²⁵⁵ − 19 is an ODD prime, so the field has characteristic ≠ 2. WHY NEEDED: the projective-niels denominator is 2·Z (the cache stores Y+X and Y−X, whose sum/difference is 2Y/2X), so recovering coordinates divides by 2; also doubling formulas. Primed to avoid clashing with mathlib's `two_ne_zero`. -/ theorem two_ne_zero' : (2 : Fp) ≠ 0 := by have h := natCast_ne_zero_of_lt_P (k := 2) (by norm_num) (by norm_num [P]) exact_mod_cast h /-- MATH: (121666 : 𝔽_p) ≠ 0 (121666 = 1 − a·d⁻¹-free spelling: it is just a small positive integer < p). WHY NEEDED: the curve constant d is characterized below as the solution of 121666·d = −121665; this lemma makes that characterization UNIQUE (121666 is invertible), which `edwards_d2_eq_two_d` and the future EdCurve.lean bridge (d = −121665/121666) exploit. -/ theorem c121666_ne_zero : (121666 : Fp) ≠ 0 := by have h := natCast_ne_zero_of_lt_P (k := 121666) (by norm_num) (by norm_num [P]) exact_mod_cast h /-! ## 1. Validity predicates and denotations Each representation gets * a VALIDITY predicate: the limb bounds under which the transpiled field ops run panic-free (the dalek 2⁵²/2⁵⁴ discipline of Proofs/FieldMain.lean), PLUS the nonzero-denominator conditions; and * a DENOTATION: the affine coordinates (x, y) ∈ 𝔽_p × 𝔽_p it represents. ⚠ Z ≠ 0 IS NOT CHECKED BY THE RUST CODE. The implementation never divides — it works with fractions symbolically — so nothing at runtime enforces a nonzero denominator, and a "point" with Z = 0 would silently denote garbage (0/0). The predicate layer here is where that obligation lives; every constructor of points we specify (identity below, decompression and the arithmetic kernels in the op-spec phase) PROVES it, and every consumer ASSUMES it. -/ /-- Validity of an extended ("ℙ³") point P = (X : Y : Z : T). MATH: all four coordinates obey the reduced limb bound 2⁵² (so any field op may consume them), Z ≢ 0 (mod p), and the Segre/extended coherence X·Y ≡ Z·T (mod p) (i.e. T carries the product x·y: T/Z = (X/Z)·(Y/Z)). Rust: the *intended* invariant of `EdwardsPoint` per curve_models.rs module docs ("the curve is given by the pair of equations −W₁² + W₂² = W₃² + dW₀², W₀W₃ = W₁W₂"); the curve equation itself is deliberately NOT part of this predicate — it belongs to the math layer (Proofs/EdCurve.lean) on the denoted pair (edX, edY). WHY NEEDED: precondition and postcondition of every extended-point operation in the op-spec phase. -/ def ExtValid (Pt : EdPoint) : Prop := Bnd Pt.X (2^52) ∧ Bnd Pt.Y (2^52) ∧ Bnd Pt.Z (2^52) ∧ Bnd Pt.T (2^52) ∧ ⟪Pt.Z⟫ ≠ 0 ∧ ⟪Pt.X⟫ * ⟪Pt.Y⟫ = ⟪Pt.Z⟫ * ⟪Pt.T⟫ /-- Affine x-coordinate denoted by an extended point: x = ⟪X⟫ / ⟪Z⟫. (Meaningful under `ExtValid` — division by ⟪Z⟫ ≠ 0; on Z ≡ 0 it would be mathlib's junk value 0.) `noncomputable` because 𝔽_p division goes through the classical field instance — irrelevant, never executed. -/ noncomputable def edX (Pt : EdPoint) : Fp := ⟪Pt.X⟫ / ⟪Pt.Z⟫ /-- Affine y-coordinate denoted by an extended point: y = ⟪Y⟫ / ⟪Z⟫. -/ noncomputable def edY (Pt : EdPoint) : Fp := ⟪Pt.Y⟫ / ⟪Pt.Z⟫ /-- Validity of a projective ("ℙ²") point P = (X : Y : Z). MATH: limb bounds 2⁵² on X, Y, Z and Z ≢ 0 (mod p). No coherence equation — ℙ² has no redundant coordinate. -/ def ProjValid (Pt : ProjPoint) : Prop := Bnd Pt.X (2^52) ∧ Bnd Pt.Y (2^52) ∧ Bnd Pt.Z (2^52) ∧ ⟪Pt.Z⟫ ≠ 0 /-- Affine x-coordinate denoted by a projective point: x = ⟪X⟫ / ⟪Z⟫. -/ noncomputable def projX (Pt : ProjPoint) : Fp := ⟪Pt.X⟫ / ⟪Pt.Z⟫ /-- Affine y-coordinate denoted by a projective point: y = ⟪Y⟫ / ⟪Z⟫. -/ noncomputable def projY (Pt : ProjPoint) : Fp := ⟪Pt.Y⟫ / ⟪Pt.Z⟫ /-- Validity of a completed ("ℙ¹×ℙ¹") point P = ((X : Z), (Y : T)). MATH: limb bounds 2⁵² on all four coordinates, Z ≢ 0 AND T ≢ 0. ⚠ TWO denominators: the completed model is a product of two projective lines, x = X/Z on the first and y = Y/T on the second — the field T here plays the role of a DENOMINATOR for y, entirely unlike the extended model's T (which is a cached numerator product). Both must be nonzero for the point to denote affine coordinates. -/ def ComplValid (Pt : ComplPoint) : Prop := Bnd Pt.X (2^52) ∧ Bnd Pt.Y (2^52) ∧ Bnd Pt.Z (2^52) ∧ Bnd Pt.T (2^52) ∧ ⟪Pt.Z⟫ ≠ 0 ∧ ⟪Pt.T⟫ ≠ 0 /-- Affine x-coordinate denoted by a completed point: x = ⟪X⟫ / ⟪Z⟫. -/ noncomputable def complX (Pt : ComplPoint) : Fp := ⟪Pt.X⟫ / ⟪Pt.Z⟫ /-- Affine y-coordinate denoted by a completed point: y = ⟪Y⟫ / ⟪T⟫ (T, not Z — see `ComplValid`). -/ noncomputable def complY (Pt : ComplPoint) : Fp := ⟪Pt.Y⟫ / ⟪Pt.T⟫ /-- Limb-bound + denominator validity of a projective-niels cache point. MATH: Bnd(Y_plus_X, 2⁵³), Bnd(Y_minus_X, 2⁵³), Bnd(Z, 2⁵²), Bnd(T2d, 2⁵²), and ⟪Z⟫ ≠ 0. The 2⁵³ bound on the two sum/difference fields is the natural one: `to_projective_niels` computes Y_plus_X with the UNREDUCED `fe_add` (output bound 2⁵³ — `run_add`, FieldMain.lean) from two reduced (2⁵²) coordinates, while Y_minus_X, Z, T2d come from `fe_sub`/`fe_mul` (output bound 2⁵²; stated as ≤ 2⁵³ resp. 2⁵² accordingly). All four are < 2⁵⁴, so every field op may consume them directly. -/ def ProjNielsValid (N : ProjNiels) : Prop := Bnd N.Y_plus_X (2^53) ∧ Bnd N.Y_minus_X (2^53) ∧ Bnd N.Z (2^52) ∧ Bnd N.T2d (2^52) ∧ ⟪N.Z⟫ ≠ 0 /-- RELATIONAL denotation of a projective-niels point: `IsNielsOf N Pt` says the cache N was correctly derived from the extended point Pt. MATH: ⟪Y_plus_X⟫ = ⟪Pt.Y⟫ + ⟪Pt.X⟫, ⟪Y_minus_X⟫ = ⟪Pt.Y⟫ − ⟪Pt.X⟫, ⟪Z⟫ = ⟪Pt.Z⟫, 121666 · ⟪T2d⟫ = −243330 · ⟪Pt.T⟫ (i.e. ⟪T2d⟫ = ⟪Pt.T⟫ · 2d, with 2d = −243330/121666 expressed denominator-free — the same characterization trick as `edwards_d_spec` below, so no `edD` definition is needed in this file). DESIGN. We deliberately specify the niels cache RELATIONALLY rather than giving it standalone coordinates (which would be x = (⟪Y_plus_X⟫ − ⟪Y_minus_X⟫)/(2⟪Z⟫), y = (⟪Y_plus_X⟫ + ⟪Y_minus_X⟫)/(2⟪Z⟫) plus a cache-coherence equation for T2d): every niels point the code ever creates comes from `to_projective_niels` on a concrete extended point, and every consumer (`add`/`sub` of EdwardsPoint + ProjNiels) immediately recombines the fields, so the derivation facts are exactly the shape the op-spec proofs use. The standalone coordinates are recoverable from this relation by field algebra (divide by 2⟪Z⟫ ≠ 0, using `two_ne_zero'` and `fp_div_eq_iff`). -/ def IsNielsOf (N : ProjNiels) (Pt : EdPoint) : Prop := ⟪N.Y_plus_X⟫ = ⟪Pt.Y⟫ + ⟪Pt.X⟫ ∧ ⟪N.Y_minus_X⟫ = ⟪Pt.Y⟫ - ⟪Pt.X⟫ ∧ ⟪N.Z⟫ = ⟪Pt.Z⟫ ∧ (121666 : Fp) * ⟪N.T2d⟫ = -243330 * ⟪Pt.T⟫ /-- Limb-bound validity of an affine-niels cache point. MATH: Bnd(y_plus_x, 2⁵³), Bnd(y_minus_x, 2⁵³), Bnd(xy2d, 2⁵²). No denominator condition — the affine cache has implicit Z = 1. (2⁵³ on the sum/difference fields for the same `fe_add` reason as in `ProjNielsValid`; the precomputed basepoint tables actually store reduced values, which satisfy this a fortiori.) -/ def AffNielsValid (N : AffNiels) : Prop := Bnd N.y_plus_x (2^53) ∧ Bnd N.y_minus_x (2^53) ∧ Bnd N.xy2d (2^52) /-- RELATIONAL denotation of an affine-niels point: `IsAffNielsOf N x y` says N caches the affine point (x, y) (implicit Z = 1). MATH: ⟪y_plus_x⟫ = y + x, ⟪y_minus_x⟫ = y − x, 121666 · ⟪xy2d⟫ = −243330 · (x · y) (i.e. ⟪xy2d⟫ = 2d·x·y, denominator-free as in `IsNielsOf`). -/ def IsAffNielsOf (N : AffNiels) (x y : Fp) : Prop := ⟪N.y_plus_x⟫ = y + x ∧ ⟪N.y_minus_x⟫ = y - x ∧ (121666 : Fp) * ⟪N.xy2d⟫ = -243330 * (x * y) /-! ## 2. The curve constants EDWARDS_D and EDWARDS_D2 Rust: `constants::EDWARDS_D` ("Edwards d value, equal to −121665/121666 mod p") and `constants::EDWARDS_D2` (= 2·d), curve25519/solana-ed25519/src/backend/serial/u64/constants.rs:45-60; transpiled at gen/CurveField/Funs.lean:1121 and :1977 as `from_limbs` of 5 literal limbs each. The specs use the denominator-free CHARACTERIZATION 121666 · d = −121665, which determines d uniquely in 𝔽_p (121666 is invertible — `c121666_ne_zero`), so this file needs no division and no dependence on the future canonical definition `edD := -(121665 : Fp)/121666` in Proofs/EdCurve.lean (the bridge there is one `fp_div_eq_iff` away). -/ /-- Rust: `constants::EDWARDS_D`, u64/constants.rs:45-51. MATH: EDWARDS_D = ok D with Bnd(D, 2⁵²) and 121666·⟪D⟫ = −121665 in 𝔽_p — i.e. ⟪D⟫ is THE Edwards curve constant d = −121665/121666. The limbs [929955233495203, 466365720129213, 1662059464998953, 2033849074728123, 1442794654840575] denote the 255-bit number N_d = 37095705934669439343138083508754565189542113879843219016388785533085940283555 and the spec certifies 121666·N_d ≡ −121665 (mod p) by kernel-verified literal arithmetic (the `sqrt_m1_spec` technique, ConstSpecs.lean). (Each limb is < 2⁵¹; stated at the uniform reduced bound 2⁵².) WHY NEEDED: a corrupted table entry here would change the curve being implemented — `is_valid`, point addition (via T2d/xy2d caches) and decompression all multiply by this constant. -/ theorem edwards_d_spec : backend.serial.u64.constants.EDWARDS_D ⦃ dfe => Bnd dfe (2^52) ∧ (121666 : Fp) * ⟪dfe⟫ = -121665 ⦄ := by unfold backend.serial.u64.constants.EDWARDS_D backend.serial.u64.field.FieldElement51.from_limbs -- simp discharges the Bnd conjunct and evaluates `feVal` to the literal -- N_d; the remaining goal is `(121666 : 𝔽_p) * (N_d : 𝔽_p) = -121665`. simp [Bnd, denote, feVal, limbsVal, Array.make] -- 121666·N_d ≡ p − 121665 (mod p), checked by literal arithmetic on ℕ -- (norm_num evaluates the ~272-bit product and the division by p in-kernel) have hmod : ((121666 * 37095705934669439343138083508754565189542113879843219016388785533085940283555 : ℕ)) % P = P - 121665 := by norm_num [P] -- assemble: 121666·(N_d:𝔽_p) = ((121666·N_d) mod p : 𝔽_p) = ((p−121665) : 𝔽_p) = −121665 have key : ((121666 : ℕ) : Fp) * ((37095705934669439343138083508754565189542113879843219016388785533085940283555 : ℕ) : Fp) = -121665 := by rw [← Nat.cast_mul, ← ZMod.natCast_mod, hmod, natCast_P_sub 121665 (by norm_num [P])] norm_num exact_mod_cast key /-- Rust: `constants::EDWARDS_D2` ("Edwards 2*d value, equal to 2*(−121665/121666) mod p"), u64/constants.rs:54-60. MATH: EDWARDS_D2 = ok D2 with Bnd(D2, 2⁵²) and 121666·⟪D2⟫ = −243330 in 𝔽_p — the SAME characterization shape as `edwards_d_spec`, with −243330 = 2·(−121665), so ⟪D2⟫ = 2d (made explicit by `edwards_d2_eq_two_d` below). The limbs [1859910466990425, 932731440258426, 1072319116312658, 1815898335770999, 633789495995903] denote N_d2 = 16295367250680780974490674513165176452449235426866156013048779062215315747161, and the spec certifies 121666·N_d2 ≡ −243330 (mod p). WHY NEEDED: `T2d`/`xy2d` caches are built by multiplying with this constant; the extended-coordinates addition formulas bake "2d" in. -/ theorem edwards_d2_spec : backend.serial.u64.constants.EDWARDS_D2 ⦃ d2 => Bnd d2 (2^52) ∧ (121666 : Fp) * ⟪d2⟫ = -243330 ⦄ := by unfold backend.serial.u64.constants.EDWARDS_D2 backend.serial.u64.field.FieldElement51.from_limbs simp [Bnd, denote, feVal, limbsVal, Array.make] -- 121666·N_d2 ≡ p − 243330 (mod p), literal arithmetic on ℕ have hmod : ((121666 * 16295367250680780974490674513165176452449235426866156013048779062215315747161 : ℕ)) % P = P - 243330 := by norm_num [P] have key : ((121666 : ℕ) : Fp) * ((16295367250680780974490674513165176452449235426866156013048779062215315747161 : ℕ) : Fp) = -243330 := by rw [← Nat.cast_mul, ← ZMod.natCast_mod, hmod, natCast_P_sub 243330 (by norm_num [P])] norm_num exact_mod_cast key /-- The two table constants are coherent: ⟪D2⟫ = 2 · ⟪D⟫. MATH: EDWARDS_D = ok D and EDWARDS_D2 = ok D2 ==> ⟪D2⟫ = 2·⟪D⟫. Phrased over ANY successful evaluations (the constants are deterministic, so D/D2 are forced to the spec witnesses). PROOF: both characterizations live over the invertible scalar 121666: 121666·⟪D2⟫ = −243330 = 2·(−121665) = 2·(121666·⟪D⟫) = 121666·(2⟪D⟫), cancel 121666 (`c121666_ne_zero`). WHY NEEDED: the op-spec phase proves `to_projective_niels` (which multiplies by EDWARDS_D2) produces `IsNielsOf` facts; this lemma is the glue identifying the D2 table entry with "2·d" wherever the math layer speaks in terms of d alone. -/ theorem edwards_d2_eq_two_d {dfe d2 : Fe} (hd : backend.serial.u64.constants.EDWARDS_D = ok dfe) (hd2 : backend.serial.u64.constants.EDWARDS_D2 = ok d2) : ⟪d2⟫ = 2 * ⟪dfe⟫ := by -- materialize the spec witnesses and identify them with dfe/d2 (determinism) obtain ⟨d', hd', _, hdv⟩ := spec_exists edwards_d_spec obtain ⟨d2', hd2', _, hd2v⟩ := spec_exists edwards_d2_spec rw [hd'] at hd; cases hd rw [hd2'] at hd2; cases hd2 -- compare the two characterizations over the common factor 121666 have h : (121666 : Fp) * ⟪d2⟫ = (121666 : Fp) * (2 * ⟪dfe⟫) := by rw [hd2v, mul_left_comm, hdv] norm_num exact mul_left_cancel₀ c121666_ne_zero h /-! ## 3. The identity constants Rust implements `traits::Identity` for each representation (curve_models.rs:229-264, edwards.rs:428-437); under Aeneas a Rust `fn identity()` becomes a 0-argument fallible computation built from the `ZERO`/`ONE` field constants, so each spec asserts TOTALITY (`= ok _`), VALIDITY (the predicates of §1 — in particular the Z ≠ 0 obligation the Rust code never states), and the DENOTATION: the neutral element of the Edwards group is the affine point (0, 1). The proofs run the constants via `run_zero`/`run_one` (FieldMain.lean) and evaluate the transpiled do-blocks on the resulting `ok` values. -/ /-- Rust: `impl Identity for ProjectivePoint` — (X:Y:Z) = (0:1:1), curve_models.rs:229-237; transpiled at gen/CurveField/Funs.lean:580. MATH: identity = ok Pt, ProjValid Pt, (projX Pt, projY Pt) = (0, 1). (x = 0/1 = 0, y = 1/1 = 1 — the Edwards neutral point.) -/ theorem run_projective_identity : ∃ Pt : ProjPoint, backend.serial.curve_models.ProjectivePoint.Insts.Curve25519_dalekTraitsIdentity.identity = ok Pt ∧ ProjValid Pt ∧ projX Pt = 0 ∧ projY Pt = 1 := by -- run the two field constants the do-block binds obtain ⟨z, hz, hzb, hz0⟩ := run_zero obtain ⟨o, ho, hob, ho1⟩ := run_one -- restate at the generated names (fe_zero/fe_one are reducible aliases) have hz' : backend.serial.u64.field.FieldElement51.ZERO = ok z := hz have ho' : backend.serial.u64.field.FieldElement51.ONE = ok o := ho refine ⟨⟨z, o, o⟩, ?_, ⟨hzb, hob, hob, ?_⟩, ?_, ?_⟩ · -- totality: substitute the ok-values, the do-block reduces definitionally unfold backend.serial.curve_models.ProjectivePoint.Insts.Curve25519_dalekTraitsIdentity.identity rw [hz', ho']; rfl · -- Z ≠ 0: ⟪Z⟫ = ⟪o⟫ = 1 ≠ 0 show ⟪o⟫ ≠ 0 rw [ho1]; exact one_ne_zero · -- x = ⟪z⟫/⟪o⟫ = 0/⟪o⟫ = 0 show ⟪z⟫ / ⟪o⟫ = 0 rw [hz0, zero_div] · -- y = ⟪o⟫/⟪o⟫ = 1/1 = 1 show ⟪o⟫ / ⟪o⟫ = 1 rw [ho1, div_one] /-- Rust: `impl Identity for EdwardsPoint` — (X:Y:Z:T) = (0:1:1:0), edwards.rs:428-437; transpiled at gen/CurveField/Funs.lean:2998. MATH: identity = ok E, ExtValid E (in particular the extended coherence X·Y = Z·T holds: 0·1 = 1·0), (edX E, edY E) = (0, 1). -/ theorem run_edwards_identity : ∃ E : EdPoint, edwards.EdwardsPoint.Insts.Curve25519_dalekTraitsIdentity.identity = ok E ∧ ExtValid E ∧ edX E = 0 ∧ edY E = 1 := by obtain ⟨z, hz, hzb, hz0⟩ := run_zero obtain ⟨o, ho, hob, ho1⟩ := run_one have hz' : backend.serial.u64.field.FieldElement51.ZERO = ok z := hz have ho' : backend.serial.u64.field.FieldElement51.ONE = ok o := ho refine ⟨⟨z, o, o, z⟩, ?_, ⟨hzb, hob, hob, hzb, ?_, ?_⟩, ?_, ?_⟩ · unfold edwards.EdwardsPoint.Insts.Curve25519_dalekTraitsIdentity.identity rw [hz', ho']; rfl · -- Z ≠ 0 show ⟪o⟫ ≠ 0 rw [ho1]; exact one_ne_zero · -- coherence X·Y = Z·T: ⟪z⟫·⟪o⟫ = ⟪o⟫·⟪z⟫ (commutativity, value-free) show ⟪z⟫ * ⟪o⟫ = ⟪o⟫ * ⟪z⟫ ring · show ⟪z⟫ / ⟪o⟫ = 0 rw [hz0, zero_div] · show ⟪o⟫ / ⟪o⟫ = 1 rw [ho1, div_one] /-- Rust: `impl Identity for AffineNielsPoint` — (y+x, y−x, 2dxy) = (1,1,0), curve_models.rs:256-264; transpiled at gen/CurveField/Funs.lean:636. MATH: identity = ok N, AffNielsValid N, IsAffNielsOf N 0 1 — the cache of the neutral affine point (x,y) = (0,1): y+x = 1, y−x = 1, 2d·x·y = 0. -/ theorem run_affine_niels_identity : ∃ N : AffNiels, backend.serial.curve_models.AffineNielsPoint.Insts.Curve25519_dalekTraitsIdentity.identity = ok N ∧ AffNielsValid N ∧ IsAffNielsOf N 0 1 := by obtain ⟨z, hz, hzb, hz0⟩ := run_zero obtain ⟨o, ho, hob, ho1⟩ := run_one have hz' : backend.serial.u64.field.FieldElement51.ZERO = ok z := hz have ho' : backend.serial.u64.field.FieldElement51.ONE = ok o := ho refine ⟨⟨o, o, z⟩, ?_, ⟨hob.mono (by norm_num), hob.mono (by norm_num), hzb⟩, ?_, ?_, ?_⟩ · unfold backend.serial.curve_models.AffineNielsPoint.Insts.Curve25519_dalekTraitsIdentity.identity rw [ho', hz']; rfl · -- ⟪y_plus_x⟫ = 1 + 0 show ⟪o⟫ = 1 + 0 rw [ho1]; norm_num · -- ⟪y_minus_x⟫ = 1 − 0 show ⟪o⟫ = 1 - 0 rw [ho1]; norm_num · -- 121666·⟪xy2d⟫ = −243330·(0·1): both sides 0 show (121666 : Fp) * ⟪z⟫ = -243330 * (0 * 1) rw [hz0]; ring /-- Rust: `impl Identity for ProjectiveNielsPoint` — (Y+X, Y−X, Z, T2d) = (1, 1, 1, 0), curve_models.rs:239-248; transpiled at gen/CurveField/Funs.lean:599. MATH: both identities run, ProjNielsValid N, and N IS the niels cache of the extended identity: IsNielsOf N E (Y+X = 1+0, Y−X = 1−0, Z = 1, 121666·0 = −243330·0). Stated jointly with the EdwardsPoint identity so the relational denotation `IsNielsOf` has its reference point in hand. -/ theorem run_projective_niels_identity : ∃ (N : ProjNiels) (E : EdPoint), backend.serial.curve_models.ProjectiveNielsPoint.Insts.Curve25519_dalekTraitsIdentity.identity = ok N ∧ edwards.EdwardsPoint.Insts.Curve25519_dalekTraitsIdentity.identity = ok E ∧ ProjNielsValid N ∧ IsNielsOf N E := by obtain ⟨z, hz, hzb, hz0⟩ := run_zero obtain ⟨o, ho, hob, ho1⟩ := run_one have hz' : backend.serial.u64.field.FieldElement51.ZERO = ok z := hz have ho' : backend.serial.u64.field.FieldElement51.ONE = ok o := ho refine ⟨⟨o, o, o, z⟩, ⟨z, o, o, z⟩, ?_, ?_, ⟨hob.mono (by norm_num), hob.mono (by norm_num), hob, hzb, ?_⟩, ?_, ?_, ?_, ?_⟩ · unfold backend.serial.curve_models.ProjectiveNielsPoint.Insts.Curve25519_dalekTraitsIdentity.identity rw [ho', hz']; rfl · unfold edwards.EdwardsPoint.Insts.Curve25519_dalekTraitsIdentity.identity rw [hz', ho']; rfl · -- ⟪Z⟫ = 1 ≠ 0 show ⟪o⟫ ≠ 0 rw [ho1]; exact one_ne_zero · -- ⟪Y_plus_X⟫ = ⟪E.Y⟫ + ⟪E.X⟫: 1 = 1 + 0 show ⟪o⟫ = ⟪o⟫ + ⟪z⟫ rw [hz0]; ring · -- ⟪Y_minus_X⟫ = ⟪E.Y⟫ − ⟪E.X⟫: 1 = 1 − 0 show ⟪o⟫ = ⟪o⟫ - ⟪z⟫ rw [hz0]; ring · -- ⟪Z⟫ = ⟪E.Z⟫ show ⟪o⟫ = ⟪o⟫ rfl · -- 121666·⟪T2d⟫ = −243330·⟪E.T⟫: both sides 0 (⟪z⟫ = 0) show (121666 : Fp) * ⟪z⟫ = -243330 * ⟪z⟫ rw [hz0]; ring /-! ## 4. Helpers for the op-spec phase Division-equation rewrites for the coordinate algebra (every point equation lives over the denominators Z, T, 2Z), a determinism extractor, and constructor-projection conveniences. All thin wrappers, named and collected here so the op-spec files read uniformly. -/ /-- MATH: b ≠ 0 ==> (a / b = c ↔ a = c·b) in 𝔽_p. WHY NEEDED: turns coordinate goals like `edX P = x` (a division) into denominator-free multiplications that `ring` can chew on — the standard `field_simp` step, packaged for one denominator. -/ theorem fp_div_eq_iff {a b c : Fp} (hb : b ≠ 0) : a / b = c ↔ a = c * b := div_eq_iff hb /-- MATH: b ≠ 0 ==> (c = a / b ↔ c·b = a) in 𝔽_p (mirror image). -/ theorem fp_eq_div_iff {a b c : Fp} (hb : b ≠ 0) : c = a / b ↔ c * b = a := eq_div_iff hb /-- MATH: b ≠ 0, d ≠ 0 ==> (a/b = c/d ↔ a·d = c·b) in 𝔽_p. WHY NEEDED: comparing two projective representations of the same affine coordinate (e.g. output of `to_extended` against the input point) cross-multiplies exactly like this. -/ theorem fp_div_eq_div_iff {a b c d : Fp} (hb : b ≠ 0) (hd : d ≠ 0) : a / b = c / d ↔ a * d = c * b := div_eq_div_iff hb hd /-- MATH: a ≠ 0 ==> 2·a ≠ 0 in 𝔽_p (char 𝔽_p ≠ 2, `two_ne_zero'`). WHY NEEDED: the niels recombination denominator is 2·⟪Z⟫. -/ theorem two_mul_ne_zero {a : Fp} (ha : a ≠ 0) : 2 * a ≠ 0 := mul_ne_zero two_ne_zero' ha /-- Determinism extractor. MATH: x = ok a and x = ok b ==> a = b (a `Result` computation is a value, not a relation — two successful runs agree). WHY NEEDED: op-spec proofs constantly match a hypothesis `f p = ok r` (from an unfolded caller) against a `run_*`/spec witness `f p = ok r'` to transport facts about r' to r. (The `rw …; cases …` idiom of FieldMain.lean, packaged.) -/ theorem ok_ext {α} {x : Result α} {a b : α} (h1 : x = ok a) (h2 : x = ok b) : a = b := by rw [h1] at h2 cases h2 rfl /-! Constructor-projection ("denote-of-pair") conveniences: once a point is exhibited as a literal `⟨…⟩`, its fields are the components — `rfl`, but naming them lets op-spec proofs rewrite without `show`-blocks. Marked `@[simp]` so `simp` collapses projections of freshly built points. -/ @[simp] theorem EdPoint.mk_X (x y z t : Fe) : (edwards.EdwardsPoint.mk x y z t).X = x := rfl @[simp] theorem EdPoint.mk_Y (x y z t : Fe) : (edwards.EdwardsPoint.mk x y z t).Y = y := rfl @[simp] theorem EdPoint.mk_Z (x y z t : Fe) : (edwards.EdwardsPoint.mk x y z t).Z = z := rfl @[simp] theorem EdPoint.mk_T (x y z t : Fe) : (edwards.EdwardsPoint.mk x y z t).T = t := rfl @[simp] theorem ProjPoint.mk_X (x y z : Fe) : (backend.serial.curve_models.ProjectivePoint.mk x y z).X = x := rfl @[simp] theorem ProjPoint.mk_Y (x y z : Fe) : (backend.serial.curve_models.ProjectivePoint.mk x y z).Y = y := rfl @[simp] theorem ProjPoint.mk_Z (x y z : Fe) : (backend.serial.curve_models.ProjectivePoint.mk x y z).Z = z := rfl @[simp] theorem ComplPoint.mk_X (x y z t : Fe) : (backend.serial.curve_models.CompletedPoint.mk x y z t).X = x := rfl @[simp] theorem ComplPoint.mk_Y (x y z t : Fe) : (backend.serial.curve_models.CompletedPoint.mk x y z t).Y = y := rfl @[simp] theorem ComplPoint.mk_Z (x y z t : Fe) : (backend.serial.curve_models.CompletedPoint.mk x y z t).Z = z := rfl @[simp] theorem ComplPoint.mk_T (x y z t : Fe) : (backend.serial.curve_models.CompletedPoint.mk x y z t).T = t := rfl @[simp] theorem ProjNiels.mk_Y_plus_X (a b c d : Fe) : (backend.serial.curve_models.ProjectiveNielsPoint.mk a b c d).Y_plus_X = a := rfl @[simp] theorem ProjNiels.mk_Y_minus_X (a b c d : Fe) : (backend.serial.curve_models.ProjectiveNielsPoint.mk a b c d).Y_minus_X = b := rfl @[simp] theorem ProjNiels.mk_Z (a b c d : Fe) : (backend.serial.curve_models.ProjectiveNielsPoint.mk a b c d).Z = c := rfl @[simp] theorem ProjNiels.mk_T2d (a b c d : Fe) : (backend.serial.curve_models.ProjectiveNielsPoint.mk a b c d).T2d = d := rfl @[simp] theorem AffNiels.mk_y_plus_x (a b c : Fe) : (backend.serial.curve_models.AffineNielsPoint.mk a b c).y_plus_x = a := rfl @[simp] theorem AffNiels.mk_y_minus_x (a b c : Fe) : (backend.serial.curve_models.AffineNielsPoint.mk a b c).y_minus_x = b := rfl @[simp] theorem AffNiels.mk_xy2d (a b c : Fe) : (backend.serial.curve_models.AffineNielsPoint.mk a b c).xy2d = c := rfl end CurveFieldProofs