Skip to content

Commit e2cf924

Browse files
committed
ML-DSA: add rejection tests and its generator
Signed-off-by: Stephan Mueller <smueller@chronox.de>
1 parent 958faf8 commit e2cf924

19 files changed

+75776
-3696
lines changed

README.debugging.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,71 @@ To perform such a side channel analysis, apply the following steps:
7070
[5] Mehmet Sinan İnci, Berk Gülmezoğlu, Gorka Irazoqui, Thomas Eisenbarth, Berk Sunar, [Seriously, get off my cloud! Cross-VM RSA Key Recovery in a Public Cloud](https://eprint.iacr.org/2015/898.pdf).
7171

7272
[6] Thierry Kaufmann, Hervé Pelletier, Serge Vaudenay, and Karine Villegas [When Constant-time Source Yields Variable-time Binary: Exploiting Curve25519-donna Built with MSVC 2015](https://infoscience.epfl.ch/record/223794/files/32_1.pdf).
73+
74+
# Generation of ML-DSA Signature Generation Rejection Test Vectors
75+
76+
The ML-DSA signature generation contains rejection code paths which are probabilistically triggered during production use. According to FIPS IG 10.3.A and also to ensure proper implementation, the rejection code paths should all be tested.
77+
78+
According to FIPS IG 10.3.A, for ML-DSA 87 and 65, three of the four possible rejection code paths are to be triggered (z, r0 and h). For ML-DSA 44, in addition the ct0 rejection code path is to be tested.
79+
80+
TODO: The tests are not able to trigger all four rejection code paths for ML-DSA 44 - this needs to be checked with NIST.
81+
82+
To generate ML-DSA signature generation test vectors that trigger all required code paths as mentioned above, leancrypto contains a test generator to generate such test vectors for pure, pre-hash and external MU interfaces. The test vector for the internal interface is derived from [1] and thus not generated by leancrypto.
83+
84+
The following steps have to be followed if test vectors shall be generated anew:
85+
86+
1. In the file `dilithium_signature_impl.h` the macro `REJECTION_TEST_SAMPLING` must be defined.
87+
88+
2. In the file `dilithium_edge_case_tester.c` the macro `GENERATE_KEYS` must be defined.
89+
90+
3. Ensure that SHA2-256 is enabled, i.e. the meson option `sha2-256` must be enabled (which is active by default)
91+
92+
4. Compile leancrypto
93+
94+
5. Generate pure ML-DSA vectors by executing:
95+
96+
* ML-DSA 87: `build/ml-dsa/tests/dilithium_pure_edge_case_tester_c > ml-dsa/tests/dilithium_pure_rejection_vectors_87.h`
97+
98+
* ML-DSA 65: `build/ml-dsa/tests/dilithium_65_pure_edge_case_tester_c > ml-dsa/tests/dilithium_pure_rejection_vectors_65.h`
99+
100+
* ML-DSA 44: `build/ml-dsa/tests/dilithium_44_pure_edge_case_tester_c > ml-dsa/tests/dilithium_pure_rejection_vectors_44.h`
101+
102+
6. Generate pre-hash ML-DSA vectors by executing:
103+
104+
* ML-DSA 87: `build/ml-dsa/tests/dilithium_prehash_edge_case_tester_c > ml-dsa/tests/dilithium_prehash_rejection_vectors_87.h`
105+
106+
* ML-DSA 65: `build/ml-dsa/tests/dilithium_65_prehash_edge_case_tester_c > ml-dsa/tests/dilithium_prehash_rejection_vectors_65.h`
107+
108+
* ML-DSA 44: `build/ml-dsa/tests/dilithium_44_prehash_edge_case_tester_c > ml-dsa/tests/dilithium_prehash_rejection_vectors_44.h`
109+
110+
7. Generate external-Mu ML-DSA vectors by executing:
111+
112+
* ML-DSA 87: `build/ml-dsa/tests/dilithium_external_mu_edge_case_tester_c > ml-dsa/tests/dilithium_external_mu_rejection_vectors_87.h`
113+
114+
* ML-DSA 65: `build/ml-dsa/tests/dilithium_65_external_mu_edge_case_tester_c > ml-dsa/tests/dilithium_external_mu_rejection_vectors_65.h`
115+
116+
* ML-DSA 44: `build/ml-dsa/tests/dilithium_44_external_mu_edge_case_tester_c > ml-dsa/tests/dilithium_external_mu_rejection_vectors_44.h`
117+
118+
8. Do not forget to undefine the macros set in steps 1 and 2.
119+
120+
## References
121+
122+
[1] https://pages.nist.gov/ACVP/draft-celi-acvp-ml-dsa.html#name-known-answer-tests
123+
124+
# Verification of ML-DSA Signature Generation Rejection Test Vectors
125+
126+
The (ML-DSA signature generation rejection test vectors)[Generation of ML-DSA Signature Generation Rejection Test Vectors] can be verified to indeed trigger all rejection code paths as follows:
127+
128+
1. Enable ML-DSA debug output: `meson configure build -Ddilithium_debug=enabled`
129+
130+
2. Compile leancrypto
131+
132+
3. Verify the triggering of the rejection code paths by invoking `build/ml-dsa/tests/dilithium_edge_case_tester_c | less` and check the presence of the following logging output depending on which rejection code path you are interested in:
133+
134+
* For z rejectin: `Siggen - z rejection`
135+
136+
* For r1 rejection: `Siggen - r0 rejection`
137+
138+
* For h rejection: `Siggen - h rejection`
139+
140+
* For ct0 rejection: `Siggen - ct0 rejection`

ml-dsa/src/avx2/dilithium_signature_avx2.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ LC_INTERFACE_FUNCTION(int, lc_dilithium_keypair_avx2,
132132
}
133133

134134
dilithium_keypair_tester(&tested, "Dilithium Keygen AVX2",
135-
lc_dilithium_keypair_avx2);
135+
lc_dilithium_keypair_from_seed_avx2);
136136

137137
row = ws->rowbuf;
138138

@@ -462,8 +462,11 @@ static int lc_dilithium_sign_avx2_internal(struct lc_dilithium_sig *sig,
462462
unpoison(&ws->z.vec[i], sizeof(poly));
463463

464464
if (poly_chknorm_avx(&ws->z.vec[i],
465-
LC_DILITHIUM_GAMMA1 - LC_DILITHIUM_BETA))
465+
LC_DILITHIUM_GAMMA1 - LC_DILITHIUM_BETA)) {
466+
dilithium_print_polyvecl(&ws->z,
467+
"Siggen - z rejection");
466468
goto rej;
469+
}
467470
}
468471

469472
/* Zero hint vector in signature */
@@ -485,8 +488,11 @@ static int lc_dilithium_sign_avx2_internal(struct lc_dilithium_sig *sig,
485488
unpoison(&ws->tmpv.w0.vec[i], sizeof(poly));
486489

487490
if (poly_chknorm_avx(&ws->tmpv.w0.vec[i],
488-
LC_DILITHIUM_GAMMA2 - LC_DILITHIUM_BETA))
491+
LC_DILITHIUM_GAMMA2 - LC_DILITHIUM_BETA)) {
492+
dilithium_print_polyveck(&ws->tmpv.w0,
493+
"Siggen - r0 rejection");
489494
goto rej;
495+
}
490496

491497
/* Compute hints */
492498
poly_pointwise_montgomery_avx(&ws->tmp, &ws->c, &ws->t0.vec[i]);
@@ -496,15 +502,21 @@ static int lc_dilithium_sign_avx2_internal(struct lc_dilithium_sig *sig,
496502
/* Timecop: the hint information is not sensitive any more. */
497503
unpoison(&ws->tmp, sizeof(poly));
498504

499-
if (poly_chknorm_avx(&ws->tmp, LC_DILITHIUM_GAMMA2))
505+
if (poly_chknorm_avx(&ws->tmp, LC_DILITHIUM_GAMMA2)) {
506+
dilithium_print_poly(&ws->tmp,
507+
"Siggen - ct0 rejection");
500508
goto rej;
509+
}
501510

502511
poly_add_avx(&ws->tmpv.w0.vec[i], &ws->tmpv.w0.vec[i],
503512
&ws->tmp);
504513
n = poly_make_hint_avx(ws->hintbuf, &ws->tmpv.w0.vec[i],
505514
&ws->w1.vec[i]);
506-
if (pos + n > LC_DILITHIUM_OMEGA)
515+
if (pos + n > LC_DILITHIUM_OMEGA) {
516+
dilithium_print_polyveck(&ws->tmpv.w0,
517+
"Siggen - h rejection");
507518
goto rej;
519+
}
508520

509521
/* Store hints in signature */
510522
memcpy(&hint[pos], ws->hintbuf, n);

ml-dsa/src/dilithium_selftest.c

Lines changed: 38 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -23,62 +23,58 @@
2323
#include "selftest_rng.h"
2424
#include "small_stack_support.h"
2525

26+
/*
27+
* Use rejection test vector which will cover all rejection code paths
28+
* as generated with the dilithium_edge_case_tester.
29+
*
30+
* For FIPS 140: The test vectors cover the requirements of IG 10.3.A.
31+
*/
2632
#if LC_DILITHIUM_MODE == 2
27-
#include "dilithium_selftest_vector_44.h"
28-
#elif LC_DILITHIUM_MODE == 3
29-
#include "dilithium_selftest_vector_65.h"
30-
#else
31-
#include "dilithium_selftest_vector_87.h"
32-
#endif
33-
34-
#if LC_DILITHIUM_MODE == 2
35-
#include "../tests/dilithium_rejection_vectors_44.h"
33+
#include "../tests/dilithium_pure_rejection_vectors_44.h"
3634
#elif LC_DILITHIUM_MODE == 3
37-
#include "../tests/dilithium_rejection_vectors_65.h"
35+
#include "../tests/dilithium_pure_rejection_vectors_65.h"
3836
#elif LC_DILITHIUM_MODE == 5
39-
#include "../tests/dilithium_rejection_vectors_87.h"
37+
#include "../tests/dilithium_pure_rejection_vectors_87.h"
4038
#endif
4139

4240
static int _dilithium_keypair_tester(
4341
const char *impl,
44-
int (*_lc_dilithium_keypair)(struct lc_dilithium_pk *pk,
45-
struct lc_dilithium_sk *sk,
46-
struct lc_rng_ctx *rng_ctx))
42+
int (*_lc_dilithium_keypair_from_seed)(struct lc_dilithium_pk *pk,
43+
struct lc_dilithium_sk *sk,
44+
const uint8_t *seed,
45+
size_t seedlen))
4746
{
4847
struct workspace {
4948
struct lc_dilithium_pk pk;
5049
struct lc_dilithium_sk sk;
5150
};
5251
char str[25];
53-
uint8_t discard[64];
52+
const struct dilithium_rejection_testvector *tc =
53+
&dilithium_rejection_testvectors[0];
5454
LC_DECLARE_MEM(ws, struct workspace, sizeof(uint64_t));
55-
LC_SELFTEST_DRNG_CTX_ON_STACK(selftest_rng);
56-
57-
/* The test vector RNG state served a message gen before keygen */
58-
lc_rng_generate(selftest_rng, NULL, 0, discard, sizeof(discard));
5955

60-
_lc_dilithium_keypair(&ws->pk, &ws->sk, selftest_rng);
56+
_lc_dilithium_keypair_from_seed(&ws->pk, &ws->sk, tc->seed,
57+
sizeof(tc->seed));
6158
snprintf(str, sizeof(str), "%s PK", impl);
62-
lc_compare_selftest(ws->pk.pk, vector.pk.pk,
63-
LC_DILITHIUM_PUBLICKEYBYTES, str);
59+
lc_compare_selftest(ws->pk.pk, tc->pk, LC_DILITHIUM_PUBLICKEYBYTES,
60+
str);
6461
snprintf(str, sizeof(str), "%s SK", impl);
65-
lc_compare_selftest(ws->sk.sk, vector.sk.sk,
66-
LC_DILITHIUM_PUBLICKEYBYTES, str);
62+
lc_compare_selftest(ws->sk.sk, tc->sk, LC_DILITHIUM_PUBLICKEYBYTES,
63+
str);
6764

6865
LC_RELEASE_MEM(ws);
69-
lc_rng_zero(selftest_rng);
7066
return 0;
7167
}
7268

73-
void dilithium_keypair_tester(
74-
int *tested, const char *impl,
75-
int (*_lc_dilithium_keypair)(struct lc_dilithium_pk *pk,
76-
struct lc_dilithium_sk *sk,
77-
struct lc_rng_ctx *rng_ctx))
69+
void dilithium_keypair_tester(int *tested, const char *impl,
70+
int (*_lc_dilithium_keypair_from_seed)(
71+
struct lc_dilithium_pk *pk,
72+
struct lc_dilithium_sk *sk,
73+
const uint8_t *seed, size_t seedlen))
7874
{
7975
LC_SELFTEST_RUN(tested);
8076

81-
if (_dilithium_keypair_tester(impl, _lc_dilithium_keypair))
77+
if (_dilithium_keypair_tester(impl, _lc_dilithium_keypair_from_seed))
8278
lc_compare_selftest((uint8_t *)"test", (uint8_t *)"fail", 4,
8379
impl);
8480
}
@@ -95,27 +91,14 @@ static int _dilithium_siggen_tester(
9591
struct lc_dilithium_sig sig;
9692
};
9793
LC_DILITHIUM_CTX_ON_STACK(ctx);
94+
const struct dilithium_rejection_testvector *tc =
95+
&dilithium_rejection_testvectors[0];
9896
LC_DECLARE_MEM(ws, struct workspace, sizeof(uint64_t));
9997

100-
_lc_dilithium_sign(&ws->sig, ctx, vector.m, sizeof(vector.m),
101-
&vector.sk, NULL);
102-
lc_compare_selftest(ws->sig.sig, vector.sig.sig,
103-
LC_DILITHIUM_CRYPTO_BYTES, impl);
104-
105-
/* Rejection test case */
106-
if (fips140_mode_enabled()) {
107-
ctx->ml_dsa_internal = 1;
108-
_lc_dilithium_sign(
109-
&ws->sig, ctx, dilithium_rejection_testvectors[0].msg,
110-
sizeof(dilithium_rejection_testvectors[0].msg),
111-
(struct lc_dilithium_sk *)
112-
dilithium_rejection_testvectors[0]
113-
.sk,
114-
NULL);
115-
lc_compare_selftest(ws->sig.sig,
116-
dilithium_rejection_testvectors[0].sig,
117-
LC_DILITHIUM_CRYPTO_BYTES, impl);
118-
}
98+
_lc_dilithium_sign(&ws->sig, ctx, tc->msg, sizeof(tc->msg),
99+
(struct lc_dilithium_sk *)tc->sk, NULL);
100+
lc_compare_selftest(ws->sig.sig, tc->sig, LC_DILITHIUM_CRYPTO_BYTES,
101+
impl);
119102

120103
LC_RELEASE_MEM(ws);
121104
lc_dilithium_ctx_zero(ctx);
@@ -146,11 +129,14 @@ void dilithium_sigver_tester(
146129
{
147130
int ret, exp;
148131
LC_DILITHIUM_CTX_ON_STACK(ctx);
132+
const struct dilithium_rejection_testvector *tc =
133+
&dilithium_rejection_testvectors[0];
149134
LC_SELFTEST_RUN(tested);
150135

151136
exp = 0;
152-
ret = _lc_dilithium_verify(&vector.sig, ctx, vector.m, sizeof(vector.m),
153-
&vector.pk);
137+
ret = _lc_dilithium_verify((struct lc_dilithium_sig *)tc->sig, ctx,
138+
tc->msg, sizeof(tc->msg),
139+
(struct lc_dilithium_pk *)tc->pk);
154140
lc_dilithium_ctx_zero(ctx);
155141

156142
lc_compare_selftest((uint8_t *)&ret, (uint8_t *)&exp, sizeof(ret),

ml-dsa/src/dilithium_selftest.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ struct dilithium_testvector {
3333
struct lc_dilithium_sig sig;
3434
};
3535

36-
void dilithium_keypair_tester(
37-
int *tested, const char *impl,
38-
int (*_lc_dilithium_keypair)(struct lc_dilithium_pk *pk,
39-
struct lc_dilithium_sk *sk,
40-
struct lc_rng_ctx *rng_ctx));
36+
void dilithium_keypair_tester(int *tested, const char *impl,
37+
int (*_lc_dilithium_keypair_from_seed)(
38+
struct lc_dilithium_pk *pk,
39+
struct lc_dilithium_sk *sk,
40+
const uint8_t *seed, size_t seedlen));
4141

4242
void dilithium_siggen_tester(
4343
int *tested, const char *impl,

0 commit comments

Comments
 (0)