DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
ssh_ed25519.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 ssh_ed25519.cpp
6 * @brief Ed25519 (RFC 8032) sign + verify over edwards25519.
7 *
8 * Points are held in extended twisted-Edwards coordinates (X, Y, Z, T) as four field
9 * elements; scalar multiplication is a constant-time bit-by-bit double-and-add; scalars
10 * are reduced mod the group order L. SHA-512 hashes the seed, the nonce input, and R||A||M.
11 * Deterministic (no RNG). Validated against the RFC 8032 §7.1 vectors and a reference
12 * implementation (test_ssh_ed25519).
13 *
14 * The field elements and point arithmetic have two implementations: the portable radix-2^16
15 * `ssh_gf` (from ssh_curve25519, the native / non-S3 path), and on the ESP32-S3 a canonical
16 * `uint32[8]` layer that does each field multiply as one 256-bit modular multiply on the
17 * RSA/MPI accelerator (ssh_fe25519.h, active as DETWS_FE25519_MPI_HW) - the same engine that
18 * accelerates the X25519 KEX, here driving the Edwards point arithmetic so the host-key
19 * signature runs several times faster. Only the point/field layer differs; the SHA-512 hashing
20 * and the scalar arithmetic mod L are shared. Both paths are byte-identical by construction.
21 */
22
24#include "network_drivers/presentation/ssh/crypto/ssh_curve25519.h" // ssh_gf + field ops (native / non-S3 path)
25#include "network_drivers/presentation/ssh/crypto/ssh_fe25519.h" // S3: canonical uint32[8] field on the RSA MODMULT
27
28// --- Shared constants -------------------------------------------------------
29
30// The group order L = 2^252 + 27742317777372353535851937790883648493, little-endian.
31static const int64_t ED_L[32] = {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7,
32 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0,
33 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10};
34
35// --- Shared helpers (representation-independent) ----------------------------
36
37// Constant-time 32-byte compare: 0 if equal, -1 otherwise.
38static int ct_verify32(const uint8_t *x, const uint8_t *y)
39{
40 unsigned diff = 0;
41 for (int i = 0; i < 32; i++)
42 diff |= (unsigned)(x[i] ^ y[i]);
43 return (int)((1 & ((diff - 1) >> 8)) - 1);
44}
45
46// --- Scalar reduction mod L -------------------------------------------------
47
48// Reduce the 512-bit little-endian value x[0..63] modulo L into r[0..31].
49static void ed_modL(uint8_t r[32], int64_t x[64])
50{
51 int64_t carry;
52 int i;
53 int j;
54 for (i = 63; i >= 32; --i)
55 {
56 carry = 0;
57 for (j = i - 32; j < i - 12; ++j)
58 {
59 x[j] += carry - 16 * x[i] * ED_L[j - (i - 32)];
60 carry = (x[j] + 128) >> 8;
61 x[j] -= carry << 8;
62 }
63 x[j] += carry;
64 x[i] = 0;
65 }
66 carry = 0;
67 for (j = 0; j < 32; ++j)
68 {
69 x[j] += carry - (x[31] >> 4) * ED_L[j];
70 carry = x[j] >> 8;
71 x[j] &= 255;
72 }
73 for (j = 0; j < 32; ++j)
74 x[j] -= carry * ED_L[j];
75 for (i = 0; i < 32; ++i)
76 {
77 x[i + 1] += x[i] >> 8;
78 r[i] = (uint8_t)(x[i] & 255);
79 }
80}
81
82// Reduce a 64-byte hash in place: r[0..31] = r mod L.
83static void ed_reduce(uint8_t r[64])
84{
85 int64_t x[64];
86 for (int i = 0; i < 64; i++)
87 x[i] = (int64_t)(uint64_t)r[i];
88 for (int i = 0; i < 64; i++)
89 r[i] = 0;
90 ed_modL(r, x);
91}
92
93// True iff the little-endian 32-byte scalar S is canonical (0 <= S < L). RFC 8032 §5.1.7 requires this:
94// S and S+L both satisfy the group equation (L*B is the identity), so without the range check the signature
95// is malleable. Verification operates only on public data, so a plain compare from the top byte down is fine.
96static bool ed_scalar_canonical(const uint8_t s[32])
97{
98 for (int i = 31; i >= 0; i--)
99 {
100 uint8_t li = (uint8_t)ED_L[i];
101 if (s[i] < li)
102 return true;
103 if (s[i] > li)
104 return false;
105 }
106 return false; // S == L is out of range
107}
108
109#ifdef DETWS_FE25519_MPI_HW
110// ===================== ESP32-S3 Edwards point arithmetic on the RSA/MPI field ============================
111// The curve constants as canonical uint32[8] (each word = ssh_gf limb 2i | limb 2i+1 << 16 of the radix-2^16
112// constants below; the point arithmetic is byte-identical to the ssh_gf path, verified by the RFC 8032 KAT).
113static const fe ED_D_FE = {0x135978a3, 0x75eb4dca, 0x4141d8ab, 0x00700a4d,
114 0x7779e898, 0x8cc74079, 0x2b6ffe73, 0x52036cee}; // d = -121665/121666
115static const fe ED_D2_FE = {0x26b2f159, 0xebd69b94, 0x8283b156, 0x00e0149a,
116 0xeef3d130, 0x198e80f2, 0x56dffce7, 0x2406d9dc}; // 2d
117static const fe ED_X_FE = {0x8f25d51a, 0xc9562d60, 0x9525a7b2, 0x692cc760,
118 0xfdd6dc5c, 0xc0a4e231, 0xcd6e53fe, 0x216936d3}; // base point x
119static const fe ED_Y_FE = {0x66666658, 0x66666666, 0x66666666, 0x66666666,
120 0x66666666, 0x66666666, 0x66666666, 0x66666666}; // base point y
121static const fe ED_I_FE = {0x4a0ea0b0, 0xc4ee1b27, 0xad2fe478, 0x2f431806,
122 0x3dfbd7a7, 0x2b4d0099, 0x4fc1df0b, 0x2b832480}; // sqrt(-1) mod p
123
124// Fixed-base comb table ED_COMB[i][j] = (j+1) * 256^i * B (extended X, Y, T; Z = 1), in flash.
126
127// p += q (twisted-Edwards addition, RFC 8032 §5.1.4). Safe when q aliases p (point doubling): every read of
128// q happens before any write of p. Requires ssh_fe_hw_enable() (the fe_mul/fe_sq run on the accelerator).
129static void edf_add(fe p[4], fe q[4])
130{
131 fe a;
132 fe b;
133 fe c;
134 fe d;
135 fe t;
136 fe e;
137 fe f;
138 fe g;
139 fe h;
140 fe_sub(a, p[1], p[0]);
141 fe_sub(t, q[1], q[0]);
142 fe_mul(a, a, t);
143 fe_add(b, p[0], p[1]);
144 fe_add(t, q[0], q[1]);
145 fe_mul(b, b, t);
146 fe_mul(c, p[3], q[3]);
147 fe_mul(c, c, ED_D2_FE);
148 fe_mul(d, p[2], q[2]);
149 fe_add(d, d, d);
150 fe_sub(e, b, a);
151 fe_sub(f, d, c);
152 fe_add(g, d, c);
153 fe_add(h, b, a);
154 fe_mul(p[0], e, f);
155 fe_mul(p[1], h, g);
156 fe_mul(p[2], g, f);
157 fe_mul(p[3], e, h);
158}
159
160// Constant-time conditional swap of points p and q when b == 1.
161static void edf_cswap(fe p[4], fe q[4], int b)
162{
163 for (int i = 0; i < 4; i++)
164 fe_cswap(p[i], q[i], (uint32_t)b);
165}
166
167// Encode a point to 32 bytes: y with x's low bit in the top bit.
168static void edf_pack(uint8_t r[32], fe p[4])
169{
170 fe tx;
171 fe ty;
172 fe zi;
173 fe_invert(zi, p[2]);
174 fe_mul(tx, p[0], zi);
175 fe_mul(ty, p[1], zi);
176 fe_tobytes(r, ty);
177 r[31] ^= (uint8_t)(fe_parity(tx) << 7);
178}
179
180// p = s * q (variable-base scalar mult), s is 32 bytes little-endian.
181static void edf_scalarmult(fe p[4], fe q[4], const uint8_t *s)
182{
183 fe_0(p[0]);
184 fe_1(p[1]);
185 fe_1(p[2]);
186 fe_0(p[3]);
187 for (int i = 255; i >= 0; i--)
188 {
189 int b = (s[i >> 3] >> (i & 7)) & 1;
190 edf_cswap(p, q, b);
191 edf_add(q, p);
192 edf_add(p, p);
193 edf_cswap(p, q, b);
194 }
195}
196
197// Constant-time select of the comb entry for a signed 4-bit digit into extended point t (Z = 1).
198// Scans all 8 entries of group idx (the address depends only on the public idx, not the secret digit),
199// selects |digit|, and conditionally negates when digit < 0. digit in [-8, 8]; digit == 0 -> identity.
200static void edf_comb_select(fe t[4], int idx, int digit)
201{
202 int32_t sign = digit >> 31; // -1 if digit < 0, else 0
203 uint32_t babs = (uint32_t)((digit ^ sign) - sign); // |digit| in 0..8
204 fe_0(t[0]);
205 fe_1(t[1]);
206 fe_1(t[2]); // Z = 1
207 fe_0(t[3]);
208 for (uint32_t j = 0; j < 8; j++)
209 {
210 uint32_t x = babs ^ (j + 1);
211 uint32_t nz = (x | (0u - x)) >> 31; // 1 if babs != j+1
212 uint32_t mask = (uint32_t)(nz - 1u); // 0xffffffff if babs == j+1, else 0
213 for (int k = 0; k < 8; k++)
214 {
215 t[0][k] = (t[0][k] & ~mask) | (ED_COMB[idx][j][0][k] & mask); // X
216 t[1][k] = (t[1][k] & ~mask) | (ED_COMB[idx][j][1][k] & mask); // Y
217 t[3][k] = (t[3][k] & ~mask) | (ED_COMB[idx][j][2][k] & mask); // T
218 }
219 }
220 // Conditional negate: -(X, Y, Z, T) = (-X, Y, Z, -T).
221 uint32_t neg = (uint32_t)sign;
222 fe zero;
223 fe negx;
224 fe negt;
225 fe_0(zero);
226 fe_sub(negx, zero, t[0]);
227 fe_sub(negt, zero, t[3]);
228 for (int k = 0; k < 8; k++)
229 {
230 t[0][k] = (t[0][k] & ~neg) | (negx[k] & neg);
231 t[3][k] = (t[3][k] & ~neg) | (negt[k] & neg);
232 }
233}
234
235// p = s * B via a constant-time signed 4-bit fixed-base comb (ref10 layout), s is 32 bytes LE.
236// The doublings are baked into ED_COMB (each group i holds 256^i * B multiples), so this is ~64
237// additions + 4 doublings instead of the 255-add / 255-double variable-base ladder - several times
238// cheaper. Both the Ed25519 sign scalar-mults (A = a*B, R = r*B) go through here.
239static void edf_scalarbase(fe p[4], const uint8_t *s)
240{
241 signed char e[64]; // 64 signed 4-bit digits of s (ref10 recoding)
242 for (int i = 0; i < 32; i++)
243 {
244 e[2 * i] = (signed char)(s[i] & 15);
245 e[2 * i + 1] = (signed char)((s[i] >> 4) & 15);
246 }
247 int carry = 0;
248 for (int i = 0; i < 63; i++)
249 {
250 e[i] = (signed char)(e[i] + carry);
251 carry = (e[i] + 8) >> 4;
252 e[i] = (signed char)(e[i] - (carry << 4));
253 }
254 e[63] = (signed char)(e[63] + carry);
255
256 fe_0(p[0]);
257 fe_1(p[1]);
258 fe_1(p[2]);
259 fe_0(p[3]);
260 fe t[4];
261 for (int i = 1; i < 64; i += 2) // odd nibbles (weight 16 * 256^(i/2))
262 {
263 edf_comb_select(t, i >> 1, e[i]);
264 edf_add(p, t);
265 }
266 edf_add(p, p); // * 16 so the odd sum aligns with the even nibbles
267 edf_add(p, p);
268 edf_add(p, p);
269 edf_add(p, p);
270 for (int i = 0; i < 64; i += 2) // even nibbles (weight 256^(i/2))
271 {
272 edf_comb_select(t, i >> 1, e[i]);
273 edf_add(p, t);
274 }
275}
276
277// Decode a point and negate it (r = -A); returns 0 on success, -1 if the encoding is not a valid point.
278static int edf_unpackneg(fe r[4], const uint8_t p[32])
279{
280 fe t;
281 fe chk;
282 fe num;
283 fe den;
284 fe den2;
285 fe den4;
286 fe den6;
287 fe_1(r[2]);
288 fe_frombytes(r[1], p); // y (top/sign bit masked off)
289 fe_sq(num, r[1]); // y^2
290 fe_mul(den, num, ED_D_FE);
291 fe_sub(num, num, r[2]); // u = y^2 - 1
292 fe_add(den, r[2], den); // v = d*y^2 + 1
293
294 fe_sq(den2, den);
295 fe_sq(den4, den2);
296 fe_mul(den6, den4, den2);
297 fe_mul(t, den6, num);
298 fe_mul(t, t, den);
299 fe_pow2523(t, t); // t = (u v^7)^((p-5)/8)
300 fe_mul(t, t, num);
301 fe_mul(t, t, den);
302 fe_mul(t, t, den);
303 fe_mul(r[0], t, den); // x candidate = u v^3 (u v^7)^((p-5)/8)
304
305 fe_sq(chk, r[0]);
306 fe_mul(chk, chk, den);
307 if (fe_neq(chk, num))
308 fe_mul(r[0], r[0], ED_I_FE); // multiply by sqrt(-1)
309 fe_sq(chk, r[0]);
310 fe_mul(chk, chk, den);
311 if (fe_neq(chk, num))
312 return -1; // no square root: invalid point
313
314 if (fe_parity(r[0]) == (p[31] >> 7))
315 {
316 fe zero;
317 fe_0(zero);
318 fe_sub(r[0], zero, r[0]); // pick the correct sign, then negate for -A
319 }
320 fe_mul(r[3], r[0], r[1]);
321 return 0;
322}
323
324// out = pack(s * B). Brackets the accelerator for the whole scalar-mult.
325static void ed_scalarbase_bytes(uint8_t out[32], const uint8_t s[32])
326{
327 fe p[4];
328 ssh_fe_hw_enable();
329 edf_scalarbase(p, s);
330 edf_pack(out, p);
331 ssh_fe_hw_disable();
332}
333
334// out = pack(S*B - h*A); false if the public key A does not decode to a curve point.
335static bool ed_verify_recompute(uint8_t out[32], const uint8_t S[32], const uint8_t h[32], const uint8_t pub[32])
336{
337 fe p[4];
338 fe q[4];
339 fe sb[4];
340 ssh_fe_hw_enable();
341 if (edf_unpackneg(q, pub) != 0) // q = -A
342 {
343 ssh_fe_hw_disable();
344 return false;
345 }
346 edf_scalarmult(p, q, h); // p = h * (-A)
347 edf_scalarbase(sb, S); // sb = S * B
348 edf_add(p, sb); // p = S*B - h*A
349 edf_pack(out, p);
350 ssh_fe_hw_disable();
351 return true;
352}
353#else
354// --- Curve constants (radix-2^16 field elements, little-endian limbs) --------
355
356static const ssh_gf GF0 = {0};
357static const ssh_gf GF1 = {1};
358// d = -121665/121666 (the twisted-Edwards curve constant) and 2d.
359static const ssh_gf ED_D = {0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070,
360 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203};
361static const ssh_gf ED_D2 = {0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0,
362 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406};
363// Base point B = (X, Y).
364static const ssh_gf ED_X = {0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c,
365 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169};
366static const ssh_gf ED_Y = {0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666,
367 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666};
368// sqrt(-1) mod p.
369static const ssh_gf ED_I = {0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43,
370 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83};
371
372// Parity (low bit) of the canonical encoding of a field element.
373static int gf_parity(const ssh_gf a)
374{
375 uint8_t d[32];
376 ssh_gf_pack(d, a);
377 return d[0] & 1;
378}
379
380// 0 if a and b encode the same field element, -1 otherwise.
381static int gf_neq(const ssh_gf a, const ssh_gf b)
382{
383 uint8_t c[32];
384 uint8_t d[32];
385 ssh_gf_pack(c, a);
386 ssh_gf_pack(d, b);
387 return ct_verify32(c, d);
388}
389
390// out = a^(2^252 - 3) - used to take the square root during point decompression.
391static void gf_pow2523(ssh_gf out, const ssh_gf a)
392{
393 ssh_gf c;
394 ssh_gf_copy(c, a);
395 for (int i = 250; i >= 0; i--)
396 {
397 ssh_gf_sq(c, c);
398 if (i != 1)
399 ssh_gf_mul(c, c, a);
400 }
401 ssh_gf_copy(out, c);
402}
403
404// p += q (twisted-Edwards addition, RFC 8032 §5.1.4 / the unified add formula).
405static void ed_add(ssh_gf p[4], ssh_gf q[4])
406{
407 ssh_gf a;
408 ssh_gf b;
409 ssh_gf c;
410 ssh_gf d;
411 ssh_gf t;
412 ssh_gf e;
413 ssh_gf f;
414 ssh_gf g;
415 ssh_gf h;
416 ssh_gf_sub(a, p[1], p[0]);
417 ssh_gf_sub(t, q[1], q[0]);
418 ssh_gf_mul(a, a, t);
419 ssh_gf_add(b, p[0], p[1]);
420 ssh_gf_add(t, q[0], q[1]);
421 ssh_gf_mul(b, b, t);
422 ssh_gf_mul(c, p[3], q[3]);
423 ssh_gf_mul(c, c, ED_D2);
424 ssh_gf_mul(d, p[2], q[2]);
425 ssh_gf_add(d, d, d);
426 ssh_gf_sub(e, b, a);
427 ssh_gf_sub(f, d, c);
428 ssh_gf_add(g, d, c);
429 ssh_gf_add(h, b, a);
430 ssh_gf_mul(p[0], e, f);
431 ssh_gf_mul(p[1], h, g);
432 ssh_gf_mul(p[2], g, f);
433 ssh_gf_mul(p[3], e, h);
434}
435
436// Constant-time conditional swap of points p and q when b == 1.
437static void ed_cswap(ssh_gf p[4], ssh_gf q[4], int b)
438{
439 for (int i = 0; i < 4; i++)
440 ssh_gf_cswap(p[i], q[i], b);
441}
442
443// Encode a point to 32 bytes: y with x's low bit in the top bit.
444static void ed_pack(uint8_t r[32], ssh_gf p[4])
445{
446 ssh_gf tx;
447 ssh_gf ty;
448 ssh_gf zi;
449 ssh_gf_inv(zi, p[2]);
450 ssh_gf_mul(tx, p[0], zi);
451 ssh_gf_mul(ty, p[1], zi);
452 ssh_gf_pack(r, ty);
453 r[31] ^= (uint8_t)(gf_parity(tx) << 7);
454}
455
456// p = s * q (variable-base scalar mult), s is 32 bytes little-endian.
457static void ed_scalarmult(ssh_gf p[4], ssh_gf q[4], const uint8_t *s)
458{
459 ssh_gf_copy(p[0], GF0);
460 ssh_gf_copy(p[1], GF1);
461 ssh_gf_copy(p[2], GF1);
462 ssh_gf_copy(p[3], GF0);
463 for (int i = 255; i >= 0; i--)
464 {
465 int b = (s[i >> 3] >> (i & 7)) & 1;
466 ed_cswap(p, q, b);
467 ed_add(q, p);
468 ed_add(p, p);
469 ed_cswap(p, q, b);
470 }
471}
472
473// p = s * B (base-point scalar mult).
474static void ed_scalarbase(ssh_gf p[4], const uint8_t *s)
475{
476 ssh_gf q[4];
477 ssh_gf_copy(q[0], ED_X);
478 ssh_gf_copy(q[1], ED_Y);
479 ssh_gf_copy(q[2], GF1);
480 ssh_gf_mul(q[3], ED_X, ED_Y);
481 ed_scalarmult(p, q, s);
482}
483
484// Decode a point and negate it (r = -A) for the verification equation; returns 0 on
485// success, -1 if the encoding is not a valid curve point.
486static int ed_unpackneg(ssh_gf r[4], const uint8_t p[32])
487{
488 ssh_gf t;
489 ssh_gf chk;
490 ssh_gf num;
491 ssh_gf den;
492 ssh_gf den2;
493 ssh_gf den4;
494 ssh_gf den6;
495 ssh_gf_copy(r[2], GF1);
496 ssh_gf_unpack(r[1], p); // y (top/sign bit masked off)
497 ssh_gf_sq(num, r[1]); // y^2
498 ssh_gf_mul(den, num, ED_D);
499 ssh_gf_sub(num, num, r[2]); // u = y^2 - 1
500 ssh_gf_add(den, r[2], den); // v = d*y^2 + 1
501
502 ssh_gf_sq(den2, den);
503 ssh_gf_sq(den4, den2);
504 ssh_gf_mul(den6, den4, den2);
505 ssh_gf_mul(t, den6, num);
506 ssh_gf_mul(t, t, den);
507 gf_pow2523(t, t); // t = (u v^7)^((p-5)/8)
508 ssh_gf_mul(t, t, num);
509 ssh_gf_mul(t, t, den);
510 ssh_gf_mul(t, t, den);
511 ssh_gf_mul(r[0], t, den); // x candidate = u v^3 (u v^7)^((p-5)/8)
512
513 ssh_gf_sq(chk, r[0]);
514 ssh_gf_mul(chk, chk, den);
515 if (gf_neq(chk, num))
516 ssh_gf_mul(r[0], r[0], ED_I); // multiply by sqrt(-1)
517 ssh_gf_sq(chk, r[0]);
518 ssh_gf_mul(chk, chk, den);
519 if (gf_neq(chk, num))
520 return -1; // no square root: invalid point
521
522 if (gf_parity(r[0]) == (p[31] >> 7))
523 ssh_gf_sub(r[0], GF0, r[0]); // pick the correct sign, then negate for -A
524 ssh_gf_mul(r[3], r[0], r[1]);
525 return 0;
526}
527
528// out = pack(s * B).
529static void ed_scalarbase_bytes(uint8_t out[32], const uint8_t s[32])
530{
531 ssh_gf p[4];
532 ed_scalarbase(p, s);
533 ed_pack(out, p);
534}
535
536// out = pack(S*B - h*A); false if the public key A does not decode to a curve point.
537static bool ed_verify_recompute(uint8_t out[32], const uint8_t S[32], const uint8_t h[32], const uint8_t pub[32])
538{
539 ssh_gf p[4];
540 ssh_gf q[4];
541 ssh_gf sb[4];
542 if (ed_unpackneg(q, pub) != 0) // q = -A
543 return false;
544 ed_scalarmult(p, q, h); // p = h * (-A)
545 ed_scalarbase(sb, S); // sb = S * B
546 ed_add(p, sb); // p = S*B - h*A
547 ed_pack(out, p);
548 return true;
549}
550#endif // DETWS_FE25519_MPI_HW
551
552// --- Public API -------------------------------------------------------------
553
554void ssh_ed25519_pubkey(uint8_t pub[32], const uint8_t seed[32])
555{
556 uint8_t d[64];
557 ssh_sha512(seed, 32, d);
558 d[0] &= 248;
559 d[31] &= 127;
560 d[31] |= 64; // clamp -> secret scalar a = d[0..31]
561 ed_scalarbase_bytes(pub, d);
562}
563
564void ssh_ed25519_sign(uint8_t sig[64], const uint8_t *msg, size_t mlen, const uint8_t seed[32])
565{
566 uint8_t d[64];
567 uint8_t pub[32];
568 uint8_t r[64];
569 uint8_t h[64];
570 ssh_sha512(seed, 32, d);
571 d[0] &= 248;
572 d[31] &= 127;
573 d[31] |= 64; // a = d[0..31]; prefix = d[32..63]
574
575 // A = a * B
576 ed_scalarbase_bytes(pub, d);
577
578 // r = SHA-512(prefix || M) mod L
579 SshSha512Ctx c;
580 ssh_sha512_init(&c);
581 ssh_sha512_update(&c, d + 32, 32);
582 ssh_sha512_update(&c, msg, mlen);
583 ssh_sha512_final(&c, r);
584 ed_reduce(r);
585
586 // R = r * B
587 ed_scalarbase_bytes(sig, r); // sig[0..31] = R
588
589 // h = SHA-512(R || A || M) mod L
590 ssh_sha512_init(&c);
591 ssh_sha512_update(&c, sig, 32);
592 ssh_sha512_update(&c, pub, 32);
593 ssh_sha512_update(&c, msg, mlen);
594 ssh_sha512_final(&c, h);
595 ed_reduce(h);
596
597 // S = (r + h*a) mod L
598 int64_t x[64];
599 for (int i = 0; i < 64; i++)
600 x[i] = 0;
601 for (int i = 0; i < 32; i++)
602 x[i] = (int64_t)(uint64_t)r[i];
603 for (int i = 0; i < 32; i++)
604 for (int j = 0; j < 32; j++)
605 x[i + j] += (int64_t)(uint64_t)h[i] * (int64_t)(uint64_t)d[j];
606 ed_modL(sig + 32, x); // sig[32..63] = S
607}
608
609bool ssh_ed25519_verify(const uint8_t pub[32], const uint8_t *msg, size_t mlen, const uint8_t sig[64])
610{
611 if (!ed_scalar_canonical(sig + 32))
612 return false; // non-canonical S (RFC 8032 §5.1.7): reject to prevent malleability
613
614 // h = SHA-512(R || A || M) mod L
615 uint8_t h[64];
616 SshSha512Ctx c;
617 ssh_sha512_init(&c);
618 ssh_sha512_update(&c, sig, 32); // R
619 ssh_sha512_update(&c, pub, 32); // A
620 ssh_sha512_update(&c, msg, mlen);
621 ssh_sha512_final(&c, h);
622 ed_reduce(h);
623
624 uint8_t t[32];
625 if (!ed_verify_recompute(t, sig + 32, h, pub))
626 return false; // invalid A
627 return ct_verify32(sig, t) == 0; // R == S*B - h*A ?
628}
void ssh_gf_cswap(ssh_gf p, ssh_gf q, int b)
constant-time conditional swap of p,q when b==1
void ssh_gf_add(ssh_gf out, const ssh_gf a, const ssh_gf b)
out = a + b (unreduced)
void ssh_gf_mul(ssh_gf out, const ssh_gf a, const ssh_gf b)
out = a * b mod p
void ssh_gf_unpack(ssh_gf out, const uint8_t in[32])
decode 32 bytes (high bit ignored)
void ssh_gf_copy(ssh_gf out, const ssh_gf in)
out = in
void ssh_gf_pack(uint8_t out[32], const ssh_gf a)
canonical little-endian 32-byte encoding
void ssh_gf_sub(ssh_gf out, const ssh_gf a, const ssh_gf b)
out = a - b (unreduced)
void ssh_gf_sq(ssh_gf out, const ssh_gf a)
out = a^2 mod p
void ssh_gf_inv(ssh_gf out, const ssh_gf a)
out = a^-1 mod p (= a^(p-2))
Curve25519 field arithmetic + X25519 (RFC 7748) for the curve25519-sha256 KEX.
int64_t ssh_gf[16]
A field element of GF(2^255 - 19): 16 limbs, radix 2^16 (limb i weighs 2^(16i)).
void ssh_ed25519_sign(uint8_t sig[64], const uint8_t *msg, size_t mlen, const uint8_t seed[32])
bool ssh_ed25519_verify(const uint8_t pub[32], const uint8_t *msg, size_t mlen, const uint8_t sig[64])
void ssh_ed25519_pubkey(uint8_t pub[32], const uint8_t seed[32])
Ed25519 signatures (RFC 8032) for ssh-ed25519 host keys + client auth.
ESP32-S3 GF(2^255-19) field layer on the RSA/MPI hardware accelerator (X25519 + Ed25519).
void ssh_sha512_final(SshSha512Ctx *ctx, uint8_t digest[SSH_SHA512_DIGEST_LEN])
Finalize the hash and write the 64-byte digest. The context is undefined afterwards; call init() agai...
void ssh_sha512_init(SshSha512Ctx *ctx)
Initialize a streaming SHA-512 context (ctx must not be NULL).
void ssh_sha512(const uint8_t *data, size_t len, uint8_t digest[SSH_SHA512_DIGEST_LEN])
One-shot SHA-512: hash len bytes of data into digest (64 bytes).
void ssh_sha512_update(SshSha512Ctx *ctx, const uint8_t *data, size_t len)
Feed len bytes of data into the running hash.
SHA-512 (FIPS 180-4) - streaming context and one-shot API.
Streaming SHA-512 context.
Definition ssh_sha512.h:33