Skip to content

Commit abfdabf

Browse files
committed
Make tests and examples compatible with reduced-API configurations
The previous tests and examples assumed all APIs (keygen, encaps, decaps) were always available: encaps tests generated a keypair as setup, decaps tests generated a keypair and ciphertext as setup, and the alloc test required keygen for every test function. This made them incompatible with reduced-API configurations where some APIs are disabled via MLK_CONFIG_NO_KEYPAIR_API, MLK_CONFIG_NO_ENCAPS_API, or MLK_CONFIG_NO_DECAPS_API. This commit introduces auto-generated test vectors and refactors all tests and examples so that each operation (keygen, encaps, decaps) can be tested independently using pre-computed test vectors. scripts/notrandombytes: - Add Python implementation of the SURF-based deterministic test PRNG, matching the C version in test/notrandombytes/ - Used by autogen --test-vectors to generate reproducible randomness for test vector generation Test vector generation (scripts/autogen --test-vectors): - Add --test-vectors flag to scripts/autogen that invokes the ACVP binaries with randomness from scripts/notrandombytes to generate test/test_vectors/expected_test_vectors.h and the multilevel variant - Generate pk, sk, ct, ss vectors for all three parameter sets (512, 768, 1024) - Include z, d, m byte-arrays in the header so consumers don't need to hardcode them - Array dimensions are explicit in the generated header Test changes (test_mlkem.c): - The existing tests (test_keys, test_invalid_pk, etc.) require all three APIs and are left unchanged, guarded by !MLK_CONFIG_NO_KEYPAIR_API && !MLK_CONFIG_NO_ENCAPS_API && !MLK_CONFIG_NO_DECAPS_API - Add test_kem_expected() as a new minimal test that works in reduced-API configurations: each block (keygen, encaps, decaps) is independently guarded and uses test vectors directly, so e.g. the decaps block can run without keygen by using test_vector_sk test_alloc.c and test_rng_fail.c: - Both files exercise the same 7 API entry points (keypair_derand, keypair, enc_derand, enc, check_pk, dec, check_sk), each independently guarded by the minimal required API - Encaps tests use test_vector_pk directly (no keygen dependency) - Decaps tests use test_vector_sk/ct directly (no keygen or encaps dependency) - main() uses r |= pattern for error accumulation test_stack.c: - Guard each test function (keygen/encaps/decaps stack measurement) by the matching API config; unavailable modes print SKIPPED gen_KAT/wycheproof/acvp: - KAT and Wycheproof stub out to a SKIPPED main() when any API is disabled (they inherently need all three) - ACVP guards each mode (keyGen / encapDecap encapsulation / encapDecap decapsulation / encapsulationKeyCheck / decapsulationKeyCheck) by the matching API config, so it builds and works in any subset Example refactoring: - Hoist test logic into static example_xxx() functions with #if/#else/#endif guards and SKIPPED stubs for disabled APIs - main() is a flat sequence of r |= example_xxx() calls - example_end_to_end runs the full keygen->encaps->decaps flow only when all three APIs are enabled; example_expected_vectors runs independent keygen/encaps/decaps KAT checks for whichever APIs are enabled - Multilevel examples use per-level static functions (test_expected_512, test_expected_768, test_expected_1024) with the same per-API guards - basic_deterministic uses test_vector_z/d/m from the header instead of hardcoded byte arrays - Add expected_test_vectors.h symlink (single- or multi-level) in each example directory pointing to test/test_vectors/ CI (config-variations): - Add keygen-only, encaps-only, decaps-only, keygen-encaps, keygen-decaps, encaps-decaps test configurations, covering all 6 combinations of 1 or 2 enabled APIs from {keygen, encaps, decaps} Signed-off-by: Hanno Becker <beckphan@amazon.co.uk>
1 parent 68cfb89 commit abfdabf

78 files changed

Lines changed: 5758 additions & 1398 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/config-variations/action.yml

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ inputs:
1111
description: 'List of tests to run (space-separated IDs) or "all" for all tests. Available IDs: pct-enabled,
1212
pct-enabled-broken, custom-alloc-heap, custom-zeroize, native-cap-ON, native-cap-OFF, native-cap-ID_AA64PFR1_EL1,
1313
native-cap-CPUID_AVX2, no-asm, serial-fips202, custom-randombytes, custom-memcpy, custom-memset, custom-stdlib,
14-
nblocks-1, nblocks-2, nblocks-4'
14+
nblocks-1, nblocks-2, nblocks-4, keygen-only, encaps-only, decaps-only, keygen-encaps, keygen-decaps, encaps-decaps'
1515
required: false
1616
default: 'all'
1717
opt:
@@ -276,3 +276,93 @@ runs:
276276
examples: false # Some examples use a custom config themselves
277277
alloc: false # Requires custom config
278278
rng_fail: true
279+
- name: "Keygen-only API"
280+
if: ${{ inputs.tests == 'all' || contains(inputs.tests, 'keygen-only') }}
281+
uses: ./.github/actions/multi-functest
282+
with:
283+
gh_token: ${{ inputs.gh_token }}
284+
compile_mode: native
285+
cflags: "-DMLK_CONFIG_NO_ENCAPS_API -DMLK_CONFIG_NO_DECAPS_API -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
286+
ldflags: "-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
287+
func: true
288+
kat: true
289+
acvp: true
290+
stack: true
291+
rng_fail: true
292+
opt: ${{ inputs.opt }}
293+
examples: true
294+
- name: "Encaps-only API"
295+
if: ${{ inputs.tests == 'all' || contains(inputs.tests, 'encaps-only') }}
296+
uses: ./.github/actions/multi-functest
297+
with:
298+
gh_token: ${{ inputs.gh_token }}
299+
compile_mode: native
300+
cflags: "-DMLK_CONFIG_NO_KEYPAIR_API -DMLK_CONFIG_NO_DECAPS_API -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
301+
ldflags: "-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
302+
func: true
303+
kat: true
304+
acvp: true
305+
stack: true
306+
rng_fail: true
307+
opt: ${{ inputs.opt }}
308+
examples: true
309+
- name: "Decaps-only API"
310+
if: ${{ inputs.tests == 'all' || contains(inputs.tests, 'decaps-only') }}
311+
uses: ./.github/actions/multi-functest
312+
with:
313+
gh_token: ${{ inputs.gh_token }}
314+
compile_mode: native
315+
cflags: "-DMLK_CONFIG_NO_KEYPAIR_API -DMLK_CONFIG_NO_ENCAPS_API -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
316+
ldflags: "-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
317+
func: true
318+
kat: true
319+
acvp: true
320+
stack: true
321+
rng_fail: true
322+
opt: ${{ inputs.opt }}
323+
examples: true
324+
- name: "Keygen+Encaps API (no decaps)"
325+
if: ${{ inputs.tests == 'all' || contains(inputs.tests, 'keygen-encaps') }}
326+
uses: ./.github/actions/multi-functest
327+
with:
328+
gh_token: ${{ inputs.gh_token }}
329+
compile_mode: native
330+
cflags: "-DMLK_CONFIG_NO_DECAPS_API -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
331+
ldflags: "-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
332+
func: true
333+
kat: true
334+
acvp: true
335+
stack: true
336+
rng_fail: true
337+
opt: ${{ inputs.opt }}
338+
examples: true
339+
- name: "Keygen+Decaps API (no encaps)"
340+
if: ${{ inputs.tests == 'all' || contains(inputs.tests, 'keygen-decaps') }}
341+
uses: ./.github/actions/multi-functest
342+
with:
343+
gh_token: ${{ inputs.gh_token }}
344+
compile_mode: native
345+
cflags: "-DMLK_CONFIG_NO_ENCAPS_API -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
346+
ldflags: "-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
347+
func: true
348+
kat: true
349+
acvp: true
350+
stack: true
351+
rng_fail: true
352+
opt: ${{ inputs.opt }}
353+
examples: true
354+
- name: "Encaps+Decaps API (no keygen)"
355+
if: ${{ inputs.tests == 'all' || contains(inputs.tests, 'encaps-decaps') }}
356+
uses: ./.github/actions/multi-functest
357+
with:
358+
gh_token: ${{ inputs.gh_token }}
359+
compile_mode: native
360+
cflags: "-DMLK_CONFIG_NO_KEYPAIR_API -fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
361+
ldflags: "-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all"
362+
func: true
363+
kat: true
364+
acvp: true
365+
stack: true
366+
rng_fail: true
367+
opt: ${{ inputs.opt }}
368+
examples: true

META.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
# Checks the KAT output of a gen_KAT binary against META.yml.
66
#
77
# Reads the KAT bytes from stdin, hashes them with SHA-256, and compares
8-
# against the kat-sha256 field for the given scheme in META.yml.
8+
# against the kat-sha256 field for the given scheme in META.yml. If stdin is
9+
# "SKIPPED" (emitted by gen_KAT for reduced-API builds), the check is reported
10+
# as skipped.
911
#
1012
# To run manually, pipe a gen_KAT binary into it, e.g.:
1113
#
@@ -53,9 +55,16 @@ def main():
5355
err(f"META.yml: no kat-sha256 entry for {scheme_name}")
5456
sys.exit(1)
5557

56-
# Hash the raw bytes piped in from gen_KAT on stdin. Read in binary mode so
58+
# Read the raw bytes piped in from gen_KAT on stdin. Read in binary mode so
5759
# that no newline translation occurs on Windows.
58-
computed = hashlib.sha256(sys.stdin.buffer.read()).hexdigest()
60+
data = sys.stdin.buffer.read()
61+
62+
# Reduced-API builds emit "SKIPPED" instead of KAT bytes; report as skipped.
63+
if data.startswith(b"SKIPPED"):
64+
info(f"META.yml {scheme_name} kat-sha256: SKIPPED")
65+
sys.exit(0)
66+
67+
computed = hashlib.sha256(data).hexdigest()
5968

6069
if computed == ref:
6170
info(f"META.yml {scheme_name} kat-sha256: OK")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../test/test_vectors/expected_test_vectors.h

examples/basic/main.c

Lines changed: 64 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,9 @@
66
#include <stdio.h>
77
#include <string.h>
88

9-
/* Import public mlkem-native API
10-
*
11-
* This requires specifying the parameter set and namespace prefix
12-
* used for the build.
13-
*/
9+
/* Import public mlkem-native API */
10+
#include "expected_test_vectors.h"
1411
#include "mlkem_native/mlkem_native.h"
15-
1612
#include "test_only_rng/notrandombytes.h"
1713

1814
#define CHECK(x) \
@@ -27,80 +23,81 @@
2723
} \
2824
} while (0)
2925

30-
int main(void)
26+
#if !defined(MLK_CONFIG_NO_KEYPAIR_API) && \
27+
!defined(MLK_CONFIG_NO_RANDOMIZED_API)
28+
static int example_keygen(void)
3129
{
3230
uint8_t pk[CRYPTO_PUBLICKEYBYTES];
3331
uint8_t sk[CRYPTO_SECRETKEYBYTES];
34-
uint8_t ct[CRYPTO_CIPHERTEXTBYTES];
35-
uint8_t key_a[CRYPTO_BYTES];
36-
uint8_t key_b[CRYPTO_BYTES];
37-
38-
/* The PCT modifies the PRNG state, so the KAT tests don't work.
39-
* We run KAT tests only for disabled PCT. */
40-
#if !defined(MLK_CONFIG_KEYGEN_PCT)
41-
#if MLK_CONFIG_PARAMETER_SET == 512
42-
const uint8_t expected_key[] = {
43-
0x77, 0x6c, 0x74, 0xdf, 0x30, 0x1f, 0x8d, 0x82, 0x52, 0x5e, 0x8e,
44-
0xbb, 0xb4, 0x00, 0x95, 0xcd, 0x2e, 0x92, 0xdf, 0x6d, 0xc9, 0x33,
45-
0xe7, 0x86, 0x62, 0x59, 0xf5, 0x31, 0xc7, 0x35, 0x0a, 0xd5};
46-
#elif MLK_CONFIG_PARAMETER_SET == 768
47-
const uint8_t expected_key[] = {
48-
0xe9, 0x13, 0x77, 0x84, 0x0e, 0x6b, 0x66, 0x94, 0xea, 0xa9, 0xf0,
49-
0x1c, 0x97, 0xff, 0x68, 0x87, 0x4e, 0x8b, 0x0c, 0x52, 0x0b, 0x00,
50-
0xc2, 0xcd, 0xe3, 0x7c, 0x4f, 0xc2, 0x39, 0x62, 0x6e, 0x70};
51-
#elif MLK_CONFIG_PARAMETER_SET == 1024
52-
const uint8_t expected_key[] = {
53-
0x5d, 0x9e, 0x23, 0x5f, 0xcc, 0xb2, 0xb3, 0x49, 0x9a, 0x5f, 0x49,
54-
0x0a, 0x56, 0xe3, 0xf0, 0xd3, 0xfd, 0x9b, 0x58, 0xbd, 0xa2, 0x8b,
55-
0x69, 0x0f, 0x91, 0xb5, 0x7b, 0x88, 0xa5, 0xa8, 0x0b, 0x90};
56-
#endif /* MLK_CONFIG_PARAMETER_SET == 1024 */
57-
#endif /* !MLK_CONFIG_KEYGEN_PCT */
5832

59-
/* WARNING: Test-only
60-
* Normally, you would want to seed a PRNG with trustworthy entropy here. */
61-
randombytes_reset();
62-
63-
printf("Generating keypair ... ");
64-
65-
/* Alice generates a public key */
33+
printf("Generating keypair... ");
6634
CHECK(crypto_kem_keypair(pk, sk) == 0);
67-
35+
CHECK(memcmp(pk, test_vector_pk, CRYPTO_PUBLICKEYBYTES) == 0);
36+
CHECK(memcmp(sk, test_vector_sk, CRYPTO_SECRETKEYBYTES) == 0);
6837
printf("DONE\n");
69-
printf("Encaps... ");
38+
return 0;
39+
}
40+
#else /* !MLK_CONFIG_NO_KEYPAIR_API && !MLK_CONFIG_NO_RANDOMIZED_API */
41+
static int example_keygen(void)
42+
{
43+
printf("Generating keypair... SKIPPED (keygen API disabled)\n");
44+
return 0;
45+
}
46+
#endif /* !(!MLK_CONFIG_NO_KEYPAIR_API && !MLK_CONFIG_NO_RANDOMIZED_API) */
7047

71-
/* Bob derives a secret key and creates a response */
72-
CHECK(crypto_kem_enc(ct, key_b, pk) == 0);
48+
#if !defined(MLK_CONFIG_NO_ENCAPS_API) && !defined(MLK_CONFIG_NO_RANDOMIZED_API)
49+
static int example_encaps(void)
50+
{
51+
uint8_t ct[CRYPTO_CIPHERTEXTBYTES];
52+
uint8_t ss[CRYPTO_BYTES];
7353

54+
printf("Encaps... ");
55+
CHECK(crypto_kem_enc(ct, ss, test_vector_pk) == 0);
56+
CHECK(memcmp(ct, test_vector_ct, CRYPTO_CIPHERTEXTBYTES) == 0);
57+
CHECK(memcmp(ss, test_vector_ss, CRYPTO_BYTES) == 0);
7458
printf("DONE\n");
75-
printf("Decaps... ");
59+
return 0;
60+
}
61+
#else /* !MLK_CONFIG_NO_ENCAPS_API && !MLK_CONFIG_NO_RANDOMIZED_API */
62+
static int example_encaps(void)
63+
{
64+
printf("Encaps... SKIPPED (encaps API disabled)\n");
65+
return 0;
66+
}
67+
#endif /* !(!MLK_CONFIG_NO_ENCAPS_API && !MLK_CONFIG_NO_RANDOMIZED_API) */
7668

77-
/* Alice uses Bobs response to get her shared key */
78-
CHECK(crypto_kem_dec(key_a, ct, sk) == 0);
69+
#if !defined(MLK_CONFIG_NO_DECAPS_API)
70+
static int example_decaps(void)
71+
{
72+
uint8_t ss[CRYPTO_BYTES];
7973

74+
printf("Decaps... ");
75+
CHECK(crypto_kem_dec(ss, test_vector_ct, test_vector_sk) == 0);
76+
CHECK(memcmp(ss, test_vector_ss, CRYPTO_BYTES) == 0);
8077
printf("DONE\n");
81-
printf("Compare... ");
82-
83-
CHECK(memcmp(key_a, key_b, CRYPTO_BYTES) == 0);
78+
return 0;
79+
}
80+
#else /* !MLK_CONFIG_NO_DECAPS_API */
81+
static int example_decaps(void)
82+
{
83+
printf("Decaps... SKIPPED (decaps API disabled)\n");
84+
return 0;
85+
}
86+
#endif /* MLK_CONFIG_NO_DECAPS_API */
8487

85-
printf("Shared secret: ");
86-
{
87-
size_t i;
88-
for (i = 0; i < sizeof(key_a); i++)
89-
{
90-
printf("%02x", key_a[i]);
91-
}
92-
}
93-
printf("\n");
88+
int main(void)
89+
{
90+
int r = 0;
9491

95-
#if !defined(MLK_CONFIG_KEYGEN_PCT)
96-
/* Check against hardcoded result to make sure that
97-
* we integrated custom FIPS202 correctly */
98-
CHECK(memcmp(key_a, expected_key, CRYPTO_BYTES) == 0);
99-
#else
100-
printf(
101-
"[WARNING] Skipping KAT test since PCT is enabled and modifies PRNG\n");
102-
#endif
92+
/* WARNING: Test-only
93+
* Normally, you would seed a PRNG _once_ with trustworthy entropy and not
94+
* reseed it afterwards. Here, we reseed before each API call to make each
95+
* test independent and reproducible even when some API is disabled. */
96+
randombytes_reset();
97+
r |= example_keygen();
98+
randombytes_reset();
99+
r |= example_encaps();
100+
r |= example_decaps();
103101

104-
printf("OK\n");
105-
return 0;
102+
return r;
106103
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../test/test_vectors/expected_test_vectors.h

0 commit comments

Comments
 (0)