From 271b35ab77ed98fdeae7e29e9c483d88c82b0681 Mon Sep 17 00:00:00 2001 From: "Matthias J. Kannwischer" Date: Tue, 22 Sep 2026 15:44:27 +0800 Subject: [PATCH] sign: Order rejection checks by rejection rate Check ||w0 - cs2||_inf < GAMMA2 - BETA before ||z||_inf < GAMMA1 - BETA, and compute ct0 along with its norm check in a separate loop after the check on z. Both loops and mld_compute_pack_z stop at the first polynomial that fails their check, so an attempt is cheapest when the check that rejects most often per polynomial runs first. Thanks to Georg Land for pointing out that this is faster. Signed-off-by: Matthias J. Kannwischer --- mldsa/src/sign.c | 57 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/mldsa/src/sign.c b/mldsa/src/sign.c index 13664213ef..814cbadfb9 100644 --- a/mldsa/src/sign.c +++ b/mldsa/src/sign.c @@ -842,24 +842,14 @@ __contract__( mld_poly_challenge(cp, challenge_bytes); mld_poly_ntt(cp); - /* @[FIPS204, Algorithm 7, lines 18+20] cs1 <- invNTT(c_hat o s1_hat) and - * z <- y + cs1, followed by the line-23 norm check ||z||_inf >= GAMMA1 - - * BETA. mld_compute_pack_z fuses all three per polynomial and, on success, - * packs z into sig; it returns MLD_ERR_FAIL if the norm check rejects z. */ - ret = mld_compute_pack_z(sig, cp, s1hat, y, t, scratch_w); - if (ret != 0) - { - goto cleanup; - } - - /* The remaining steps realize @[FIPS204, Algorithm 7, lines 21-28] (the - * low-bits norm check and the hint h) via the faster alternative formulation - * of @[Round3_Spec, Section 5.1]. @[FIPS204] explicitly permits this: the + /* The next steps realize @[FIPS204, Algorithm 7, lines 21-28] (the low-bits + * norm check and the hint h) via the faster alternative formulation of + * @[Round3_Spec, Section 5.1]. @[FIPS204] explicitly permits this: the * note accompanying Algorithm 7 states that the validity checks on z and the * computation of h may instead be implemented "as described in Section 5.1 of * [6]", and that reference is @[Round3_Spec, Section 5.1]. * - * The loop below builds w0 - cs2 + ct0 in place in w0; w1_packed is + * The two loops below build w0 - cs2 + ct0 in place in w0; w1_packed is * unmodified, and holds w1Encode(HighBits(w)) from line 13. Those are the * inputs to the streamlined computation of MakeHint explained below. * @@ -885,6 +875,15 @@ __contract__( * ||ct0||_inf >= GAMMA2 check is the mld_poly_chknorm(scratch_w, GAMMA2) * call on ct0 below; the weight bound is enforced by mld_pack_sig_h. * + * Order: we evaluate the two conditions of @[FIPS204, Algorithm 7, line 23] + * in reverse order, checking w0 - cs2 before z, since a rejected attempt + * returns at the first failing check and GAMMA2 < GAMMA1 makes the former + * fail more often. As line 23 rejects if either condition holds, the order + * does not change the result. Through timing, the order determines which + * check results are revealed. Revealing the result of any of the checks + * leaks nothing about the secret key, as the rejection probabilities are + * independent of it @[Round3_Spec, Section 5.5]. + * * Building w0 per-component and checking norms incrementally also avoids * allocating a full polyveck for h. */ for (k = 0; k < MLDSA_K; k++) @@ -893,8 +892,11 @@ __contract__( object_whole(scratch_w), object_whole(w0)) invariant(k <= MLDSA_K) - invariant(forall(k0, k, MLDSA_K, - array_abs_bound(w0->vec[k0].coeffs, 0, MLDSA_N, MLDSA_GAMMA2 + 1))) + invariant(forall(k0, 0, k, + array_abs_bound(w0->vec[k0].coeffs, 0, MLDSA_N, + MLDSA_GAMMA2 - MLDSA_BETA))) + invariant(forall(k1, k, MLDSA_K, + array_abs_bound(w0->vec[k1].coeffs, 0, MLDSA_N, MLDSA_GAMMA2 + 1))) decreases(MLDSA_K - k) ) { @@ -917,7 +919,30 @@ __contract__( ret = MLD_ERR_FAIL; /* reject */ goto cleanup; } + } + /* @[FIPS204, Algorithm 7, lines 18+20] cs1 <- invNTT(c_hat o s1_hat) and + * z <- y + cs1, followed by the line-23 norm check ||z||_inf >= GAMMA1 - + * BETA. mld_compute_pack_z fuses all three per polynomial and, on success, + * packs z into sig; it returns MLD_ERR_FAIL if the norm check rejects z. */ + ret = mld_compute_pack_z(sig, cp, s1hat, y, t, scratch_w); + if (ret != 0) + { + goto cleanup; + } + + for (k = 0; k < MLDSA_K; k++) + __loop__( + assigns(k, + object_whole(scratch_w), + object_whole(w0)) + invariant(k <= MLDSA_K) + invariant(forall(k0, k, MLDSA_K, + array_abs_bound(w0->vec[k0].coeffs, 0, MLDSA_N, + MLDSA_GAMMA2 - MLDSA_BETA))) + decreases(MLDSA_K - k) + ) + { /* @[FIPS204, Algorithm 7, line 25] ct0[k] <- invNTT(c_hat o t0_hat)[k]. */ mld_sk_t0hat_get_poly(scratch_w, t0hat, k); mld_poly_pointwise_montgomery(scratch_w, cp);