ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ecdsa.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 ecdsa.cpp
6 * @brief ECDSA over NIST P-256 for ecdsa-sha2-nistp256 (RFC 5656 / FIPS 186-4).
7 *
8 * ═══════════════════════════════════════════════════════════════════════════
9 * THREE BUILD PATHS
10 * ═══════════════════════════════════════════════════════════════════════════
11 *
12 * ESP32-S3 (Arduino): a self-contained P-256 whose every 256-bit field / scalar multiply is one
13 * modular multiply on the RSA/MPI hardware accelerator (the same engine pc_fe25519.h drives for
14 * X25519 / Ed25519) - the MODMULT is modulus-generic, so it serves both the field domain (mod p)
15 * and the scalar domain (mod n) by swapping the {M, m', R^2} constants. Point arithmetic uses the
16 * exception-free complete formulas (Renes-Costello-Batina 2016, EFD add/dbl-2015-rcb, a = -3) driven by
17 * a constant-time 4-bit fixed-window scalar multiply (uniform op sequence, full-table masked select, no
18 * input-dependent branches). Signing is RFC 6979 deterministic, so the on-device output is byte-exact to
19 * the published vectors (same KATs as native). This is the production path (PC_ECDSA_MPI_HW); sign /
20 * verify / ecdh run ~2.7-2.9x faster than the mbedTLS ECP path it replaces on non-S3 targets.
21 *
22 * Native: the identical complete-formula / RFC 6979 code, but each field multiply is a software
23 * schoolbook product reduced bit-serially. Only fp_mul differs from the S3 path, so the native KATs
24 * validate the exact point / scalar arithmetic the accelerator runs. Test-only, not in firmware.
25 *
26 * Other Arduino (classic ESP32 etc.): mbedTLS (mbedtls_ecdsa_*, mbedtls_ecp_*) - hardware big-integer
27 * math and side-channel hardening, signing with the ESP32 hardware RNG. The MODMULT register layout is
28 * an S3 specialization, so non-S3 targets keep the portable mbedTLS path (no perf regression).
29 *
30 * ═══════════════════════════════════════════════════════════════════════════
31 * WIRE FORMATS (assembled by the SSH transport/auth layers, not here)
32 * ═══════════════════════════════════════════════════════════════════════════
33 *
34 * Public-key blob: string("ecdsa-sha2-nistp256") || string("nistp256") || string(Q), Q = 0x04||X||Y.
35 * Signature blob: string("ecdsa-sha2-nistp256") || string( mpint(r) || mpint(s) ); this module
36 * exposes raw r||s (32+32 big-endian) and the layers mpint-wrap them.
37 * ECDH shared secret (RFC 5656 sec 4): K = X coordinate of d*Q_peer, raw 32-byte big-endian.
38 *
39 * @author Douglas Quigg (dstroy0)
40 * @date 2026
41 */
42
44#include "crypto/hash/sha256.h"
45#include <string.h>
46
47#ifdef ARDUINO
48#include "sdkconfig.h" // CONFIG_IDF_TARGET_ESP32S3 - selects the MODMULT field layer
49#endif
50
51// The S3 field/scalar layer drives the RSA peripheral through mbedTLS's port (esp_mpi_*), which only
52// exists in the on-device toolchain and whose MODMULT register map is an S3 specialization.
53#if defined(ARDUINO) && defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_IDF_TARGET_ESP32S3
54#define PC_ECDSA_MPI_HW 1
55#endif
56
57// Platform-conditional headers, hoisted here so no #include follows code (no mid-file includes -
58// ci_tooling/check/check_src_banned.py enforces it). The implementation branches below use the same guards.
59#if defined(ARDUINO) && !defined(PC_ECDSA_MPI_HW)
60#include <esp_random.h> // esp_fill_random() for the ECDSA nonce / blinding RNG
61#include <mbedtls/ecdh.h>
62#include <mbedtls/ecdsa.h>
63#include <mbedtls/ecp.h>
64#else
65#include "crypto/mac/hmac_sha256.h" // RFC 6979 HMAC-DRBG for the deterministic-nonce complete-formula path
66#ifdef PC_ECDSA_MPI_HW
67#include "board_drivers/hal/esp/esp_crypto_hal.h" // pc_rsa_modmul + pc_rsa_hw_acquire/release (RSA-accelerator HAL)
68#endif
69#endif
70
71// Measured (crypto bench): the S3 P-256 MODMULT path's ~14% -O3 win is carried by -fpeel-loops (bisected
72// on-device); pin just that transform on the -O2 floor. Non-S3 dies run ecdsa on mbedtls/HW (flat at any level)
73// and take the crypto_opt per-die default (P4 -O3, else -O2). sdkconfig is included above.
74#include "crypto/crypto_opt.h"
75#if defined(CONFIG_IDF_TARGET_ESP32S3) && CONFIG_IDF_TARGET_ESP32S3
77#else
79#endif
80
81// ---------------------------------------------------------------------------
82// Other Arduino (non-S3) - mbedTLS path (portable, hardware-accelerated)
83// ---------------------------------------------------------------------------
84
85#if defined(ARDUINO) && !defined(PC_ECDSA_MPI_HW)
86
87namespace
88{
89// RNG callback backed by the ESP32 hardware RNG.
90int ecdsa_rng(void *ctx, unsigned char *buf, size_t len)
91{
92 (void)ctx;
93 esp_fill_random(buf, len);
94 return 0;
95}
96} // namespace
97
99{
100 mbedtls_ecp_group grp;
101 mbedtls_ecp_point Q;
102 mbedtls_mpi d;
103 mbedtls_ecp_group_init(&grp);
104 mbedtls_ecp_point_init(&Q);
105 mbedtls_mpi_init(&d);
106
107 bool ok = false;
108 if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) == 0 &&
109 mbedtls_mpi_read_binary(&d, priv, PC_ECDSA_P256_PRIV_LEN) == 0 &&
110 mbedtls_ecp_mul(&grp, &Q, &d, &grp.G, ecdsa_rng, nullptr) == 0)
111 {
112 size_t olen = 0;
113 if (mbedtls_ecp_point_write_binary(&grp, &Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, pub, PC_ECDSA_P256_PUB_LEN) ==
114 0 &&
115 olen == PC_ECDSA_P256_PUB_LEN)
116 {
117 ok = true;
118 }
119 }
120
121 mbedtls_mpi_free(&d);
122 mbedtls_ecp_point_free(&Q);
123 mbedtls_ecp_group_free(&grp);
124 return ok;
125}
126
127bool pc_ecdsa_p256_sign(uint8_t sig[PC_ECDSA_P256_SIG_LEN], const uint8_t *msg, size_t mlen,
128 const uint8_t priv[PC_ECDSA_P256_PRIV_LEN])
129{
130 uint8_t h[PC_SHA256_DIGEST_LEN];
131 pc_sha256(msg, mlen, h);
132
133 mbedtls_ecp_group grp;
134 mbedtls_mpi d;
135 mbedtls_mpi r;
136 mbedtls_mpi s;
137 mbedtls_ecp_group_init(&grp);
138 mbedtls_mpi_init(&d);
139 mbedtls_mpi_init(&r);
140 mbedtls_mpi_init(&s);
141
142 bool ok = false;
143 if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) == 0 &&
144 mbedtls_mpi_read_binary(&d, priv, PC_ECDSA_P256_PRIV_LEN) == 0 &&
145 mbedtls_ecdsa_sign(&grp, &r, &s, &d, h, PC_SHA256_DIGEST_LEN, ecdsa_rng, nullptr) == 0 &&
146 mbedtls_mpi_write_binary(&r, sig, PC_ECDSA_P256_COORD_LEN) == 0 &&
147 mbedtls_mpi_write_binary(&s, sig + PC_ECDSA_P256_COORD_LEN, PC_ECDSA_P256_COORD_LEN) == 0)
148 {
149 ok = true;
150 }
151
152 mbedtls_mpi_free(&s);
153 mbedtls_mpi_free(&r);
154 mbedtls_mpi_free(&d);
155 mbedtls_ecp_group_free(&grp);
156 return ok;
157}
158
159bool pc_ecdsa_p256_verify(const uint8_t pub[PC_ECDSA_P256_PUB_LEN], const uint8_t *msg, size_t mlen,
160 const uint8_t sig[PC_ECDSA_P256_SIG_LEN])
161{
162 uint8_t h[PC_SHA256_DIGEST_LEN];
163 pc_sha256(msg, mlen, h);
164
165 mbedtls_ecp_group grp;
166 mbedtls_ecp_point Q;
167 mbedtls_mpi r;
168 mbedtls_mpi s;
169 mbedtls_ecp_group_init(&grp);
170 mbedtls_ecp_point_init(&Q);
171 mbedtls_mpi_init(&r);
172 mbedtls_mpi_init(&s);
173
174 bool ok = false;
175 if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) == 0 &&
176 mbedtls_ecp_point_read_binary(&grp, &Q, pub, PC_ECDSA_P256_PUB_LEN) == 0 &&
177 mbedtls_ecp_check_pubkey(&grp, &Q) == 0 && mbedtls_mpi_read_binary(&r, sig, PC_ECDSA_P256_COORD_LEN) == 0 &&
178 mbedtls_mpi_read_binary(&s, sig + PC_ECDSA_P256_COORD_LEN, PC_ECDSA_P256_COORD_LEN) == 0 &&
179 mbedtls_ecdsa_verify(&grp, h, PC_SHA256_DIGEST_LEN, &Q, &r, &s) == 0)
180 {
181 ok = true;
182 }
183
184 mbedtls_mpi_free(&s);
185 mbedtls_mpi_free(&r);
186 mbedtls_ecp_point_free(&Q);
187 mbedtls_ecp_group_free(&grp);
188 return ok;
189}
190
191bool pc_ecdsa_p256_ecdh(uint8_t shared_x[PC_ECDSA_P256_COORD_LEN], const uint8_t peer_pub[PC_ECDSA_P256_PUB_LEN],
192 const uint8_t priv[PC_ECDSA_P256_PRIV_LEN])
193{
194 mbedtls_ecp_group grp;
195 mbedtls_ecp_point Q;
196 mbedtls_mpi d;
197 mbedtls_mpi z; // shared secret = the X coordinate of d*Q
198 mbedtls_ecp_group_init(&grp);
199 mbedtls_ecp_point_init(&Q);
200 mbedtls_mpi_init(&d);
201 mbedtls_mpi_init(&z);
202
203 bool ok = false;
204 if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) == 0 &&
205 mbedtls_ecp_point_read_binary(&grp, &Q, peer_pub, PC_ECDSA_P256_PUB_LEN) == 0 &&
206 mbedtls_ecp_check_pubkey(&grp, &Q) == 0 && mbedtls_mpi_read_binary(&d, priv, PC_ECDSA_P256_PRIV_LEN) == 0 &&
207 mbedtls_ecdh_compute_shared(&grp, &z, &Q, &d, ecdsa_rng, nullptr) == 0 &&
208 mbedtls_mpi_write_binary(&z, shared_x, PC_ECDSA_P256_COORD_LEN) == 0)
209 {
210 ok = true;
211 }
212
213 mbedtls_mpi_free(&z);
214 mbedtls_mpi_free(&d);
215 mbedtls_ecp_point_free(&Q);
216 mbedtls_ecp_group_free(&grp);
217 return ok;
218}
219
220#else // ---- S3 HW-MODMULT path, or native software path (shared complete-formula P-256) ----
221
222namespace
223{
224// ---- 256-bit little-endian field / scalar arithmetic ----
225// Values are eight uint32 limbs (limb 0 least significant), held canonical (< the domain modulus).
226
227// P-256 domain parameters (little-endian words).
228const uint32_t P256_P[8] = {0xffffffffu, 0xffffffffu, 0xffffffffu, 0x00000000u,
229 0x00000000u, 0x00000000u, 0x00000001u, 0xffffffffu};
230const uint32_t P256_N[8] = {0xfc632551u, 0xf3b9cac2u, 0xa7179e84u, 0xbce6faadu,
231 0xffffffffu, 0xffffffffu, 0x00000000u, 0xffffffffu};
232const uint32_t P256_B[8] = {0x27d2604bu, 0x3bce3c3eu, 0xcc53b0f6u, 0x651d06b0u,
233 0x769886bcu, 0xb3ebbd55u, 0xaa3a93e7u, 0x5ac635d8u};
234const uint32_t P256_B3[8] = {0x777720e2u, 0xb36ab4bau, 0x64fb12e2u, 0x2f571411u,
235 0x63c99435u, 0x1bc33800u, 0xfeafbbb6u, 0x1052a18au}; // 3b mod p
236
237// R = 2^256. Montgomery constants for the MODMULT (m' = -M^-1 mod 2^32, R^2 mod M); scratchpad/p256_verify.py.
238const uint32_t P256_P_R2[8] = {0x00000003u, 0x00000000u, 0xffffffffu, 0xfffffffbu,
239 0xfffffffeu, 0xffffffffu, 0xfffffffdu, 0x00000004u};
240const uint32_t P256_N_R2[8] = {0xbe79eea2u, 0x83244c95u, 0x49bd6fa6u, 0x4699799cu,
241 0x2b6bec59u, 0x2845b239u, 0xf3d95620u, 0x66e12d94u};
242
243// A field/scalar domain: its modulus and (S3) the two MODMULT constants.
244struct Fp
245{
246 const uint32_t *m;
247 uint32_t mprime;
248 const uint32_t *r2;
249};
250const Fp FP = {P256_P, 0x00000001u, P256_P_R2}; // field domain (mod p): m' = 1 since p ends in 0xffffffff
251const Fp FN = {P256_N, 0xee00bc4fu, P256_N_R2}; // scalar domain (mod n)
252
253void fp_set(uint32_t r[8], const uint32_t a[8])
254{
255 for (int i = 0; i < 8; i++)
256 {
257 r[i] = a[i];
258 }
259}
260void fp_zero(uint32_t r[8])
261{
262 for (int i = 0; i < 8; i++)
263 {
264 r[i] = 0;
265 }
266}
267bool fp_is_zero(const uint32_t a[8])
268{
269 uint32_t x = 0;
270 for (int i = 0; i < 8; i++)
271 {
272 x |= a[i];
273 }
274 return x == 0;
275}
276// -1 if a<b, 0 if a==b, 1 if a>b.
277int fp_cmp(const uint32_t a[8], const uint32_t b[8])
278{
279 for (int i = 7; i >= 0; i--)
280 {
281 if (a[i] != b[i])
282 {
283 return a[i] > b[i] ? 1 : -1;
284 }
285 }
286 return 0;
287}
288// r = a - b (mod 2^256); returns borrow.
289uint32_t sub_raw(uint32_t r[8], const uint32_t a[8], const uint32_t b[8])
290{
291 uint64_t brw = 0;
292 for (int i = 0; i < 8; i++)
293 {
294 uint64_t t = (uint64_t)a[i] - b[i] - brw;
295 r[i] = (uint32_t)t;
296 brw = (t >> 32) & 1u;
297 }
298 return (uint32_t)brw;
299}
300// r = a + b (mod m); a,b < m -> one conditional subtract of m. Constant-time.
301void fp_add(uint32_t r[8], const uint32_t a[8], const uint32_t b[8], const Fp *F)
302{
303 uint32_t s[8];
304 uint64_t c = 0;
305 for (int i = 0; i < 8; i++)
306 {
307 c += (uint64_t)a[i] + b[i];
308 s[i] = (uint32_t)c;
309 c >>= 32;
310 }
311 uint32_t carry = (uint32_t)c; // a+b may be a 257-bit value
312 uint32_t t[8];
313 uint64_t b2 = 0;
314 for (int i = 0; i < 8; i++)
315 {
316 uint64_t v = (uint64_t)s[i] - F->m[i] - b2;
317 t[i] = (uint32_t)v;
318 b2 = (v >> 32) & 1u;
319 }
320 // keep s if (s - m) borrowed and there was no carry out of the add; else take s - m.
321 uint32_t take_t = carry | (uint32_t)(1u - b2); // 1 -> s>=m, use t
322 uint32_t mask = (uint32_t)(-(int32_t)take_t);
323 for (int i = 0; i < 8; i++)
324 {
325 r[i] = (t[i] & mask) | (s[i] & ~mask);
326 }
327}
328// r = a - b (mod m); a,b < m -> conditional add of m on borrow. Constant-time.
329void fp_sub(uint32_t r[8], const uint32_t a[8], const uint32_t b[8], const Fp *F)
330{
331 uint32_t t[8];
332 uint32_t borrow = sub_raw(t, a, b); // 1 if a < b
333 uint32_t mask = (uint32_t)(-(int32_t)borrow);
334 uint64_t c = 0;
335 for (int i = 0; i < 8; i++)
336 {
337 c += (uint64_t)t[i] + (F->m[i] & mask);
338 r[i] = (uint32_t)c;
339 c >>= 32;
340 }
341}
342// Reduce a single value a in [0, 2m) into [0, m): subtract m once if a >= m. Constant-time.
343void fp_reduce_once(uint32_t r[8], const uint32_t a[8], const uint32_t m[8])
344{
345 uint32_t t[8];
346 uint32_t borrow = sub_raw(t, a, m); // 1 -> a < m, keep a
347 uint32_t mask = (uint32_t)(-(int32_t)borrow);
348 for (int i = 0; i < 8; i++)
349 {
350 r[i] = (a[i] & mask) | (t[i] & ~mask);
351 }
352}
353
354#ifdef PC_ECDSA_MPI_HW
355// z = x*y mod F->m on the S3 RSA accelerator. Requires ecdsa_hw_on() first. Preloading R^2 into the result
356// block makes MODMULT return the plain residue (the esp_mpi_mul_mpi_mod convention). Output canonical (< m).
357void fp_mul(uint32_t z[8], const uint32_t x[8], const uint32_t y[8], const Fp *F) // safe if z aliases x/y
358{
359 // Same 256-bit RSA MODMULT the 25519 field layer uses, parameterized by the domain (mod p or mod n): the
360 // HAL owns the register access; here we only pass this domain's {m, m', R^2 mod m}. Output canonical (< m).
361 pc_rsa_modmul(z, x, y, F->m, F->mprime, F->r2, 8);
362}
363void ecdsa_hw_on()
364{
365 pc_rsa_hw_acquire();
366}
367void ecdsa_hw_off()
368{
369 pc_rsa_hw_release();
370}
371#else
372// acc[0..7] >= m[0..7]? Compares the low 8 limbs from the most significant down.
373// Both P256_P and P256_N are prime, and every operand ever passed into fp_mul (hence into this
374// bit-serial reduction) is invariant-bound strictly below its own modulus before use (see every
375// call site: d/k/r/s/e/qx/qy are all range-checked or reduced before reaching a multiply). So the
376// integer product of two such operands can never equal the modulus exactly - that would need one
377// factor to be exactly 1 and the other exactly the modulus, and the modulus itself is never a valid
378// operand anywhere in this file. A full 8-limb "acc == m" coincidence at any bit-serial reduction
379// step, or even a partial (single-limb) coincidence while the loop is still scanning, was never
380// observed across this suite's 590M+ calls to this function. Not exercised by any host-reachable input.
381bool reduce_low8_ge(const uint32_t acc[8], const uint32_t m[8])
382{
383 for (int k = 7; k >= 0; k--) // GCOVR_EXCL_BR_LINE see comment above
384 {
385 if (acc[k] != m[k]) // GCOVR_EXCL_BR_LINE see comment above
386 {
387 return acc[k] > m[k];
388 }
389 }
390 return true; // GCOVR_EXCL_LINE all limbs equal - unreachable, see comment above
391}
392// Reduce a 512-bit product mod m (bit-serial, MSB to LSB). Correct but slow; the native path is test-only.
393void reduce_mod(uint32_t r[8], const uint32_t prod[16], const uint32_t m[8])
394{
395 uint32_t acc[9];
396 for (int k = 0; k < 9; k++)
397 {
398 acc[k] = 0;
399 }
400 for (int bit = 511; bit >= 0; bit--)
401 {
402 uint32_t carry = 0;
403 // GCOVR_EXCL_START this innermost loop runs (bits-of-product * 9) times per fp_mul call, and
404 // this suite's existing native tests alone drive it past 7e9 hits; gcovr's sonarqube exporter
405 // treats any hit count >= 2^32 as "suspicious" (see --gcov-suspicious-hits-threshold, default
406 // 2^32) and zeroes it, even though raw `gcov -b -c` on this file's .gcda reports 100% line and
407 // branch execution here. This is a coverage-tool counter-threshold artifact, not an actual gap.
408 for (int k = 0; k < 9; k++)
409 {
410 uint32_t nc = acc[k] >> 31;
411 acc[k] = (acc[k] << 1) | carry;
412 carry = nc;
413 }
414 // GCOVR_EXCL_STOP
415 acc[0] |= (prod[bit >> 5] >> (bit & 31)) & 1u;
416 bool ge = acc[8] != 0;
417 if (!ge)
418 {
419 ge = reduce_low8_ge(acc, m);
420 }
421 if (ge)
422 {
423 uint64_t brw = 0;
424 for (int k = 0; k < 8; k++)
425 {
426 uint64_t t = (uint64_t)acc[k] - m[k] - brw;
427 acc[k] = (uint32_t)t;
428 brw = (t >> 32) & 1u;
429 }
430 acc[8] -= (uint32_t)brw;
431 }
432 }
433 for (int k = 0; k < 8; k++)
434 {
435 r[k] = acc[k];
436 }
437}
438// z = (x * y) mod F->m (software schoolbook + reduction).
439void fp_mul(uint32_t z[8], const uint32_t x[8], const uint32_t y[8], const Fp *F)
440{
441 uint32_t prod[16];
442 for (int k = 0; k < 16; k++)
443 {
444 prod[k] = 0;
445 }
446 for (int i = 0; i < 8; i++)
447 {
448 uint64_t carry = 0;
449 for (int j = 0; j < 8; j++)
450 {
451 uint64_t t = (uint64_t)prod[i + j] + (uint64_t)x[i] * y[j] + carry;
452 prod[i + j] = (uint32_t)t;
453 carry = t >> 32;
454 }
455 int k = i + 8;
456 while (carry)
457 {
458 uint64_t t = (uint64_t)prod[k] + carry;
459 prod[k] = (uint32_t)t;
460 carry = t >> 32;
461 k++;
462 }
463 }
464 reduce_mod(z, prod, F->m);
465}
466void ecdsa_hw_on()
467{
468 // Software big-integer path: there is no hardware MPI accelerator to enable on this build (the HW
469 // variant of these hooks is compiled instead when the accelerator is present).
470}
471void ecdsa_hw_off()
472{
473 // Counterpart to ecdsa_hw_on(): nothing to disable on the software path.
474}
475#endif
476
477void fp_sqr(uint32_t r[8], const uint32_t a[8], const Fp *F)
478{
479 fp_mul(r, a, a, F);
480}
481// r = a*x mod p where the curve a = p - 3, i.e. r = -3x. Two adds + a negate instead of a MODMULT.
482// Alias-safe (r may be x). Only the field domain has this a, so it is hard-wired to FP.
483void fp_mul_by_a(uint32_t r[8], const uint32_t x[8])
484{
485 uint32_t tx[8];
486 fp_add(tx, x, x, &FP);
487 fp_add(tx, tx, x, &FP); // 3x
488 static const uint32_t zero[8] = {0, 0, 0, 0, 0, 0, 0, 0};
489 fp_sub(r, zero, tx, &FP); // -3x
490}
491// r = a^(m-2) mod m (Fermat inverse; m prime). Fixed public exponent -> constant-time in a.
492void fp_inv(uint32_t r[8], const uint32_t a[8], const Fp *F)
493{
494 static const uint32_t two[8] = {2, 0, 0, 0, 0, 0, 0, 0};
495 uint32_t e[8];
496 sub_raw(e, F->m, two); // e = m - 2
497 uint32_t res[8] = {1, 0, 0, 0, 0, 0, 0, 0};
498 uint32_t base[8];
499 fp_set(base, a);
500 for (int i = 0; i < 256; i++)
501 {
502 if ((e[i >> 5] >> (i & 31)) & 1u)
503 {
504 fp_mul(res, res, base, F);
505 }
506 fp_mul(base, base, base, F);
507 }
508 fp_set(r, res);
509}
510
511void load_be(uint32_t r[8], const uint8_t b[32])
512{
513 for (int i = 0; i < 8; i++)
514 {
515 const uint8_t *p = b + (28 - 4 * i);
516 r[i] = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
517 }
518}
519void store_be(uint8_t b[32], const uint32_t r[8])
520{
521 for (int i = 0; i < 8; i++)
522 {
523 uint8_t *p = b + (28 - 4 * i);
524 p[0] = (uint8_t)(r[i] >> 24);
525 p[1] = (uint8_t)(r[i] >> 16);
526 p[2] = (uint8_t)(r[i] >> 8);
527 p[3] = (uint8_t)r[i];
528 }
529}
530
531// ---- Point arithmetic: complete formulas on y^2 = x^3 - 3x + b, projective (X:Y:Z), x=X/Z, y=Y/Z ----
532// Exception-free for all inputs on the prime-order curve (RCB 2016), so the constant-time ladder needs no
533// special cases. Identity is (0:1:0). Every field op is mod p (FP).
534
535struct Pt
536{
537 uint32_t X[8];
538 uint32_t Y[8];
539 uint32_t Z[8];
540};
541
542// Base point G (affine, Z = 1).
543const Pt P256_G = {
544 {0xd898c296u, 0xf4a13945u, 0x2deb33a0u, 0x77037d81u, 0x63a440f2u, 0xf8bce6e5u, 0xe12c4247u, 0x6b17d1f2u},
545 {0x37bf51f5u, 0xcbb64068u, 0x6b315eceu, 0x2bce3357u, 0x7c0f9e16u, 0x8ee7eb4au, 0xfe1a7f9bu, 0x4fe342e2u},
546 {1u, 0, 0, 0, 0, 0, 0, 0}};
547
548bool pt_is_infinity(const Pt *p)
549{
550 return fp_is_zero(p->Z);
551}
552void pt_set_infinity(Pt *p)
553{
554 fp_zero(p->X);
555 fp_zero(p->Y);
556 p->Y[0] = 1;
557 fp_zero(p->Z);
558}
559void pt_from_affine(Pt *p, const uint32_t x[8], const uint32_t y[8])
560{
561 fp_set(p->X, x);
562 fp_set(p->Y, y);
563 fp_zero(p->Z);
564 p->Z[0] = 1;
565}
566// (x, y) = (X/Z, Y/Z). Caller ensures p is not the identity.
567void pt_to_affine(uint32_t x[8], uint32_t y[8], const Pt *p)
568{
569 uint32_t zi[8];
570 fp_inv(zi, p->Z, &FP);
571 fp_mul(x, p->X, zi, &FP);
572 fp_mul(y, p->Y, zi, &FP);
573}
574
575// r = a + b (EFD add-2015-rcb, a = -3). Alias-safe: all reads land in locals before *r is written.
576void pt_add(Pt *r, const Pt *a, const Pt *b)
577{
578 uint32_t t0[8];
579 uint32_t t1[8];
580 uint32_t t2[8];
581 uint32_t t3[8];
582 uint32_t t4[8];
583 uint32_t t5[8];
584 uint32_t x3[8];
585 uint32_t y3[8];
586 uint32_t z3[8];
587 fp_mul(t0, a->X, b->X, &FP);
588 fp_mul(t1, a->Y, b->Y, &FP);
589 fp_mul(t2, a->Z, b->Z, &FP);
590 fp_add(t3, a->X, a->Y, &FP);
591 fp_add(t4, b->X, b->Y, &FP);
592 fp_mul(t3, t3, t4, &FP);
593 fp_add(t4, t0, t1, &FP);
594 fp_sub(t3, t3, t4, &FP);
595 fp_add(t4, a->X, a->Z, &FP);
596 fp_add(t5, b->X, b->Z, &FP);
597 fp_mul(t4, t4, t5, &FP);
598 fp_add(t5, t0, t2, &FP);
599 fp_sub(t4, t4, t5, &FP);
600 fp_add(t5, a->Y, a->Z, &FP);
601 fp_add(x3, b->Y, b->Z, &FP);
602 fp_mul(t5, t5, x3, &FP);
603 fp_add(x3, t1, t2, &FP);
604 fp_sub(t5, t5, x3, &FP);
605 fp_mul_by_a(z3, t4);
606 fp_mul(x3, P256_B3, t2, &FP);
607 fp_add(z3, x3, z3, &FP);
608 fp_sub(x3, t1, z3, &FP);
609 fp_add(z3, t1, z3, &FP);
610 fp_mul(y3, x3, z3, &FP);
611 fp_add(t1, t0, t0, &FP);
612 fp_add(t1, t1, t0, &FP);
613 fp_mul_by_a(t2, t2);
614 fp_mul(t4, P256_B3, t4, &FP);
615 fp_add(t1, t1, t2, &FP);
616 fp_sub(t2, t0, t2, &FP);
617 fp_mul_by_a(t2, t2);
618 fp_add(t4, t4, t2, &FP);
619 fp_mul(t0, t1, t4, &FP);
620 fp_add(y3, y3, t0, &FP);
621 fp_mul(t0, t5, t4, &FP);
622 fp_mul(x3, t3, x3, &FP);
623 fp_sub(x3, x3, t0, &FP);
624 fp_mul(t0, t3, t1, &FP);
625 fp_mul(z3, t5, z3, &FP);
626 fp_add(z3, z3, t0, &FP);
627 fp_set(r->X, x3);
628 fp_set(r->Y, y3);
629 fp_set(r->Z, z3);
630}
631
632// r = 2*a (EFD dbl-2015-rcb, a = -3). Alias-safe.
633void pt_dbl(Pt *r, const Pt *a)
634{
635 uint32_t t0[8];
636 uint32_t t1[8];
637 uint32_t t2[8];
638 uint32_t t3[8];
639 uint32_t x3[8];
640 uint32_t y3[8];
641 uint32_t z3[8];
642 fp_sqr(t0, a->X, &FP);
643 fp_sqr(t1, a->Y, &FP);
644 fp_sqr(t2, a->Z, &FP);
645 fp_mul(t3, a->X, a->Y, &FP);
646 fp_add(t3, t3, t3, &FP);
647 fp_mul(z3, a->X, a->Z, &FP);
648 fp_add(z3, z3, z3, &FP);
649 fp_mul_by_a(x3, z3);
650 fp_mul(y3, P256_B3, t2, &FP);
651 fp_add(y3, x3, y3, &FP);
652 fp_sub(x3, t1, y3, &FP);
653 fp_add(y3, t1, y3, &FP);
654 fp_mul(y3, x3, y3, &FP);
655 fp_mul(x3, t3, x3, &FP);
656 fp_mul(z3, P256_B3, z3, &FP);
657 fp_mul_by_a(t2, t2);
658 fp_sub(t3, t0, t2, &FP);
659 fp_mul_by_a(t3, t3);
660 fp_add(t3, t3, z3, &FP);
661 fp_add(z3, t0, t0, &FP);
662 fp_add(t0, z3, t0, &FP);
663 fp_add(t0, t0, t2, &FP);
664 fp_mul(t0, t0, t3, &FP);
665 fp_add(y3, y3, t0, &FP);
666 fp_mul(t2, a->Y, a->Z, &FP);
667 fp_add(t2, t2, t2, &FP);
668 fp_mul(t0, t2, t3, &FP);
669 fp_sub(x3, x3, t0, &FP);
670 fp_mul(z3, t2, t1, &FP);
671 fp_add(z3, z3, z3, &FP);
672 fp_add(z3, z3, z3, &FP);
673 fp_set(r->X, x3);
674 fp_set(r->Y, y3);
675 fp_set(r->Z, z3);
676}
677
678// dst = table[idx], scanning all 16 entries so the access pattern is independent of the (secret) idx.
679void pt_table_select(Pt *dst, const Pt table[16], uint32_t idx)
680{
681 fp_zero(dst->X);
682 fp_zero(dst->Y);
683 fp_zero(dst->Z);
684 for (uint32_t e = 0; e < 16; e++)
685 {
686 uint32_t x = e ^ idx;
687 uint32_t nz = (x | (0u - x)) >> 31; // 1 if e != idx, else 0
688 uint32_t mask = (uint32_t)(nz - 1u); // 0xffffffff if e == idx, else 0
689 for (int i = 0; i < 8; i++)
690 {
691 dst->X[i] |= table[e].X[i] & mask;
692 dst->Y[i] |= table[e].Y[i] & mask;
693 dst->Z[i] |= table[e].Z[i] & mask;
694 }
695 }
696}
697
698// r = k * p, k a 256-bit little-endian scalar. Constant-time 4-bit fixed window: a uniform op sequence
699// (256 doublings + 64 additions + a data-independent table build) with the only secret-dependent step a
700// full-table masked select - no input-dependent branches, and the complete formulas are exception-free.
701// The 16-entry table (~1.5 KB) is on the stack, live only in this shallow phase (well under the SSH KEX
702// peak), so it is reentrant across worker tasks. Used for secret scalars.
703void pt_scalarmul(Pt *r, const uint32_t k[8], const Pt *p)
704{
705 Pt table[16]; // table[i] = i * p; table[0] = identity
706 pt_set_infinity(&table[0]);
707 table[1] = *p;
708 for (int i = 2; i < 16; i++)
709 {
710 if (i & 1)
711 {
712 pt_add(&table[i], &table[i - 1], p);
713 }
714 else
715 {
716 pt_dbl(&table[i], &table[i / 2]);
717 }
718 }
719 Pt acc;
720 pt_set_infinity(&acc);
721 for (int w = 63; w >= 0; w--) // 64 nibbles, most significant first (Horner)
722 {
723 pt_dbl(&acc, &acc);
724 pt_dbl(&acc, &acc);
725 pt_dbl(&acc, &acc);
726 pt_dbl(&acc, &acc); // acc *= 16
727 uint32_t idx = (k[w >> 3] >> ((w & 7) * 4)) & 0xfu;
728 Pt sel;
729 pt_table_select(&sel, table, idx);
730 pt_add(&acc, &acc, &sel);
731 }
732 *r = acc;
733}
734
735// Check (x, y) is on y^2 = x^3 - 3x + b (mod p) and both coordinates are in range. Requires ecdsa_hw_on().
736bool on_curve(const uint32_t x[8], const uint32_t y[8])
737{
738 if (fp_cmp(x, P256_P) >= 0 || fp_cmp(y, P256_P) >= 0)
739 {
740 return false;
741 }
742 uint32_t lhs[8];
743 uint32_t rhs[8];
744 uint32_t t[8];
745 fp_sqr(lhs, y, &FP); // y^2
746 fp_sqr(rhs, x, &FP);
747 fp_mul(rhs, rhs, x, &FP); // x^3
748 fp_add(t, x, x, &FP);
749 fp_add(t, t, x, &FP); // 3x
750 fp_sub(rhs, rhs, t, &FP); // x^3 - 3x
751 fp_add(rhs, rhs, P256_B, &FP);
752 return fp_cmp(lhs, rhs) == 0;
753}
754
755// ---- RFC 6979 deterministic nonce (HMAC-SHA256 DRBG, hlen = qlen = 256) ----
756
757// out = HMAC-SHA256(key, V || (tag>=0 ? tag||x||e : nothing)).
758void pc_hmac_cat(uint8_t out[32], const uint8_t key[32], const uint8_t *v, size_t vlen, const int tag, const uint8_t *x,
759 const uint8_t *e)
760{
761 uint8_t buf[97]; // 32 (V) + 1 (tag) + 32 (x) + 32 (e)
762 size_t n = 0;
763 memcpy(buf + n, v, vlen);
764 n += vlen;
765 if (tag >= 0)
766 {
767 buf[n++] = (uint8_t)tag;
768 memcpy(buf + n, x, 32);
769 n += 32;
770 memcpy(buf + n, e, 32);
771 n += 32;
772 }
773 pc_hmac_sha256(key, 32, buf, n, out);
774}
775
776// One RFC 6979 candidate k: if it yields a valid r and s, write the 64-byte signature and return true.
777//
778// The three guards below (k invalid, r == 0, s == 0) are RFC 6979's mandated rejection-sampling safety
779// net (RFC 6979 section 3.2 step h.3 / SEC1 section 4.1.3): each rejects a candidate with probability
780// roughly 2^-32 (k landing in [n, 2^256) - n is only ~2^224 short of 2^256) or roughly 2^-256 (r or s
781// landing on exactly 0). Every value guarded here is HMAC-SHA256 output (bits2int(V), or derived from
782// it through the field/scalar arithmetic) - a cryptographic PRF with no host-reachable way to steer its
783// output to one specific rare value short of an infeasible (2^32+ evaluation) brute-force search. None
784// of these guards tripped once across this suite's full run (hundreds of scalar multiplies over dozens
785// of distinct keys/messages), so the retry path in ecdsa_sign_core is dead for the same reason.
786bool ecdsa_try_sign(const uint32_t k[8], const uint32_t d[8], const uint32_t e[8], uint8_t sig[64])
787{
788 if (fp_is_zero(k) || // GCOVR_EXCL_BR_LINE runs every call; only the k==0 trip is excluded, see comment above
789 fp_cmp(k, P256_N) >= 0) // GCOVR_EXCL_BR_LINE runs every call; only the trip is excluded, see comment above
790 {
791 return false; // GCOVR_EXCL_LINE see comment above
792 }
793 Pt R;
794 pt_scalarmul(&R, k, &P256_G);
795 // k is already range-checked to [1, n-1] above, and P-256 has cofactor 1 (its group order is the
796 // prime n exactly), so every non-identity point - including G - has order exactly n: k*G can only be
797 // the identity if n divides k, which is impossible for k in [1, n-1].
798 if (pt_is_infinity(&R)) // GCOVR_EXCL_BR_LINE k in [1,n-1] and G has order n (cofactor 1) - never O
799 {
800 return false; // GCOVR_EXCL_LINE unreachable, see comment above
801 }
802 uint32_t rx[8];
803 uint32_t ry[8];
804 pt_to_affine(rx, ry, &R);
805 uint32_t r[8];
806 fp_reduce_once(r, rx, P256_N); // r = Rx mod n (Rx < p < 2n -> one subtract)
807 if (fp_is_zero(r)) // GCOVR_EXCL_BR_LINE runs every call; only r==0 is excluded, see comment above ecdsa_try_sign
808 {
809 return false; // GCOVR_EXCL_LINE see comment above
810 }
811 uint32_t kinv[8];
812 uint32_t s[8];
813 fp_inv(kinv, k, &FN);
814 fp_mul(s, r, d, &FN); // r*d
815 fp_add(s, s, e, &FN); // e + r*d
816 fp_mul(s, kinv, s, &FN); // k^-1 (e + r*d)
817 if (fp_is_zero(s)) // GCOVR_EXCL_BR_LINE runs every call; only s==0 is excluded, see comment above ecdsa_try_sign
818 {
819 return false; // GCOVR_EXCL_LINE see comment above
820 }
821 store_be(sig, r);
822 store_be(sig + 32, s);
823 return true;
824}
825
826// ECDSA core: sign hash h1 (32) with scalar d, deterministic k per RFC 6979. Requires ecdsa_hw_on().
827bool ecdsa_sign_core(uint8_t sig[64], const uint8_t h1[32], const uint32_t d[8])
828{
829 uint32_t e[8];
830 uint32_t etmp[8];
831 load_be(etmp, h1);
832 fp_reduce_once(e, etmp, P256_N); // bits2int(h1) mod n
833
834 uint8_t x_oct[32];
835 uint8_t h_oct[32];
836 store_be(x_oct, d);
837 store_be(h_oct, e); // bits2octets(h1)
838
839 uint8_t V[32];
840 uint8_t K[32];
841 memset(V, 0x01, 32);
842 memset(K, 0x00, 32);
843 pc_hmac_cat(K, K, V, 32, 0x00, x_oct, h_oct);
844 pc_hmac_cat(V, K, V, 32, -1, nullptr, nullptr);
845 pc_hmac_cat(K, K, V, 32, 0x01, x_oct, h_oct);
846 pc_hmac_cat(V, K, V, 32, -1, nullptr, nullptr);
847
848 for (int guard = 0; guard < 64; guard++) // GCOVR_EXCL_BR_LINE ecdsa_try_sign never rejects in
849 // practice (see its comment), so the first candidate
850 // always succeeds and the loop never reaches guard==64
851 {
852 pc_hmac_cat(V, K, V, 32, -1, nullptr, nullptr); // T = HMAC_K(V), one block
853 uint32_t k[8];
854 load_be(k, V); // bits2int(T)
855 if (ecdsa_try_sign(k, d, e, sig)) // GCOVR_EXCL_BR_LINE always true, see comment above
856 {
857 return true;
858 }
859 // GCOVR_EXCL_START retry path (RFC 6979 step h.3): only reached if ecdsa_try_sign rejected a
860 // candidate, which does not happen in practice - see the comment above ecdsa_try_sign.
861 uint8_t buf[33]; // retry: K = HMAC_K(V || 0x00); V = HMAC_K(V)
862 memcpy(buf, V, 32);
863 buf[32] = 0x00;
864 pc_hmac_sha256(K, 32, buf, 33, K);
865 pc_hmac_cat(V, K, V, 32, -1, nullptr, nullptr);
866 // GCOVR_EXCL_STOP
867 }
868 return false; // GCOVR_EXCL_LINE unreachable: the guard never exhausts 64 tries, see comment above
869}
870
871} // namespace
872
873bool pc_ecdsa_p256_pubkey(uint8_t pub[PC_ECDSA_P256_PUB_LEN], const uint8_t priv[PC_ECDSA_P256_PRIV_LEN])
874{
875 uint32_t d[8];
876 load_be(d, priv);
877 if (fp_is_zero(d) || fp_cmp(d, P256_N) >= 0)
878 {
879 return false;
880 }
881
882 ecdsa_hw_on();
883 Pt Q;
884 pt_scalarmul(&Q, d, &P256_G);
885 bool ok = !pt_is_infinity(&Q);
886 // d is already range-checked to [1, n-1] above, and P-256 has cofactor 1 (group order exactly n), so
887 // d*G can only be the identity if n divides d - impossible here; ok is therefore always true.
888 if (ok) // GCOVR_EXCL_BR_LINE d in [1,n-1], cofactor 1 -> d*G never the identity, see comment above
889 {
890 uint32_t qx[8];
891 uint32_t qy[8];
892 pt_to_affine(qx, qy, &Q);
893 pub[0] = 0x04;
894 store_be(pub + 1, qx);
895 store_be(pub + 33, qy);
896 }
897 ecdsa_hw_off();
898 return ok;
899}
900
901bool pc_ecdsa_p256_sign(uint8_t sig[PC_ECDSA_P256_SIG_LEN], const uint8_t *msg, size_t mlen,
902 const uint8_t priv[PC_ECDSA_P256_PRIV_LEN])
903{
904 uint32_t d[8];
905 load_be(d, priv);
906 if (fp_is_zero(d) || fp_cmp(d, P256_N) >= 0)
907 {
908 return false;
909 }
910 uint8_t h1[PC_SHA256_DIGEST_LEN];
911 pc_sha256(msg, mlen, h1);
912
913 ecdsa_hw_on();
914 bool ok = ecdsa_sign_core(sig, h1, d);
915 ecdsa_hw_off();
916 return ok;
917}
918
919bool pc_ecdsa_p256_verify(const uint8_t pub[PC_ECDSA_P256_PUB_LEN], const uint8_t *msg, size_t mlen,
920 const uint8_t sig[PC_ECDSA_P256_SIG_LEN])
921{
922 if (pub[0] != 0x04)
923 {
924 return false;
925 }
926 uint32_t qx[8];
927 uint32_t qy[8];
928 load_be(qx, pub + 1);
929 load_be(qy, pub + 33);
930
931 uint32_t r[8];
932 uint32_t s[8];
933 load_be(r, sig);
934 load_be(s, sig + 32);
935 if (fp_is_zero(r) || fp_cmp(r, P256_N) >= 0 || fp_is_zero(s) || fp_cmp(s, P256_N) >= 0)
936 {
937 return false;
938 }
939
940 uint8_t h1[PC_SHA256_DIGEST_LEN];
941 pc_sha256(msg, mlen, h1);
942 uint32_t e[8];
943 uint32_t etmp[8];
944 load_be(etmp, h1);
945 fp_reduce_once(e, etmp, P256_N);
946
947 ecdsa_hw_on();
948 bool ok = on_curve(qx, qy);
949 if (ok)
950 {
951 uint32_t w[8];
952 uint32_t u1[8];
953 uint32_t u2[8];
954 fp_inv(w, s, &FN);
955 fp_mul(u1, e, w, &FN);
956 fp_mul(u2, r, w, &FN);
957
958 Pt Q;
959 Pt Rg;
960 Pt Rq;
961 Pt R;
962 pt_from_affine(&Q, qx, qy);
963 pt_scalarmul(&Rg, u1, &P256_G);
964 pt_scalarmul(&Rq, u2, &Q);
965 pt_add(&R, &Rg, &Rq);
966 if (pt_is_infinity(&R))
967 {
968 ok = false;
969 }
970 else
971 {
972 uint32_t rx[8];
973 uint32_t ry[8];
974 uint32_t rxn[8];
975 pt_to_affine(rx, ry, &R);
976 fp_reduce_once(rxn, rx, P256_N);
977 ok = fp_cmp(rxn, r) == 0;
978 }
979 }
980 ecdsa_hw_off();
981 return ok;
982}
983
984bool pc_ecdsa_p256_ecdh(uint8_t shared_x[PC_ECDSA_P256_COORD_LEN], const uint8_t peer_pub[PC_ECDSA_P256_PUB_LEN],
985 const uint8_t priv[PC_ECDSA_P256_PRIV_LEN])
986{
987 if (peer_pub[0] != 0x04)
988 {
989 return false;
990 }
991 uint32_t qx[8];
992 uint32_t qy[8];
993 load_be(qx, peer_pub + 1);
994 load_be(qy, peer_pub + 33);
995 uint32_t d[8];
996 load_be(d, priv);
997 if (fp_is_zero(d) || fp_cmp(d, P256_N) >= 0)
998 {
999 return false;
1000 }
1001
1002 ecdsa_hw_on();
1003 bool ok = on_curve(qx, qy); // rejects off-curve / out-of-range peer points
1004 if (ok)
1005 {
1006 Pt Q;
1007 Pt R;
1008 pt_from_affine(&Q, qx, qy);
1009 pt_scalarmul(&R, d, &Q);
1010 // Q already passed on_curve() above, and P-256 has cofactor 1, so every validated on-curve point
1011 // has order exactly n; d is already range-checked to [1, n-1] in this function, so d*Q can only
1012 // be the identity if n divides d - impossible here.
1013 if (pt_is_infinity(&R)) // GCOVR_EXCL_BR_LINE Q has order n (cofactor 1), d in [1,n-1], see above
1014 {
1015 ok = false; // GCOVR_EXCL_LINE unreachable, see comment above
1016 }
1017 else
1018 {
1019 uint32_t rx[8];
1020 uint32_t ry[8];
1021 pt_to_affine(rx, ry, &R);
1022 store_be(shared_x, rx); // K = X coordinate (big-endian)
1023 }
1024 }
1025 ecdsa_hw_off();
1026 return ok;
1027}
1028
1029#endif // ARDUINO path selection
Per-translation-unit optimization override for hot, pure-integer crypto.
#define PC_CRYPTO_HOT_PEEL
Definition crypto_opt.h:94
bool pc_ecdsa_p256_ecdh(uint8_t shared_x[PC_ECDSA_P256_COORD_LEN], const uint8_t peer_pub[PC_ECDSA_P256_PUB_LEN], const uint8_t priv[PC_ECDSA_P256_PRIV_LEN])
P-256 ECDH: the shared-secret X coordinate of d * Q_peer (RFC 5656 §4 / RFC 5903).
Definition ecdsa.cpp:191
bool pc_ecdsa_p256_verify(const uint8_t pub[PC_ECDSA_P256_PUB_LEN], const uint8_t *msg, size_t mlen, const uint8_t sig[PC_ECDSA_P256_SIG_LEN])
Verify a P-256 ECDSA signature (SHA-256) against an uncompressed public point.
Definition ecdsa.cpp:159
bool pc_ecdsa_p256_sign(uint8_t sig[PC_ECDSA_P256_SIG_LEN], const uint8_t *msg, size_t mlen, const uint8_t priv[PC_ECDSA_P256_PRIV_LEN])
Sign mlen bytes of msg with a P-256 private key (ECDSA, SHA-256).
Definition ecdsa.cpp:127
bool pc_ecdsa_p256_pubkey(uint8_t pub[PC_ECDSA_P256_PUB_LEN], const uint8_t priv[PC_ECDSA_P256_PRIV_LEN])
Derive the uncompressed public point Q = d*G from a P-256 private scalar.
Definition ecdsa.cpp:98
NIST P-256 primitives for SSH: ECDSA signatures and ECDH (RFC 5656 / FIPS 186-4).
#define PC_ECDSA_P256_COORD_LEN
P-256 coordinate length (one of X, Y).
Definition ecdsa.h:57
#define PC_ECDSA_P256_SIG_LEN
Raw ECDSA signature length: r || s (32 + 32, big-endian).
Definition ecdsa.h:61
#define PC_ECDSA_P256_PUB_LEN
P-256 uncompressed public point length: 0x04 || X || Y.
Definition ecdsa.h:59
#define PC_ECDSA_P256_PRIV_LEN
P-256 private key (scalar d) length.
Definition ecdsa.h:55
Single owner of the ESP32 RSA/MPI accelerator, by DIRECT register access - a self-contained HAL.
void pc_hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *data, size_t len, uint8_t mac[PC_HMAC_SHA256_LEN])
Compute HMAC-SHA2-256 over a single contiguous buffer.
HMAC-SHA2-256 (RFC 2104 + FIPS 198-1) - streaming context and one-shot API.
int ecdsa_rng(void *ctx, unsigned char *buf, size_t len)
Definition ecdsa.cpp:90
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_sha256(const uint8_t *data, size_t len, uint8_t digest[PC_SHA256_DIGEST_LEN])
One-shot SHA-256: hash len bytes of data into digest (32 bytes).
Definition sha256.cpp:58
SHA-256 (FIPS 180-4) - streaming context and one-shot API.
#define PC_SHA256_DIGEST_LEN
SHA-256 digest length in bytes.
Definition sha256.h:25