TI C2000: add PRECOMPA build using a flash-resident ML-DSA-87 matrix A

pull/617/head
David Garske 2026-09-02 10:33:13 -07:00
parent fd1f216859
commit 9046a9548f
4 changed files with 1645 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -130,6 +130,13 @@ ifeq ($(SECUREBOOT),1)
CFLAGS += --define=WOLF_SECUREBOOT --define=WOLF_MLDSA_ALL_LEVELS
endif
# PRECOMPA=1 stores matrix A (expanded at build time from the fixed
# verification key) in flash, so verify skips the SHAKE128 rejection sampling.
PRECOMPA ?= 0
ifeq ($(PRECOMPA),1)
CFLAGS += --define=WOLFSSL_MLDSA_VERIFY_PRECOMP_A
endif
MLDSA ?= 0
ifeq ($(MLDSA),1)
CFLAGS += --define=WOLF_MLDSA_ALL_LEVELS

View File

@ -110,6 +110,9 @@
* ML-DSA-87 vectors from wolfcrypt/test/test.c stay in the default image. */
#include <wolfssl/wolfcrypt/hash.h>
#include "mldsa_octet_kat.h"
#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A
#include "mldsa87_precomp_a.h"
#endif
#else
#include "mldsa87_kat.h"
#endif
@ -1283,6 +1286,14 @@ static void sb_verify(const char* what, long flip, int want)
ret = wc_MlDsaKey_ImportPubRaw(&sb_key, sb_mldsa87_pub,
(word32)sizeof(sb_mldsa87_pub));
}
#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A
if (ret == 0) {
/* A was expanded at build time and lives in flash: verify skips the
* SHAKE128 rejection sampling entirely. */
ret = wc_MlDsaKey_SetPrecompA(&sb_key, sb_mldsa87_A, SB_A_LEN,
sb_rho, (word32)sizeof(sb_rho));
}
#endif
if (ret == 0) {
ret = wc_MlDsaKey_VerifyMu(&sb_key, sb_mldsa87_sig,
(word32)sizeof(sb_mldsa87_sig), mu, MLDSA_MU_SZ, &res);
@ -1297,8 +1308,64 @@ static void wolf_secureboot_test(void)
printf("secure boot: %lu-octet packed image, %u-octet chunks, "
"image never resident\r\n",
(unsigned long)SB_IMG_SZ, (unsigned)SB_CHUNK);
#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A
printf("matrix A: precomputed in flash (%ld coefficients)\r\n",
(long)SB_A_LEN);
#else
printf("matrix A: expanded at run time (SHAKE128 rejection sampling)\r\n");
#endif
sb_verify("ML-DSA-87 pure-mode packed-image verify:", -1, 1);
sb_verify("ML-DSA-87 corrupted-image reject:", SB_IMG_SZ / 2, 0);
#ifdef WOLF_MLDSA_VERIFY_BENCH
/* Time the ML-DSA verify alone (mu is already built), which is the figure
* a bootloader budget cares about. */
{
byte mu[MLDSA_MU_SZ];
int res = 0;
int bret;
int bi;
double t;
/* Time mu construction too: for a real firmware image the streamed
* SHAKE-256 hashing, not the ML-DSA verify, sets the boot budget. */
(void)current_time(1);
bret = sb_build_mu(mu, -1);
t = current_time(0);
if (bret == 0) {
printf("mu build (%lu octets streamed): %.2f ms = %.1f KiB/s\r\n",
(unsigned long)SB_IMG_SZ, t * 1000.0,
((double)SB_IMG_SZ / 1024.0) / t);
}
if (bret == 0) {
bret = wc_MlDsaKey_Init(&sb_key, NULL, INVALID_DEVID);
}
if (bret == 0) {
bret = wc_MlDsaKey_SetParams(&sb_key, WC_ML_DSA_87);
}
if (bret == 0) {
bret = wc_MlDsaKey_ImportPubRaw(&sb_key, sb_mldsa87_pub,
(word32)sizeof(sb_mldsa87_pub));
}
#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A
if (bret == 0) {
bret = wc_MlDsaKey_SetPrecompA(&sb_key, sb_mldsa87_A, SB_A_LEN,
sb_rho, (word32)sizeof(sb_rho));
}
#endif
if (bret == 0) {
(void)current_time(1);
for (bi = 0; bi < 10; bi++) {
(void)wc_MlDsaKey_VerifyMu(&sb_key, sb_mldsa87_sig,
(word32)sizeof(sb_mldsa87_sig), mu, MLDSA_MU_SZ, &res);
}
t = current_time(0);
printf("ML-DSA-87 VerifyMu bench: 10 ops in %.3f s = %.2f ms/op\r\n",
t, (t * 1000.0) / 10.0);
}
wc_MlDsaKey_Free(&sb_key);
}
#endif
}
#endif /* WOLF_SECUREBOOT */

View File

@ -31,10 +31,15 @@
#include <wolfssl/wolfcrypt/sha256.h>
#include <wolfssl/wolfcrypt/sha512.h>
#include <wolfssl/wolfcrypt/hash.h>
#include <wolfssl/wolfcrypt/sha3.h>
#include <stdio.h>
#define MSG_SZ 512
#define OUT_NAME "mldsa_octet_kat.h"
/* Second output: matrix A for the secure-boot key, expanded off target. */
#define PRECOMP_OUT_NAME "mldsa87_precomp_a.h"
/* SHAKE-128 rate, and the 3-byte triplets RejNTTPoly consumes from it. */
#define SHAKE128_BLOCK_SZ 168
/* A stand-in firmware image for the secure-boot demo. Deliberately larger
* than anything a bootloader would buffer, to show the image is never
@ -160,6 +165,113 @@ static int do_level(int type, const char* tag, const byte* msg,
return ret;
}
/* Expand matrix A for the secure-boot public key and write it as a flash
* resident C array, so verify on target can skip the SHAKE128 rejection
* sampling that otherwise dominates it.
*
* FIPS 204 Algorithm 32 ExpandA / Algorithm 30 RejNTTPoly, over the public
* SHAKE-128 API only. A is a function of rho alone, so a fixed verification
* key makes it a build-time constant. Coefficients come out in the NTT
* domain and in the same row-major (r, s) order wc_mldsa.c indexes. */
static int do_precomp_a(const byte* pub)
{
FILE* pf;
wc_Shake shake;
byte seed[MLDSA_PUB_SEED_SZ + 2];
byte h[SHAKE128_BLOCK_SZ];
long total = (long)PARAMS_ML_DSA_87_K * PARAMS_ML_DSA_87_L * MLDSA_N;
long n = 0;
int r;
int s;
int c;
int j;
int ret = 0;
pf = fopen(PRECOMP_OUT_NAME, "w");
if (pf == NULL) {
fprintf(stderr, "cannot open %s\n", PRECOMP_OUT_NAME);
return -1;
}
fprintf(pf,
"/* Precomputed ML-DSA-87 matrix A for the secure-boot key.\n"
" *\n"
" * GENERATED by tools/gen_kat.c - do not edit. A = ExpandA(rho)\n"
" * and depends only on the public key, so a fixed verification\n"
" * key lets it be built once and stored in flash, removing the\n"
" * SHAKE128 rejection sampling from every verify.\n"
" *\n"
" * SECURITY: protect this exactly as you protect the public key.\n"
" * Substituting it can influence verification. */\n"
"#ifndef MLDSA87_PRECOMP_A_H\n"
"#define MLDSA87_PRECOMP_A_H\n\n"
"#define SB_A_LEN %ldL\n\n"
"static const sword32 sb_mldsa87_A[SB_A_LEN] = {\n", total);
/* rho is the leading MLDSA_PUB_SEED_SZ octets of the raw public key. */
XMEMCPY(seed, pub, MLDSA_PUB_SEED_SZ);
for (r = 0; (ret == 0) && (r < PARAMS_ML_DSA_87_K); r++) {
/* Seed layout is rho || s || r, matching mldsa_verify_with_mu(). */
seed[MLDSA_PUB_SEED_SZ + 1] = (byte)r;
for (s = 0; (ret == 0) && (s < PARAMS_ML_DSA_87_L); s++) {
seed[MLDSA_PUB_SEED_SZ + 0] = (byte)s;
ret = wc_InitShake128(&shake, NULL, INVALID_DEVID);
if (ret == 0) {
ret = wc_Shake128_Absorb(&shake, seed, (word32)sizeof(seed));
}
/* Rejection sample 256 coefficients from squeezed blocks. */
for (j = 0; (ret == 0) && (j < MLDSA_N); ) {
ret = wc_Shake128_SqueezeBlocks(&shake, h, 1);
for (c = 0; (ret == 0) && (c < SHAKE128_BLOCK_SZ) &&
(j < MLDSA_N); c += 3) {
/* CoeffFromThreeBytes: 23-bit little-endian, drop >= q. */
sword32 t = (sword32)h[c] + ((sword32)h[c + 1] << 8) +
((sword32)h[c + 2] << 16);
t &= 0x7fffff;
if (t < MLDSA_Q) {
fprintf(pf, "%s%ldL,", ((n % 10) == 0) ? " " : " ",
(long)t);
n++;
if ((n % 10) == 0) {
fprintf(pf, "\n");
}
j++;
}
}
}
wc_Shake128_Free(&shake);
}
}
if ((ret == 0) && ((n % 10) != 0)) {
fprintf(pf, "\n");
}
if (ret == 0) {
fprintf(pf, "};\n\nstatic const unsigned char sb_rho[%d] = {\n",
MLDSA_PUB_SEED_SZ);
for (j = 0; j < MLDSA_PUB_SEED_SZ; j++) {
fprintf(pf, "%s0x%02x,", ((j % 12) == 0) ? " " : " ", pub[j]);
if (((j + 1) % 12) == 0) {
fprintf(pf, "\n");
}
}
if ((MLDSA_PUB_SEED_SZ % 12) != 0) {
fprintf(pf, "\n");
}
fprintf(pf, "};\n\n#endif /* MLDSA87_PRECOMP_A_H */\n");
}
fclose(pf);
if (ret != 0) {
fprintf(stderr, "matrix A generation failed: %d\n", ret);
/* Never leave a truncated header for the build to pick up. */
remove(PRECOMP_OUT_NAME);
}
return ret;
}
/* Secure-boot vectors: an ML-DSA-87 key, a packed "firmware image", and a
* PURE (non pre-hash) signature over that image. */
static int do_secureboot(byte* img)
@ -188,6 +300,7 @@ static int do_secureboot(byte* img)
emit_bytes("sb_mldsa87_pub", pub, pubLen);
emit_packed("sb_image_packed", img, IMG_SZ);
emit_bytes("sb_mldsa87_sig", sig, sigLen);
ret = do_precomp_a(pub);
}
else {
fprintf(stderr, "secure-boot vector generation failed: %d\n", ret);