mirror of
https://github.com/saymrwulf/risc0-ed25519-verified.git
synced 2026-09-04 20:03:41 +00:00
85 lines
4.6 KiB
Text
85 lines
4.6 KiB
Text
|
|
/- ──────────────────────────────────────────────────────────────────────────────
|
|||
|
|
Proofs/DsmNafSpec.lean — NAF campaign, stage 4: the public spec of
|
|||
|
|
`Scalar::non_adjacent_form(5)`.
|
|||
|
|
|
|||
|
|
Composes the proven stages: both entry masserts DISCHARGED (w = 5 is in
|
|||
|
|
[2,8]), the LE byte→word load (DsmNafLoadSpec), width = 1<<<5 = 32 and
|
|||
|
|
window_mask = 31 computed, and the digit loop (DsmNafLoopSpec) seeded
|
|||
|
|
with the all-zeros state whose invariant is trivial.
|
|||
|
|
|
|||
|
|
POST: the 256 digits satisfy the NAF conditions (odd-or-zero, |d| < 16 —
|
|||
|
|
exactly `NafDigits`, what dsm_loop_spec consumes) and their signed sum
|
|||
|
|
reconstructs the scalar's little-endian byte value EXACTLY:
|
|||
|
|
nafSum res 256 = V (as integers, no modular slack).
|
|||
|
|
Requires V < 2^253 — canonical scalars, which the mul call sites provide.
|
|||
|
|
────────────────────────────────────────────────────────────────────────────── -/
|
|||
|
|
import Proofs.DsmNafLoadSpec
|
|||
|
|
import Proofs.DsmNafLoopSpec
|
|||
|
|
open Aeneas Aeneas.Std Result ControlFlow
|
|||
|
|
open curve25519_dalek
|
|||
|
|
|
|||
|
|
set_option maxHeartbeats 8000000
|
|||
|
|
set_option linter.unusedSimpArgs false
|
|||
|
|
set_option maxRecDepth 8000
|
|||
|
|
|
|||
|
|
namespace CurveFieldProofs
|
|||
|
|
|
|||
|
|
open Aeneas.Std.WP
|
|||
|
|
|
|||
|
|
/-- **Scalar::non_adjacent_form(5)**: for a scalar whose 32-byte LE value V
|
|||
|
|
is below 2^253, the result is a 256-entry NAF digit array — every digit
|
|||
|
|
odd or zero with |d| < 16, and Σ naf[k]·2^k = V exactly. Both entry
|
|||
|
|
masserts are discharged. -/
|
|||
|
|
theorem non_adjacent_form_spec (self : scalar.Scalar)
|
|||
|
|
(b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 b11 b12 b13 b14 b15 b16 b17 b18 b19 b20 b21 b22 b23 b24 b25 b26 b27 b28 b29 b30 b31 : Std.U8)
|
|||
|
|
(hb : (↑self.bytes : List Std.U8) = [b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17, b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31])
|
|||
|
|
(V : ℕ)
|
|||
|
|
(hVbytes : V = b0.val + b1.val * 2^8 + b2.val * 2^16 + b3.val * 2^24 + b4.val * 2^32 + b5.val * 2^40 + b6.val * 2^48 + b7.val * 2^56 + b8.val * 2^64 + b9.val * 2^72 + b10.val * 2^80 + b11.val * 2^88 + b12.val * 2^96 + b13.val * 2^104 + b14.val * 2^112 + b15.val * 2^120 + b16.val * 2^128 + b17.val * 2^136 + b18.val * 2^144 + b19.val * 2^152 + b20.val * 2^160 + b21.val * 2^168 + b22.val * 2^176 + b23.val * 2^184 + b24.val * 2^192 + b25.val * 2^200 + b26.val * 2^208 + b27.val * 2^216 + b28.val * 2^224 + b29.val * 2^232 + b30.val * 2^240 + b31.val * 2^248)
|
|||
|
|
(hV : V < 2^253) :
|
|||
|
|
scalar.Scalar.non_adjacent_form self 5#usize ⦃ res =>
|
|||
|
|
NafDigits res ∧ nafSum res 256 = (V : ℤ) ⦄ := by
|
|||
|
|
unfold scalar.Scalar.non_adjacent_form
|
|||
|
|
step with (massert_spec (5#usize ≥ 2#usize) (by scalar_tac)) as ⟨h2⟩
|
|||
|
|
step with (massert_spec (5#usize ≤ 8#usize) (by scalar_tac)) as ⟨h8⟩
|
|||
|
|
-- the LE load fills x_u64[0..3]; word 4 stays 0
|
|||
|
|
step with (naf_load_spec self (Array.repeat 5#usize 0#u64)
|
|||
|
|
b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 b11 b12 b13 b14 b15 b16 b17 b18 b19 b20 b21 b22 b23 b24 b25 b26 b27 b28 b29 b30 b31
|
|||
|
|
hb (by simp [List.replicate])) as ⟨v0, v1, v2, v3, ws, hws, hv0, hv1, hv2, hv3⟩
|
|||
|
|
-- width ← 1 <<< 5 (= 32), window_mask ← width − 1 (= 31)
|
|||
|
|
step as ⟨wd, hwd⟩
|
|||
|
|
have hwdv : wd = 32#u64 := by clear * - hwd; scalar_tac
|
|||
|
|
rw [hwdv]
|
|||
|
|
step as ⟨mk, hmk⟩
|
|||
|
|
have hmkv : mk = 31#u64 := by clear * - hmk; scalar_tac
|
|||
|
|
rw [hmkv]
|
|||
|
|
-- the initial all-zeros digit state
|
|||
|
|
have hz : ∀ k, k < 256 → nafDigit (Array.repeat 256#usize 0#i8) k = 0 := by
|
|||
|
|
intro k hk
|
|||
|
|
unfold nafDigit
|
|||
|
|
rw [getElem!_pos (↑(Array.repeat 256#usize 0#i8) : List Std.I8) k
|
|||
|
|
(by simp; omega)]
|
|||
|
|
simp only [Array.repeat_val, List.getElem_replicate]
|
|||
|
|
simp
|
|||
|
|
have hsum0 : nafSum (Array.repeat 256#usize 0#i8) 256 = 0 := by
|
|||
|
|
unfold nafSum
|
|||
|
|
apply Finset.sum_eq_zero
|
|||
|
|
intro k hk
|
|||
|
|
rw [hz k (Finset.mem_range.mp hk)]
|
|||
|
|
ring
|
|||
|
|
-- the word-form value
|
|||
|
|
have hVw : V = v0.val + 2^64 * (v1.val + 2^64 * (v2.val + 2^64 * (v3.val + 2^64 * 0))) := by
|
|||
|
|
rw [hVbytes, hv0, hv1, hv2, hv3]
|
|||
|
|
ring
|
|||
|
|
-- the digit loop from the trivial invariant
|
|||
|
|
apply naf_digit_loop_spec ws v0 v1 v2 v3 V hws hVw hV 256
|
|||
|
|
(Array.repeat 256#usize 0#i8) 0#usize 0#u64
|
|||
|
|
(by scalar_tac)
|
|||
|
|
(by scalar_tac)
|
|||
|
|
(by intro h; simp at h)
|
|||
|
|
(fun k _ hk => hz k hk)
|
|||
|
|
(fun k hk => ⟨Or.inl (hz k hk), by rw [hz k hk]; norm_num,
|
|||
|
|
by rw [hz k hk]; norm_num⟩)
|
|||
|
|
(by simp [hsum0])
|
|||
|
|
|
|||
|
|
end CurveFieldProofs
|