ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
sntrup761.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/**
5 * @file sntrup761.cpp
6 * @brief Streamlined NTRU Prime sntrup761 - full KEM: KeyGen, Encaps, Decaps (see sntrup761.h).
7 *
8 * Ported from OpenSSH's embedded sntrup761 reference (public domain; D. J. Bernstein,
9 * C. Chuengsatiansup, T. Lange, C. van Vredendaal). We target a known set of platforms (the ESP32
10 * variants + native), so the constant-time integer helpers are written directly with our int widths
11 * instead of the reference's portable crypto_int layer. The generic Encode/Decode take a scratch
12 * arena rather than the reference's variable-length recursion arrays, so the stack stays bounded.
13 * SHA-512 and the RNG come from the SSH crypto seams; byte encodings and the hashing (prefix bytes
14 * 1/2/3/4) match OpenSSH exactly, so a ciphertext produced here decapsulates on a real peer and a
15 * public key generated here encapsulates on one - verified byte-exact against the reference both ways.
16 *
17 * Everything NOT called out above tracks upstream line for line on purpose - crypto_sort_int32 is
18 * upstream's own vendored djbsort (supercop crypto_sort/int32/portable4), and R3_recip / Rq_recip3
19 * are unchanged. The way this file gets re-audited when a new sntrup revision lands is to diff it
20 * against upstream, so inherited lines keep upstream's shape even where our style rules differ
21 * (multi-declarator locals, the sort's goto, its nesting depth). sonar-project.properties carries
22 * the matching rule carve-outs, scoped to this file; the project-written wrappers below follow
23 * house style normally.
24 */
25
27
28#if PC_ENABLE_SSH_SNTRUP761
29
30#include "crypto/hash/sha512.h"
31#include <string.h>
32
33// The CSPRNG seam (defined in ssh_dh.cpp) - forward-declared so this PQC primitive does not pull in
34// the SSH key-exchange machinery (ServerConfig / bignum / keymat) that ssh_dh.h transitively includes.
35void ssh_rng_fill(uint8_t *buf, size_t len);
36
37namespace
38{
39
40// --- parameters (sntrup761) ---
41// The spec names these p, q, w. Spelled out here because a bare `#define P`
42// rewrites the token P in every header this TU includes.
43#define PC_SNTRUP_P 761 // spec: p
44#define PC_SNTRUP_Q 4591 // spec: q
45#define PC_SNTRUP_W 286 // spec: w
46#define PC_Q12 ((PC_SNTRUP_Q - 1) / 2) // 2295
47#define PC_HASH_BYTES 32
48#define PC_SMALL_BYTES ((PC_SNTRUP_P + 3) / 4) // 191
49#define PC_CONFIRM_BYTES 32
50#define PC_CT_BYTES PC_SNTRUP761_CT_BYTES // 1039
51#define PC_PK_BYTES PC_SNTRUP761_PK_BYTES // 1158
52
53typedef int8_t small_t;
54typedef int16_t Fq;
55
56// Scratch arena for the Encode/Decode recursion (sum over levels of (len_i+1)/2 = 764; Decode carves
57// 3 uint16 arrays + 1 uint32 array per level, Encode 2 uint16 arrays - both fit these).
58#define PC_SCR16 2304
59#define PC_SCR32 768
60
61small_t F3_freeze(int16_t x)
62{
63 return (small_t)(x - 3 * ((10923 * x + 16384) >> 15));
64}
65
66Fq Fq_freeze(int32_t x)
67{
68 const int32_t q16 = (0x10000 + PC_SNTRUP_Q / 2) / PC_SNTRUP_Q;
69 const int32_t q20 = (0x100000 + PC_SNTRUP_Q / 2) / PC_SNTRUP_Q;
70 const int32_t q28 = (0x10000000 + PC_SNTRUP_Q / 2) / PC_SNTRUP_Q;
71 x -= PC_SNTRUP_Q * ((q16 * x) >> 16);
72 x -= PC_SNTRUP_Q * ((q20 * x) >> 20);
73 return (Fq)(x - PC_SNTRUP_Q * ((q28 * x + 0x8000000) >> 28));
74}
75
76// sign bit of x broadcast to all 32 bits (portable, constant-time).
77inline int32_t negative_mask(int32_t x)
78{
79 return -(int32_t)((uint32_t)x >> 31);
80}
81
82void uint32_divmod_uint14(uint32_t *Qout, uint16_t *rout, uint32_t x, uint16_t m)
83{
84 uint32_t qpart, mask, v = 0x80000000u / m;
85 qpart = (uint32_t)((x * (uint64_t)v) >> 31);
86 x -= qpart * m;
87 *Qout = qpart;
88 qpart = (uint32_t)((x * (uint64_t)v) >> 31);
89 x -= qpart * m;
90 *Qout += qpart;
91 x -= m;
92 *Qout += 1;
93 mask = (uint32_t)negative_mask((int32_t)x);
94 x += mask & (uint32_t)m;
95 *Qout += mask;
96 *rout = (uint16_t)x;
97}
98
99uint16_t uint32_mod_uint14(uint32_t x, uint16_t m)
100{
101 uint32_t Qq;
102 uint16_t r;
103 uint32_divmod_uint14(&Qq, &r, x, m);
104 return r;
105}
106
107// Generic Encode: appends bytes at *out, halving (R,M) each level via a scratch arena.
108uint8_t *Encode(uint8_t *out, const uint16_t *R, const uint16_t *M, int len, uint16_t *scr)
109{
110 if (len == 1)
111 {
112 uint16_t r = R[0], m = M[0];
113 while (m > 1)
114 {
115 *out++ = (uint8_t)r;
116 r >>= 8;
117 m = (uint16_t)((m + 255) >> 8);
118 }
119 return out;
120 }
121 int half = (len + 1) / 2;
122 uint16_t *R2 = scr, *M2 = scr + half;
123 int i;
124 for (i = 0; i + 1 < len; i += 2)
125 {
126 uint32_t m0 = M[i];
127 uint32_t r = (uint32_t)R[i] + (uint32_t)R[i + 1] * m0;
128 uint32_t m = (uint32_t)M[i + 1] * m0;
129 while (m >= 16384)
130 {
131 *out++ = (uint8_t)r;
132 r >>= 8;
133 m = (m + 255) >> 8;
134 }
135 R2[i / 2] = (uint16_t)r;
136 M2[i / 2] = (uint16_t)m;
137 }
138 if (i < len)
139 {
140 R2[i / 2] = R[i];
141 M2[i / 2] = M[i];
142 }
143 return Encode(out, R2, M2, half, scr + 2 * half);
144}
145
146// Generic Decode: fills out[len] from S using moduli M, via a scratch arena.
147void Decode(uint16_t *out, const uint8_t *S, const uint16_t *M, int len, uint16_t *scr, uint32_t *scr32)
148{
149 if (len == 1)
150 {
151 // Decode's only two callers (Rq_decode: M[i] = Q = 4591; Rounded_decode: M[i] = (Q+2)/3 = 1531,
152 // both constant across the array) recurse this generic halving purely on P = 761 and the fixed
153 // modulus - the base-case M[0] is therefore deterministic, not data-dependent, and works out to
154 // 1608 and 3475 respectively for those two configurations: never <= 256. The M[0]==1 and
155 // M[0]<=256 arms exist for other parameter sets this generic routine was written to support
156 // upstream; unreachable from any host input given this file's two fixed call sites.
157 if (M[0] == 1) // GCOVR_EXCL_BR_LINE see above: M[0] is never 1 for either fixed caller
158 {
159 out[0] = 0; // GCOVR_EXCL_LINE
160 }
161 else if (M[0] <= 256) // GCOVR_EXCL_BR_LINE see above: M[0] is never <= 256 for either fixed caller
162 {
163 out[0] = uint32_mod_uint14(S[0], M[0]); // GCOVR_EXCL_LINE
164 }
165 else
166 {
167 out[0] = uint32_mod_uint14((uint32_t)S[0] + ((uint32_t)(uint16_t)S[1] << 8), M[0]);
168 }
169 return;
170 }
171 int half = (len + 1) / 2;
172 uint16_t *R2 = scr, *M2 = scr + half, *bottomr = scr + 2 * half;
173 uint32_t *bottomt = scr32;
174 int i;
175 for (i = 0; i + 1 < len; i += 2)
176 {
177 uint32_t m = (uint32_t)M[i] * (uint32_t)M[i + 1];
178 if (m > 256 * 16383)
179 {
180 bottomt[i / 2] = 256 * 256;
181 bottomr[i / 2] = (uint16_t)(S[0] + 256 * S[1]);
182 S += 2;
183 M2[i / 2] = (uint16_t)((((m + 255) >> 8) + 255) >> 8);
184 }
185 else if (m >= 16384) // GCOVR_EXCL_BR_LINE m = M[i]*M[i+1] is a function of the two fixed
186 // caller moduli (Q or (Q+2)/3) and P = 761 alone, never of decoded data;
187 // for both configurations m never drops below 16384 at any recursion
188 // level, so the else below is unreachable from any host input.
189 {
190 bottomt[i / 2] = 256;
191 bottomr[i / 2] = S[0];
192 S += 1;
193 M2[i / 2] = (uint16_t)((m + 255) >> 8);
194 }
195 else
196 {
197 bottomt[i / 2] = 1; // GCOVR_EXCL_LINE
198 bottomr[i / 2] = 0; // GCOVR_EXCL_LINE
199 M2[i / 2] = (uint16_t)m; // GCOVR_EXCL_LINE
200 }
201 }
202 if (i < len)
203 {
204 M2[i / 2] = M[i];
205 }
206 Decode(R2, S, M2, half, scr + 3 * half, scr32 + half);
207 for (i = 0; i + 1 < len; i += 2)
208 {
209 uint32_t r1, r = bottomr[i / 2];
210 uint16_t r0;
211 r += bottomt[i / 2] * R2[i / 2];
212 uint32_divmod_uint14(&r1, &r0, r, M[i]);
213 r1 = uint32_mod_uint14(r1, M[i + 1]);
214 *out++ = r0;
215 *out++ = (uint16_t)r1;
216 }
217 if (i < len)
218 {
219 *out++ = R2[i / 2];
220 }
221}
222
223// h = f * g in Rq (g small), mod x^p - x - 1.
224void Rq_mult_small(Fq *h, const Fq *f, const small_t *g)
225{
226 int32_t fg[PC_SNTRUP_P + PC_SNTRUP_P - 1];
227 int i, j;
228 for (i = 0; i < PC_SNTRUP_P + PC_SNTRUP_P - 1; ++i)
229 {
230 fg[i] = 0;
231 }
232 for (i = 0; i < PC_SNTRUP_P; ++i)
233 {
234 for (j = 0; j < PC_SNTRUP_P; ++j)
235 {
236 fg[i + j] += f[i] * (int32_t)g[j];
237 }
238 }
239 for (i = PC_SNTRUP_P; i < PC_SNTRUP_P + PC_SNTRUP_P - 1; ++i)
240 {
241 fg[i - PC_SNTRUP_P] += fg[i];
242 }
243 for (i = PC_SNTRUP_P; i < PC_SNTRUP_P + PC_SNTRUP_P - 1; ++i)
244 {
245 fg[i - PC_SNTRUP_P + 1] += fg[i];
246 }
247 for (i = 0; i < PC_SNTRUP_P; ++i)
248 {
249 h[i] = Fq_freeze(fg[i]);
250 }
251}
252
253void Round(Fq *out, const Fq *a)
254{
255 for (int i = 0; i < PC_SNTRUP_P; ++i)
256 {
257 out[i] = (Fq)(a[i] - F3_freeze(a[i]));
258 }
259}
260
261// --- constant-time uint32 sort (djbsort network), for Short_fromlist ---
262inline void int32_minmax(int32_t *pp, int32_t *pq)
263{
264 int32_t x = *pp, y = *pq;
265 int64_t d = (int64_t)y - (int64_t)x; // 64-bit to avoid overflow
266 // (d>>63) is the sign bit (0 or 1); negate it as a signed 0/1 to get a 0 / all-ones mask - the branchless
267 // constant-time select. (Negating the *unsigned* value was an equivalent idiom but is a Sonar bug flag.)
268 int32_t swap = -(int32_t)((uint64_t)d >> 63) & (x ^ y); // all-ones when y < x
269 *pp = x ^ swap;
270 *pq = y ^ swap;
271}
272
273void crypto_sort_int32(int32_t *x, long long n)
274{
275 long long top, p, q, r, i, j;
276 // The sole caller (crypto_sort_uint32, itself only called from Short_fromlist) always passes
277 // n = P = 761, a fixed compile-time constant, so n < 2 can never hold for any host input.
278 if (n < 2) // GCOVR_EXCL_BR_LINE see above: n is always 761
279 {
280 return; // GCOVR_EXCL_LINE
281 }
282 top = 1;
283 while (top < n - top)
284 {
285 top += top;
286 }
287 for (p = top; p >= 1; p >>= 1)
288 {
289 i = 0;
290 while (i + 2 * p <= n)
291 {
292 for (j = i; j < i + p; ++j)
293 {
294 int32_minmax(&x[j], &x[j + p]);
295 }
296 i += 2 * p;
297 }
298 for (j = i; j < n - p; ++j)
299 {
300 int32_minmax(&x[j], &x[j + p]);
301 }
302 i = 0;
303 j = 0;
304 for (q = top; q > p; q >>= 1)
305 {
306 if (j != i)
307 {
308 for (;;)
309 {
310 // The (i,j,p,q) control values driving this network depend only on n, which is
311 // always P = 761 (see crypto_sort_int32's n < 2 note above) - never on the data
312 // being sorted. For n = 761 this inner loop always exits via j == i + p below;
313 // confirmed by exhaustively tracing the (i,j,p,q) sequence for n = 761, where this
314 // arm is never taken. Verbatim upstream djbsort shape kept for other n (see file
315 // header); unreachable from any host input at this file's fixed P.
316 if (j == n - q) // GCOVR_EXCL_BR_LINE see above: never true for n = 761
317 {
318 goto done; // GCOVR_EXCL_LINE
319 }
320 int32_t a = x[j + p];
321 for (r = q; r > p; r >>= 1)
322 {
323 int32_minmax(&a, &x[j + r]);
324 }
325 x[j + p] = a;
326 ++j;
327 if (j == i + p)
328 {
329 i += 2 * p;
330 break;
331 }
332 }
333 }
334 while (i + p <= n - q)
335 {
336 for (j = i; j < i + p; ++j)
337 {
338 int32_t a = x[j + p];
339 for (r = q; r > p; r >>= 1)
340 {
341 int32_minmax(&a, &x[j + r]);
342 }
343 x[j + p] = a;
344 }
345 i += 2 * p;
346 }
347 j = i;
348 while (j < n - q)
349 {
350 int32_t a = x[j + p];
351 for (r = q; r > p; r >>= 1)
352 {
353 int32_minmax(&a, &x[j + r]);
354 }
355 x[j + p] = a;
356 ++j;
357 }
358 done:;
359 }
360 }
361}
362
363void crypto_sort_uint32(uint32_t *x, long long n)
364{
365 for (long long j = 0; j < n; ++j)
366 {
367 x[j] ^= 0x80000000u;
368 }
369 crypto_sort_int32((int32_t *)x, n);
370 for (long long j = 0; j < n; ++j)
371 {
372 x[j] ^= 0x80000000u;
373 }
374}
375
376void Short_fromlist(small_t *out, const uint32_t *in)
377{
378 uint32_t L[PC_SNTRUP_P];
379 int i;
380 for (i = 0; i < PC_SNTRUP_W; ++i)
381 {
382 L[i] = in[i] & (uint32_t)-2;
383 }
384 for (i = PC_SNTRUP_W; i < PC_SNTRUP_P; ++i)
385 {
386 L[i] = (in[i] & (uint32_t)-3) | 1;
387 }
388 crypto_sort_uint32(L, PC_SNTRUP_P);
389 for (i = 0; i < PC_SNTRUP_P; ++i)
390 {
391 out[i] = (small_t)((L[i] & 3) - 1);
392 }
393}
394
395void Short_random(small_t *out)
396{
397 uint32_t L[PC_SNTRUP_P];
398 uint8_t rb[4];
399 for (int i = 0; i < PC_SNTRUP_P; ++i)
400 {
401 ssh_rng_fill(rb, 4);
402 L[i] = (uint32_t)rb[0] | ((uint32_t)rb[1] << 8) | ((uint32_t)rb[2] << 16) | ((uint32_t)rb[3] << 24);
403 }
404 Short_fromlist(out, L);
405}
406
407// out = SHA512(b || in)[0:32].
408void Hash_prefix(uint8_t *out, int b, const uint8_t *in, size_t inlen)
409{
410 pc_sha512_ctx ctx;
411 uint8_t h[PC_SHA512_DIGEST_LEN];
412 uint8_t bb = (uint8_t)b;
413 pc_sha512_init(&ctx);
414 pc_sha512_update(&ctx, &bb, 1);
415 pc_sha512_update(&ctx, in, inlen);
416 pc_sha512_final(&ctx, h);
417 memcpy(out, h, PC_HASH_BYTES);
418}
419
420void Small_encode(uint8_t *s, const small_t *f)
421{
422 for (int i = 0; i < PC_SNTRUP_P / 4; ++i)
423 {
424 small_t x = 0;
425 for (int j = 0; j < 4; ++j)
426 {
427 x = (small_t)(x + ((*f++ + 1) << (2 * j)));
428 }
429 *s++ = (uint8_t)x;
430 }
431 *s = (uint8_t)(*f + 1);
432}
433
434void Rq_decode(Fq *r, const uint8_t *s, uint16_t *scr, uint32_t *scr32)
435{
436 uint16_t Rr[PC_SNTRUP_P], M[PC_SNTRUP_P];
437 for (int i = 0; i < PC_SNTRUP_P; ++i)
438 {
439 M[i] = PC_SNTRUP_Q;
440 }
441 Decode(Rr, s, M, PC_SNTRUP_P, scr, scr32);
442 for (int i = 0; i < PC_SNTRUP_P; ++i)
443 {
444 r[i] = (Fq)(((Fq)Rr[i]) - PC_Q12);
445 }
446}
447
448void Rounded_encode(uint8_t *s, const Fq *r, uint16_t *scr)
449{
450 uint16_t Rr[PC_SNTRUP_P], M[PC_SNTRUP_P];
451 for (int i = 0; i < PC_SNTRUP_P; ++i)
452 {
453 Rr[i] = (uint16_t)(((r[i] + PC_Q12) * 10923) >> 15);
454 }
455 for (int i = 0; i < PC_SNTRUP_P; ++i)
456 {
457 M[i] = (PC_SNTRUP_Q + 2) / 3;
458 }
459 Encode(s, Rr, M, PC_SNTRUP_P, scr);
460}
461
462void HashConfirm(uint8_t *h, const uint8_t *r_enc, const uint8_t *cache)
463{
464 uint8_t x[PC_HASH_BYTES * 2];
465 Hash_prefix(x, 3, r_enc, PC_SMALL_BYTES);
466 memcpy(x + PC_HASH_BYTES, cache, PC_HASH_BYTES);
467 Hash_prefix(h, 2, x, sizeof x);
468}
469
470void HashSession(uint8_t *k, int b, const uint8_t *r_enc, const uint8_t *c)
471{
472 uint8_t x[PC_HASH_BYTES + PC_CT_BYTES];
473 Hash_prefix(x, 3, r_enc, PC_SMALL_BYTES);
474 memcpy(x + PC_HASH_BYTES, c, PC_CT_BYTES);
475 Hash_prefix(k, b, x, sizeof x);
476}
477
478// Encapsulation reused for the Decapsulation FO re-encrypt check.
479void Hide(uint8_t *c, uint8_t *r_enc, const small_t *r, const uint8_t *pk, const uint8_t *cache, uint16_t *scr,
480 uint32_t *scr32)
481{
482 Small_encode(r_enc, r);
483 Fq h[PC_SNTRUP_P], cp[PC_SNTRUP_P];
484 Rq_decode(h, pk, scr, scr32);
485 Rq_mult_small(cp, h, r); // c = Round(h * r)
486 Round(cp, cp);
487 Rounded_encode(c, cp, scr);
488 HashConfirm(c + PC_CT_BYTES - PC_CONFIRM_BYTES, r_enc, cache);
489}
490
491// ===========================================================================
492// KeyGen + Decapsulation (the reverse-SSH client / initiator side)
493// ===========================================================================
494
495// -1 (all ones) when the argument is nonzero / negative; 0 otherwise (our int widths are known).
496inline int nonzero_mask16(int16_t x)
497{
498 uint32_t u = (uint16_t)x;
499 return -(int)((u | (0u - u)) >> 31);
500}
501inline int negative_mask16(int16_t x)
502{
503 return -((uint16_t)x >> 15);
504}
505
506void R3_fromRq(small_t *out, const Fq *r)
507{
508 for (int i = 0; i < PC_SNTRUP_P; ++i)
509 {
510 out[i] = F3_freeze(r[i]);
511 }
512}
513
514void R3_mult(small_t *h, const small_t *f, const small_t *g)
515{
516 int16_t fg[PC_SNTRUP_P + PC_SNTRUP_P - 1];
517 int i, j;
518 for (i = 0; i < PC_SNTRUP_P + PC_SNTRUP_P - 1; ++i)
519 {
520 fg[i] = 0;
521 }
522 for (i = 0; i < PC_SNTRUP_P; ++i)
523 {
524 for (j = 0; j < PC_SNTRUP_P; ++j)
525 {
526 fg[i + j] = (int16_t)(fg[i + j] + f[i] * (int16_t)g[j]);
527 }
528 }
529 for (i = PC_SNTRUP_P; i < PC_SNTRUP_P + PC_SNTRUP_P - 1; ++i)
530 {
531 fg[i - PC_SNTRUP_P] = (int16_t)(fg[i - PC_SNTRUP_P] + fg[i]);
532 }
533 for (i = PC_SNTRUP_P; i < PC_SNTRUP_P + PC_SNTRUP_P - 1; ++i)
534 {
535 fg[i - PC_SNTRUP_P + 1] = (int16_t)(fg[i - PC_SNTRUP_P + 1] + fg[i]);
536 }
537 for (i = 0; i < PC_SNTRUP_P; ++i)
538 {
539 h[i] = F3_freeze(fg[i]);
540 }
541}
542
543// 1/in in R3 (mod 3); returns 0 on success (in invertible), -1 otherwise. Constant-time GCD.
544int R3_recip(small_t *out, const small_t *in)
545{
546 small_t f[PC_SNTRUP_P + 1], g[PC_SNTRUP_P + 1], v[PC_SNTRUP_P + 1], r[PC_SNTRUP_P + 1];
547 int sign, swap, t, i, loop, delta = 1;
548 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
549 {
550 v[i] = 0;
551 }
552 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
553 {
554 r[i] = 0;
555 }
556 r[0] = 1;
557 for (i = 0; i < PC_SNTRUP_P; ++i)
558 {
559 f[i] = 0;
560 }
561 f[0] = 1;
562 f[PC_SNTRUP_P - 1] = f[PC_SNTRUP_P] = -1;
563 for (i = 0; i < PC_SNTRUP_P; ++i)
564 {
565 g[PC_SNTRUP_P - 1 - i] = in[i];
566 }
567 g[PC_SNTRUP_P] = 0;
568 for (loop = 0; loop < 2 * PC_SNTRUP_P - 1; ++loop)
569 {
570 for (i = PC_SNTRUP_P; i > 0; --i)
571 {
572 v[i] = v[i - 1];
573 }
574 v[0] = 0;
575 sign = -g[0] * f[0];
576 swap = negative_mask16((int16_t)-delta) & nonzero_mask16(g[0]);
577 delta ^= swap & (delta ^ -delta);
578 delta += 1;
579 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
580 {
581 t = swap & (f[i] ^ g[i]);
582 f[i] = (small_t)(f[i] ^ t);
583 g[i] = (small_t)(g[i] ^ t);
584 t = swap & (v[i] ^ r[i]);
585 v[i] = (small_t)(v[i] ^ t);
586 r[i] = (small_t)(r[i] ^ t);
587 }
588 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
589 {
590 g[i] = F3_freeze((int16_t)(g[i] + sign * f[i]));
591 }
592 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
593 {
594 r[i] = F3_freeze((int16_t)(r[i] + sign * v[i]));
595 }
596 for (i = 0; i < PC_SNTRUP_P; ++i)
597 {
598 g[i] = g[i + 1];
599 }
600 g[PC_SNTRUP_P] = 0;
601 }
602 sign = f[0];
603 for (i = 0; i < PC_SNTRUP_P; ++i)
604 {
605 out[i] = (small_t)(sign * v[PC_SNTRUP_P - 1 - i]);
606 }
607 return nonzero_mask16((int16_t)delta);
608}
609
610void Rq_mult3(Fq *h, const Fq *f)
611{
612 for (int i = 0; i < PC_SNTRUP_P; ++i)
613 {
614 h[i] = Fq_freeze(3 * f[i]);
615 }
616}
617
618Fq Fq_recip(Fq a1)
619{
620 int i = 1;
621 Fq ai = a1;
622 while (i < PC_SNTRUP_Q - 2)
623 {
624 ai = Fq_freeze(a1 * (int32_t)ai);
625 i += 1;
626 }
627 return ai;
628}
629
630// out = 1/(3*in) in Rq (used by KeyGen). Constant-time GCD over Fq.
631int Rq_recip3(Fq *out, const small_t *in)
632{
633 Fq f[PC_SNTRUP_P + 1], g[PC_SNTRUP_P + 1], v[PC_SNTRUP_P + 1], r[PC_SNTRUP_P + 1], scale;
634 int swap, i, loop, delta = 1;
635 int32_t f0, g0;
636 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
637 {
638 v[i] = 0;
639 }
640 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
641 {
642 r[i] = 0;
643 }
644 r[0] = Fq_recip(3);
645 for (i = 0; i < PC_SNTRUP_P; ++i)
646 {
647 f[i] = 0;
648 }
649 f[0] = 1;
650 f[PC_SNTRUP_P - 1] = f[PC_SNTRUP_P] = -1;
651 for (i = 0; i < PC_SNTRUP_P; ++i)
652 {
653 g[PC_SNTRUP_P - 1 - i] = in[i];
654 }
655 g[PC_SNTRUP_P] = 0;
656 for (loop = 0; loop < 2 * PC_SNTRUP_P - 1; ++loop)
657 {
658 for (i = PC_SNTRUP_P; i > 0; --i)
659 {
660 v[i] = v[i - 1];
661 }
662 v[0] = 0;
663 swap = negative_mask16((int16_t)-delta) & nonzero_mask16(g[0]);
664 delta ^= swap & (delta ^ -delta);
665 delta += 1;
666 Fq tmp;
667 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
668 {
669 tmp = (Fq)(swap & (f[i] ^ g[i]));
670 f[i] ^= tmp;
671 g[i] ^= tmp;
672 tmp = (Fq)(swap & (v[i] ^ r[i]));
673 v[i] ^= tmp;
674 r[i] ^= tmp;
675 }
676 f0 = f[0];
677 g0 = g[0];
678 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
679 {
680 g[i] = Fq_freeze(f0 * g[i] - g0 * f[i]);
681 }
682 for (i = 0; i < PC_SNTRUP_P + 1; ++i)
683 {
684 r[i] = Fq_freeze(f0 * r[i] - g0 * v[i]);
685 }
686 for (i = 0; i < PC_SNTRUP_P; ++i)
687 {
688 g[i] = g[i + 1];
689 }
690 g[PC_SNTRUP_P] = 0;
691 }
692 scale = Fq_recip(f[0]);
693 for (i = 0; i < PC_SNTRUP_P; ++i)
694 {
695 out[i] = Fq_freeze(scale * (int32_t)v[PC_SNTRUP_P - 1 - i]);
696 }
697 return nonzero_mask16((int16_t)delta);
698}
699
700int Weightw_mask(const small_t *r)
701{
702 int weight = 0;
703 for (int i = 0; i < PC_SNTRUP_P; ++i)
704 {
705 weight += (r[i] & 1);
706 }
707 return nonzero_mask16((int16_t)(weight - PC_SNTRUP_W));
708}
709
710void Small_random(small_t *out)
711{
712 uint8_t rb[4];
713 for (int i = 0; i < PC_SNTRUP_P; ++i)
714 {
715 ssh_rng_fill(rb, 4);
716 uint32_t u = (uint32_t)rb[0] | ((uint32_t)rb[1] << 8) | ((uint32_t)rb[2] << 16) | ((uint32_t)rb[3] << 24);
717 out[i] = (small_t)((((u & 0x3fffffff) * 3) >> 30) - 1);
718 }
719}
720
721void KeyGen(Fq *h, small_t *f, small_t *ginv)
722{
723 small_t g[PC_SNTRUP_P];
724 Fq finv[PC_SNTRUP_P];
725 for (;;)
726 {
727 Small_random(g);
728 if (R3_recip(ginv, g) == 0)
729 {
730 break;
731 }
732 }
733 Short_random(f);
734 Rq_recip3(finv, f);
735 Rq_mult_small(h, finv, g);
736}
737
738void Small_decode(small_t *f, const uint8_t *s)
739{
740 for (int i = 0; i < PC_SNTRUP_P / 4; ++i)
741 {
742 uint8_t x = *s++;
743 for (int j = 0; j < 4; ++j)
744 {
745 *f++ = (small_t)(((x >> (2 * j)) & 3) - 1);
746 }
747 }
748 *f = (small_t)((*s & 3) - 1);
749}
750
751void Rounded_decode(Fq *r, const uint8_t *s, uint16_t *scr, uint32_t *scr32)
752{
753 uint16_t Rr[PC_SNTRUP_P], M[PC_SNTRUP_P];
754 for (int i = 0; i < PC_SNTRUP_P; ++i)
755 {
756 M[i] = (PC_SNTRUP_Q + 2) / 3;
757 }
758 Decode(Rr, s, M, PC_SNTRUP_P, scr, scr32);
759 for (int i = 0; i < PC_SNTRUP_P; ++i)
760 {
761 r[i] = (Fq)(Rr[i] * 3 - PC_Q12);
762 }
763}
764
765void Rq_encode(uint8_t *s, const Fq *r, uint16_t *scr)
766{
767 uint16_t Rr[PC_SNTRUP_P], M[PC_SNTRUP_P];
768 for (int i = 0; i < PC_SNTRUP_P; ++i)
769 {
770 Rr[i] = (uint16_t)(r[i] + PC_Q12);
771 }
772 for (int i = 0; i < PC_SNTRUP_P; ++i)
773 {
774 M[i] = PC_SNTRUP_Q;
775 }
776 Encode(s, Rr, M, PC_SNTRUP_P, scr);
777}
778
779void Decrypt(small_t *r, const Fq *c, const small_t *f, const small_t *ginv)
780{
781 Fq cf[PC_SNTRUP_P], cf3[PC_SNTRUP_P];
782 small_t e[PC_SNTRUP_P], ev[PC_SNTRUP_P];
783 int mask, i;
784 Rq_mult_small(cf, c, f);
785 Rq_mult3(cf3, cf);
786 R3_fromRq(e, cf3);
787 R3_mult(ev, e, ginv);
788 mask = Weightw_mask(ev);
789 for (i = 0; i < PC_SNTRUP_W; ++i)
790 {
791 r[i] = (small_t)(((ev[i] ^ 1) & ~mask) ^ 1);
792 }
793 for (i = PC_SNTRUP_W; i < PC_SNTRUP_P; ++i)
794 {
795 r[i] = (small_t)(ev[i] & ~mask);
796 }
797}
798
799// 0 when the two ciphertexts are equal, -1 otherwise.
800int Ciphertexts_diff_mask(const uint8_t *c, const uint8_t *c2)
801{
802 uint16_t differentbits = 0;
803 for (int i = 0; i < PC_CT_BYTES; ++i)
804 {
805 differentbits |= (uint16_t)(c[i] ^ c2[i]);
806 }
807 // keep the uint16_t cast: it makes the differentbits==0 case wrap to 0xffff explicitly rather
808 // than leaning on an implementation-defined arithmetic shift of the promoted -1.
809 return ((((uint16_t)(differentbits - 1)) >> 8) & 1) - 1;
810}
811
812} // namespace
813
814void pc_sntrup761_enc(const uint8_t pk[PC_SNTRUP761_PK_BYTES], uint8_t ct[PC_SNTRUP761_CT_BYTES],
815 uint8_t ss[PC_SNTRUP761_SS_BYTES])
816{
817 uint16_t scr16[PC_SCR16];
818 uint32_t scr32[PC_SCR32];
819 small_t r[PC_SNTRUP_P];
820 uint8_t r_enc[PC_SMALL_BYTES];
821 uint8_t cache[PC_HASH_BYTES];
822
823 Hash_prefix(cache, 4, pk, PC_PK_BYTES);
824 Short_random(r);
825 Hide(ct, r_enc, r, pk, cache, scr16, scr32);
826 HashSession(ss, 1, r_enc, ct);
827}
828
829void pc_sntrup761_keypair(uint8_t pk[PC_SNTRUP761_PK_BYTES], uint8_t sk[PC_SNTRUP761_SK_BYTES])
830{
831 uint16_t scr16[PC_SCR16];
832 Fq h[PC_SNTRUP_P];
833 small_t f[PC_SNTRUP_P];
834 small_t ginv[PC_SNTRUP_P];
835
836 KeyGen(h, f, ginv);
837 Rq_encode(pk, h, scr16);
838 Small_encode(sk, f);
839 Small_encode(sk + PC_SMALL_BYTES, ginv);
840 // ...then the pk copy, a random rho for implicit reject, and the cached H(4||pk).
841 uint8_t *tail = sk + 2 * PC_SMALL_BYTES; // SecretKeys_bytes = 2 * Small_bytes
842 memcpy(tail, pk, PC_PK_BYTES);
843 ssh_rng_fill(tail + PC_PK_BYTES, PC_SMALL_BYTES);
844 Hash_prefix(tail + PC_PK_BYTES + PC_SMALL_BYTES, 4, pk, PC_PK_BYTES);
845}
846
847void pc_sntrup761_dec(const uint8_t sk[PC_SNTRUP761_SK_BYTES], const uint8_t ct[PC_SNTRUP761_CT_BYTES],
848 uint8_t ss[PC_SNTRUP761_SS_BYTES])
849{
850 uint16_t scr16[PC_SCR16];
851 uint32_t scr32[PC_SCR32];
852 const uint8_t *pk = sk + 2 * PC_SMALL_BYTES;
853 const uint8_t *rho = pk + PC_PK_BYTES;
854 const uint8_t *cache = rho + PC_SMALL_BYTES;
855 small_t f[PC_SNTRUP_P];
856 small_t ginv[PC_SNTRUP_P];
857 small_t r[PC_SNTRUP_P];
858 Fq cp[PC_SNTRUP_P];
859 uint8_t r_enc[PC_SMALL_BYTES];
860 uint8_t cnew[PC_CT_BYTES];
861
862 Small_decode(f, sk);
863 Small_decode(ginv, sk + PC_SMALL_BYTES);
864 Rounded_decode(cp, ct, scr16, scr32);
865 Decrypt(r, cp, f, ginv);
866 Hide(cnew, r_enc, r, pk, cache, scr16, scr32); // re-encrypt: FO check
867 int mask = Ciphertexts_diff_mask(ct, cnew);
868 for (int i = 0; i < PC_SMALL_BYTES; ++i)
869 {
870 r_enc[i] = (uint8_t)(r_enc[i] ^ (mask & (r_enc[i] ^ rho[i]))); // implicit reject -> rho
871 }
872 HashSession(ss, 1 + mask, r_enc, ct);
873}
874
875#endif // PC_ENABLE_SSH_SNTRUP761
#define R2(A, B, C, D, K, S)
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
void pc_sha512_update(pc_sha512_ctx *ctx, const uint8_t *data, size_t len)
Feed len bytes of data into the running hash.
Definition sha512.cpp:38
PC_CRYPTO_HOT void pc_sha512_init(pc_sha512_ctx *ctx)
Initialize a streaming SHA-512 context (ctx must not be NULL).
Definition sha512.cpp:28
void pc_sha512_final(pc_sha512_ctx *ctx, uint8_t digest[PC_SHA512_DIGEST_LEN])
Finalize the hash and write the 64-byte digest. The context is undefined afterwards; call init() agai...
Definition sha512.cpp:47
SHA-512 (FIPS 180-4) - streaming context and one-shot API.
#define PC_SHA512_DIGEST_LEN
SHA-512 digest length in bytes.
Definition sha512.h:24
Streamlined NTRU Prime sntrup761 KEM - responder (encapsulation) only.
void ssh_rng_fill(uint8_t *buf, size_t len)
Fill len bytes of buf with cryptographically random data.
Definition ssh_dh.cpp:20
Streaming SHA-512 context.
Definition sha512.h:33