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