diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 35da3ed..5aea891 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,6 +48,9 @@ and in `tests/test_runner.c` and `benchmarks/bench_runner.c`. * Mathematical definition: * result = integral from a to b of f(x) dx * + * Reference: Author, Title, Venue, Year. Full citation: + * docs/algorithms/[module].md#references. + * * @param[in] param1 Description, valid range. * @param[out] result Pointer to store result. Must not be NULL. * @@ -68,11 +71,39 @@ numx_status_t numx_function(numx_real_t param1, numx_real_t *result); --- +## Citing sources + +Every function implementing a published algorithm cites that source inline in its +docstring (short form, per the template above); the full bibliographic entry lives in +the module's `docs/algorithms/[name].md#references`, not duplicated in the code. This +applies equally to a function you wrote from a textbook and to a fix contributed by +someone outside the core team (e.g. a community-submitted correctness or performance +fix): the docstring should say what it's based on either way. `docs/algorithms/*.md` +formula sections carry the same `[n]` marker inline, next to the formula, not only in +a bottom References list, so a reader doesn't have to hunt for which citation backs +which equation. + +Modules with an available external reference implementation or published dataset +should include a `validation/reference//` directory: a small generator script +(any language/tool) that produces a frozen test-vector fixture from that external +source, checked into `tests/vectors/`, with a README documenting provenance (source, +pinned commit/version, regeneration steps). This is validation tooling only; it is +never a build or runtime dependency of the library itself. See +`validation/reference/ntt/` for the reference instance of this pattern. + +--- + ## Test file structure Tests are organized into four levels: -- **L1** — Known-answer tests: verified against analytical truth or a reference implementation. +- **L1** — Known-answer tests: verified against analytical truth, or a reference + implementation. For crypto/security-critical modules, "reference implementation" + means an external, independently-citable one (published test vectors, or an + established third-party implementation), not code written for this project. An + internally-written reference (e.g. a naive O(n^2) implementation checked against an + optimized one) is still valid for general numerical modules, but is self-consistency, + not independent proof, and is not sufficient on its own for crypto modules. - **L2** — Property tests: mathematical invariants that must always hold. - **L3** — Edge cases: zero, negative, extremes, near-singular, empty input. - **L4** — Error handling: null pointers and invalid args return the correct error code. diff --git a/ROADMAP.md b/ROADMAP.md index e1e17be..0805538 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -34,7 +34,9 @@ fixes: Cholesky decomposition (contributed by Erfan Esmaeili), a branchless Barrett reduction closing the NTT module's one known constant-time gap (contributed by u/robchroma on r/C_Programming, verified exhaustively against 22,164,483 inputs before merging), function/data-section garbage -collection, and Kahan compensated summation across 8 hot loops. Remaining: +collection, Kahan compensated summation across 8 hot loops, and cross- +validating the `ntt` module bit-for-bit against PQClean's official reference +implementation (closing a self-consistency-only test gap). Remaining: multi-platform Cholesky validation, a post-Kahan benchmark refresh, and the release itself. @@ -42,13 +44,26 @@ release itself. ## Next +### [Academic paper](https://github.com/NIKX-Tech/numx/milestone/9) + +Promoted from backlog: an outside academic reviewer confirmed the algorithms +and cross-platform validation methodology are publication-grade, provided +every implementation is traceable to a citable scientific source (in the +docstring, not just the docs) and independently validated, not just +self-consistency-tested, the way the NTT/PQClean cross-validation above now +does. Covers outline, target venue, a citation audit of the crypto modules, +and an AI-assisted-development disclosure section. + ### [v1.2.0 - Post-quantum crypto and rng](https://github.com/NIKX-Tech/numx/milestone/4) CRYSTALS-Kyber KEM and CRYSTALS-Dilithium signatures on top of `numx_ntt_*`, making numx a verified, allocation-free, pure C99 post-quantum stack for embedded systems. Includes a new `rng` module (portable PRNG plus a caller-supplied entropy hook) that also closes a known ESP32 test -portability issue. Gated on an [open invitation for independent security +portability issue. Architecture design and `rng` can proceed now; the Kyber +and Dilithium implementation issues themselves are gated on the citation +audit and reference-validation groundwork above landing first. Also gated on +an [open invitation for independent security review](https://github.com/NIKX-Tech/numx/issues/68) of the `ntt` module. ### [v1.3.0 - Estimation and control](https://github.com/NIKX-Tech/numx/milestone/5) @@ -69,7 +84,6 @@ require hand-derived Jacobians. - [Fixed-point DSP (q15/q31)](https://github.com/NIKX-Tech/numx/milestone/7) - vector ops for FPU-less targets (Cortex-M0, AVR) - [RISC-V hardware validation](https://github.com/NIKX-Tech/numx/milestone/8) - closing the gap between the platform list and what's actually been run on real silicon -- [Academic paper](https://github.com/NIKX-Tech/numx/milestone/9) - documenting the algorithms and cross-platform validation methodology - [v2.0.0 - API evolution](https://github.com/NIKX-Tech/numx/milestone/10) - breaking changes, applied together with a migration guide (first candidate: a context parameter on `numx_func1d_t` callbacks) --- diff --git a/docs/algorithms/ntt.md b/docs/algorithms/ntt.md index 9888148..0537e96 100644 --- a/docs/algorithms/ntt.md +++ b/docs/algorithms/ntt.md @@ -74,10 +74,11 @@ $$x^{256} + 1 = \prod_{k=0}^{127} (x^2 - \omega^{2k+1})$$ Each factor $\mathbb{Z}_q[x]/(x^2 - c_k)$ is a degree-2 ring with modulus $c_k = \omega^{2k+1} \bmod q$. -### Forward NTT (Cooley-Tukey) +### Forward NTT (Cooley-Tukey) [2, 5] The forward NTT maps $f \in R_q$ to its 128 CRT residues in seven Cooley-Tukey -butterfly stages. Each butterfly with twiddle factor $\zeta$: +butterfly stages [5], specialized for negacyclic lattice-based cryptography +following Longa & Naehrig [2]. Each butterfly with twiddle factor $\zeta$: $$f[j] \leftarrow f[j] + \zeta \cdot f[j + \ell] \pmod{q}$$ $$f[j + \ell] \leftarrow f[j] - \zeta \cdot f[j + \ell] \pmod{q}$$ @@ -85,9 +86,9 @@ $$f[j + \ell] \leftarrow f[j] - \zeta \cdot f[j + \ell] \pmod{q}$$ where $\ell$ is the current half-group length (128, 64, 32, 16, 8, 4, 2 across stages). The output pairs $(f[2i], f[2i+1])$ represent $f \bmod (x^2 - c_i)$ for $i = 0, \ldots, 127$. -### Inverse NTT (Gentleman-Sande) +### Inverse NTT (Gentleman-Sande) [1, 3] -The inverse NTT uses Gentleman-Sande (decimation-in-frequency) butterflies, processing +The inverse NTT uses Gentleman-Sande (decimation-in-frequency) butterflies [3], processing stages in reverse order ($\ell = 2, 4, \ldots, 128$). Each inverse butterfly: $$t \leftarrow f[j]$$ @@ -95,7 +96,8 @@ $$f[j] \leftarrow t + f[j + \ell] \pmod{q}$$ $$f[j + \ell] \leftarrow \zeta^{-1} \cdot (t - f[j + \ell]) \pmod{q}$$ After all stages, every coefficient is multiplied by the normalization constant -$n^{-1} = 128^{-1} \equiv 3303 \pmod{3329}$, recovering the original polynomial. +$n^{-1} = 128^{-1} \equiv 3303 \pmod{3329}$, recovering the original polynomial, +following the Kyber specification's inverse-NTT algorithm [1]. **Key invariant:** within each inverse stage, butterfly groups must use the same twiddle indices as the corresponding forward stage (FORWARD order within each stage, not @@ -114,16 +116,17 @@ Because the NTT is a linear map over $\mathbb{Z}_q$, these operations are valid Both functions apply Barrett reduction to keep outputs in $[0, q-1]$. -### Pointwise multiplication (basemul) +### Pointwise multiplication (basemul) [1] Once both polynomials are in NTT domain, their product is computed in O(n) time via -128 independent degree-2 ring multiplications. For each pair $i$ with twiddle $c_i$: +128 independent degree-2 ring multiplications, following the Kyber specification's +basemul algorithm [1]. For each pair $i$ with twiddle $c_i$: $$(a_0 + a_1 x)(b_0 + b_1 x) \bmod (x^2 - c_i) = (a_0 b_0 + c_i a_1 b_1) + (a_0 b_1 + a_1 b_0)\, x$$ -### Barrett reduction +### Barrett reduction [4] -All modular multiplications use Barrett reduction to avoid the hardware division +All modular multiplications use Barrett reduction [4] to avoid the hardware division instruction, which is slow or absent on embedded processors: $$\text{barrett}(a) = a - \left\lfloor \frac{(a \ggg 10) \cdot v}{2^{15}} \right\rfloor \cdot q, \qquad v = 10079$$ @@ -149,6 +152,18 @@ known data-dependent branches. Contributed by u/robchroma (r/C_Programming, 2026 and exhaustively verified against `a % q` for all 22,164,483 valid inputs before adoption. +### Validation provenance + +The known-answer tests in `tests/test_ntt.c` originally checked the fast NTT +only against a naive O(n^2) reference multiplication written for this +project: self-consistency, not proof against an independent ground truth. +`numx_ntt_forward` and `numx_ntt_polymul` are now additionally cross-validated +bit-for-bit against PQClean's `ml-kem-512` reference implementation (the +FIPS 203-standardized successor to CRYSTALS-Kyber-512), an external, +independently-citable implementation not written by this project. See +`validation/reference/ntt/README.md` for the pinned commit, the Montgomery- +domain reasoning, and regeneration steps. + --- ## Complexity @@ -214,6 +229,8 @@ $f \bmod (x^2 - c_i)$ for $i = 0, \ldots, 127$. Coefficients in $[0, q-1]$. *AFIPS Fall Joint Computing Conf.*, Vol. 29, pp. 563-578, 1966. 4. Barrett, P. — "Implementing the Rivest Shamir and Adleman Public Key Encryption Algorithm on a Standard Digital Signal Processor", *CRYPTO 1986*, LNCS 263, 1987. +5. Cooley, J. W. & Tukey, J. W. — "An Algorithm for the Machine Calculation of + Complex Fourier Series", *Mathematics of Computation*, 19(90), pp. 297-301, 1965. --- diff --git a/include/numx/ntt.h b/include/numx/ntt.h index ed405b5..86a49e5 100644 --- a/include/numx/ntt.h +++ b/include/numx/ntt.h @@ -36,6 +36,9 @@ * The output is 128 pairs (f[2i], f[2i+1]) representing f mod (x^2 - c_i). * All coefficients are reduced to [0, q-1] after the transform. * + * References: Cooley & Tukey, 1965; Longa & Naehrig, CANS 2016 (NTT-specific + * speedup). Full citations: docs/algorithms/ntt.md#references. + * * @param[in,out] f Array of NUMX_NTT_N coefficients. Must not be NULL. * Each value must be in [0, NUMX_NTT_Q - 1] on entry. * @@ -52,6 +55,10 @@ numx_status_t numx_ntt_forward(numx_q15_t *f); * normalization constant 128^{-1} = 3303 (mod 3329). The output coefficients * are reduced to [0, q-1]. * + * References: Gentleman & Sande, AFIPS 1966; Avanzi et al. (Kyber spec), + * NIST PQC Round 3, 2021, for the normalization constant. Full citations: + * docs/algorithms/ntt.md#references. + * * @param[in,out] f Array of NUMX_NTT_N NTT-domain coefficients. Must not be NULL. * * @return NUMX_OK on success. @@ -72,6 +79,9 @@ numx_status_t numx_ntt_inverse(numx_q15_t *f); * Combined with numx_ntt_inverse, this implements polynomial multiplication * in Z_3329[x]/(x^256 + 1) in O(n) time after the O(n log n) NTT steps. * + * Reference: Avanzi et al. (Kyber spec), NIST PQC Round 3, 2021, degree-2 + * ring basemul formula. Full citation: docs/algorithms/ntt.md#references. + * * @param[in] a First NTT-domain array of NUMX_NTT_N coefficients. Must not be NULL. * @param[in] b Second NTT-domain array of NUMX_NTT_N coefficients. Must not be NULL. * @param[out] out Output array of NUMX_NTT_N coefficients. Must not be NULL. @@ -98,6 +108,10 @@ numx_status_t numx_ntt_pointwise_mul( * All temporaries are stack-allocated (512 bytes). The caller must supply * distinct output buffer out; a and b may be the same pointer. * + * Cross-validated bit-for-bit against PQClean's ml-kem-512 reference + * implementation (FIPS 203), not just numx's own naive reference multiply. + * See validation/reference/ntt/README.md for provenance. + * * @param[in] a First polynomial: NUMX_NTT_N coefficients in [0, q-1]. Must not be NULL. * @param[in] b Second polynomial: NUMX_NTT_N coefficients in [0, q-1]. Must not be NULL. * @param[out] out Product polynomial: NUMX_NTT_N coefficients in [0, q-1]. Must not be NULL. @@ -117,6 +131,9 @@ numx_status_t numx_ntt_polymul( * values in [0, 2*q-1] to the canonical range [0, q-1]. Useful after * external coefficient arithmetic before passing to numx_ntt_forward. * + * Reference: Barrett, CRYPTO 1986. Full citation: + * docs/algorithms/ntt.md#references. + * * @param[in,out] f Array of NUMX_NTT_N coefficients. Must not be NULL. * * @return NUMX_OK on success. diff --git a/src/ntt.c b/src/ntt.c index 68c92c6..f930d2d 100644 --- a/src/ntt.c +++ b/src/ntt.c @@ -428,6 +428,7 @@ static const int16_t priv_basemul[128] = { /* * Barrett reduction mod 3329 for inputs in [0, 2*q^2]. + * Reference: Barrett, CRYPTO 1986 (docs/algorithms/ntt.md#references [4]). * * Branchless canonicalization: uses only unsigned arithmetic and a boolean * comparison (well-defined in C99), not the reference Kyber technique's diff --git a/tests/test_ntt.c b/tests/test_ntt.c index 9723e2a..1333374 100644 --- a/tests/test_ntt.c +++ b/tests/test_ntt.c @@ -12,6 +12,7 @@ #include "unity.h" #include "numx/ntt.h" +#include "vectors/ntt_pqclean_kat.h" /* q = 3329 */ #define NTT_Q 3329 @@ -88,6 +89,33 @@ void test_ntt_forward_zero(void) TEST_ASSERT_EQUAL_INT16(0, f[i]); } +/* L1 */ +void test_ntt_forward_vs_pqclean_reference(void) +{ + /* Cross-validated against PQClean's ml-kem-512 (FIPS 203) reference + * implementation, not numx's own code -- see + * validation/reference/ntt/README.md for provenance. */ + static const numx_q15_t *const inputs[5] = { + ntt_pqclean_a_0, ntt_pqclean_a_1, ntt_pqclean_a_2, + ntt_pqclean_a_3, ntt_pqclean_a_4 + }; + static const numx_q15_t *const expected[5] = { + ntt_pqclean_ntt_a_0, ntt_pqclean_ntt_a_1, ntt_pqclean_ntt_a_2, + ntt_pqclean_ntt_a_3, ntt_pqclean_ntt_a_4 + }; + numx_q15_t f[256]; + int v, i; + + for (v = 0; v < 5; v++) + { + for (i = 0; i < 256; i++) f[i] = inputs[v][i]; + TEST_ASSERT_EQUAL(NUMX_OK, numx_ntt_forward(f)); + for (i = 0; i < 256; i++) + TEST_ASSERT_EQUAL_INT16_MESSAGE(expected[v][i], f[i], + "forward NTT != PQClean reference"); + } +} + /* L4 */ void test_ntt_forward_null(void) { @@ -335,6 +363,37 @@ void test_ntt_polymul_random_vs_ref(void) } } +/* L1 */ +void test_ntt_polymul_vs_pqclean_reference(void) +{ + /* Full negacyclic polynomial multiply cross-validated against + * PQClean's ml-kem-512 (FIPS 203) reference implementation, not + * numx's own naive poly_mul_ref() -- see + * validation/reference/ntt/README.md for provenance. */ + static const numx_q15_t *const inputs_a[5] = { + ntt_pqclean_a_0, ntt_pqclean_a_1, ntt_pqclean_a_2, + ntt_pqclean_a_3, ntt_pqclean_a_4 + }; + static const numx_q15_t *const inputs_b[5] = { + ntt_pqclean_b_0, ntt_pqclean_b_1, ntt_pqclean_b_2, + ntt_pqclean_b_3, ntt_pqclean_b_4 + }; + static const numx_q15_t *const expected[5] = { + ntt_pqclean_polymul_0, ntt_pqclean_polymul_1, ntt_pqclean_polymul_2, + ntt_pqclean_polymul_3, ntt_pqclean_polymul_4 + }; + numx_q15_t out[256]; + int v, i; + + for (v = 0; v < 5; v++) + { + TEST_ASSERT_EQUAL(NUMX_OK, numx_ntt_polymul(inputs_a[v], inputs_b[v], out)); + for (i = 0; i < 256; i++) + TEST_ASSERT_EQUAL_INT16_MESSAGE(expected[v][i], out[i], + "NTT polymul != PQClean reference"); + } +} + /* L4 */ void test_ntt_polymul_null(void) { @@ -485,6 +544,7 @@ void numx_test_ntt(void) /* Forward NTT */ RUN_TEST(test_ntt_forward_delta); RUN_TEST(test_ntt_forward_zero); + RUN_TEST(test_ntt_forward_vs_pqclean_reference); RUN_TEST(test_ntt_forward_null); /* Inverse NTT */ @@ -507,6 +567,7 @@ void numx_test_ntt(void) RUN_TEST(test_ntt_polymul_commutativity); RUN_TEST(test_ntt_polymul_known_linear); RUN_TEST(test_ntt_polymul_random_vs_ref); + RUN_TEST(test_ntt_polymul_vs_pqclean_reference); RUN_TEST(test_ntt_polymul_null); /* Reduce */ diff --git a/tests/vectors/ntt_pqclean_kat.h b/tests/vectors/ntt_pqclean_kat.h new file mode 100644 index 0000000..8d7263c --- /dev/null +++ b/tests/vectors/ntt_pqclean_kat.h @@ -0,0 +1,402 @@ +/** + * @file ntt_pqclean_kat.h + * @brief External-reference NTT test vectors. + * + * Generated by validation/reference/ntt/generate_ntt_pqclean_vectors.c + * against PQClean's ml-kem-512 "clean" reference implementation + * (FIPS 203 / formerly CRYSTALS-Kyber-512), NOT against numx's own + * code. See validation/reference/ntt/README.md for provenance, + * the pinned PQClean commit, and regeneration steps. + */ + +#ifndef NUMX_TEST_VECTORS_NTT_PQCLEAN_KAT_H +#define NUMX_TEST_VECTORS_NTT_PQCLEAN_KAT_H + +#include "numx/numx_types.h" + +/* seed = 1 */ +static const numx_q15_t ntt_pqclean_a_0[256] = { +705,3287,2114,1372,2471,1787,3069,1217,117,1349,2995,99,799,1759,963,1634, +1564,399,2607,750,1127,2173,3292,1608,681,929,3105,1789,3114,2687,3283,696, +2566,697,1987,2021,3296,559,53,2206,644,596,1685,1284,149,175,3214,2385, +69,1347,1947,448,822,2061,81,241,249,380,1535,1237,932,610,129,1046, +2237,350,2688,569,1402,2185,1803,1148,3230,2958,975,848,2936,26,367,75, +856,2658,2355,1351,1025,117,1331,1638,377,312,1915,2345,3113,2397,1893,2485, +2342,2471,318,2778,3017,1101,2401,345,2627,1493,1903,1550,1540,2450,499,849, +2779,693,2643,2345,2336,605,624,978,810,35,219,502,1843,1875,2342,319, +306,1168,94,2999,665,3130,1952,2887,1962,2858,426,2491,2257,1609,1430,1395, +1469,1611,1482,1622,1531,1178,2870,263,411,1118,1279,2108,3224,3270,2254,264, +2294,2430,1787,924,1151,96,3148,1312,151,3169,629,360,1266,1441,603,177, +1297,2232,2084,9,1033,266,2357,930,2466,1,2140,541,1888,1856,784,2531, +3237,1447,345,856,2151,2598,2118,1016,1156,2154,2762,1504,2734,2237,496,141, +82,1163,2108,2944,2147,921,1995,1252,1368,2664,2670,813,1808,600,1392,2449, +148,3314,1185,3075,953,1931,1637,2148,2752,447,1994,601,711,18,2587,2680, +1054,1611,425,2585,1838,314,730,1643,3214,1324,2231,2195,1522,2881,2586,1805, +}; + +static const numx_q15_t ntt_pqclean_b_0[256] = { +1486,351,2302,1862,2468,2753,1139,2316,479,2810,2245,3233,2540,1827,2049,353, +891,666,2423,1629,2611,968,2178,1952,1256,1067,3008,300,791,1150,521,2916, +1147,2064,820,61,502,2488,2752,1999,228,349,2977,1337,1495,1803,3042,811, +329,2617,2009,103,1901,2854,1309,2193,1803,2580,2105,1022,1329,856,1692,511, +2390,3208,380,552,1789,2559,3287,1734,1080,261,547,1813,2060,425,92,895, +1563,1684,230,46,84,2554,1092,3168,2271,691,72,3268,1498,1202,2960,459, +2652,2408,3129,839,1335,484,896,247,1127,1392,1416,515,685,1290,41,1198, +859,3238,2674,2073,2453,133,532,3138,2523,498,2988,2180,1506,450,1539,1771, +2075,1536,1086,1684,416,2462,377,2357,285,2764,2282,249,1078,2995,1450,137, +2600,2581,742,2153,597,2984,598,529,810,1970,1134,582,185,2579,2277,2635, +907,299,1421,1179,543,800,1941,3061,187,976,3289,163,2865,1843,2503,611, +3264,2214,3069,2842,1632,1279,1001,1213,2512,2653,2906,1592,296,3239,3094,2285, +855,1651,3152,2757,2839,1791,1728,120,2937,2790,1476,1675,967,2560,3090,637, +58,1227,1397,697,1676,1152,710,1863,1410,2245,1632,611,1871,3174,180,3191, +1649,453,1827,416,28,1165,1278,2800,1016,582,34,1192,162,920,125,1361, +2988,1227,1373,1568,2387,2856,2118,2646,390,104,2720,2698,860,211,795,2211, +}; + +static const numx_q15_t ntt_pqclean_ntt_a_0[256] = { +73,999,2250,866,1689,1709,1070,1230,2298,1210,2567,1875,2355,2201,3155,932, +2883,59,3262,263,3115,2113,2823,1606,1055,3077,2863,1592,1919,3200,2285,1351, +1363,3068,1108,951,2141,164,3253,1800,2132,2374,223,1071,3299,3040,1480,2633, +1462,1311,906,3250,662,1181,2086,3132,1122,2639,2773,437,1379,3191,2234,324, +170,209,640,370,143,2576,1140,3083,81,1097,25,3124,2794,514,180,1116, +1467,2192,3213,1000,1919,742,3066,1855,2239,1112,561,3084,2220,2950,2835,2550, +1949,931,1395,307,2855,3101,3180,1390,1523,3124,2053,898,2564,3210,2287,1817, +2061,2005,3119,1155,2873,987,1515,2383,1011,2214,364,564,3305,1129,1803,643, +190,2811,1512,1427,1244,46,1932,2039,666,21,2309,3195,2140,2202,2928,3295, +870,1204,2860,1547,166,372,2422,2468,1329,1865,1900,1191,2715,2214,3285,321, +866,289,942,799,2952,2270,822,2759,233,1657,34,14,2935,3227,1304,666, +2541,2270,1268,496,1984,2091,2647,3188,1881,3078,1872,1356,1272,624,2264,1603, +1517,1515,2036,2183,1161,2851,1513,1046,830,1002,980,2013,2179,2556,2440,872, +448,812,3060,2372,2270,1320,2841,2119,2719,794,39,1249,24,1299,1999,1827, +584,2046,949,2579,909,2568,1312,849,3314,2032,1927,344,2584,3050,1088,542, +29,504,2065,1441,2757,1002,998,2674,3163,2061,1809,827,1581,1157,1484,617, +}; + +static const numx_q15_t ntt_pqclean_polymul_0[256] = { +1048,852,3239,3098,3198,809,2404,1763,2891,2809,2432,1476,94,1821,2031,2562, +2214,3240,999,915,2726,2630,796,3277,1719,2074,3089,1724,336,2597,2797,2984, +2820,2765,1800,2487,730,604,2002,2127,3321,1781,1681,3136,201,1961,2966,2401, +2322,286,2994,1133,2531,2884,457,2143,3139,481,2175,462,1186,1423,2640,730, +3193,1327,1209,387,1571,402,2459,573,657,2533,1762,1960,566,1529,747,530, +3322,1597,1552,362,1460,686,283,1967,1221,1932,2354,2310,1951,1239,58,3204, +872,1477,2756,308,3314,626,1895,2281,452,734,767,550,510,830,1917,942, +1583,1257,1775,859,1474,3178,1341,770,1888,2130,1739,589,1818,30,1853,176, +1324,1257,827,724,1981,2093,2142,637,1852,1405,353,1759,186,1912,533,1286, +2277,380,1737,678,2622,3010,1376,2763,2325,348,697,319,172,1455,1799,1370, +2538,655,2295,2180,149,1361,924,279,1816,2829,1652,2275,246,1021,742,483, +1430,690,1224,160,412,2164,2580,828,1140,3044,2350,2543,935,3053,2240,2793, +2494,1160,1567,2559,2521,1545,899,2261,82,215,2818,1346,2730,1295,2263,933, +33,648,3037,39,721,2138,2369,2949,1544,662,1997,2204,2537,1609,2238,682, +2843,1890,3265,1742,1128,3160,3201,2324,444,1597,1067,1269,3113,2984,2751,3211, +2894,3192,3006,2403,2788,1398,2497,2301,1894,1692,2828,2415,1636,2552,414,1703, +}; + +/* seed = 42 */ +static const numx_q15_t ntt_pqclean_a_1[256] = { +1730,204,1286,866,1805,2442,2452,785,3204,2408,1149,2092,2317,2314,2878,2976, +371,3088,1004,2851,3182,1904,1161,1732,2761,2627,464,76,3062,1513,44,1538, +2270,3153,985,2229,1242,3036,1483,2065,1957,1556,1534,1650,1028,2332,1292,2668, +1796,2903,801,852,1735,746,212,285,2463,1503,631,280,3191,89,392,1957, +205,3074,494,68,2053,2208,82,1518,3073,514,2860,1232,746,2838,2560,1597, +2864,1598,34,2133,2286,1170,1944,929,987,78,504,411,244,2455,1859,1183, +346,1415,967,906,1930,3034,1002,3025,1514,2115,2443,335,2195,964,2821,994, +404,2779,3274,2612,333,1870,940,387,2817,2849,2405,2913,268,1597,3260,2782, +2515,1706,1500,1138,577,814,1631,1091,311,299,3187,33,1088,477,3201,2443, +2385,901,3047,772,2020,422,1817,2028,3238,2821,198,1090,1593,358,1739,63, +1136,925,1000,2946,1579,2423,134,2279,1307,2063,2244,2870,90,332,556,1223, +554,3111,1054,2236,2810,49,1228,575,1029,2727,1239,2409,2220,1558,2863,1873, +1666,108,998,1090,1899,2385,28,3153,475,1559,271,2250,2160,191,165,2223, +504,1370,267,523,801,2856,3261,726,3038,3065,3180,1868,1797,1229,3247,2850, +2081,397,2836,2152,1135,2117,925,265,1964,1314,807,1058,3270,209,331,75, +2216,1684,140,1597,55,2549,1334,1239,2962,122,520,1970,889,1415,3247,3290, +}; + +static const numx_q15_t ntt_pqclean_b_1[256] = { +2133,1064,2469,503,790,3271,1131,2848,1347,1620,2992,427,309,2494,392,1727, +896,178,45,1294,1234,1810,2958,2836,3297,1889,627,30,3061,1324,1548,1136, +751,757,3051,2443,2403,3159,975,149,2373,3240,837,2230,1800,2473,1451,3140, +184,1717,2601,3320,354,850,1022,1652,2022,806,1788,1598,832,1494,3282,2695, +2592,1559,1619,85,2760,1304,210,131,215,20,1845,1283,645,748,2940,628, +814,662,2957,523,2724,845,425,3067,3209,3100,490,2354,1061,2333,1923,581, +880,354,2409,2669,2012,2146,612,28,198,2815,486,530,1273,2580,751,845, +721,1360,132,2255,266,3206,1538,2569,1108,70,2137,178,1537,2250,1344,3160, +1225,1413,1564,2026,2215,3079,1405,2474,1154,2354,2746,2409,503,1760,2421,81, +1592,2336,1697,2435,2206,945,2696,939,744,2107,2945,455,1696,1500,3124,2399, +403,1102,1803,2432,779,1109,3106,2080,1973,1070,2824,2924,753,2773,1230,391, +2938,2844,472,2745,1784,2708,3011,2275,1272,2698,766,53,1268,315,620,3311, +3001,575,351,1364,1701,135,351,833,2440,514,1130,1525,1406,1233,2210,747, +118,2244,3134,2127,2779,1354,2265,2759,2012,1106,1639,2722,2983,1503,810,3251, +2066,3068,2362,840,2979,2315,2492,2061,2398,625,333,2568,2139,1472,711,3180, +2853,625,1609,628,1725,2670,1017,2513,3072,2993,1792,1719,2477,461,474,1280, +}; + +static const numx_q15_t ntt_pqclean_ntt_a_1[256] = { +1711,1862,1814,1667,776,845,2203,1304,2538,1924,805,858,2017,1825,2331,3108, +1378,198,943,224,1304,3205,1150,1173,589,683,676,2214,1302,1330,2816,1795, +1949,544,2164,141,2487,1342,1174,1896,1710,1140,2065,1049,1746,1296,557,913, +53,442,1364,564,2876,386,2609,1976,3322,234,1456,610,1635,1222,3133,2324, +2003,2797,1683,296,823,1664,2104,1287,789,636,458,1382,2125,2892,711,132, +472,1794,1173,364,656,1026,276,1621,266,2074,411,3327,695,1754,1430,1883, +2636,1377,2461,2695,645,915,3101,1581,2992,244,1298,1512,1709,191,1424,2304, +2652,2087,1951,2878,1093,380,2606,2130,385,80,1832,1124,2643,1399,2675,1707, +1375,2410,68,3212,422,1164,2119,3164,1047,2301,1114,1794,702,1002,2054,484, +2325,1180,3137,1991,2252,1768,1345,672,1080,2751,1135,59,794,3304,2536,2465, +332,1246,1924,1887,1279,3271,1640,274,145,514,1049,3027,2423,1492,1215,439, +2566,1966,2482,1530,1457,1370,578,2675,1807,1218,2938,1885,2226,1538,1345,1555, +76,889,643,1118,13,920,2585,686,907,219,16,3047,1067,1083,2783,416, +395,2081,2851,3091,618,1745,1173,1100,3097,93,758,1678,515,1472,3002,3259, +1448,1817,921,226,2397,1661,783,783,2752,1502,863,1001,166,3178,2549,1254, +1120,1644,155,1801,635,1211,2081,150,2010,2923,2097,3142,2144,1552,446,2385, +}; + +static const numx_q15_t ntt_pqclean_polymul_1[256] = { +2004,1364,3227,1014,33,1566,434,2537,1196,255,1819,1920,1729,1728,192,2337, +797,3236,2438,2045,1352,2198,531,1550,2139,1468,1561,646,1933,709,1044,1659, +1413,1025,2923,2302,28,69,222,3319,1925,484,727,2121,642,336,2366,2009, +2527,1085,1376,94,3328,115,224,2896,2249,2125,2883,3064,1784,3272,1658,723, +2788,1148,1623,1546,618,3306,1520,565,1364,1770,2099,736,2956,433,2258,935, +3277,1814,2888,576,2493,2048,3145,2660,1626,1217,1915,1327,2265,2782,56,1148, +1578,2646,2895,596,3141,2702,672,13,1078,2831,2382,320,1761,2225,1960,606, +2709,1374,797,2067,1893,117,2402,405,1673,2538,1684,188,2792,3026,322,2364, +449,195,306,2945,727,2952,868,1692,2026,564,758,1907,2370,3285,94,692, +2580,2346,185,1097,3278,1344,166,2458,2210,3220,2487,22,1861,1081,1778,3059, +2456,865,1551,125,249,675,1987,457,2181,230,3137,1423,1770,2448,1992,2487, +2254,1880,955,2628,1379,3117,1601,2029,2757,1961,2615,942,829,460,2577,3291, +1642,2851,2807,3072,1577,1751,2915,2835,1685,129,2866,1641,71,612,394,125, +1292,1549,2310,2740,422,1690,2742,1204,1717,1253,2878,2501,2173,1344,1055,1744, +1458,2562,2349,1192,2541,382,3082,80,2799,1917,1927,1750,3318,3101,697,1537, +1300,1617,1819,3090,248,2140,1240,2363,1482,1886,2436,2091,482,2857,2393,2974, +}; + +/* seed = 12345 */ +static const numx_q15_t ntt_pqclean_a_2[256] = { +2930,1646,3005,1729,1390,1265,1158,2684,3136,637,2468,313,2553,1532,192,1801, +362,300,2085,868,1325,2795,308,1559,2791,3225,740,2470,593,2052,3123,951, +302,1530,2313,515,3176,2585,1036,947,1070,1056,2639,2043,1065,2489,1018,2386, +3256,1227,2428,2718,2436,141,1748,1199,1141,3083,2353,2635,2494,373,1606,2183, +104,856,2042,2190,1863,2426,1099,2530,817,3072,589,2455,1367,2031,985,2648, +1755,3176,969,801,671,2828,758,890,2753,3093,3279,2882,3060,1662,219,692, +246,2334,2613,1473,2858,3191,55,480,2284,2475,312,900,312,2122,2068,3160, +2456,2781,2687,1452,2961,901,2698,366,1058,3192,1589,1721,797,1796,437,1268, +2438,2192,2588,1388,2288,2283,2893,2313,802,278,2332,1963,293,2174,302,1224, +2440,2264,92,2666,2168,839,588,2597,2286,2505,1871,2896,374,28,1124,2101, +743,270,2729,2547,1455,846,2992,676,442,2717,2973,2254,2692,293,967,342, +1858,2670,3186,756,3094,792,494,259,2056,2674,561,2176,144,2316,1755,300, +2388,3242,1316,1281,1638,3035,119,1332,76,3253,3100,2371,2468,2643,1427,122, +1955,1998,2844,2316,3130,3274,2800,2580,2751,1307,612,1294,2024,3325,547,1666, +3001,690,2781,2119,34,1454,945,587,2634,2546,731,2111,2826,2742,387,2313, +1741,399,2751,2894,1620,2577,232,1836,1555,2481,777,559,2861,1826,2986,202, +}; + +static const numx_q15_t ntt_pqclean_b_2[256] = { +1280,1723,2416,627,1721,1705,1884,2451,1788,1174,1754,2037,548,2896,3198,2794, +1943,1636,2134,1038,1107,686,538,2173,1686,1557,2374,316,3066,589,3313,686, +1699,3040,3018,676,1767,3144,1056,2830,2033,2232,1199,3264,1392,46,3236,1561, +3136,2990,1690,2248,2758,1974,479,879,1163,3071,1898,839,2039,2431,1062,2026, +1192,893,1573,1749,1145,2833,1509,2005,582,722,3319,2884,1364,1897,3229,1481, +1577,2648,2340,2331,1059,509,2474,970,87,137,1631,3061,2014,959,1790,3295, +1739,344,2572,2270,75,222,716,787,1412,2174,2862,1977,2010,1532,2564,797, +652,326,3146,2070,976,758,2637,2663,641,3055,3091,1186,1615,2413,2408,1025, +1109,1433,1989,3040,2706,478,2431,3116,595,696,1716,2282,536,1445,3322,1976, +159,1951,2041,1189,171,942,2740,1293,3138,93,267,2770,2923,2470,3144,1170, +141,1047,704,1095,1824,1792,774,2745,967,2277,1005,2761,1041,2824,914,2861, +2343,2393,3110,2412,2834,2293,2932,6,609,1280,1676,1982,1703,183,262,1215, +2121,308,1690,1153,72,2144,2911,508,788,131,2958,436,2554,869,1456,2190, +2532,2003,1298,2099,1792,1773,2362,395,860,1271,1025,2907,613,1310,2707,2762, +1442,2490,2839,838,70,3148,962,3074,1753,1329,406,892,1740,921,1688,112, +477,62,408,2209,2435,2061,3131,524,463,1297,514,1032,570,3022,1414,1543, +}; + +static const numx_q15_t ntt_pqclean_ntt_a_2[256] = { +1222,2499,1501,672,3138,174,2820,2158,1690,2750,236,3278,1290,228,3011,2894, +2607,177,150,1701,1628,1656,2990,1121,1719,1060,3141,603,1450,2976,1497,1620, +3228,1323,1803,2424,2909,2232,2364,1721,1200,2054,800,3191,2561,2190,723,2798, +2744,1434,959,3035,1847,1512,2570,1743,678,2283,2054,1337,2772,2789,3212,67, +850,1049,1350,3172,2254,910,1292,949,115,1355,1060,57,741,3140,1735,2062, +1432,3038,984,2601,2174,2638,168,1653,2536,2762,1149,2905,1160,2971,796,1445, +1786,941,1483,2573,2808,2673,3093,2419,3145,2434,818,273,1074,2453,2988,2913, +2267,2734,2914,1194,1057,2236,2333,1177,2299,437,933,4,1698,445,1773,372, +797,1798,2599,1071,2622,2512,2447,1458,3316,264,1959,1188,2892,1579,2783,187, +2349,2714,2236,1915,2868,696,2654,1213,2452,256,183,51,2725,3088,2127,534, +2546,1554,2669,2737,2566,1635,650,598,845,2909,517,1753,363,2221,2127,1207, +3318,1959,2183,1415,1521,2362,3101,483,3037,857,513,990,379,777,1824,2679, +2042,845,1088,2520,999,2127,466,1992,2168,1758,118,1845,3069,2816,1187,3296, +872,2716,2179,177,887,774,549,2514,3166,1544,2251,791,644,3137,1030,1739, +2892,2859,3081,1300,260,747,1797,3004,2324,1279,566,1884,831,3281,2752,1984, +633,632,818,2926,493,1485,2265,2572,3093,2380,1972,2642,18,1325,2107,768, +}; + +static const numx_q15_t ntt_pqclean_polymul_2[256] = { +3089,2070,1771,327,648,1341,1339,2087,352,2333,2515,3325,866,3120,2589,1021, +3204,3141,1153,1249,2656,3128,1056,2541,2185,1232,255,80,599,578,253,909, +2100,1369,2283,2145,521,451,596,2006,867,2815,2163,2404,2959,2213,400,2342, +1775,811,1,1706,2572,2440,718,1641,1378,1256,1011,497,637,1905,2530,3044, +2332,196,1323,319,2381,1098,1657,1682,618,441,3278,2284,2476,1621,2032,1720, +2775,1100,532,655,193,2455,2582,233,2841,1328,2377,1505,1276,3213,443,50, +2768,1750,1873,3043,915,1812,2329,1008,980,1384,332,1794,2744,2818,378,527, +1249,2456,1710,2509,2736,1686,2234,421,1407,2088,1693,3211,10,3120,387,340, +2216,242,473,1093,1867,239,2418,1532,143,2535,93,2615,3215,1892,711,1282, +994,2108,1652,2255,1149,870,1370,1669,1358,3264,1347,766,822,1858,1958,1746, +493,1000,3184,786,152,320,2172,2850,2334,2756,263,2567,868,2075,290,1324, +2593,342,2962,1929,172,1213,1358,2289,786,2952,2083,2447,323,1766,2080,2618, +1086,1766,2235,1476,2483,3176,2296,2292,2043,3170,1732,877,3069,2250,1440,3325, +400,233,1724,2147,426,1608,1905,1373,1795,3150,1721,2396,340,1771,1723,2221, +1733,2465,805,2915,1456,3097,1665,1075,3227,2279,414,1664,1133,1403,958,2996, +509,586,2577,1191,18,2775,1196,1609,1499,1312,1062,1335,649,2987,2489,2158, +}; + +/* seed = 999983 */ +static const numx_q15_t ntt_pqclean_a_3[256] = { +1836,887,296,3141,1307,141,1261,2185,523,2937,1712,3263,235,3201,61,1773, +1801,2866,2957,1437,368,1228,86,3161,538,1715,42,456,3122,2288,1356,398, +459,2597,517,169,852,1650,376,1968,2718,862,256,2432,1783,1276,703,2158, +973,1340,293,1685,2875,526,741,2335,1799,1494,1723,782,3016,2192,1097,65, +744,2595,3305,1428,661,1603,3104,156,2753,1885,3032,1141,2870,3022,1519,94, +2187,2045,1264,1333,3305,1846,1162,323,2849,1349,333,803,3242,133,475,2643, +1343,695,2382,135,919,3224,2760,1622,2429,1523,2430,979,2679,1626,3185,122, +631,743,2974,2191,1826,2030,1493,980,1531,2861,3113,1573,389,420,466,1797, +2872,828,1928,400,59,2020,3246,2287,2654,505,2286,2830,2228,191,3283,1418, +2091,1888,2998,317,76,920,3101,1292,723,130,1495,735,1515,633,305,1956, +2713,1055,1912,1827,690,450,430,460,1253,606,507,731,1664,503,360,606, +3084,2818,1981,1932,3246,1981,3271,1879,950,1725,226,3131,1574,364,2791,2914, +835,955,1314,2950,1586,2620,37,790,1816,802,3117,2683,2346,948,1832,1015, +1468,1659,3015,2823,729,961,3217,1797,2467,616,207,2804,2660,1840,348,1183, +1254,1518,2291,3209,2991,967,1111,1057,898,3198,2200,476,2786,2547,788,1339, +3100,2630,2898,2389,2009,3266,2777,2490,470,1020,2445,1726,68,559,1657,1333, +}; + +static const numx_q15_t ntt_pqclean_b_3[256] = { +2669,2546,2059,867,2044,1538,2566,699,712,2311,663,3193,2085,1675,1162,3025, +2485,2104,1605,828,149,3256,1036,1755,1225,189,665,3033,915,689,2318,1323, +3144,677,281,1751,60,687,1317,1542,1848,1199,2765,1107,1072,1917,2548,2885, +2857,1301,2804,597,2375,1620,1631,2483,1987,1210,1391,1145,214,2998,2848,2488, +2707,3324,313,1984,3153,3073,2160,519,3273,1990,908,1054,1165,236,93,2460, +2756,876,2623,1730,738,3293,2871,589,2340,1665,1889,2986,741,279,1400,905, +2727,67,2030,464,1016,956,2480,903,871,684,2892,2400,2288,1535,1872,632, +87,919,1613,1108,829,2271,1582,2387,245,2265,1507,729,1179,1899,314,1332, +3315,1871,2342,1054,104,2289,262,2320,2949,362,1331,3076,2141,2012,2256,1742, +754,1685,66,1823,1075,2101,2578,2533,1297,815,1052,2380,693,1862,2272,1916, +511,2598,606,2488,438,650,126,2209,792,1929,178,2907,366,3206,1156,2790, +662,265,721,2568,1373,1319,2532,154,2810,955,707,2547,283,2219,2180,1581, +1790,227,2578,347,2886,521,527,1643,2683,1408,893,283,2392,3256,801,200, +3092,3172,753,236,3197,1461,2153,1340,3074,820,1491,1061,185,891,1837,3250, +1192,2159,2522,2458,3128,750,2603,2107,1966,657,983,1416,814,2936,326,2406, +1326,2415,2383,1883,1480,1651,1290,630,2085,2920,2446,270,387,1140,678,1882, +}; + +static const numx_q15_t ntt_pqclean_ntt_a_3[256] = { +1833,1845,440,2850,1478,341,1740,3091,1387,1901,1265,2347,3012,3214,1221,2927, +2231,2639,439,914,1993,2081,1164,1332,75,7,2100,2884,2371,1312,1223,2462, +1333,1901,1267,2087,603,2364,2889,2908,2310,3052,2837,2393,2889,1375,2245,3096, +1434,3270,2728,941,2167,1205,620,683,2538,3136,2662,1417,1565,368,1293,1965, +425,2706,66,1478,1751,1952,2128,2852,1253,966,1134,457,516,3193,2790,1714, +1179,1674,2351,475,2851,1137,1176,1126,1505,856,745,3298,2361,68,2643,2555, +294,836,1195,3163,1009,1393,895,2849,1411,2559,3064,865,1303,1723,1180,1782, +350,319,999,3238,611,345,1786,2268,1924,3267,844,1056,499,1890,987,752, +3049,2774,816,1642,1035,1231,3233,461,2067,2889,1284,1850,1067,2832,1054,759, +44,165,2862,301,776,2227,2282,2825,2029,1253,1249,3009,960,2277,1416,777, +1046,2296,1470,1520,322,379,2325,1906,1501,2189,2536,1404,2833,973,3113,1649, +1848,1218,2720,1594,117,154,1900,2680,1318,245,1148,2868,1099,2641,3158,990, +1015,558,248,2754,1626,205,3081,2157,460,225,1441,2716,384,3005,536,922, +395,3062,1527,366,2913,1471,85,2191,3123,2436,310,529,3249,270,2142,432, +1465,717,2865,1396,2745,1816,1940,1688,1031,2473,793,1990,3213,1541,1480,2818, +1565,2459,1572,1734,1419,1825,2487,2207,3025,2632,540,2329,1110,23,3008,1348, +}; + +static const numx_q15_t ntt_pqclean_polymul_3[256] = { +2039,301,211,2985,1189,995,1973,1968,1128,2197,2607,674,1386,6,637,1507, +1178,1870,3021,1600,1501,2383,1379,2873,1688,561,1144,43,2564,1290,2880,1517, +302,3146,3116,1207,3188,2054,3291,1343,783,2284,909,2645,360,1180,567,1104, +2322,711,592,322,1287,2248,503,1880,371,328,2315,763,2169,1114,607,2447, +1914,1988,213,3143,2897,2361,1960,1219,379,275,351,2743,1438,1722,1456,30, +2846,2030,1190,2526,1561,1886,2287,60,1915,1882,1327,1325,2242,871,1627,2412, +147,2172,1106,851,1978,3142,1161,644,77,1808,962,1133,1083,768,2340,2310, +1960,1943,1209,2711,692,2424,397,3068,1116,586,96,3005,821,3285,2082,2951, +1128,960,555,2863,1500,517,247,175,1042,2027,1289,1336,67,492,327,2846, +1664,2880,1115,1182,925,3174,1930,761,651,2074,3130,2307,1414,45,51,1742, +3307,2011,1264,1705,492,1006,1,2100,162,2607,1811,2966,2828,826,928,677, +3143,2448,2242,1915,1373,2321,537,1206,924,3085,709,3075,2378,234,3024,984, +546,840,2007,2846,1530,3024,732,3032,1175,3139,2984,1778,2883,1306,2014,930, +1570,1521,138,144,1893,2829,2834,861,1086,855,1873,3314,575,111,1164,264, +2082,1836,2983,2804,2427,1091,2087,1666,2556,1863,1242,1036,115,998,2276,2170, +160,2789,975,1230,878,853,518,2202,2515,2782,1713,2877,835,2460,774,2729, +}; + +/* seed = 2026 */ +static const numx_q15_t ntt_pqclean_a_4[256] = { +42,495,284,842,214,840,1259,3001,334,1531,1257,2409,2468,3215,595,2468, +1623,815,631,2040,169,1846,2622,1873,1903,710,50,2603,1953,1783,404,1502, +2486,1959,2036,2426,446,2954,1662,603,2307,3102,2800,2031,3282,2157,2048,2872, +686,940,3239,1349,2046,781,940,2024,2103,466,2853,3243,553,2964,1724,2689, +822,2861,1861,854,2681,1998,2793,1419,711,144,2882,486,2538,2015,804,937, +865,3234,734,1059,33,1697,158,1249,2486,2075,3219,3066,1299,1411,1341,72, +3107,1720,2054,2190,2917,2436,2146,3201,3118,2548,2666,2847,619,1309,1059,1129, +1612,2446,911,2664,1250,1970,940,2835,2878,1177,2635,1458,1263,1485,822,2428, +553,2466,1499,2370,3116,2932,3297,1713,1866,3301,1515,3071,1531,1865,1493,2232, +1556,2065,36,859,160,1917,775,693,3163,434,1411,3227,1763,2112,170,889, +817,1124,1597,1871,594,1139,1235,2694,798,1166,2404,644,2916,1427,1091,2760, +3078,1627,1681,185,3225,844,1774,1415,791,1003,2739,1442,2435,842,423,986, +2722,483,1691,2275,2583,2956,1606,638,1216,2658,2449,2450,2087,315,2579,850, +1078,2127,2571,360,2167,891,167,2324,3257,453,2083,1982,3209,3106,1678,3191, +209,2085,2288,2424,952,1824,2822,1990,1881,717,2378,1035,603,2778,2437,1027, +866,2690,702,961,2683,3187,2218,2707,1576,776,1741,233,2333,1619,1738,1671, +}; + +static const numx_q15_t ntt_pqclean_b_4[256] = { +335,2896,484,289,922,2866,1989,1220,366,2693,1119,3001,3023,349,156,781, +1624,1817,3245,2670,3118,2438,1545,1216,2220,376,235,439,1678,413,2147,310, +2676,1964,1197,2334,2961,723,2295,797,3043,3091,2458,3211,884,453,2297,2637, +1604,1444,1267,1453,3099,2949,1177,3149,2462,2080,1675,3009,80,2660,2155,1262, +573,1149,1982,2919,2891,2341,940,2555,2403,109,2453,1950,2662,1128,1787,2815, +2917,1637,2398,3015,936,2602,2214,3182,363,2528,360,565,1351,2252,694,1106, +483,334,2911,2191,1768,232,1206,1658,466,1050,2020,457,3188,2365,3305,3193, +86,485,1493,334,1526,2220,1121,2637,2160,2053,129,2491,1586,232,1167,726, +2735,2214,1268,886,684,3165,3093,646,2639,2348,2251,711,186,696,1225,1320, +1480,242,606,2162,2414,2238,1068,225,832,2812,2398,1750,2586,1679,624,1982, +2046,1579,183,2821,2636,900,1966,1317,2531,2154,2545,1586,2391,2617,2296,1756, +599,562,356,465,451,3326,1943,3279,240,2644,1478,163,1034,825,1832,3135, +1674,606,105,2415,670,700,1248,259,1820,443,3100,2803,211,2531,363,2756, +2706,3181,1903,2360,1781,3060,3058,3205,2950,589,1220,2997,3271,1243,689,1446, +618,2931,3270,1850,1077,2159,2433,3305,1726,1506,1068,1601,1616,123,2269,1575, +2254,3283,1643,1507,1867,3310,2419,2007,2436,3,2322,3050,640,3047,2032,3163, +}; + +static const numx_q15_t ntt_pqclean_ntt_a_4[256] = { +1986,392,652,1779,652,2040,720,2443,2902,2523,2123,2152,2842,2810,1544,969, +1860,1347,1693,566,1924,280,2458,234,2833,341,2554,488,2730,3173,1466,356, +2339,3208,2828,2750,715,390,22,835,1344,2213,1756,959,2774,2247,1740,36, +348,1860,1951,2414,799,2594,785,1793,284,1039,2839,3239,2142,1506,1177,2514, +1430,3229,2935,3275,550,2207,2087,804,654,2448,1027,2910,177,3213,2189,1686, +2632,1454,472,329,81,810,509,1601,1482,2333,3236,2573,1994,1104,142,2320, +1532,156,2264,2808,2635,3006,2814,1422,2042,2402,1077,770,2526,1966,2190,1227, +608,1014,369,434,3151,2020,1003,816,1185,3217,1407,3315,3277,1915,518,868, +2019,2164,1855,3026,649,312,2491,2372,1543,117,276,636,2860,728,3244,2004, +975,2070,2579,979,3199,1522,1889,1464,1884,3235,1038,526,1267,940,1621,42, +171,3318,1810,376,163,744,1566,808,1528,2234,539,1145,992,1154,321,1557, +2355,829,3226,3058,2067,1729,2558,710,1815,93,2391,1477,3033,1814,1789,1044, +202,1047,3295,1025,626,2981,1985,2177,3251,962,1654,33,187,706,448,517, +1926,3235,518,3328,1885,1120,3010,2733,2300,1618,1204,1863,795,1835,1749,105, +1872,1123,1549,2126,3142,2309,262,1625,1050,2104,729,1218,2961,3247,2799,2944, +2891,1790,623,465,614,1612,467,104,2355,94,2658,1454,3273,1814,2133,1824, +}; + +static const numx_q15_t ntt_pqclean_polymul_4[256] = { +2417,73,2028,2676,1804,2334,2597,1944,2777,525,1281,2770,43,2236,304,1371, +473,182,1588,373,1984,788,3283,93,1095,539,1052,1459,936,432,1838,2110, +2284,1000,3002,917,2097,149,523,2668,2595,2308,663,2124,1551,2120,1644,1671, +1911,1892,338,1155,2039,2488,2810,2135,3309,2267,538,467,750,1485,1672,3263, +1166,2698,2135,374,489,30,1680,1801,3313,2476,2920,725,2881,3159,448,565, +2943,1309,2700,974,3046,1934,881,2384,675,2507,705,1557,2874,1491,1114,1232, +2829,699,3029,794,1553,857,3304,2729,2570,2623,1858,1707,2638,2157,2737,2215, +396,2920,3001,3096,1970,2113,1509,2040,1640,706,334,1169,1176,2111,2807,1550, +19,2549,1382,1954,3317,1196,1026,1196,2576,688,2061,562,1085,547,1454,1368, +2784,2404,3122,1630,259,2096,711,62,2755,588,2707,433,1570,50,248,1960, +1089,1719,194,1084,3085,2840,642,2692,623,591,872,1604,1873,2300,1013,2094, +2251,406,1481,2928,2812,1652,2611,8,2255,1478,689,1493,849,248,78,3205, +1559,2397,2501,1873,3263,768,1770,631,506,1296,2871,2131,3291,2063,2926,1039, +499,3313,1350,405,1987,1616,571,2254,388,2399,127,1046,1108,3309,2145,3167, +584,85,3250,2893,3051,114,965,425,1396,1029,2805,81,100,1068,1465,1490, +2464,2674,1132,1354,2,608,1769,607,1925,813,1713,3217,130,683,1650,1335, +}; + +#endif /* NUMX_TEST_VECTORS_NTT_PQCLEAN_KAT_H */ diff --git a/validation/reference/ntt/README.md b/validation/reference/ntt/README.md new file mode 100644 index 0000000..6fa2106 --- /dev/null +++ b/validation/reference/ntt/README.md @@ -0,0 +1,78 @@ +# NTT external-reference validation + +`tests/test_ntt.c`'s original known-answer tests checked the fast NTT-based +polynomial multiply against `poly_mul_ref()`, a naive O(n^2) reference +multiplication written for this project. That is self-consistency, not proof +against an independent ground truth: a shared systematic error (wrong root of +unity, wrong modulus constant) in both the fast and "reference" path would +pass every test. This directory closes that gap by cross-validating `numx`'s +NTT against an external, independently-citable implementation. + +## What's here + +`generate_ntt_pqclean_vectors.c` links against [PQClean](https://github.com/PQClean/PQClean)'s +`ml-kem-512` "clean" reference implementation (the FIPS 203-standardized +successor to CRYSTALS-Kyber-512) and produces `tests/vectors/ntt_pqclean_kat.h`, +a frozen fixture of forward-NTT and full negacyclic-polynomial-multiply +outputs. `tests/test_ntt.c` checks `numx_ntt_forward` and `numx_ntt_polymul` +against that fixture bit-for-bit. + +This generator is validation tooling only. It is never built as part of the +numx library or its CI, so PQClean is never a build or runtime dependency of +numx, and the zero-external-dependency guarantee is untouched. CI only reads +the committed, frozen header. + +## Provenance + +- Source: https://github.com/PQClean/PQClean +- Pinned commit: `202a8f96315f9ed219387a50f7e40d04af037ea8` (2026-05-14) +- Files used: `crypto_kem/ml-kem-512/clean/ntt.c`, `ntt.h`, `reduce.c`, + `reduce.h`, `params.h` (unmodified, used only to build the generator) + +## Why the values line up despite different internal representations + +`numx`'s `ntt.c` works entirely in "plain" domain: `priv_zetas[k] = 17^brv(k) +mod q`. PQClean's reference implementation works in Montgomery domain for +speed: its `zetas[k]` table is `17^brv(k) * R mod q` (`R = 2^16 mod q`), and +its `basemul`/`invntt_tomont` deliberately introduce and cancel factors of +`R` across the multiply-NTT-INTT chain. Working through the Montgomery +reduction identity (`fqmul(a,b) = a*b*R^-1 mod q`) shows that the sequence + +``` +poly_ntt(a); poly_ntt(b); +poly_basemul_montgomery(c, a, b); +poly_invntt_tomont(c); +poly_reduce(c); +``` + +produces exactly the plain negacyclic product `a*b mod (x^256+1, q)`, with no +leftover `R` factor. This is not a bespoke construction: it is the same +chain PQClean's own `indcpa.c` uses for its real polynomial multiplications +(see `polyvec_basemul_acc_montgomery` + `poly_invntt_tomont` + `poly_reduce` +in `indcpa.c`). `generate_ntt_pqclean_vectors.c` replicates that chain +directly against the raw `ntt.c`/`reduce.c` primitives (no hash/KEM +dependencies needed), then canonicalizes every output coefficient into +`[0, q)` for comparison against `numx`'s own `[0, q)` convention. + +Confirmed by direct comparison before adoption: `numx_ntt_forward` and +`numx_ntt_polymul` match the PQClean-derived fixture bit-for-bit across all +generated vectors. + +## Regenerating the fixture + +```sh +git clone https://github.com/PQClean/PQClean.git +cd PQClean && git checkout 202a8f96315f9ed219387a50f7e40d04af037ea8 +PQD=crypto_kem/ml-kem-512/clean + +cc -std=c99 -O2 -Wall -Wextra \ + -I "$PQD" -I /path/to/numx/include \ + /path/to/numx/validation/reference/ntt/generate_ntt_pqclean_vectors.c \ + "$PQD/ntt.c" "$PQD/reduce.c" \ + -o generate_ntt_pqclean_vectors + +./generate_ntt_pqclean_vectors > /path/to/numx/tests/vectors/ntt_pqclean_kat.h +``` + +Only needed if the seeds or vector count in `generate_ntt_pqclean_vectors.c` +change, or to independently re-verify the committed fixture. diff --git a/validation/reference/ntt/generate_ntt_pqclean_vectors.c b/validation/reference/ntt/generate_ntt_pqclean_vectors.c new file mode 100644 index 0000000..5cdaa16 --- /dev/null +++ b/validation/reference/ntt/generate_ntt_pqclean_vectors.c @@ -0,0 +1,140 @@ +/* + * Generates tests/vectors/ntt_pqclean_kat.h from PQClean's ml-kem-512 + * reference implementation (the standardized FIPS 203 successor to + * CRYSTALS-Kyber, formerly crypto_kem/kyber512). + * + * This file is NOT part of the numx library. It links against PQClean's + * ntt.c and reduce.c to produce fixture vectors that numx's own tests + * check against, closing the external-reference-validation gap described + * in validation/reference/ntt/README.md. It is never built as part of + * the numx library or its CI, so it does not affect numx's zero + * external-dependency guarantee. + * + * Usage: see README.md in this directory for the exact clone/build/run + * steps and the pinned PQClean commit. + * + * The chain below (ntt -> poly_reduce -> basemul_montgomery -> + * invntt_tomont -> poly_reduce) mirrors PQClean's own indcpa.c usage + * (see indcpa.c: polyvec_basemul_acc_montgomery + poly_invntt_tomont + + * poly_reduce), which is the reference implementation's real polynomial- + * multiplication path, not a bespoke construction. + */ + +#include +#include +#include + +#include "ntt.h" +#include "params.h" +#include "reduce.h" + +/* Matches numx's tests/test_ntt.c priv_lcg exactly, so the same seeds + * produce the same input polynomials on both sides. */ +static uint32_t priv_lcg(uint32_t *s) +{ + *s = *s * 1664525u + 1013904223u; + return *s; +} + +static void rand_poly(int16_t *f, uint32_t *seed) +{ + int i; + for (i = 0; i < KYBER_N; i++) + f[i] = (int16_t)(priv_lcg(seed) % (uint32_t)KYBER_Q); +} + +/* Canonicalize a centered-representative coefficient into [0, q). */ +static int16_t canon(int16_t v) +{ + v = PQCLEAN_MLKEM512_CLEAN_barrett_reduce(v); + if (v < 0) v += KYBER_Q; + return v; +} + +static void print_array(const char *name, const int16_t *v) +{ + int i; + printf("static const numx_q15_t %s[256] = {\n", name); + for (i = 0; i < 256; i++) + { + printf("%d,", v[i]); + if ((i + 1) % 16 == 0) printf("\n"); + } + printf("};\n\n"); +} + +static void gen_vector(uint32_t seed, int idx) +{ + int16_t a[256], b[256]; + int16_t ntt_a[256]; + int16_t prod[256]; + int i; + char name[64]; + uint32_t working_seed = seed; + + rand_poly(a, &working_seed); + rand_poly(b, &working_seed); + + /* Forward NTT of a, canonicalized. */ + memcpy(ntt_a, a, sizeof(a)); + PQCLEAN_MLKEM512_CLEAN_ntt(ntt_a); + for (i = 0; i < 256; i++) ntt_a[i] = canon(ntt_a[i]); + + /* Full negacyclic polynomial multiply a*b, canonicalized. */ + { + int16_t aa[256], bb[256]; + memcpy(aa, a, sizeof(a)); + memcpy(bb, b, sizeof(b)); + + PQCLEAN_MLKEM512_CLEAN_ntt(aa); + for (i = 0; i < 256; i++) aa[i] = canon(aa[i]); + PQCLEAN_MLKEM512_CLEAN_ntt(bb); + for (i = 0; i < 256; i++) bb[i] = canon(bb[i]); + + for (i = 0; i < KYBER_N / 4; i++) + { + PQCLEAN_MLKEM512_CLEAN_basemul(&prod[4 * i], &aa[4 * i], &bb[4 * i], + PQCLEAN_MLKEM512_CLEAN_zetas[64 + i]); + PQCLEAN_MLKEM512_CLEAN_basemul(&prod[4 * i + 2], &aa[4 * i + 2], &bb[4 * i + 2], + (int16_t)(-PQCLEAN_MLKEM512_CLEAN_zetas[64 + i])); + } + PQCLEAN_MLKEM512_CLEAN_invntt(prod); + for (i = 0; i < 256; i++) prod[i] = canon(prod[i]); + } + + printf("/* seed = %u */\n", seed); + snprintf(name, sizeof(name), "ntt_pqclean_a_%d", idx); + print_array(name, a); + snprintf(name, sizeof(name), "ntt_pqclean_b_%d", idx); + print_array(name, b); + snprintf(name, sizeof(name), "ntt_pqclean_ntt_a_%d", idx); + print_array(name, ntt_a); + snprintf(name, sizeof(name), "ntt_pqclean_polymul_%d", idx); + print_array(name, prod); +} + +int main(void) +{ + static const uint32_t seeds[] = {1u, 42u, 12345u, 999983u, 2026u}; + int i; + + printf("/**\n"); + printf(" * @file ntt_pqclean_kat.h\n"); + printf(" * @brief External-reference NTT test vectors.\n"); + printf(" *\n"); + printf(" * Generated by validation/reference/ntt/generate_ntt_pqclean_vectors.c\n"); + printf(" * against PQClean's ml-kem-512 \"clean\" reference implementation\n"); + printf(" * (FIPS 203 / formerly CRYSTALS-Kyber-512), NOT against numx's own\n"); + printf(" * code. See validation/reference/ntt/README.md for provenance,\n"); + printf(" * the pinned PQClean commit, and regeneration steps.\n"); + printf(" */\n\n"); + printf("#ifndef NUMX_TEST_VECTORS_NTT_PQCLEAN_KAT_H\n"); + printf("#define NUMX_TEST_VECTORS_NTT_PQCLEAN_KAT_H\n\n"); + printf("#include \"numx/numx_types.h\"\n\n"); + + for (i = 0; i < (int)(sizeof(seeds) / sizeof(seeds[0])); i++) + gen_vector(seeds[i], i); + + printf("#endif /* NUMX_TEST_VECTORS_NTT_PQCLEAN_KAT_H */\n"); + return 0; +}