mirror of
https://github.com/saymrwulf/anza-ed25519-verified.git
synced 2026-09-05 20:30:53 +00:00
208 lines
9.5 KiB
Text
208 lines
9.5 KiB
Text
|
|
/- ──────────────────────────────────────────────────────────────────────────────
|
|||
|
|
Proofs/SigApexSpec.lean — the signature-layer apex, phase 1:
|
|||
|
|
the EdDSA verification equation, SHA-512 opaque.
|
|||
|
|
|
|||
|
|
`VerificationKey.verify_sha512 self sig msg` is the extracted
|
|||
|
|
solana-ed25519 verifier (dalek-style canonical-R path, gen/CurveField —
|
|||
|
|
anza's verify code lives in the SAME crate as the curve, so the whole
|
|||
|
|
path shares one extraction universe): run the legacy filters, parse the
|
|||
|
|
scalar `s`, recompute
|
|||
|
|
R' = compress( [k]·(−A) + [s]·B ) (k from the SHA-512 hash)
|
|||
|
|
and accept iff R' equals the signature's R, byte-for-byte.
|
|||
|
|
|
|||
|
|
This file proves the verifier's control flow reduces EXACTLY to that
|
|||
|
|
recompute-and-compare — the literal EdDSA check — over the PROVEN curve
|
|||
|
|
model. SHA-512 stays an opaque oracle: the statement holds for whatever
|
|||
|
|
bytes the hash produces, so the theorem is the honest
|
|||
|
|
"accept ↔ the recomputed compressed point equals R".
|
|||
|
|
|
|||
|
|
The legacy filters (all-zero key, excluded R list) and the s < ℓ parse
|
|||
|
|
are fixed by hypotheses, mirroring the sibling pyramids' `hparse`; the
|
|||
|
|
apex boundary is the tightest of the four: the SHA-512 oracle plus the
|
|||
|
|
foreign `ed25519::Signature` type with its two byte accessors — the
|
|||
|
|
`Error` enum and the backend dispatch are real extracted code here.
|
|||
|
|
|
|||
|
|
Phase 2 (the point-level equation, needing `to_bytes` canonicity and
|
|||
|
|
`decompress`) is deliberately deferred and documented — mirroring the
|
|||
|
|
dsm layer's phase split.
|
|||
|
|
────────────────────────────────────────────────────────────────────────────── -/
|
|||
|
|
import Proofs.ScalarDenote
|
|||
|
|
import Proofs.AddSpec
|
|||
|
|
open Aeneas Aeneas.Std Result ControlFlow
|
|||
|
|
open curve25519
|
|||
|
|
|
|||
|
|
set_option maxHeartbeats 4000000
|
|||
|
|
set_option linter.unusedSimpArgs false
|
|||
|
|
set_option maxRecDepth 8000
|
|||
|
|
|
|||
|
|
namespace CurveFieldProofs
|
|||
|
|
|
|||
|
|
open Aeneas.Std.WP
|
|||
|
|
|
|||
|
|
/-- Byte-equality of two 32-byte arrays over the tail `[n, 32)`. -/
|
|||
|
|
def rangeEq (e r : Array Std.U8 32#usize) (n : ℕ) : Prop :=
|
|||
|
|
∀ j, n ≤ j → j < 32 → e.val[j]! = r.val[j]!
|
|||
|
|
|
|||
|
|
instance (e r : Array Std.U8 32#usize) (n : ℕ) : Decidable (rangeEq e r n) := by
|
|||
|
|
have : rangeEq e r n ↔ ∀ j, j < 32 → n ≤ j → e.val[j]! = r.val[j]! := by
|
|||
|
|
unfold rangeEq; exact ⟨fun h j hj hn => h j hn hj, fun h j hn hj => h j hj hn⟩
|
|||
|
|
exact decidable_of_iff _ this.symm
|
|||
|
|
|
|||
|
|
/-- **The comparison loop returns byte-equality.** From accumulator `b` at
|
|||
|
|
index `i ≤ 32`, `verify_sha512_loop` returns `b ∧ (all bytes in [i,32)
|
|||
|
|
agree)`. NOTE the extracted loop's parameter order is `(r, e, b, i)`
|
|||
|
|
while the body compares `e[k] != r[k]`. -/
|
|||
|
|
theorem verify_loop_spec (e r : Array Std.U8 32#usize) :
|
|||
|
|
∀ (n : ℕ) (b : Bool) (i : Usize), i.val = 32 - n → n ≤ 32 →
|
|||
|
|
ed_sigs.verification_key.VerificationKey.verify_sha512_loop r e b i
|
|||
|
|
⦃ res => res = (b && decide (rangeEq e r (32 - n))) ⦄ := by
|
|||
|
|
intro n
|
|||
|
|
induction n with
|
|||
|
|
| zero =>
|
|||
|
|
intro b i hi _
|
|||
|
|
unfold ed_sigs.verification_key.VerificationKey.verify_sha512_loop
|
|||
|
|
apply loop_step
|
|||
|
|
simp only [ed_sigs.verification_key.VerificationKey.verify_sha512_loop.body]
|
|||
|
|
have hge : ¬ (i < 32#usize) := by clear * - hi; scalar_tac
|
|||
|
|
rw [if_neg hge]
|
|||
|
|
try simp only [spec_ok]
|
|||
|
|
have hemp : decide (rangeEq e r (32 - 0)) = true := by
|
|||
|
|
simp only [decide_eq_true_eq]; intro j hj1 hj2; omega
|
|||
|
|
rw [hemp, Bool.and_true]
|
|||
|
|
| succ n ih =>
|
|||
|
|
intro b i hi hle
|
|||
|
|
unfold ed_sigs.verification_key.VerificationKey.verify_sha512_loop
|
|||
|
|
apply loop_step
|
|||
|
|
simp only [ed_sigs.verification_key.VerificationKey.verify_sha512_loop.body]
|
|||
|
|
have hlt : i < 32#usize := by clear * - hi hle; scalar_tac
|
|||
|
|
rw [if_pos hlt]
|
|||
|
|
have hiv : i.val = 32 - (n + 1) := hi
|
|||
|
|
have hb1 : i.val < (e.val).length := by clear * - hle hiv; scalar_tac
|
|||
|
|
have hb2 : i.val < (r.val).length := by clear * - hle hiv; scalar_tac
|
|||
|
|
-- e[i], r[i]
|
|||
|
|
step as ⟨x, hx⟩
|
|||
|
|
step as ⟨y, hy⟩
|
|||
|
|
-- the accumulator update: reduce the `if` to a plain `ok`
|
|||
|
|
have hite : (if (x != y) = true then (ok false : Result Bool) else ok b)
|
|||
|
|
= ok (if (x != y) = true then false else b) := by
|
|||
|
|
by_cases hc : (x != y) = true
|
|||
|
|
· rw [if_pos hc, if_pos hc]
|
|||
|
|
· rw [if_neg hc, if_neg hc]
|
|||
|
|
rw [hite]
|
|||
|
|
-- name the reduced accumulator, then reduce the trivial `ok` bind
|
|||
|
|
generalize heq1 : (if (x != y) = true then false else b) = eq1
|
|||
|
|
simp only [bind_tc_ok]
|
|||
|
|
-- i + 1
|
|||
|
|
step as ⟨i3, hi3⟩
|
|||
|
|
have hnext : i3.val = 32 - n := by clear * - hi3 hiv hle; scalar_tac
|
|||
|
|
try simp only [spec_ok]
|
|||
|
|
-- close with the IH at (eq1, i3); rewrite the range split
|
|||
|
|
apply spec_mono (ih eq1 i3 hnext (by omega))
|
|||
|
|
intro res hres
|
|||
|
|
rw [hres]
|
|||
|
|
-- eq1 = (b && e[i]=r[i]); the [i,32) range = byte i ∧ [i+1,32)
|
|||
|
|
have hxv : x = e.val[i.val]'hb1 := by rw [hx]
|
|||
|
|
have hyv : y = r.val[i.val]'hb2 := by rw [hy]
|
|||
|
|
have heq1v : eq1 = (b && decide (e.val[i.val]! = r.val[i.val]!)) := by
|
|||
|
|
rw [← heq1, hxv, hyv]
|
|||
|
|
rw [getElem!_pos e.val i.val hb1, getElem!_pos r.val i.val hb2]
|
|||
|
|
by_cases h : e.val[i.val]'hb1 = r.val[i.val]'hb2
|
|||
|
|
· have hb : ¬ ((e.val[i.val]'hb1 != r.val[i.val]'hb2) = true) := by
|
|||
|
|
simp [bne_iff_ne, h]
|
|||
|
|
rw [if_neg hb]; simp [h]
|
|||
|
|
· have hb : (e.val[i.val]'hb1 != r.val[i.val]'hb2) = true := by
|
|||
|
|
simp [bne_iff_ne, h]
|
|||
|
|
rw [if_pos hb]; simp [h]
|
|||
|
|
rw [heq1v, Bool.and_assoc]
|
|||
|
|
congr 1
|
|||
|
|
-- decide(byte i) && decide(tail [i+1,32)) = decide(rangeEq [i,32))
|
|||
|
|
have hiff : rangeEq e r (32 - (n + 1)) ↔
|
|||
|
|
(e.val[i.val]! = r.val[i.val]!) ∧ rangeEq e r (32 - n) := by
|
|||
|
|
have h32 : i.val < 32 := by clear * - hlt; scalar_tac
|
|||
|
|
constructor
|
|||
|
|
· intro h
|
|||
|
|
refine ⟨h i.val (by clear * - hiv; omega) h32, ?_⟩
|
|||
|
|
intro j hj1 hj2; exact h j (by omega) hj2
|
|||
|
|
· rintro ⟨hbyte, htail⟩ j hj1 hj2
|
|||
|
|
rcases Nat.lt_or_ge j (32 - n) with hj | hj
|
|||
|
|
· have hji : j = i.val := by clear * - hj1 hj hiv; omega
|
|||
|
|
rw [hji]; exact hbyte
|
|||
|
|
· exact htail j hj hj2
|
|||
|
|
have hda : (decide (e.val[i.val]! = r.val[i.val]!) && decide (rangeEq e r (32 - n)))
|
|||
|
|
= decide ((e.val[i.val]! = r.val[i.val]!) ∧ rangeEq e r (32 - n)) := by
|
|||
|
|
by_cases hp : (e.val[i.val]! = r.val[i.val]!) <;>
|
|||
|
|
by_cases hq : rangeEq e r (32 - n) <;> simp [hp, hq]
|
|||
|
|
rw [hda, decide_eq_decide]
|
|||
|
|
exact hiff.symm
|
|||
|
|
|
|||
|
|
/-- The comparison loop from the verifier's entry state (`b = true`,
|
|||
|
|
`i = 0`): the result equals the full 32-byte equality. -/
|
|||
|
|
theorem verify_loop_full (e r : Array Std.U8 32#usize) :
|
|||
|
|
ed_sigs.verification_key.VerificationKey.verify_sha512_loop r e true 0#usize
|
|||
|
|
⦃ res => res = decide (rangeEq e r 0) ⦄ := by
|
|||
|
|
have h := verify_loop_spec e r 32 true 0#usize (by scalar_tac) (le_refl _)
|
|||
|
|
apply spec_mono h
|
|||
|
|
intro res hres; rw [hres]; simp
|
|||
|
|
|
|||
|
|
/-! ### The apex: the EdDSA verification equation -/
|
|||
|
|
|
|||
|
|
open ed_sigs ed_sigs.verification_key in
|
|||
|
|
/-- **The EdDSA verification equation, SHA-512 opaque.** For a signature
|
|||
|
|
whose byte accessors are total (`hrb`, `hsb`), a key that passes the
|
|||
|
|
all-zero filter (`hA`), an `R` outside the legacy-excluded list
|
|||
|
|
(`hleg`), a canonical scalar `s < ℓ` (`hs`), and a total recomputation
|
|||
|
|
(`hrec`, `he`), the extracted solana-ed25519 dalek-style verifier
|
|||
|
|
accepts **iff** the recomputed compressed point equals the signature's
|
|||
|
|
`R`, byte-for-byte:
|
|||
|
|
verify_sha512 self sig msg = ok (Ok ()) ↔ e = R (all 32 bytes).
|
|||
|
|
|
|||
|
|
`er` (via `recompute_r_sha512`) is the PROVEN composition
|
|||
|
|
`compress( [k]·(−A) + [s]·B )` over the certified curve model; `k` is
|
|||
|
|
the scalar the SHA-512 oracle produces — the hash stays opaque, so this
|
|||
|
|
is exactly the honest EdDSA acceptance criterion (ZIP-215 legacy
|
|||
|
|
filters conditioned out by hypothesis). -/
|
|||
|
|
theorem verify_accepts_iff
|
|||
|
|
(self : verification_key.VerificationKey)
|
|||
|
|
(sig : ed25519.Signature) (msg : Slice Std.U8)
|
|||
|
|
(rb sb : Array Std.U8 32#usize) (s : scalar.Scalar)
|
|||
|
|
(er : edwards.CompressedEdwardsY) (e : Array Std.U8 32#usize)
|
|||
|
|
(hrb : ed25519.Signature.r_bytes sig = ok rb)
|
|||
|
|
(hsb : ed25519.Signature.s_bytes sig = ok sb)
|
|||
|
|
(hA : VerificationKey.a_bytes_nonzero self = ok true)
|
|||
|
|
(hleg : is_legacy_excluded_r rb = ok false)
|
|||
|
|
(hs : check_scalar_canonical sb = ok (core.result.Result.Ok s))
|
|||
|
|
(hrec : VerificationKey.recompute_r_sha512 self rb s msg = ok er)
|
|||
|
|
(he : edwards.CompressedEdwardsY.as_bytes er = ok e) :
|
|||
|
|
VerificationKey.verify_sha512 self sig msg = ok (core.result.Result.Ok ())
|
|||
|
|
↔ rangeEq e rb 0 := by
|
|||
|
|
unfold VerificationKey.verify_sha512
|
|||
|
|
rw [hrb]
|
|||
|
|
simp only [bind_tc_ok]
|
|||
|
|
rw [hsb]
|
|||
|
|
simp only [bind_tc_ok]
|
|||
|
|
rw [hA]
|
|||
|
|
simp only [bind_tc_ok, if_true]
|
|||
|
|
rw [hleg]
|
|||
|
|
simp only [bind_tc_ok, Bool.false_eq_true, if_false]
|
|||
|
|
rw [hs]
|
|||
|
|
simp only [bind_tc_ok]
|
|||
|
|
simp only [core.result.Result.Insts.CoreOpsTry_traitTry.branch, bind_tc_ok]
|
|||
|
|
rw [hrec]
|
|||
|
|
simp only [bind_tc_ok]
|
|||
|
|
rw [he]
|
|||
|
|
simp only [bind_tc_ok]
|
|||
|
|
have hloop := verify_loop_full e rb
|
|||
|
|
obtain ⟨v, hv, hpost⟩ := spec_imp_exists hloop
|
|||
|
|
rw [hv]
|
|||
|
|
simp only [bind_tc_ok]
|
|||
|
|
rw [hpost]
|
|||
|
|
by_cases hb : rangeEq e rb 0
|
|||
|
|
· rw [decide_eq_true hb, if_pos rfl]
|
|||
|
|
simp only [hb, iff_true]
|
|||
|
|
· rw [decide_eq_false hb, if_neg (by simp)]
|
|||
|
|
constructor
|
|||
|
|
· intro hcontra; simp_all
|
|||
|
|
· intro hc; exact absurd hc hb
|
|||
|
|
|
|||
|
|
end CurveFieldProofs
|