From b87017a1ca14fad41da7c6e8c85ccd3440c64560 Mon Sep 17 00:00:00 2001 From: Hanno Becker Date: Fri, 2 Jan 2026 04:25:54 +0000 Subject: [PATCH] Bundle multiple allocations into workspaces This commit replaces multiple successive allocations from MLD_ALLOC by a single allocation of a suitable 'workspace' structure. This reduces the number of allocations as well as the number of exit paths, thereby reducing the complexity of CBMC proofs. It also unifies the zeroization at the end of each function. Signed-off-by: Hanno Becker --- mldsa/src/sign.c | 434 +++++++++++++++++++++++------------------------ 1 file changed, 215 insertions(+), 219 deletions(-) diff --git a/mldsa/src/sign.c b/mldsa/src/sign.c index 02dd6e192..958f7d2f8 100644 --- a/mldsa/src/sign.c +++ b/mldsa/src/sign.c @@ -82,23 +82,28 @@ __contract__( static int mld_check_pct(uint8_t const pk[MLDSA_CRYPTO_PUBLICKEYBYTES], uint8_t const sk[MLDSA_CRYPTO_SECRETKEYBYTES]) { + typedef struct + { + MLD_ALIGN uint8_t signature[MLDSA_CRYPTO_BYTES]; + MLD_ALIGN uint8_t pk_test[MLDSA_CRYPTO_PUBLICKEYBYTES]; + } workspace; + MLD_ALIGN uint8_t message[1] = {0}; size_t siglen; int ret; - MLD_ALLOC(signature, uint8_t, MLDSA_CRYPTO_BYTES); - MLD_ALLOC(pk_test, uint8_t, MLDSA_CRYPTO_PUBLICKEYBYTES); - if (signature == NULL || pk_test == NULL) + MLD_ALLOC(ws, workspace, 1); + if (ws == NULL) { ret = MLD_ERR_OUT_OF_MEMORY; goto cleanup; } /* Copy public key for testing */ - mld_memcpy(pk_test, pk, MLDSA_CRYPTO_PUBLICKEYBYTES); + mld_memcpy(ws->pk_test, pk, MLDSA_CRYPTO_PUBLICKEYBYTES); /* Sign a test message using the original secret key */ - ret = crypto_sign_signature(signature, &siglen, message, sizeof(message), + ret = crypto_sign_signature(ws->signature, &siglen, message, sizeof(message), NULL, 0, sk); if (ret != 0) { @@ -109,18 +114,17 @@ static int mld_check_pct(uint8_t const pk[MLDSA_CRYPTO_PUBLICKEYBYTES], /* Deliberately break public key for testing purposes */ if (mld_break_pct()) { - pk_test[0] = ~pk_test[0]; + ws->pk_test[0] = ~ws->pk_test[0]; } #endif /* MLD_CONFIG_KEYGEN_PCT_BREAKAGE_TEST */ /* Verify the signature using the (potentially corrupted) public key */ - ret = crypto_sign_verify(signature, siglen, message, sizeof(message), NULL, 0, - pk_test); + ret = crypto_sign_verify(ws->signature, siglen, message, sizeof(message), + NULL, 0, ws->pk_test); cleanup: /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ - MLD_FREE(pk_test, uint8_t, MLDSA_CRYPTO_PUBLICKEYBYTES); - MLD_FREE(signature, uint8_t, MLDSA_CRYPTO_BYTES); + MLD_FREE(ws, workspace, 1); return ret; } @@ -227,29 +231,34 @@ __contract__( ensures(forall(k2, 0, MLDSA_K, array_bound(t1->vec[k2].coeffs, 0, MLDSA_N, 0, 1 << 10))) ensures(return_value == 0 || return_value == MLD_ERR_OUT_OF_MEMORY)) { + typedef struct + { + mld_polymat mat; + mld_polyvecl s1hat; + mld_polyveck t; + } workspace; + int ret; - MLD_ALLOC(mat, mld_polymat, 1); - MLD_ALLOC(s1hat, mld_polyvecl, 1); - MLD_ALLOC(t, mld_polyveck, 1); - if (mat == NULL || s1hat == NULL || t == NULL) + MLD_ALLOC(ws, workspace, 1); + if (ws == NULL) { ret = MLD_ERR_OUT_OF_MEMORY; goto cleanup; } /* Expand matrix */ - mld_polyvec_matrix_expand(mat, rho); + mld_polyvec_matrix_expand(&ws->mat, rho); /* Matrix-vector multiplication */ - *s1hat = *s1; - mld_polyvecl_ntt(s1hat); - mld_polyvec_matrix_pointwise_montgomery(t, mat, s1hat); - mld_polyveck_reduce(t); - mld_polyveck_invntt_tomont(t); + ws->s1hat = *s1; + mld_polyvecl_ntt(&ws->s1hat); + mld_polyvec_matrix_pointwise_montgomery(&ws->t, &ws->mat, &ws->s1hat); + mld_polyveck_reduce(&ws->t); + mld_polyveck_invntt_tomont(&ws->t); /* Add error vector s2 */ - mld_polyveck_add(t, s2); + mld_polyveck_add(&ws->t, s2); /* Reference: The following reduction is not present in the reference * implementation. Omitting this reduction requires the output of @@ -260,11 +269,11 @@ __contract__( * reasoning. We instead add an additional reduction, and can * consequently, relax the bounds requirements for the invntt. */ - mld_polyveck_reduce(t); + mld_polyveck_reduce(&ws->t); /* Decompose to get t1, t0 */ - mld_polyveck_caddq(t); - mld_polyveck_power2round(t1, t0, t); + mld_polyveck_caddq(&ws->t); + mld_polyveck_power2round(t1, t0, &ws->t); /* Pack public key and compute tr */ mld_pack_pk(pk, rho, t1); @@ -274,9 +283,7 @@ __contract__( cleanup: /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ - MLD_FREE(t, mld_polyveck, 1); - MLD_FREE(s1hat, mld_polyvecl, 1); - MLD_FREE(mat, mld_polymat, 1); + MLD_FREE(ws, workspace, 1); return ret; } @@ -286,30 +293,34 @@ int crypto_sign_keypair_internal(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], const uint8_t seed[MLDSA_SEEDBYTES]) { + typedef struct + { + MLD_ALIGN uint8_t seedbuf[2 * MLDSA_SEEDBYTES + MLDSA_CRHBYTES]; + MLD_ALIGN uint8_t inbuf[MLDSA_SEEDBYTES + 2]; + MLD_ALIGN uint8_t tr[MLDSA_TRBYTES]; + mld_polyvecl s1; + mld_polyveck s2; + mld_polyveck t1; + mld_polyveck t0; + } workspace; + int ret; const uint8_t *rho, *rhoprime, *key; - MLD_ALLOC(seedbuf, uint8_t, 2 * MLDSA_SEEDBYTES + MLDSA_CRHBYTES); - MLD_ALLOC(inbuf, uint8_t, MLDSA_SEEDBYTES + 2); - MLD_ALLOC(tr, uint8_t, MLDSA_TRBYTES); - MLD_ALLOC(s1, mld_polyvecl, 1); - MLD_ALLOC(s2, mld_polyveck, 1); - MLD_ALLOC(t1, mld_polyveck, 1); - MLD_ALLOC(t0, mld_polyveck, 1); - - if (seedbuf == NULL || inbuf == NULL || tr == NULL || s1 == NULL || - s2 == NULL || t1 == NULL || t0 == NULL) + + MLD_ALLOC(ws, workspace, 1); + if (ws == NULL) { ret = MLD_ERR_OUT_OF_MEMORY; goto cleanup; } /* Get randomness for rho, rhoprime and key */ - mld_memcpy(inbuf, seed, MLDSA_SEEDBYTES); - inbuf[MLDSA_SEEDBYTES + 0] = MLDSA_K; - inbuf[MLDSA_SEEDBYTES + 1] = MLDSA_L; - mld_shake256(seedbuf, 2 * MLDSA_SEEDBYTES + MLDSA_CRHBYTES, inbuf, + mld_memcpy(ws->inbuf, seed, MLDSA_SEEDBYTES); + ws->inbuf[MLDSA_SEEDBYTES + 0] = MLDSA_K; + ws->inbuf[MLDSA_SEEDBYTES + 1] = MLDSA_L; + mld_shake256(ws->seedbuf, 2 * MLDSA_SEEDBYTES + MLDSA_CRHBYTES, ws->inbuf, MLDSA_SEEDBYTES + 2); - rho = seedbuf; + rho = ws->seedbuf; rhoprime = rho + MLDSA_SEEDBYTES; key = rhoprime + MLDSA_CRHBYTES; @@ -317,30 +328,25 @@ int crypto_sign_keypair_internal(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], MLD_CT_TESTING_DECLASSIFY(rho, MLDSA_SEEDBYTES); /* Sample s1 and s2 */ - mld_sample_s1_s2(s1, s2, rhoprime); + mld_sample_s1_s2(&ws->s1, &ws->s2, rhoprime); /* Compute t0, t1, tr, and pk from rho, s1, s2 */ - ret = mld_compute_t0_t1_tr_from_sk_components(t0, t1, tr, pk, rho, s1, s2); + ret = mld_compute_t0_t1_tr_from_sk_components(&ws->t0, &ws->t1, ws->tr, pk, + rho, &ws->s1, &ws->s2); if (ret != 0) { goto cleanup; } /* Pack secret key */ - mld_pack_sk(sk, rho, tr, key, t0, s1, s2); + mld_pack_sk(sk, rho, ws->tr, key, &ws->t0, &ws->s1, &ws->s2); /* Constant time: pk is the public key, inherently public data */ MLD_CT_TESTING_DECLASSIFY(pk, MLDSA_CRYPTO_PUBLICKEYBYTES); cleanup: /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ - MLD_FREE(t0, mld_polyveck, 1); - MLD_FREE(t1, mld_polyveck, 1); - MLD_FREE(s2, mld_polyveck, 1); - MLD_FREE(s1, mld_polyvecl, 1); - MLD_FREE(tr, uint8_t, MLDSA_TRBYTES); - MLD_FREE(inbuf, uint8_t, MLDSA_SEEDBYTES + 2); - MLD_FREE(seedbuf, uint8_t, 2 * MLDSA_SEEDBYTES + MLDSA_CRHBYTES); + MLD_FREE(ws, workspace, 1); if (ret != 0) { @@ -486,68 +492,71 @@ __contract__( return_value == MLD_ERR_OUT_OF_MEMORY) ) { + typedef struct + { + MLD_ALIGN uint8_t challenge_bytes[MLDSA_CTILDEBYTES]; + /* TODO: Remove the following workaround for + * https://github.com/diffblue/cbmc/issues/8813 */ + MLD_ALIGN MLK_UNION_OR_STRUCT + { + mld_polyvecl y; + mld_polyveck h; + } + yh; + mld_polyvecl z; + mld_polyveck w1; + mld_polyveck w0; + mld_poly cp; + } workspace; + unsigned int n; uint32_t z_invalid, w0_invalid, h_invalid; int ret; - /* TODO: Remove the following workaround for - * https://github.com/diffblue/cbmc/issues/8813 */ - typedef MLK_UNION_OR_STRUCT - { - mld_polyvecl y; - mld_polyveck h; - } - yh_u; mld_polyvecl *y; mld_polyveck *h; - MLD_ALLOC(challenge_bytes, uint8_t, MLDSA_CTILDEBYTES); - MLD_ALLOC(yh, yh_u, 1); - MLD_ALLOC(z, mld_polyvecl, 1); - MLD_ALLOC(w1, mld_polyveck, 1); - MLD_ALLOC(w0, mld_polyveck, 1); - MLD_ALLOC(cp, mld_poly, 1); - - if (challenge_bytes == NULL || yh == NULL || z == NULL || w1 == NULL || - w0 == NULL || cp == NULL) + MLD_ALLOC(ws, workspace, 1); + if (ws == NULL) { ret = MLD_ERR_OUT_OF_MEMORY; goto cleanup; } - y = &yh->y; - h = &yh->h; + + y = &ws->yh.y; + h = &ws->yh.h; /* Sample intermediate vector y */ mld_polyvecl_uniform_gamma1(y, rhoprime, nonce); /* Matrix-vector multiplication */ - *z = *y; - mld_polyvecl_ntt(z); - mld_polyvec_matrix_pointwise_montgomery(w0, mat, z); - mld_polyveck_reduce(w0); - mld_polyveck_invntt_tomont(w0); + ws->z = *y; + mld_polyvecl_ntt(&ws->z); + mld_polyvec_matrix_pointwise_montgomery(&ws->w0, mat, &ws->z); + mld_polyveck_reduce(&ws->w0); + mld_polyveck_invntt_tomont(&ws->w0); /* Decompose w and call the random oracle */ - mld_polyveck_caddq(w0); - mld_polyveck_decompose(w1, w0); - mld_polyveck_pack_w1(sig, w1); + mld_polyveck_caddq(&ws->w0); + mld_polyveck_decompose(&ws->w1, &ws->w0); + mld_polyveck_pack_w1(sig, &ws->w1); - mld_H(challenge_bytes, MLDSA_CTILDEBYTES, mu, MLDSA_CRHBYTES, sig, + mld_H(ws->challenge_bytes, MLDSA_CTILDEBYTES, mu, MLDSA_CRHBYTES, sig, MLDSA_K * MLDSA_POLYW1_PACKEDBYTES, NULL, 0); /* Constant time: Leaking challenge_bytes does not reveal any information * about the secret key as H() is modelled as random oracle. * This also applies to challenges for rejected signatures. * See Section 5.5 of @[Round3_Spec]. */ - MLD_CT_TESTING_DECLASSIFY(challenge_bytes, MLDSA_CTILDEBYTES); - mld_poly_challenge(cp, challenge_bytes); - mld_poly_ntt(cp); + MLD_CT_TESTING_DECLASSIFY(ws->challenge_bytes, MLDSA_CTILDEBYTES); + mld_poly_challenge(&ws->cp, ws->challenge_bytes); + mld_poly_ntt(&ws->cp); /* Compute z, reject if it reveals secret */ - mld_polyvecl_pointwise_poly_montgomery(z, cp, s1); - mld_polyvecl_invntt_tomont(z); - mld_polyvecl_add(z, y); - mld_polyvecl_reduce(z); + mld_polyvecl_pointwise_poly_montgomery(&ws->z, &ws->cp, s1); + mld_polyvecl_invntt_tomont(&ws->z); + mld_polyvecl_add(&ws->z, y); + mld_polyvecl_reduce(&ws->z); - z_invalid = mld_polyvecl_chknorm(z, MLDSA_GAMMA1 - MLDSA_BETA); + z_invalid = mld_polyvecl_chknorm(&ws->z, MLDSA_GAMMA1 - MLDSA_BETA); /* Constant time: It is fine (and prohibitively expensive to avoid) * leaking the result of the norm check. In case of rejection it * would even be okay to leak which coefficient led to rejection @@ -563,17 +572,17 @@ __contract__( /* If z is valid, then its coefficients are bounded by */ /* MLDSA_GAMMA1 - MLDSA_BETA. This will be needed below */ /* to prove the pre-condition of pack_sig() */ - mld_assert_abs_bound_2d(z->vec, MLDSA_L, MLDSA_N, + mld_assert_abs_bound_2d(ws->z.vec, MLDSA_L, MLDSA_N, (MLDSA_GAMMA1 - MLDSA_BETA)); /* Check that subtracting cs2 does not change high bits of w and low bits * do not reveal secret information */ - mld_polyveck_pointwise_poly_montgomery(h, cp, s2); + mld_polyveck_pointwise_poly_montgomery(h, &ws->cp, s2); mld_polyveck_invntt_tomont(h); - mld_polyveck_sub(w0, h); - mld_polyveck_reduce(w0); + mld_polyveck_sub(&ws->w0, h); + mld_polyveck_reduce(&ws->w0); - w0_invalid = mld_polyveck_chknorm(w0, MLDSA_GAMMA2 - MLDSA_BETA); + w0_invalid = mld_polyveck_chknorm(&ws->w0, MLDSA_GAMMA2 - MLDSA_BETA); /* Constant time: w0_invalid may be leaked - see comment for z_invalid. */ MLD_CT_TESTING_DECLASSIFY(&w0_invalid, sizeof(uint32_t)); if (w0_invalid) @@ -583,7 +592,7 @@ __contract__( } /* Compute hints for w1 */ - mld_polyveck_pointwise_poly_montgomery(h, cp, t0); + mld_polyveck_pointwise_poly_montgomery(h, &ws->cp, t0); mld_polyveck_invntt_tomont(h); mld_polyveck_reduce(h); @@ -596,7 +605,7 @@ __contract__( goto cleanup; } - mld_polyveck_add(w0, h); + mld_polyveck_add(&ws->w0, h); /* Constant time: At this point all norm checks have passed and we, hence, * know that the signature does not leak any secret information. @@ -606,9 +615,9 @@ __contract__( * h=c*t0 is public as both c and t0 are public. * For a more detailed discussion, refer to https://eprint.iacr.org/2022/1406. */ - MLD_CT_TESTING_DECLASSIFY(w0, sizeof(*w0)); - MLD_CT_TESTING_DECLASSIFY(w1, sizeof(*w1)); - n = mld_polyveck_make_hint(h, w0, w1); + MLD_CT_TESTING_DECLASSIFY(&ws->w0, sizeof(ws->w0)); + MLD_CT_TESTING_DECLASSIFY(&ws->w1, sizeof(ws->w1)); + n = mld_polyveck_make_hint(h, &ws->w0, &ws->w1); if (n > MLDSA_OMEGA) { ret = MLD_ERR_FAIL; /* reject */ @@ -619,19 +628,14 @@ __contract__( /* Constant time: At this point it is clear that the signature is valid - it * can, hence, be considered public. */ MLD_CT_TESTING_DECLASSIFY(h, sizeof(*h)); - MLD_CT_TESTING_DECLASSIFY(z, sizeof(*z)); - mld_pack_sig(sig, challenge_bytes, z, h, n); + MLD_CT_TESTING_DECLASSIFY(&ws->z, sizeof(ws->z)); + mld_pack_sig(sig, ws->challenge_bytes, &ws->z, h, n); ret = 0; /* success */ cleanup: /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ - MLD_FREE(cp, mld_poly, 1); - MLD_FREE(w0, mld_polyveck, 1); - MLD_FREE(w1, mld_polyveck, 1); - MLD_FREE(z, mld_polyvecl, 1); - MLD_FREE(yh, yh_u, 1); - MLD_FREE(challenge_bytes, uint8_t, MLDSA_CTILDEBYTES); + MLD_FREE(ws, workspace, 1); return ret; } @@ -643,28 +647,33 @@ int crypto_sign_signature_internal( const uint8_t rnd[MLDSA_RNDBYTES], const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES], int externalmu) { + typedef struct + { + MLD_ALIGN uint8_t + seedbuf[2 * MLDSA_SEEDBYTES + MLDSA_TRBYTES + 2 * MLDSA_CRHBYTES]; + mld_polymat mat; + mld_polyvecl s1; + mld_polyveck t0; + mld_polyveck s2; + } workspace; + int ret; uint8_t *rho, *tr, *key, *mu, *rhoprime; uint16_t nonce = 0; - MLD_ALLOC(seedbuf, uint8_t, - 2 * MLDSA_SEEDBYTES + MLDSA_TRBYTES + 2 * MLDSA_CRHBYTES); - MLD_ALLOC(mat, mld_polymat, 1); - MLD_ALLOC(s1, mld_polyvecl, 1); - MLD_ALLOC(t0, mld_polyveck, 1); - MLD_ALLOC(s2, mld_polyveck, 1); - - if (seedbuf == NULL || mat == NULL || s1 == NULL || t0 == NULL || s2 == NULL) + + MLD_ALLOC(ws, workspace, 1); + if (ws == NULL) { ret = MLD_ERR_OUT_OF_MEMORY; goto cleanup; } - rho = seedbuf; + rho = ws->seedbuf; tr = rho + MLDSA_SEEDBYTES; key = tr + MLDSA_TRBYTES; mu = key + MLDSA_SEEDBYTES; rhoprime = mu + MLDSA_CRHBYTES; - mld_unpack_sk(rho, tr, key, t0, s1, s2, sk); + mld_unpack_sk(rho, tr, key, &ws->t0, &ws->s1, &ws->s2, sk); if (!externalmu) { @@ -684,10 +693,10 @@ int crypto_sign_signature_internal( /* Constant time: rho is part of the public key and, hence, public. */ MLD_CT_TESTING_DECLASSIFY(rho, MLDSA_SEEDBYTES); /* Expand matrix and transform vectors */ - mld_polyvec_matrix_expand(mat, rho); - mld_polyvecl_ntt(s1); - mld_polyveck_ntt(s2); - mld_polyveck_ntt(t0); + mld_polyvec_matrix_expand(&ws->mat, rho); + mld_polyvecl_ntt(&ws->s1); + mld_polyveck_ntt(&ws->s2); + mld_polyveck_ntt(&ws->t0); /* By default, return failure. Flip to success and write output * once signature generation succeeds. */ @@ -705,10 +714,10 @@ int crypto_sign_signature_internal( /* loop. We can therefore re-assert their bounds here as part of the */ /* loop invariant. This makes proof noticeably faster with CBMC */ invariant(forall(k1, 0, MLDSA_K, forall(l1, 0, MLDSA_L, - array_bound(mat->vec[k1].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) - invariant(forall(k2, 0, MLDSA_K, array_abs_bound(t0->vec[k2].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) - invariant(forall(k3, 0, MLDSA_L, array_abs_bound(s1->vec[k3].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) - invariant(forall(k4, 0, MLDSA_K, array_abs_bound(s2->vec[k4].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + array_bound(ws->mat.vec[k1].vec[l1].coeffs, 0, MLDSA_N, 0, MLDSA_Q)))) + invariant(forall(k2, 0, MLDSA_K, array_abs_bound(ws->t0.vec[k2].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + invariant(forall(k3, 0, MLDSA_L, array_abs_bound(ws->s1.vec[k3].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) + invariant(forall(k4, 0, MLDSA_K, array_abs_bound(ws->s2.vec[k4].coeffs, 0, MLDSA_N, MLD_NTT_BOUND))) invariant(ret == MLD_ERR_FAIL) ) { @@ -723,8 +732,8 @@ int crypto_sign_signature_internal( break; } - ret = mld_attempt_signature_generation(sig, mu, rhoprime, nonce, mat, s1, - s2, t0); + ret = mld_attempt_signature_generation(sig, mu, rhoprime, nonce, &ws->mat, + &ws->s1, &ws->s2, &ws->t0); nonce++; if (ret == 0) { @@ -750,12 +759,7 @@ int crypto_sign_signature_internal( } /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ - MLD_FREE(s2, mld_polyveck, 1); - MLD_FREE(t0, mld_polyveck, 1); - MLD_FREE(s1, mld_polyvecl, 1); - MLD_FREE(mat, mld_polymat, 1); - MLD_FREE(seedbuf, uint8_t, - 2 * MLDSA_SEEDBYTES + MLDSA_TRBYTES + 2 * MLDSA_CRHBYTES); + MLD_FREE(ws, workspace, 1); return ret; } @@ -767,19 +771,24 @@ int crypto_sign_signature(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, size_t ctxlen, const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES]) { + typedef struct + { + MLD_ALIGN uint8_t pre[MLD_DOMAIN_SEPARATION_MAX_BYTES]; + MLD_ALIGN uint8_t rnd[MLDSA_RNDBYTES]; + } workspace; + size_t pre_len; int ret; - MLD_ALLOC(pre, uint8_t, MLD_DOMAIN_SEPARATION_MAX_BYTES); - MLD_ALLOC(rnd, uint8_t, MLDSA_RNDBYTES); - if (pre == NULL || rnd == NULL) + MLD_ALLOC(ws, workspace, 1); + if (ws == NULL) { ret = MLD_ERR_OUT_OF_MEMORY; goto cleanup; } /* Prepare domain separation prefix for pure ML-DSA */ - pre_len = mld_prepare_domain_separation_prefix(pre, NULL, 0, ctx, ctxlen, + pre_len = mld_prepare_domain_separation_prefix(ws->pre, NULL, 0, ctx, ctxlen, MLD_PREHASH_NONE); if (pre_len == 0) { @@ -789,11 +798,11 @@ int crypto_sign_signature(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, /* Randomized variant of ML-DSA. If you need the deterministic variant, * call crypto_sign_signature_internal directly with all-zero rnd. */ - mld_randombytes(rnd, MLDSA_RNDBYTES); - MLD_CT_TESTING_SECRET(rnd, sizeof(rnd)); + mld_randombytes(ws->rnd, MLDSA_RNDBYTES); + MLD_CT_TESTING_SECRET(ws->rnd, sizeof(ws->rnd)); - ret = crypto_sign_signature_internal(sig, siglen, m, mlen, pre, pre_len, rnd, - sk, 0); + ret = crypto_sign_signature_internal(sig, siglen, m, mlen, ws->pre, pre_len, + ws->rnd, sk, 0); cleanup: if (ret != 0) @@ -809,8 +818,7 @@ int crypto_sign_signature(uint8_t sig[MLDSA_CRYPTO_BYTES], size_t *siglen, } /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ - MLD_FREE(rnd, uint8_t, MLDSA_RNDBYTES); - MLD_FREE(pre, uint8_t, MLD_DOMAIN_SEPARATION_MAX_BYTES); + MLD_FREE(ws, workspace, 1); return ret; } @@ -874,24 +882,27 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen, const uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], int externalmu) { + typedef struct + { + MLD_ALIGN uint8_t buf[MLDSA_K * MLDSA_POLYW1_PACKEDBYTES]; + MLD_ALIGN uint8_t rho[MLDSA_SEEDBYTES]; + MLD_ALIGN uint8_t mu[MLDSA_CRHBYTES]; + MLD_ALIGN uint8_t c[MLDSA_CTILDEBYTES]; + MLD_ALIGN uint8_t c2[MLDSA_CTILDEBYTES]; + mld_poly cp; + mld_polymat mat; + mld_polyvecl z; + mld_polyveck t1; + mld_polyveck w1; + mld_polyveck tmp; + mld_polyveck h; + } workspace; + unsigned int i; int ret; - MLD_ALLOC(buf, uint8_t, (MLDSA_K * MLDSA_POLYW1_PACKEDBYTES)); - MLD_ALLOC(rho, uint8_t, MLDSA_SEEDBYTES); - MLD_ALLOC(mu, uint8_t, MLDSA_CRHBYTES); - MLD_ALLOC(c, uint8_t, MLDSA_CTILDEBYTES); - MLD_ALLOC(c2, uint8_t, MLDSA_CTILDEBYTES); - MLD_ALLOC(cp, mld_poly, 1); - MLD_ALLOC(mat, mld_polymat, 1); - MLD_ALLOC(z, mld_polyvecl, 1); - MLD_ALLOC(t1, mld_polyveck, 1); - MLD_ALLOC(w1, mld_polyveck, 1); - MLD_ALLOC(tmp, mld_polyveck, 1); - MLD_ALLOC(h, mld_polyveck, 1); - - if (buf == NULL || rho == NULL || mu == NULL || c == NULL || c2 == NULL || - cp == NULL || mat == NULL || z == NULL || t1 == NULL || w1 == NULL || - tmp == NULL || h == NULL) + + MLD_ALLOC(ws, workspace, 1); + if (ws == NULL) { ret = MLD_ERR_OUT_OF_MEMORY; goto cleanup; @@ -903,17 +914,17 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen, goto cleanup; } - mld_unpack_pk(rho, t1, pk); + mld_unpack_pk(ws->rho, &ws->t1, pk); /* mld_unpack_sig and mld_polyvecl_chknorm signal failure through a * single non-zero error code that's not yet aligned with MLD_ERR_XXX. * Map it to MLD_ERR_FAIL explicitly. */ - if (mld_unpack_sig(c, z, h, sig)) + if (mld_unpack_sig(ws->c, &ws->z, &ws->h, sig)) { ret = MLD_ERR_FAIL; goto cleanup; } - if (mld_polyvecl_chknorm(z, MLDSA_GAMMA1 - MLDSA_BETA)) + if (mld_polyvecl_chknorm(&ws->z, MLDSA_GAMMA1 - MLDSA_BETA)) { ret = MLD_ERR_FAIL; goto cleanup; @@ -925,7 +936,7 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen, MLD_ALIGN uint8_t hpk[MLDSA_CRHBYTES]; mld_H(hpk, MLDSA_TRBYTES, pk, MLDSA_CRYPTO_PUBLICKEYBYTES, NULL, 0, NULL, 0); - mld_H(mu, MLDSA_CRHBYTES, hpk, MLDSA_TRBYTES, pre, prelen, m, mlen); + mld_H(ws->mu, MLDSA_CRHBYTES, hpk, MLDSA_TRBYTES, pre, prelen, m, mlen); /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ mld_zeroize(hpk, sizeof(hpk)); @@ -933,32 +944,32 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen, else { /* mu has been provided directly */ - mld_memcpy(mu, m, MLDSA_CRHBYTES); + mld_memcpy(ws->mu, m, MLDSA_CRHBYTES); } /* Matrix-vector multiplication; compute Az - c2^dt1 */ - mld_poly_challenge(cp, c); - mld_polyvec_matrix_expand(mat, rho); + mld_poly_challenge(&ws->cp, ws->c); + mld_polyvec_matrix_expand(&ws->mat, ws->rho); - mld_polyvecl_ntt(z); - mld_polyvec_matrix_pointwise_montgomery(w1, mat, z); + mld_polyvecl_ntt(&ws->z); + mld_polyvec_matrix_pointwise_montgomery(&ws->w1, &ws->mat, &ws->z); - mld_poly_ntt(cp); - mld_polyveck_shiftl(t1); - mld_polyveck_ntt(t1); + mld_poly_ntt(&ws->cp); + mld_polyveck_shiftl(&ws->t1); + mld_polyveck_ntt(&ws->t1); - mld_polyveck_pointwise_poly_montgomery(tmp, cp, t1); + mld_polyveck_pointwise_poly_montgomery(&ws->tmp, &ws->cp, &ws->t1); - mld_polyveck_sub(w1, tmp); - mld_polyveck_reduce(w1); - mld_polyveck_invntt_tomont(w1); + mld_polyveck_sub(&ws->w1, &ws->tmp); + mld_polyveck_reduce(&ws->w1); + mld_polyveck_invntt_tomont(&ws->w1); /* Reconstruct w1 */ - mld_polyveck_caddq(w1); - mld_polyveck_use_hint(tmp, w1, h); - mld_polyveck_pack_w1(buf, tmp); + mld_polyveck_caddq(&ws->w1); + mld_polyveck_use_hint(&ws->tmp, &ws->w1, &ws->h); + mld_polyveck_pack_w1(ws->buf, &ws->tmp); /* Call random oracle and verify challenge */ - mld_H(c2, MLDSA_CTILDEBYTES, mu, MLDSA_CRHBYTES, buf, + mld_H(ws->c2, MLDSA_CTILDEBYTES, ws->mu, MLDSA_CRHBYTES, ws->buf, MLDSA_K * MLDSA_POLYW1_PACKEDBYTES, NULL, 0); /* Constant time: All data in verification is usually considered public. @@ -969,14 +980,14 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen, * a hash call (that should behave like a random oracle), it is safe to * declassify here even with a secret message. */ - MLD_CT_TESTING_DECLASSIFY(c2, MLDSA_CTILDEBYTES); + MLD_CT_TESTING_DECLASSIFY(ws->c2, MLDSA_CTILDEBYTES); ret = MLD_ERR_FAIL; for (i = 0; i < MLDSA_CTILDEBYTES; ++i) __loop__( invariant(i <= MLDSA_CTILDEBYTES) ) { - if (c[i] != c2[i]) + if (ws->c[i] != ws->c2[i]) { goto cleanup; } @@ -986,18 +997,7 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen, cleanup: /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ - MLD_FREE(h, mld_polyveck, 1); - MLD_FREE(tmp, mld_polyveck, 1); - MLD_FREE(w1, mld_polyveck, 1); - MLD_FREE(t1, mld_polyveck, 1); - MLD_FREE(z, mld_polyvecl, 1); - MLD_FREE(mat, mld_polymat, 1); - MLD_FREE(cp, mld_poly, 1); - MLD_FREE(c2, uint8_t, MLDSA_CTILDEBYTES); - MLD_FREE(c, uint8_t, MLDSA_CTILDEBYTES); - MLD_FREE(mu, uint8_t, MLDSA_CRHBYTES); - MLD_FREE(rho, uint8_t, MLDSA_SEEDBYTES); - MLD_FREE(buf, uint8_t, (MLDSA_K * MLDSA_POLYW1_PACKEDBYTES)); + MLD_FREE(ws, workspace, 1); return ret; } @@ -1314,40 +1314,44 @@ MLD_EXTERNAL_API int crypto_sign_pk_from_sk(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], const uint8_t sk[MLDSA_CRYPTO_SECRETKEYBYTES]) { + typedef struct + { + MLD_ALIGN uint8_t rho[MLDSA_SEEDBYTES]; + MLD_ALIGN uint8_t tr[MLDSA_TRBYTES]; + MLD_ALIGN uint8_t tr_computed[MLDSA_TRBYTES]; + MLD_ALIGN uint8_t key[MLDSA_SEEDBYTES]; + mld_polyvecl s1; + mld_polyveck s2; + mld_polyveck t0; + mld_polyveck t0_computed; + mld_polyveck t1; + } workspace; + uint8_t cmp, cmp0, cmp1; int ret; - MLD_ALLOC(rho, uint8_t, MLDSA_SEEDBYTES); - MLD_ALLOC(tr, uint8_t, MLDSA_TRBYTES); - MLD_ALLOC(tr_computed, uint8_t, MLDSA_TRBYTES); - MLD_ALLOC(key, uint8_t, MLDSA_SEEDBYTES); - MLD_ALLOC(s1, mld_polyvecl, 1); - MLD_ALLOC(s2, mld_polyveck, 1); - MLD_ALLOC(t0, mld_polyveck, 1); - MLD_ALLOC(t0_computed, mld_polyveck, 1); - MLD_ALLOC(t1, mld_polyveck, 1); - - if (rho == NULL || tr == NULL || tr_computed == NULL || key == NULL || - s1 == NULL || s2 == NULL || t0 == NULL || t0_computed == NULL || - t1 == NULL) + + MLD_ALLOC(ws, workspace, 1); + if (ws == NULL) { ret = MLD_ERR_OUT_OF_MEMORY; goto cleanup; } /* Unpack secret key */ - mld_unpack_sk(rho, tr, key, t0, s1, s2, sk); + mld_unpack_sk(ws->rho, ws->tr, ws->key, &ws->t0, &ws->s1, &ws->s2, sk); /* Recompute t0, t1, tr, and pk from rho, s1, s2 */ - ret = mld_compute_t0_t1_tr_from_sk_components(t0_computed, t1, tr_computed, - pk, rho, s1, s2); + ret = mld_compute_t0_t1_tr_from_sk_components(&ws->t0_computed, &ws->t1, + ws->tr_computed, pk, ws->rho, + &ws->s1, &ws->s2); if (ret != 0) { goto cleanup; } /* Validate t0 and tr using constant-time comparisons */ - cmp0 = mld_ct_memcmp(t0, t0_computed, sizeof(mld_polyveck)); - cmp1 = mld_ct_memcmp(tr, tr_computed, MLDSA_TRBYTES); + cmp0 = mld_ct_memcmp(&ws->t0, &ws->t0_computed, sizeof(mld_polyveck)); + cmp1 = mld_ct_memcmp(ws->tr, ws->tr_computed, MLDSA_TRBYTES); cmp = mld_value_barrier_u8(cmp0 | cmp1); /* Declassify the final result of the validity check. */ @@ -1365,15 +1369,7 @@ int crypto_sign_pk_from_sk(uint8_t pk[MLDSA_CRYPTO_PUBLICKEYBYTES], MLD_CT_TESTING_DECLASSIFY(pk, MLDSA_CRYPTO_PUBLICKEYBYTES); /* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */ - MLD_FREE(t1, mld_polyveck, 1); - MLD_FREE(t0_computed, mld_polyveck, 1); - MLD_FREE(t0, mld_polyveck, 1); - MLD_FREE(s2, mld_polyveck, 1); - MLD_FREE(s1, mld_polyvecl, 1); - MLD_FREE(key, uint8_t, MLDSA_SEEDBYTES); - MLD_FREE(tr_computed, uint8_t, MLDSA_TRBYTES); - MLD_FREE(tr, uint8_t, MLDSA_TRBYTES); - MLD_FREE(rho, uint8_t, MLDSA_SEEDBYTES); + MLD_FREE(ws, workspace, 1); return ret; }