/- ─────────────────────────────────────────────────────────────────────────────
   Proofs/Square2Spec.lean — total correctness of `square2` (compute 2·a²)

   WHAT THIS FILE PROVES
     `square2_spec` (and its step-friendly wrapper `square2_spec'`):
       ASCII:  Bnd(a, 2^54)  ==>  square2 a = ok r  with
               Bnd(r, 2^53)  and  [[r]] = 2 * ([[a]] * [[a]])
       LaTeX:  $\mathrm{Bnd}(a,2^{54}) \Rightarrow
               \llbracket \mathrm{square2}(a)\rrbracket
               = 2\,\llbracket a\rrbracket^2$
   where Bnd/[[·]] = ⟪·⟫ are the invariant and denotation of Proofs/Denote.lean
   and p = 2^255 - 19. As everywhere, `f x ⦃ post ⦄` is TOTAL correctness:
   no panic/overflow — in particular the five u64 `*= 2` multiplications of the
   doubling loop are PROVED in range, not assumed.

   RUST ANALOG
   `FieldElement51::square2`, curve25519/solana-ed25519/src/backend/serial/u64/
   field.rs:566-573:
       let mut square = self.pow2k(1);
       for i in 0..5 { square.0[i] *= 2; }
       square
   i.e. one radix-2^51 squaring (pow2k with k = 1, verified in
   Proofs/SquareSpec.lean) followed by a limbwise doubling — point doubling
   (`ProjectivePoint::double`, curve_models.rs:381-397) calls it for 2·Z².
   Charon splits the Rust `for` loop into two generated items in
   gen/CurveField/Funs.lean (same shape as `add_assign`'s loop in
   Proofs/AddSpec.lean):
     * `…FieldElement51.square2_loop.body (iter, square)` — ONE iteration,
       returning a `ControlFlow` value: it calls `Iterator::next` on the
       `Range<usize>` iterator and either answers `.done square` (range
       exhausted) or doubles limb i (`Array.index_usize`, `* 2#u64`,
       `Array.update`) and answers `.cont (iter1, a)`;
     * `…FieldElement51.square2_loop` — `Aeneas.Std.loop` applied to that body;
   and `…FieldElement51.square2` itself is `pow2k self 1#u32` bound into the
   loop started at the literal range { start := 0, end := 5 }.
   (No `fe_square2` alias exists in Proofs/Denote.lean; we use the full
   generated name throughout — Denote.lean is not modified.)

   WHY THE DOUBLING LOOP CANNOT OVERFLOW
   `pow2k` outputs limbs < 2^51 + 2^13 (pow2k_spec, Proofs/SquareSpec.lean), so
   each u64 product limb·2 is < 2^52 + 2^14 < 2^64: the `*= 2` (a genuine U64
   multiplication in the generated code, `i1 * 2#u64`) is always in range.
   The output limbs are < 2^52 + 2^14 ≤ 2^53, which is the (comfortable) bound
   we expose — still strictly below the 2^54 input invariant of mul/sub/square,
   so square2's result can feed any downstream field op directly.

   PROOF ARCHITECTURE
   Exactly the AddSpec playbook for the one other `for i in 0..5` loop in this
   crate, specialized to one array instead of two:
     * `square2_loop_spec` unrolls the loop 5-fold by hand: 5 × (apply
       `loop_step` (Proofs/AddSpec.lean), substitute the generated body, run
       `range_next_lt_spec` + index/mul/update steps — the mul's side condition
       is closed from the < 2^63 limb hypothesis) and a 6th `loop_step` where
       `range_next_ge_spec` (5 ≥ 5) makes the body return `done`. The result
       array is the input overwritten at 0..4 with the doubled limbs;
       collapsing the five set operations (`Array.set_val_eq`) gives the
       limb-exact postcondition r_i = 2·s_i.
     * `square2_spec` chains pow2k_spec (k = 1, reused as-is via `spec_bind`)
       into the loop spec (`spec_mono`), then repackages:
       - bound:  r_i = 2·s_i < 2·(2^51 + 2^13) ≤ 2^53            (omega);
       - value:  feVal r = 2·feVal s EXACTLY over ℕ (the doubling is linear
         in the limbs — omega), cast into 𝔽_p, then
         ⟪r⟫ = 2·⟪s⟫ = 2·⟪a⟫^(2^1) = 2·(⟪a⟫·⟪a⟫) — the pow2k exponent 2^1 is
         bridged to the product form by `ring`, as in square_spec.
     * `square2_spec'` is the `@[step]`-registered wrapper with the limbs
       hidden (destructured internally via `Fe.exists_limbs`), so the `step` /
       `let*` machinery of downstream proofs (e.g. point doubling) can consume
       square2 in one step.

   ROLE IN THE MAIN THEOREM
   Not a field axiom itself: square2 is an EdDSA-level optimization
   (2·Z² in `ProjectivePoint::double` saves one full mul). Verifying it here,
   with the same invariant discipline as the eleven core ops, makes the
   point-doubling code symbolically executable later.
   Imports: Proofs/SquareSpec (pow2k_spec; transitively MulSpec's architecture
   and AddSpec's loop_step / range_next_*_spec machinery).
   ───────────────────────────────────────────────────────────────────────── -/
import Proofs.SquareSpec
open Aeneas Aeneas.Std Result
open curve25519_dalek

set_option maxHeartbeats 4000000
set_option maxRecDepth 8000
set_option linter.unusedSimpArgs false

namespace CurveFieldProofs

-- the weakest-precondition layer: spec_mono / spec_bind / spec_ok used below
open Aeneas.Std.WP

/-- Limb-level spec for the doubling loop of `square2`: total (no u64
    overflow) when all input limbs are < 2⁶³, and the output limbs are
    exactly the doubled input limbs.

    Rust: the `for i in 0..5 { square.0[i] *= 2; }` loop of
    `FieldElement51::square2`,
    curve25519/solana-ed25519/src/backend/serial/u64/field.rs:568-570
    (generated as `…FieldElement51.square2_loop` = `Aeneas.Std.loop` applied
    to `…square2_loop.body`, started at the range { start := 0, end := 5 }).

    MATH:
    ASCII:  forall s : Fe,  (s_i < 2^63 for i = 0..4)  ==>
            square2_loop {0, 5} s = ok r  with  r_i = 2 * s_i  for all i.
    LaTeX:  $\forall i,\ s_i < 2^{63} \Rightarrow \exists r,\
            \mathrm{loop}(s) = \mathrm{ok}\ r \wedge \forall i,\ r_i = 2 s_i$.
    The hypothesis is exactly the panic condition of the Rust `*= 2` on u64
    (s_i·2 < 2^64 iff s_i < 2^63); the caller instantiates it with the much
    stronger pow2k output bound 2^51 + 2^13.

    WHY NEEDED: the strongest (limb-exact) description of the loop, consumed
    by `square2_spec` below; as in AddSpec, keeping the 6-fold `loop_step`
    unrolling separate from the feVal/Bnd repackaging keeps both readable.   -/
theorem square2_loop_spec (s : Fe) (s0 s1 s2 s3 s4 : U64)
    (hs : (↑s : List U64) = [s0, s1, s2, s3, s4])
    (hbnd : s0.val < 2^63 ∧ s1.val < 2^63 ∧ s2.val < 2^63 ∧
            s3.val < 2^63 ∧ s4.val < 2^63) :
    backend.serial.u64.field.FieldElement51.square2_loop
      { start := 0#usize, «end» := 5#usize } s
      ⦃ r => ∃ r0 r1 r2 r3 r4 : U64,
        (↑r : List U64) = [r0, r1, r2, r3, r4] ∧
        r0.val = 2 * s0.val ∧ r1.val = 2 * s1.val ∧ r2.val = 2 * s2.val ∧
        r3.val = 2 * s3.val ∧ r4.val = 2 * s4.val ⦄ := by
  obtain ⟨hbnd0, hbnd1, hbnd2, hbnd3, hbnd4⟩ := hbnd
  -- expose the loop combinator (gen/CurveField/Funs.lean)
  unfold backend.serial.u64.field.FieldElement51.square2_loop
  -- Iteration 1 (i = 0)
  -- Pattern repeated for each of the 5 iterations (cf. add_limbs_spec):
  --   loop_step            — peel one iteration of the loop combinator,
  --   simp only [..body]   — substitute the loop body's definition,
  --   step with range_next_lt_spec — Iterator::next yields some i, range
  --                          advances by one,
  --   step                 — read square[i] (x_k),
  --   step                 — u64 multiply by 2#u64; its overflow side
  --                          condition is closed by hbnd_i (x_k < 2^63),
  --   step                 — write the doubled limb back (array update t_k),
  --   spec_ok              — the body returns `cont` with the updated state.
  -- hv_k records v_k = s_k · 2; hd_k records the updated array for the next
  -- round's get-after-set bookkeeping.
  apply loop_step
  simp only [backend.serial.u64.field.FieldElement51.square2_loop.body]
  step with range_next_lt_spec as ⟨o1, iter1, ho1, hs1, he1⟩
  simp only [ho1]
  step as ⟨x1, hx1⟩
  simp [hs] at hx1
  step as ⟨v0, hv0⟩
  rw [hx1] at hv0
  step as ⟨t1, hd1⟩
  try simp only [spec_ok]
  -- Iteration 2 (i = 1) — the simp at hx2 additionally rewrites through
  -- iteration 1's array update (hd1 + Array.set_val_eq: get-after-set) so the
  -- read still refers to the ORIGINAL limb s1.
  apply loop_step
  simp only [backend.serial.u64.field.FieldElement51.square2_loop.body]
  step with range_next_lt_spec as ⟨o2, iter2, ho2, hs2, he2⟩
  simp only [ho2]
  step as ⟨x2, hx2⟩
  simp [hd1, Array.set_val_eq, hs, hs1, he1] at hx2
  step as ⟨v1, hv1⟩
  rw [hx2] at hv1
  step as ⟨t2, hd2⟩
  try simp only [spec_ok]
  -- Iteration 3 (i = 2)
  apply loop_step
  simp only [backend.serial.u64.field.FieldElement51.square2_loop.body]
  step with range_next_lt_spec as ⟨o3, iter3, ho3, hs3, he3⟩
  simp only [ho3]
  step as ⟨x3, hx3⟩
  simp [hd1, hd2, Array.set_val_eq, hs, hs1, he1, hs2, he2] at hx3
  step as ⟨v2, hv2⟩
  rw [hx3] at hv2
  step as ⟨t3, hd3⟩
  try simp only [spec_ok]
  -- Iteration 4 (i = 3)
  apply loop_step
  simp only [backend.serial.u64.field.FieldElement51.square2_loop.body]
  step with range_next_lt_spec as ⟨o4, iter4, ho4, hs4, he4⟩
  simp only [ho4]
  step as ⟨x4, hx4⟩
  simp [hd1, hd2, hd3, Array.set_val_eq, hs, hs1, he1, hs2, he2, hs3, he3] at hx4
  step as ⟨v3, hv3⟩
  rw [hx4] at hv3
  step as ⟨t4, hd4⟩
  try simp only [spec_ok]
  -- Iteration 5 (i = 4)
  apply loop_step
  simp only [backend.serial.u64.field.FieldElement51.square2_loop.body]
  step with range_next_lt_spec as ⟨o5, iter5, ho5, hs5, he5⟩
  simp only [ho5]
  step as ⟨x5, hx5⟩
  simp [hd1, hd2, hd3, hd4, Array.set_val_eq, hs, hs1, he1, hs2, he2, hs3, he3,
    hs4, he4] at hx5
  step as ⟨v4, hv4⟩
  rw [hx5] at hv4
  step as ⟨t5, hd5⟩
  try simp only [spec_ok]
  -- Iteration 6 (range exhausted: 5 ≥ 5) — next returns none, body answers
  -- `done` with the accumulated element
  apply loop_step
  simp only [backend.serial.u64.field.FieldElement51.square2_loop.body]
  step with range_next_ge_spec as ⟨o6, iter6, ho6, hr6⟩
  simp only [ho6]
  try simp only [spec_ok]
  -- Final: exhibit the limbs — the result array is s's array overwritten at
  -- 0..4 with v0..v4; collapsing the five set operations (Array.set_val_eq)
  -- gives [v0,...,v4]; each value equation v_k = 2·s_k is hv_k (scalar_tac
  -- normalizes the (2#u64).val literal and the multiplication order).
  refine ⟨v0, v1, v2, v3, v4, ?_, by scalar_tac, by scalar_tac, by scalar_tac,
    by scalar_tac, by scalar_tac⟩
  simp [hd1, hd2, hd3, hd4, hd5, Array.set_val_eq, hs, hs1, hs2, hs3, hs4]

/-- Main spec for `square2`: under the 2⁵⁴ invariant, no panic, output limbs
    < 2⁵³, and the denotation is twice the square in 𝔽_p.

    Rust: `FieldElement51::square2`,
    curve25519/solana-ed25519/src/backend/serial/u64/field.rs:566-573
    (`pow2k(1)` followed by `for i in 0..5 { square.0[i] *= 2; }`).
    MATH (ASCII):  Bnd(a, 2^54)  ==>  square2 a = ok r  with
      Bnd(r, 2^53)  and  [[r]] = 2 * ([[a]] * [[a]]).
    LaTeX:  $\llbracket r\rrbracket = 2\,\llbracket a\rrbracket^2$.
    PROOF: chain pow2k_spec at k = 1 (Proofs/SquareSpec.lean) — yielding s
    with Bnd(s, 2^51 + 2^13) and ⟪s⟫ = ⟪a⟫^(2^1) — into `square2_loop_spec`
    (2^51 + 2^13 < 2^63 keeps every `*= 2` in u64 range). The doubled limbs
    give, EXACTLY over ℕ, feVal r = 2·feVal s (the radix-2^51 value is linear
    in the limbs), hence ⟪r⟫ = 2·⟪s⟫ after the cast into 𝔽_p; the exponent
    bridge ⟪a⟫^(2^1) = ⟪a⟫·⟪a⟫ is `ring`, exactly as in square_spec. Bounds:
    r_i = 2·s_i < 2^52 + 2^14 ≤ 2^53 — comfortably re-usable, since every
    downstream op only asks for < 2^54.
    WHY NEEDED: 2·Z² in point doubling (`ProjectivePoint::double`,
    curve_models.rs:381-397, generated right below square2 in Funs.lean).    -/
theorem square2_spec (a : Fe) (x0 x1 x2 x3 x4 : U64)
    (ha : (↑a : List U64) = [x0, x1, x2, x3, x4])
    (hba : Bnd a (2^54)) :
    backend.serial.u64.field.FieldElement51.square2 a
      ⦃ r => Bnd r (2^53) ∧ ⟪r⟫ = 2 * (⟪a⟫ * ⟪a⟫) ⦄ := by
  unfold backend.serial.u64.field.FieldElement51.square2
  -- run pow2k at the literal 1#u32 (its debug_assert!(k > 0) is discharged
  -- inside pow2k_spec); s carries Bnd(s, 2^51+2^13) and ⟪s⟫ = ⟪a⟫^(2^1)
  apply spec_bind (pow2k_spec a 1#u32 x0 x1 x2 x3 x4 ha hba (by scalar_tac))
  rintro s ⟨hbs, hvs⟩
  -- name the limbs of the squared element and turn its invariant into the
  -- five explicit inequalities s_i < 2^51 + 2^13
  obtain ⟨s0, s1, s2, s3, s4, hsl⟩ := Fe.exists_limbs s
  rw [Bnd_eq s s0 s1 s2 s3 s4 _ hsl] at hbs
  -- run the doubling loop: 2^51 + 2^13 < 2^63, so no u64 overflow
  apply spec_mono (square2_loop_spec s s0 s1 s2 s3 s4 hsl
    ⟨by omega, by omega, by omega, by omega, by omega⟩)
  rintro r ⟨r0, r1, r2, r3, r4, hrl, h0, h1, h2, h3, h4⟩
  -- value over ℕ: doubling every limb doubles the radix-2^51 value EXACTLY
  have hval : feVal r = 2 * feVal s := by
    rw [feVal_eq r r0 r1 r2 r3 r4 hrl, feVal_eq s s0 s1 s2 s3 s4 hsl]
    simp only [limbsVal]
    omega
  refine ⟨?_, ?_⟩
  -- bound: r_i = 2·s_i < 2·(2^51 + 2^13) = 2^52 + 2^14 ≤ 2^53
  · rw [Bnd_eq r r0 r1 r2 r3 r4 _ hrl]
    refine ⟨by omega, by omega, by omega, by omega, by omega⟩
  -- denotation: cast the exact ℕ equation into 𝔽_p, then bridge ⟪a⟫^(2^1)
  -- to ⟪a⟫·⟪a⟫ (cf. square_spec)
  · have hr2 : ⟪r⟫ = 2 * ⟪s⟫ := by
      simp only [denote]
      rw [hval]
      push_cast
      ring
    have h1v : (1#u32).val = 1 := by scalar_tac
    rw [hr2, hvs, h1v]
    ring

/-- Step-friendly wrapper for `square2_spec`: same statement with the limbs
    hidden (no `ha` hypothesis), registered with `@[step]`.

    MATH: identical to square2_spec —
      Bnd(a, 2^54)  ==>  square2 a = ok r  with  Bnd(r, 2^53)  and
      [[r]] = 2 * ([[a]] * [[a]]).
    PROOF: destructure the limbs internally via `Fe.exists_limbs` and apply
    `square2_spec`.
    WHY NEEDED: the `step`/`let*` machinery matches spec lemmas against the
    goal syntactically; a lemma whose hypotheses mention existentially-found
    limbs x0..x4 cannot be applied automatically, this one can. Downstream
    proofs about point doubling consume square2 through this lemma in one
    `let*` step.                                                            -/
@[step]
theorem square2_spec' (a : Fe) (hba : Bnd a (2^54)) :
    backend.serial.u64.field.FieldElement51.square2 a
      ⦃ r => Bnd r (2^53) ∧ ⟪r⟫ = 2 * (⟪a⟫ * ⟪a⟫) ⦄ := by
  obtain ⟨x0, x1, x2, x3, x4, ha⟩ := Fe.exists_limbs a
  exact square2_spec a x0 x1 x2 x3 x4 ha hba

end CurveFieldProofs
