dalek-ed25519-verified/verification/Proofs/EdDenote.lean
mrwulf 8ce599c4aa group-law layer: complete twisted Edwards addition law proven
Extraction widened to backend::serial::curve_models + edwards (own gen/,
209 defs). Adapted the reference Ed* proof suite: namespace rename +
Curve25519TraitsIdentity -> Curve25519_dalekTraitsIdentity. New opaque
externals (scalar_mul backends, get_selected_backend, decompress, sum,
from_slice) are axioms outside both certificate cones — verified by the
Phase-3 audit. All 21 proofs compile; field certificates unchanged.

Not claimed (matching the reference solution's honest scope): edAdd
associativity, scalar multiplication, decompress/compress, AVX2 backend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:50:42 +02:00

655 lines
33 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/- ───────────────────────────────────────────────────────────────────────────
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 HisilWongCarterDawson 2008 and the ℙ¹×ℙ¹ picture of
CostelloSmith 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, YX, Z, T·2d) — a CACHE of readily-added
combinations of an extended point;
* `AffineNielsPoint` (y+x, yx, 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 YX, 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) = ((p121665) : 𝔽_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, yx, 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, yx = 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, YX, 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, YX = 10, 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