diff --git a/.github/configs/pq-all.json b/.github/configs/pq-all.json index fcf45260533..0bc09c3612f 100644 --- a/.github/configs/pq-all.json +++ b/.github/configs/pq-all.json @@ -20,6 +20,15 @@ "--enable-slhdsa=yes,small", "--enable-dilithium=yes,small", "--enable-dual-alg-certs", "--disable-qt", "CPPFLAGS=-pedantic -Wdeclaration-after-statement -Wnull-dereference -DWOLFCRYPT_TEST_LINT -DNO_WOLFSSL_CIPHER_SUITE_TEST -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE -DWOLFSSL_MLKEM_MAKEKEY_SMALL_MEM -DWOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM -DWOLFSSL_MLKEM_NO_LARGE_CODE -DWOLFSSL_MLDSA_SIGN_SMALL_MEM -DWOLFSSL_MLDSA_VERIFY_SMALL_MEM -DWOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM -DWOLFSSL_MLDSA_NO_LARGE_CODE"]}, +{"name": "pq-nonblock-smallmem", "minutes": 4.0, + "comment": "Non-blocking ML-DSA (WC_MLDSA_NONBLOCK) and ML-KEM (WC_MLKEM_NONBLOCK) built on the small-mem paths. make check runs mldsa_nonblock_test and mlkem_nonblock_test, which drive each operation's resumable MP_WOULDBLOCK state machine and confirm the result matches the blocking path.", + "configure": ["--enable-dilithium=yes,small", "--enable-mlkem", + "--enable-experimental", + "CPPFLAGS=-pedantic -Wdeclaration-after-statement -Wnull-dereference -DWOLFSSL_MLDSA_SIGN_SMALL_MEM -DWOLFSSL_MLDSA_VERIFY_SMALL_MEM -DWOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM -DWOLFSSL_MLDSA_NO_LARGE_CODE -DWC_MLDSA_NONBLOCK -DWOLFSSL_MLKEM_MAKEKEY_SMALL_MEM -DWOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM -DWC_MLKEM_NONBLOCK"]}, +{"name": "pq-nonblock-smallmem-dynkeys", "minutes": 2.5, + "comment": "Non-blocking ML-KEM with the cached matrix A and dynamic key buffers: a resumed keygen must keep the vectors its first slice allocated, and encapsulation must read the cached matrix across yields", + "configure": ["--enable-cryptonly", "--enable-mlkem", "--enable-smallstack", + "CPPFLAGS=-DWOLFSSL_MLKEM_MAKEKEY_SMALL_MEM -DWOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM -DWC_MLKEM_NONBLOCK -DWOLFSSL_MLKEM_CACHE_A -DWOLFSSL_MLKEM_DYNAMIC_KEYS"]}, {"name": "slhdsa-pqc-only", "minutes": 22.3, "comment": "PQC-only TLS 1.3: ML-KEM key exchange + SLH-DSA (FIPS 205) auth with RSA, ECC and DH disabled; exercises the SLH-DSA CertificateVerify path", "configure": ["--enable-mlkem", "--enable-slhdsa", diff --git a/doc/dox_comments/header_files/wc_mldsa.h b/doc/dox_comments/header_files/wc_mldsa.h index 032e430712c..701b2680863 100644 --- a/doc/dox_comments/header_files/wc_mldsa.h +++ b/doc/dox_comments/header_files/wc_mldsa.h @@ -672,9 +672,11 @@ int wc_MlDsaKey_Verify(wc_MlDsaKey* key, const byte* sig, word32 sigLen, /*! \ingroup ML_DSA - \brief Returns the size in bytes of the encoded private key for - the parameter set selected on this key. Equivalent to - wc_MlDsaKey_PrivSize() and provided for API compatibility. + \brief Returns the size in bytes of the encoded private key alone + (FIPS 204 sk) for the parameter set selected on this key. This is + the length wc_MlDsaKey_ExportPrivRaw() writes and + wc_MlDsaKey_ImportPrivRaw() expects. It is not the same value as + wc_MlDsaKey_PrivSize(), which includes the public key. \return Encoded private key size in bytes on success (positive value). @@ -692,15 +694,21 @@ int wc_MlDsaKey_Size(wc_MlDsaKey* key); /*! \ingroup ML_DSA - \brief Returns the size in bytes of the encoded private key for - the parameter set selected on this key. + \brief Returns the size in bytes of the encoded private key with the + encoded public key appended, for the parameter set selected on this + key. This is wc_MlDsaKey_Size() plus wc_MlDsaKey_PubSize(), the + length of the private-plus-public encoding that + wc_MlDsaKey_ImportKey() and wc_MlDsaKey_PrivateKeyDecode() accept. + The name is kept for compatibility with wc_dilithium_priv_size(). - \return Encoded private key size on success (positive value). + \return Encoded private-plus-public key size on success (positive + value). \return BAD_FUNC_ARG if key is NULL or no parameter set is selected. \param [in] key Pointer to a wc_MlDsaKey with parameters set. + \sa wc_MlDsaKey_Size \sa wc_MlDsaKey_PubSize \sa wc_MlDsaKey_GetPrivLen */ @@ -743,18 +751,20 @@ int wc_MlDsaKey_SigSize(wc_MlDsaKey* key); /*! \ingroup ML_DSA - \brief Writes the encoded private key size into *len. Equivalent - information to wc_MlDsaKey_PrivSize() but uses an out-parameter - style. + \brief Writes the size of the encoded private key with the public + key appended into *len. Equivalent information to + wc_MlDsaKey_PrivSize() but uses an out-parameter style. For the + private key alone use wc_MlDsaKey_Size(). \return 0 on success. \return BAD_FUNC_ARG if key or len is NULL, or no parameter set is selected. \param [in] key Pointer to a wc_MlDsaKey with parameters set. - \param [out] len Receives the private key size in bytes. + \param [out] len Receives the private-plus-public key size in bytes. \sa wc_MlDsaKey_PrivSize + \sa wc_MlDsaKey_Size */ int wc_MlDsaKey_GetPrivLen(wc_MlDsaKey* key, int* len); diff --git a/tests/api/test_mldsa.c b/tests/api/test_mldsa.c index 0d0d2fbc0a1..6a89e617983 100644 --- a/tests/api/test_mldsa.c +++ b/tests/api/test_mldsa.c @@ -631,6 +631,9 @@ int test_mldsa(void) ExpectIntEQ(wc_MlDsaKey_Size(key), WC_MLDSA_87_KEY_SIZE); #ifdef WOLFSSL_MLDSA_PUBLIC_KEY ExpectIntEQ(wc_MlDsaKey_PrivSize(key), WC_MLDSA_87_PRV_KEY_SIZE); + /* PrivSize is the private key with the public key appended. */ + ExpectIntEQ(wc_MlDsaKey_PrivSize(key), + wc_MlDsaKey_Size(key) + wc_MlDsaKey_PubSize(key)); #endif #endif #ifdef WOLFSSL_MLDSA_PUBLIC_KEY diff --git a/wolfcrypt/src/wc_mldsa.c b/wolfcrypt/src/wc_mldsa.c index 1c6a27b1ee1..20a48f43852 100644 --- a/wolfcrypt/src/wc_mldsa.c +++ b/wolfcrypt/src/wc_mldsa.c @@ -260,6 +260,45 @@ #error "WOLFSSL_MLDSA_SIGN_CHECK_Y/_W0 are not allowed in a FIPS build" #endif +/* Non-blocking (chunked) signing/keygen/verify is built only on the + * small-memory paths, which already process a row/polynomial at a time. */ +#ifdef WC_MLDSA_NONBLOCK + #ifdef WOLF_CRYPTO_CB_ONLY_MLDSA + #error "WC_MLDSA_NONBLOCK needs the software implementation" + #endif + #if !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_SIGN_SMALL_MEM) + #error "WC_MLDSA_NONBLOCK requires WOLFSSL_MLDSA_SIGN_SMALL_MEM" + #endif + #if !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + (defined(WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC) || \ + defined(WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC_A) || \ + defined(WOLFSSL_MLDSA_SIGN_SMALLEST_MEM)) + #error "WC_MLDSA_NONBLOCK sign needs plain SIGN_SMALL_MEM" + #endif + #if !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM) + #error "WC_MLDSA_NONBLOCK requires WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM" + #endif + #if !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + !defined(WOLFSSL_MLDSA_VERIFY_SMALL_MEM) + #error "WC_MLDSA_NONBLOCK requires WOLFSSL_MLDSA_VERIFY_SMALL_MEM" + #endif + #if !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + (FIPS_VERSION3_GE(7,0,0) || defined(WOLFSSL_VALIDATE_MLDSA_KEYGEN)) + #error "WC_MLDSA_NONBLOCK keygen unsupported with the keygen PCT" + #endif + #if !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + (defined(WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM) || \ + defined(WOLFSSL_MLDSA_VERIFY_NO_MALLOC)) + #error "WC_MLDSA_NONBLOCK verify needs plain VERIFY_SMALL_MEM" + #endif + #ifdef WOLFSSL_NO_MALLOC + #error "WC_MLDSA_NONBLOCK keeps working vectors on the heap" + #endif +static void mldsa_nb_drop_stale_work(wc_MlDsaKey* key, word32 sz); +#endif /* WC_MLDSA_NONBLOCK */ + /* Signing drives the whole-vector helpers when it holds a full vector: the * default implementation, and PRECALC_A which multiplies the pre-calculated * rows of matrix A as a vector. */ @@ -11779,6 +11818,9 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) unsigned int r; byte kl[2]; unsigned int allocSz = 0; + /* Phase cursor for the resumable switch below. Stays 0 (runs every phase + * via fall-through) unless a non-blocking context advances it. */ + int nbState = 0; #ifdef WOLFSSL_MLDSA_DYNAMIC_KEYS ret = mldsa_alloc_priv_buf(key); @@ -11808,7 +11850,23 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) /* t64 */ allocSz += (unsigned int)MLDSA_POLY_SIZE * 2U; #endif - s1 = (sword32*)XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); + #ifdef WC_MLDSA_NONBLOCK + mldsa_nb_drop_stale_work(key, allocSz); + if ((key->nb != NULL) && (key->nb->work != NULL)) { + /* Resume: reuse the working allocation from the previous call. */ + s1 = (sword32*)key->nb->work; + } + else + #endif + { + s1 = (sword32*)XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); + #ifdef WC_MLDSA_NONBLOCK + if ((s1 != NULL) && (key->nb != NULL)) { + key->nb->work = s1; + key->nb->workSz = allocSz; + } + #endif + } if (s1 == NULL) { ret = MEMORY_E; } @@ -11825,6 +11883,15 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) } } +#ifdef WC_MLDSA_NONBLOCK + if (key->nb != NULL) { + nbState = key->nb->state; + } +#endif + /* Resumable phases: with a non-blocking context each case yields after it + * runs; a NULL context falls through them all and signs in one call. */ + switch (nbState) { + case 0: if (ret == 0) { #ifdef WOLFSSL_MLDSA_FIPS204_DRAFT if (key->params->level >= WC_ML_DSA_DRAFT) { @@ -11846,6 +11913,15 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) pub_seed, MLDSA_SEEDS_SZ); } } +#ifdef WC_MLDSA_NONBLOCK + if ((key->nb != NULL) && (ret == 0)) { + key->nb->state = 1; + ret = MP_WOULDBLOCK; + break; + } +#endif + FALL_THROUGH; + case 1: if (ret == 0) { byte* priv_seed = key->k + MLDSA_PUB_SEED_SZ; @@ -11856,20 +11932,20 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) ret = mldsa_expand_s(&key->shake, priv_seed, params->eta, s1, params->l, s2, params->k, key->heap); } +#ifdef WC_MLDSA_NONBLOCK + if ((key->nb != NULL) && (ret == 0)) { + key->nb->state = 2; + ret = MP_WOULDBLOCK; + break; + } +#endif + FALL_THROUGH; + case 2: if (ret == 0) { byte* k = pub_seed + MLDSA_PUB_SEED_SZ; - byte* tr = k + MLDSA_K_SZ; - byte* s1p = tr + MLDSA_TR_SZ; + byte* s1p = k + MLDSA_K_SZ + MLDSA_TR_SZ; byte* s2p = s1p + params->s1EncSz; - byte* t0 = s2p + params->s2EncSz; - byte* t1 = key->p + MLDSA_PUB_SEED_SZ; byte kBuf[MLDSA_K_SZ]; - byte aseed[MLDSA_GEN_A_SEED_SZ]; - /* One decoded s2 polynomial, held after t in the dead s2 vector. */ - sword32* s2t = t + MLDSA_N; - sword32* tt = t; - const byte* s2pt = s2p; - word32 s2Stride = (word32)params->s2EncSz / params->k; #ifdef WOLFSSL_CHECK_MEM_ZERO wc_MemZero_Add("mldsa make_key kBuf", kBuf, sizeof(kBuf)); @@ -11887,37 +11963,83 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) if (ret == 0) { ret = mldsa_vec_encode_eta_bits(s2, params->k, params->eta, s2p); } - /* Step 5: NTT(s1) */ if (ret == 0) { ret = mldsa_vec_ntt_small_full(s1, params->l); } - /* Step 5: t <- NTT-1(A_circum o NTT(s1)) + s2 */ + #ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(kBuf, sizeof(kBuf)); + #endif + } +#ifdef WC_MLDSA_NONBLOCK + if ((key->nb != NULL) && (ret == 0)) { + key->nb->state = 3; + key->nb->row = 0; + ret = MP_WOULDBLOCK; + break; + } +#endif + FALL_THROUGH; + case 3: + if (ret == 0) { + /* Step 5: t <- NTT-1(A_circum o NTT(s1)) + s2. One row of t per call + * when non-blocking, resuming at the saved row. */ + byte* k = pub_seed + MLDSA_PUB_SEED_SZ; + byte* s1p = k + MLDSA_K_SZ + MLDSA_TR_SZ; + byte* s2p = s1p + params->s1EncSz; + byte* t0base = s2p + params->s2EncSz; + byte* t1base = key->p + MLDSA_PUB_SEED_SZ; + word32 s2Stride = (word32)params->s2EncSz / params->k; + unsigned int r0 = 0; + byte aseed[MLDSA_GEN_A_SEED_SZ]; + /* One decoded s2 polynomial, held after t in the dead s2 vector. */ + sword32* s2t = t + MLDSA_N; + sword32* tt = t; + const byte* s2pt; + byte* t0; + byte* t1; + + #ifdef WC_MLDSA_NONBLOCK + if (key->nb != NULL) { + r0 = key->nb->row; + } + #endif + /* Per-row output offsets are recomputed from the resume row. */ + s2pt = s2p + (size_t)r0 * s2Stride; + t0 = t0base + (size_t)r0 * (MLDSA_D * MLDSA_N / 8); + t1 = t1base + (size_t)r0 * (MLDSA_U * MLDSA_N / 8); XMEMCPY(aseed, pub_seed, MLDSA_PUB_SEED_SZ); - for (r = 0; (ret == 0) && (r < params->k); r++) { + for (r = r0; (ret == 0) && (r < params->k); r++) { ret = mldsa_calc_t_row(&key->shake, aseed, (byte)r, s1, params->l, s2pt, params->eta, tt, s2t, a, h, t64); - - /* Step 6, Step 7, Step 9. Alg 22 Steps 2-4, Alg 24 Steps 8-10. - * Decompose t in t0 and t1 and encode into public and private - * key. One polynomial at a time trades the AVX-512 paired encoder - * for the s2Sz saving this implementation exists to make. */ + /* Decompose t into t0 and t1 and encode into the keys, one poly. */ if (ret == 0) { ret = mldsa_vec_encode_t0_t1(tt, 1, t0, t1); } - s2pt += s2Stride; t0 += MLDSA_D * MLDSA_N / 8; t1 += MLDSA_U * MLDSA_N / 8; + #ifdef WC_MLDSA_NONBLOCK + /* Yield per row so one slice is about a single row's work. */ + if ((key->nb != NULL) && (ret == 0) && + (r + 1 < (unsigned int)params->k)) { + key->nb->row = (word16)(r + 1); + key->nb->state = 3; + ret = MP_WOULDBLOCK; + } + #endif } - if (ret == 0) { - /* Step 8. Alg 24, Step 1: Hash public key into private key. */ - ret = mldsa_shake256(&key->shake, key->p, params->pkSz, tr, - MLDSA_TR_SZ); - } - #ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(kBuf, sizeof(kBuf)); - #endif + } + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) { + break; + } + FALL_THROUGH; + case 4: + if (ret == 0) { + byte* tr = pub_seed + MLDSA_PUB_SEED_SZ + MLDSA_K_SZ; + /* Step 8. Alg 24, Step 1: Hash public key into private key. */ + ret = mldsa_shake256(&key->shake, key->p, params->pkSz, tr, + MLDSA_TR_SZ); } if (ret == 0) { /* Public key and private key are available. */ @@ -11935,17 +12057,46 @@ static int mldsa_make_key_from_seed(wc_MlDsaKey* key, const byte* seed) #endif } + break; + default: + break; + } + /* Zeroize the whole buffer before freeing. It holds the private vectors * s1, s2 and t at the front, then the public matrix polynomial a, then * (POLY64 builds) the t64 accumulator holding A o NTT(s1) - from which * s1 is recoverable - and finally the public rejection-sampling buffer * h. As the secret material is not contiguous, zeroize the entire * allocation rather than a sub-range. */ - if (s1 != NULL) { - ForceZero(s1, allocSz); +#ifdef WC_MLDSA_NONBLOCK + if ((key->nb != NULL) && (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK))) { + /* Yielding: keep the working allocation for the next call. */ } - XFREE(s1, key->heap, DYNAMIC_TYPE_MLDSA); - if (ret != 0) { + else +#endif + { + if (s1 != NULL) { + ForceZero(s1, allocSz); + } + XFREE(s1, key->heap, DYNAMIC_TYPE_MLDSA); +#ifdef WC_MLDSA_NONBLOCK + if (key->nb != NULL) { + /* Final success or error: release and reset the context. */ + key->nb->work = NULL; + key->nb->state = 0; + key->nb->kappa = 0; + key->nb->row = 0; + key->nb->hintPos = 0; + key->nb->started = 0; + ForceZero(key->nb->seedMu, sizeof(key->nb->seedMu)); + } +#endif + } + if ((ret != 0) +#ifdef WC_MLDSA_NONBLOCK + && (ret != WC_NO_ERR_TRACE(MP_WOULDBLOCK)) +#endif + ) { mldsa_wipe_priv(key); mldsa_shake_wipe(&key->shake); } @@ -12038,8 +12189,25 @@ static int mldsa_make_key(wc_MlDsaKey* key, WC_RNG* rng) int ret; byte seed[MLDSA_SEED_SZ]; - /* Step 1: Generate a 32 byte random seed. */ - ret = wc_RNG_GenerateBlock(rng, seed, MLDSA_SEED_SZ); +#ifdef WC_MLDSA_NONBLOCK + if ((key->nb != NULL) && key->nb->started) { + /* Resume: reuse the seed drawn on the first call so each resumed phase + * derives the same key. */ + XMEMCPY(seed, key->nb->seedMu, MLDSA_SEED_SZ); + ret = 0; + } + else +#endif + { + /* Step 1: Generate a 32 byte random seed. */ + ret = wc_RNG_GenerateBlock(rng, seed, MLDSA_SEED_SZ); +#ifdef WC_MLDSA_NONBLOCK + if ((ret == 0) && (key->nb != NULL)) { + XMEMCPY(key->nb->seedMu, seed, MLDSA_SEED_SZ); + key->nb->started = 1; + } +#endif + } /* seed now holds the secret key seed; register before the derive step so a * future early-exit ahead of the ForceZero cannot leak it. */ #ifdef WOLFSSL_CHECK_MEM_ZERO @@ -12642,7 +12810,23 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, (unsigned int)maxK * params->l * (unsigned int)MLDSA_POLY_SIZE; #endif - y = (sword32*)XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); + #ifdef WC_MLDSA_NONBLOCK + mldsa_nb_drop_stale_work(key, allocSz); + if ((key->nb != NULL) && (key->nb->work != NULL)) { + /* Resume: reuse the working allocation from the previous call. */ + y = (sword32*)key->nb->work; + } + else + #endif + { + y = (sword32*)XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); + #ifdef WC_MLDSA_NONBLOCK + if ((y != NULL) && (key->nb != NULL)) { + key->nb->work = y; + key->nb->workSz = allocSz; + } + #endif + } if (y == NULL) { ret = MEMORY_E; } @@ -12712,7 +12896,11 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, } #endif if (ret == 0) { + #ifdef WC_MLDSA_NONBLOCK + word16 kappa = (key->nb != NULL) ? key->nb->kappa : 0; + #else word16 kappa = 0; + #endif int valid; /* Step 11: Start rejection sampling loop */ @@ -12801,7 +12989,18 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, /* Alg 26. Step 2: Loop over second dimension of matrix. * A column at a time transforms each polynomial of y once. With * every row pre-calculated there is nothing left to stream. */ - for (s = 0; (ret == 0) && valid && (rStart < params->k) && + s = 0; + #ifdef WC_MLDSA_NONBLOCK + /* Resume the column loop at the row saved on the last yield. */ + if (key->nb != NULL) { + s = (byte)key->nb->row; + #ifdef WC_MLDSA_FAULT_HARDEN + /* The fault-check shadow of the column index resumes too. */ + yc = y + (unsigned int)s * MLDSA_N; + #endif + } + #endif + for (; (ret == 0) && valid && (rStart < params->k) && (s < params->l); s++) { unsigned int e; @@ -12907,6 +13106,16 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, #ifdef WC_MLDSA_FAULT_HARDEN yc += MLDSA_N; #endif + #ifdef WC_MLDSA_NONBLOCK + /* Yield per column so one slice is about a single row's work. + * The w0 accumulators persist in the working allocation and + * expand_mask re-derives y deterministically on resume. */ + if ((key->nb != NULL) && (ret == 0) && valid && + (s + 1 < params->l)) { + key->nb->row = (word16)(s + 1); + ret = MP_WOULDBLOCK; + } + #endif } /* Steps 13-15: Invert transform, decompose and encode each row of @@ -13213,16 +13422,33 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, */ kappa = (word16)(kappa + params->l); } + #ifdef WC_MLDSA_NONBLOCK + /* Rejected attempt: advance the nonce, restart the column loop at + * row 0 for the next attempt, and yield. */ + if ((key->nb != NULL) && (ret == 0) && (!valid)) { + key->nb->kappa = kappa; + key->nb->row = 0; + ret = MP_WOULDBLOCK; + } + #endif } /* Step 11: Check we have a valid signature. */ while ((ret == 0) && (!valid)); - if (ret != 0) { + if ((ret != 0) + #ifdef WC_MLDSA_NONBLOCK + && (ret != WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + #endif + ) { /* sig holds a rejected candidate (ISO/IEC 19790:2012 7.9.7). */ ForceZero(sig, params->sigSz); } } - if ((ret != 0) && (*sigLen == params->sigSz)) { + if ((ret != 0) && +#ifdef WC_MLDSA_NONBLOCK + (ret != WC_NO_ERR_TRACE(MP_WOULDBLOCK)) && +#endif + (*sigLen == params->sigSz)) { /* FIPS 204 3.6.3: a failed sign leaves no part of a signature. */ ForceZero(sig, params->sigSz); } @@ -13230,11 +13456,35 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, #ifdef WOLFSSL_CHECK_MEM_ZERO wc_MemZero_Check(priv_rand_seed, sizeof(priv_rand_seed)); #endif - if (y != NULL) { - ForceZero(y, allocSz); +#ifdef WC_MLDSA_NONBLOCK + if ((key->nb != NULL) && (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK))) { + /* Yielding: keep the working allocation for the next call. */ } - XFREE(y, key->heap, DYNAMIC_TYPE_MLDSA); - if (ret != 0) { + else +#endif + { + if (y != NULL) { + ForceZero(y, allocSz); + } + XFREE(y, key->heap, DYNAMIC_TYPE_MLDSA); +#ifdef WC_MLDSA_NONBLOCK + if (key->nb != NULL) { + /* Final success or error: release and reset the context. */ + key->nb->work = NULL; + key->nb->state = 0; + key->nb->kappa = 0; + key->nb->row = 0; + key->nb->hintPos = 0; + key->nb->started = 0; + ForceZero(key->nb->seedMu, sizeof(key->nb->seedMu)); + } +#endif + } + if ((ret != 0) +#ifdef WC_MLDSA_NONBLOCK + && (ret != WC_NO_ERR_TRACE(MP_WOULDBLOCK)) +#endif + ) { mldsa_shake_wipe(&key->shake); } return ret; @@ -13876,14 +14126,30 @@ static int mldsa_sign_ctx_msg(wc_MlDsaKey* key, WC_RNG* rng, ret = BAD_FUNC_ARG; } - if (ret == 0) { - /* Step 7: Generate random seed. */ - ret = wc_RNG_GenerateBlock(rng, seedMu, MLDSA_RND_SZ); +#ifdef WC_MLDSA_NONBLOCK + if ((ret == 0) && (key->nb != NULL) && key->nb->started) { + /* Resume: reuse the rnd||mu drawn on the first call so every attempt + * signs under the same secret mask. */ + XMEMCPY(seedMu, key->nb->seedMu, sizeof(seedMu)); } - if (ret == 0) { - /* Step 6. Calculate mu. */ - ret = mldsa_hash256_ctx_msg(&key->shake, tr, MLDSA_TR_SZ, 0, - ctx, ctxLen, msg, msgLen, mu, MLDSA_MU_SZ); + else +#endif + { + if (ret == 0) { + /* Step 7: Generate random seed. */ + ret = wc_RNG_GenerateBlock(rng, seedMu, MLDSA_RND_SZ); + } + if (ret == 0) { + /* Step 6. Calculate mu. */ + ret = mldsa_hash256_ctx_msg(&key->shake, tr, MLDSA_TR_SZ, 0, + ctx, ctxLen, msg, msgLen, mu, MLDSA_MU_SZ); + } +#ifdef WC_MLDSA_NONBLOCK + if ((ret == 0) && (key->nb != NULL)) { + XMEMCPY(key->nb->seedMu, seedMu, sizeof(seedMu)); + key->nb->started = 1; + } +#endif } if (ret == 0) { ret = mldsa_sign_with_seed_mu(key, seedMu, sig, sigLen); @@ -14442,6 +14708,9 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, byte o; byte* encW1; byte* seed = commit_calc; + /* Phase cursor for the resumable switch below. Stays 0 (runs every phase + * via fall-through) unless a non-blocking context advances it. */ + int nbState = 0; #ifdef WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM /* Bytes of encoded z per polynomial - z is streamed one poly at a time. */ word32 zStride = (word32)(MLDSA_N / 8) * (word32)(params->gamma1_bits + 1); @@ -14477,12 +14746,35 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 allocSz += (unsigned int)MLDSA_POLY_SIZE * 2U; #endif - z = (sword32*)XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); + #ifdef WC_MLDSA_NONBLOCK + mldsa_nb_drop_stale_work(key, allocSz); + if ((key->nb != NULL) && (key->nb->work != NULL)) { + /* Resume: reuse the working allocation from the previous call. */ + z = (sword32*)key->nb->work; + } + else + #endif + { + z = (sword32*)XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); + #ifdef WC_MLDSA_NONBLOCK + if ((z != NULL) && (key->nb != NULL)) { + key->nb->work = z; + key->nb->workSz = allocSz; + } + #endif + } if (z == NULL) { ret = MEMORY_E; } else { - XMEMSET(z, 0, allocSz); + #ifdef WC_MLDSA_NONBLOCK + /* Only zero the buffers on the first call; a resume must keep what + * earlier phases wrote. */ + if ((key->nb == NULL) || (key->nb->state == 0)) + #endif + { + XMEMSET(z, 0, allocSz); + } c = z + zSz / sizeof(*t1); w = c + MLDSA_N; t1 = w + MLDSA_N; @@ -14510,6 +14802,21 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, } #endif +#ifdef WC_MLDSA_NONBLOCK + if (key->nb != NULL) { + nbState = key->nb->state; + } + /* State only advances past the decode/NTT/sample phases once z checked out + * valid, so on resume valid is implied. */ + if (nbState >= 1) { + valid = 1; + } +#endif + /* Resumable phases: each case yields after it runs for a non-blocking + * context; a NULL context falls through them all and verifies in one + * call. */ + switch (nbState) { + case 0: if (ret == 0) { /* Step 13: Check z is valid - values are low enough. */ hi = ((sword32)1 << params->gamma1_bits) - params->beta; @@ -14555,16 +14862,50 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, params->lambda / 4, params->tau, c, block); #endif } +#ifdef WC_MLDSA_NONBLOCK + /* Yield before the expensive per-row matrix loop. Only reached when the + * signature decoded valid; an invalid z falls through to the result. */ + if ((key->nb != NULL) && (ret == 0) && valid) { + key->nb->state = 1; + ret = MP_WOULDBLOCK; + break; + } +#endif + FALL_THROUGH; + case 1: if ((ret == 0) && valid) { ret = mldsa_ntt_small_full(c); + } +#ifdef WC_MLDSA_NONBLOCK + if ((key->nb != NULL) && (ret == 0) && valid) { + key->nb->state = 2; + key->nb->row = 0; + key->nb->hintPos = 0; + ret = MP_WOULDBLOCK; + break; + } +#endif + FALL_THROUGH; + case 2: + if ((ret == 0) && valid) { + unsigned int r0 = 0; o = 0; - encW1 = w1e; + #ifdef WC_MLDSA_NONBLOCK + if (key->nb != NULL) { + r0 = key->nb->row; + o = key->nb->hintPos; + } + #endif + /* Recompute the per-row output offsets from the resume row. */ + encW1 = w1e + (size_t)r0 * ((size_t)params->w1EncSz / params->k); + t1p = pub_seed + MLDSA_PUB_SEED_SZ + + (size_t)r0 * (MLDSA_U * MLDSA_N / 8); /* Copy the seed into a buffer that has space for s and r. */ XMEMCPY(seed, pub_seed, MLDSA_PUB_SEED_SZ); /* Step 1: Loop over first dimension of matrix. */ - for (r = 0; (ret == 0) && (r < params->k); r++) { + for (r = r0; (ret == 0) && (r < params->k); r++) { unsigned int s; unsigned int e; const sword32* zt = z; @@ -14784,8 +15125,23 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, #endif { } + #ifdef WC_MLDSA_NONBLOCK + /* Yield per row so one slice is about a single row's work. */ + if ((key->nb != NULL) && (ret == 0) && + (r + 1 < (unsigned int)params->k)) { + key->nb->row = (word16)(r + 1); + key->nb->hintPos = o; + key->nb->state = 2; + ret = MP_WOULDBLOCK; + } + #endif } } + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) { + break; + } + FALL_THROUGH; + case 3: if ((ret == 0) && valid) { /* Step 12: Hash mu and encoded w1. */ ret = mldsa_hash256(&key->shake, mu, MLDSA_MU_SZ, w1e, @@ -14795,10 +15151,34 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, /* Step 13: Compare commit. */ valid = (XMEMCMP(commit, commit_calc, params->lambda / 4) == 0); } + break; + default: + break; + } *res = (ret == 0) ? valid : 0; #ifndef WOLFSSL_MLDSA_VERIFY_NO_MALLOC - XFREE(z, key->heap, DYNAMIC_TYPE_MLDSA); +#ifdef WC_MLDSA_NONBLOCK + if ((key->nb != NULL) && (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK))) { + /* Yielding: keep the working allocation for the next call. */ + } + else +#endif + { + XFREE(z, key->heap, DYNAMIC_TYPE_MLDSA); +#ifdef WC_MLDSA_NONBLOCK + if (key->nb != NULL) { + /* Final success or error: release and reset the context. */ + key->nb->work = NULL; + key->nb->state = 0; + key->nb->kappa = 0; + key->nb->row = 0; + key->nb->hintPos = 0; + key->nb->started = 0; + ForceZero(key->nb->seedMu, sizeof(key->nb->seedMu)); + } +#endif + } #endif return ret; #endif /* !WOLFSSL_MLDSA_VERIFY_SMALL_MEM */ @@ -16364,6 +16744,66 @@ int wc_MlDsaKey_Init(wc_MlDsaKey* key, void* heap, int devId) return ret; } +#ifdef WC_MLDSA_NONBLOCK +/* Release whatever an in-flight non-blocking operation left in the attached + * context and reset it, so an abandoned operation neither leaks nor leaves + * secret material behind. */ +static void mldsa_nb_release(wc_MlDsaKey* key) +{ + wc_MlDsaNonBlock* nb = key->nb; + + if (nb != NULL) { + if (nb->work != NULL) { + ForceZero(nb->work, nb->workSz); + XFREE(nb->work, key->heap, DYNAMIC_TYPE_MLDSA); + } + ForceZero(nb, sizeof(*nb)); + } +} + +/* Drop a work buffer left behind by an abandoned operation of another kind: + * its size differs from the one this operation needs, and so do its cursors. + * The randomness drawn for this operation is kept. */ +static void mldsa_nb_drop_stale_work(wc_MlDsaKey* key, word32 sz) +{ + wc_MlDsaNonBlock* nb = key->nb; + + if ((nb != NULL) && (nb->work != NULL) && (nb->workSz != sz)) { + ForceZero(nb->work, nb->workSz); + XFREE(nb->work, key->heap, DYNAMIC_TYPE_MLDSA); + nb->work = NULL; + nb->workSz = 0; + nb->state = 0; + nb->kappa = 0; + nb->row = 0; + nb->hintPos = 0; + } +} + +/* Attach a non-blocking context to the key, or clear it with nb == NULL. While + * set, MakeKey/Sign/Verify run one chunk per call and return MP_WOULDBLOCK + * until the operation completes. The context is owned by the caller and must + * outlive the operation. Attaching, clearing or freeing the key abandons any + * operation still in flight on the current context. */ +int wc_MlDsaKey_set_nonblock(wc_MlDsaKey* key, wc_MlDsaNonBlock* nb) +{ + int ret = 0; + + if (key == NULL) { + ret = BAD_FUNC_ARG; + } + else { + mldsa_nb_release(key); + if (nb != NULL) { + XMEMSET(nb, 0, sizeof(*nb)); + } + key->nb = nb; + } + + return ret; +} +#endif /* WC_MLDSA_NONBLOCK */ + #ifdef WOLF_PRIVATE_KEY_ID /* Level set on a key held by a device: the highest this build has, as the * level of the device key is not known here. */ @@ -16599,6 +17039,11 @@ int wc_MlDsaKey_GetParams(wc_MlDsaKey* key, byte* level) void wc_MlDsaKey_Free(wc_MlDsaKey* key) { if (key != NULL) { +#ifdef WC_MLDSA_NONBLOCK + /* An operation abandoned mid-flight must not leak its scratch. */ + mldsa_nb_release(key); + key->nb = NULL; +#endif #if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_FREE) if (key->devId != INVALID_DEVID) { (void)wc_CryptoCb_Free(key->devId, WC_ALGO_TYPE_PK, @@ -16694,10 +17139,12 @@ int wc_MlDsaKey_Size(wc_MlDsaKey* key) } #ifdef WOLFSSL_MLDSA_PUBLIC_KEY -/* Returns the size of a ML-DSA private plus public key. +/* Returns the size of a ML-DSA private key with the public key appended, + * the encoding wc_MlDsaKey_ImportKey() and wc_MlDsaKey_PrivateKeyDecode() + * accept. wc_MlDsaKey_Size() is the private key alone. * * @param [in] key ML-DSA private/public key. - * @return Private key size on success for set level. + * @return Private plus public key size on success for set level. * @return BAD_FUNC_ARG when key is NULL or level not set, */ int wc_MlDsaKey_PrivSize(wc_MlDsaKey* key) @@ -16718,6 +17165,7 @@ int wc_MlDsaKey_PrivSize(wc_MlDsaKey* key) else if (key->params->level == WC_ML_DSA_87_DRAFT) { ret = WC_MLDSA_87_PRV_KEY_SIZE; } + else #endif if (key->level == WC_ML_DSA_44) { ret = WC_MLDSA_44_PRV_KEY_SIZE; @@ -16733,10 +17181,11 @@ int wc_MlDsaKey_PrivSize(wc_MlDsaKey* key) return ret; } -/* Returns the size of a ML-DSA private plus public key. +/* Returns the size of a ML-DSA private key with the public key appended. + * Same value as wc_MlDsaKey_PrivSize(). * * @param [in] key ML-DSA private/public key. - * @param [out] len Private key size for set level. + * @param [out] len Private plus public key size for set level. * @return 0 on success. * @return BAD_FUNC_ARG when key or len is NULL or level not set, */ diff --git a/wolfcrypt/src/wc_mlkem.c b/wolfcrypt/src/wc_mlkem.c index b16d4ed972c..d2a5bc91e00 100644 --- a/wolfcrypt/src/wc_mlkem.c +++ b/wolfcrypt/src/wc_mlkem.c @@ -144,6 +144,34 @@ #error "Cannot use dynamic key buffers without malloc" #endif +/* Non-blocking (chunked) keygen/encapsulate/decapsulate is built only on the + * small-memory paths, which already process a row/polynomial at a time. */ +#ifdef WC_MLKEM_NONBLOCK + #ifdef WOLF_CRYPTO_CB_ONLY_MLKEM + #error "WC_MLKEM_NONBLOCK needs the software implementation" + #endif + #if !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_MAKEKEY_SMALL_MEM) + #error "WC_MLKEM_NONBLOCK requires WOLFSSL_MLKEM_MAKEKEY_SMALL_MEM" + #endif + /* Decapsulate re-encrypts, so it needs the small-mem encapsulate path + * too. */ + #if (!defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) || \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE)) && \ + !defined(WOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM) + #error "WC_MLKEM_NONBLOCK requires WOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM" + #endif + #if !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + (FIPS_VERSION3_GE(7,0,0) || defined(WOLFSSL_VALIDATE_MLKEM_KEYGEN)) + #error "WC_MLKEM_NONBLOCK keygen unsupported with the keygen PCT" + #endif + #ifdef WOLFSSL_NO_MALLOC + #error "WC_MLKEM_NONBLOCK keeps working vectors on the heap" + #endif +static void mlkemkey_nb_release(MlKemKey* key); +static void mlkemkey_nb_drop_stale_work(MlKemKey* key, word32 sz); +#endif /* WC_MLKEM_NONBLOCK */ + #if defined(WOLFSSL_KYBER1024) || defined(WOLFSSL_WC_ML_KEM_1024) #define MLKEM_MAX_COMP_POLY_SZ MLKEM_POLY_COMPRESSED_SZ(11) #else @@ -503,6 +531,9 @@ int wc_MlKemKey_Init(MlKemKey* key, int type, void* heap, int devId) /* A zeroed object reads as WC_ML_KEM_512, so record the difference * here rather than inferring it from the type. */ key->flags = (type == WC_ML_KEM_TYPE_UNSET) ? 0 : MLKEM_FLAG_TYPE_SET; + #ifdef WC_MLKEM_NONBLOCK + key->nb = NULL; + #endif #ifdef WOLFSSL_MLKEM_DYNAMIC_KEYS key->priv = NULL; @@ -675,6 +706,11 @@ int wc_MlKemKey_Free(MlKemKey* key) /* Clear flags as values are no longer set. */ key->flags = 0; +#ifdef WC_MLKEM_NONBLOCK + /* An operation abandoned mid-flight must not leak its scratch. */ + mlkemkey_nb_release(key); + key->nb = NULL; +#endif #ifdef WOLF_CRYPTO_CB /* Mark the key as having no device so a second free does not call * out to it again. */ @@ -686,6 +722,70 @@ int wc_MlKemKey_Free(MlKemKey* key) return 0; } +#ifdef WC_MLKEM_NONBLOCK +/* Release whatever an in-flight non-blocking operation left in the attached + * context and reset it, so an abandoned operation neither leaks nor leaves + * secret material behind. */ +static void mlkemkey_nb_release(MlKemKey* key) +{ + wc_MlKemNonBlock* nb = key->nb; + + if (nb != NULL) { + if (nb->work != NULL) { + ForceZero(nb->work, nb->workSz); + XFREE(nb->work, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + if (nb->decWork != NULL) { + ForceZero(nb->decWork, nb->decWorkSz); + XFREE(nb->decWork, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + } + ForceZero(nb, sizeof(*nb)); + } +} + +/* Drop a work buffer left behind by an abandoned operation of another kind: + * its size differs from the one this operation needs, and so do its cursors. + * The randomness drawn for this operation is kept. */ +static void mlkemkey_nb_drop_stale_work(MlKemKey* key, word32 sz) +{ + wc_MlKemNonBlock* nb = key->nb; + + if ((nb != NULL) && (nb->work != NULL) && (nb->workSz != sz)) { + ForceZero(nb->work, nb->workSz); + XFREE(nb->work, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + nb->work = NULL; + nb->workSz = 0; + nb->state = 0; + nb->encState = 0; + nb->cmpFail = 0; + nb->row = 0; + } +} + +/* Attach a non-blocking context to the key, or clear it with nb == NULL. While + * set, MakeKey/Encapsulate/Decapsulate run one chunk per call and return + * MP_WOULDBLOCK until the operation completes. The context is owned by the + * caller and must outlive the operation. Attaching, clearing or freeing the + * key abandons any operation still in flight on the current context. */ +int wc_MlKemKey_set_nonblock(MlKemKey* key, wc_MlKemNonBlock* nb) +{ + int ret = 0; + + if (key == NULL) { + ret = BAD_FUNC_ARG; + } + else { + mlkemkey_nb_release(key); + if (nb != NULL) { + XMEMSET(nb, 0, sizeof(*nb)); + } + key->nb = nb; + } + + return ret; +} +#endif /* WC_MLKEM_NONBLOCK */ + #if !defined(WOLF_CRYPTO_CB_ONLY_MLKEM) && \ (!defined(WOLFSSL_MLKEM_NO_MAKE_KEY) || \ !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) || \ @@ -805,11 +905,27 @@ int wc_MlKemKey_MakeKey(MlKemKey* key, WC_RNG* rng) #endif if (ret == 0) { +#ifdef WC_MLKEM_NONBLOCK + if ((key->nb != NULL) && key->nb->started) { + /* Resume: reuse the seeds drawn on the first call so each resumed + * phase derives the same key. */ + XMEMCPY(rand, key->nb->rand, sizeof(rand)); + } + else +#endif + { /* Generate random to use with PRFs. * Step 1: d is 32 random bytes * Step 2: z is 32 random bytes */ ret = wc_RNG_GenerateBlock(rng, rand, WC_ML_KEM_SYM_SZ * 2); +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && (key->nb != NULL)) { + XMEMCPY(key->nb->rand, rand, sizeof(rand)); + key->nb->started = 1; + } +#endif + } /* Step 3: ret is not zero when d == NULL or z == NULL. */ /* rand now holds the secret seeds d||z; register before key gen / * PCT so any future early-exit before the ForceZero is caught. */ @@ -984,7 +1100,23 @@ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand, #endif #else /* e (p) - matrix A is generated a polynomial at a time into it. */ - e = (sword16*)XMALLOC(eSz, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + #ifdef WC_MLKEM_NONBLOCK + mlkemkey_nb_drop_stale_work(key, (word32)eSz); + if ((key->nb != NULL) && (key->nb->work != NULL)) { + /* Resume: reuse the noise scratch from the previous call. */ + e = (sword16*)key->nb->work; + } + else + #endif + { + e = (sword16*)XMALLOC(eSz, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + #ifdef WC_MLKEM_NONBLOCK + if ((e != NULL) && (key->nb != NULL)) { + key->nb->work = e; + key->nb->workSz = (word32)eSz; + } + #endif + } #endif if (e == NULL) { ret = MEMORY_E; @@ -992,17 +1124,24 @@ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand, } #endif #ifdef WOLFSSL_MLKEM_DYNAMIC_KEYS - if (ret == 0) { + /* The allocators discard what the buffers hold, so a resumed non-blocking + * call must keep the vectors the first call allocated. */ +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && ((key->nb == NULL) || (key->nb->state == 0))) +#else + if (ret == 0) +#endif + { ret = mlkemkey_alloc_priv(key, (unsigned int)k); - } - if (ret == 0) { - ret = mlkemkey_alloc_pub(key, (unsigned int)k); - } + if (ret == 0) { + ret = mlkemkey_alloc_pub(key, (unsigned int)k); + } #ifdef WOLFSSL_MLKEM_CACHE_A - if (ret == 0) { - ret = mlkemkey_alloc_a(key, (unsigned int)k); - } + if (ret == 0) { + ret = mlkemkey_alloc_a(key, (unsigned int)k); + } #endif + } #endif if (ret == 0) { const byte* d = rand; @@ -1096,18 +1235,56 @@ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand, } if (ret == 0) { #else - /* Generate noise using PRF. - * Alg 13: Steps 8-11: generate s - */ - ret = mlkem_get_noise(&key->prf, k, s, NULL, NULL, sigma); + /* Generate noise s (Alg 13 Steps 8-11) and NTT it (Step 16). Both run + * once; a non-blocking resume must not repeat them - get_noise would + * overwrite s (key->priv) and the NTT would be applied twice. */ +#ifdef WC_MLKEM_NONBLOCK + if ((key->nb == NULL) || (key->nb->state == 0)) +#endif + { + ret = mlkem_get_noise(&key->prf, k, s, NULL, NULL, sigma); + if (ret == 0) { + mlkem_keygen_ntt_priv(s, k); + } +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && (key->nb != NULL)) { + /* Yield after the prelude; the per-row loop resumes below. */ + key->nb->state = 1; + key->nb->row = 0; + ret = MP_WOULDBLOCK; + } +#endif + } } if (ret == 0) { - /* Generate key pair from private vector and seeds. - * Alg 13: Steps 3-7: generate matrix A_hat - * Alg 13: Steps 12-15: generate e - * Alg 13: Steps 16-18: calculate t_hat from A_hat, s and e - */ - ret = mlkem_keygen_seeds(s, t, &key->prf, e, a, k, rho, sigma); + /* Generate the public vector t one row at a time from A_hat, s_hat and + * e. Alg 13 Steps 3-7, 12-15, 17-18. s is already NTT transformed; t + * (key->pub) and s (key->priv) carry the result across yields. */ + int i; + int i0 = 0; +#ifdef WC_MLKEM_NONBLOCK + if (key->nb != NULL) { + i0 = key->nb->row; + } +#endif + for (i = i0; (ret == 0) && (i < k); i++) { + ret = mlkem_keygen_seeds_row(s, t, &key->prf, e, a, k, rho, sigma, + i); +#ifdef WC_MLKEM_NONBLOCK + /* Yield per row so one slice is about a single row's work. */ + if ((key->nb != NULL) && (ret == 0) && (i + 1 < k)) { + key->nb->row = (word16)(i + 1); + ret = MP_WOULDBLOCK; + } +#endif + } + if ((ret != 0) +#ifdef WC_MLKEM_NONBLOCK + && (ret != WC_NO_ERR_TRACE(MP_WOULDBLOCK)) +#endif + ) { + ForceZero(t, (size_t)k * MLKEM_N * sizeof(sword16)); + } } if (ret == 0) { #endif @@ -1115,11 +1292,34 @@ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand, key->flags |= MLKEM_FLAG_PRIV_SET | MLKEM_FLAG_PUB_SET; #ifdef WOLFSSL_MLKEM_CACHE_A key->flags |= MLKEM_FLAG_A_SET; +#endif +#ifdef WC_MLKEM_NONBLOCK + if (key->nb != NULL) { + /* Complete: reset the cursor so the context is reusable. */ + key->nb->state = 0; + key->nb->row = 0; + key->nb->started = 0; + ForceZero(key->nb->rand, sizeof(key->nb->rand)); + } #endif } - else if ((key != NULL) && (k != 0)) { + else if ((key != NULL) && (k != 0) +#ifdef WC_MLKEM_NONBLOCK + && (ret != WC_NO_ERR_TRACE(MP_WOULDBLOCK)) +#endif + ) { /* Keygen failed after s and z were written; k is 0 until the - * argument checks passed (ISO/IEC 19790:2012 7.9.7). */ + * argument checks passed (ISO/IEC 19790:2012 7.9.7). A non-blocking + * yield is not a failure, so it must not wipe the partial key. */ +#ifdef WC_MLKEM_NONBLOCK + if (key->nb != NULL) { + /* A failed operation must not leave a half-advanced cursor. */ + key->nb->state = 0; + key->nb->row = 0; + key->nb->started = 0; + ForceZero(key->nb->rand, sizeof(key->nb->rand)); + } +#endif #ifdef WOLFSSL_MLKEM_DYNAMIC_KEYS if (key->priv != NULL) { ForceZero(key->priv, key->privAllocSz); @@ -1144,9 +1344,21 @@ int wc_MlKemKey_MakeKeyWithRandom(MlKemKey* key, const unsigned char* rand, #ifndef WOLFSSL_NO_MALLOC /* Free dynamic memory allocated in function. */ +#ifdef WC_MLKEM_NONBLOCK + if ((key != NULL) && (key->nb != NULL) && + (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK))) { + /* Yielding: keep the noise scratch for the next call. */ + } + else +#endif if (e != NULL) { ForceZero(e, eSz); XFREE(e, key->heap, DYNAMIC_TYPE_TMP_BUFFER); +#ifdef WC_MLKEM_NONBLOCK + if ((key != NULL) && (key->nb != NULL)) { + key->nb->work = NULL; + } +#endif } #else /* e is a stack buffer holding the secret noise; zeroize it. */ @@ -1491,7 +1703,23 @@ static int mlkemkey_encapsulate(MlKemKey* key, const byte* m, byte* r, byte* c, yAllocSz += MLKEM_MAX_COMP_POLY_SZ; } #endif - y = (sword16*)XMALLOC(yAllocSz, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + #ifdef WC_MLKEM_NONBLOCK + mlkemkey_nb_drop_stale_work(key, (word32)yAllocSz); + if ((key->nb != NULL) && (key->nb->work != NULL)) { + /* Resume: reuse the working vectors from the previous call. */ + y = (sword16*)key->nb->work; + } + else + #endif + { + y = (sword16*)XMALLOC(yAllocSz, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + #ifdef WC_MLKEM_NONBLOCK + if ((y != NULL) && (key->nb != NULL)) { + key->nb->work = y; + key->nb->workSz = (word32)yAllocSz; + } + #endif + } if (y == NULL) { ret = MEMORY_E; } @@ -1568,17 +1796,40 @@ static int mlkemkey_encapsulate(MlKemKey* key, const byte* m, byte* r, byte* c, * y (v) | u (p) | a (p) [| block] */ u = y + MLKEM_N * k; a = u + MLKEM_N; - + } + /* Steps 9-12: generate y and transform it. Both run once; a non-blocking + * resume must not repeat them, as the NTT would be applied twice. */ +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && ((key->nb == NULL) || (key->nb->encState == 0))) +#else + if (ret == 0) +#endif + { /* Initialize the PRF for use in the noise generation. */ ret = mlkem_prf_reset(&key->prf); - } - if (ret == 0) { - /* Generate noise using PRF. - * Steps 9-12: generate y */ - ret = mlkem_get_noise(&key->prf, (int)k, y, NULL, NULL, r); + if (ret == 0) { + /* Generate noise using PRF. + * Steps 9-12: generate y */ + ret = mlkem_get_noise(&key->prf, (int)k, y, NULL, NULL, r); + } + if (ret == 0) { + mlkem_encapsulate_ntt_y(y, (int)k); + } +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && (key->nb != NULL)) { + key->nb->encState = 1; + key->nb->row = 0; + key->nb->cmpFail = 0; + ret = MP_WOULDBLOCK; + } +#endif } if (ret == 0) { byte* cb = c; + int cmpFail = 0; + int* cmpFailP = &cmpFail; + unsigned int i; + unsigned int i0 = 0; if (cmp != NULL) { /* Cipher text is compared a block at a time as it is calculated. */ @@ -1590,25 +1841,51 @@ static int mlkemkey_encapsulate(MlKemKey* key, const byte* m, byte* r, byte* c, ret = BAD_FUNC_ARG; #endif } - - /* Perform encapsulation maths and encode the cipher text. - * Steps 13-17: generate e_1 and e_2 - * Steps 18-19, 21: calculate u and v - * Steps 22-24: c <- (c_1||c_2) */ #ifdef WOLFSSL_MLKEM_CACHE_A - if ((ret == 0) && (cachedA == NULL)) { + if (cachedA == NULL) { fillA = key->a; } #endif +#ifdef WC_MLKEM_NONBLOCK + if (key->nb != NULL) { + /* The row cursor and the compare result so far survive yields. */ + i0 = key->nb->row; + cmpFailP = &key->nb->cmpFail; + } +#endif + + /* Perform encapsulation maths and encode the cipher text. + * Steps 13-16, 18-19, 22: one row of u per call when non-blocking */ +#ifdef WC_MLKEM_NONBLOCK + if ((key->nb == NULL) || (key->nb->encState == 1)) +#endif + { + for (i = i0; (ret == 0) && (i < k); i++) { + ret = mlkem_encapsulate_seeds_row(&key->prf, cb, cmp, cmpFailP, + u, a, y, (int)k, key->pubSeed, r, cachedA, fillA, (int)i); +#ifdef WC_MLKEM_NONBLOCK + /* Yield per row; the last row hands over to the v phase. */ + if ((key->nb != NULL) && (ret == 0)) { + key->nb->row = (word16)(i + 1); + if (i + 1 == k) { + key->nb->encState = 2; + } + ret = MP_WOULDBLOCK; + } +#endif + } + } + /* Steps 17, 20-21, 23-24: e_2, the message, v and c_2 */ if (ret == 0) { - ret = mlkem_encapsulate_seeds(key->pub, &key->prf, cb, cmp, fail, - u, a, y, (int)k, m, key->pubSeed, r, cachedA, fillA); + ret = mlkem_encapsulate_seeds_tail(key->pub, &key->prf, cb, cmp, + fail, *cmpFailP, u, a, y, (int)k, m, r); } #ifdef WOLFSSL_MLKEM_CACHE_A if ((ret == 0) && (fillA != NULL)) { key->flags |= MLKEM_FLAG_A_SET; } #endif + ForceZero(&cmpFail, sizeof(cmpFail)); } (void)compVecSz; #endif /* WOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM */ @@ -1663,9 +1940,28 @@ static int mlkemkey_encapsulate(MlKemKey* key, const byte* m, byte* r, byte* c, * material: y (ephemeral noise) and, in the default layout, mu (message * polynomial) and e1/e2 (noise vectors). Zeroize the whole allocation * before release - FIPS 203 section 3.3. */ - if (y != NULL) { - ForceZero(y, yAllocSz); - XFREE(y, key->heap, DYNAMIC_TYPE_TMP_BUFFER); +#ifdef WC_MLKEM_NONBLOCK + if ((key->nb != NULL) && (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK))) { + /* Yielding: keep the working vectors for the next call. */ + } + else +#endif + { + if (y != NULL) { + ForceZero(y, yAllocSz); + XFREE(y, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + } +#ifdef WC_MLKEM_NONBLOCK + if (key->nb != NULL) { + /* Final success or error: release and reset the cursor. */ + key->nb->work = NULL; + key->nb->encState = 0; + key->nb->cmpFail = 0; + key->nb->row = 0; + key->nb->started = 0; + ForceZero(key->nb->rand, sizeof(key->nb->rand)); + } +#endif } #else /* y is a stack buffer holding secret noise/message material; zeroize it. */ @@ -1807,10 +2103,26 @@ int wc_MlKemKey_Encapsulate(MlKemKey* key, unsigned char* ct, unsigned char* ss, } if (ret == 0) { +#ifdef WC_MLKEM_NONBLOCK + if ((key->nb != NULL) && key->nb->started) { + /* Resume: reuse the randomness drawn on the first call so each + * resumed phase produces the same ciphertext and secret. */ + XMEMCPY(m, key->nb->rand, sizeof(m)); + } + else +#endif + { /* Generate seed for use with PRFs. * Step 1: m is 32 random bytes */ ret = wc_RNG_GenerateBlock(rng, m, sizeof(m)); +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && (key->nb != NULL)) { + XMEMCPY(key->nb->rand, m, sizeof(m)); + key->nb->started = 1; + } +#endif + } /* m now holds the encapsulation randomness (the shared secret is * derived from it); register before the encapsulate call. */ #ifdef WOLFSSL_CHECK_MEM_ZERO @@ -2048,8 +2360,11 @@ int wc_MlKemKey_EncapsulateWithRandom(MlKemKey* key, unsigned char* ct, } #endif - if ((ret != 0) && (ct != NULL) && - (wc_MlKemKey_CipherTextSize(key, &ctSz) == 0)) { + if ((ret != 0) && +#ifdef WC_MLKEM_NONBLOCK + (ret != WC_NO_ERR_TRACE(MP_WOULDBLOCK)) && +#endif + (ct != NULL) && (wc_MlKemKey_CipherTextSize(key, &ctSz) == 0)) { /* FIPS 203 3.3: a failed encapsulation leaves no ciphertext. */ ForceZero(ct, ctSz); } @@ -2440,21 +2755,53 @@ int wc_MlKemKey_Decapsulate(MlKemKey* key, unsigned char* ss, mlkemkey_hash_memzero_add(key); } #endif - if (ret == 0) { + /* Decrypt and derive the re-encryption inputs. Runs once; a non-blocking + * resume carries msg and kr in the scratch and goes straight to the + * re-encryption. */ +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && ((key->nb == NULL) || (key->nb->state == 0))) +#else + if (ret == 0) +#endif + { /* Decapsulate the cipher text. */ ret = mlkemkey_decapsulate(key, msg, ct); + if (ret == 0) { + /* Check we have H, hash of public, set. */ + ret = wc_mlkemkey_check_h(key); + } + if (ret == 0) { + /* Hash message into seed buffer. */ + ret = MLKEM_HASH_G(&key->hash, msg, WC_ML_KEM_SYM_SZ, key->h, + WC_ML_KEM_SYM_SZ, kr); + } +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && (key->nb != NULL)) { + /* msg and kr must outlive the per-row yields of re-encryption. */ + key->nb->decWork = XMALLOC(sizeof(msg) + sizeof(kr), key->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (key->nb->decWork == NULL) { + ret = MEMORY_E; + } + else { + key->nb->decWorkSz = (word32)(sizeof(msg) + sizeof(kr)); + XMEMCPY((byte*)key->nb->decWork, msg, sizeof(msg)); + XMEMCPY((byte*)key->nb->decWork + sizeof(msg), kr, sizeof(kr)); + key->nb->state = 1; + ret = MP_WOULDBLOCK; + } + } +#endif } - if (ret == 0) { - /* Check we have H, hash of public, set. */ - ret = wc_mlkemkey_check_h(key); - } - if (ret == 0) { - /* Hash message into seed buffer. */ - ret = MLKEM_HASH_G(&key->hash, msg, WC_ML_KEM_SYM_SZ, key->h, - WC_ML_KEM_SYM_SZ, kr); +#ifdef WC_MLKEM_NONBLOCK + if ((ret == 0) && (key->nb != NULL) && (key->nb->state == 1)) { + XMEMCPY(msg, (byte*)key->nb->decWork, sizeof(msg)); + XMEMCPY(kr, (byte*)key->nb->decWork + sizeof(msg), sizeof(kr)); } +#endif if (ret == 0) { - /* Encapsulate the message. */ + /* Encapsulate the message. Non-blocking, this yields per row and the + * compare completes on the call that finishes it. */ #ifndef WOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM ret = mlkemkey_encapsulate(key, msg, kr + WC_ML_KEM_SYM_SZ, cmp, NULL, NULL); @@ -2519,6 +2866,19 @@ int wc_MlKemKey_Decapsulate(MlKemKey* key, unsigned char* ss, /* cmp is a stack buffer holding the re-encrypted ciphertext; zeroize it. */ ForceZero(cmp, sizeof(cmp)); #endif +#endif +#ifdef WC_MLKEM_NONBLOCK + if ((key != NULL) && (key->nb != NULL) && + (ret != WC_NO_ERR_TRACE(MP_WOULDBLOCK))) { + /* Final success or error: release the carried msg and kr. */ + if (key->nb->decWork != NULL) { + ForceZero(key->nb->decWork, key->nb->decWorkSz); + XFREE(key->nb->decWork, key->heap, DYNAMIC_TYPE_TMP_BUFFER); + key->nb->decWork = NULL; + key->nb->decWorkSz = 0; + } + key->nb->state = 0; + } #endif ForceZero(msg, sizeof(msg)); diff --git a/wolfcrypt/src/wc_mlkem_poly.c b/wolfcrypt/src/wc_mlkem_poly.c index fd4554e597c..257d0d19a01 100644 --- a/wolfcrypt/src/wc_mlkem_poly.c +++ b/wolfcrypt/src/wc_mlkem_poly.c @@ -2033,7 +2033,32 @@ int mlkem_keygen(sword16* s, sword16* t, sword16* e, const sword16* a, int k) * 18: t_hat <- A_hat o s_hat + e_hat * ... * - * @param [in, out] s Private key vector of polynomials. + * Split into mlkem_keygen_ntt_priv(), run once, and mlkem_keygen_seeds_row(), + * run for each row of the public vector, so a non-blocking caller can yield + * between rows. On a row failure the caller wipes t. + */ + +/* Transform the private vector in place. All of the result is used in the + * public key calculation. Step 16: s_hat = NTT(s). Runs once, before the + * per-row loop; a non-blocking caller must not repeat it. + * + * @param [in, out] s Private key vector of polynomials. + * @param [in] k Number of polynomials in vector. + */ +void mlkem_keygen_ntt_priv(sword16* s, int k) +{ + int i; + + for (i = 0; i < k; ++i) { + mlkem_ntt(s + i * MLKEM_N); + } +} + +/* One row of public key generation. Step 17, Step 18: calculate public vector + * polynomial i from A_hat, s_hat and e_hat. s must already be NTT transformed + * by mlkem_keygen_ntt_priv(). On failure t is left for the caller to wipe. + * + * @param [in] s Private key vector of polynomials, NTT form. * @param [out] t Public key vector of polynomials. * @param [in, out] prf XOF object. * @param [out] tp Temporary polynomial. Must not alias s or t. @@ -2041,42 +2066,29 @@ int mlkem_keygen(sword16* s, sword16* t, sword16* e, const sword16* a, int k) * @param [in] k Number of polynomials in vector. * @param [in] rho Random seed to generate matrix A from. * @param [in, out] sigma Random seed to generate noise from. + * @param [in] i Row to calculate. * @return 0 on success. * @return MEMORY_E when dynamic memory allocation fails. Only possible when * WOLFSSL_SMALL_STACK is defined. * @return Other negative value when a hash error occurred. */ -int mlkem_keygen_seeds(sword16* s, sword16* t, MLKEM_PRF_T* prf, - sword16* tp, sword16* cacheA, int k, byte* rho, byte* sigma) +int mlkem_keygen_seeds_row(sword16* s, sword16* t, MLKEM_PRF_T* prf, + sword16* tp, sword16* cacheA, int k, byte* rho, byte* sigma, int i) { - int i; - int ret = 0; + int ret; + int j; sword16* e = tp; sword16* a = (cacheA != NULL) ? cacheA : tp; unsigned int aStride = (cacheA != NULL) ? MLKEM_N : 0; - /* Transform private key. All of result used in public key calculation - * Step 16: s_hat = NTT(s) */ - for (i = 0; i < k; ++i) { - mlkem_ntt(s + i * MLKEM_N); - } - - /* For each polynomial in the vectors. - * Step 17, Step 18: Calculate public from A_hat, s_hat and e_hat. */ - for (i = 0; i < k; ++i) { - int j; - - /* Generate a vector of matrix A, a polynomial at a time, multiplying - * each into the public polynomial. - * Steps 4-6: generate A[i] - * Step 18: ... A_hat o s_hat ... */ - ret = mlkem_gen_matrix_i_acc(prf, t + i * MLKEM_N, - a + (unsigned int)i * (unsigned int)k * aStride, aStride, s, k, - rho, i, 0); - if (ret != 0) { - break; - } - + /* Generate a vector of matrix A, a polynomial at a time, multiplying + * each into the public polynomial. + * Steps 4-6: generate A[i] + * Step 18: ... A_hat o s_hat ... */ + ret = mlkem_gen_matrix_i_acc(prf, t + i * MLKEM_N, + a + (unsigned int)i * (unsigned int)k * aStride, aStride, s, k, + rho, i, 0); + if (ret == 0) { /* Convert public polynomial to Montgomery form. * Step 18: ... MontRed(A_hat o s_hat) ... */ for (j = 0; j < MLKEM_N; ++j) { @@ -2087,9 +2099,8 @@ int mlkem_keygen_seeds(sword16* s, sword16* t, MLKEM_PRF_T* prf, /* Generate noise using PRF. * Step 13: e[i] <- SamplePolyCBD_eta_1(PRF_eta_1(sigma, N)) */ ret = mlkem_get_noise_i(prf, k, e, sigma, i, 1); - if (ret != 0) { - break; - } + } + if (ret == 0) { /* Transform error values polynomial. * Step 17: e_hat = NTT(e) */ #if defined(WOLFSSL_MLKEM_SMALL) || defined(WOLFSSL_MLKEM_NO_LARGE_CODE) @@ -2107,10 +2118,6 @@ int mlkem_keygen_seeds(sword16* s, sword16* t, MLKEM_PRF_T* prf, #endif } - if (ret != 0) { - ForceZero(t, (size_t)k * MLKEM_N * sizeof(sword16)); - } - return ret; } @@ -2246,115 +2253,118 @@ int mlkem_encapsulate(const sword16* pub, sword16* u, sword16* v, /* Encapsulate message and encode the cipher text a polynomial of u at a time. * - * @param [in] pub Public key vector of polynomials. - * @param [in, out] prf XOF object. - * @param [out] c Cipher text, or one block of it when comparing. - * @param [in] cmp Cipher text to compare against. May be NULL. - * @param [out] fail Set to 0 on a match and -1 on a mismatch, once - * every block is compared. Only used when cmp is set. - * @param [in, out] u Polynomial. Holds v once u is encoded. - * @param [out] tp Temporary polynomial. Must not alias u or y. - * @param [in, out] y Vector of polynomials. - * @param [in] k Number of polynomials in vector. - * @param [in] msg Message to encapsulate. - * @param [in] seed Random seed to generate matrix A from. - * @param [in, out] coins Random seed to generate noise from. - * @param [in] cacheA Cached matrix A to transpose in place of - * generating it. May be NULL. - * @param [out] fillA Matrix to keep the generated polynomials of A in, - * laid out as key generation leaves them. May be - * NULL. Ignored when cacheA is not NULL. - * @return 0 on success. - * @return BAD_FUNC_ARG when c is NULL, or cmp is not NULL and either fail is - * NULL or decapsulation is not compiled in. - * @return MEMORY_E when dynamic memory allocation fails. Only possible when - * WOLFSSL_SMALL_STACK is defined. - * @return Other negative value when a hash error occurred. + * Split into mlkem_encapsulate_ntt_y(), run once, + * mlkem_encapsulate_seeds_row(), run for each polynomial of u, and + * mlkem_encapsulate_seeds_tail() for v, so a non-blocking caller can yield + * between rows. With cmp set, the blocks of the cipher text are compared + * against cmp as they are produced instead of being written out, and the + * result of every block is accumulated into cmpFail; only the tail may report + * it through fail. */ -int mlkem_encapsulate_seeds(const sword16* pub, MLKEM_PRF_T* prf, byte* c, - const byte* cmp, int* fail, sword16* u, sword16* tp, sword16* y, int k, - const byte* msg, byte* seed, byte* coins, const sword16* cacheA, - sword16* fillA) + +/* Number of bytes a polynomial of u is compressed into. */ +static unsigned int mlkem_u_block_sz(int k) { - int ret = 0; - int i; - sword16* a = tp; - sword16* e1 = tp; - sword16* e2 = tp; - sword16* v = u; - sword16* m = y; - byte* cb = c; - /* Number of bytes a polynomial of u is compressed into. */ unsigned int blockSz = MLKEM_POLY_COMPRESSED_SZ(MLKEM_COMP_10BITS); -#ifndef WOLFSSL_MLKEM_NO_DECAPSULATE - int cmpFail = 0; - int blockFail = 0; -#endif #if defined(WOLFSSL_KYBER1024) || defined(WOLFSSL_WC_ML_KEM_1024) if (k == WC_ML_KEM_1024_K) { blockSz = MLKEM_POLY_COMPRESSED_SZ(MLKEM_COMP_11BITS); } +#else + (void)k; #endif - if ((c == NULL) || ((cmp != NULL) && (fail == NULL))) { - ret = BAD_FUNC_ARG; - } -#ifdef WOLFSSL_MLKEM_NO_DECAPSULATE - if (cmp != NULL) { - ret = BAD_FUNC_ARG; - } -#endif + return blockSz; +} - if (ret == 0) { - /* Transform y. All of result used in calculation of u and v. */ - for (i = 0; i < k; ++i) { - mlkem_ntt(y + i * MLKEM_N); - } +/* Transform the noise vector y in place. All of the result is used in the + * calculation of u and v. Runs once, before the per-row loop; a non-blocking + * caller must not repeat it. */ +void mlkem_encapsulate_ntt_y(sword16* y, int k) +{ + int i; + + for (i = 0; i < k; ++i) { + mlkem_ntt(y + i * MLKEM_N); } +} - /* For each polynomial in the vectors. */ - for (i = 0; (ret == 0) && (i < k); ++i) { - int j; +/* One row of the cipher text first component: u[i] = A_hat_trans[i] o y + e1[i] + * encoded into block i of c, or compared against block i of cmp with the + * result accumulated into cmpFail. y must already be NTT transformed by + * mlkem_encapsulate_ntt_y(). + * + * @param [in, out] prf XOF object. + * @param [out] c Cipher text, or one block of it when comparing. + * @param [in] cmp Cipher text to compare against. May be NULL. + * @param [in, out] cmpFail OR of the block compare results so far. + * @param [out] u Polynomial. + * @param [out] tp Temporary polynomial. Must not alias u or y. + * @param [in] y Vector of polynomials, NTT form. + * @param [in] k Number of polynomials in vector. + * @param [in] seed Random seed to generate matrix A from. + * @param [in, out] coins Random seed to generate noise from. + * @param [in] cacheA Cached matrix A to transpose in place of + * generating it. May be NULL. + * @param [out] fillA Matrix to keep the generated polynomials of A + * in, laid out as key generation leaves them. May + * be NULL. Ignored when cacheA is not NULL. + * @param [in] i Row to calculate. + * @return 0 on success. + * @return MEMORY_E when dynamic memory allocation fails. Only possible when + * WOLFSSL_SMALL_STACK is defined. + * @return Other negative value when a hash error occurred. + */ +int mlkem_encapsulate_seeds_row(MLKEM_PRF_T* prf, byte* c, const byte* cmp, + int* cmpFail, sword16* u, sword16* tp, sword16* y, int k, byte* seed, + byte* coins, const sword16* cacheA, sword16* fillA, int i) +{ + int ret = 0; + int j; + sword16* a = tp; + sword16* e1 = tp; + unsigned int blockSz = mlkem_u_block_sz(k); + /* Compared cipher text is encoded one block at a time into c. */ + byte* cb = (cmp == NULL) ? (c + (unsigned int)i * blockSz) : c; +#ifndef WOLFSSL_MLKEM_NO_DECAPSULATE + int blockFail = 0; +#endif - if (cacheA != NULL) { - /* A_trans[i,j] is A[j,i] of the cached matrix. */ - for (j = 0; j < k; j++) { - const sword16* aij = cacheA + - ((unsigned int)j * (unsigned int)k + (unsigned int)i) * - MLKEM_N; - if (j == 0) { - mlkem_basemul_mont(u, aij, y); - } - else { - mlkem_basemul_mont_add(u, aij, y + j * MLKEM_N); - } + if (cacheA != NULL) { + /* A_trans[i,j] is A[j,i] of the cached matrix. */ + for (j = 0; j < k; j++) { + const sword16* aij = cacheA + + ((unsigned int)j * (unsigned int)k + (unsigned int)i) * + MLKEM_N; + if (j == 0) { + mlkem_basemul_mont(u, aij, y); + } + else { + mlkem_basemul_mont_add(u, aij, y + j * MLKEM_N); } } - else { - sword16* arow = a; - unsigned int aStride = 0; + } + else { + sword16* arow = a; + unsigned int aStride = 0; - if (fillA != NULL) { - arow = fillA + (unsigned int)i * MLKEM_N; - aStride = (unsigned int)k * MLKEM_N; - } - /* Generate a vector of matrix A, a polynomial at a time, - * multiplying each into the u polynomial. */ - ret = mlkem_gen_matrix_i_acc(prf, u, arow, aStride, y, k, seed, i, - 1); - if (ret != 0) { - break; - } + if (fillA != NULL) { + arow = fillA + (unsigned int)i * MLKEM_N; + aStride = (unsigned int)k * MLKEM_N; } + /* Generate a vector of matrix A, a polynomial at a time, + * multiplying each into the u polynomial. */ + ret = mlkem_gen_matrix_i_acc(prf, u, arow, aStride, y, k, seed, i, 1); + } + if (ret == 0) { /* Inverse transform u polynomial. */ mlkem_invntt(u); /* Generate noise using PRF. */ ret = mlkem_get_noise_i(prf, k, e1, coins, i, 0); - if (ret != 0) { - break; - } + } + if (ret == 0) { /* Add errors to u and reduce. */ #if defined(WOLFSSL_MLKEM_SMALL) || defined(WOLFSSL_MLKEM_NO_LARGE_CODE) for (j = 0; j < MLKEM_N; ++j) { @@ -2399,32 +2409,65 @@ int mlkem_encapsulate_seeds(const sword16* pub, MLKEM_PRF_T* prf, byte* c, ret = mlkem_vec_compress_11(cb, u, 1); } #endif - if (ret != 0) { - break; - } - if (cmp == NULL) { - cb += blockSz; - } + } #ifndef WOLFSSL_MLKEM_NO_DECAPSULATE - else { - ret = mlkem_cmp(cb, cmp, (int)blockSz, &blockFail); - cmpFail |= blockFail; - if (ret != 0) { - break; - } - cmp += blockSz; - } -#endif + if ((ret == 0) && (cmp != NULL)) { + ret = mlkem_cmp(cb, cmp + (unsigned int)i * blockSz, (int)blockSz, + &blockFail); + *cmpFail |= blockFail; } + ForceZero(&blockFail, sizeof(blockFail)); +#else + (void)cmpFail; +#endif - if (ret == 0) { - /* Multiply public key by y into v polynomial. */ - mlkem_pointwise_acc_mont(v, pub, y, (unsigned int)k); - /* Inverse transform v. */ - mlkem_invntt(v); + return ret; +} - ret = mlkem_from_msg(m, msg); - } +/* Cipher text second component: v = pub o y + e2 + Decompress(msg), encoded + * into the last block of c or compared against the last block of cmp. With + * cmp set, fail is the result of every block compared, including cmpFail from + * the rows. y must be NTT transformed; it is reused in place to hold the + * decompressed message. + * + * @param [in] pub Public key vector of polynomials. + * @param [in, out] prf XOF object. + * @param [out] c Cipher text, or one block of it when comparing. + * @param [in] cmp Cipher text to compare against. May be NULL. + * @param [out] fail Set to 0 on a match and -1 on a mismatch of any + * block. Only used when cmp is set. + * @param [in] cmpFail OR of the row compare results. + * @param [in, out] u Polynomial. Holds v. + * @param [out] tp Temporary polynomial. Must not alias u or y. + * @param [in, out] y Vector of polynomials, NTT form. + * @param [in] k Number of polynomials in vector. + * @param [in] msg Message to encapsulate. + * @param [in, out] coins Random seed to generate noise from. + * @return 0 on success. + * @return Other negative value when a hash error occurred. + */ +int mlkem_encapsulate_seeds_tail(const sword16* pub, MLKEM_PRF_T* prf, + byte* c, const byte* cmp, int* fail, int cmpFail, sword16* u, sword16* tp, + sword16* y, int k, const byte* msg, byte* coins) +{ + int ret; + int i; + sword16* e2 = tp; + sword16* v = u; + sword16* m = y; + unsigned int uSz = (unsigned int)k * mlkem_u_block_sz(k); + unsigned int blockSz = 0; + byte* cb = (cmp == NULL) ? (c + uSz) : c; +#ifndef WOLFSSL_MLKEM_NO_DECAPSULATE + int blockFail = 0; +#endif + + /* Multiply public key by y into v polynomial. */ + mlkem_pointwise_acc_mont(v, pub, y, (unsigned int)k); + /* Inverse transform v. */ + mlkem_invntt(v); + + ret = mlkem_from_msg(m, msg); if (ret == 0) { /* Generate noise using PRF. */ coins[WC_ML_KEM_SYM_SZ] = WC_OCTET(2 * k); @@ -2481,7 +2524,7 @@ int mlkem_encapsulate_seeds(const sword16* pub, MLKEM_PRF_T* prf, byte* c, } #ifndef WOLFSSL_MLKEM_NO_DECAPSULATE if ((ret == 0) && (cmp != NULL)) { - ret = mlkem_cmp(cb, cmp, (int)blockSz, &blockFail); + ret = mlkem_cmp(cb, cmp + uSz, (int)blockSz, &blockFail); if (ret == 0) { /* Only a comparison of every block may clear the caller's fail. */ *fail = cmpFail | blockFail; @@ -2489,6 +2532,10 @@ int mlkem_encapsulate_seeds(const sword16* pub, MLKEM_PRF_T* prf, byte* c, } ForceZero(&cmpFail, sizeof(cmpFail)); ForceZero(&blockFail, sizeof(blockFail)); +#else + (void)fail; + (void)cmpFail; + (void)blockSz; #endif /* Step 24: return c <- (c_1||c_2) */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 9fabf2da026..e07fec0b930 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -71308,6 +71308,137 @@ static wc_test_ret_t mlkem_decap_reject_test(int type, WC_RNG* rng) return ret; } #endif +#if defined(WC_MLKEM_NONBLOCK) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) +/* Drive ML-KEM key generation, encapsulation and decapsulation through the + * non-blocking (WC_MLKEM_NONBLOCK) path for one parameter set. Each operation + * must hand control back with MP_WOULDBLOCK at least once, the two secrets must + * agree with each other and with a blocking decapsulation of the same + * ciphertext, and a tampered ciphertext must be implicitly rejected. */ +static wc_test_ret_t mlkem_nonblock_test_type(int type, WC_RNG* rng) +{ + wc_test_ret_t ret; + MlKemKey key; + wc_MlKemNonBlock nb; + byte* ct = NULL; + byte ss[WC_ML_KEM_SS_SZ]; + byte ssDec[WC_ML_KEM_SS_SZ]; + word32 ctSz = 0; + int yields; + int keyInited = 0; + + ret = wc_MlKemKey_Init(&key, type, HEAP_HINT, devId); + if (ret == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + /* Parameter set not built. */ + return 0; + } + if (ret == 0) { + keyInited = 1; + ret = wc_MlKemKey_set_nonblock(&key, &nb); + } + if (ret == 0) + ret = wc_MlKemKey_CipherTextSize(&key, &ctSz); + if (ret == 0) { + ct = (byte*)XMALLOC(ctSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (ct == NULL) + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + } + /* Key generation - must yield at least once. */ + if (ret == 0) { + yields = 0; + do { + ret = wc_MlKemKey_MakeKey(&key, rng); + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + yields++; + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && (yields < 1)) + ret = WC_TEST_RET_ENC_NC; + } + /* Encapsulation - must yield at least once. */ + if (ret == 0) { + yields = 0; + do { + ret = wc_MlKemKey_Encapsulate(&key, ct, ss, rng); + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + yields++; + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && (yields < 1)) + ret = WC_TEST_RET_ENC_NC; + } + /* Decapsulation - must yield and recover the same secret. */ + if (ret == 0) { + yields = 0; + do { + ret = wc_MlKemKey_Decapsulate(&key, ssDec, ct, ctSz); + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + yields++; + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && + ((yields < 1) || (XMEMCMP(ss, ssDec, sizeof(ss)) != 0))) + ret = WC_TEST_RET_ENC_NC; + } + /* The blocking path must agree with the non-blocking ciphertext. */ + if (ret == 0) + ret = wc_MlKemKey_set_nonblock(&key, NULL); + if (ret == 0) { + ret = wc_MlKemKey_Decapsulate(&key, ssDec, ct, ctSz); + if ((ret == 0) && (XMEMCMP(ss, ssDec, sizeof(ss)) != 0)) + ret = WC_TEST_RET_ENC_NC; + } + /* A tampered ciphertext is implicitly rejected: no error, other secret. */ + if (ret == 0) + ret = wc_MlKemKey_set_nonblock(&key, &nb); + if (ret == 0) { + ct[0] ^= 0x80; + do { + ret = wc_MlKemKey_Decapsulate(&key, ssDec, ct, ctSz); + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && (XMEMCMP(ss, ssDec, sizeof(ss)) == 0)) + ret = WC_TEST_RET_ENC_NC; + } + /* An operation abandoned after its first yield is released by + * re-attaching the context, and a fresh operation must then succeed. */ + if (ret == 0) { + ret = wc_MlKemKey_Encapsulate(&key, ct, ss, rng); + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + ret = wc_MlKemKey_set_nonblock(&key, &nb); + else if (ret == 0) + ret = WC_TEST_RET_ENC_NC; + } + if (ret == 0) { + do { + ret = wc_MlKemKey_Encapsulate(&key, ct, ss, rng); + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + } + if (ret == 0) { + do { + ret = wc_MlKemKey_Decapsulate(&key, ssDec, ct, ctSz); + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && (XMEMCMP(ss, ssDec, sizeof(ss)) != 0)) + ret = WC_TEST_RET_ENC_NC; + } + + if (ct != NULL) + XFREE(ct, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (keyInited) + wc_MlKemKey_Free(&key); + return ret; +} + +static wc_test_ret_t mlkem_nonblock_test(WC_RNG* rng) +{ + wc_test_ret_t ret; + + ret = mlkem_nonblock_test_type(WC_ML_KEM_512, rng); + if (ret == 0) + ret = mlkem_nonblock_test_type(WC_ML_KEM_768, rng); + if (ret == 0) + ret = mlkem_nonblock_test_type(WC_ML_KEM_1024, rng); + return ret; +} +#endif /* WC_MLKEM_NONBLOCK && RNG && keygen && encapsulate && decapsulate */ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mlkem_test(void) { @@ -71714,6 +71845,14 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mlkem_test(void) #endif #endif #endif +#if defined(WC_MLKEM_NONBLOCK) && !defined(WC_NO_RNG) && \ + !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) + ret = mlkem_nonblock_test(&rng); + if (ret != 0) + goto out; +#endif out: @@ -77081,6 +77220,144 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t falcon_test(void) #define MLDSA_CB_ONLY_LEVEL WC_ML_DSA_87 #endif +#if defined(WC_MLDSA_NONBLOCK) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) +/* Drive ML-DSA key generation, signing and verification through the + * non-blocking (WC_MLDSA_NONBLOCK) path for one parameter set. Each operation + * must hand control back with MP_WOULDBLOCK at least once and still produce a + * result identical to the blocking path, and a tampered signature must be + * rejected. */ +static wc_test_ret_t mldsa_nonblock_test_level(int level, WC_RNG* rng) +{ + wc_test_ret_t ret; + wc_MlDsaKey key; + wc_MlDsaNonBlock nb; + byte* sig = NULL; + int sigSz = 0; + word32 sigLen; + int res; + int yields; + int keyInited = 0; + static const byte msg[] = "wolfSSL ML-DSA non-blocking test"; + + ret = wc_MlDsaKey_Init(&key, HEAP_HINT, devId); + if (ret == 0) { + keyInited = 1; + ret = wc_MlDsaKey_SetParams(&key, (byte)level); + } + if (ret == 0) + ret = wc_MlDsaKey_set_nonblock(&key, &nb); + + /* Key generation - must yield at least once. */ + if (ret == 0) { + yields = 0; + do { + ret = wc_MlDsaKey_MakeKey(&key, rng); + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + yields++; + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && (yields < 1)) + ret = WC_TEST_RET_ENC_NC; + } + if (ret == 0) + ret = wc_MlDsaKey_GetSigLen(&key, &sigSz); + if (ret == 0) { + sig = (byte*)XMALLOC((size_t)sigSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (sig == NULL) + ret = WC_TEST_RET_ENC_EC(MEMORY_E); + } + /* Sign - must yield at least once, even when the first attempt succeeds. */ + if (ret == 0) { + yields = 0; + sigLen = (word32)sigSz; + do { + ret = wc_MlDsaKey_SignCtx(&key, NULL, 0, sig, &sigLen, msg, + (word32)sizeof(msg), rng); + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + yields++; + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && (yields < 1)) + ret = WC_TEST_RET_ENC_NC; + } + /* Verify a good signature - must yield and accept. */ + if (ret == 0) { + yields = 0; + res = 0; + do { + ret = wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res); + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + yields++; + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && ((res != 1) || (yields < 1))) + ret = WC_TEST_RET_ENC_NC; + } + /* A tampered signature must be rejected through the non-blocking path. */ + if (ret == 0) { + sig[0] ^= 0x80; + res = 1; + do { + ret = wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res); + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && (res != 0)) + ret = WC_TEST_RET_ENC_NC; + sig[0] ^= 0x80; + } + /* An operation abandoned after its first yield is released by + * re-attaching the context, and a fresh operation must then succeed. */ + if (ret == 0) { + sigLen = (word32)sigSz; + ret = wc_MlDsaKey_SignCtx(&key, NULL, 0, sig, &sigLen, msg, + (word32)sizeof(msg), rng); + if (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) + ret = wc_MlDsaKey_set_nonblock(&key, &nb); + else if (ret == 0) + ret = WC_TEST_RET_ENC_NC; + } + if (ret == 0) { + sigLen = (word32)sigSz; + do { + ret = wc_MlDsaKey_SignCtx(&key, NULL, 0, sig, &sigLen, msg, + (word32)sizeof(msg), rng); + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + } + if (ret == 0) { + res = 0; + do { + ret = wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res); + } while (ret == WC_NO_ERR_TRACE(MP_WOULDBLOCK)); + if ((ret == 0) && (res != 1)) + ret = WC_TEST_RET_ENC_NC; + } + + if (sig != NULL) + XFREE(sig, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (keyInited) + wc_MlDsaKey_Free(&key); + return ret; +} + +static wc_test_ret_t mldsa_nonblock_test(WC_RNG* rng) +{ + wc_test_ret_t ret = 0; +#ifndef WOLFSSL_NO_ML_DSA_44 + if (ret == 0) + ret = mldsa_nonblock_test_level(WC_ML_DSA_44, rng); +#endif +#ifndef WOLFSSL_NO_ML_DSA_65 + if (ret == 0) + ret = mldsa_nonblock_test_level(WC_ML_DSA_65, rng); +#endif +#ifndef WOLFSSL_NO_ML_DSA_87 + if (ret == 0) + ret = mldsa_nonblock_test_level(WC_ML_DSA_87, rng); +#endif + return ret; +} +#endif /* WC_MLDSA_NONBLOCK && make-key && sign && verify */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mldsa_test(void) { wc_test_ret_t ret; @@ -77188,6 +77465,14 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t mldsa_test(void) #endif /* (WOLFSSL_MLDSA_PUBLIC_KEY && !WOLFSSL_MLDSA_NO_VERIFY) || * (WOLFSSL_MLDSA_PRIVATE_KEY && !WOLFSSL_MLDSA_NO_SIGN) */ +#if defined(WC_MLDSA_NONBLOCK) && !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) + ret = mldsa_nonblock_test(&rng); + if (ret != 0) { + ERROR_OUT(ret, out); + } +#endif + #if !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) || \ !defined(WOLFSSL_MLDSA_NO_VERIFY) || \ defined(WOLFSSL_MLDSA_PRIVATE_KEY) || \ diff --git a/wolfssl/wolfcrypt/wc_mldsa.h b/wolfssl/wolfcrypt/wc_mldsa.h index be2bb8fcd88..dae83c84823 100644 --- a/wolfssl/wolfcrypt/wc_mldsa.h +++ b/wolfssl/wolfcrypt/wc_mldsa.h @@ -613,6 +613,23 @@ typedef struct wc_MlDsaParams { typedef struct wc_MlDsaParams MlDsaParams; #endif +#ifdef WC_MLDSA_NONBLOCK +/* Resumable state for a non-blocking ML-DSA operation. Attach one to a key with + * wc_MlDsaKey_set_nonblock(); the operation then runs one chunk per call, + * returning MP_WOULDBLOCK at each yield point until it returns a final result. + * Only the small-memory build (WOLFSSL_MLDSA_*_SMALL_MEM) is supported. */ +typedef struct wc_MlDsaNonBlock { + void* work; /* working allocation kept alive across yields */ + word32 workSz; /* size of work, for release on abandon or free */ + int state; /* operation phase cursor */ + word16 kappa; /* sign: rejection nonce carried across attempts */ + word16 row; /* per-row loop cursor (keygen/verify/sign matrix row) */ + byte hintPos; /* verify: running hint offset across the row loop */ + byte started; /* seedMu has been drawn and stored */ + byte seedMu[MLDSA_RND_SZ + MLDSA_MU_SZ]; /* sign: stable rnd||mu */ +} wc_MlDsaNonBlock; +#endif /* WC_MLDSA_NONBLOCK */ + struct wc_MlDsaKey { /* Set when this object holds the public key bytes in p. A key generated * on a crypto-callback device has none here and leaves it clear. */ @@ -658,6 +675,9 @@ struct wc_MlDsaKey { const wc_MlDsaParams* params; wc_Shake shake; +#ifdef WC_MLDSA_NONBLOCK + wc_MlDsaNonBlock* nb; /* non-NULL selects resumable operation */ +#endif #ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A /* Caller-supplied matrix A, expanded off-target and typically flash * resident. NULL means expand it here as usual. Read only, never freed: @@ -832,6 +852,11 @@ int wc_MlDsaKey_Delete(wc_MlDsaKey* key, wc_MlDsaKey** key_p); WOLFSSL_API int wc_MlDsaKey_Init(wc_MlDsaKey* key, void* heap, int devId); +#ifdef WC_MLDSA_NONBLOCK +WOLFSSL_API +int wc_MlDsaKey_set_nonblock(wc_MlDsaKey* key, wc_MlDsaNonBlock* nb); +#endif + #ifdef WOLF_PRIVATE_KEY_ID WOLFSSL_API int wc_MlDsaKey_InitId(wc_MlDsaKey* key, const unsigned char* id, int len, diff --git a/wolfssl/wolfcrypt/wc_mlkem.h b/wolfssl/wolfcrypt/wc_mlkem.h index 68b57cde5c5..3c16e052b8b 100644 --- a/wolfssl/wolfcrypt/wc_mlkem.h +++ b/wolfssl/wolfcrypt/wc_mlkem.h @@ -383,6 +383,25 @@ enum { #define MLKEM_PRF_T wc_Shake /* ML-KEM key. */ +#ifdef WC_MLKEM_NONBLOCK +/* Resumable state for a non-blocking ML-KEM operation. Attach one to a key with + * wc_MlKemKey_set_nonblock(); the operation then runs one chunk per call, + * returning MP_WOULDBLOCK at each yield point until it returns a final result. + * Only the small-memory build (WOLFSSL_MLKEM_*_SMALL_MEM) is supported. */ +typedef struct wc_MlKemNonBlock { + void* work; /* keygen/encapsulate vectors, kept alive across yields */ + void* decWork; /* decapsulate scratch: msg and kr for the re-encryption */ + word32 workSz; /* sizes of the two above, for release on abandon */ + word32 decWorkSz; + int state; /* keygen / decapsulate phase cursor */ + int encState; /* encapsulate phase cursor (direct or re-encryption) */ + int cmpFail; /* re-encryption block compare result so far */ + word16 row; /* per-row loop cursor */ + byte started; /* keygen/encaps randomness has been drawn and stored */ + byte rand[WC_ML_KEM_MAKEKEY_RAND_SZ]; /* stable keygen/encaps seed */ +} wc_MlKemNonBlock; +#endif /* WC_MLKEM_NONBLOCK */ + struct MlKemKey { /* Type of key: WC_ML_KEM_512, WC_ML_KEM_768, WC_ML_KEM_1024 */ int type; @@ -412,6 +431,10 @@ struct MlKemKey { MLKEM_HASH_T hash; /* A pseudo-random function object. */ MLKEM_PRF_T prf; +#ifdef WC_MLKEM_NONBLOCK + /* Non-NULL selects resumable (non-blocking) operation. */ + wc_MlKemNonBlock* nb; +#endif #ifndef WOLFSSL_MLKEM_DYNAMIC_KEYS /* Private key as a vector. */ @@ -454,6 +477,9 @@ WOLFSSL_API int wc_MlKemKey_Delete(MlKemKey* key, MlKemKey** key_p); WOLFSSL_API int wc_MlKemKey_Init(MlKemKey* key, int type, void* heap, int devId); WOLFSSL_API int wc_MlKemKey_Free(MlKemKey* key); +#ifdef WC_MLKEM_NONBLOCK +WOLFSSL_API int wc_MlKemKey_set_nonblock(MlKemKey* key, wc_MlKemNonBlock* nb); +#endif #ifdef WOLF_PRIVATE_KEY_ID WOLFSSL_API int wc_MlKemKey_Init_Id(MlKemKey* key, int type, const unsigned char* id, int len, void* heap, int devId); @@ -555,8 +581,10 @@ int mlkem_keygen(sword16* priv, sword16* pub, sword16* e, const sword16* a, int kp); #else WOLFSSL_LOCAL -int mlkem_keygen_seeds(sword16* priv, sword16* pub, MLKEM_PRF_T* prf, - sword16* e, sword16* cacheA, int kp, byte* seed, byte* noiseSeed); +void mlkem_keygen_ntt_priv(sword16* priv, int kp); +WOLFSSL_LOCAL +int mlkem_keygen_seeds_row(sword16* priv, sword16* pub, MLKEM_PRF_T* prf, + sword16* e, sword16* cacheA, int kp, byte* seed, byte* noiseSeed, int i); #endif #ifndef WOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM WOLFSSL_LOCAL @@ -565,10 +593,15 @@ int mlkem_encapsulate(const sword16* pub, sword16* bp, sword16* v, const sword16* m, int kp); #else WOLFSSL_LOCAL -int mlkem_encapsulate_seeds(const sword16* pub, MLKEM_PRF_T* prf, byte* c, - const byte* cmp, int* fail, sword16* bp, sword16* tp, sword16* sp, int kp, - const byte* msg, byte* seed, byte* coins, const sword16* cacheA, - sword16* fillA); +void mlkem_encapsulate_ntt_y(sword16* sp, int kp); +WOLFSSL_LOCAL +int mlkem_encapsulate_seeds_row(MLKEM_PRF_T* prf, byte* c, const byte* cmp, + int* cmpFail, sword16* bp, sword16* tp, sword16* sp, int kp, byte* seed, + byte* coins, const sword16* cacheA, sword16* fillA, int i); +WOLFSSL_LOCAL +int mlkem_encapsulate_seeds_tail(const sword16* pub, MLKEM_PRF_T* prf, + byte* c, const byte* cmp, int* fail, int cmpFail, sword16* bp, + sword16* tp, sword16* sp, int kp, const byte* msg, byte* coins); #endif WOLFSSL_LOCAL int mlkem_decapsulate(const sword16* priv, sword16* mp, sword16* bp,