Skip to content

Commit 93f9f73

Browse files
authored
Make poly_chknorm constant flow (#2788)
### Issues: Resolves #V1982530715 and #V1982532566 ### Description of changes: The reference implementation implements `poly_chknorm` in variables time. It argues that while the input coefficients itself are secret in some call sites, it is okay to leak which coefficient lead to rejection. It, hence, does absolute value computation in constant-time and then checks the bound using a conditional. This approach appears safe, but somewhat unclean as it is still operating on secret data. When performing constant-time testing it also requires a number of declassifications. This commit takes a more conservative approach and changes `poly_chknorm` to a constant-time implementation in the hope that the performance penalty is acceptable. A minor change is that the API of `poly_chknorm` is changed to returning `0xFFFFFFFF` in the case of failure to be able to re-use existing constant-time primitives. ### Call-outs: PR source adapted from: Upstream PR in pq-code-package/mldsa-native#392. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.
1 parent 4ed9b0b commit 93f9f73

File tree

5 files changed

+44
-28
lines changed

5 files changed

+44
-28
lines changed

crypto/fipsmodule/ml_dsa/ml_dsa_ref/poly.c

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -228,29 +228,32 @@ void ml_dsa_poly_use_hint(ml_dsa_params *params,
228228
* Arguments: - const poly *a: pointer to polynomial
229229
* - int32_t B: norm bound
230230
*
231-
* Returns 0 if norm is strictly smaller than B <= (Q-1)/8 and 1 otherwise.
231+
* Returns 0 if norm is strictly smaller than B <= (Q-1)/8 and 0xFFFFFFFF otherwise.
232232
**************************************************/
233-
int ml_dsa_poly_chknorm(const ml_dsa_poly *a, int32_t B) {
233+
uint32_t ml_dsa_poly_chknorm(const ml_dsa_poly *a, int32_t B) {
234234
unsigned int i;
235235
int32_t t;
236+
uint32_t r = 0;
236237

237238
if(B > (ML_DSA_Q-1)/8) {
238-
return 1;
239+
return 0xFFFFFFFF;
239240
}
240241

241-
/* It is ok to leak which coefficient violates the bound since
242-
the probability for each coefficient is independent of secret
243-
data but we must not leak the sign of the centralized representative. */
242+
/* Constant-time implementation as defense-in-depth. According to Section 5.5
243+
of the Dilithium specification, it is safe to leak which coefficient violates
244+
the bound, but we implement this in constant-time as additional hardening.
245+
We accumulate violations using bitwise OR instead of early exit. See 5.5 in
246+
https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf
247+
*/
244248
for(i = 0; i < ML_DSA_N; ++i) {
245249
/* Absolute value */
246250
t = constant_time_select_int(constant_time_msb_w(a->coeffs[i]),
247251
-a->coeffs[i], a->coeffs[i]);
248252

249-
if(t >= B) {
250-
return 1;
251-
}
253+
/* Check if t >= B and accumulate result */
254+
r |= constant_time_ge_w((uint32_t)t, (uint32_t)B);
252255
}
253-
return 0;
256+
return r;
254257
}
255258

256259
/*************************************************

crypto/fipsmodule/ml_dsa/ml_dsa_ref/poly.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void ml_dsa_poly_use_hint(ml_dsa_params *params,
4343
const ml_dsa_poly *a,
4444
const ml_dsa_poly *h);
4545

46-
int ml_dsa_poly_chknorm(const ml_dsa_poly *a, int32_t B);
46+
uint32_t ml_dsa_poly_chknorm(const ml_dsa_poly *a, int32_t B);
4747

4848
void ml_dsa_poly_uniform(ml_dsa_poly *a,
4949
const uint8_t seed[ML_DSA_SEEDBYTES],

crypto/fipsmodule/ml_dsa/ml_dsa_ref/polyvec.c

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,16 @@ void ml_dsa_polyvecl_pointwise_acc_montgomery(ml_dsa_params *params,
223223
* - int32_t B: norm bound
224224
*
225225
* Returns 0 if norm of all polynomials is strictly smaller than B <= (Q-1)/8
226-
* and 1 otherwise.
226+
* and 0xFFFFFFFF otherwise.
227227
**************************************************/
228-
int ml_dsa_polyvecl_chknorm(ml_dsa_params *params, const polyvecl *v, int32_t bound) {
228+
uint32_t ml_dsa_polyvecl_chknorm(ml_dsa_params *params, const polyvecl *v, int32_t bound) {
229229
unsigned int i;
230+
uint32_t r = 0;
231+
230232
for(i = 0; i < params->l; ++i) {
231-
if(ml_dsa_poly_chknorm(&v->vec[i], bound)) {
232-
return 1;
233-
}
233+
r |= ml_dsa_poly_chknorm(&v->vec[i], bound);
234234
}
235-
return 0;
235+
return r;
236236
}
237237

238238
/**************************************************************/
@@ -418,16 +418,20 @@ void ml_dsa_polyveck_pointwise_poly_montgomery(ml_dsa_params *params,
418418
* - int32_t B: norm bound
419419
*
420420
* Returns 0 if norm of all polynomials are strictly smaller than B <= (Q-1)/8
421-
* and 1 otherwise.
421+
* and 0xFFFFFFFF otherwise.
422422
**************************************************/
423-
int ml_dsa_polyveck_chknorm(ml_dsa_params *params, const polyveck *v, int32_t bound) {
423+
uint32_t ml_dsa_polyveck_chknorm(ml_dsa_params *params, const polyveck *v, int32_t bound) {
424424
unsigned int i;
425+
uint32_t r = 0;
426+
427+
/* Reference: Leaks which polynomial violates the bound via a conditional.
428+
* We are more conservative to reduce the number of declassifications in
429+
* constant-time testing.
430+
*/
425431
for(i = 0; i < params->k; ++i) {
426-
if(ml_dsa_poly_chknorm(&v->vec[i], bound)) {
427-
return 1;
428-
}
432+
r |= ml_dsa_poly_chknorm(&v->vec[i], bound);
429433
}
430-
return 0;
434+
return r;
431435
}
432436

433437
/*************************************************

crypto/fipsmodule/ml_dsa/ml_dsa_ref/polyvec.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void ml_dsa_polyvecl_pointwise_acc_montgomery(ml_dsa_params *params,
4141
const polyvecl *u,
4242
const polyvecl *v);
4343

44-
int ml_dsa_polyvecl_chknorm(ml_dsa_params *params, const polyvecl *v, int32_t B);
44+
uint32_t ml_dsa_polyvecl_chknorm(ml_dsa_params *params, const polyvecl *v, int32_t B);
4545

4646
typedef struct {
4747
ml_dsa_poly vec[ML_DSA_K_MAX];
@@ -77,7 +77,7 @@ void ml_dsa_polyveck_pointwise_poly_montgomery(ml_dsa_params *params,
7777
const ml_dsa_poly *a,
7878
const polyveck *v);
7979

80-
int ml_dsa_polyveck_chknorm(ml_dsa_params *params, const polyveck *v, int32_t B);
80+
uint32_t ml_dsa_polyveck_chknorm(ml_dsa_params *params, const polyveck *v, int32_t B);
8181

8282
void ml_dsa_polyveck_power2round(ml_dsa_params *params,
8383
polyveck *v1,

crypto/fipsmodule/ml_dsa/ml_dsa_ref/sign.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ int ml_dsa_sign_internal(ml_dsa_params *params,
178178
uint8_t seedbuf[2*ML_DSA_SEEDBYTES + ML_DSA_TRBYTES + 2*ML_DSA_CRHBYTES];
179179
uint8_t *rho, *tr, *key, *mu, *rhoprime;
180180
uint16_t nonce = 0;
181+
uint32_t z_invalid, w0_invalid, h_invalid;
181182
polyvecl mat[ML_DSA_K_MAX], s1, y, z;
182183
polyveck t0, s2, w1, w0, h;
183184
ml_dsa_poly cp;
@@ -248,7 +249,8 @@ int ml_dsa_sign_internal(ml_dsa_params *params,
248249
ml_dsa_polyvecl_invntt_tomont(params, &z);
249250
ml_dsa_polyvecl_add(params, &z, &z, &y);
250251
ml_dsa_polyvecl_reduce(params, &z);
251-
if(ml_dsa_polyvecl_chknorm(params, &z, params->gamma1 - params->beta)) {
252+
z_invalid = ml_dsa_polyvecl_chknorm(params, &z, params->gamma1 - params->beta);
253+
if(z_invalid) {
252254
goto rej;
253255
}
254256

@@ -258,15 +260,22 @@ int ml_dsa_sign_internal(ml_dsa_params *params,
258260
ml_dsa_polyveck_invntt_tomont(params, &h);
259261
ml_dsa_polyveck_sub(params, &w0, &w0, &h);
260262
ml_dsa_polyveck_reduce(params, &w0);
261-
if(ml_dsa_polyveck_chknorm(params, &w0, params->gamma2 - params->beta)) {
263+
w0_invalid = ml_dsa_polyveck_chknorm(params, &w0, params->gamma2 - params->beta);
264+
/* Leaking the fact that a signature was rejected at this stage is acceptable as
265+
* the next attempt at a signature will be (indistinguishable from) independent of
266+
* this one. See 5.5 in
267+
* https://pq-crystals.org/dilithium/data/dilithium-specification-round3-20210208.pdf
268+
*/
269+
if(w0_invalid) {
262270
goto rej;
263271
}
264272

265273
/* FIPS 204: line 25 */
266274
ml_dsa_polyveck_pointwise_poly_montgomery(params, &h, &cp, &t0);
267275
ml_dsa_polyveck_invntt_tomont(params, &h);
268276
ml_dsa_polyveck_reduce(params, &h);
269-
if(ml_dsa_polyveck_chknorm(params, &h, params->gamma2)) {
277+
h_invalid = ml_dsa_polyveck_chknorm(params, &h, params->gamma2);
278+
if(h_invalid) {
270279
goto rej;
271280
}
272281

0 commit comments

Comments
 (0)