1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00
Files
wolfssl/wolfcrypt/benchmark/fips_cast_bench.c
Daniel Pouzzner 103ef37cc7 globally rename WC_RNG_BANK_SUPPORT to HAVE_WC_RNG_BANK, and add WC_RNG_WANT_BANKREF_SUPPORT as an explicit feature flag for the bankref mechanism, always disabled for FIPS configurations.
wolfcrypt/src/random.c: allow WC_HAVE_RNG_BANKREF with HAVE_FIPS if WOLFSSL_FIPS_DEV.

wolfssl/wolfcrypt/random.h:

   * remove WC_RNG_BANK_SUPPORT from the exclusion list for WC_RNG_LOCK_ATFORK
   * pivot WC_HAVE_RNG_BANKREF on WC_RNG_WANT_BANKREF_SUPPORT, with FIPS exclusion.

wolfcrypt/src/rng_bank.c: update wc_InitRng_BankRef_local() and wc_BankRef_Release() to use WC_RNG_FLAG_BANKREF, not WC_DRBG_BANKREF.

wolfssl/wolfcrypt/rng_bank.h: remove backward-compat clause defining WC_HAVE_RNG_BANKREF.

linuxkm/lkcapi_rsa_glue.c: refactor km_rsa_ctx_init_rng() to check ctx->rng.flags, not ctx->rng.status, for bankref status.

configure.ac: in ENABLED_RNG_BANK setup, split out WC_RNG_WANT_BANKREF_SUPPORT as a separate flag, and don't define it if FIPS.

wolfcrypt/benchmark/fips_cast_bench.c: omit bench_pct_slhdsa() if defined(WOLFSSL_SLHDSA_VERIFY_ONLY).

wolfcrypt/test/test.c: in myCryptoDevCb(), add HAVE_INTEL_RDRAND to list of macros enabling use of wc_RNG_GenerateBlock() for WC_ALGO_TYPE_RNG.

linuxkm/Makefile: use bash-4.3-compatible syntax in $(()) expression.

.wolfssl_known_macro_extras: remove unneeded entries.
2026-09-24 01:36:32 -05:00

971 lines
33 KiB
C

/* fips_cast_bench.c
*
* Copyright (C) 2006-2026 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
/* Times the module's self-tests so an operator can budget start-up on slow
* hardware. Two kinds:
* known-answer tests, once per algorithm (FIPS 140-3 IG 10.3.A)
* -p key-pair tests, on EVERY key generation
* (ISO/IEC 19790:2012 sec 7.10.3.3) */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if !defined(WOLFSSL_USER_SETTINGS) && !defined(WOLFSSL_NO_OPTIONS_H)
#include <wolfssl/options.h>
#endif
#include <wolfssl/wolfcrypt/settings.h> /* also picks up user_settings.h */
/* wc_RunCast_fips() is v7.0.0+ and needs fips.c, which dev-no-post compiles
* only into linuxkm, not libwolfssl. Both flavors use the stub below. */
#if defined(HAVE_FIPS) && FIPS_VERSION3_GE(7,0,0) && \
!defined(WOLFSSL_FIPS_DEV_NO_POST)
#include <wolfssl/version.h>
#include <wolfssl/wolfcrypt/types.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/fips_test.h>
#include <wolfssl/wolfcrypt/random.h>
#ifdef WOLFSSL_HAVE_MLKEM
#include <wolfssl/wolfcrypt/wc_mlkem.h>
#endif
#ifdef WOLFSSL_HAVE_MLDSA
#include <wolfssl/wolfcrypt/wc_mldsa.h>
#endif
#ifdef WOLFSSL_HAVE_SLHDSA
#include <wolfssl/wolfcrypt/wc_slhdsa.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include <errno.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <time.h>
#endif
#define BENCH_DEFAULT_ITERS 10
/* Upper bound for -i, far above any useful run. */
#define BENCH_MAX_ITERS 1000000
/* Names for the CAST ids in wolfssl/wolfcrypt/fips_test.h. Hand-maintained,
* so an id added there and not here prints "(unknown)" rather than silently
* going missing. */
static const char* cast_name(int id)
{
switch (id) {
case FIPS_CAST_AES_CBC: return "AES-CBC";
case FIPS_CAST_AES_GCM: return "AES-GCM";
case FIPS_CAST_HMAC_SHA1: return "HMAC-SHA-1";
case FIPS_CAST_HMAC_SHA2_256: return "HMAC-SHA2-256";
case FIPS_CAST_HMAC_SHA2_512: return "HMAC-SHA2-512";
case FIPS_CAST_HMAC_SHA3_256: return "HMAC-SHA3-256";
case FIPS_CAST_DRBG: return "DRBG (SHA-256)";
case FIPS_CAST_RSA_SIGN_PKCS1v15: return "RSA-SIGN-PKCS1v15";
case FIPS_CAST_ECC_CDH: return "ECC-CDH";
case FIPS_CAST_ECC_PRIMITIVE_Z: return "ECC-Primitive-Z";
case FIPS_CAST_DH_PRIMITIVE_Z: return "DH-Primitive-Z";
case FIPS_CAST_ECDSA: return "ECDSA";
case FIPS_CAST_KDF_TLS12: return "KDF-TLS12";
case FIPS_CAST_KDF_TLS13: return "KDF-TLS13";
case FIPS_CAST_KDF_SSH: return "KDF-SSH";
/* No inner version guards: this file only compiles for v7.0.0+
* (see the #if at the top), where every id below exists. */
case FIPS_CAST_KDF_SRTP: return "KDF-SRTP";
case FIPS_CAST_ED25519: return "Ed25519";
case FIPS_CAST_ED448: return "Ed448";
case FIPS_CAST_PBKDF2: return "PBKDF2";
case FIPS_CAST_AES_ECB: return "AES-ECB";
case FIPS_CAST_ML_KEM: return "ML-KEM";
case FIPS_CAST_ML_DSA: return "ML-DSA";
case FIPS_CAST_LMS: return "LMS";
case FIPS_CAST_XMSS: return "XMSS";
case FIPS_CAST_DRBG_SHA512: return "DRBG (SHA-512)";
case FIPS_CAST_SLH_DSA: return "SLH-DSA";
case FIPS_CAST_AES_CMAC: return "AES-CMAC";
case FIPS_CAST_SHAKE: return "SHAKE";
case FIPS_CAST_AES_KW: return "AES-KW";
default: return "(unknown)";
}
}
/* Wall-clock seconds, the way benchmark.c's current_time() does it: convert
* to double before dividing, so no integer overflow is possible. */
static double bench_now(void)
{
#ifdef _WIN32
static LARGE_INTEGER freq = { 0 };
LARGE_INTEGER count;
if (freq.QuadPart == 0) {
if (!QueryPerformanceFrequency(&freq) || (freq.QuadPart == 0))
return -1.0;
}
if (!QueryPerformanceCounter(&count))
return -1.0;
return (double)count.QuadPart / (double)freq.QuadPart;
#else
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return -1.0;
return (double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0;
#endif
}
/* Elapsed seconds, never negative. bench_now() returns -1.0 if the clock
* fails, and a negative interval would corrupt the statistics. */
static double bench_delta(double t0, double t1)
{
double dt = t1 - t0;
return (dt > 0.0) ? dt : 0.0;
}
/* Run a single CAST iters times, populate stats (in milliseconds).
* Returns 0 on success, non-zero on first CAST failure. */
static int run_one_cast(int id, int iters,
double* out_mean_ms, double* out_stddev_ms,
double* out_min_ms, double* out_max_ms)
{
int i;
double mn = 0.0;
double mx = 0.0;
double mean = 0.0; /* running mean, seconds */
double m2 = 0.0; /* running sum of squared deviations */
if (iters <= 0)
return BAD_FUNC_ARG;
/* Welford's method, so the samples need not be kept. */
for (i = 0; i < iters; i++) {
double t0, t1, dt, d;
int rc;
t0 = bench_now();
rc = wc_RunCast_fips(id);
t1 = bench_now();
if (rc != 0)
return rc;
dt = bench_delta(t0, t1);
if ((i == 0) || (dt < mn))
mn = dt;
if (dt > mx)
mx = dt;
d = dt - mean;
mean += d / (double)(i + 1);
m2 += d * (dt - mean);
}
*out_mean_ms = mean * 1000.0;
*out_stddev_ms = sqrt(m2 / (double)iters) * 1000.0;
*out_min_ms = mn * 1000.0;
*out_max_ms = mx * 1000.0;
return 0;
}
/* Pairwise consistency tests. A CAST runs once at start-up; these run on
* every key generation, so none of the cost shows in the CAST numbers.
* ISO/IEC 19790:2012 sec 7.10.3.3.
*
* KeyGen+PCT what a caller pays today. MEASURED.
* PCT alone the same test repeated on the finished key. MEASURED.
* KeyGen raw the difference. DERIVED: no build skips the test. */
#define BENCH_PCT_DEFAULT_ITERS 1
#if defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_HAVE_MLDSA) || \
defined(WOLFSSL_HAVE_SLHDSA)
#define BENCH_HAVE_PCT
#endif
#if defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_HAVE_MLDSA)
/* The two families that share the four-column table. */
#define BENCH_HAVE_PCT_TABLE
#endif
#ifdef BENCH_HAVE_PCT
/* Largest seed size over the FIPS 205 parameter sets. Checked against the
* key at run time, so a set with a bigger one is skipped with a message
* rather than overflowing these buffers. */
#define BENCH_SLHDSA_MAX_N 32
#ifdef BENCH_HAVE_PCT_TABLE
/* KeyGen raw goes negative if the test is not actually inside key generation.
* Compiling the SLH-DSA one out took it from +54.8 ms to -365.5 ms, which set
* the margin below; noise cannot swing that far.
* Returns 1 when key generation does not appear to run the test. */
static int pct_missing(double raw_ms, double pct_ms)
{
return (raw_ms < 0.0) && ((-raw_ms) > (pct_ms * 0.05));
}
static void pct_hdr(const char* title, const char* what)
{
printf("%s\n", title);
printf(" PCT = %s\n", what);
printf("Alg | KeyGen+PCT | PCT alone | KeyGen raw | PCT share\n");
printf(" | (ms) | (ms) | (ms) DERIV | (%%)\n");
printf("----------------+------------+------------+------------+----------\n");
}
/* Returns 1 when the row shows the PCT missing from key generation. */
static int pct_row(const char* name, double kg_s, double pct_s, int iters)
{
double kg = kg_s / (double)iters * 1000.0;
double pct = pct_s / (double)iters * 1000.0;
double raw = kg - pct;
double share = (kg > 0.0) ? (pct * 100.0 / kg) : 0.0;
printf("%-15s | %10.3f | %10.3f | %10.3f | %8.1f%s\n",
name, kg, pct, raw, share,
pct_missing(raw, pct) ? " PCT NOT IN KEYGEN" : "");
return pct_missing(raw, pct);
}
#endif /* BENCH_HAVE_PCT_TABLE */
static void pct_skip(const char* name, int rc)
{
printf("%-15s | not available in this build (rc=%d, %s)\n",
name, rc, wc_GetErrorString(rc));
}
static void pct_fail(const char* name, int rc)
{
printf("%-15s | FAILED rc=%d (%s)\n", name, rc, wc_GetErrorString(rc));
}
#endif /* BENCH_HAVE_PCT */
#ifdef WOLFSSL_HAVE_MLKEM
/* Repeats what wc_mlkem.c's test does, fixed `m` included, so the number is
* for the same work and not a different round trip. */
static int bench_pct_mlkem(int iters)
{
static const int types[] = { WC_ML_KEM_512, WC_ML_KEM_768,
WC_ML_KEM_1024 };
static const char* names[] = { "ML-KEM-512", "ML-KEM-768",
"ML-KEM-1024" };
static const byte pct_m[WC_ML_KEM_ENC_RAND_SZ] = {
0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB,
0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB,
0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB,
0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB
};
WC_DECLARE_VAR(key, MlKemKey, 1, NULL);
WC_DECLARE_VAR(kgrand, byte, WC_ML_KEM_MAKEKEY_RAND_SZ, NULL);
WC_DECLARE_VAR(ss1, byte, WC_ML_KEM_SS_SZ, NULL);
WC_DECLARE_VAR(ss2, byte, WC_ML_KEM_SS_SZ, NULL);
byte* ct = NULL;
int failures = 0;
size_t t;
WC_ALLOC_VAR_EX(key, MlKemKey, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER,
{ failures = MEMORY_E; goto out; });
WC_ALLOC_VAR_EX(kgrand, byte, WC_ML_KEM_MAKEKEY_RAND_SZ, NULL,
DYNAMIC_TYPE_TMP_BUFFER, { failures = MEMORY_E; goto out; });
WC_ALLOC_VAR_EX(ss1, byte, WC_ML_KEM_SS_SZ, NULL,
DYNAMIC_TYPE_TMP_BUFFER, { failures = MEMORY_E; goto out; });
WC_ALLOC_VAR_EX(ss2, byte, WC_ML_KEM_SS_SZ, NULL,
DYNAMIC_TYPE_TMP_BUFFER, { failures = MEMORY_E; goto out; });
ct = (byte*)XMALLOC(WC_ML_KEM_MAX_CIPHER_TEXT_SIZE, NULL,
DYNAMIC_TYPE_TMP_BUFFER);
if (ct == NULL) {
failures = MEMORY_E;
goto out;
}
XMEMSET(kgrand, 0x5A, WC_ML_KEM_MAKEKEY_RAND_SZ);
pct_hdr("ML-KEM (FIPS 203)", "encapsulate + decapsulate + compare");
for (t = 0; t < sizeof(types) / sizeof(types[0]); t++) {
double kg_s = 0.0;
double pct_s = 0.0;
int i;
int rc;
rc = wc_MlKemKey_Init(key, types[t], NULL, INVALID_DEVID);
if (rc != 0) {
pct_skip(names[t], rc);
continue;
}
for (i = 0; i < iters; i++) {
double t0, t1;
word32 ctSz = 0;
/* Vary the key each round so nothing measured is a repeat of
* byte-identical work. */
kgrand[0] = (byte)i;
t0 = bench_now();
rc = wc_MlKemKey_MakeKeyWithRandom(key, kgrand,
WC_ML_KEM_MAKEKEY_RAND_SZ);
t1 = bench_now();
if (rc != 0)
break;
kg_s += bench_delta(t0, t1);
t0 = bench_now();
rc = wc_MlKemKey_CipherTextSize(key, &ctSz);
if (rc == 0) {
rc = wc_MlKemKey_EncapsulateWithRandom(key, ct, ss1, pct_m,
(int)sizeof(pct_m));
}
if (rc == 0)
rc = wc_MlKemKey_Decapsulate(key, ss2, ct, ctSz);
if ((rc == 0) && (XMEMCMP(ss1, ss2, WC_ML_KEM_SS_SZ) != 0))
rc = ML_KEM_PCT_E;
t1 = bench_now();
if (rc != 0)
break;
pct_s += bench_delta(t0, t1);
}
if (rc != 0) {
pct_fail(names[t], rc);
failures++;
}
else {
failures += pct_row(names[t], kg_s, pct_s, iters);
}
wc_MlKemKey_Free(key);
}
/* Fixed-seed material, not a live key, but leaving a shared secret in
* the work buffers of a FIPS benchmark costs nothing to avoid. */
XMEMSET(ss1, 0, WC_ML_KEM_SS_SZ);
XMEMSET(ss2, 0, WC_ML_KEM_SS_SZ);
printf("\n");
out:
XFREE(ct, NULL, DYNAMIC_TYPE_TMP_BUFFER);
WC_FREE_VAR_EX(ss2, NULL, DYNAMIC_TYPE_TMP_BUFFER);
WC_FREE_VAR_EX(ss1, NULL, DYNAMIC_TYPE_TMP_BUFFER);
WC_FREE_VAR_EX(kgrand, NULL, DYNAMIC_TYPE_TMP_BUFFER);
WC_FREE_VAR_EX(key, NULL, DYNAMIC_TYPE_TMP_BUFFER);
return failures;
}
#endif /* WOLFSSL_HAVE_MLKEM */
#ifdef WOLFSSL_HAVE_MLDSA
/* Repeats what mldsa_pct() in wc_mldsa.c does, same message and same all-zero
* rnd, so this times the path the module actually runs. */
static int bench_pct_mldsa(int iters)
{
static const byte levels[] = { WC_ML_DSA_44, WC_ML_DSA_65, WC_ML_DSA_87 };
static const char* names[] = { "ML-DSA-44", "ML-DSA-65", "ML-DSA-87" };
static const byte pct_msg[] = "wolfSSL ML-DSA PCT";
static const byte pct_seed[MLDSA_SEED_SZ] = { 0 };
WC_DECLARE_VAR(key, wc_MlDsaKey, 1, NULL);
WC_DECLARE_VAR(seed, byte, MLDSA_SEED_SZ, NULL);
byte* sig = NULL;
int failures = 0;
size_t t;
WC_ALLOC_VAR_EX(key, wc_MlDsaKey, 1, NULL, DYNAMIC_TYPE_TMP_BUFFER,
{ failures = MEMORY_E; goto out; });
WC_ALLOC_VAR_EX(seed, byte, MLDSA_SEED_SZ, NULL,
DYNAMIC_TYPE_TMP_BUFFER, { failures = MEMORY_E; goto out; });
sig = (byte*)XMALLOC(MLDSA_MAX_SIG_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (sig == NULL) {
failures = MEMORY_E;
goto out;
}
XMEMSET(seed, 0x3C, MLDSA_SEED_SZ);
pct_hdr("ML-DSA (FIPS 204)", "sign + verify");
for (t = 0; t < sizeof(levels) / sizeof(levels[0]); t++) {
double kg_s = 0.0;
double pct_s = 0.0;
int i;
int rc;
int inited = 0;
rc = wc_MlDsaKey_Init(key, NULL, INVALID_DEVID);
if (rc == 0) {
inited = 1;
rc = wc_MlDsaKey_SetParams(key, levels[t]);
}
if (rc != 0) {
pct_skip(names[t], rc);
if (inited)
wc_MlDsaKey_Free(key);
continue;
}
for (i = 0; i < iters; i++) {
double t0, t1;
word32 sigSz = MLDSA_MAX_SIG_SIZE;
int res = 0;
seed[0] = (byte)i;
t0 = bench_now();
rc = wc_MlDsaKey_MakeKeyFromSeed(key, seed);
t1 = bench_now();
if (rc != 0)
break;
kg_s += bench_delta(t0, t1);
t0 = bench_now();
rc = wc_MlDsaKey_SignCtxWithSeed(key, NULL, 0, sig, &sigSz,
pct_msg, (word32)sizeof(pct_msg), pct_seed);
if (rc == 0) {
rc = wc_MlDsaKey_VerifyCtx(key, sig, sigSz, NULL, 0, pct_msg,
(word32)sizeof(pct_msg), &res);
}
if ((rc == 0) && (res != 1))
rc = ML_DSA_PCT_E;
t1 = bench_now();
if (rc != 0)
break;
pct_s += bench_delta(t0, t1);
}
if (rc != 0) {
pct_fail(names[t], rc);
failures++;
}
else {
failures += pct_row(names[t], kg_s, pct_s, iters);
}
wc_MlDsaKey_Free(key);
}
printf("\n");
out:
XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
WC_FREE_VAR_EX(seed, NULL, DYNAMIC_TYPE_TMP_BUFFER);
WC_FREE_VAR_EX(key, NULL, DYNAMIC_TYPE_TMP_BUFFER);
return failures;
}
#endif /* WOLFSSL_HAVE_MLDSA */
#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY)
/* SLH-DSA elected the root recompute over sign and verify. This times what
* the module costs now, and the alternative it rejected, so the decision can
* be rechecked rather than remembered. */
static void bench_pct_slhdsa_notes(void)
{
printf(
"KeyGen+PCT what the module costs now: generate the key, then recompute the\n"
" public root from the private seed and compare. The recompute is\n"
" a second root computation, so this is roughly double a key\n"
" generation with no test at all.\n"
"sign+vfy the alternative NOT taken. Compare it with KeyGen+PCT to see\n"
" what electing the root recompute saved.\n"
"PK.seed the shortcut FIPS 140-3 IG 10.3.A Additional Comment 1 allows for\n"
" SLH-DSA. IT PROVES NOTHING HERE: the public key is a slice of\n"
" the private one, so it compares bytes with themselves and can\n"
" never fail. Timed only so the claim rests on a measurement.\n");
}
static int bench_pct_slhdsa(int iters)
{
static const int params[] = {
SLHDSA_SHAKE128S, SLHDSA_SHAKE128F, SLHDSA_SHAKE192S,
SLHDSA_SHAKE192F, SLHDSA_SHAKE256S, SLHDSA_SHAKE256F
#ifdef WOLFSSL_SLHDSA_SHA2
, SLHDSA_SHA2_128S, SLHDSA_SHA2_128F, SLHDSA_SHA2_192S,
SLHDSA_SHA2_192F, SLHDSA_SHA2_256S, SLHDSA_SHA2_256F
#endif
};
static const char* names[] = {
"SHAKE-128s", "SHAKE-128f", "SHAKE-192s",
"SHAKE-192f", "SHAKE-256s", "SHAKE-256f"
#ifdef WOLFSSL_SLHDSA_SHA2
, "SHA2-128s", "SHA2-128f", "SHA2-192s",
"SHA2-192f", "SHA2-256s", "SHA2-256f"
#endif
};
static const byte pct_msg[] = "wolfSSL SLH-DSA PCT";
byte sk_seed[BENCH_SLHDSA_MAX_N];
byte sk_prf[BENCH_SLHDSA_MAX_N];
byte pk_seed[BENCH_SLHDSA_MAX_N];
byte priv[BENCH_SLHDSA_MAX_N * 4];
byte pub[BENCH_SLHDSA_MAX_N * 2];
int failures = 0;
size_t t;
printf("SLH-DSA (FIPS 205)\n");
printf("Param | KeyGen+PCT | sign+vfy | PK.seed |"
" elected saves\n");
printf(" | (ms) | (ms) | (ms) |"
" \n");
printf("----------------+------------+----------+---------+"
"--------------\n");
for (t = 0; t < sizeof(params) / sizeof(params[0]); t++) {
SlhDsaKey key;
double kg_s = 0.0;
double a_s = 0.0;
double c_s = 0.0;
byte* sig = NULL;
int sigLen;
int privLen;
int n;
int i;
int rc;
rc = wc_SlhDsaKey_Init(&key, (enum SlhDsaParam)params[t], NULL,
INVALID_DEVID);
if (rc != 0) {
pct_skip(names[t], rc);
continue;
}
privLen = wc_SlhDsaKey_PrivateSize(&key);
sigLen = wc_SlhDsaKey_SigSize(&key);
n = (privLen > 0) ? (privLen / 4) : 0;
if ((n <= 0) || (n > BENCH_SLHDSA_MAX_N) || (sigLen <= 0)) {
printf("%-15s | skipped: n=%d sigLen=%d outside what this"
" benchmark allocates\n", names[t], n, sigLen);
wc_SlhDsaKey_Free(&key);
continue;
}
sig = (byte*)XMALLOC((size_t)sigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (sig == NULL) {
wc_SlhDsaKey_Free(&key);
return MEMORY_E;
}
for (i = 0; i < iters; i++) {
double t0, t1;
word32 sigSz = (word32)sigLen;
word32 pl = (word32)privLen;
word32 ul = (word32)(n * 2);
XMEMSET(sk_seed, (byte)(0x11 + i), (size_t)n);
XMEMSET(sk_prf, (byte)(0x22 + i), (size_t)n);
XMEMSET(pk_seed, (byte)(0x33 + i), (size_t)n);
t0 = bench_now();
rc = wc_SlhDsaKey_MakeKeyWithRandom(&key, sk_seed, (word32)n,
sk_prf, (word32)n, pk_seed, (word32)n);
t1 = bench_now();
if (rc != 0)
break;
kg_s += bench_delta(t0, t1);
/* The alternative not taken, timed for comparison. */
t0 = bench_now();
rc = wc_SlhDsaKey_SignDeterministic(&key, NULL, 0, pct_msg,
(word32)sizeof(pct_msg), sig, &sigSz);
if (rc == 0) {
rc = wc_SlhDsaKey_Verify(&key, NULL, 0, pct_msg,
(word32)sizeof(pct_msg), sig, sigSz);
}
t1 = bench_now();
if (rc != 0)
break;
a_s += bench_delta(t0, t1);
/* The IG shortcut. See bench_pct_slhdsa_notes(). */
t0 = bench_now();
rc = wc_SlhDsaKey_ExportPrivate(&key, priv, &pl);
if (rc == 0)
rc = wc_SlhDsaKey_ExportPublic(&key, pub, &ul);
if ((rc == 0) && (XMEMCMP(priv + (size_t)(n * 2), pub,
(size_t)n) != 0)) {
rc = SLH_DSA_PCT_E;
}
t1 = bench_now();
if (rc != 0)
break;
c_s += bench_delta(t0, t1);
}
if (rc != 0) {
pct_fail(names[t], rc);
failures++;
}
else {
double kg = kg_s / (double)iters * 1000.0;
double a = a_s / (double)iters * 1000.0;
double c = c_s / (double)iters * 1000.0;
/* The elected test must stay the cheap one. A 2x margin, not a
* bare comparison: elected measures 3.8x to 13.8x cheaper and a
* reverted one costs about 1.1x sign+verify, so 2x separates
* them even on a loaded machine. */
if (a > (kg * 2.0)) {
printf("%-15s | %10.3f | %8.3f | %7.4f | %8.1fx\n",
names[t], kg, a, c, (kg > 0.0) ? (a / kg) : 0.0);
}
else {
printf("%-15s | %10.3f | %8.3f | %7.4f |"
" SIGN+VERIFY IN KEYGEN\n", names[t], kg, a, c);
failures++;
}
}
XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
wc_SlhDsaKey_Free(&key);
}
printf("\n");
bench_pct_slhdsa_notes();
printf("\n");
return failures;
}
#endif /* WOLFSSL_HAVE_SLHDSA && !WOLFSSL_SLHDSA_VERIFY_ONLY */
/* Returns the number of algorithms whose PCT measurement failed. */
static int bench_pct(int iters, const char* only)
{
int failures = 0;
int ran = 0;
#ifdef BENCH_HAVE_PCT
int rc;
int err = 0;
#endif
#ifndef BENCH_HAVE_PCT
(void)iters;
#endif
/* The tests below call private-key operations from outside the module,
* where a FIPS build gates them behind the private-key lock and they
* would return -287. The module's own tests run inside and never meet
* that gate, so unlock here, outside every timed region, rather than
* charge them for a check they do not pay. */
#ifdef BENCH_HAVE_PCT
PRIVATE_KEY_UNLOCK();
#endif
printf("KeyGen+PCT and PCT alone are MEASURED; KeyGen raw is DERIVED as\n"
"the difference, because no build of this module generates a key\n"
"without running the PCT.\n\n");
/* Each helper returns a count of failed rows, or a negative wolfCrypt
* error if it could not run. Keep those apart: summing them lets a
* -125 cancel real failures and report success. */
#ifdef WOLFSSL_HAVE_MLKEM
if ((err == 0) && ((only == NULL) || (XSTRCMP(only, "mlkem") == 0))) {
rc = bench_pct_mlkem(iters);
if (rc < 0)
err = rc;
else {
failures += rc;
ran++;
}
}
#endif
#ifdef WOLFSSL_HAVE_MLDSA
if ((err == 0) && ((only == NULL) || (XSTRCMP(only, "mldsa") == 0))) {
rc = bench_pct_mldsa(iters);
if (rc < 0)
err = rc;
else {
failures += rc;
ran++;
}
}
#endif
#if defined(WOLFSSL_HAVE_SLHDSA) && !defined(WOLFSSL_SLHDSA_VERIFY_ONLY)
if ((err == 0) && ((only == NULL) || (XSTRCMP(only, "slhdsa") == 0))) {
rc = bench_pct_slhdsa(iters);
if (rc < 0)
err = rc;
else {
failures += rc;
ran++;
}
}
#endif
/* Otherwise an algorithm this build lacks prints a header and no rows,
* which reads as "measured, costs nothing". */
#ifdef BENCH_HAVE_PCT
PRIVATE_KEY_LOCK();
/* Returned only after the lock is restored: an early return above would
* leave private-key operations unlocked for the whole process. */
if (err != 0)
return err;
#endif
if (ran == 0) {
fprintf(stderr, "no PCT algorithm to benchmark%s%s\n",
(only != NULL) ? " for -a " : "",
(only != NULL) ? only : "");
return -1;
}
return failures;
}
static void usage(const char* prog)
{
printf("usage: %s [-i ITERS] [-c CAST_ID] [-l] [-p [-a FAMILY]]\n", prog);
printf(" -i ITERS iterations (default %d for CASTs, %d for -p)\n",
BENCH_DEFAULT_ITERS, BENCH_PCT_DEFAULT_ITERS);
printf(" -c CAST_ID benchmark only the named CAST id\n");
printf(" -l list CAST ids and names; do not run\n");
printf(" -p benchmark the Pairwise Consistency Tests instead\n");
printf(" -a FAMILY with -p, one of mlkem, mldsa, slhdsa\n");
printf(" -h show this help\n");
}
/* atoi() silently truncates a too-large number, and the wrapped value lands
* inside the range checks below: "-i 4294967297" ran 1 iteration and
* "-c 4294967296" ran CAST 0, neither complaining. strtol() lets us reject
* both trailing garbage and out-of-range input.
* Returns 0 and stores the value, or -1 on bad input. */
static int parse_int_arg(const char* s, long* out)
{
char* end = NULL;
long v;
errno = 0;
v = strtol(s, &end, 10);
if ((end == s) || (*end != '\0') || (errno == ERANGE) ||
(v < (long)INT_MIN) || (v > (long)INT_MAX)) {
return -1;
}
*out = v;
return 0;
}
int main(int argc, char** argv)
{
int iters = BENCH_DEFAULT_ITERS;
int iters_set = 0;
int single = -1;
int list_only = 0;
int pct_only = 0;
const char* pct_family = NULL;
int i;
int first, last;
int failures = 0;
int run_count = 0;
double total_mean_ms = 0.0;
for (i = 1; i < argc; i++) {
if (XSTRCMP(argv[i], "-i") == 0 && i + 1 < argc) {
long v = 0;
if ((parse_int_arg(argv[++i], &v) != 0) || (v <= 0) ||
(v > BENCH_MAX_ITERS)) {
fprintf(stderr, "-i requires an iteration count in 1..%d\n",
BENCH_MAX_ITERS);
return 2;
}
iters = (int)v;
iters_set = 1;
} else if (XSTRCMP(argv[i], "-p") == 0) {
pct_only = 1;
} else if (XSTRCMP(argv[i], "-a") == 0 && i + 1 < argc) {
pct_family = argv[++i];
if ((XSTRCMP(pct_family, "mlkem") != 0) &&
(XSTRCMP(pct_family, "mldsa") != 0) &&
(XSTRCMP(pct_family, "slhdsa") != 0)) {
fprintf(stderr,
"-a requires one of mlkem, mldsa, slhdsa\n");
return 2;
}
} else if (XSTRCMP(argv[i], "-c") == 0 && i + 1 < argc) {
long v = 0;
if ((parse_int_arg(argv[++i], &v) != 0) || (v < 0)) {
fprintf(stderr, "-c requires a non-negative CAST id\n");
return 2;
}
single = (int)v;
} else if (XSTRCMP(argv[i], "-l") == 0) {
list_only = 1;
} else if (XSTRCMP(argv[i], "-h") == 0
|| XSTRCMP(argv[i], "--help") == 0) {
usage(argv[0]);
return 0;
} else if ((XSTRCMP(argv[i], "-i") == 0) ||
(XSTRCMP(argv[i], "-c") == 0) ||
(XSTRCMP(argv[i], "-a") == 0)) {
fprintf(stderr, "%s requires a value\n", argv[i]);
return 2;
} else {
fprintf(stderr, "unknown argument: %s\n", argv[i]);
usage(argv[0]);
return 2;
}
}
if ((pct_family != NULL) && !pct_only) {
fprintf(stderr, "-a selects a PCT family and only applies with -p\n");
return 2;
}
if (pct_only && (single >= 0)) {
fprintf(stderr, "-c names a CAST id and does not apply with -p\n");
return 2;
}
/* One round of -p costs far more than one CAST, so it defaults lower.
* An explicit -i still wins. */
if (pct_only && !iters_set)
iters = BENCH_PCT_DEFAULT_ITERS;
/* Before -l, so an out-of-range -c is rejected whichever mode runs. */
if (single >= FIPS_CAST_COUNT) { /* -1 when -c was not given */
fprintf(stderr, "CAST id %d out of range (0..%d)\n",
single, FIPS_CAST_COUNT - 1);
return 2;
}
if (list_only) {
printf("FIPS CAST IDs (FIPS_CAST_COUNT = %d):\n", FIPS_CAST_COUNT);
for (i = 0; i < FIPS_CAST_COUNT; i++)
printf(" %2d %s\n", i, cast_name(i));
return 0;
}
printf("wolfCrypt FIPS %s benchmark\n", pct_only ? "PCT" : "CAST");
printf("Library version: %s\n", LIBWOLFSSL_VERSION_STRING);
printf("FIPS_CAST_COUNT: %d\n", FIPS_CAST_COUNT);
printf("Iterations per %s: %d\n", pct_only ? "algorithm" : "CAST", iters);
printf("Clock: %s\n",
#ifdef _WIN32
"QueryPerformanceCounter"
#else
"clock_gettime(CLOCK_MONOTONIC)"
#endif
);
printf("\n");
/* Bring the library up before any wolfCrypt call, as benchmark.c and
* wolfcrypt/test/test.c do; some ports set up heap and hardware here. */
{
int init_rc = wolfCrypt_Init();
if (init_rc != 0) {
fprintf(stderr, "wolfCrypt_Init returned %d\n", init_rc);
return 1;
}
}
/* The RNG needs a seed source registered before it will start, the same
* as benchmark.c and wolfcrypt/test/test.c do. */
#ifdef WC_RNG_SEED_CB
{
int seed_cb_rc = wc_SetSeed_Cb(WC_GENERATE_SEED_DEFAULT);
if (seed_cb_rc != 0) {
fprintf(stderr,
"wc_SetSeed_Cb returned %d - DRBG-using CASTs will fail.\n",
seed_cb_rc);
}
}
#endif
/* Run every CAST once first, so what we time afterwards is the test
* itself and not the one-off set-up behind it. */
{
int prime_rc = wc_RunAllCast_fips();
if (prime_rc != 0) {
fprintf(stderr,
"wc_RunAllCast_fips() prime returned %d - some CASTs may have failed.\n"
"Per-CAST measurements continue but failed CASTs will report errors.\n\n",
prime_rc);
}
}
if (pct_only) {
int pct_rc = bench_pct(iters, pct_family);
wolfCrypt_Cleanup();
return (pct_rc != 0) ? 1 : 0;
}
printf("ID | Name | Mean(ms) | StdDev(ms) | Min(ms) "
"| Max(ms)\n");
printf("---+---------------------+----------+------------+---------"
"+---------\n");
first = (single >= 0) ? single : 0;
last = (single >= 0) ? single + 1 : FIPS_CAST_COUNT;
for (i = first; i < last; i++) {
double mean_ms = 0, sd_ms = 0, mn_ms = 0, mx_ms = 0;
int rc = run_one_cast(i, iters, &mean_ms, &sd_ms, &mn_ms, &mx_ms);
if (rc != 0) {
printf("%2d | %-19s | FAILED rc=%d (%s)\n",
i, cast_name(i), rc, wc_GetErrorString(rc));
failures++;
continue;
}
printf("%2d | %-19s | %8.3f | %10.3f | %7.3f | %7.3f\n",
i, cast_name(i), mean_ms, sd_ms, mn_ms, mx_ms);
total_mean_ms += mean_ms;
run_count++;
}
printf("\n");
if (run_count > 0) {
printf("Sum of mean CAST times (one wc_RunAllCast_fips() pass): "
"%.3f ms\n", total_mean_ms);
}
wolfCrypt_Cleanup();
if (failures > 0) {
printf("WARN: %d CAST(s) failed.\n", failures);
return 1;
}
return 0;
}
#else /* !(HAVE_FIPS && v7.0.0+ && !WOLFSSL_FIPS_DEV_NO_POST) */
#include <stdio.h>
int main(void)
{
#ifndef HAVE_FIPS
fprintf(stderr,
"fips_cast_bench: built without HAVE_FIPS - nothing to measure\n");
#elif defined(WOLFSSL_FIPS_DEV_NO_POST)
fprintf(stderr,
"fips_cast_bench: dev-no-post compiles fips.c only into linuxkm, "
"so the CAST entry points are absent from libwolfssl - "
"nothing to measure\n");
#else
fprintf(stderr,
"fips_cast_bench: requires v7.0.0+ FIPS module "
"(wc_RunCast_fips / wc_RunAllCast_fips were added in v7) - "
"nothing to measure on this older module flavor\n");
#endif
return 0;
}
#endif /* HAVE_FIPS && v7.0.0+ && !WOLFSSL_FIPS_DEV_NO_POST */