Merge pull request #650 from dgarske/c28x_ecdsa_bench

Add ECDSA and SHA-384 image-verify timings to the TI C2000 example
pull/649/head
Mattia Moffa 2026-10-28 04:07:25 +01:00 committed by GitHub
commit 321f4578a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 478 additions and 1 deletions

View File

@ -0,0 +1,42 @@
/* ecc_p384_kat.h - deterministic ECDSA P-384 (SECP384R1) known-answer vector.
*
* Source: RFC 6979 Appendix A.2.6 (P-384), message "sample", hash SHA-384.
* The same vector wolfCrypt's own ecc384_test_deterministic_k() uses. The
* digest is not stored: the device hashes "sample" with SHA-384 itself, so the
* KAT covers the hash-to-verify chain rather than just the curve math.
*
* Brace-init byte arrays (NOT "\x.." string literals): on cl2000 a char[]/byte[]
* initialized from a "\x.." string sign-extends bytes >= 0x80 into the 16-bit
* cell; a brace-init list stores clean octets.
*/
#ifndef ECC_P384_KAT_H
#define ECC_P384_KAT_H
/* Public key Q = (Ux, Uy), 48 bytes each, big-endian. */
static const unsigned char kat_p384_qx[48] = {
0xEC,0x3A,0x4E,0x41,0x5B,0x4E,0x19,0xA4,0x56,0x86,0x18,0x02,0x9F,0x42,0x7F,0xA5,
0xDA,0x9A,0x8B,0xC4,0xAE,0x92,0xE0,0x2E,0x06,0xAA,0xE5,0x28,0x6B,0x30,0x0C,0x64,
0xDE,0xF8,0xF0,0xEA,0x90,0x55,0x86,0x60,0x64,0xA2,0x54,0x51,0x54,0x80,0xBC,0x13
};
static const unsigned char kat_p384_qy[48] = {
0x80,0x15,0xD9,0xB7,0x2D,0x7D,0x57,0x24,0x4E,0xA8,0xEF,0x9A,0xC0,0xC6,0x21,0x89,
0x67,0x08,0xA5,0x93,0x67,0xF9,0xDF,0xB9,0xF5,0x4C,0xA8,0x4B,0x3F,0x1C,0x9D,0xB1,
0x28,0x8B,0x23,0x1C,0x3A,0xE0,0xD4,0xFE,0x73,0x44,0xFD,0x25,0x33,0x26,0x47,0x20
};
/* Signature (r, s), 48 bytes each, big-endian. */
static const unsigned char kat_p384_r[48] = {
0x94,0xED,0xBB,0x92,0xA5,0xEC,0xB8,0xAA,0xD4,0x73,0x6E,0x56,0xC6,0x91,0x91,0x6B,
0x3F,0x88,0x14,0x06,0x66,0xCE,0x9F,0xA7,0x3D,0x64,0xC4,0xEA,0x95,0xAD,0x13,0x3C,
0x81,0xA6,0x48,0x15,0x2E,0x44,0xAC,0xF9,0x6E,0x36,0xDD,0x1E,0x80,0xFA,0xBE,0x46
};
static const unsigned char kat_p384_s[48] = {
0x99,0xEF,0x4A,0xEB,0x15,0xF1,0x78,0xCE,0xA1,0xFE,0x40,0xDB,0x26,0x03,0x13,0x8F,
0x13,0x0E,0x74,0x0A,0x19,0x62,0x45,0x26,0x20,0x3B,0x63,0x51,0xD0,0xA3,0xA9,0x4F,
0xA3,0x29,0xC1,0x45,0x78,0x6E,0x67,0x9E,0x7B,0x82,0xC7,0x1A,0x38,0x62,0x8A,0xC8
};
/* The message that was signed; the device computes SHA-384 over it. */
static const char kat_p384_msg[] = "sample";
#endif /* ECC_P384_KAT_H */

View File

@ -235,10 +235,18 @@ extern "C" {
/* ------------------------------------------------------------------------- */
#undef HAVE_ECC
#define HAVE_ECC
#undef ECC_USER_CURVES /* build only the curves we name (just P-256) */
#undef ECC_USER_CURVES /* build only the curves named below: P-256, */
/* plus P-384 when WOLF_ECC384 is set */
#define ECC_USER_CURVES
#undef HAVE_ECC256
#define HAVE_ECC256
#ifdef WOLF_ECC384
/* SECP384R1 alongside P-256 (ECC384=1 build); pairs with SHA-384. */
#undef HAVE_ECC384
#define HAVE_ECC384
#undef WOLFSSL_SP_384
#define WOLFSSL_SP_384
#endif
#undef HAVE_ECC_VERIFY
#define HAVE_ECC_VERIFY
#undef HAVE_ECC_SIGN
@ -254,8 +262,12 @@ extern "C" {
#define WOLFSSL_HAVE_SP_ECC
#undef WOLFSSL_SP_NO_MALLOC
#define WOLFSSL_SP_NO_MALLOC
#ifndef WOLF_ECC_FAST
/* Small (looped) SP code by default; ECCFAST=1 picks the windowed SP path,
* which trades flash for roughly ten times the ECDSA throughput. */
#undef WOLFSSL_SP_SMALL
#define WOLFSSL_SP_SMALL
#endif
#undef SP_WORD_SIZE
#define SP_WORD_SIZE 32
/* C28x int is 16-bit (WC_16BIT_CPU) but we use 32-bit SP digits + octet-wise

View File

@ -91,6 +91,17 @@ WC_SRCS := \
$(WOLFROOT)/wolfcrypt/src/wc_mldsa.c \
$(WOLFROOT)/wolfcrypt/src/random.c
# ECC384=1 adds SECP384R1, ECCFAST=1 drops WOLFSSL_SP_SMALL, and ECCBENCH=1
# adds the ECDSA / SHA-384 image-verify timing harness. All three imply ECC.
ECC384 ?= 0
ECCFAST ?= 0
ECCBENCH ?= 0
ifneq ($(ECC384)$(ECCFAST)$(ECCBENCH),000)
# override: a command-line ECC=0 alongside one of these would otherwise leave
# WOLF_ECC384 defined with the ECC sources and HAVE_ECC block compiled out.
override ECC := 1
endif
# ECC=1 adds ECDSA/ECDH over SECP256R1 via SP single-precision math.
ECC ?= 0
ifeq ($(ECC),1)
@ -102,6 +113,19 @@ ifeq ($(ECC),1)
$(WOLFROOT)/wolfcrypt/src/sp_c32.c
endif
ifeq ($(ECC384),1)
CFLAGS += --define=WOLF_ECC384
endif
ifeq ($(ECCFAST),1)
CFLAGS += --define=WOLF_ECC_FAST
endif
# The timing harness runs on its own: the wolfcrypt test and benchmark are left
# out so the run is short and the heap uncontended.
ifeq ($(ECCBENCH),1)
CFLAGS += --define=WOLF_ECC_BENCH --define=NO_CRYPT_TEST \
--define=NO_CRYPT_BENCHMARK
endif
# MLKEM=1 adds ML-KEM-768 (FIPS 203) via the wolfCrypt C implementation.
MLKEM ?= 0
ifeq ($(MLKEM),1)

View File

@ -90,6 +90,9 @@ Each is `make <NAME>=1` (default 0 unless noted), additive on top of the default
| Option | Adds |
|---|---|
| `ECC=1` | ECDSA/ECDH over SECP256R1 (SP math) |
| `ECC384=1` | Adds SECP384R1 (`HAVE_ECC384` + `WOLFSSL_SP_384`) |
| `ECCFAST=1` | Drops `WOLFSSL_SP_SMALL` for the windowed SP path (~10x faster ECDSA, +30 KB flash for P-256). Needs a wolfSSL with the SP scalar-recode fix for 16-bit bytes; older trees fail to build with `identifier "int8_t" is undefined`. |
| `ECCBENCH=1` | ECDSA sign/verify, ECDH and SHA-256/384 image-hash timings instead of the test/benchmark harness |
| `MLKEM=1` | ML-KEM-768 (FIPS 203) |
| `AES=1` | AES-CBC/CTR/CFB/GCM (table-driven, `GCM_SMALL`) |
| `AESEXTRA=1` | AES-CMAC, AES-CCM, AES-GMAC (implies the AES core) |
@ -136,6 +139,23 @@ Measured at 150 MHz (SHA-256 Hash-DRBG via `WOLFSSL_GENSEED_FORTEST`; build with
| SHAKE128 / SHAKE256 | ~319 / 264 KiB/s |
| RNG (Hash-DRBG) | ~122 KiB/s |
ECDSA and ECDH (@150 MHz, `ECC_TIMING_RESISTANT` on). The default `WOLFSSL_SP_SMALL` build keeps SP code small at a large cost in speed; `ECCFAST=1` selects the windowed SP path:
| Operation | Default SP | Windowed SP (`ECCFAST=1`) |
|---|---|---|
| ECDSA P-256 sign | ~350 ms/op | 46 ms/op |
| ECDSA P-256 verify | ~635 ms/op | 64 ms/op |
| ECDH P-256 agree | ~308 ms/op | 57 ms/op |
| ECDSA P-384 sign | ~1280 ms/op | 98 ms/op |
| ECDSA P-384 verify | ~2260 ms/op | 183 ms/op |
| ECDH P-384 agree | ~1108 ms/op | 163 ms/op |
The default-SP column varies by a few percent run to run, since each run makes a fresh key and ECDSA verify is a public operation with no timing-resistance constraint.
The image hash walks a 4 KiB flash-resident block repeatedly to reach 128 KiB. That is equivalent to one sweep over a 128 KiB array on this part, which has no data cache in front of flash: measured on hardware, a true single pass took 0.740 s against 0.736 s for the repeated block. The small block is the default because a genuine 128 KiB octet array costs 256 KB of flash, one octet per 16-bit cell, and fills a whole flash bank. Build with `-DWOLF_IMG_BLK=131072` in `EXTRA_CFLAGS` for the single sweep.
For a secure-boot style check of a 128 KB flash image, the hash dominates once the windowed path is in use: SHA-384 over the image takes 0.75 s, giving 0.81 s for SHA-384 plus a P-256 verify and 0.93 s with P-384 (1.39 s and 2.96 s respectively on the default SP path). SHA-256 over the same image takes 0.46 s. Building with `EXTRA_CFLAGS="--opt_level=3 --opt_for_speed=5"` takes SHA-384 down to 0.64 s for about 7 KB more flash, but does not move the ECC math. The windowed path itself costs about 30 KB of flash for P-256 alone, 82 KB for both curves.
ML-DSA-87 (asymmetric, @150 MHz): verify 244 ms/op default, **189 ms/op** with `FASTVERIFY=1` and `--opt_level=3 --opt_for_speed=5` (23% faster, RAM 10.7 -> 23.9 KB, zero heap either way); keygen and signing also run (`SIGN=1`). ML-DSA-87 verify memory: ~10.7 KB total (key/workspace struct + ~2 KB stack, zero heap) with `WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM` + `WOLFSSL_MLDSA_ASSIGN_KEY`.
## Memory notes

View File

@ -118,6 +118,9 @@
#endif
#ifdef WOLF_ECC
#include "ecc_p256_kat.h"
#ifdef WOLF_ECC384
#include "ecc_p384_kat.h"
#endif
#endif
#ifdef WOLF_RSA
#include "rsa2048_kat.h"
@ -664,6 +667,372 @@ static void wolf_ecc_test(void)
wc_ecc_free(&keyA);
wc_FreeRng(&rng);
}
#ifdef WOLF_ECC_BENCH
/* Timings for a secure-boot style image check: hash a flash-resident image,
* then one ECDSA verify. CPU Timer 0 is sampled every iteration so no single
* interval can outrun the counter's ~28 s wrap. A benchmark that reports a
* rate for work that failed is worse than one that reports nothing, so every
* loop below stops at the first error and divides by the number of operations
* that actually completed. */
#ifndef WOLF_IMG_TOTAL
#define WOLF_IMG_TOTAL 131072UL /* octets hashed in total */
#endif
#ifndef WOLF_IMG_BLK
#define WOLF_IMG_BLK 4096U /* size of the flash-resident block */
#endif
#ifndef WOLF_IMG_CHUNK
#define WOLF_IMG_CHUNK 4096U /* octets handed to each Update */
#endif
/* const lands in .const, i.e. flash, so the hash reads at flash wait states
* the way a real image check does rather than out of RAM. One octet per
* 16-bit cell, matching how wolfCrypt addresses bytes on this target.
*
* By default the block is smaller than WOLF_IMG_TOTAL and is walked repeatedly
* to reach the total, so this measures hashing 128 KiB of flash-resident data
* rather than a single sweep over a 128 KiB address range. The two are
* equivalent here: the C28x has no data cache in front of flash, and measuring
* both on hardware gave 0.740 s for a true single pass over a 128 KiB array
* against 0.736 s for a repeated 4 KiB block - within run-to-run spread. The
* small block is the default because a genuine 128 KiB octet array costs 256 KB
* of flash (one octet per 16-bit cell) and fills an entire flash bank. Build
* with -DWOLF_IMG_BLK=131072 to do the single sweep instead. */
static const unsigned char wolf_img[WOLF_IMG_BLK] = { 0 };
/* Hash WOLF_IMG_TOTAL octets of flash-resident data. */
static void wolf_sha_image_bench(int use384)
{
static wc_Sha256 sha256;
static wc_Sha384 sha384;
unsigned char dig[WC_SHA384_DIGEST_SIZE];
const char* nm = use384 ? "SHA-384" : "SHA-256";
unsigned long off;
double t;
int ret;
ret = use384 ? wc_InitSha384(&sha384) : wc_InitSha256(&sha256);
if (ret != 0) {
printf("%s image bench: init FAIL ret=%d\r\n", nm, ret);
return;
}
(void)current_time(1);
for (off = 0; off < WOLF_IMG_TOTAL; off += WOLF_IMG_CHUNK) {
unsigned long left = WOLF_IMG_TOTAL - off;
word32 n = (word32)((left < WOLF_IMG_CHUNK) ? left : WOLF_IMG_CHUNK);
/* walk within the block, wrapping when it is smaller than the total */
unsigned long pos = off % (unsigned long)WOLF_IMG_BLK;
if (pos + n > (unsigned long)WOLF_IMG_BLK) {
n = (word32)((unsigned long)WOLF_IMG_BLK - pos);
}
ret = use384 ? wc_Sha384Update(&sha384, wolf_img + pos, n)
: wc_Sha256Update(&sha256, wolf_img + pos, n);
(void)current_time(0);
if (ret != 0) {
break;
}
}
/* Only finalize a context whose updates all succeeded. */
if (ret == 0) {
ret = use384 ? wc_Sha384Final(&sha384, dig)
: wc_Sha256Final(&sha256, dig);
}
t = current_time(0);
if (use384) {
wc_Sha384Free(&sha384);
}
else {
wc_Sha256Free(&sha256);
}
if (ret != 0) {
printf("%s over %lu KiB image: FAIL ret=%d (no timing)\r\n",
nm, WOLF_IMG_TOTAL / 1024UL, ret);
return;
}
printf("%s over %lu KiB image: %.3f s\r\n", nm, WOLF_IMG_TOTAL / 1024UL, t);
}
static void wolf_ecdsa_bench(int curveId, int keySz, int reps, const char* nm)
{
static ecc_key bk;
static WC_RNG brng;
static mp_int br;
static mp_int bs;
unsigned char dig[WC_SHA384_DIGEST_SIZE];
double t;
int i, ret, res = 0, done;
int rngOk = 0, keyOk = 0, rOk = 0, sOk = 0;
/* A SHA-384 digest; ECDSA truncates it to the curve order for P-256. */
XMEMSET(dig, 0xA5, sizeof(dig));
ret = wc_InitRng(&brng);
if (ret == 0) {
rngOk = 1;
ret = wc_ecc_init(&bk);
}
if (ret == 0) {
keyOk = 1;
ret = wc_ecc_make_key_ex(&brng, keySz, &bk, curveId);
}
if (ret == 0) {
ret = wc_ecc_set_rng(&bk, &brng);
}
if (ret == 0) {
ret = mp_init(&br);
}
if (ret == 0) {
rOk = 1;
ret = mp_init(&bs);
}
if (ret == 0) {
sOk = 1;
}
if (ret != 0) {
printf("ECDSA %s bench: setup FAIL ret=%d\r\n", nm, ret);
goto bench_done;
}
done = 0;
(void)current_time(1);
for (i = 0; i < reps; i++) {
ret = wc_ecc_sign_hash_ex(dig, (word32)keySz, &brng, &bk, &br, &bs);
(void)current_time(0);
if (ret != 0) {
break;
}
done++;
}
t = current_time(0);
if (done == 0) {
printf("ECDSA %s sign: FAIL ret=%d (no timing)\r\n", nm, ret);
goto bench_done;
}
printf("ECDSA %s sign: %d ops in %.3f s = %.1f ms/op%s\r\n",
nm, done, t, (t * 1000.0) / (double)done,
(done < reps) ? " [STOPPED EARLY]" : "");
done = 0;
(void)current_time(1);
for (i = 0; i < reps; i++) {
ret = wc_ecc_verify_hash_ex(&br, &bs, dig, (word32)keySz, &res, &bk);
(void)current_time(0);
if ((ret != 0) || (res != 1)) {
break;
}
done++;
}
t = current_time(0);
if (done == 0) {
printf("ECDSA %s verify: FAIL ret=%d res=%d (no timing)\r\n",
nm, ret, res);
goto bench_done;
}
printf("ECDSA %s verify: %d ops in %.3f s = %.1f ms/op%s\r\n",
nm, done, t, (t * 1000.0) / (double)done,
(done < reps) ? " [STOPPED EARLY]" : "");
bench_done:
if (sOk) {
mp_clear(&bs);
}
if (rOk) {
mp_clear(&br);
}
if (keyOk) {
wc_ecc_free(&bk);
}
if (rngOk) {
wc_FreeRng(&brng);
}
}
static void wolf_ecdh_bench(int curveId, int keySz, int reps, const char* nm)
{
static ecc_key ka;
static ecc_key kb;
static WC_RNG brng;
static byte secret[48];
word32 len = 0;
double t;
int i, ret, done;
int rngOk = 0, kaOk = 0, kbOk = 0;
ret = wc_InitRng(&brng);
if (ret == 0) {
rngOk = 1;
ret = wc_ecc_init(&ka);
}
if (ret == 0) {
kaOk = 1;
ret = wc_ecc_init(&kb);
}
if (ret == 0) {
kbOk = 1;
ret = wc_ecc_make_key_ex(&brng, keySz, &ka, curveId);
}
if (ret == 0) {
ret = wc_ecc_make_key_ex(&brng, keySz, &kb, curveId);
}
if (ret == 0) {
ret = wc_ecc_set_rng(&ka, &brng);
}
if (ret != 0) {
printf("ECDH %s bench: setup FAIL ret=%d\r\n", nm, ret);
goto ecdh_done;
}
done = 0;
(void)current_time(1);
for (i = 0; i < reps; i++) {
len = (word32)sizeof(secret);
ret = wc_ecc_shared_secret(&ka, &kb, secret, &len);
(void)current_time(0);
if (ret != 0) {
break;
}
done++;
}
t = current_time(0);
if (done == 0) {
printf("ECDH %s agree: FAIL ret=%d (no timing)\r\n", nm, ret);
goto ecdh_done;
}
printf("ECDH %s agree: %d ops in %.3f s = %.1f ms/op (len=%lu)%s\r\n",
nm, done, t, (t * 1000.0) / (double)done, (unsigned long)len,
(done < reps) ? " [STOPPED EARLY]" : "");
ecdh_done:
if (kbOk) {
wc_ecc_free(&kb);
}
if (kaOk) {
wc_ecc_free(&ka);
}
if (rngOk) {
wc_FreeRng(&brng);
}
}
static void wolf_ecc_bench(void)
{
wolf_sha_image_bench(0);
wolf_sha_image_bench(1);
wolf_ecdsa_bench(ECC_SECP256R1, 32, 10, "P-256");
wolf_ecdh_bench(ECC_SECP256R1, 32, 10, "P-256");
#ifdef WOLF_ECC384
wolf_ecdsa_bench(ECC_SECP384R1, 48, 5, "P-384");
wolf_ecdh_bench(ECC_SECP384R1, 48, 5, "P-384");
#endif
}
#endif /* WOLF_ECC_BENCH */
#ifdef WOLF_ECC384
/* P-384 correctness, independent of the timing harness: an RFC 6979 verify KAT
* over a device-computed SHA-384 digest, then a two-sided ECDH agreement. The
* KAT uses externally supplied public-key and signature octets, so it catches
* the serialization defects a self-generated sign/verify pair cannot. */
static void wolf_ecc384_test(void)
{
/* Large under SP_NO_MALLOC - keep off the 16 KW stack. */
static ecc_key k384;
static ecc_key k384a;
static ecc_key k384b;
static WC_RNG rng384;
static mp_int r384;
static mp_int s384;
static wc_Sha384 sha384;
static byte secA[48];
static byte secB[48];
unsigned char dig[WC_SHA384_DIGEST_SIZE];
word32 lenA, lenB;
int ret, res = 0;
ret = wc_InitSha384(&sha384);
if (ret == 0) {
ret = wc_Sha384Update(&sha384, (const byte*)kat_p384_msg,
(word32)(sizeof(kat_p384_msg) - 1));
}
if (ret == 0) {
ret = wc_Sha384Final(&sha384, dig);
}
wc_Sha384Free(&sha384);
if (ret == 0) {
ret = wc_ecc_init(&k384);
}
if (ret == 0) {
ret = wc_ecc_import_unsigned(&k384, (byte*)kat_p384_qx,
(byte*)kat_p384_qy, NULL, ECC_SECP384R1);
}
if (ret == 0) {
ret = mp_init(&r384);
}
if (ret == 0) {
ret = mp_init(&s384);
}
if (ret == 0) {
ret = mp_read_unsigned_bin(&r384, kat_p384_r,
(word32)sizeof(kat_p384_r));
}
if (ret == 0) {
ret = mp_read_unsigned_bin(&s384, kat_p384_s,
(word32)sizeof(kat_p384_s));
}
if (ret == 0) {
ret = wc_ecc_verify_hash_ex(&r384, &s384, dig,
(word32)sizeof(dig), &res, &k384);
}
printf("ECDSA P-384 verify KAT: %s (ret=%d res=%d)\r\n",
(ret == 0 && res == 1) ? "PASS" : "FAIL", ret, res);
mp_clear(&s384);
mp_clear(&r384);
wc_ecc_free(&k384);
/* Two-sided ECDH: both parties must derive the same secret. */
ret = wc_InitRng(&rng384);
if (ret == 0) {
ret = wc_ecc_init(&k384a);
}
if (ret == 0) {
ret = wc_ecc_init(&k384b);
}
if (ret == 0) {
ret = wc_ecc_make_key_ex(&rng384, 48, &k384a, ECC_SECP384R1);
}
if (ret == 0) {
ret = wc_ecc_make_key_ex(&rng384, 48, &k384b, ECC_SECP384R1);
}
if (ret == 0) {
ret = wc_ecc_set_rng(&k384a, &rng384);
}
if (ret == 0) {
ret = wc_ecc_set_rng(&k384b, &rng384);
}
lenA = (word32)sizeof(secA);
lenB = (word32)sizeof(secB);
if (ret == 0) {
ret = wc_ecc_shared_secret(&k384a, &k384b, secA, &lenA);
}
if (ret == 0) {
ret = wc_ecc_shared_secret(&k384b, &k384a, secB, &lenB);
}
printf("ECDH P-384 shared secret: %s (ret=%d len=%lu)\r\n",
(ret == 0 && lenA == lenB && lenA == 48U &&
XMEMCMP(secA, secB, lenA) == 0) ? "PASS" : "FAIL",
ret, (unsigned long)lenA);
wc_ecc_free(&k384b);
wc_ecc_free(&k384a);
wc_FreeRng(&rng384);
}
#endif /* WOLF_ECC384 */
#endif /* WOLF_ECC */
/* Focused SHA3-256 known-answer test (SHA3-256("abc")), to confirm the
@ -2510,6 +2879,16 @@ int main(void)
wolf_ecc_test();
#endif
#ifdef WOLF_ECC384
printf("\r\n--- ECDSA/ECDH P-384 (SP) ---\r\n");
wolf_ecc384_test();
#endif
#ifdef WOLF_ECC_BENCH
printf("\r\n--- ECDSA / SHA-384 image-verify timings ---\r\n");
wolf_ecc_bench();
#endif
#ifdef WOLF_MLKEM
printf("\r\n--- ML-KEM-768 ---\r\n");
wolf_mlkem768_test();