mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
mlkem/easycrypt: port ML-KEM proofs to current EasyCrypt
Fixed message-type inconsistencies in the model; '^^' is now boolean-xor (use '^' = RField.exp via Real); import RealOrder; stricter apply/rewrite. Reproved mlkem_correctness and IND-CCA2 advantage bound. Verified: all check under easycrypt (z3 + alt-ergo), 0 admits. Part of the 41/41 EasyCrypt corpus that now checks against the current EC build. Added modeling axioms are trusted-base only (non-negativity, group right-identity, byte-decode/CT-leakage specs in the same vein as pre-existing primitive axioms) — no security conclusion assumed, no lemma weakened. Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
co-authored by
Hanzo Dev
parent
0770209513
commit
79bc183c6e
@@ -32,3 +32,4 @@ QWEN.md
|
||||
target/
|
||||
test-results/
|
||||
tmp/
|
||||
*.eco
|
||||
|
||||
@@ -145,12 +145,20 @@ op encaps : ps_id_t -> rand_t -> pk_t -> ct_t * ss_t.
|
||||
op decaps : ps_id_t -> sk_t -> ct_t -> ss_t.
|
||||
|
||||
(* The underlying Kyber.CPAPKE (CPA-secure PKE), used for the
|
||||
Encaps/Decaps payload before the FO-K transform. *)
|
||||
op cpapke_encrypt : ps_id_t -> rand_t -> pk_t -> ss_t -> ct_t.
|
||||
op cpapke_decrypt : ps_id_t -> sk_t -> ct_t -> ss_t.
|
||||
Encaps/Decaps payload before the FO-K transform.
|
||||
|
||||
The plaintext "message" m and the decrypted message m' are 32-byte
|
||||
values of type `rand_t` (the same domain the encaps message is drawn
|
||||
from); the shared secret K is the distinct type `ss_t`. The encryption
|
||||
coins are `rand_t` as well. So:
|
||||
cpapke_encrypt p coins pk m -- coins : rand_t, message m : rand_t
|
||||
cpapke_decrypt p sk ct -- recovers the message m : rand_t *)
|
||||
op cpapke_encrypt : ps_id_t -> rand_t -> pk_t -> rand_t -> ct_t.
|
||||
op cpapke_decrypt : ps_id_t -> sk_t -> ct_t -> rand_t.
|
||||
|
||||
(* The FIPS 203 Encaps internally calls G to derive (K, r) from (m, pk)
|
||||
and then cpapke_encrypt(r, pk, m). We name the G output. *)
|
||||
and then cpapke_encrypt(r, pk, m). We name the G output (K : ss_t,
|
||||
r : rand_t). *)
|
||||
op hash_g : (rand_t * pk_t) -> ss_t * rand_t.
|
||||
|
||||
(* H : pk -> 32-byte hash. *)
|
||||
@@ -174,10 +182,10 @@ op hash_j : sk_t -> ct_t -> ss_t.
|
||||
op good_tape : ps_id_t -> rand_t -> pk_t -> bool.
|
||||
|
||||
axiom cpapke_decrypt_inverse
|
||||
(p : ps_id_t) (r : rand_t) (pk : pk_t) (sk : sk_t) (m : ss_t) :
|
||||
(p : ps_id_t) (coins : rand_t) (pk : pk_t) (sk : sk_t) (m : rand_t) :
|
||||
honest_keypair pk sk =>
|
||||
good_tape p r pk =>
|
||||
cpapke_decrypt p sk (cpapke_encrypt p r pk m) = m.
|
||||
good_tape p m pk =>
|
||||
cpapke_decrypt p sk (cpapke_encrypt p coins pk m) = m.
|
||||
|
||||
(* HYP 2: FO-K transform recovery.
|
||||
|
||||
@@ -191,12 +199,12 @@ axiom fo_k_recovery
|
||||
(p : ps_id_t) (r : rand_t) (pk : pk_t) (sk : sk_t) :
|
||||
honest_keypair pk sk =>
|
||||
good_tape p r pk =>
|
||||
let (m, k_seed) = hash_g (r, pk) in
|
||||
let ct = cpapke_encrypt p k_seed pk m in
|
||||
let (k_seed, r') = hash_g (r, pk) in
|
||||
let ct = cpapke_encrypt p r' pk r in
|
||||
let m' = cpapke_decrypt p sk ct in
|
||||
let (k_seed', _) = hash_g (m', pk) in
|
||||
let (k_seed', r'') = hash_g (m', pk) in
|
||||
k_seed = k_seed' /\
|
||||
cpapke_encrypt p k_seed' pk m' = ct.
|
||||
cpapke_encrypt p r'' pk m' = ct.
|
||||
|
||||
(* HYP 3: hash_g is deterministic and matches the FIPS 203 spec. *)
|
||||
axiom hash_g_functional :
|
||||
@@ -214,7 +222,7 @@ axiom hash_h_functional :
|
||||
produce the ciphertext via cpapke_encrypt. *)
|
||||
op encaps_internal (p : ps_id_t) (r : rand_t) (pk : pk_t) : ct_t * ss_t =
|
||||
let (k_seed, r') = hash_g (r, pk) in
|
||||
let ct = cpapke_encrypt p k_seed pk r in
|
||||
let ct = cpapke_encrypt p r' pk r in
|
||||
(ct, k_seed).
|
||||
|
||||
(* Decaps internals: decrypt the message, recompute K, and check
|
||||
@@ -238,8 +246,8 @@ axiom decaps_internal_spec
|
||||
(p : ps_id_t) (sk : sk_t) (ct : ct_t) (pk : pk_t) :
|
||||
honest_keypair pk sk =>
|
||||
let m' = cpapke_decrypt p sk ct in
|
||||
let (k_seed, _) = hash_g (m', pk) in
|
||||
let ct' = cpapke_encrypt p k_seed pk m' in
|
||||
let (k_seed, r') = hash_g (m', pk) in
|
||||
let ct' = cpapke_encrypt p r' pk m' in
|
||||
decaps_internal p sk ct = (if ct' = ct then k_seed else hash_j sk ct).
|
||||
|
||||
lemma mlkem_correctness
|
||||
@@ -249,26 +257,28 @@ lemma mlkem_correctness
|
||||
let (ct, ss) = encaps p r pk in
|
||||
decaps p sk ct = ss.
|
||||
proof.
|
||||
move => Hkey Htape.
|
||||
move=> Hkey Htape.
|
||||
rewrite (encaps_is_internal p r pk) /encaps_internal /=.
|
||||
pose g_out := hash_g (r, pk).
|
||||
have HgEq : hash_g (r, pk) = g_out by trivial.
|
||||
pose k_seed := g_out.`1.
|
||||
pose r' := g_out.`2.
|
||||
pose ct := cpapke_encrypt p k_seed pk r.
|
||||
pose ss := k_seed.
|
||||
rewrite (decaps_is_internal p sk ct).
|
||||
(* Apply FO-K recovery on the honest tape. *)
|
||||
(* Expose hash_g (r, pk) = (k_seed, r') once, keeping the equation Hg so
|
||||
the shared subterm in the decaps obligations reduces uniformly. *)
|
||||
have [k_seed r' Hg] : exists a b, hash_g (r, pk) = (a, b)
|
||||
by exists (hash_g (r, pk)).`1 (hash_g (r, pk)).`2; rewrite -pairS.
|
||||
rewrite Hg /=.
|
||||
pose ct := cpapke_encrypt p r' pk r.
|
||||
(* Decryption recovers the encapsulated message r on the honest tape. *)
|
||||
have Hdec : cpapke_decrypt p sk ct = r
|
||||
by rewrite /ct (cpapke_decrypt_inverse p r' pk sk r Hkey Htape).
|
||||
(* FO-K recovery, reduced via Hg / Hdec: its k_seed = k_seed' conjunct
|
||||
collapses (both are k_seed), leaving the re-encryption equality. *)
|
||||
have HFO := fo_k_recovery p r pk sk Hkey Htape.
|
||||
rewrite /= in HFO.
|
||||
case HFO => Heq Hreencrypt.
|
||||
rewrite (decaps_internal_spec p sk ct pk Hkey) /=.
|
||||
have Hreenc :
|
||||
let m' = cpapke_decrypt p sk ct in
|
||||
let (k_seed', _) = hash_g (m', pk) in
|
||||
cpapke_encrypt p k_seed' pk m' = ct
|
||||
by smt(fo_k_recovery hash_g_functional).
|
||||
smt(fo_k_recovery cpapke_decrypt_inverse hash_g_functional).
|
||||
rewrite Hg /= -/ct Hdec Hg /= in HFO.
|
||||
(* Decaps spec, reduced: decaps_internal returns k_seed iff the
|
||||
re-encryption matches, which it does by HFO. *)
|
||||
have Hspec := decaps_internal_spec p sk ct pk Hkey.
|
||||
move: Hspec => /=.
|
||||
rewrite Hdec Hg /=.
|
||||
move=> Hspec.
|
||||
by rewrite (decaps_is_internal p sk ct) Hspec HFO.
|
||||
qed.
|
||||
|
||||
(* -------------------------------------------------------------------- *)
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
(* [NIST24] NIST FIPS 203, August 2024. *)
|
||||
(* -------------------------------------------------------------------- *)
|
||||
|
||||
require import AllCore List Int IntDiv Distr DBool DInterval SmtMap Real.
|
||||
require import AllCore List Int IntDiv Distr DBool DInterval SmtMap Real StdOrder.
|
||||
import RealOrder.
|
||||
|
||||
(* -------------------------------------------------------------------- *)
|
||||
(* Reuse types from MLKEM_Correctness.ec *)
|
||||
@@ -84,9 +85,9 @@ module IND_CCA2 (A : IND_CCA2_Adv) = {
|
||||
|
||||
proc main() : bool = {
|
||||
var rk, re : rand_t;
|
||||
var ct_real, ct_chal;
|
||||
var ct_real : ct_t;
|
||||
var ss_chal : ss_t;
|
||||
var guess;
|
||||
var guess : bool;
|
||||
rk <$ duniform [witness];
|
||||
(pk, sk) <- keygen MLKem768 rk;
|
||||
re <$ duniform [witness];
|
||||
@@ -124,11 +125,17 @@ op delta_decrypt : ps_id_t -> real. (* Per-key delta from FIPS 203 7.3 *)
|
||||
op msg_bits : int. (* |m| = 256 for ML-KEM *)
|
||||
op j_bits : int. (* |J output| = 256 for ML-KEM *)
|
||||
|
||||
(* The query bounds are non-negative counts. This is a definitional fact
|
||||
about the model's parameters (a number of oracle queries is never
|
||||
negative); it is what makes the linear advantage bound below
|
||||
monotone in the per-query advantages. *)
|
||||
axiom q_bounds_ge0 : 0 <= q_D_bound /\ 0 <= q_G_bound /\ 0 <= q_H_bound.
|
||||
|
||||
axiom delta_bound_mlkem768 :
|
||||
delta_decrypt MLKem768 <= 2%r ^^ (-164).
|
||||
delta_decrypt MLKem768 <= 2%r ^ (-164).
|
||||
|
||||
axiom delta_bound_mlkem1024 :
|
||||
delta_decrypt MLKem1024 <= 2%r ^^ (-174).
|
||||
delta_decrypt MLKem1024 <= 2%r ^ (-174).
|
||||
|
||||
(* The IND-CPA PKE adversary advantage (Module-LWE / Module-LWR hardness). *)
|
||||
op adv_indcpa_pke : ps_id_t -> real.
|
||||
@@ -137,8 +144,8 @@ op adv_indcpa_pke : ps_id_t -> real.
|
||||
op adv_indcca2_kem (p : ps_id_t) (A : real) : real =
|
||||
q_G_bound%r * delta_decrypt p +
|
||||
2%r * q_G_bound%r * adv_indcpa_pke p +
|
||||
q_H_bound%r / 2%r ^^ msg_bits +
|
||||
q_D_bound%r / 2%r ^^ j_bits.
|
||||
q_H_bound%r / 2%r ^ msg_bits +
|
||||
q_D_bound%r / 2%r ^ j_bits.
|
||||
|
||||
(* The FO-K reduction theorem statement. *)
|
||||
axiom fo_k_reduction (p : ps_id_t) (Aadv : real) :
|
||||
@@ -165,6 +172,17 @@ op adv_mlwr : ps_id_t -> real.
|
||||
axiom indcpa_pke_reduction (p : ps_id_t) :
|
||||
adv_indcpa_pke p <= adv_mlwe p + adv_mlwr p.
|
||||
|
||||
(* Advantages and per-key decryption-failure probabilities are
|
||||
non-negative reals. This is a definitional fact about the model's
|
||||
quantities (an advantage / probability is never negative); it is what
|
||||
makes the linear bound monotone when the query counts are replaced by
|
||||
their upper bounds in the concrete instantiation below. *)
|
||||
axiom advantages_ge0 (p : ps_id_t) :
|
||||
0%r <= delta_decrypt p /\
|
||||
0%r <= adv_indcpa_pke p /\
|
||||
0%r <= adv_mlwe p /\
|
||||
0%r <= adv_mlwr p.
|
||||
|
||||
(* -------------------------------------------------------------------- *)
|
||||
(* Composed bound *)
|
||||
(* -------------------------------------------------------------------- *)
|
||||
@@ -175,11 +193,12 @@ lemma mlkem_indcca2_security (p : ps_id_t) (Aadv : real) :
|
||||
Aadv <=
|
||||
q_G_bound%r * delta_decrypt p +
|
||||
2%r * q_G_bound%r * (adv_mlwe p + adv_mlwr p) +
|
||||
q_H_bound%r / 2%r ^^ msg_bits +
|
||||
q_D_bound%r / 2%r ^^ j_bits.
|
||||
q_H_bound%r / 2%r ^ msg_bits +
|
||||
q_D_bound%r / 2%r ^ j_bits.
|
||||
proof.
|
||||
have H1 := fo_k_reduction p Aadv.
|
||||
have H2 := indcpa_pke_reduction p.
|
||||
have Hq := q_bounds_ge0.
|
||||
rewrite /adv_indcca2_kem in H1.
|
||||
smt().
|
||||
qed.
|
||||
@@ -205,18 +224,22 @@ axiom mlkem768_concrete_bound :
|
||||
q_G_bound <= 2 ^ 64 /\
|
||||
q_H_bound <= 2 ^ 64 /\
|
||||
q_D_bound <= 2 ^ 64 /\
|
||||
adv_mlwe MLKem768 <= 2%r ^^ (-192) /\
|
||||
adv_mlwr MLKem768 <= 2%r ^^ (-192).
|
||||
adv_mlwe MLKem768 <= 2%r ^ (-192) /\
|
||||
adv_mlwr MLKem768 <= 2%r ^ (-192).
|
||||
|
||||
lemma mlkem768_concrete_security (Aadv : real) :
|
||||
Aadv <=
|
||||
(2^64)%r * delta_decrypt MLKem768 +
|
||||
2%r * (2^64)%r * (adv_mlwe MLKem768 + adv_mlwr MLKem768) +
|
||||
(2^64)%r / 2%r ^^ msg_bits +
|
||||
(2^64)%r / 2%r ^^ j_bits.
|
||||
(2^64)%r / 2%r ^ msg_bits +
|
||||
(2^64)%r / 2%r ^ j_bits.
|
||||
proof.
|
||||
have H := mlkem_indcca2_security MLKem768 Aadv.
|
||||
have HC := mlkem768_concrete_bound.
|
||||
have Hq := q_bounds_ge0.
|
||||
have Ha := advantages_ge0 MLKem768.
|
||||
have Hp1 : 0%r < 2%r ^ msg_bits by apply expr_gt0.
|
||||
have Hp2 : 0%r < 2%r ^ j_bits by apply expr_gt0.
|
||||
smt().
|
||||
qed.
|
||||
|
||||
|
||||
@@ -138,14 +138,14 @@ lemma mlkem_wire_format_keygen (p : ps_id_t) (r : rand_t) :
|
||||
let (pk, sk) = keygen p r in
|
||||
(pk_bytes pk, sk_bytes sk) = fips203_keygen p (rand_to_bytes r).
|
||||
proof.
|
||||
by apply circl_fips203_compliant_keygen.
|
||||
by apply (circl_fips203_compliant_keygen p r).
|
||||
qed.
|
||||
|
||||
lemma mlkem_wire_format_encaps (p : ps_id_t) (r : rand_t) (pk : pk_t) :
|
||||
let (ct, ss) = encaps p r pk in
|
||||
(ct_bytes ct, ss_bytes ss) = fips203_encaps p (rand_to_bytes r) (pk_bytes pk).
|
||||
proof.
|
||||
by apply circl_fips203_compliant_encaps.
|
||||
by apply (circl_fips203_compliant_encaps p r pk).
|
||||
qed.
|
||||
|
||||
lemma mlkem_wire_format_decaps (p : ps_id_t) (sk : sk_t) (ct : ct_t) :
|
||||
@@ -170,7 +170,8 @@ proof.
|
||||
move => HtapeEq.
|
||||
have H1 := circl_fips203_compliant_keygen p r1.
|
||||
have H2 := circl_fips203_compliant_keygen p r2.
|
||||
rewrite /= in H1 H2.
|
||||
rewrite /= in H1.
|
||||
rewrite /= in H2.
|
||||
smt().
|
||||
qed.
|
||||
|
||||
@@ -183,7 +184,8 @@ proof.
|
||||
move => HtapeEq.
|
||||
have H1 := circl_fips203_compliant_encaps p r1 pk.
|
||||
have H2 := circl_fips203_compliant_encaps p r2 pk.
|
||||
rewrite /= in H1 H2.
|
||||
rewrite /= in H1.
|
||||
rewrite /= in H2.
|
||||
smt().
|
||||
qed.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user