risc0-ed25519-verified/verification/Proofs/EdAddAffNiels.lean
mrwulf 681399d0de lean-guard: disable core dumps (no more apport popups on capped aborts)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:23:30 +02:00

376 lines
21 KiB
Text
Raw Permalink 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/EdAddAffNiels.lean — coordinate-level specs for the MIXED addition
kernels EdwardsPoint ± AffineNielsPoint -> CompletedPoint and for the
negation of an AffineNielsPoint.
CONTEXT. The Rust crate curve25519/solana-ed25519 adds an extended point
P = (X₁:Y₁:Z₁:T₁) and a PRECOMPUTED affine cache N = (y+x, yx, 2dxy)
(an `AffineNielsPoint`, implicit Z = 1 — see Proofs/EdDenote.lean) with
the HisilWongCarterDawson mixed formulas
(src/backend/serial/curve_models.rs:458-472 for `add`, 479-493 for `sub`):
PP = (Y₁+X₁)·(y+x) MM = (Y₁X₁)·(yx)
Txy2d = T₁·(2dxy) Z2 = Z₁+Z₁
add: (X, Y, Z, T) = (PPMM, PP+MM, Z2+Txy2d, Z2Txy2d)
sub: mirrored — multiply CROSSWISE (PM = (Y₁+X₁)(yx),
MP = (Y₁X₁)(y+x)) and FLIP the sign of Txy2d in Z/T:
(X, Y, Z, T) = (PMMP, PM+MP, Z2Txy2d, Z2+Txy2d)
(3 muls, 3 unreduced adds, 3 reduced subs each — one mul fewer than the
projective-Niels kernels because the affine cache has no Z field).
`neg` (curve_models.rs:516-522) negates the cached point by SWAPPING the
y+x / yx fields and negating 2dxy: (x, y) = (x, y), so
y+(x) = yx, y(x) = y+x, 2d(x)y = 2dxy.
THIS FILE proves, in the STATEMENT POLICY of the point-spec phase
(coordinate level ONLY — no curve constants, no division, no OnCurve;
the algebra-law packaging happens in a later file):
* `add_affniels_spec` / `sub_affniels_spec` — under `ExtValid Pt` and
`AffNielsValid N` (the limb-bound + Z ≠ 0 validity predicates of
Proofs/EdDenote.lean) the kernels run PANIC-FREE, every output field
carries the exact bound its producing field op guarantees
(sub: 2⁵², single unreduced add of two reduced/mul outputs: 2⁵³,
unreduced add consuming another unreduced add: 2⁵⁴), and the four
coordinates satisfy the formulas above, stated STRICTLY in terms of
the input struct-field denotations ⟪Pt.X⟫ … ⟪N.xy2d⟫;
* `affniels_neg_spec` — negation runs, preserves `AffNielsValid`, and
denotes the field swap + negation.
BOUND BOOKKEEPING (the entire panic-freedom argument). Inputs carry
Bnd 2⁵² (ExtValid) resp. Bnd 2⁵³/2⁵² (AffNielsValid); the field ops
require Bnd 2⁵⁴ (mul/sub, MulSpec/SubNegSpec) resp. pairwise limb sums
< 2⁶⁴ (the unreduced add, AddSpec). Chasing the chain:
Y₁+X₁, Z2 : add of two 2⁵² values -> 2⁵³ (< 2⁵⁴ ✓)
Y₁X₁ : sub -> 2⁵²
PP/MM/PM/MP/Txy2d : mul of ≤ 2⁵³/2⁵³ inputs -> 2⁵¹+2¹³
X-output : sub of two mul outputs -> 2⁵²
Y-output : add of two mul outputs -> 2⁵³
Z2 ± Txy2d: sub -> 2⁵²; add of 2⁵³ + (2⁵¹+2¹³) values -> 2⁵⁴
so every intermediate is a legal input for its consumer, and the output
bounds exposed below (X: 2⁵², Y: 2⁵³, then add: Z 2⁵⁴ / T 2⁵²,
sub: Z 2⁵² / T 2⁵⁴) are the TRUE per-field bounds of the chain.
PROOF TECHNIQUE: the InvertSpec playbook — unfold the transpiled body,
walk it with `let* ⟨x, posts…⟩ ← spec by edis` (one field op per line;
`edis` discharges each op's Bnd side condition by hypothesis weakening;
where the needed bound is verbatim in context the `let*` machinery
discharges it itself and no `by` block is given). The final `let*`
also reduces the trailing `ok {…}` and collapses the constructor
projections, so the four coordinate equations close by rewriting the
recorded postconditions (plus `ring` where 2·Z appears as Z+Z).
The three `aff_*`-prefixed wrappers re-state AddSpec's `add_spec` (whose
hypotheses name all 10 limbs, so the `let*` machinery cannot apply it
directly) and SubNegSpec's `sub_spec`/`neg_spec` in hypothesis-light form,
exactly like `mul_spec'` (InvertSpec.lean); prefixed `aff_` to avoid name
collisions with the sibling point-op spec files, which declare their own.
Nothing in gen/ is modified; we only run the transpiled code.
Imports: Proofs/EdDenote (validity predicates, mk_* lemmas, ⟪·⟫/Bnd via
FieldMain) and Proofs/Square2Spec (uniform field-op spec environment of
the point-op phase). Imported by: the forthcoming Edwards algebra layer.
─────────────────────────────────────────────────────────────────────── -/
import Proofs.EdDenote
import Proofs.Square2Spec
open Aeneas Aeneas.Std Result
open curve25519_dalek
set_option maxHeartbeats 4000000
set_option maxRecDepth 8000
namespace CurveFieldProofs
-- the weakest-precondition layer: spec_mono / spec_ok used below
open Aeneas.Std.WP
/-- Discharge: linear arithmetic, or a `Bnd` weakening from any hypothesis.
Every field op consumed below needs its inputs bounded (mul/sub: 2⁵⁴,
the add wrappers: 2⁵²/2⁵³), while the producing step only recorded a
tighter bound (2⁵², 2⁵³ or 2⁵¹+2¹³); this side-condition tactic closes
such goals either by `scalar_tac` or by weakening an existing `Bnd _ c`
hypothesis with `Bnd.mono` and `c ≤ c'` by `norm_num`. Passed as the
discharger to every `let*` step. (Local re-declaration of InvertSpec's
`bnd` macro — macros are kept file-local in this development.)
(`name :=` disambiguates the generated syntax-kind declaration from the
sibling files' `edis` macros so they can all be imported together.) -/
macro (name := edisAffNiels) "edis" : tactic =>
`(tactic| (first
| scalar_tac
| exact Bnd.mono (by assumption) (by norm_num)))
/-! ## Hypothesis-light wrappers for the unreduced add, sub and negate
The base specs take the 5 limbs of every argument as explicit variables
(their proofs compute limb by limb), so the `let*` machinery cannot
apply them directly; each wrapper repackages the limbs existentially via
`Fe.exists_limbs` — the `mul_spec'` pattern of Proofs/InvertSpec.lean.
The unreduced `fe_add` needs TWO instances because its output bound is
input-relative (`∀ c, Bnd a c → Bnd b c → Bnd r (2·c)`, AddSpec.lean):
the kernels below add reduced values (2⁵² → 2⁵³) but also feed one add
output into another add (2⁵³ → 2⁵⁴). -/
/-- RUST ANALOG: `impl Add for FieldElement51` (the `+` operator),
curve25519/solana-ed25519/src/backend/serial/u64/field.rs:68-72 (limbwise
`a[i] + b[i]`, no carry, no reduction) — verified in AddSpec.lean.
MATH: Bnd(a, 2⁵²) and Bnd(b, 2⁵²) ==> fe_add a b = ok r with
Bnd(r, 2⁵³) and ⟪r⟫ = ⟪a⟫ + ⟪b⟫.
LaTeX: $\mathrm{Bnd}(a,2^{52}) \wedge \mathrm{Bnd}(b,2^{52}) \Rightarrow
\llbracket r\rrbracket = \llbracket a\rrbracket+\llbracket b\rrbracket$.
Totality: each limb sum is < 2⁵² + 2⁵² = 2⁵³ < 2⁶⁴ — no u64 overflow.
WHY NEEDED: the Y₁+X₁ / Z₁+Z₁ / PP+MM additions below consume REDUCED
(2⁵², or 2⁵¹+2¹³ mul-output) values; this instance records the tight
2⁵³ output bound those sums actually satisfy. -/
theorem aff_add_spec53 (a b : Fe) (ha : Bnd a (2^52)) (hb : Bnd b (2^52)) :
fe_add a b ⦃ r => Bnd r (2^53) ∧ ⟪r⟫ = ⟪a⟫ + ⟪b⟫ ⦄ := by
obtain ⟨x0, x1, x2, x3, x4, hA⟩ := Fe.exists_limbs a -- materialize a's limbs
obtain ⟨y0, y1, y2, y3, y4, hB⟩ := Fe.exists_limbs b -- materialize b's limbs
-- per-limb inequalities for the pairwise-sum < 2⁶⁴ side condition
have ha' := (Bnd_eq a x0 x1 x2 x3 x4 _ hA).mp ha
have hb' := (Bnd_eq b y0 y1 y2 y3 y4 _ hB).mp hb
apply spec_mono (add_spec a b x0 x1 x2 x3 x4 y0 y1 y2 y3 y4 hA hB
⟨by omega, by omega, by omega, by omega, by omega⟩)
rintro r ⟨-, hval, hbnd⟩
-- bound: instantiate AddSpec's relative law at c = 2⁵², weaken 2·2⁵² ≤ 2⁵³
refine ⟨(hbnd (2^52) ha hb).mono (by norm_num), ?_⟩
-- value: feVal r = feVal a + feVal b over , cast once into 𝔽_p
simp only [denote, hval, Nat.cast_add]
/-- RUST ANALOG: same operator as `aff_add_spec53`, at the next bound level.
MATH: Bnd(a, 2⁵³) and Bnd(b, 2⁵³) ==> fe_add a b = ok r with
Bnd(r, 2⁵⁴) and ⟪r⟫ = ⟪a⟫ + ⟪b⟫.
Totality: limb sums < 2⁵³ + 2⁵³ = 2⁵⁴ < 2⁶⁴.
WHY NEEDED: the Z-coordinate of the `add` kernel (resp. T of `sub`) is
Z2 + Txy2d where Z2 = Z₁+Z₁ is itself an UNREDUCED add output (2⁵³) —
one level above what `aff_add_spec53` admits. Output 2⁵⁴ is still a
legal input for every downstream field op (their invariant is < 2⁵⁴…
consumers weaken via `Bnd.mono` where they need ≤). -/
@[step]
theorem aff_add_spec54 (a b : Fe) (ha : Bnd a (2^53)) (hb : Bnd b (2^53)) :
fe_add a b ⦃ r => Bnd r (2^54) ∧ ⟪r⟫ = ⟪a⟫ + ⟪b⟫ ⦄ := by
obtain ⟨x0, x1, x2, x3, x4, hA⟩ := Fe.exists_limbs a
obtain ⟨y0, y1, y2, y3, y4, hB⟩ := Fe.exists_limbs b
have ha' := (Bnd_eq a x0 x1 x2 x3 x4 _ hA).mp ha
have hb' := (Bnd_eq b y0 y1 y2 y3 y4 _ hB).mp hb
apply spec_mono (add_spec a b x0 x1 x2 x3 x4 y0 y1 y2 y3 y4 hA hB
⟨by omega, by omega, by omega, by omega, by omega⟩)
rintro r ⟨-, hval, hbnd⟩
refine ⟨(hbnd (2^53) ha hb).mono (by norm_num), ?_⟩
simp only [denote, hval, Nat.cast_add]
/-- RUST ANALOG: `impl Sub for FieldElement51` (the 16p-trick subtraction),
field.rs:84-101 — verified in SubNegSpec.lean.
MATH: Bnd(a, 2⁵⁴) and Bnd(b, 2⁵⁴) ==> fe_sub a b = ok r with
Bnd(r, 2⁵²) and ⟪r⟫ = ⟪a⟫ ⟪b⟫.
WHY NEEDED: hypothesis-light restatement of `sub_spec` (which names all
10 limbs) for the `let*` machinery — the Y₁X₁, PPMM and Z2Txy2d
steps below. -/
@[step]
theorem aff_sub_spec (a b : Fe) (ha : Bnd a (2^54)) (hb : Bnd b (2^54)) :
fe_sub a b ⦃ r => Bnd r (2^52) ∧ ⟪r⟫ = ⟪a⟫ - ⟪b⟫ ⦄ := by
obtain ⟨x0, x1, x2, x3, x4, hA⟩ := Fe.exists_limbs a
obtain ⟨y0, y1, y2, y3, y4, hB⟩ := Fe.exists_limbs b
exact sub_spec a b x0 x1 x2 x3 x4 y0 y1 y2 y3 y4 hA hB ha hb
/-- RUST ANALOG: `FieldElement51::negate` (16p a, then reduce),
field.rs:276-286 — verified in SubNegSpec.lean.
MATH: Bnd(a, 2⁵⁴) ==> fe_neg a = ok r with Bnd(r, 2⁵²) and
⟪r⟫ = ⟪a⟫.
WHY NEEDED: hypothesis-light restatement of `neg_spec` for the single
negate step of `affniels_neg_spec`. -/
@[step]
theorem aff_neg_spec (a : Fe) (ha : Bnd a (2^54)) :
fe_neg a ⦃ r => Bnd r (2^52) ∧ ⟪r⟫ = -⟪a⟫ ⦄ := by
obtain ⟨x0, x1, x2, x3, x4, hA⟩ := Fe.exists_limbs a
exact neg_spec a x0 x1 x2 x3 x4 hA ha
/-! ## The two mixed-addition kernels -/
/-- Rust: `impl Add<&AffineNielsPoint, CompletedPoint> for &EdwardsPoint`,
src/backend/serial/curve_models.rs:458-472; transpiled at
gen/CurveField/Funs.lean:1604-1640 as
`SharedAEdwardsPoint.Insts.CoreOpsArithAddSharedBAffineNielsPointCompletedPoint.add`.
MATH (coordinate level; writing X₁ = ⟪Pt.X⟫ etc., y±x = ⟪N.y_plus_x⟫ /
⟪N.y_minus_x⟫, 2dxy = ⟪N.xy2d⟫): under ExtValid Pt and AffNielsValid N
the kernel runs panic-free and returns the completed point
X = (Y₁+X₁)(y+x) (Y₁X₁)(yx) [= PP MM]
Y = (Y₁+X₁)(y+x) + (Y₁X₁)(yx) [= PP + MM]
Z = 2Z₁ + T₁·2dxy [= Z2 + Txy2d]
T = 2Z₁ T₁·2dxy [= Z2 Txy2d]
with the exact per-field bounds of the producing ops: X,T from `sub`
(2⁵²), Y a single unreduced add of two mul outputs (2⁵³), Z an
unreduced add consuming the unreduced Z2 (2⁵⁴) — all < 2⁵⁴+1, i.e.
consumable by every field op. NO curve constant and NO division
appears: the equations are stated strictly over the input struct-field
denotations; the later algebra layer combines them with `IsAffNielsOf`
(which characterizes ⟪N.xy2d⟫ via 121666·⟪N.xy2d⟫ = 243330·(x·y)) and
the curve equation to obtain the Edwards addition law.
WHY NEEDED: this is THE workhorse of fixed-base scalar multiplication —
`mul_base` adds table entries (AffineNielsPoint) to the accumulator with
exactly this kernel. -/
theorem add_affniels_spec (Pt : EdPoint) (N : AffNiels)
(hPt : ExtValid Pt) (hN : AffNielsValid N) :
SharedAEdwardsPoint.Insts.CoreOpsArithAddSharedBAffineNielsPointCompletedPoint.add
Pt N ⦃ r =>
Bnd r.X (2^52) ∧ Bnd r.Y (2^53) ∧ Bnd r.Z (2^54) ∧ Bnd r.T (2^52) ∧
⟪r.X⟫ = (⟪Pt.Y⟫ + ⟪Pt.X⟫) * ⟪N.y_plus_x⟫
- (⟪Pt.Y⟫ - ⟪Pt.X⟫) * ⟪N.y_minus_x⟫ ∧
⟪r.Y⟫ = (⟪Pt.Y⟫ + ⟪Pt.X⟫) * ⟪N.y_plus_x⟫
+ (⟪Pt.Y⟫ - ⟪Pt.X⟫) * ⟪N.y_minus_x⟫ ∧
⟪r.Z⟫ = 2 * ⟪Pt.Z⟫ + ⟪Pt.T⟫ * ⟪N.xy2d⟫ ∧
⟪r.T⟫ = 2 * ⟪Pt.Z⟫ - ⟪Pt.T⟫ * ⟪N.xy2d⟫ ⦄ := by
-- unpack the validity predicates (Z≠0 and the Segre coherence are not
-- needed at coordinate level — they ride along for the algebra layer)
obtain ⟨hPX, hPY, hPZ, hPT, -, -⟩ := hPt
obtain ⟨hNyp, hNym, hNxy⟩ := hN
-- expose the transpiled 10-step monadic body
unfold SharedAEdwardsPoint.Insts.CoreOpsArithAddSharedBAffineNielsPointCompletedPoint.add
-- Y_plus_X = Y₁+X₁ (unreduced add of two 2⁵² coords -> 2⁵³;
-- the 2⁵² side conditions are hypotheses verbatim — no discharger needed)
let* ⟨ YpX, YpX_bnd, YpX_val ⟩ ← aff_add_spec53
-- Y_minus_X = Y₁X₁ (reduced sub -> 2⁵²)
let* ⟨ YmX, YmX_bnd, YmX_val ⟩ ← aff_sub_spec by edis
-- PP = (Y₁+X₁)·(y+x); inputs 2⁵³ ≤ 2⁵⁴ -> 2⁵¹+2¹³
let* ⟨ PP, PP_bnd, PP_val ⟩ ← mul_spec' by edis
-- MM = (Y₁X₁)·(yx)
let* ⟨ MM, MM_bnd, MM_val ⟩ ← mul_spec' by edis
-- Txy2d = T₁·(2dxy)
let* ⟨ Txy, Txy_bnd, Txy_val ⟩ ← mul_spec' by edis
-- Z2 = Z₁+Z₁ (unreduced -> 2⁵³; side conditions verbatim in context)
let* ⟨ Z2, Z2_bnd, Z2_val ⟩ ← aff_add_spec53
-- X = PP MM (sub -> 2⁵²)
let* ⟨ rX, rX_bnd, rX_val ⟩ ← aff_sub_spec by edis
-- Y = PP + MM (mul outputs ≤ 2⁵² -> 2⁵³)
let* ⟨ rY, rY_bnd, rY_val ⟩ ← aff_add_spec53 by edis
-- Z = Z2 + Txy2d (2⁵³ + mul output -> 2⁵⁴)
let* ⟨ rZ, rZ_bnd, rZ_val ⟩ ← aff_add_spec54 by edis
-- T = Z2 Txy2d (sub -> 2⁵²)
let* ⟨ rT, rT_bnd, rT_val ⟩ ← aff_sub_spec by edis
-- the tail `ok { X := rX, Y := rY, Z := rZ, T := rT }` was already reduced
-- by the final `let*` (it also collapsed the constructor projections);
-- left: the four bounds + four coordinate equations. Substitute the
-- recorded step posts; X/Y close by rewriting alone, Z/T need `ring`
-- for Z₁+Z₁ = 2·Z₁
refine ⟨rX_bnd, rY_bnd, rZ_bnd, rT_bnd, ?_, ?_, ?_, ?_⟩
· rw [rX_val, PP_val, MM_val, YpX_val, YmX_val]
· rw [rY_val, PP_val, MM_val, YpX_val, YmX_val]
· rw [rZ_val, Z2_val, Txy_val]; ring
· rw [rT_val, Z2_val, Txy_val]; ring
/-- Rust: `impl Sub<&AffineNielsPoint, CompletedPoint> for &EdwardsPoint`,
src/backend/serial/curve_models.rs:479-493; transpiled at
gen/CurveField/Funs.lean:1657-1693 as
`SharedAEdwardsPoint.Insts.CoreOpsArithSubSharedBAffineNielsPointCompletedPoint.sub`.
MATH: the mirror image of `add_affniels_spec` — subtraction of the
cached point is addition of its negation (x, y), whose cache swaps
y+x ↔ yx and negates 2dxy (cf. `affniels_neg_spec` below); the Rust
code inlines that swap by multiplying CROSSWISE and flipping the sign
of Txy2d in the Z/T outputs. Under ExtValid Pt and AffNielsValid N:
X = (Y₁+X₁)(yx) (Y₁X₁)(y+x) [= PM MP]
Y = (Y₁+X₁)(yx) + (Y₁X₁)(y+x) [= PM + MP]
Z = 2Z₁ T₁·2dxy [= Z2 Txy2d]
T = 2Z₁ + T₁·2dxy [= Z2 + Txy2d]
with the per-field bounds of the producing ops — note Z/T trade places
with `add`'s: here Z comes from `sub` (2⁵²) and T from the unreduced
add (2⁵⁴).
WHY NEEDED: scalar multiplication with signed digit recodings (NAF)
subtracts table entries as often as it adds them. -/
theorem sub_affniels_spec (Pt : EdPoint) (N : AffNiels)
(hPt : ExtValid Pt) (hN : AffNielsValid N) :
SharedAEdwardsPoint.Insts.CoreOpsArithSubSharedBAffineNielsPointCompletedPoint.sub
Pt N ⦃ r =>
Bnd r.X (2^52) ∧ Bnd r.Y (2^53) ∧ Bnd r.Z (2^52) ∧ Bnd r.T (2^54) ∧
⟪r.X⟫ = (⟪Pt.Y⟫ + ⟪Pt.X⟫) * ⟪N.y_minus_x⟫
- (⟪Pt.Y⟫ - ⟪Pt.X⟫) * ⟪N.y_plus_x⟫ ∧
⟪r.Y⟫ = (⟪Pt.Y⟫ + ⟪Pt.X⟫) * ⟪N.y_minus_x⟫
+ (⟪Pt.Y⟫ - ⟪Pt.X⟫) * ⟪N.y_plus_x⟫ ∧
⟪r.Z⟫ = 2 * ⟪Pt.Z⟫ - ⟪Pt.T⟫ * ⟪N.xy2d⟫ ∧
⟪r.T⟫ = 2 * ⟪Pt.Z⟫ + ⟪Pt.T⟫ * ⟪N.xy2d⟫ ⦄ := by
obtain ⟨hPX, hPY, hPZ, hPT, -, -⟩ := hPt
obtain ⟨hNyp, hNym, hNxy⟩ := hN
unfold SharedAEdwardsPoint.Insts.CoreOpsArithSubSharedBAffineNielsPointCompletedPoint.sub
-- Y_plus_X = Y₁+X₁ -> 2⁵³ (side conditions verbatim in context);
-- Y_minus_X = Y₁X₁ -> 2⁵²
let* ⟨ YpX, YpX_bnd, YpX_val ⟩ ← aff_add_spec53
let* ⟨ YmX, YmX_bnd, YmX_val ⟩ ← aff_sub_spec by edis
-- the crosswise products: PM = (Y₁+X₁)(yx), MP = (Y₁X₁)(y+x)
let* ⟨ PM, PM_bnd, PM_val ⟩ ← mul_spec' by edis
let* ⟨ MP, MP_bnd, MP_val ⟩ ← mul_spec' by edis
-- Txy2d = T₁·(2dxy); Z2 = Z₁+Z₁ (side conditions verbatim in context)
let* ⟨ Txy, Txy_bnd, Txy_val ⟩ ← mul_spec' by edis
let* ⟨ Z2, Z2_bnd, Z2_val ⟩ ← aff_add_spec53
-- X = PM MP; Y = PM + MP; Z = Z2 Txy2d; T = Z2 + Txy2d
let* ⟨ rX, rX_bnd, rX_val ⟩ ← aff_sub_spec by edis
let* ⟨ rY, rY_bnd, rY_val ⟩ ← aff_add_spec53 by edis
let* ⟨ rZ, rZ_bnd, rZ_val ⟩ ← aff_sub_spec by edis
let* ⟨ rT, rT_bnd, rT_val ⟩ ← aff_add_spec54 by edis
-- bounds + equations (the final `let*` reduced the trailing `ok {…}`)
refine ⟨rX_bnd, rY_bnd, rZ_bnd, rT_bnd, ?_, ?_, ?_, ?_⟩
· rw [rX_val, PM_val, MP_val, YpX_val, YmX_val]
· rw [rY_val, PM_val, MP_val, YpX_val, YmX_val]
· rw [rZ_val, Z2_val, Txy_val]; ring
· rw [rT_val, Z2_val, Txy_val]; ring
/-! ## Negation of an affine-Niels cache point -/
/-- Rust: `impl Neg for &AffineNielsPoint`,
src/backend/serial/curve_models.rs:516-522; transpiled at
gen/CurveField/Funs.lean:1767-1773 as
`SharedAAffineNielsPoint.Insts.CoreOpsArithNegAffineNielsPoint.neg`
(the single field negation goes through the operator wrapper
`SharedAFieldElement51.Insts.CoreOpsArithNegFieldElement51.neg`,
Funs.lean:1732, which is definitionally `FieldElement51::negate`).
MATH: AffNielsValid N ==> neg N = ok r with AffNielsValid r and
⟪r.y_plus_x⟫ = ⟪N.y_minus_x⟫,
⟪r.y_minus_x⟫ = ⟪N.y_plus_x⟫,
⟪r.xy2d⟫ = ⟪N.xy2d⟫.
On denotations this IS the cache of the negated affine point: if N
caches (x, y) then r caches (x, y), since y+(x) = yx, y(x) = y+x
and 2d(x)y = (2dxy) — the algebra layer derives
`IsAffNielsOf N x y → IsAffNielsOf r (x) y` from these three equations
by ring reasoning on the 121666-characterization of `IsAffNielsOf`.
Validity is PRESERVED (not just some bound): the swapped fields keep
their 2⁵³ bounds verbatim, and `negate` REDUCES, returning 2⁵² — so r
can re-enter the add/sub kernels above. (The y±x swap is pure data
movement — the two equations hold by `rfl`; only xy2d runs code.)
WHY NEEDED: signed-digit lookup tables (`NafLookupTable`/`select`)
produce N for negative digits with exactly this function. -/
theorem affniels_neg_spec (N : AffNiels) (hN : AffNielsValid N) :
SharedAAffineNielsPoint.Insts.CoreOpsArithNegAffineNielsPoint.neg N ⦃ r =>
AffNielsValid r ∧
⟪r.y_plus_x⟫ = ⟪N.y_minus_x⟫ ∧
⟪r.y_minus_x⟫ = ⟪N.y_plus_x⟫ ∧
⟪r.xy2d⟫ = -⟪N.xy2d⟫ ⦄ := by
obtain ⟨hNyp, hNym, hNxy⟩ := hN
-- expose the body and the operator wrapper around `negate`
unfold SharedAAffineNielsPoint.Insts.CoreOpsArithNegAffineNielsPoint.neg
SharedAFieldElement51.Insts.CoreOpsArithNegFieldElement51.neg
-- the one field op: xy2d' = xy2d (input 2⁵² ≤ 2⁵⁴, output reduced 2⁵²)
let* ⟨ nx, nx_bnd, nx_val ⟩ ← aff_neg_spec by edis
-- the final `let*` reduced the tail `ok { y_plus_x := N.y_minus_x,
-- y_minus_x := N.y_plus_x, xy2d := nx }` and already closed the two
-- pure-data-movement equations (they are `rfl` after the projections
-- collapse); left: validity — the swap preserves the 2⁵³ bounds and
-- negate's reduced output is the required 2⁵² — and the xy2d equation
exact ⟨⟨hNym, hNyp, nx_bnd⟩, nx_val⟩
end CurveFieldProofs