ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
mlkem.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#include "crypto/pqc/mlkem.h"
5
6#if PC_ENABLE_PQC_KEX
7
8#include "crypto/hash/sha3.h"
9#include <string.h>
10
11// ML-KEM-768 parameters (FIPS 203).
12#define MK_N 256
13#define MK_Q 3329
14#define MK_K 3
15#define MK_ETA 2 // eta1 == eta2 == 2 for ML-KEM-768
16#define MK_DU 10
17#define MK_DV 4
18#define MK_POLYBYTES 384 // 12 bits * 256 / 8
19#define MK_QINV (-3327) // q^-1 mod 2^16, signed
20
21// Twiddle factors zeta^BitRev7(i) in Montgomery form, reduced to (-q/2, q/2] (FIPS 203 / pq-crystals).
22static const int16_t mk_zetas[128] = {
23 -1044, -758, -359, -1517, 1493, 1422, 287, 202, -171, 622, 1577, 182, 962, -1202, -1474, 1468,
24 573, -1325, 264, 383, -829, 1458, -1602, -130, -681, 1017, 732, 608, -1542, 411, -205, -1571,
25 1223, 652, -552, 1015, -1293, 1491, -282, -1544, 516, -8, -320, -666, -1618, -1162, 126, 1469,
26 -853, -90, -271, 830, 107, -1421, -247, -951, -398, 961, -1508, -725, 448, -1065, 677, -1275,
27 -1103, 430, 555, 843, -1251, 871, 1550, 105, 422, 587, 177, -235, -291, -460, 1574, 1653,
28 -246, 778, 1159, -147, -777, 1483, -602, 1119, -1590, 644, -872, 349, 418, 329, -156, -75,
29 817, 1097, 603, 610, 1322, -1285, -1465, 384, -1215, -136, 1218, -1335, -874, 220, -1187, -1659,
30 -1185, -1530, -1278, 794, -1510, -854, -870, 478, -108, -308, 996, 991, 958, -1460, 1522, 1628};
31
32// Montgomery reduction: given a = m*R, return m mod q in (-q, q). R = 2^16.
33static int16_t mont_reduce(int32_t a)
34{
35 int16_t t = (int16_t)((int16_t)a * (int16_t)MK_QINV);
36 t = (int16_t)((a - (int32_t)t * MK_Q) >> 16);
37 return t;
38}
39
40static inline int16_t fqmul(int16_t a, int16_t b)
41{
42 return mont_reduce((int32_t)a * b);
43}
44
45// Barrett reduction: a mod q in (-q/2, q/2].
46static int16_t barrett_reduce(int16_t a)
47{
48 const int16_t v = 20159; // round(2^26 / q)
49 int16_t t = (int16_t)(((int32_t)v * a + (1 << 25)) >> 26);
50 t = (int16_t)(t * MK_Q);
51 return (int16_t)(a - t);
52}
53
54// Forward NTT (in place). Coefficients enter in normal order, leave in bit-reversed NTT order.
55static void ntt(int16_t r[MK_N])
56{
57 unsigned k = 1;
58 for (unsigned len = 128; len >= 2; len >>= 1)
59 {
60 for (unsigned start = 0; start < MK_N; start += (len << 1))
61 {
62 int16_t zeta = mk_zetas[k++];
63 for (unsigned j = start; j < start + len; j++)
64 {
65 int16_t t = fqmul(zeta, r[j + len]);
66 r[j + len] = (int16_t)(r[j] - t);
67 r[j] = (int16_t)(r[j] + t);
68 }
69 }
70 }
71}
72
73// Inverse NTT (in place), with the 1/128 * Montgomery scaling folded into the final multiply.
74static void invntt(int16_t r[MK_N])
75{
76 const int16_t f = 1441; // mont^2 / 128
77 unsigned k = 127;
78 for (unsigned len = 2; len <= 128; len <<= 1)
79 {
80 for (unsigned start = 0; start < MK_N; start += (len << 1))
81 {
82 int16_t zeta = mk_zetas[k--];
83 for (unsigned j = start; j < start + len; j++)
84 {
85 int16_t t = r[j];
86 r[j] = barrett_reduce((int16_t)(t + r[j + len]));
87 r[j + len] = (int16_t)(r[j + len] - t);
88 r[j + len] = fqmul(zeta, r[j + len]);
89 }
90 }
91 }
92 for (unsigned j = 0; j < MK_N; j++)
93 {
94 r[j] = fqmul(r[j], f);
95 }
96}
97
98// Multiply two degree-1 residues mod (X^2 - zeta) in the NTT domain.
99static void basemul(int16_t r[2], const int16_t a[2], const int16_t b[2], int16_t zeta)
100{
101 r[0] = fqmul(a[1], b[1]);
102 r[0] = fqmul(r[0], zeta);
103 r[0] = (int16_t)(r[0] + fqmul(a[0], b[0]));
104 r[1] = fqmul(a[0], b[1]);
105 r[1] = (int16_t)(r[1] + fqmul(a[1], b[0]));
106}
107
108static void poly_basemul(int16_t r[MK_N], const int16_t a[MK_N], const int16_t b[MK_N])
109{
110 for (unsigned i = 0; i < MK_N / 4; i++)
111 {
112 basemul(&r[4 * i], &a[4 * i], &b[4 * i], mk_zetas[64 + i]);
113 basemul(&r[4 * i + 2], &a[4 * i + 2], &b[4 * i + 2], (int16_t)(-mk_zetas[64 + i]));
114 }
115}
116
117// r = sum_i a[i] o b[i] (pointwise in the NTT domain), then Barrett-reduced.
118static void polyvec_basemul_acc(int16_t r[MK_N], const int16_t a[MK_K][MK_N], const int16_t b[MK_K][MK_N])
119{
120 int16_t t[MK_N];
121 poly_basemul(r, a[0], b[0]);
122 for (unsigned i = 1; i < MK_K; i++)
123 {
124 poly_basemul(t, a[i], b[i]);
125 for (unsigned j = 0; j < MK_N; j++)
126 {
127 r[j] = (int16_t)(r[j] + t[j]);
128 }
129 }
130 for (unsigned j = 0; j < MK_N; j++)
131 {
132 r[j] = barrett_reduce(r[j]);
133 }
134}
135
136// ByteDecode_12: 384 octets -> 256 coefficients in [0, 2^12).
137static void poly_frombytes(int16_t r[MK_N], const uint8_t a[MK_POLYBYTES])
138{
139 for (unsigned i = 0; i < MK_N / 2; i++)
140 {
141 r[2 * i] = (int16_t)(((a[3 * i + 0] >> 0) | ((uint16_t)a[3 * i + 1] << 8)) & 0xFFF);
142 r[2 * i + 1] = (int16_t)(((a[3 * i + 1] >> 4) | ((uint16_t)a[3 * i + 2] << 4)) & 0xFFF);
143 }
144}
145
146// Decompress_1: each message bit b -> b ? (q+1)/2 : 0.
147static void poly_frommsg(int16_t r[MK_N], const uint8_t msg[32])
148{
149 for (unsigned i = 0; i < 32; i++)
150 {
151 for (unsigned j = 0; j < 8; j++)
152 {
153 int16_t mask = (int16_t)(-(int16_t)((msg[i] >> j) & 1));
154 r[8 * i + j] = (int16_t)(mask & ((MK_Q + 1) / 2));
155 }
156 }
157}
158
159static inline uint32_t load32_le(const uint8_t *x)
160{
161 return (uint32_t)x[0] | ((uint32_t)x[1] << 8) | ((uint32_t)x[2] << 16) | ((uint32_t)x[3] << 24);
162}
163
164// Centered binomial distribution, eta = 2: 128 octets of PRF output -> 256 coefficients in [-2, 2].
165static void cbd2(int16_t r[MK_N], const uint8_t buf[128])
166{
167 for (unsigned i = 0; i < MK_N / 8; i++)
168 {
169 uint32_t t = load32_le(buf + 4 * i);
170 uint32_t d = t & 0x55555555u;
171 d += (t >> 1) & 0x55555555u;
172 for (unsigned j = 0; j < 8; j++)
173 {
174 int16_t a = (int16_t)((d >> (4 * j)) & 0x3);
175 int16_t b = (int16_t)((d >> (4 * j + 2)) & 0x3);
176 r[8 * i + j] = (int16_t)(a - b);
177 }
178 }
179}
180
181// PRF_eta(seed, nonce) = SHAKE256(seed || nonce), then sample CBD_eta (eta = 2).
182static void poly_getnoise(int16_t r[MK_N], const uint8_t seed[32], uint8_t nonce)
183{
184 uint8_t extseed[33];
185 memcpy(extseed, seed, 32);
186 extseed[32] = nonce;
187 uint8_t buf[MK_ETA * MK_N / 4]; // 128
188 shake256(buf, sizeof(buf), extseed, sizeof(extseed));
189 cbd2(r, buf);
190}
191
192// One transposed matrix entry: A^T[i][j] = SampleNTT(XOF(rho || i || j)) (FIPS 203 gen with (i,j)).
193static void gen_matrix_entry(int16_t out[MK_N], const uint8_t rho[32], uint8_t i, uint8_t j)
194{
195 uint8_t seed[34];
196 memcpy(seed, rho, 32);
197 seed[32] = i;
198 seed[33] = j;
199 KeccakCtx ctx;
200 pc_shake128_absorb(&ctx, seed, sizeof(seed));
201
202 unsigned count = 0;
203 while (count < MK_N)
204 {
205 uint8_t buf[KECCAK_RATE_SHAKE128]; // 168 = 56*3, no 3-octet group straddles a block
206 pc_keccak_squeeze(&ctx, buf, sizeof(buf));
207 for (unsigned p = 0; p + 3 <= sizeof(buf) && count < MK_N; p += 3)
208 {
209 uint16_t d1 = (uint16_t)(buf[p] | ((uint16_t)(buf[p + 1] & 0xF) << 8));
210 uint16_t d2 = (uint16_t)((buf[p + 1] >> 4) | ((uint16_t)buf[p + 2] << 4));
211 if (d1 < MK_Q)
212 {
213 out[count++] = (int16_t)d1;
214 }
215 if (count < MK_N && d2 < MK_Q)
216 {
217 out[count++] = (int16_t)d2;
218 }
219 }
220 }
221}
222
223// Compress_10 + ByteEncode_10 for one polynomial: 256 coefficients -> 320 octets.
224static void poly_compress10(uint8_t r[320], const int16_t a[MK_N])
225{
226 unsigned k = 0;
227 for (unsigned i = 0; i < MK_N / 4; i++)
228 {
229 uint16_t t[4];
230 for (unsigned j = 0; j < 4; j++)
231 {
232 int16_t u = a[4 * i + j];
233 u = (int16_t)(u + ((u >> 15) & MK_Q)); // to [0, q)
234 t[j] = (uint16_t)(((((uint32_t)u << 10) + MK_Q / 2) / MK_Q) & 0x3FF);
235 }
236 r[k + 0] = (uint8_t)(t[0]);
237 r[k + 1] = (uint8_t)((t[0] >> 8) | (t[1] << 2));
238 r[k + 2] = (uint8_t)((t[1] >> 6) | (t[2] << 4));
239 r[k + 3] = (uint8_t)((t[2] >> 4) | (t[3] << 6));
240 r[k + 4] = (uint8_t)(t[3] >> 2);
241 k += 5;
242 }
243}
244
245// Compress_4 + ByteEncode_4 for one polynomial: 256 coefficients -> 128 octets.
246static void poly_compress4(uint8_t r[128], const int16_t a[MK_N])
247{
248 for (unsigned i = 0; i < MK_N / 8; i++)
249 {
250 uint8_t t[8];
251 for (unsigned j = 0; j < 8; j++)
252 {
253 int16_t u = a[8 * i + j];
254 u = (int16_t)(u + ((u >> 15) & MK_Q));
255 t[j] = (uint8_t)(((((uint16_t)u << 4) + MK_Q / 2) / MK_Q) & 15);
256 }
257 r[4 * i + 0] = (uint8_t)(t[0] | (t[1] << 4));
258 r[4 * i + 1] = (uint8_t)(t[2] | (t[3] << 4));
259 r[4 * i + 2] = (uint8_t)(t[4] | (t[5] << 4));
260 r[4 * i + 3] = (uint8_t)(t[6] | (t[7] << 4));
261 }
262}
263
264// FIPS 203 modulus check: ek's decoded coefficients must all be < q.
265static bool check_ek(const uint8_t ek[MLKEM768_EK_BYTES])
266{
267 for (unsigned i = 0; i < MK_K; i++)
268 {
269 int16_t p[MK_N];
270 poly_frombytes(p, ek + i * MK_POLYBYTES);
271 for (unsigned j = 0; j < MK_N; j++)
272 {
273 if ((uint16_t)p[j] >= MK_Q)
274 {
275 return false;
276 }
277 }
278 }
279 return true;
280}
281
282// K-PKE.Encrypt(ek, m, r) -> ct. u is streamed and compressed one row at a time to bound stack.
283static void k_pke_encrypt(uint8_t ct[MLKEM768_CT_BYTES], const uint8_t ek[MLKEM768_EK_BYTES], const uint8_t m[32],
284 const uint8_t coins[32])
285{
286 int16_t that[MK_K][MK_N];
287 for (unsigned i = 0; i < MK_K; i++)
288 {
289 poly_frombytes(that[i], ek + i * MK_POLYBYTES);
290 }
291 const uint8_t *rho = ek + MK_K * MK_POLYBYTES;
292
293 int16_t sp[MK_K][MK_N];
294 for (unsigned i = 0; i < MK_K; i++)
295 {
296 poly_getnoise(sp[i], coins, (uint8_t)i); // y, nonce 0..k-1
297 ntt(sp[i]);
298 }
299
300 // u = NTT^-1(A^T o y) + e1, compressed with du = 10 (320 octets/row).
301 for (unsigned i = 0; i < MK_K; i++)
302 {
303 int16_t at_row[MK_K][MK_N];
304 for (unsigned j = 0; j < MK_K; j++)
305 {
306 gen_matrix_entry(at_row[j], rho, (uint8_t)i, (uint8_t)j);
307 }
308 int16_t u_row[MK_N];
309 polyvec_basemul_acc(u_row, at_row, sp);
310 invntt(u_row);
311 int16_t e1[MK_N];
312 poly_getnoise(e1, coins, (uint8_t)(MK_K + i)); // e1, nonce k..2k-1
313 for (unsigned x = 0; x < MK_N; x++)
314 {
315 u_row[x] = barrett_reduce((int16_t)(u_row[x] + e1[x]));
316 }
317 poly_compress10(ct + i * 320, u_row);
318 }
319
320 // v = NTT^-1(t^T o y) + e2 + Decompress_1(m), compressed with dv = 4 (128 octets).
321 int16_t v[MK_N];
322 polyvec_basemul_acc(v, that, sp);
323 invntt(v);
324 int16_t e2[MK_N];
325 poly_getnoise(e2, coins, (uint8_t)(2 * MK_K)); // e2, nonce 2k
326 int16_t mu[MK_N];
327 poly_frommsg(mu, m);
328 for (unsigned x = 0; x < MK_N; x++)
329 {
330 v[x] = barrett_reduce((int16_t)(v[x] + e2[x] + mu[x]));
331 }
332 poly_compress4(ct + MK_K * 320, v);
333}
334
335bool pc_mlkem768_encaps(const uint8_t ek[MLKEM768_EK_BYTES], const uint8_t m[MLKEM768_MSG_BYTES],
336 uint8_t ct[MLKEM768_CT_BYTES], uint8_t ss[MLKEM768_SS_BYTES])
337{
338 if (!check_ek(ek))
339 {
340 return false;
341 }
342
343 // (K, r) = G(m || H(ek)); ss = K.
344 uint8_t g_in[64];
345 memcpy(g_in, m, 32);
346 sha3_256(g_in + 32, ek, MLKEM768_EK_BYTES); // H(ek)
347 uint8_t g_out[64];
348 sha3_512(g_out, g_in, sizeof(g_in));
349 memcpy(ss, g_out, 32);
350
351 k_pke_encrypt(ct, ek, m, g_out + 32);
352 return true;
353}
354
355// ── Initiator side: KeyGen + Decaps (the FO transform) ──────────────────────────────────────────
356// These reuse the same NTT / sampling / K-PKE.Encrypt core as Encaps; only the inverse codecs
357// (ByteEncode_12, Decompress+ByteDecode for du/dv, Compress_1) and the two FIPS 203 top-level
358// algorithms are new.
359
360// ByteEncode_12: 256 coefficients -> 384 octets (inverse of poly_frombytes). Coefficients are frozen
361// to [0, q) first (a negative representative maps to +q).
362static void poly_tobytes(uint8_t r[MK_POLYBYTES], const int16_t a[MK_N])
363{
364 for (unsigned i = 0; i < MK_N / 2; i++)
365 {
366 uint16_t t0 = (uint16_t)(a[2 * i] + ((a[2 * i] >> 15) & MK_Q));
367 uint16_t t1 = (uint16_t)(a[2 * i + 1] + ((a[2 * i + 1] >> 15) & MK_Q));
368 r[3 * i + 0] = (uint8_t)(t0);
369 r[3 * i + 1] = (uint8_t)((t0 >> 8) | (t1 << 4));
370 r[3 * i + 2] = (uint8_t)(t1 >> 4);
371 }
372}
373
374// ByteDecode_10 + Decompress_10: 320 octets -> 256 coefficients (inverse of poly_compress10).
375static void poly_decompress10(int16_t r[MK_N], const uint8_t a[320])
376{
377 unsigned k = 0;
378 for (unsigned i = 0; i < MK_N / 4; i++)
379 {
380 uint16_t t[4];
381 t[0] = (uint16_t)((a[k + 0] | ((uint16_t)a[k + 1] << 8)) & 0x3FF);
382 t[1] = (uint16_t)(((a[k + 1] >> 2) | ((uint16_t)a[k + 2] << 6)) & 0x3FF);
383 t[2] = (uint16_t)(((a[k + 2] >> 4) | ((uint16_t)a[k + 3] << 4)) & 0x3FF);
384 t[3] = (uint16_t)(((a[k + 3] >> 6) | ((uint16_t)a[k + 4] << 2)) & 0x3FF);
385 k += 5;
386 for (unsigned j = 0; j < 4; j++)
387 {
388 r[4 * i + j] = (int16_t)(((uint32_t)t[j] * MK_Q + 512) >> 10);
389 }
390 }
391}
392
393// ByteDecode_4 + Decompress_4: 128 octets -> 256 coefficients (inverse of poly_compress4).
394static void poly_decompress4(int16_t r[MK_N], const uint8_t a[128])
395{
396 for (unsigned i = 0; i < MK_N / 2; i++)
397 {
398 uint8_t byte = a[i];
399 r[2 * i + 0] = (int16_t)(((uint32_t)(byte & 0x0F) * MK_Q + 8) >> 4);
400 r[2 * i + 1] = (int16_t)(((uint32_t)(byte >> 4) * MK_Q + 8) >> 4);
401 }
402}
403
404// Compress_1 + ByteEncode_1: 256 coefficients -> 32 octets (inverse of poly_frommsg). Each bit is
405// round(2*coeff/q) mod 2 - whether the coefficient sits closer to q/2 than to 0.
406static void poly_tomsg(uint8_t msg[32], const int16_t a[MK_N])
407{
408 for (unsigned i = 0; i < 32; i++)
409 {
410 msg[i] = 0;
411 for (unsigned j = 0; j < 8; j++)
412 {
413 int16_t t = a[8 * i + j];
414 t = (int16_t)(t + ((t >> 15) & MK_Q)); // to [0, q)
415 uint16_t bit = (uint16_t)(((((uint32_t)t << 1) + MK_Q / 2) / MK_Q) & 1);
416 msg[i] |= (uint8_t)(bit << j);
417 }
418 }
419}
420
421// Convert a polynomial into Montgomery form (multiply each coefficient by R = 2^16 mod q via one
422// Montgomery reduction of coeff * (2^32 mod q)). polyvec_basemul_acc leaves an extra R^-1 factor; on
423// the Encaps/Decrypt paths a following invntt absorbs it, but KeyGen encodes t in the NTT domain
424// directly, so it must undo that factor here.
425static void poly_tomont(int16_t r[MK_N])
426{
427 const int16_t f = 1353; // 2^32 mod q
428 for (unsigned i = 0; i < MK_N; i++)
429 {
430 r[i] = mont_reduce((int32_t)r[i] * f);
431 }
432}
433
434// Constant-time inequality mask over n octets: 0x00 if a == b, 0xFF if they differ. No data-dependent
435// branch or early exit - the FO transform must not leak whether the re-encryption matched.
436static uint8_t ct_diff_mask(const uint8_t *a, const uint8_t *b, size_t n)
437{
438 uint32_t acc = 0;
439 for (size_t i = 0; i < n; i++)
440 {
441 acc |= (uint32_t)((uint8_t)(a[i] ^ b[i]));
442 }
443 uint8_t equal = (uint8_t)(((acc - 1) >> 31) & 1); // 1 iff acc == 0 (all octets equal)
444 return (uint8_t)(equal - 1); // 0x00 if equal, 0xFF if any differ
445}
446
447// K-PKE.KeyGen(d) -> ek_PKE (ByteEncode_12(t) || rho) and dk_PKE (ByteEncode_12(s)), both in the NTT
448// domain. t[i] = sum_j A[i][j] o s[j]. K-PKE.Encrypt multiplies by the SAME matrix transposed - its
449// u[i] = sum_j XOF(rho, i, j) o y[j] - so for the KEM to invert, KeyGen's A[i][j] = XOF(rho, j, i).
450static void k_pke_keygen(uint8_t ek[MLKEM768_EK_BYTES], uint8_t dk_pke[MK_K * MK_POLYBYTES], const uint8_t d[32])
451{
452 // (rho, sigma) = G(d || k). The trailing k byte is the FIPS 203 domain separation on module rank.
453 uint8_t g_in[33];
454 memcpy(g_in, d, 32);
455 g_in[32] = MK_K;
456 uint8_t g_out[64];
457 sha3_512(g_out, g_in, sizeof(g_in));
458 const uint8_t *rho = g_out;
459 const uint8_t *sigma = g_out + 32;
460
461 int16_t s[MK_K][MK_N];
462 int16_t e[MK_K][MK_N];
463 uint8_t nonce = 0;
464 for (unsigned i = 0; i < MK_K; i++)
465 {
466 poly_getnoise(s[i], sigma, nonce++); // s, nonce 0..k-1
467 }
468 for (unsigned i = 0; i < MK_K; i++)
469 {
470 poly_getnoise(e[i], sigma, nonce++); // e, nonce k..2k-1
471 }
472 for (unsigned i = 0; i < MK_K; i++)
473 {
474 ntt(s[i]);
475 ntt(e[i]);
476 for (unsigned x = 0; x < MK_N; x++) // ntt() leaves coeffs unreduced; canonicalize both
477 {
478 s[i][x] = barrett_reduce(s[i][x]);
479 e[i][x] = barrett_reduce(e[i][x]);
480 }
481 }
482
483 for (unsigned i = 0; i < MK_K; i++)
484 {
485 int16_t a_row[MK_K][MK_N];
486 for (unsigned j = 0; j < MK_K; j++)
487 {
488 gen_matrix_entry(a_row[j], rho, (uint8_t)j, (uint8_t)i); // A[i][j] = XOF(rho, j, i)
489 }
490 int16_t t_row[MK_N];
491 polyvec_basemul_acc(t_row, a_row, s);
492 poly_tomont(t_row); // undo the R^-1 from basemul (no invntt here to absorb it)
493 for (unsigned x = 0; x < MK_N; x++)
494 {
495 t_row[x] = barrett_reduce((int16_t)(t_row[x] + e[i][x]));
496 }
497 poly_tobytes(ek + i * MK_POLYBYTES, t_row);
498 }
499 memcpy(ek + MK_K * MK_POLYBYTES, rho, 32);
500
501 for (unsigned i = 0; i < MK_K; i++)
502 {
503 poly_tobytes(dk_pke + i * MK_POLYBYTES, s[i]);
504 }
505}
506
507// K-PKE.Decrypt(dk_PKE, ct) -> m: w = v - NTT^-1(s^T o NTT(u)), then Compress_1.
508static void k_pke_decrypt(uint8_t m[32], const uint8_t dk_pke[MK_K * MK_POLYBYTES], const uint8_t ct[MLKEM768_CT_BYTES])
509{
510 int16_t u[MK_K][MK_N];
511 for (unsigned i = 0; i < MK_K; i++)
512 {
513 poly_decompress10(u[i], ct + i * 320);
514 ntt(u[i]);
515 }
516 int16_t shat[MK_K][MK_N];
517 for (unsigned i = 0; i < MK_K; i++)
518 {
519 poly_frombytes(shat[i], dk_pke + i * MK_POLYBYTES);
520 }
521
522 int16_t w[MK_N];
523 polyvec_basemul_acc(w, shat, u); // s^T o u (dot product in the NTT domain)
524 invntt(w);
525
526 int16_t v[MK_N];
527 poly_decompress4(v, ct + MK_K * 320);
528 for (unsigned x = 0; x < MK_N; x++)
529 {
530 w[x] = barrett_reduce((int16_t)(v[x] - w[x]));
531 }
532 poly_tomsg(m, w);
533}
534
535void pc_mlkem768_keygen(const uint8_t d[MLKEM768_D_BYTES], const uint8_t z[MLKEM768_Z_BYTES],
536 uint8_t ek[MLKEM768_EK_BYTES], uint8_t dk[MLKEM768_DK_BYTES])
537{
538 // dk = dk_PKE || ek || H(ek) || z (FIPS 203 Algorithm 16).
539 k_pke_keygen(ek, dk, d);
540 memcpy(dk + MK_K * MK_POLYBYTES, ek, MLKEM768_EK_BYTES);
541 sha3_256(dk + MK_K * MK_POLYBYTES + MLKEM768_EK_BYTES, ek, MLKEM768_EK_BYTES);
542 memcpy(dk + MK_K * MK_POLYBYTES + MLKEM768_EK_BYTES + 32, z, 32);
543}
544
545void pc_mlkem768_decaps(const uint8_t dk[MLKEM768_DK_BYTES], const uint8_t ct[MLKEM768_CT_BYTES],
546 uint8_t ss[MLKEM768_SS_BYTES])
547{
548 const uint8_t *dk_pke = dk;
549 const uint8_t *ek_pke = dk + MK_K * MK_POLYBYTES;
550 const uint8_t *h = ek_pke + MLKEM768_EK_BYTES; // H(ek), 32 octets
551 const uint8_t *z = h + 32; // implicit-reject seed, 32 octets
552
553 // m' = K-PKE.Decrypt(dk_PKE, ct); (K', r') = G(m' || H(ek)).
554 uint8_t mprime[32];
555 k_pke_decrypt(mprime, dk_pke, ct);
556 uint8_t g_in[64];
557 memcpy(g_in, mprime, 32);
558 memcpy(g_in + 32, h, 32);
559 uint8_t g_out[64];
560 sha3_512(g_out, g_in, sizeof(g_in));
561
562 // Implicit-reject key K_bar = J(z || ct) = SHAKE256(z || ct, 32).
563 uint8_t jbuf[32 + MLKEM768_CT_BYTES];
564 memcpy(jbuf, z, 32);
565 memcpy(jbuf + 32, ct, MLKEM768_CT_BYTES);
566 uint8_t kbar[32];
567 shake256(kbar, sizeof(kbar), jbuf, sizeof(jbuf));
568
569 // Re-encrypt under the embedded ek with the derived coins r'; select in constant time.
570 uint8_t ctprime[MLKEM768_CT_BYTES];
571 k_pke_encrypt(ctprime, ek_pke, mprime, g_out + 32);
572 uint8_t diff = ct_diff_mask(ct, ctprime, MLKEM768_CT_BYTES);
573 for (unsigned i = 0; i < 32; i++)
574 {
575 ss[i] = (uint8_t)((g_out[i] & (uint8_t)~diff) | (kbar[i] & diff)); // K' if match, K_bar if not
576 }
577}
578
579#endif // PC_ENABLE_PQC_KEX
ML-KEM-768 (FIPS 203): Encaps (responder) + KeyGen and Decaps (initiator).
uint32_t mask(uint8_t width)
Mask of width low bits (width 32 handled without a 32-bit shift, which is UB).
Definition crc.h:61
Keccak-f[1600] sponge: SHA3-256, SHA3-512, SHAKE128, SHAKE256 (FIPS 202).