pasta-pallas-verified/verification/Proofs/MulSpec.lean
mrwulf 7d7bc25353 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:31 +02:00

160 lines
7.4 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/MulSpec.lean — multiplication of the transpiled Fp.
RUST ANALOG (src/fields/fp.rs:367-370 / 423-447):
`mul = montgomery_reduce ∘ mul_unreduced`, where mul_unreduced is the
schoolbook 4×4 product: row i (multiplier aᵢ) runs a 4-step mac chain over
b's limbs, accumulating into the running 8-limb result.
PROOF ARCHITECTURE
1. `mul_unreduced_spec` — the EXACT 512-bit identity
Σ uₖ·2^(64k) = feVal a · feVal b
The 16 mac equations are summed with weights 2^(64(i+j)) — the
cross-products aᵢ·bⱼ are NONLINEAR atoms, so this is `linear_combination`
over (after zify), NOT omega. Intermediate accumulators and carries
telescope away by construction.
2. `mul_spec` — compose with montgomery_reduce_spec (t = a·b < P² < 2²⁵⁶·P)
to get Canon r ∧ (feVal r · 2²⁵⁶) % P = (feVal a · feVal b) % P,
i.e. ⟪r⟫ = ⟪a⟫·⟪b⟫ after the denotation absorbs both R factors
(packaged in FieldMain).
MEMORY DISCIPLINE: cheap-first `dix` discharge; the linear_combination is
one closed-form certificate (no search).
────────────────────────────────────────────────────────────────────────────── -/
import Proofs.ReduceSpec
import Mathlib.Tactic.LinearCombination
open Aeneas Aeneas.Std Result
open pasta_curves
set_option maxHeartbeats 8000000
set_option linter.unusedTactic false
set_option linter.unreachableTactic false
namespace PastaProofs
open Aeneas.Std.WP
macro "dix" : tactic =>
`(tactic| (first | scalar_tac | (subst_vars; simp [Array.set_val_eq, *]; scalar_tac)))
/-- 8-limb value (the unreduced product). -/
def limbsVal8 (u0 u1 u2 u3 u4 u5 u6 u7 : U64) : :=
u0.val + 2^64 * u1.val + 2^128 * u2.val + 2^192 * u3.val +
2^256 * u4.val + 2^320 * u5.val + 2^384 * u6.val + 2^448 * u7.val
/-- `mul_unreduced` computes the exact 512-bit product. -/
theorem mul_unreduced_spec (a b : Fe) (a0 a1 a2 a3 b0 b1 b2 b3 : U64)
(hla : (↑a : List U64) = [a0, a1, a2, a3])
(hlb : (↑b : List U64) = [b0, b1, b2, b3]) :
fields.fp.Fp.mul_unreduced a b
⦃ u => ∃ u0 u1 u2 u3 u4 u5 u6 u7 : U64,
(↑u : List U64) = [u0, u1, u2, u3, u4, u5, u6, u7] ∧
limbsVal8 u0 u1 u2 u3 u4 u5 u6 u7 =
limbsVal a0 a1 a2 a3 * limbsVal b0 b1 b2 b3 ⦄ := by
unfold fields.fp.Fp.mul_unreduced
let* ⟨ i, hi ⟩ ← Array.index_usize_spec by dix
let* ⟨ i1, hi1 ⟩ ← Array.index_usize_spec by dix
let* ⟨ r0, carry, hm00 ⟩ ← mac_spec by dix
let* ⟨ i2, hi2 ⟩ ← Array.index_usize_spec by dix
let* ⟨ r1, carry1, hm01 ⟩ ← mac_spec by dix
let* ⟨ i3, hi3 ⟩ ← Array.index_usize_spec by dix
let* ⟨ r2, carry2, hm02 ⟩ ← mac_spec by dix
let* ⟨ i4, hi4 ⟩ ← Array.index_usize_spec by dix
let* ⟨ r3, r4, hm03 ⟩ ← mac_spec by dix
let* ⟨ i5, hi5 ⟩ ← Array.index_usize_spec by dix
let* ⟨ r11, carry3, hm10 ⟩ ← mac_spec by dix
let* ⟨ r21, carry4, hm11 ⟩ ← mac_spec by dix
let* ⟨ r31, carry5, hm12 ⟩ ← mac_spec by dix
let* ⟨ r41, r5, hm13 ⟩ ← mac_spec by dix
let* ⟨ i6, hi6 ⟩ ← Array.index_usize_spec by dix
let* ⟨ r22, carry6, hm20 ⟩ ← mac_spec by dix
let* ⟨ r32, carry7, hm21 ⟩ ← mac_spec by dix
let* ⟨ r42, carry8, hm22 ⟩ ← mac_spec by dix
let* ⟨ r51, r6, hm23 ⟩ ← mac_spec by dix
let* ⟨ i7, hi7 ⟩ ← Array.index_usize_spec by dix
let* ⟨ r33, carry9, hm30 ⟩ ← mac_spec by dix
let* ⟨ r43, carry10, hm31 ⟩ ← mac_spec by dix
let* ⟨ r52, carry11, hm32 ⟩ ← mac_spec by dix
let* ⟨ r61, r7, hm33 ⟩ ← mac_spec by dix
-- identify the limb reads
have hv_i : i.val = a0.val := by simp [hi, hla]
have hv_i1 : i1.val = b0.val := by simp [hi1, hlb]
have hv_i2 : i2.val = b1.val := by simp [hi2, hlb]
have hv_i3 : i3.val = b2.val := by simp [hi3, hlb]
have hv_i4 : i4.val = b3.val := by simp [hi4, hlb]
have hv_i5 : i5.val = a1.val := by simp [hi5, hla]
have hv_i6 : i6.val = a2.val := by simp [hi6, hla]
have hv_i7 : i7.val = a3.val := by simp [hi7, hla]
rw [hv_i, hv_i1, hv_i2, hv_i3, hv_i4] at *
rw [hv_i5] at hm10 hm11 hm12 hm13
rw [hv_i6] at hm20 hm21 hm22 hm23
rw [hv_i7] at hm30 hm31 hm32 hm33
refine ⟨r0, r11, r22, r33, r43, r52, r61, r7, rfl, ?_⟩
-- the exact 512-bit identity: weighted sum of the 16 mac equations.
unfold limbsVal8 limbsVal
zify at hm00 hm01 hm02 hm03 hm10 hm11 hm12 hm13
hm20 hm21 hm22 hm23 hm30 hm31 hm32 hm33 ⊢
linear_combination
hm00 + 2^64 * hm01 + 2^128 * hm02 + 2^192 * hm03 +
2^64 * hm10 + 2^128 * hm11 + 2^192 * hm12 + 2^256 * hm13 +
2^128 * hm20 + 2^192 * hm21 + 2^256 * hm22 + 2^320 * hm23 +
2^192 * hm30 + 2^256 * hm31 + 2^320 * hm32 + 2^384 * hm33
/-- `Fp::mul`: total, canonical, r·R ≡ a·b (mod p). -/
theorem mul_spec (a b : Fe) (ha : Canon a) (hb : Canon b) :
fields.fp.Fp.mul a b
⦃ r => Canon r ∧
(feVal r * 2^256) % P = (feVal a * feVal b) % P ⦄ := by
obtain ⟨a0, a1, a2, a3, hla⟩ := Fe.exists_limbs a
obtain ⟨b0, b1, b2, b3, hlb⟩ := Fe.exists_limbs b
unfold fields.fp.Fp.mul
let* ⟨ u, hu ⟩ ← mul_unreduced_spec by
(first | exact hla | exact hlb)
obtain ⟨u0, u1, u2, u3, u4, u5, u6, u7, hlu, huval⟩ := hu
let* ⟨ j0, hj0 ⟩ ← Array.index_usize_spec by dix
let* ⟨ j1, hj1 ⟩ ← Array.index_usize_spec by dix
let* ⟨ j2, hj2 ⟩ ← Array.index_usize_spec by dix
let* ⟨ j3, hj3 ⟩ ← Array.index_usize_spec by dix
let* ⟨ j4, hj4 ⟩ ← Array.index_usize_spec by dix
let* ⟨ j5, hj5 ⟩ ← Array.index_usize_spec by dix
let* ⟨ j6, hj6 ⟩ ← Array.index_usize_spec by dix
let* ⟨ j7, hj7 ⟩ ← Array.index_usize_spec by dix
have he0 : j0 = u0 := by simp [hj0, hlu]
have he1 : j1 = u1 := by simp [hj1, hlu]
have he2 : j2 = u2 := by simp [hj2, hlu]
have he3 : j3 = u3 := by simp [hj3, hlu]
have he4 : j4 = u4 := by simp [hj4, hlu]
have he5 : j5 = u5 := by simp [hj5, hlu]
have he6 : j6 = u6 := by simp [hj6, hlu]
have he7 : j7 = u7 := by simp [hj7, hlu]
subst he0 he1 he2 he3 he4 he5 he6 he7
-- t = a·b < P·P ≤ 2²⁵⁶·P: montgomery_reduce's precondition
unfold Canon at ha hb
rw [feVal_eq a a0 a1 a2 a3 hla] at ha
rw [feVal_eq b b0 b1 b2 b3 hlb] at hb
have hbound : limbsVal u0 u1 u2 u3 + 2^256 * limbsVal u4 u5 u6 u7 <
2^256 * P := by
have h1 : limbsVal a0 a1 a2 a3 * limbsVal b0 b1 b2 b3 < P * P :=
Nat.mul_lt_mul'' ha hb
have h2 : P * P ≤ 2^256 * P := by
apply Nat.mul_le_mul_right
unfold P
norm_num
have h3 : limbsVal u0 u1 u2 u3 + 2^256 * limbsVal u4 u5 u6 u7 =
limbsVal8 u0 u1 u2 u3 u4 u5 u6 u7 := by
unfold limbsVal limbsVal8
ring
rw [h3, huval]
omega
let* ⟨ r, hr_canon, hr_val ⟩ ← montgomery_reduce_spec by
exact hbound
refine ⟨hr_canon, ?_⟩
rw [feVal_eq a a0 a1 a2 a3 hla, feVal_eq b b0 b1 b2 b3 hlb]
have h3 : limbsVal u0 u1 u2 u3 + 2^256 * limbsVal u4 u5 u6 u7 =
limbsVal8 u0 u1 u2 u3 u4 u5 u6 u7 := by
unfold limbsVal limbsVal8
ring
rw [h3, huval] at hr_val
exact hr_val
end PastaProofs