1use crate::keccak::{sha3_256_bytes, sha3_512_bytes, shake256_bytes};
10use crate::ntt::{
11 mlkem_base_mul_w16, mlkem_byte_decode_w16, mlkem_byte_encode_w16, mlkem_cbd2_w16,
12 mlkem_compress_w16, mlkem_decompress_w16, mlkem_inv_ntt_w16, mlkem_ntt_w16,
13 mlkem_sample_matrix_w16, mlkem_to_mont_w16,
14};
15use logicaffeine_base::Word16;
16
17const K: usize = 3; const N: usize = 256; const Q: u32 = 3329;
20const DU: usize = 10; const DV: usize = 4; const POLY_BYTES: usize = 384; pub const EK_BYTES: usize = POLY_BYTES * K + 32;
26pub const DK_BYTES: usize = POLY_BYTES * K + EK_BYTES + 32 + 32;
28pub const CT_BYTES: usize = K * 32 * DU + 32 * DV;
30pub const SS_BYTES: usize = 32;
32
33fn g(input: &[u8]) -> ([u8; 32], [u8; 32]) {
35 let h = sha3_512_bytes(input);
36 let mut a = [0u8; 32];
37 let mut b = [0u8; 32];
38 a.copy_from_slice(&h[..32]);
39 b.copy_from_slice(&h[32..]);
40 (a, b)
41}
42
43fn noise_raw(seed: &[u8; 32], nonce: u8) -> Vec<Word16> {
45 let mut pin = seed.to_vec();
46 pin.push(nonce);
47 mlkem_cbd2_w16(&shake256_bytes(&pin, 128))
48}
49
50fn noise_batch_raw(seed: &[u8; 32], base: u8, count: usize) -> Vec<Vec<Word16>> {
57 let mut raw: Vec<Vec<Word16>> = Vec::with_capacity(count);
58 #[cfg(target_arch = "x86_64")]
59 {
60 if std::is_x86_feature_detected!("avx2") {
61 let mut i = 0;
62 while i + 4 <= count {
63 let ins: [[u8; 33]; 4] = std::array::from_fn(|l| {
64 let mut a = [0u8; 33];
65 a[..32].copy_from_slice(seed);
66 a[32] = base + (i + l) as u8;
67 a
68 });
69 let refs: [&[u8]; 4] = [&ins[0], &ins[1], &ins[2], &ins[3]];
70 let outs = unsafe { crate::keccak::shake256_x4_128(&refs) };
71 for o in &outs {
72 raw.push(mlkem_cbd2_w16(o));
73 }
74 i += 4;
75 }
76 for j in i..count {
77 raw.push(noise_raw(seed, base + j as u8));
78 }
79 return raw;
80 }
81 }
82 for j in 0..count {
83 raw.push(noise_raw(seed, base + j as u8));
84 }
85 raw
86}
87
88#[inline]
92fn addq(a: &[Word16], b: &[Word16]) -> Vec<Word16> {
93 let q = Q as u16;
94 (0..N)
95 .map(|c| {
96 let s = a[c].0 + b[c].0; Word16(if s >= q { s - q } else { s })
98 })
99 .collect()
100}
101
102fn kpke_keygen(d: &[u8; 32]) -> (Vec<u8>, Vec<u8>) {
104 let mut gin = d.to_vec();
105 gin.push(K as u8);
106 let (rho, sigma) = g(&gin);
107 let matrix = mlkem_sample_matrix_w16(&rho); let se = noise_batch_raw(&sigma, 0, 2 * K);
110 let s_hat: Vec<Vec<Word16>> = se[0..K].iter().map(|p| mlkem_ntt_w16(p)).collect();
111 let e_hat: Vec<Vec<Word16>> = se[K..2 * K].iter().map(|p| mlkem_ntt_w16(p)).collect();
112
113 let mut ek = Vec::with_capacity(EK_BYTES);
114 for i in 0..K {
115 let mut acc = vec![Word16(0); N];
116 for j in 0..K {
117 let e = (j * K + i) * N; acc = addq(&acc, &mlkem_base_mul_w16(&matrix[e..e + N], &s_hat[j]));
119 }
120 let ti = addq(&mlkem_to_mont_w16(&acc), &e_hat[i]);
121 ek.extend(mlkem_byte_encode_w16(&ti, 12));
122 }
123 ek.extend_from_slice(&rho);
124
125 let mut dk_pke = Vec::with_capacity(POLY_BYTES * K);
126 for poly in &s_hat {
127 dk_pke.extend(mlkem_byte_encode_w16(poly, 12));
128 }
129 (ek, dk_pke)
130}
131
132fn kpke_encrypt(ek: &[u8], m: &[u8; 32], r: &[u8; 32]) -> Vec<u8> {
134 let t_hat: Vec<Vec<Word16>> =
135 (0..K).map(|i| mlkem_byte_decode_w16(&ek[POLY_BYTES * i..POLY_BYTES * (i + 1)], 12)).collect();
136 let rho = &ek[POLY_BYTES * K..POLY_BYTES * K + 32];
137 let matrix = mlkem_sample_matrix_w16(rho); let all = noise_batch_raw(r, 0, 2 * K + 1);
141 let r_hat: Vec<Vec<Word16>> = all[0..K].iter().map(|p| mlkem_ntt_w16(p)).collect();
142 let e1: Vec<Vec<Word16>> = all[K..2 * K].to_vec();
143 let e2 = all[2 * K].clone();
144
145 let mut c = Vec::with_capacity(CT_BYTES);
146 for i in 0..K {
147 let mut acc = vec![Word16(0); N];
148 for j in 0..K {
149 let e = (i * K + j) * N;
150 acc = addq(&acc, &mlkem_base_mul_w16(&matrix[e..e + N], &r_hat[j]));
151 }
152 let ui = addq(&mlkem_inv_ntt_w16(&acc), &e1[i]);
153 c.extend(mlkem_byte_encode_w16(&mlkem_compress_w16(&ui, DU), DU));
154 }
155 let mu = mlkem_decompress_w16(&mlkem_byte_decode_w16(m, 1), 1);
156 let mut acc = vec![Word16(0); N];
157 for i in 0..K {
158 acc = addq(&acc, &mlkem_base_mul_w16(&t_hat[i], &r_hat[i]));
159 }
160 let v = addq(&addq(&mlkem_inv_ntt_w16(&acc), &e2), &mu);
161 c.extend(mlkem_byte_encode_w16(&mlkem_compress_w16(&v, DV), DV));
162 c
163}
164
165fn kpke_decrypt(dk_pke: &[u8], c: &[u8]) -> [u8; 32] {
167 let s_hat: Vec<Vec<Word16>> = (0..K)
168 .map(|i| mlkem_byte_decode_w16(&dk_pke[POLY_BYTES * i..POLY_BYTES * (i + 1)], 12))
169 .collect();
170 let u: Vec<Vec<Word16>> = (0..K)
171 .map(|i| {
172 let bytes = &c[32 * DU * i..32 * DU * (i + 1)];
173 mlkem_decompress_w16(&mlkem_byte_decode_w16(bytes, DU), DU)
174 })
175 .collect();
176 let v = mlkem_decompress_w16(&mlkem_byte_decode_w16(&c[32 * DU * K..], DV), DV);
177
178 let mut acc = vec![Word16(0); N];
179 for i in 0..K {
180 acc = addq(&acc, &mlkem_base_mul_w16(&s_hat[i], &mlkem_ntt_w16(&u[i])));
181 }
182 let inv = mlkem_inv_ntt_w16(&acc);
183 let q = Q as u16;
185 let w: Vec<Word16> = (0..N)
186 .map(|c| {
187 let s = v[c].0 + q - inv[c].0;
188 Word16(if s >= q { s - q } else { s })
189 })
190 .collect();
191 let mut m = [0u8; 32];
192 m.copy_from_slice(&mlkem_byte_encode_w16(&mlkem_compress_w16(&w, 1), 1));
193 m
194}
195
196pub fn keygen(d: &[u8; 32], z: &[u8; 32]) -> (Vec<u8>, Vec<u8>) {
198 let (ek, dk_pke) = kpke_keygen(d);
199 let mut dk = dk_pke;
200 dk.extend_from_slice(&ek);
201 dk.extend_from_slice(&sha3_256_bytes(&ek));
202 dk.extend_from_slice(z);
203 (ek, dk)
204}
205
206pub fn encaps(ek: &[u8], m: &[u8; 32]) -> (Vec<u8>, [u8; 32]) {
208 let h = sha3_256_bytes(ek);
209 let (kk, r) = g(&[m.as_slice(), &h].concat());
210 (kpke_encrypt(ek, m, &r), kk)
211}
212
213pub fn decaps(dk: &[u8], c: &[u8]) -> [u8; 32] {
215 let dk_pke = &dk[0..POLY_BYTES * K];
216 let ek = &dk[POLY_BYTES * K..POLY_BYTES * K + EK_BYTES];
217 let h = &dk[POLY_BYTES * K + EK_BYTES..POLY_BYTES * K + EK_BYTES + 32];
218 let z = &dk[POLY_BYTES * K + EK_BYTES + 32..POLY_BYTES * K + EK_BYTES + 64];
219
220 let m_prime = kpke_decrypt(dk_pke, c);
221 let (k_prime, r_prime) = g(&[m_prime.as_slice(), h].concat());
222 let mut k_bar = [0u8; 32];
223 k_bar.copy_from_slice(&shake256_bytes(&[z, c].concat(), 32));
224
225 if c == kpke_encrypt(ek, &m_prime, &r_prime).as_slice() {
226 k_prime
227 } else {
228 k_bar
229 }
230}
231
232fn bytes(s: &[i64]) -> Vec<u8> {
235 s.iter().map(|&x| x.rem_euclid(256) as u8).collect()
236}
237fn seq(v: &[u8]) -> logicaffeine_data::LogosSeq<i64> {
238 logicaffeine_data::LogosSeq::from_vec(v.iter().map(|&b| b as i64).collect())
239}
240fn seed32(s: &[i64]) -> [u8; 32] {
241 let mut a = [0u8; 32];
242 for (b, &x) in a.iter_mut().zip(s) {
243 *b = x.rem_euclid(256) as u8;
244 }
245 a
246}
247
248pub fn mlkem_noise_batch_from_int(seed: &[i64], base: i64, count: i64) -> logicaffeine_data::LogosSeq<Word16> {
253 let s = seed32(seed);
254 let n = count.max(0) as usize;
255 let polys = noise_batch_raw(&s, base.max(0) as u8, n);
256 let mut out: Vec<Word16> = Vec::with_capacity(n * N);
257 for p in &polys {
258 out.extend_from_slice(p);
259 }
260 logicaffeine_data::LogosSeq::from_vec(out)
261}
262
263pub fn mlkem_keypair_seq(d: &[i64], z: &[i64]) -> logicaffeine_data::LogosSeq<i64> {
265 let (ek, dk) = keygen(&seed32(d), &seed32(z));
266 let mut out = ek;
267 out.extend(dk);
268 seq(&out)
269}
270pub fn mlkem_encaps_seq(ek: &[i64], m: &[i64]) -> logicaffeine_data::LogosSeq<i64> {
272 let (ct, ss) = encaps(&bytes(ek), &seed32(m));
273 let mut out = ct;
274 out.extend_from_slice(&ss);
275 seq(&out)
276}
277pub fn mlkem_decaps_seq(dk: &[i64], ct: &[i64]) -> logicaffeine_data::LogosSeq<i64> {
279 seq(&decaps(&bytes(dk), &bytes(ct)))
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn keygen_encaps_decaps_round_trips() {
288 let d = [0x11u8; 32];
290 let z = [0x22u8; 32];
291 let m = [0x33u8; 32];
292 let (ek, dk) = keygen(&d, &z);
293 assert_eq!(ek.len(), EK_BYTES, "ek = 1184 bytes");
294 assert_eq!(dk.len(), DK_BYTES, "dk = 2400 bytes");
295
296 let (ct, ss_a) = encaps(&ek, &m);
297 assert_eq!(ct.len(), CT_BYTES, "ct = 1088 bytes");
298 let ss_b = decaps(&dk, &ct);
299 assert_eq!(ss_a, ss_b, "decaps must recover the encaps shared secret");
300
301 let mut bad = ct.clone();
303 bad[0] ^= 1;
304 assert_ne!(decaps(&dk, &bad), ss_a, "tampered ct ⇒ implicit reject");
305 }
306
307 #[test]
308 fn distinct_keypairs_give_distinct_secrets() {
309 let m = [0x55u8; 32];
310 let (ek1, _) = keygen(&[1u8; 32], &[2u8; 32]);
311 let (ek2, _) = keygen(&[3u8; 32], &[4u8; 32]);
312 assert_ne!(ek1, ek2, "distinct seeds ⇒ distinct public keys");
313 assert_ne!(encaps(&ek1, &m).1, encaps(&ek2, &m).1, "distinct keys ⇒ distinct shared secrets");
314 }
315
316 #[test]
319 #[ignore = "profiler — cargo test -p logicaffeine-system profile_mlkem -- --ignored --nocapture"]
320 fn profile_mlkem_keygen_decaps_phases() {
321 use std::hint::black_box;
322 use std::time::Instant;
323 fn t<R, F: FnMut() -> R>(iters: usize, mut f: F) -> f64 {
324 for _ in 0..iters / 5 + 1 {
325 std::hint::black_box(f());
326 }
327 let s = Instant::now();
328 for _ in 0..iters {
329 std::hint::black_box(f());
330 }
331 s.elapsed().as_nanos() as f64 / iters as f64
332 }
333 const IT: usize = 4000;
334 let d = [0x11u8; 32];
335 let z = [0x22u8; 32];
336 let m = [0x33u8; 32];
337 let (ek, dk) = keygen(&d, &z);
338 let (ct, _) = encaps(&ek, &m);
339
340 let mut gin = d.to_vec();
341 gin.push(K as u8);
342 let (rho, sigma) = g(&gin);
343
344 let g_ns = t(IT, || black_box(g(black_box(&gin))));
346 let expand_ns = t(IT, || black_box(mlkem_sample_matrix_w16(black_box(&rho))));
347 let noise_ns = t(IT, || black_box(noise_batch_raw(black_box(&sigma), 0, 2 * K)));
348 let noise_ntt_ns = t(IT, || {
349 let se = noise_batch_raw(&sigma, 0, 2 * K);
350 black_box(se.iter().map(|p| mlkem_ntt_w16(p)).collect::<Vec<_>>())
351 });
352 let one_enc12_ns = t(IT, || {
353 black_box(mlkem_byte_encode_w16(black_box(&vec![Word16(1234); N]), 12))
354 });
355 let keygen_ns = t(IT, || black_box(keygen(black_box(&d), black_box(&z))));
356
357 let dk_pke = &dk[0..POLY_BYTES * K];
359 let decrypt_ns = t(IT, || black_box(kpke_decrypt(black_box(dk_pke), black_box(&ct))));
360 let encrypt_ns = t(IT, || black_box(kpke_encrypt(black_box(&ek), black_box(&m), black_box(&[7u8; 32]))));
361 let decaps_ns = t(IT, || black_box(decaps(black_box(&dk), black_box(&ct))));
362 let encaps_ns = t(IT, || black_box(encaps(black_box(&ek), black_box(&m))));
363
364 println!("\n=== ML-KEM-768 phase profile (ns/op, IT={IT}, native) ===");
365 println!(" G(SHA3-512) {g_ns:>8.0}");
366 println!(" ExpandA (matrix) {expand_ns:>8.0} ← 4-way SHAKE128 rejection");
367 println!(" noise CBD (raw) {noise_ns:>8.0} ← 4-way SHAKE256 (batched)");
368 println!(" noise CBD + NTT {noise_ntt_ns:>8.0}");
369 println!(" ByteEncode12 (×1) {one_enc12_ns:>8.0} (keygen does ×{K}, dk ×{K})");
370 println!(" -------------------------------------------");
371 println!(" KEYGEN full {keygen_ns:>8.0} (libcrux 16658)");
372 println!(" kpke_decrypt {decrypt_ns:>8.0}");
373 println!(" kpke_encrypt {encrypt_ns:>8.0} ← re-encrypt (ExpandA+noise+compress)");
374 println!(" ENCAPS full {encaps_ns:>8.0} (libcrux 28479)");
375 println!(" DECAPS full {decaps_ns:>8.0} (libcrux 21351) = decrypt + re-encrypt");
376
377 let a16: Vec<Word16> = (0..256).map(|i| Word16((i * 7 % Q as usize) as u16)).collect();
379 let b16: Vec<Word16> = (0..256).map(|i| Word16((i * 13 % Q as usize) as u16)).collect();
380 let basemul_ns = t(IT, || mlkem_base_mul_w16(black_box(&a16), black_box(&b16)));
381 #[cfg(target_arch = "x86_64")]
382 {
383 if std::is_x86_feature_detected!("avx2") {
384 use std::arch::x86_64::*;
385 let mut kst = [unsafe { _mm256_setzero_si256() }; 25];
386 let kperm_ns = t(IT * 4, || {
387 unsafe { crate::keccak::keccak_f1600_x4(&mut kst) };
388 kst[0]
389 });
390 println!(" -------------------------------------------");
391 println!(" keccak_f1600_x4 (1 perm) {kperm_ns:>8.2} ← ExpandA ≈ 6 of these (2×4-way) + 3 scalar");
392 println!(" keccak est. in ExpandA {:>8.0} (6 × 4-way perm)", 6.0 * kperm_ns);
393 }
394 }
395 println!(" base_mul (1 poly, scalar) {basemul_ns:>8.2} ← ×9 keygen, ×15 decaps");
396 }
397}