pasta-pallas-verified/verification/Proofs/SubNegSpec.lean

244 lines
12 KiB
Text
Raw Normal View History

/- ──────────────────────────────────────────────────────────────────────────────
Proofs/SubNegSpec.lean — subtraction and negation of the transpiled Fp.
RUST ANALOG (src/fields/fp.rs):
* `Fp::sub` (fp.rs:374-388): 4-limb sbb chain, then a conditional add-back
of the modulus masked by the final borrow word (`MODULUS.0[i] & borrow`).
* `Fp::neg` (fp.rs:405-...): computes p a by a 4-limb sbb chain, then
zeroes the result iff a = 0 (the `mask = ((a≠0) as u64)` trick).
THE SUB SPEC IS DELIBERATELY MORE GENERAL THAN Canon × Canon:
hypotheses feVal b ≤ P and feVal a < feVal b + P
conclusion Canon r ∧ (r + b = a r + b = a + P) (exact )
This covers the two call shapes in the crate:
* canonical x, y (x < P ≤ y + P): field subtraction;
* `sub t MODULUS` with t < 2P: the final conditional reduction of
`add` and `montgomery_reduce` (t ≥ P → t P; t < P → borrow, add-back
gives t itself).
The additive phrasing avoids -subtraction entirely; casting to 𝔽_p kills
the +P branch ((P : 𝔽_p) = 0), giving ⟪r⟫ = ⟪a⟫ ⟪b⟫.
Imports: Proofs/HelperSpecs (adc/sbb/mac atoms).
Imported by: AddSpec (add = adc chain ∘ sub · MODULUS), ReduceSpec
(montgomery_reduce ends with the same call), ConstSpecs, FieldMain.
────────────────────────────────────────────────────────────────────────────── -/
import Proofs.HelperSpecs
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 "dis" : tactic =>
`(tactic| (subst_vars; try simp [Array.set_val_eq, *]; try scalar_tac))
/-- The transpiled MODULUS constant, as a limb list. -/
theorem MODULUS_limbs :
(↑fields.fp.MODULUS : List U64) =
[11037532056220336129#u64, 2469829653914515739#u64, 0#u64,
4611686018427387904#u64] := by
unfold fields.fp.MODULUS
rfl
/-- Its exact value is the Pallas prime. -/
theorem feVal_MODULUS : feVal fields.fp.MODULUS = P := by
rw [feVal_eq _ _ _ _ _ MODULUS_limbs]
unfold limbsVal P
norm_num
/-- `Fp::sub`: exact two-case value identity + canonicity (see file header). -/
theorem sub_spec (a b : Fe) (hbP : feVal b ≤ P) (hab : feVal a < feVal b + P) :
fields.fp.Fp.sub a b
⦃ r => Canon r ∧
(feVal r + feVal b = feVal a
feVal r + feVal b = feVal a + P) ⦄ := by
obtain ⟨a0, a1, a2, a3, hla⟩ := Fe.exists_limbs a
obtain ⟨b0, b1, b2, b3, hlb⟩ := Fe.exists_limbs b
rw [feVal_eq b b0 b1 b2 b3 hlb] at hbP
rw [feVal_eq a a0 a1 a2 a3 hla, feVal_eq b b0 b1 b2 b3 hlb] at hab
unfold fields.fp.Fp.sub
-- ── limb reads + the 4-step sbb chain ────────────────────────────────────
let* ⟨ i, hi ⟩ ← Array.index_usize_spec by dis
let* ⟨ i1, hi1 ⟩ ← Array.index_usize_spec by dis
let* ⟨ d0, borrow0, hsb0 ⟩ ← sbb_spec by dis
let* ⟨ i2, hi2 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i3, hi3 ⟩ ← Array.index_usize_spec by dis
let* ⟨ d1, borrow1, hsb1 ⟩ ← sbb_spec by dis
let* ⟨ i4, hi4 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i5, hi5 ⟩ ← Array.index_usize_spec by dis
let* ⟨ d2, borrow2, hsb2 ⟩ ← sbb_spec by dis
let* ⟨ i6, hi6 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i7, hi7 ⟩ ← Array.index_usize_spec by dis
let* ⟨ d3, borrow3, hsb3 ⟩ ← sbb_spec by dis
-- ── conditional add-back: (MODULUS[i] & borrow) + adc chain ──────────────
let* ⟨ i8, hi8 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i9, hi9, hi9bv ⟩ ← UScalar.and_spec by dis
let* ⟨ d01, carry0, hadc0 ⟩ ← adc_spec by dis
let* ⟨ i10, hi10 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i11, hi11, hi11bv ⟩ ← UScalar.and_spec by dis
let* ⟨ d11, carry1, hadc1 ⟩ ← adc_spec by dis
let* ⟨ i12, hi12 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i13, hi13, hi13bv ⟩ ← UScalar.and_spec by dis
let* ⟨ d21, carry2, hadc2 ⟩ ← adc_spec by dis
let* ⟨ i14, hi14 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i15, hi15, hi15bv ⟩ ← UScalar.and_spec by dis
let* ⟨ d31, carry3, hadc3 ⟩ ← adc_spec by dis
-- ── assemble ─────────────────────────────────────────────────────────────
-- identify the reads with the named limbs / MODULUS literals
-- val-level identifications of the reads (omega links through these)
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 = a1.val := by simp [hi2, hla]
have hv_i3 : i3.val = b1.val := by simp [hi3, hlb]
have hv_i4 : i4.val = a2.val := by simp [hi4, hla]
have hv_i5 : i5.val = b2.val := by simp [hi5, hlb]
have hv_i6 : i6.val = a3.val := by simp [hi6, hla]
have hv_i7 : i7.val = b3.val := by simp [hi7, hlb]
have hv_i8 : i8.val = 11037532056220336129 := by simp [hi8, MODULUS_limbs]
have hv_i10 : i10.val = 2469829653914515739 := by simp [hi10, MODULUS_limbs]
have hv_i12 : i12.val = 0 := by simp [hi12, MODULUS_limbs]
have hv_i14 : i14.val = 4611686018427387904 := by simp [hi14, MODULUS_limbs]
-- expose the -level land in the mask equations
simp only [UScalar.val_and, hv_i8, hv_i10, hv_i12, hv_i14] at hi9 hi11 hi13 hi15
-- limb bounds (make everything linear for omega)
have hb_d0 : d0.val < 2^64 := by scalar_tac
have hb_d1 : d1.val < 2^64 := by scalar_tac
have hb_d2 : d2.val < 2^64 := by scalar_tac
have hb_d3 : d3.val < 2^64 := by scalar_tac
have hb_d01 : d01.val < 2^64 := by scalar_tac
have hb_d11 : d11.val < 2^64 := by scalar_tac
have hb_d21 : d21.val < 2^64 := by scalar_tac
have hb_d31 : d31.val < 2^64 := by scalar_tac
have hb_a0 : a0.val < 2^64 := by scalar_tac
have hb_a1 : a1.val < 2^64 := by scalar_tac
have hb_a2 : a2.val < 2^64 := by scalar_tac
have hb_a3 : a3.val < 2^64 := by scalar_tac
have hb_b0 : b0.val < 2^64 := by scalar_tac
have hb_b1 : b1.val < 2^64 := by scalar_tac
have hb_b2 : b2.val < 2^64 := by scalar_tac
have hb_b3 : b3.val < 2^64 := by scalar_tac
-- resolve the mask values in the two borrow3 cases
rcases hsb3 with ⟨hbor3, hval3⟩ | ⟨hbor3, hval3⟩ <;>
[ (simp only [hbor3, Nat.and_zero] at hi9 hi11 hi13 hi15);
(simp only [hbor3, Nat.and_two_pow_sub_one_eq_mod] at hi9 hi11 hi13 hi15;
norm_num at hi9 hi11 hi13 hi15) ] <;>
· rw [feVal_eq a a0 a1 a2 a3 hla, feVal_eq b b0 b1 b2 b3 hlb]
constructor
· -- Canon: feVal r < P
unfold Canon
simp only [feVal_make]
unfold limbsVal P at *
trace_state
omega
· -- the two-case value identity
simp only [feVal_make]
unfold limbsVal P at *
omega
/-- `Fp::neg`: total, canonical, and denotes ⟪a⟫ (for canonical input). -/
theorem neg_spec (a : Fe) (ha : Canon a) :
fields.fp.Fp.neg a
⦃ r => Canon r ∧
(feVal r + feVal a = P (feVal r = 0 ∧ feVal a = 0)) ⦄ := by
obtain ⟨a0, a1, a2, a3, hla⟩ := Fe.exists_limbs a
unfold Canon at ha
rw [feVal_eq a a0 a1 a2 a3 hla] at ha
unfold fields.fp.Fp.neg
let* ⟨ i, hi ⟩ ← Array.index_usize_spec by dis
let* ⟨ i1, hi1 ⟩ ← Array.index_usize_spec by dis
let* ⟨ d0, borrow0, hsb0 ⟩ ← sbb_spec by dis
let* ⟨ i2, hi2 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i3, hi3 ⟩ ← Array.index_usize_spec by dis
let* ⟨ d1, borrow1, hsb1 ⟩ ← sbb_spec by dis
let* ⟨ i4, hi4 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i5, hi5 ⟩ ← Array.index_usize_spec by dis
let* ⟨ d2, borrow2, hsb2 ⟩ ← sbb_spec by dis
let* ⟨ i6, hi6 ⟩ ← Array.index_usize_spec by dis
let* ⟨ i7, hi7 ⟩ ← Array.index_usize_spec by dis
let* ⟨ d3, borrow3, hsb3 ⟩ ← sbb_spec by dis
-- the is-zero test: i8 = a0 ||| a1, i9 = i8 ||| a2, i10 = i9 ||| a3
let* ⟨ i8, hi8, hi8bv ⟩ ← UScalar.or_spec by dis
let* ⟨ i9, hi9, hi9bv ⟩ ← UScalar.or_spec by dis
let* ⟨ i10, hi10, hi10bv ⟩ ← UScalar.or_spec by dis
let* ⟨ i11, hi11 ⟩ ← lift_spec by dis
let* ⟨ mask, hmask ⟩ ← lift_spec by dis
let* ⟨ i12, hi12, hi12bv ⟩ ← UScalar.and_spec by dis
let* ⟨ i13, hi13, hi13bv ⟩ ← UScalar.and_spec by dis
let* ⟨ i14, hi14, hi14bv ⟩ ← UScalar.and_spec by dis
let* ⟨ i15, hi15, hi15bv ⟩ ← UScalar.and_spec by dis
have hv_i : i.val = 11037532056220336129 := by simp [hi, MODULUS_limbs]
have hv_i1 : i1.val = a0.val := by simp [hi1, hla]
have hv_i2 : i2.val = 2469829653914515739 := by simp [hi2, MODULUS_limbs]
have hv_i3 : i3.val = a1.val := by simp [hi3, hla]
have hv_i4 : i4.val = 0 := by simp [hi4, MODULUS_limbs]
have hv_i5 : i5.val = a2.val := by simp [hi5, hla]
have hv_i6 : i6.val = 4611686018427387904 := by simp [hi6, MODULUS_limbs]
have hv_i7 : i7.val = a3.val := by simp [hi7, hla]
simp only [UScalar.val_or, hv_i1, hv_i3, hv_i5, hv_i7] at hi8 hi9 hi10
simp only [UScalar.val_and] at hi12 hi13 hi14 hi15
have hb_a0 : a0.val < 2^64 := by scalar_tac
have hb_a1 : a1.val < 2^64 := by scalar_tac
have hb_a2 : a2.val < 2^64 := by scalar_tac
have hb_a3 : a3.val < 2^64 := by scalar_tac
have hb_d0 : d0.val < 2^64 := by scalar_tac
have hb_d1 : d1.val < 2^64 := by scalar_tac
have hb_d2 : d2.val < 2^64 := by scalar_tac
have hb_d3 : d3.val < 2^64 := by scalar_tac
-- case: is the input zero?
by_cases hz : a0.val = 0 ∧ a1.val = 0 ∧ a2.val = 0 ∧ a3.val = 0
· -- a = 0: or-chain is 0, i11 = 1, mask = 0, result limbs all 0
obtain ⟨h0, h1, h2, h3⟩ := hz
have hor : i10.val = 0 := by
simp [hi10, hi9, hi8, h0, h1, h2, h3]
have hz10 : i10 = 0#u64 := by scalar_tac
have h11 : i11.val = 1 := by
subst hi11
simp [hz10]
have hm : mask.val = 0 := by
subst hmask
simp only [core.num.U64.wrapping_sub_val_eq]
simp [h11, U64.size, U64.numBits_def]
simp only [hm, Nat.and_zero] at hi12 hi13 hi14 hi15
constructor
· unfold Canon; simp only [feVal_make]; unfold limbsVal P; omega
· right
constructor
· simp only [feVal_make]; unfold limbsVal; omega
· rw [feVal_eq a a0 a1 a2 a3 hla]; unfold limbsVal; omega
· -- a ≠ 0: or-chain nonzero, i11 = 0, mask = all-ones, result = p a
have hor : i10.val ≠ 0 := by
rw [hi10, hi9, hi8]
intro hcon
apply hz
have c1 := nat_or_eq_zero hcon
have c2 := nat_or_eq_zero c1.1
have c3 := nat_or_eq_zero c2.1
exact ⟨c3.1, c3.2, c2.2, c1.2⟩
have hz10 : ¬ (i10 = 0#u64) := by scalar_tac
have h11 : i11.val = 0 := by
subst hi11
simp [hz10]
have hm : mask.val = 2^64 - 1 := by
subst hmask
simp only [core.num.U64.wrapping_sub_val_eq]
simp [h11, U64.size, U64.numBits_def]
rw [hm] at hi12 hi13 hi14 hi15
simp only [Nat.and_two_pow_sub_one_eq_mod] at hi12 hi13 hi14 hi15
rw [Nat.mod_eq_of_lt hb_d0] at hi12
rw [Nat.mod_eq_of_lt hb_d1] at hi13
rw [Nat.mod_eq_of_lt hb_d2] at hi14
rw [Nat.mod_eq_of_lt hb_d3] at hi15
constructor
· unfold Canon; simp only [feVal_make]; unfold limbsVal P at *; omega
· left
simp only [feVal_make, feVal_eq a a0 a1 a2 a3 hla]
unfold limbsVal P at *
omega
end PastaProofs