/- ────────────────────────────────────────────────────────────────────────────── Proofs/PointEqSpec.lean — phase 2, THE POINT-LEVEL VERIFICATION EQUATION. THE THEOREM (`verify_accepts_iff_point_eq`): under the half-lift's hypotheses, for ANY valid on-curve point Q whose canonical encoding is the signature's R bytes, verifier accepts ⇔ Q = [k]·(−A) + [s]·B (as denoted points). This closes the gap between "the bytes match" and "the points match" WITHOUT decompress: the canonical encoding (y-residue + x-parity bit) is INJECTIVE on curve points, because the curve equation determines x² from y — here is where the non-squareness of d (edD_not_square, the Bernstein–Lange completeness ingredient) does its second job: 1 + d·y² can never vanish, so x² = (y²−1)/(1+d·y²) is well-defined — and the parity bit selects between the two roots (±x have different parities mod an odd prime, unless x = 0, where they coincide). Decompress (extracted this increment, proofs in the sequel) will add the CONSTRUCTIVE version: the accepted bytes decompress to the point. ────────────────────────────────────────────────────────────────────────────── -/ import Proofs.PointLiftSpec open Aeneas Aeneas.Std Result open curve25519_dalek set_option maxHeartbeats 4000000 set_option linter.unusedSimpArgs false namespace CurveFieldProofs open Aeneas.Std.WP /-- 1 + d·y² never vanishes: otherwise d = −(1/y)², and since −1 IS a square mod p (p ≡ 1 mod 4), d would be a square — contradicting `edD_not_square`. -/ theorem one_add_d_y_sq_ne_zero (y : Fp) : 1 + edD * y ^ 2 ≠ 0 := by intro h have hy : y ≠ 0 := by intro h0 rw [h0] at h simp at h -- −1 is a square mod p: p % 4 = 1 have hsq : IsSquare (-1 : Fp) := by rw [ZMod.exists_sq_eq_neg_one_iff] unfold P norm_num obtain ⟨i, hi⟩ := hsq -- d = −(y⁻¹)² = (i·y⁻¹)² apply edD_not_square refine ⟨i * y⁻¹, ?_⟩ have hd : edD * y ^ 2 = -1 := by linear_combination h have hy2 : (y : Fp) ^ 2 ≠ 0 := pow_ne_zero 2 hy calc edD = edD * y ^ 2 * (y ^ 2)⁻¹ := by field_simp _ = -1 * (y ^ 2)⁻¹ := by rw [hd] _ = (i * i) * (y ^ 2)⁻¹ := by rw [← hi] _ = (i * y⁻¹) * (i * y⁻¹) := by field_simp /-- On-curve x² is determined by y. -/ theorem x_sq_of_onCurve {x y : Fp} (h : OnCurve x y) : x ^ 2 * (1 + edD * y ^ 2) = y ^ 2 - 1 := by unfold OnCurve at h linear_combination -h /-- **Canonical-encoding injectivity on the curve** (coordinate form): same y, same x-parity, both on-curve ⇒ same x. -/ theorem enc_inj_coord {x1 x2 y : Fp} (h1 : OnCurve x1 y) (h2 : OnCurve x2 y) (hpar : x1.val % 2 = x2.val % 2) : x1 = x2 := by haveI : NeZero P := ⟨by unfold P; norm_num⟩ -- x1² = x2² from the curve equation (1 + d·y² is invertible) have hsq : x1 ^ 2 = x2 ^ 2 := by have e1 := x_sq_of_onCurve h1 have e2 := x_sq_of_onCurve h2 have hne := one_add_d_y_sq_ne_zero y have : (x1 ^ 2 - x2 ^ 2) * (1 + edD * y ^ 2) = 0 := by linear_combination e1 - e2 rcases mul_eq_zero.mp this with h | h · linear_combination h · exact absurd h hne -- hence x1 = ±x2 have hpm : x1 = x2 ∨ x1 = -x2 := by have : (x1 - x2) * (x1 + x2) = 0 := by ring_nf; linear_combination hsq rcases mul_eq_zero.mp this with h | h · left; linear_combination h · right; linear_combination h rcases hpm with h | h · exact h · -- x1 = −x2: parity separates them unless x2 = 0 by_cases hz : x2 = 0 · rw [h, hz]; simp · exfalso have hval : x1.val = P - x2.val := by rw [h, ZMod.neg_val, if_neg hz] have hlt : x2.val < P := ZMod.val_lt x2 have hpos : 0 < x2.val := by rcases Nat.eq_zero_or_pos x2.val with h0 | h0 · exact absurd ((ZMod.val_eq_zero x2).mp h0) hz · exact h0 have hodd : P % 2 = 1 := by unfold P; norm_num omega /-- **Canonical-encoding injectivity on points**: equal encodings of valid on-curve points force equal denoted points. -/ theorem enc_point_inj (Pt Q : EdPoint) (hPv : ExtValid Pt) (hPc : OnCurveExt Pt) (hQv : ExtValid Q) (hQc : OnCurveExt Q) (h : (edY Pt).val + ((edX Pt).val % 2) * 2^255 = (edY Q).val + ((edX Q).val % 2) * 2^255) : edPt Pt = edPt Q := by haveI : NeZero P := ⟨by unfold P; norm_num⟩ -- split the encoding: y-residues < p < 2²⁵⁵, parities ∈ {0,1} have hyP : (edY Pt).val < 2^255 := lt_of_lt_of_le (ZMod.val_lt _) (by unfold P; norm_num) have hyQ : (edY Q).val < 2^255 := lt_of_lt_of_le (ZMod.val_lt _) (by unfold P; norm_num) have hb1 := Nat.mod_two_eq_zero_or_one (edX Pt).val have hb2 := Nat.mod_two_eq_zero_or_one (edX Q).val have hkey : (edY Pt).val = (edY Q).val ∧ (edX Pt).val % 2 = (edX Q).val % 2 := by rcases hb1 with h1 | h1 <;> rcases hb2 with h2 | h2 <;> rw [h1, h2] at h <;> constructor <;> omega obtain ⟨hy, hpar⟩ := hkey have hyy : edY Pt = edY Q := ZMod.val_injective _ hy have hxx : edX Pt = edX Q := by apply enc_inj_coord (y := edY Q) · rw [← hyy]; exact hPc · exact hQc · exact hpar unfold edPt rw [hxx, hyy] open ed25519_dalek in /-- **THE POINT-LEVEL VERIFICATION EQUATION.** Under the half-lift's hypotheses, for any valid on-curve point Q whose canonical encoding is the signature's R bytes: the verifier accepts **iff** Q equals the recomputed point [k]·(−A) + [s]·B as denoted affine points. -/ theorem verify_accepts_iff_point_eq (key : verifying.VerifyingKey) (msg : Slice Std.U8) (sig : ed25519.Signature) (val : signature.InternalSignature) (er : curve25519_dalek.edwards.CompressedEdwardsY) (e r1 : Std.Array Std.U8 32#usize) (hparse : signature.InternalSignature.Insts.CoreConvertTryFromShared0SignatureError.try_from sig = ok (core.result.Result.Ok val)) (hrec : verifying.recompute_r_sha512 key val msg = ok er) (he : curve25519_dalek.edwards.CompressedEdwardsY.as_bytes er = ok e) (hr1 : curve25519_dalek.edwards.CompressedEdwardsY.as_bytes val.R = ok r1) (hkv : ExtValid key.point) (hkc : OnCurveExt key.point) (t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t18 t19 t20 t21 t22 t23 t24 t25 t26 t27 t28 t29 t30 t31 : Std.U8) (hsb : (↑val.s.bytes : List Std.U8) = [t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24, t25, t26, t27, t28, t29, t30, t31]) (Vs : ℕ) (hVs : Vs = t0.val + t1.val * 2^8 + t2.val * 2^16 + t3.val * 2^24 + t4.val * 2^32 + t5.val * 2^40 + t6.val * 2^48 + t7.val * 2^56 + t8.val * 2^64 + t9.val * 2^72 + t10.val * 2^80 + t11.val * 2^88 + t12.val * 2^96 + t13.val * 2^104 + t14.val * 2^112 + t15.val * 2^120 + t16.val * 2^128 + t17.val * 2^136 + t18.val * 2^144 + t19.val * 2^152 + t20.val * 2^160 + t21.val * 2^168 + t22.val * 2^176 + t23.val * 2^184 + t24.val * 2^192 + t25.val * 2^200 + t26.val * 2^208 + t27.val * 2^216 + t28.val * 2^224 + t29.val * 2^232 + t30.val * 2^240 + t31.val * 2^248) (hVslt : Vs < 2^253) (Q : EdPoint) (hQv : ExtValid Q) (hQc : OnCurveExt Q) (henc : bytesVal r1 = (edY Q).val + ((edX Q).val % 2) * 2^255) : ∃ (R' : EdPoint), ExtValid R' ∧ OnCurveExt R' ∧ (verifying.verify_sha512 key msg sig = ok (core.result.Result.Ok ()) ↔ edPt Q = edPt R') := by obtain ⟨R', hRv, hRc, hiff⟩ := verify_accepts_iff_point key msg sig val er e r1 hparse hrec he hr1 hkv hkc t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 t16 t17 t18 t19 t20 t21 t22 t23 t24 t25 t26 t27 t28 t29 t30 t31 hsb Vs hVs hVslt refine ⟨R', hRv, hRc, ?_⟩ rw [hiff] constructor · -- bytes match ⇒ encodings of Q and R' coincide ⇒ points coincide intro h exact enc_point_inj Q R' hQv hQc hRv hRc (by rw [← henc, h]) · -- points coincide ⇒ encodings coincide ⇒ bytes match intro h have hx : edX Q = edX R' := congrArg Prod.fst h have hy : edY Q = edY R' := congrArg Prod.snd h rw [henc, hx, hy] end CurveFieldProofs