summaryrefslogtreecommitdiff
path: root/src/regress/lib/libcrypto/mlkem/mlkem_iteration_tests.c
diff options
context:
space:
mode:
authortb <>2024-12-26 00:04:24 +0000
committertb <>2024-12-26 00:04:24 +0000
commit31d1b04da9af806cdb66a2b49ed6490e67479eef (patch)
treef187d226245651988501e2fb8891081ff9eea9f2 /src/regress/lib/libcrypto/mlkem/mlkem_iteration_tests.c
parentfe8b80dbfd7a71d866da84cfdab5d2ce23feac28 (diff)
downloadopenbsd-31d1b04da9af806cdb66a2b49ed6490e67479eef.tar.gz
openbsd-31d1b04da9af806cdb66a2b49ed6490e67479eef.tar.bz2
openbsd-31d1b04da9af806cdb66a2b49ed6490e67479eef.zip
Overhaul ML-KEM regress once more
Implement a file parser that drives a state machine to extract the test data from the .txt files and manages the parsed data. Comments and empty lines are ignored. The code currently assumes that instruction lines are at the start of the file (which isn't generally true) and only supports two line types for now. This is good enough for all the ML-KEM tests but should be easy enough to extend. Once all data for a test case is parsed in the expected order, a test handler is called which can retrieve the test data via a simple API and throw warnings and errors with information on the test case line number, etc. Merge the tests into three programs: one parsing the .txt files and running the corresponding test cases, a unit test and the iteration tests. Deduplicate the actual test code and let the caller pass in an object containing the API functions, private keys and arrays that need to be different between the 768 version and the 1024 version. This way we don't have two sets of half a dozen .c files differing only in 3 or 4 occurrences of 768 and 1024. All this will also make it a lot easier to hook these tests into portable.
Diffstat (limited to 'src/regress/lib/libcrypto/mlkem/mlkem_iteration_tests.c')
-rw-r--r--src/regress/lib/libcrypto/mlkem/mlkem_iteration_tests.c230
1 files changed, 230 insertions, 0 deletions
diff --git a/src/regress/lib/libcrypto/mlkem/mlkem_iteration_tests.c b/src/regress/lib/libcrypto/mlkem/mlkem_iteration_tests.c
new file mode 100644
index 0000000000..1ecda95bb9
--- /dev/null
+++ b/src/regress/lib/libcrypto/mlkem/mlkem_iteration_tests.c
@@ -0,0 +1,230 @@
1/* $OpenBSD: mlkem_iteration_tests.c,v 1.1 2024/12/26 00:04:24 tb Exp $ */
2/*
3 * Copyright (c) 2024 Google Inc.
4 * Copyright (c) 2024 Bob Beck <beck@obtuse.com>
5 * Copyright (c) 2024 Theo Buehler <tb@openbsd.org>
6 *
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14 * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
16 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
17 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <err.h>
21#include <stdint.h>
22#include <stdio.h>
23#include <stdlib.h>
24
25#include "mlkem.h"
26
27#include "mlkem_internal.h"
28#include "mlkem_tests_util.h"
29#include "sha3_internal.h"
30
31/*
32 * Based on https://c2sp.org/CCTV/ML-KEM
33 *
34 * The final value has been updated to reflect the change from Kyber to ML-KEM.
35 *
36 * The deterministic RNG is a single SHAKE-128 instance with an empty input.
37 * (The RNG stream starts with 7f9c2ba4e88f827d616045507605853e.)
38 */
39const uint8_t kExpectedSeedStart[16] = {
40 0x7f, 0x9c, 0x2b, 0xa4, 0xe8, 0x8f, 0x82, 0x7d, 0x61, 0x60, 0x45,
41 0x50, 0x76, 0x05, 0x85, 0x3e
42};
43
44/*
45 * Filippo says:
46 * ML-KEM-768: f7db260e1137a742e05fe0db9525012812b004d29040a5b606aad3d134b548d3
47 * but Boring believes this:
48 */
49const uint8_t kExpectedAdam768[32] = {
50 0xf9, 0x59, 0xd1, 0x8d, 0x3d, 0x11, 0x80, 0x12, 0x14, 0x33, 0xbf,
51 0x0e, 0x05, 0xf1, 0x1e, 0x79, 0x08, 0xcf, 0x9d, 0x03, 0xed, 0xc1,
52 0x50, 0xb2, 0xb0, 0x7c, 0xb9, 0x0b, 0xef, 0x5b, 0xc1, 0xc1
53};
54
55/*
56 * Filippo says:
57 * ML-KEM-1024: 47ac888fe61544efc0518f46094b4f8a600965fc89822acb06dc7169d24f3543
58 * but Boring believes this:
59 */
60const uint8_t kExpectedAdam1024[32] = {
61 0xe3, 0xbf, 0x82, 0xb0, 0x13, 0x30, 0x7b, 0x2e, 0x9d, 0x47, 0xdd,
62 0xe7, 0x91, 0xff, 0x6d, 0xfc, 0x82, 0xe6, 0x94, 0xe6, 0x38, 0x24,
63 0x04, 0xab, 0xdb, 0x94, 0x8b, 0x90, 0x8b, 0x75, 0xba, 0xd5
64};
65
66typedef void (*mlkem_public_from_private_fn)(void *out_public_key,
67 const void *private_key);
68
69struct iteration_ctx {
70 uint8_t *encoded_public_key;
71 size_t encoded_public_key_len;
72 uint8_t *ciphertext;
73 size_t ciphertext_len;
74 uint8_t *invalid_ciphertext;
75 size_t invalid_ciphertext_len;
76 void *priv;
77 void *pub;
78
79 mlkem_encode_private_key_fn encode_private_key;
80 mlkem_encap_external_entropy_fn encap_external_entropy;
81 mlkem_generate_key_external_entropy_fn generate_key_external_entropy;
82 mlkem_public_from_private_fn public_from_private;
83 mlkem_decap_fn decap;
84
85 const uint8_t *start;
86 size_t start_len;
87
88 const uint8_t *expected;
89 size_t expected_len;
90};
91
92static int
93MlkemIterativeTest(struct iteration_ctx *ctx)
94{
95 uint8_t shared_secret[MLKEM_SHARED_SECRET_BYTES];
96 uint8_t encap_entropy[MLKEM_ENCAP_ENTROPY];
97 uint8_t seed[MLKEM_SEED_BYTES] = {0};
98 sha3_ctx drng, results;
99 uint8_t out[32];
100 int i;
101
102 shake128_init(&drng);
103 shake128_init(&results);
104
105 shake_xof(&drng);
106 for (i = 0; i < 10000; i++) {
107 uint8_t *encoded_private_key = NULL;
108 size_t encoded_private_key_len;
109
110 /*
111 * This should draw both d and z from DRNG concatenating in
112 * seed.
113 */
114 shake_out(&drng, seed, sizeof(seed));
115 if (i == 0) {
116 if (compare_data(seed, ctx->start, ctx->start_len,
117 "seed start") != 0)
118 errx(1, "compare_data");
119 }
120
121 /* generate ek as encoded_public_key */
122 ctx->generate_key_external_entropy(ctx->encoded_public_key,
123 ctx->priv, seed);
124 ctx->public_from_private(ctx->pub, ctx->priv);
125
126 /* hash in ek */
127 shake_update(&results, ctx->encoded_public_key,
128 ctx->encoded_public_key_len);
129
130 /* marshal priv to dk as encoded_private_key */
131 if (!ctx->encode_private_key(ctx->priv, &encoded_private_key,
132 &encoded_private_key_len))
133 errx(1, "encode private key");
134
135 /* hash in dk */
136 shake_update(&results, encoded_private_key,
137 encoded_private_key_len);
138
139 free(encoded_private_key);
140
141 /* draw m as encap entropy from DRNG */
142 shake_out(&drng, encap_entropy, sizeof(encap_entropy));
143
144 /* generate ct as ciphertext, k as shared_secret */
145 ctx->encap_external_entropy(ctx->ciphertext, shared_secret,
146 ctx->pub, encap_entropy);
147
148 /* hash in ct */
149 shake_update(&results, ctx->ciphertext, ctx->ciphertext_len);
150 /* hash in k */
151 shake_update(&results, shared_secret, sizeof(shared_secret));
152
153 /* draw ct as invalid_ciphertxt from DRNG */
154 shake_out(&drng, ctx->invalid_ciphertext,
155 ctx->invalid_ciphertext_len);
156
157 /* generate k as shared secret from invalid ciphertext */
158 if (!ctx->decap(shared_secret, ctx->invalid_ciphertext,
159 ctx->invalid_ciphertext_len, ctx->priv))
160 errx(1, "decap failed");
161
162 /* hash in k */
163 shake_update(&results, shared_secret, sizeof(shared_secret));
164 }
165 shake_xof(&results);
166 shake_out(&results, out, sizeof(out));
167
168 return compare_data(ctx->expected, out, sizeof(out), "final result hash");
169}
170
171int
172main(void)
173{
174 uint8_t encoded_public_key768[MLKEM768_PUBLIC_KEY_BYTES];
175 uint8_t ciphertext768[MLKEM768_CIPHERTEXT_BYTES];
176 uint8_t invalid_ciphertext768[MLKEM768_CIPHERTEXT_BYTES];
177 struct MLKEM768_private_key priv768;
178 struct MLKEM768_public_key pub768;
179 struct iteration_ctx iteration768 = {
180 .encoded_public_key = encoded_public_key768,
181 .encoded_public_key_len = sizeof(encoded_public_key768),
182 .ciphertext = ciphertext768,
183 .ciphertext_len = sizeof(ciphertext768),
184 .invalid_ciphertext = invalid_ciphertext768,
185 .invalid_ciphertext_len = sizeof(invalid_ciphertext768),
186 .priv = &priv768,
187 .pub = &pub768,
188 .encap_external_entropy = mlkem768_encap_external_entropy,
189 .encode_private_key = mlkem768_encode_private_key,
190 .generate_key_external_entropy =
191 mlkem768_generate_key_external_entropy,
192 .public_from_private = mlkem768_public_from_private,
193 .decap = mlkem768_decap,
194 .start = kExpectedSeedStart,
195 .start_len = sizeof(kExpectedSeedStart),
196 .expected = kExpectedAdam768,
197 .expected_len = sizeof(kExpectedAdam768),
198 };
199 uint8_t encoded_public_key1024[MLKEM1024_PUBLIC_KEY_BYTES];
200 uint8_t ciphertext1024[MLKEM1024_CIPHERTEXT_BYTES];
201 uint8_t invalid_ciphertext1024[MLKEM1024_CIPHERTEXT_BYTES];
202 struct MLKEM1024_private_key priv1024;
203 struct MLKEM1024_public_key pub1024;
204 struct iteration_ctx iteration1024 = {
205 .encoded_public_key = encoded_public_key1024,
206 .encoded_public_key_len = sizeof(encoded_public_key1024),
207 .ciphertext = ciphertext1024,
208 .ciphertext_len = sizeof(ciphertext1024),
209 .invalid_ciphertext = invalid_ciphertext1024,
210 .invalid_ciphertext_len = sizeof(invalid_ciphertext1024),
211 .priv = &priv1024,
212 .pub = &pub1024,
213 .encap_external_entropy = mlkem1024_encap_external_entropy,
214 .encode_private_key = mlkem1024_encode_private_key,
215 .generate_key_external_entropy =
216 mlkem1024_generate_key_external_entropy,
217 .public_from_private = mlkem1024_public_from_private,
218 .decap = mlkem1024_decap,
219 .start = kExpectedSeedStart,
220 .start_len = sizeof(kExpectedSeedStart),
221 .expected = kExpectedAdam1024,
222 .expected_len = sizeof(kExpectedAdam1024),
223 };
224 int failed = 0;
225
226 failed |= MlkemIterativeTest(&iteration768);
227 failed |= MlkemIterativeTest(&iteration1024);
228
229 return failed;
230}