mirror of
https://github.com/saymrwulf/betrusted-ed25519-verified.git
synced 2026-09-04 20:24:08 +00:00
453 lines
24 KiB
Text
453 lines
24 KiB
Text
/- ───────────────────────────────────────────────────────────────────────────
|
||
Proofs/EdConvert.lean — coordinate-level specs for the REPRESENTATION
|
||
CONVERSIONS between the four point models of curve25519/solana-ed25519.
|
||
|
||
CONTEXT. The Rust crate keeps Edwards points in four internal models
|
||
(see the header of Proofs/EdDenote.lean): extended ℙ³ (`EdwardsPoint`,
|
||
x = X/Z, y = Y/Z, Segre coherence X·Y = Z·T), projective ℙ²
|
||
(`ProjectivePoint`, x = X/Z, y = Y/Z), completed ℙ¹×ℙ¹
|
||
(`CompletedPoint`, x = X/Z, y = Y/T) and the readily-addable niels
|
||
caches (`ProjectiveNielsPoint` = (Y+X, Y−X, Z, T·2d),
|
||
`AffineNielsPoint`). The add/double kernels produce COMPLETED points;
|
||
the conversion methods specified here move the result back into the
|
||
model the next operation wants — each one is a handful of field
|
||
mul/square/add/sub calls and NOTHING else (no division ever happens at
|
||
runtime; the affine value is preserved because numerator and denominator
|
||
are multiplied by the same nonzero factor).
|
||
|
||
THIS FILE proves one spec per conversion, coordinate-level ONLY:
|
||
|
||
* `proj_as_extended_spec` — ProjectivePoint.as_extended
|
||
(X:Y:Z) ↦ (XZ : YZ : Z² : XY) [curve_models.rs:338-345]
|
||
* `compl_as_projective_spec` — CompletedPoint.as_projective
|
||
((X:Z),(Y:T)) ↦ (XT : YZ : ZT) [curve_models.rs:353-359]
|
||
* `compl_as_extended_spec` — CompletedPoint.as_extended
|
||
((X:Z),(Y:T)) ↦ (XT : YZ : ZT : XY) [curve_models.rs:365-372]
|
||
* `edwards_as_projective_niels_spec`— EdwardsPoint.as_projective_niels
|
||
(X:Y:Z:T) ↦ (Y+X, Y−X, Z, T·2d) [edwards.rs:528-535]
|
||
* `edwards_neg_spec` — Neg for &EdwardsPoint
|
||
(X:Y:Z:T) ↦ (−X : Y : Z : −T) [edwards.rs:844-851]
|
||
|
||
Each spec asserts, under the input validity predicate of EdDenote:
|
||
TOTALITY (the triple — every limb stays inside the dalek 2⁵²/2⁵⁴
|
||
discipline, so no u64/u128 overflow, no panic), the output VALIDITY
|
||
predicate (limb bounds + the Z ≠ 0 obligation that the Rust code never
|
||
states + Segre coherence where applicable), the per-field COORDINATE
|
||
EQUATIONS over 𝔽_p strictly in terms of the input struct-field
|
||
denotations (e.g. ⟪r.X⟫ = ⟪p.X⟫·⟪p.Z⟫ — no curve constants, no
|
||
division), and the resulting DENOTATION identities (edX/edY/projX/
|
||
projY/complX/complY agree across the conversion, resp. `IsNielsOf`).
|
||
The algebra-law packaging (group laws, OnCurve preservation) happens in
|
||
a later file (EdMain), which composes exactly these posts.
|
||
|
||
FUTURE WORK (deliberately skipped here):
|
||
* `EdwardsPoint.as_affine_niels` (edwards.rs:551-561) and
|
||
`to_affine`-style code — both run `invert` (a 254-squaring Fermat
|
||
chain); their specs belong next to the scalar-mul layer and need
|
||
`invert_spec` composition with the validity predicates.
|
||
|
||
PROOF TECHNIQUE. Identical to Proofs/InvertSpec.lean: unfold the
|
||
transpiled body, walk every field-op bind with `let* ⟨x, posts⟩ ← spec`
|
||
(the Aeneas step machinery), discharging each 2⁵⁴-bound side condition
|
||
with the `edis` macro below, then close the terminal `ok {…}` with
|
||
`spec_ok` and prove the conjunction — bound goals by `Bnd.mono`/
|
||
`norm_num`, value goals by rewriting the step posts and `ring`, and the
|
||
division identities by cross-multiplying with `fp_div_eq_div_iff`
|
||
(EdDenote) over the nonzero denominators.
|
||
|
||
Imports: Proofs/EdDenote.lean (validity predicates, denotations, the
|
||
EDWARDS_D2 characterization, division helpers) and Proofs/Square2Spec
|
||
(square2_spec' — not used by the conversions themselves, but imported
|
||
here so this file sits at the same layer as the doubling specs that
|
||
need it, keeping the downstream import graph linear).
|
||
Imported by: the forthcoming point-operation files (EdMain).
|
||
─────────────────────────────────────────────────────────────────────── -/
|
||
import Proofs.EdDenote
|
||
import Proofs.Square2Spec
|
||
open Aeneas Aeneas.Std Result
|
||
open Aeneas.Std.WP
|
||
open curve25519_dalek
|
||
|
||
set_option maxHeartbeats 4000000
|
||
set_option maxRecDepth 8000
|
||
|
||
namespace CurveFieldProofs
|
||
|
||
/-! ## Step-friendly wrappers for the remaining field ops
|
||
|
||
`mul_spec'`/`square_spec'`/`pow2k_spec'` (InvertSpec.lean) and
|
||
`square2_spec'` (Square2Spec.lean) are already `@[step]`-registered.
|
||
The point bodies additionally call `fe_add`, `fe_sub` and the
|
||
borrowed-`Neg` wrapper; their base specs (AddSpec.lean, SubNegSpec.lean)
|
||
take explicit limb lists, which the step machinery cannot invent, so —
|
||
exactly as in InvertSpec — we re-state them with the limbs repackaged
|
||
via `Fe.exists_limbs` and register the wrappers with `@[step]`. -/
|
||
|
||
/-- Step-friendly `fe_add` spec at the REDUCED input level.
|
||
|
||
Rust: `impl Add<&FieldElement51> for &FieldElement51`, u64/field.rs:58-73
|
||
(limbwise `a[i] + b[i]`, NO carry, NO reduction — Proofs/AddSpec.lean).
|
||
|
||
MATH: Bnd(a,2⁵²) and Bnd(b,2⁵²) ==> fe_add a b = ok r with
|
||
Bnd(r, 2⁵³) and ⟪r⟫ = ⟪a⟫ + ⟪b⟫ in 𝔽_p.
|
||
|
||
The 2⁵² → 2⁵³ instantiation is the one the point code lives on: every
|
||
`Y + X` in a niels-cache construction adds two REDUCED coordinates, and
|
||
the doubled bound 2⁵³ < 2⁵⁴ keeps the sum a legal input for any
|
||
subsequent mul/sub. Derived from `add_spec` (AddSpec.lean): its
|
||
pairwise-overflow hypothesis holds since a_i + b_i < 2⁵² + 2⁵² = 2⁵³
|
||
< 2⁶⁴; its generic bound law `∀ c, Bnd a c → Bnd b c → Bnd r (2c)` is
|
||
instantiated at c = 2⁵²; its EXACT ℕ value equation
|
||
`feVal r = feVal a + feVal b` is cast into 𝔽_p by `push_cast`.
|
||
|
||
WHY NEEDED: `as_projective_niels` below computes Y_plus_X with this
|
||
unreduced add; the addition/doubling kernels of the next file do too. -/
|
||
@[step]
|
||
theorem add_spec'' (a b : Fe) (hba : Bnd a (2^52)) (hbb : Bnd b (2^52)) :
|
||
fe_add a b ⦃ r => Bnd r (2^53) ∧ ⟪r⟫ = ⟪a⟫ + ⟪b⟫ ⦄ := by
|
||
-- materialize the limbs of both inputs and restate the bounds limbwise
|
||
obtain ⟨a0, a1, a2, a3, a4, ha⟩ := Fe.exists_limbs a
|
||
obtain ⟨b0, b1, b2, b3, b4, hb⟩ := Fe.exists_limbs b
|
||
have hba' := hba
|
||
have hbb' := hbb
|
||
rw [Bnd_eq a a0 a1 a2 a3 a4 _ ha] at hba'
|
||
rw [Bnd_eq b b0 b1 b2 b3 b4 _ hb] at hbb'
|
||
-- run the base spec; the 5 pairwise sums are < 2⁵³ < 2⁶⁴ (omega)
|
||
apply spec_mono (add_spec a b a0 a1 a2 a3 a4 b0 b1 b2 b3 b4 ha hb
|
||
⟨by omega, by omega, by omega, by omega, by omega⟩)
|
||
rintro r ⟨-, hval, hbnd⟩
|
||
constructor
|
||
· -- bound: the generic law at c = 2⁵² gives 2·2⁵² = 2⁵³
|
||
exact (hbnd _ hba hbb).mono (by norm_num)
|
||
· -- value: cast the exact ℕ equation feVal r = feVal a + feVal b to 𝔽_p
|
||
simp only [denote]
|
||
rw [hval]
|
||
push_cast
|
||
ring
|
||
|
||
/-- Step-friendly `fe_sub` spec (limbs hidden, `@[step]`-registered).
|
||
|
||
Rust: `impl Sub<&FieldElement51> for &FieldElement51`,
|
||
u64/field.rs:84-101 (adds the constant 16p limbwise before subtracting
|
||
so u64 subtraction cannot underflow, then reduces —
|
||
Proofs/SubNegSpec.lean).
|
||
|
||
MATH: Bnd(a,2⁵⁴) and Bnd(b,2⁵⁴) ==> fe_sub a b = ok r with
|
||
Bnd(r, 2⁵²) and ⟪r⟫ = ⟪a⟫ − ⟪b⟫ in 𝔽_p.
|
||
|
||
WHY NEEDED: `Y − X` in the niels-cache construction below; the
|
||
addition/doubling kernels of the next file subtract throughout. -/
|
||
@[step]
|
||
theorem sub_spec'' (a b : Fe) (hba : Bnd a (2^54)) (hbb : Bnd b (2^54)) :
|
||
fe_sub a b ⦃ r => Bnd r (2^52) ∧ ⟪r⟫ = ⟪a⟫ - ⟪b⟫ ⦄ := by
|
||
obtain ⟨a0, a1, a2, a3, a4, ha⟩ := Fe.exists_limbs a
|
||
obtain ⟨b0, b1, b2, b3, b4, hb⟩ := Fe.exists_limbs b
|
||
exact sub_spec a b a0 a1 a2 a3 a4 b0 b1 b2 b3 b4 ha hb hba hbb
|
||
|
||
/-- Step-friendly spec for the borrowed-`Neg` wrapper.
|
||
|
||
Rust: `impl Neg for &FieldElement51 { fn neg }`, u64/field.rs:218-222 —
|
||
literally `self.negate()`; transpiled at gen/CurveField/Funs.lean:1732
|
||
as a one-line `do`-wrapper around `FieldElement51.negate`
|
||
(= `fe_neg`, verified in Proofs/SubNegSpec.lean).
|
||
|
||
MATH: Bnd(a,2⁵⁴) ==> neg a = ok r with Bnd(r, 2⁵²) and
|
||
⟪r⟫ = −⟪a⟫ in 𝔽_p.
|
||
|
||
Stated about the WRAPPER (the name the point bodies actually call) so
|
||
the step machinery matches it syntactically.
|
||
WHY NEEDED: `EdwardsPoint::neg` below negates X and T through it. -/
|
||
@[step]
|
||
theorem neg_spec'' (a : Fe) (hba : Bnd a (2^54)) :
|
||
SharedAFieldElement51.Insts.CoreOpsArithNegFieldElement51.neg a
|
||
⦃ r => Bnd r (2^52) ∧ ⟪r⟫ = -⟪a⟫ ⦄ := by
|
||
-- the wrapper body is exactly `negate self`
|
||
unfold SharedAFieldElement51.Insts.CoreOpsArithNegFieldElement51.neg
|
||
obtain ⟨a0, a1, a2, a3, a4, ha⟩ := Fe.exists_limbs a
|
||
exact neg_spec a a0 a1 a2 a3 a4 ha hba
|
||
|
||
/-- Discharge macro for the step side conditions of this file (local copy of
|
||
InvertSpec's `bnd` — tactic macros do not travel across files): every
|
||
field-op step demands `Bnd · (2⁵⁴)` (or 2⁵² for `add_spec''`) on its
|
||
inputs while the context holds the tighter validity bounds (2⁵²/2⁵³
|
||
from the predicates, 2⁵¹+2¹³ from previous steps); close such goals by
|
||
direct assumption, by `scalar_tac`, or by weakening any `Bnd`
|
||
hypothesis with `Bnd.mono` + `norm_num`.
|
||
(`name :=` disambiguates the generated syntax-kind declaration from the
|
||
sibling files' `edis` macros so they can all be imported together.) -/
|
||
macro (name := edisConvert) "edis" : tactic =>
|
||
`(tactic| (first
|
||
| assumption
|
||
| scalar_tac
|
||
| exact Bnd.mono (by assumption) (by norm_num)))
|
||
|
||
/-! ## 1. ProjectivePoint → EdwardsPoint -/
|
||
|
||
/-- Rust: `ProjectivePoint::as_extended`, curve_models.rs:338-345 —
|
||
"Convert this point from the ℙ² model to the ℙ³ model. This costs
|
||
3M + 1S."; transpiled at gen/CurveField/Funs.lean:1360.
|
||
|
||
CODE: X' = X·Z, Y' = Y·Z, Z' = Z², T' = X·Y.
|
||
|
||
MATH: ProjValid p ==> as_extended p = ok r with
|
||
* ExtValid r — all four outputs are mul/square outputs (bound
|
||
2⁵¹+2¹³ ≤ 2⁵²); ⟪r.Z⟫ = ⟪p.Z⟫² ≠ 0 since ⟪p.Z⟫ ≠ 0; Segre holds
|
||
BY CONSTRUCTION: (XZ)·(YZ) = (Z²)·(XY) — pure `ring`;
|
||
* the four coordinate equations
|
||
⟪r.X⟫ = ⟪p.X⟫·⟪p.Z⟫, ⟪r.Y⟫ = ⟪p.Y⟫·⟪p.Z⟫,
|
||
⟪r.Z⟫ = ⟪p.Z⟫·⟪p.Z⟫, ⟪r.T⟫ = ⟪p.X⟫·⟪p.Y⟫;
|
||
* the affine point is UNCHANGED: edX r = projX p, edY r = projY p
|
||
(numerator and denominator both gained the factor ⟪p.Z⟫ ≠ 0:
|
||
(XZ)/(Z²) = X/Z, cross-multiplied via `fp_div_eq_div_iff`).
|
||
|
||
WHY NEEDED: `double_and_add`-style chains re-extend the running
|
||
projective point before each extended-coordinates addition. -/
|
||
theorem proj_as_extended_spec (p : ProjPoint) (hp : ProjValid p) :
|
||
backend.serial.curve_models.ProjectivePoint.as_extended p ⦃ r =>
|
||
ExtValid r ∧
|
||
⟪r.X⟫ = ⟪p.X⟫ * ⟪p.Z⟫ ∧ ⟪r.Y⟫ = ⟪p.Y⟫ * ⟪p.Z⟫ ∧
|
||
⟪r.Z⟫ = ⟪p.Z⟫ * ⟪p.Z⟫ ∧ ⟪r.T⟫ = ⟪p.X⟫ * ⟪p.Y⟫ ∧
|
||
edX r = projX p ∧ edY r = projY p ⦄ := by
|
||
obtain ⟨hX, hY, hZ, hZ0⟩ := hp
|
||
-- expose the 4-step transpiled body
|
||
unfold backend.serial.curve_models.ProjectivePoint.as_extended
|
||
-- fe ← mul X Z with ⟪fe⟫ = ⟪p.X⟫·⟪p.Z⟫
|
||
let* ⟨ fe, fe_post1, fe_post2 ⟩ ← mul_spec' by edis
|
||
-- fe1 ← mul Y Z with ⟪fe1⟫ = ⟪p.Y⟫·⟪p.Z⟫
|
||
let* ⟨ fe1, fe1_post1, fe1_post2 ⟩ ← mul_spec' by edis
|
||
-- fe2 ← square Z with ⟪fe2⟫ = ⟪p.Z⟫·⟪p.Z⟫
|
||
let* ⟨ fe2, fe2_post1, fe2_post2 ⟩ ← square_spec' by edis
|
||
-- fe3 ← mul X Y with ⟪fe3⟫ = ⟪p.X⟫·⟪p.Y⟫
|
||
let* ⟨ fe3, fe3_post1, fe3_post2 ⟩ ← mul_spec' by edis
|
||
-- the terminal `ok {X := fe, Y := fe1, Z := fe2, T := fe3}` was already
|
||
-- consumed by the step machinery (projections collapsed); unfold the
|
||
-- predicate and the four denotations to expose the conjunction
|
||
simp only [ExtValid, edX, edY, projX, projY]
|
||
-- the new denominator ⟪fe2⟫ = ⟪p.Z⟫² is nonzero
|
||
have hrZ : ⟪fe2⟫ ≠ 0 := by
|
||
rw [fe2_post2]; exact mul_ne_zero hZ0 hZ0
|
||
refine ⟨⟨fe_post1.mono (by norm_num), fe1_post1.mono (by norm_num),
|
||
fe2_post1.mono (by norm_num), fe3_post1.mono (by norm_num),
|
||
hrZ, ?_⟩,
|
||
fe_post2, fe1_post2, fe2_post2, fe3_post2, ?_, ?_⟩
|
||
· -- Segre coherence: (XZ)·(YZ) = (Z²)·(XY)
|
||
rw [fe_post2, fe1_post2, fe2_post2, fe3_post2]; ring
|
||
· -- x preserved: (XZ)/(Z²) = X/Z ⟺ (XZ)·Z = X·Z²
|
||
rw [fp_div_eq_div_iff hrZ hZ0, fe_post2, fe2_post2]; ring
|
||
· -- y preserved: (YZ)/(Z²) = Y/Z ⟺ (YZ)·Z = Y·Z²
|
||
rw [fp_div_eq_div_iff hrZ hZ0, fe1_post2, fe2_post2]; ring
|
||
|
||
/-! ## 2. CompletedPoint → ProjectivePoint -/
|
||
|
||
/-- Rust: `CompletedPoint::as_projective`, curve_models.rs:353-359 —
|
||
"Convert this point from the ℙ¹×ℙ¹ model to the ℙ² model. This costs
|
||
3M."; transpiled at gen/CurveField/Funs.lean:1379.
|
||
|
||
CODE: X' = X·T, Y' = Y·Z, Z' = Z·T.
|
||
|
||
MATH: ComplValid p ==> as_projective p = ok r with
|
||
* ProjValid r — three mul outputs (≤ 2⁵²), and
|
||
⟪r.Z⟫ = ⟪p.Z⟫·⟪p.T⟫ ≠ 0 because BOTH completed denominators are
|
||
nonzero (`mul_ne_zero`);
|
||
* the coordinate equations ⟪r.X⟫ = ⟪p.X⟫·⟪p.T⟫,
|
||
⟪r.Y⟫ = ⟪p.Y⟫·⟪p.Z⟫, ⟪r.Z⟫ = ⟪p.Z⟫·⟪p.T⟫;
|
||
* the affine point is UNCHANGED: projX r = complX p (the x-fraction
|
||
X/Z was multiplied through by T) and projY r = complY p (the
|
||
y-fraction Y/T was multiplied through by Z) — the two ℙ¹ lines are
|
||
put over the COMMON denominator Z·T.
|
||
|
||
WHY NEEDED: every doubling step of a scalar-mul ladder feeds the
|
||
completed output back as a projective point through this conversion. -/
|
||
theorem compl_as_projective_spec (p : ComplPoint) (hp : ComplValid p) :
|
||
backend.serial.curve_models.CompletedPoint.as_projective p ⦃ r =>
|
||
ProjValid r ∧
|
||
⟪r.X⟫ = ⟪p.X⟫ * ⟪p.T⟫ ∧ ⟪r.Y⟫ = ⟪p.Y⟫ * ⟪p.Z⟫ ∧
|
||
⟪r.Z⟫ = ⟪p.Z⟫ * ⟪p.T⟫ ∧
|
||
projX r = complX p ∧ projY r = complY p ⦄ := by
|
||
obtain ⟨hX, hY, hZ, hT, hZ0, hT0⟩ := hp
|
||
-- expose the 3-step transpiled body
|
||
unfold backend.serial.curve_models.CompletedPoint.as_projective
|
||
-- fe ← mul X T with ⟪fe⟫ = ⟪p.X⟫·⟪p.T⟫
|
||
let* ⟨ fe, fe_post1, fe_post2 ⟩ ← mul_spec' by edis
|
||
-- fe1 ← mul Y Z with ⟪fe1⟫ = ⟪p.Y⟫·⟪p.Z⟫
|
||
let* ⟨ fe1, fe1_post1, fe1_post2 ⟩ ← mul_spec' by edis
|
||
-- fe2 ← mul Z T with ⟪fe2⟫ = ⟪p.Z⟫·⟪p.T⟫
|
||
let* ⟨ fe2, fe2_post1, fe2_post2 ⟩ ← mul_spec' by edis
|
||
-- the terminal `ok {X := fe, Y := fe1, Z := fe2}` was already consumed by
|
||
-- the step machinery (projections collapsed); unfold predicate/denotations
|
||
simp only [ProjValid, projX, projY, complX, complY]
|
||
-- the common denominator ⟪fe2⟫ = ⟪p.Z⟫·⟪p.T⟫ is nonzero
|
||
have hrZ : ⟪fe2⟫ ≠ 0 := by
|
||
rw [fe2_post2]; exact mul_ne_zero hZ0 hT0
|
||
refine ⟨⟨fe_post1.mono (by norm_num), fe1_post1.mono (by norm_num),
|
||
fe2_post1.mono (by norm_num), hrZ⟩,
|
||
fe_post2, fe1_post2, fe2_post2, ?_, ?_⟩
|
||
· -- x preserved: (XT)/(ZT) = X/Z ⟺ (XT)·Z = X·(ZT)
|
||
rw [fp_div_eq_div_iff hrZ hZ0, fe_post2, fe2_post2]; ring
|
||
· -- y preserved: (YZ)/(ZT) = Y/T ⟺ (YZ)·T = Y·(ZT)
|
||
rw [fp_div_eq_div_iff hrZ hT0, fe1_post2, fe2_post2]; ring
|
||
|
||
/-! ## 3. CompletedPoint → EdwardsPoint -/
|
||
|
||
/-- Rust: `CompletedPoint::as_extended`, curve_models.rs:365-372 —
|
||
"Convert this point from the ℙ¹×ℙ¹ model to the ℙ³ model. This costs
|
||
4M."; transpiled at gen/CurveField/Funs.lean:1397.
|
||
|
||
CODE: X' = X·T, Y' = Y·Z, Z' = Z·T, T' = X·Y.
|
||
|
||
MATH: ComplValid p ==> as_extended p = ok r with
|
||
* ExtValid r — four mul outputs (≤ 2⁵²); ⟪r.Z⟫ = ⟪p.Z⟫·⟪p.T⟫ ≠ 0
|
||
(`mul_ne_zero` on the two completed denominators); Segre BY
|
||
CONSTRUCTION: (XT)·(YZ) = (ZT)·(XY) — pure `ring` (this is
|
||
exactly why the extended T-cache is computed as X·Y here:
|
||
T'/Z' = (X/Z)·(Y/T) = x·y);
|
||
* the coordinate equations ⟪r.X⟫ = ⟪p.X⟫·⟪p.T⟫,
|
||
⟪r.Y⟫ = ⟪p.Y⟫·⟪p.Z⟫, ⟪r.Z⟫ = ⟪p.Z⟫·⟪p.T⟫, ⟪r.T⟫ = ⟪p.X⟫·⟪p.Y⟫;
|
||
* the affine point is UNCHANGED: edX r = complX p,
|
||
edY r = complY p (same common-denominator argument as
|
||
`compl_as_projective_spec`).
|
||
|
||
WHY NEEDED: the result of a point addition (a completed point) is
|
||
re-extended through this conversion whenever the next operation needs
|
||
the T-cache (e.g. another addition or a niels-cache build). -/
|
||
theorem compl_as_extended_spec (p : ComplPoint) (hp : ComplValid p) :
|
||
backend.serial.curve_models.CompletedPoint.as_extended p ⦃ r =>
|
||
ExtValid r ∧
|
||
⟪r.X⟫ = ⟪p.X⟫ * ⟪p.T⟫ ∧ ⟪r.Y⟫ = ⟪p.Y⟫ * ⟪p.Z⟫ ∧
|
||
⟪r.Z⟫ = ⟪p.Z⟫ * ⟪p.T⟫ ∧ ⟪r.T⟫ = ⟪p.X⟫ * ⟪p.Y⟫ ∧
|
||
edX r = complX p ∧ edY r = complY p ⦄ := by
|
||
obtain ⟨hX, hY, hZ, hT, hZ0, hT0⟩ := hp
|
||
-- expose the 4-step transpiled body
|
||
unfold backend.serial.curve_models.CompletedPoint.as_extended
|
||
-- fe ← mul X T with ⟪fe⟫ = ⟪p.X⟫·⟪p.T⟫
|
||
let* ⟨ fe, fe_post1, fe_post2 ⟩ ← mul_spec' by edis
|
||
-- fe1 ← mul Y Z with ⟪fe1⟫ = ⟪p.Y⟫·⟪p.Z⟫
|
||
let* ⟨ fe1, fe1_post1, fe1_post2 ⟩ ← mul_spec' by edis
|
||
-- fe2 ← mul Z T with ⟪fe2⟫ = ⟪p.Z⟫·⟪p.T⟫
|
||
let* ⟨ fe2, fe2_post1, fe2_post2 ⟩ ← mul_spec' by edis
|
||
-- fe3 ← mul X Y with ⟪fe3⟫ = ⟪p.X⟫·⟪p.Y⟫
|
||
let* ⟨ fe3, fe3_post1, fe3_post2 ⟩ ← mul_spec' by edis
|
||
-- the terminal `ok {X := fe, Y := fe1, Z := fe2, T := fe3}` was already
|
||
-- consumed by the step machinery (projections collapsed); unfold
|
||
-- predicate/denotations
|
||
simp only [ExtValid, edX, edY, complX, complY]
|
||
-- the common denominator ⟪fe2⟫ = ⟪p.Z⟫·⟪p.T⟫ is nonzero
|
||
have hrZ : ⟪fe2⟫ ≠ 0 := by
|
||
rw [fe2_post2]; exact mul_ne_zero hZ0 hT0
|
||
refine ⟨⟨fe_post1.mono (by norm_num), fe1_post1.mono (by norm_num),
|
||
fe2_post1.mono (by norm_num), fe3_post1.mono (by norm_num),
|
||
hrZ, ?_⟩,
|
||
fe_post2, fe1_post2, fe2_post2, fe3_post2, ?_, ?_⟩
|
||
· -- Segre coherence: (XT)·(YZ) = (ZT)·(XY)
|
||
rw [fe_post2, fe1_post2, fe2_post2, fe3_post2]; ring
|
||
· -- x preserved: (XT)/(ZT) = X/Z ⟺ (XT)·Z = X·(ZT)
|
||
rw [fp_div_eq_div_iff hrZ hZ0, fe_post2, fe2_post2]; ring
|
||
· -- y preserved: (YZ)/(ZT) = Y/T ⟺ (YZ)·T = Y·(ZT)
|
||
rw [fp_div_eq_div_iff hrZ hT0, fe1_post2, fe2_post2]; ring
|
||
|
||
/-! ## 4. EdwardsPoint → ProjectiveNielsPoint -/
|
||
|
||
/-- Rust: `EdwardsPoint::as_projective_niels`, edwards.rs:528-535 —
|
||
"Convert to a ProjectiveNielsPoint"; transpiled at
|
||
gen/CurveField/Funs.lean:3165.
|
||
|
||
CODE: Y_plus_X = Y + X, Y_minus_X = Y − X, Z = Z,
|
||
T2d = T · EDWARDS_D2.
|
||
|
||
MATH: ExtValid P ==> as_projective_niels P = ok r with
|
||
* ProjNielsValid r — Y_plus_X is the single UNREDUCED add of two
|
||
reduced coordinates (bound 2⁵³, `add_spec''`), Y_minus_X a reduced
|
||
sub output (2⁵² ≤ 2⁵³), Z is P.Z verbatim (2⁵²), T2d a mul output
|
||
(≤ 2⁵²); ⟪r.Z⟫ = ⟪P.Z⟫ ≠ 0 carries over;
|
||
* IsNielsOf r P — the cache really derives from P (the EXACT shape
|
||
EdDenote fixed):
|
||
⟪r.Y_plus_X⟫ = ⟪P.Y⟫ + ⟪P.X⟫,
|
||
⟪r.Y_minus_X⟫ = ⟪P.Y⟫ − ⟪P.X⟫,
|
||
⟪r.Z⟫ = ⟪P.Z⟫,
|
||
121666·⟪r.T2d⟫ = −243330·⟪P.T⟫
|
||
(the last is the denominator-free characterization of
|
||
⟪r.T2d⟫ = ⟪P.T⟫·2d: the EDWARDS_D2 step yields the table entry D2
|
||
with 121666·⟪D2⟫ = −243330 (`edwards_d2_spec`, EdDenote) and the
|
||
mul step yields ⟪r.T2d⟫ = ⟪P.T⟫·⟪D2⟫; multiply the latter by
|
||
121666 and substitute).
|
||
|
||
WHY NEEDED: every extended-coordinates point addition `P + Q` first
|
||
caches Q in this form; the next file's add/sub specs consume exactly
|
||
`ProjNielsValid` + `IsNielsOf`. -/
|
||
theorem edwards_as_projective_niels_spec (P : EdPoint) (hP : ExtValid P) :
|
||
edwards.EdwardsPoint.as_projective_niels P ⦃ r =>
|
||
ProjNielsValid r ∧ IsNielsOf r P ⦄ := by
|
||
obtain ⟨hX, hY, hZ, hT, hZ0, -⟩ := hP
|
||
-- expose the 4-step transpiled body
|
||
unfold edwards.EdwardsPoint.as_projective_niels
|
||
-- fe ← add Y X with ⟪fe⟫ = ⟪P.Y⟫ + ⟪P.X⟫, Bnd 2⁵³
|
||
-- (no discharge needed: the 2⁵² preconditions are hypotheses verbatim)
|
||
let* ⟨ fe, fe_post1, fe_post2 ⟩ ← add_spec''
|
||
-- fe1 ← sub Y X with ⟪fe1⟫ = ⟪P.Y⟫ − ⟪P.X⟫, Bnd 2⁵²
|
||
let* ⟨ fe1, fe1_post1, fe1_post2 ⟩ ← sub_spec'' by edis
|
||
-- fe2 ← EDWARDS_D2 with 121666·⟪fe2⟫ = −243330 (the 2d table
|
||
-- entry; a closed constant — no preconditions to discharge)
|
||
let* ⟨ fe2, fe2_post1, fe2_post2 ⟩ ← edwards_d2_spec
|
||
-- fe3 ← mul T fe2 with ⟪fe3⟫ = ⟪P.T⟫·⟪fe2⟫
|
||
let* ⟨ fe3, fe3_post1, fe3_post2 ⟩ ← mul_spec' by edis
|
||
-- the terminal `ok {…}` was already consumed by the step machinery (which
|
||
-- also collapsed the constructor projections, so the ⟪r.Z⟫ = ⟪P.Z⟫
|
||
-- conjunct is already `True`); unfold the two predicates to expose the
|
||
-- conjunction
|
||
simp only [ProjNielsValid, IsNielsOf]
|
||
refine ⟨⟨fe_post1, fe1_post1.mono (by norm_num), hZ,
|
||
fe3_post1.mono (by norm_num), hZ0⟩,
|
||
fe_post2, fe1_post2, trivial, ?_⟩
|
||
-- T2d characterization: 121666·(⟪P.T⟫·⟪fe2⟫) = ⟪P.T⟫·(121666·⟪fe2⟫)
|
||
-- = ⟪P.T⟫·(−243330) = −243330·⟪P.T⟫
|
||
calc (121666 : Fp) * ⟪fe3⟫
|
||
= ⟪P.T⟫ * ((121666 : Fp) * ⟪fe2⟫) := by rw [fe3_post2]; ring
|
||
_ = -243330 * ⟪P.T⟫ := by rw [fe2_post2]; ring
|
||
|
||
/-! ## 5. Negation of an EdwardsPoint -/
|
||
|
||
/-- Rust: `impl Neg for &EdwardsPoint { fn neg }`, edwards.rs:844-851 —
|
||
negate X and T, keep Y and Z; transpiled at
|
||
gen/CurveField/Funs.lean:3371.
|
||
|
||
CODE: r = (−X : Y : Z : −T).
|
||
|
||
MATH: ExtValid P ==> neg P = ok r with
|
||
* ExtValid r — the two negate outputs are reduced (2⁵²,
|
||
`neg_spec''`), Y/Z are P's verbatim; ⟪r.Z⟫ = ⟪P.Z⟫ ≠ 0 carries
|
||
over; Segre is PRESERVED: (−X)·Y = Z·(−T) follows from
|
||
X·Y = Z·T by negating both sides;
|
||
* the coordinate equations ⟪r.X⟫ = −⟪P.X⟫, ⟪r.Y⟫ = ⟪P.Y⟫,
|
||
⟪r.Z⟫ = ⟪P.Z⟫, ⟪r.T⟫ = −⟪P.T⟫;
|
||
* the denoted affine point is the EDWARDS NEGATIVE:
|
||
edX r = −(edX P) (−X/Z = −(X/Z), `neg_div`) and edY r = edY P
|
||
— on a twisted Edwards curve −(x, y) = (−x, y).
|
||
|
||
WHY NEEDED: subtraction `P − Q` is implemented as `P + (−Q)`; the
|
||
group-law layer (EdMain) packages this spec as the implementation of
|
||
`edNeg` (EdCurve.lean). -/
|
||
theorem edwards_neg_spec (P : EdPoint) (hP : ExtValid P) :
|
||
SharedAEdwardsPoint.Insts.CoreOpsArithNegEdwardsPoint.neg P ⦃ r =>
|
||
ExtValid r ∧
|
||
⟪r.X⟫ = -⟪P.X⟫ ∧ ⟪r.Y⟫ = ⟪P.Y⟫ ∧ ⟪r.Z⟫ = ⟪P.Z⟫ ∧ ⟪r.T⟫ = -⟪P.T⟫ ∧
|
||
edX r = -(edX P) ∧ edY r = edY P ⦄ := by
|
||
obtain ⟨hX, hY, hZ, hT, hZ0, hSeg⟩ := hP
|
||
-- expose the 2-step transpiled body
|
||
unfold SharedAEdwardsPoint.Insts.CoreOpsArithNegEdwardsPoint.neg
|
||
-- fe ← neg X with ⟪fe⟫ = −⟪P.X⟫, Bnd 2⁵²
|
||
let* ⟨ fe, fe_post1, fe_post2 ⟩ ← neg_spec'' by edis
|
||
-- fe1 ← neg T with ⟪fe1⟫ = −⟪P.T⟫, Bnd 2⁵²
|
||
let* ⟨ fe1, fe1_post1, fe1_post2 ⟩ ← neg_spec'' by edis
|
||
-- the terminal `ok { P with X := fe, T := fe1 }` (= ok (mk fe P.Y P.Z fe1))
|
||
-- was already consumed by the step machinery, which also collapsed the
|
||
-- projections: the Y/Z coordinate equations and `edY r = edY P` are
|
||
-- already `True`; unfold the predicate/denotation defs to expose the rest
|
||
simp only [ExtValid, edX, edY]
|
||
refine ⟨⟨fe_post1, hY, hZ, fe1_post1, hZ0, ?_⟩,
|
||
fe_post2, fe1_post2, ?_, trivial⟩
|
||
· -- Segre preserved: (−X)·Y = −(X·Y) = −(Z·T) = Z·(−T)
|
||
rw [fe_post2, fe1_post2, neg_mul, hSeg, mul_neg]
|
||
· -- x negated: ⟪fe⟫/⟪P.Z⟫ = (−⟪P.X⟫)/⟪P.Z⟫ = −(⟪P.X⟫/⟪P.Z⟫)
|
||
rw [fe_post2, neg_div]
|
||
|
||
end CurveFieldProofs
|