/- ─────────────────────────────────────────────────────────────────────────── Proofs/EdAddProjNiels.lean — coordinate-level specs for the MIXED addition/subtraction kernels EdwardsPoint ± ProjectiveNielsPoint → CompletedPoint, and for projective-niels negation. CONTEXT. The Rust crate curve25519/solana-ed25519 adds points in extended coordinates against a precomputed "niels" cache (Y+X, Y−X, Z, T·2d) using the Hisil–Wong–Carter–Dawson mixed-addition formulas (src/backend/serial/curve_models.rs:411-452): add: PP = (Y₁+X₁)·(Y₂+X₂) sub: PM = (Y₁+X₁)·(Y₂−X₂) MM = (Y₁−X₁)·(Y₂−X₂) MP = (Y₁−X₁)·(Y₂+X₂) TT2d = T₁·(T₂·2d) TT2d = T₁·(T₂·2d) ZZ = Z₁·Z₂, ZZ2 = ZZ+ZZ ZZ = Z₁·Z₂, ZZ2 = ZZ+ZZ X' = PP−MM Y' = PP+MM X' = PM−MP Y' = PM+MP Z' = ZZ2+TT2d T' = ZZ2−TT2d Z' = ZZ2−TT2d T' = ZZ2+TT2d The result lives in the ℙ¹×ℙ¹ "completed" model ((X':Z'), (Y':T')). Charon+Aeneas transpiled both bodies (and the `Neg` impl for the niels cache, curve_models.rs:500-511) into gen/CurveField/Funs.lean. THIS FILE proves, for each of the three kernels, TOTAL correctness at the COORDINATE level (the statement policy of the point-op phase): * hypotheses — only the EdDenote validity predicates (`ExtValid`, `ProjNielsValid`), which carry the limb bounds making the field ops panic-free, plus ⟪Z⟫ ≠ 0 side facts; * conclusions — per-output-field `Bnd` bounds (exactly what the field-op chain yields: `fe_sub`/`fe_mul` outputs are reduced ≤ 2⁵², a single unreduced `fe_add` of reduced inputs gives 2⁵³, and the stacked add ZZ2 + TT2d gives 2⁵⁴) AND the four coordinate equations over 𝔽_p, phrased STRICTLY in the input struct-field denotations, e.g. ⟪r.Z⟫ = 2·⟪P.Z⟫·⟪N.Z⟫ + ⟪P.T⟫·⟪N.T2d⟫. No curve constants, no division, no `OnCurve` here — identifying these limb-level equations with the Edwards group law (via `IsNielsOf` and the d-characterization of EdDenote.lean) happens in a later file. PROOF TECHNIQUE. Identical to Proofs/InvertSpec.lean: `unfold` the transpiled monadic body, then walk it with `let* ⟨x, post…⟩ ← spec by edis` — one field operation per line, the named postconditions accumulating in the context — and close the four coordinate equations by `rw`+`ring`. The `edis` side-condition macro (re-declared here; macros do not travel across files) discharges every `Bnd _ (2⁵²/2⁵³/2⁵⁴)` obligation by `assumption` or by weakening an existing bound (`Bnd.mono`). Because the base specs `sub_spec`/`neg_spec`/`add_spec` (SubNegSpec.lean/AddSpec.lean) take explicit limb variables, this file first re-packages them limb-free (`sub_spec'`, `neg_spec'`, `add52_spec`, `add53_spec`) via `Fe.exists_limbs`, mirroring `mul_spec'` (InvertSpec.lean). Imports: Proofs/EdDenote.lean (point predicates + mk-projection simps) and Proofs/Square2Spec.lean (the point-op phase's common field-op stock). Imported by: the forthcoming algebra-law packaging file (EdCurve phase). ─────────────────────────────────────────────────────────────────────── -/ 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 /-! ## Limb-free wrappers for the unreduced add, sub, and negate The base specs (AddSpec.lean / SubNegSpec.lean) name all five limbs of every argument explicitly because their proofs compute limb by limb; the `let*` automation cannot invent those variables. As with `mul_spec'` (InvertSpec.lean), we re-state each spec with the limbs repackaged via `Fe.exists_limbs`. `fe_add` performs NO reduction (limbwise `aᵢ+bᵢ`), so its output bound genuinely doubles the input bound — we expose the two instances this file needs (2⁵²→2⁵³ and 2⁵³→2⁵⁴) of one parametric lemma. -/ /-- Parametric limb-free `fe_add` spec. Rust: `impl Add<&FieldElement51> for &FieldElement51`, u64/field.rs:68-72 (limbwise sum, no carry, no reduction) — verified in Proofs/AddSpec.lean. MATH: c ≤ 2⁶³, Bnd(a,c), Bnd(b,c) ==> fe_add a b = ok r with Bnd(r, 2c) and ⟪r⟫ = ⟪a⟫ + ⟪b⟫. The hypothesis c ≤ 2⁶³ makes every limbwise sum aᵢ + bᵢ < 2c ≤ 2⁶⁴ — exactly the u64 no-overflow side condition of `add_spec`, i.e. the panic-freedom of the unreduced add. The value clause is `add_spec`'s exact ℕ equation `feVal r = feVal a + feVal b` pushed through the mod-p cast (additions commute with ℕ → 𝔽_p). WHY NEEDED: parent of the two fixed-bound instances below; stated parametrically so the bound bookkeeping is proved once. (`private`: Proofs/EdConvert.lean exports a different `add_spec''` under the same name; this one is only consumed in-file, so privacy avoids the duplicate-declaration clash without changing any statement.) -/ private theorem add_spec'' (c : ℕ) (hc : c ≤ 2^63) (a b : Fe) (hba : Bnd a c) (hbb : Bnd b c) : fe_add a b ⦃ r => Bnd r (2*c) ∧ ⟪r⟫ = ⟪a⟫ + ⟪b⟫ ⦄ := by -- materialize the limbs of both arguments and their per-limb bounds obtain ⟨a0, a1, a2, a3, a4, hA⟩ := Fe.exists_limbs a obtain ⟨b0, b1, b2, b3, b4, hB⟩ := Fe.exists_limbs b have hA' := (Bnd_eq a a0 a1 a2 a3 a4 c hA).mp hba have hB' := (Bnd_eq b b0 b1 b2 b3 b4 c hB).mp hbb -- run the base spec; each pairwise sum < 2c ≤ 2⁶⁴ closes by 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⟩ -- bound: the "doubles any common bound" law at c; value: one cast to 𝔽_p exact ⟨hbnd c hba hbb, by simp [denote, hval]⟩ /-- `fe_add` on two REDUCED (2⁵²) inputs: output bound 2⁵³. MATH: Bnd(a,2⁵²), Bnd(b,2⁵²) ==> fe_add a b = ok r, Bnd(r,2⁵³), ⟪r⟫ = ⟪a⟫ + ⟪b⟫. WHY NEEDED: the three "first-generation" adds of the kernels below (Y₁+X₁ on the 2⁵²-bounded extended coordinates, ZZ+ZZ and PP+MM on 2⁵²-bounded mul outputs) all fit this instance. -/ theorem add52_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 apply spec_mono (add_spec'' (2^52) (by norm_num) a b hba hbb) rintro r ⟨h1, h2⟩ exact ⟨h1.mono (by norm_num), h2⟩ -- 2·2⁵² = 2⁵³ /-- `fe_add` on two 2⁵³-bounded inputs: output bound 2⁵⁴ (still a legal input for every field op — the dalek 2⁵⁴ discipline's outer edge). MATH: Bnd(a,2⁵³), Bnd(b,2⁵³) ==> fe_add a b = ok r, Bnd(r,2⁵⁴), ⟪r⟫ = ⟪a⟫ + ⟪b⟫. WHY NEEDED: the "second-generation" add ZZ2 + TT2d stacks on top of the unreduced ZZ2 (Bnd 2⁵³), so it needs this wider instance. -/ theorem add53_spec (a b : Fe) (hba : Bnd a (2^53)) (hbb : Bnd b (2^53)) : fe_add a b ⦃ r => Bnd r (2^54) ∧ ⟪r⟫ = ⟪a⟫ + ⟪b⟫ ⦄ := by apply spec_mono (add_spec'' (2^53) (by norm_num) a b hba hbb) rintro r ⟨h1, h2⟩ exact ⟨h1.mono (by norm_num), h2⟩ -- 2·2⁵³ = 2⁵⁴ /-- Limb-free `fe_sub` spec (the "+16p then subtract, then reduce" trick). Rust: `impl Sub<&FieldElement51> for &FieldElement51`, u64/field.rs:84-101 — verified in Proofs/SubNegSpec.lean. MATH: Bnd(a,2⁵⁴), Bnd(b,2⁵⁴) ==> fe_sub a b = ok r, Bnd(r,2⁵²), ⟪r⟫ = ⟪a⟫ − ⟪b⟫. WHY NEEDED: the kernels below subtract four times each (Y₁−X₁, PP−MM / PM−MP, ZZ2−TT2d); this is `sub_spec` with the limbs repackaged so `let*` can apply it. -/ 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 /-- Limb-free `fe_neg` spec (16p − a, then reduce). Rust: `FieldElement51::negate`, u64/field.rs:276-286 — verified in Proofs/SubNegSpec.lean. MATH: Bnd(a,2⁵⁴) ==> fe_neg a = ok r, Bnd(r,2⁵²), ⟪r⟫ = −⟪a⟫. WHY NEEDED: `ProjectiveNielsPoint::neg` (below) negates the T2d field. -/ theorem neg_spec' (a : Fe) (hba : Bnd a (2^54)) : fe_neg a ⦃ r => Bnd r (2^52) ∧ ⟪r⟫ = -⟪a⟫ ⦄ := by 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 `let*` side conditions of this file (the `bnd` pattern of InvertSpec.lean, re-declared because macros are local to their file): every obligation is a `Bnd _ c` goal, closed either by an exact hypothesis (`assumption`) or by weakening a tighter bound from the context (`Bnd.mono` + `norm_num` on the ≤ between the two numerals); `scalar_tac` mops up any residual linear-arithmetic goal. (`name :=` disambiguates the generated syntax-kind declaration from the sibling files' `edis` macros so they can all be imported together.) -/ macro (name := edisProjNiels) "edis" : tactic => `(tactic| (first | assumption | exact Bnd.mono (by assumption) (by norm_num) | scalar_tac)) /-! ## 1. Mixed addition: EdwardsPoint + &ProjectiveNielsPoint → CompletedPoint -/ /-- Rust: `impl Add<&ProjectiveNielsPoint> for &EdwardsPoint`, src/backend/serial/curve_models.rs:411-430; transpiled at gen/CurveField/Funs.lean (`SharedAEdwardsPoint.Insts. CoreOpsArithAddSharedBProjectiveNielsPointCompletedPoint.add`). MATH (HWCD08 mixed addition, ℙ¹×ℙ¹ output): for an extended point P (ExtValid: all coords Bnd 2⁵², ⟪Z⟫ ≠ 0, X·Y = Z·T) and a niels cache N (ProjNielsValid: Y±X Bnd 2⁵³, Z/T2d Bnd 2⁵², ⟪Z⟫ ≠ 0), the kernel is TOTAL (every intermediate field op stays inside the 2⁵⁴ discipline: the 20 + … machine-op side conditions are discharged step by step) and the completed-point output r satisfies Bnd r.X 2⁵² (PP − MM: reduced sub output) Bnd r.Y 2⁵³ (PP + MM: one unreduced add of two ≤2⁵² mul outputs) Bnd r.Z 2⁵⁴ (ZZ2 + TT2d: add stacked on the unreduced ZZ2 ≤ 2⁵³) Bnd r.T 2⁵² (ZZ2 − TT2d: reduced sub output) ⟪r.X⟫ = (⟪P.Y⟫+⟪P.X⟫)·⟪N.Y_plus_X⟫ − (⟪P.Y⟫−⟪P.X⟫)·⟪N.Y_minus_X⟫ ⟪r.Y⟫ = (⟪P.Y⟫+⟪P.X⟫)·⟪N.Y_plus_X⟫ + (⟪P.Y⟫−⟪P.X⟫)·⟪N.Y_minus_X⟫ ⟪r.Z⟫ = 2·⟪P.Z⟫·⟪N.Z⟫ + ⟪P.T⟫·⟪N.T2d⟫ ⟪r.T⟫ = 2·⟪P.Z⟫·⟪N.Z⟫ − ⟪P.T⟫·⟪N.T2d⟫ — the computation order of the Rust source (PP, MM, TT2d, ZZ, ZZ2 = ZZ+ZZ, then X' = PP−MM, Y' = PP+MM, Z' = ZZ2+TT2d, T' = ZZ2−TT2d) verbatim, with each intermediate eliminated. Deliberately NO curve constant, division or group-law claim here: combined with `IsNielsOf N Q` (EdDenote.lean) the right-hand sides become the HWCD08 addition formulas for P + Q, which the algebra-law packaging file exploits. WHY NEEDED: this is the workhorse of scalar multiplication — every table-lookup addition in the double-and-add ladder goes through it. -/ theorem add_projniels_spec (Pt : EdPoint) (N : ProjNiels) (hPt : ExtValid Pt) (hN : ProjNielsValid N) : SharedAEdwardsPoint.Insts.CoreOpsArithAddSharedBProjectiveNielsPointCompletedPoint.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⟫ * ⟪N.Z⟫ + ⟪Pt.T⟫ * ⟪N.T2d⟫ ∧ ⟪r.T⟫ = 2 * ⟪Pt.Z⟫ * ⟪N.Z⟫ - ⟪Pt.T⟫ * ⟪N.T2d⟫ ⦄ := by -- unpack the validity predicates into named Bnd facts for `edis` obtain ⟨hPX, hPY, hPZ, hPT, _hPZ0, _hPcoh⟩ := hPt obtain ⟨hNyp, hNym, hNZ, hNT, _hNZ0⟩ := hN -- expose the transpiled 11-step monadic body unfold SharedAEdwardsPoint.Insts.CoreOpsArithAddSharedBProjectiveNielsPointCompletedPoint.add -- Y₁+X₁ : unreduced add of two reduced coordinates (Bnd 2⁵³); -- preconditions are verbatim hypotheses, found by `let*` itself let* ⟨ Y_plus_X, Ypx_bnd, Ypx_val ⟩ ← add52_spec -- Y₁−X₁ : reduced sub (Bnd 2⁵²) let* ⟨ Y_minus_X, Ymx_bnd, Ymx_val ⟩ ← sub_spec' by edis -- PP = (Y₁+X₁)·N.Y_plus_X let* ⟨ PP, PP_bnd, PP_val ⟩ ← mul_spec' by edis -- MM = (Y₁−X₁)·N.Y_minus_X let* ⟨ MM, MM_bnd, MM_val ⟩ ← mul_spec' by edis -- TT2d = T₁·N.T2d let* ⟨ TT2d, TT_bnd, TT_val ⟩ ← mul_spec' by edis -- ZZ = Z₁·N.Z let* ⟨ ZZ, ZZ_bnd, ZZ_val ⟩ ← mul_spec' by edis -- ZZ2 = ZZ + ZZ (unreduced doubling, Bnd 2⁵³) let* ⟨ ZZ2, ZZ2_bnd, ZZ2_val ⟩ ← add52_spec by edis -- X' = PP − MM let* ⟨ rX, rX_bnd, rX_val ⟩ ← sub_spec' by edis -- Y' = PP + MM let* ⟨ rY, rY_bnd, rY_val ⟩ ← add52_spec by edis -- Z' = ZZ2 + TT2d (the stacked add, Bnd 2⁵⁴) let* ⟨ rZ, rZ_bnd, rZ_val ⟩ ← add53_spec by edis -- T' = ZZ2 − TT2d let* ⟨ rT, rT_bnd, rT_val ⟩ ← sub_spec' by edis -- `let*` already collapsed the final `ok {…}` constructor's projections; -- bounds are the recorded step posts; equations close by rewriting the -- chain of step values and (for Z/T) merging ZZ+ZZ into 2·ZZ by ring 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, ZZ2_val, ZZ_val, TT_val]; ring · rw [rT_val, ZZ2_val, ZZ_val, TT_val]; ring /-! ## 2. Mixed subtraction: EdwardsPoint − &ProjectiveNielsPoint → CompletedPoint -/ /-- Rust: `impl Sub<&ProjectiveNielsPoint> for &EdwardsPoint`, src/backend/serial/curve_models.rs:433-452; transpiled at gen/CurveField/Funs.lean (`…SubSharedBProjectiveNielsPointCompletedPoint.sub`). MATH: same hypotheses and totality as `add_projniels_spec`; the body is the addition kernel with the cache's Y_plus_X/Y_minus_X CROSSED (PM = (Y₁+X₁)·(Y₂−X₂), MP = (Y₁−X₁)·(Y₂+X₂)) and the Z'/T' roles of ZZ2 ± TT2d swapped — algebraically, addition of the NEGATED niels point (cf. `projniels_neg_spec` below: negation swaps Y±X and flips T2d): Bnd r.X 2⁵², Bnd r.Y 2⁵³, Bnd r.Z 2⁵², Bnd r.T 2⁵⁴ (Z' is now the reduced SUB ZZ2−TT2d and T' the stacked ADD ZZ2+TT2d, so the 2⁵²/2⁵⁴ bounds trade places relative to `add_projniels_spec`) ⟪r.X⟫ = (⟪P.Y⟫+⟪P.X⟫)·⟪N.Y_minus_X⟫ − (⟪P.Y⟫−⟪P.X⟫)·⟪N.Y_plus_X⟫ ⟪r.Y⟫ = (⟪P.Y⟫+⟪P.X⟫)·⟪N.Y_minus_X⟫ + (⟪P.Y⟫−⟪P.X⟫)·⟪N.Y_plus_X⟫ ⟪r.Z⟫ = 2·⟪P.Z⟫·⟪N.Z⟫ − ⟪P.T⟫·⟪N.T2d⟫ ⟪r.T⟫ = 2·⟪P.Z⟫·⟪N.Z⟫ + ⟪P.T⟫·⟪N.T2d⟫ WHY NEEDED: the signed-digit (NAF) scalar-multiplication ladder subtracts table entries as often as it adds them. -/ theorem sub_projniels_spec (Pt : EdPoint) (N : ProjNiels) (hPt : ExtValid Pt) (hN : ProjNielsValid N) : SharedAEdwardsPoint.Insts.CoreOpsArithSubSharedBProjectiveNielsPointCompletedPoint.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⟫ * ⟪N.Z⟫ - ⟪Pt.T⟫ * ⟪N.T2d⟫ ∧ ⟪r.T⟫ = 2 * ⟪Pt.Z⟫ * ⟪N.Z⟫ + ⟪Pt.T⟫ * ⟪N.T2d⟫ ⦄ := by obtain ⟨hPX, hPY, hPZ, hPT, _hPZ0, _hPcoh⟩ := hPt obtain ⟨hNyp, hNym, hNZ, hNT, _hNZ0⟩ := hN unfold SharedAEdwardsPoint.Insts.CoreOpsArithSubSharedBProjectiveNielsPointCompletedPoint.sub -- Y₁+X₁, Y₁−X₁ — same prologue as the addition kernel let* ⟨ Y_plus_X, Ypx_bnd, Ypx_val ⟩ ← add52_spec let* ⟨ Y_minus_X, Ymx_bnd, Ymx_val ⟩ ← sub_spec' by edis -- PM = (Y₁+X₁)·N.Y_minus_X (crossed relative to `add`) let* ⟨ PM, PM_bnd, PM_val ⟩ ← mul_spec' by edis -- MP = (Y₁−X₁)·N.Y_plus_X let* ⟨ MP, MP_bnd, MP_val ⟩ ← mul_spec' by edis -- TT2d = T₁·N.T2d, ZZ = Z₁·N.Z, ZZ2 = ZZ+ZZ let* ⟨ TT2d, TT_bnd, TT_val ⟩ ← mul_spec' by edis let* ⟨ ZZ, ZZ_bnd, ZZ_val ⟩ ← mul_spec' by edis let* ⟨ ZZ2, ZZ2_bnd, ZZ2_val ⟩ ← add52_spec by edis -- X' = PM − MP, Y' = PM + MP let* ⟨ rX, rX_bnd, rX_val ⟩ ← sub_spec' by edis let* ⟨ rY, rY_bnd, rY_val ⟩ ← add52_spec by edis -- Z' = ZZ2 − TT2d, T' = ZZ2 + TT2d (roles swapped relative to `add`) let* ⟨ rZ, rZ_bnd, rZ_val ⟩ ← sub_spec' by edis let* ⟨ rT, rT_bnd, rT_val ⟩ ← add53_spec by edis 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, ZZ2_val, ZZ_val, TT_val]; ring · rw [rT_val, ZZ2_val, ZZ_val, TT_val]; ring /-! ## 3. Negation of a projective-niels cache point -/ /-- Rust: `impl Neg for &ProjectiveNielsPoint`, src/backend/serial/curve_models.rs:500-511; transpiled at gen/CurveField/Funs.lean (`SharedAProjectiveNielsPoint.Insts. CoreOpsArithNegProjectiveNielsPoint.neg`). MATH: negating an Edwards point (x,y) ↦ (−x,y) sends the cache (Y+X, Y−X, Z, T·2d) to (Y−X, Y+X, Z, −T·2d): the two sum/difference fields SWAP, Z is untouched, and T2d is negated through the (total, `neg_spec'`) field negation. Hence for ProjNielsValid N: neg N = ok r, ProjNielsValid r (swap preserves the 2⁵³/2⁵³ bounds, ⟪r.Z⟫ = ⟪N.Z⟫ ≠ 0, and fe_neg outputs a reduced 2⁵² T2d), and ⟪r.Y_plus_X⟫ = ⟪N.Y_minus_X⟫, ⟪r.Y_minus_X⟫ = ⟪N.Y_plus_X⟫, ⟪r.Z⟫ = ⟪N.Z⟫, ⟪r.T2d⟫ = −⟪N.T2d⟫. (Field-wise relational form, matching the statement policy: combined with `IsNielsOf N Q` these four equations say exactly `IsNielsOf r (−Q)` — e.g. 121666·⟪r.T2d⟫ = −121666·⟪N.T2d⟫ = 243330·⟪Q.T⟫ = −243330·(−⟪Q.T⟫) — which the packaging file derives.) WHY NEEDED: the NAF ladder materializes negative table digits through this kernel; it also explains `sub_projniels_spec` as "add the negation". -/ theorem projniels_neg_spec (N : ProjNiels) (hN : ProjNielsValid N) : SharedAProjectiveNielsPoint.Insts.CoreOpsArithNegProjectiveNielsPoint.neg N ⦃ r => ProjNielsValid r ∧ ⟪r.Y_plus_X⟫ = ⟪N.Y_minus_X⟫ ∧ ⟪r.Y_minus_X⟫ = ⟪N.Y_plus_X⟫ ∧ ⟪r.Z⟫ = ⟪N.Z⟫ ∧ ⟪r.T2d⟫ = -⟪N.T2d⟫ ⦄ := by obtain ⟨hNyp, hNym, hNZ, hNT, hNZ0⟩ := hN -- expose the body; the inner `…NegFieldElement51.neg` is a direct call to -- `FieldElement51::negate` (= fe_neg), so unfold both layers unfold SharedAProjectiveNielsPoint.Insts.CoreOpsArithNegProjectiveNielsPoint.neg SharedAFieldElement51.Insts.CoreOpsArithNegFieldElement51.neg -- t2d = −N.T2d (total: Bnd 2⁵² ≤ 2⁵⁴; output reduced to 2⁵²) let* ⟨ t2d, t2d_bnd, t2d_val ⟩ ← neg_spec' by edis -- `let*` collapsed the `{ N with … }` constructor's projections and already -- closed the three definitional field equations (Y±X swap, Z untouched); -- remaining: validity (swapped bounds + Z ≠ 0 + reduced new T2d) and the -- T2d value clause (= neg's post) exact ⟨⟨hNym, hNyp, hNZ, t2d_bnd, hNZ0⟩, t2d_val⟩ end CurveFieldProofs