Skip to main content

dcrypt_algorithms/poly/
params.rs

1//! params.rs - Enhanced polynomial ring parameters with NTT support
2
3/// Basic trait defining the modulus and degree for a polynomial ring
4pub trait Modulus {
5    /// The primary modulus Q for coefficient arithmetic
6    const Q: u32;
7
8    /// The polynomial degree N (number of coefficients)
9    const N: usize;
10
11    /// Barrett reduction constant mu = floor(2^k / Q)
12    /// Set to 0 for dynamic computation
13    const BARRETT_MU: u128 = 0;
14
15    /// Barrett reduction shift amount k
16    /// Set to 0 for dynamic computation
17    const BARRETT_K: u32 = 0;
18}
19
20//───────────────────────────────────────────────────────────────────────────────
21//  What flavour of output should `inv_ntt()` return?
22//───────────────────────────────────────────────────────────────────────────────
23
24/// Post-processing mode after a Gentleman–Sande inverse NTT.
25#[derive(Copy, Clone, Debug, Eq, PartialEq)]
26pub enum PostInvNtt {
27    /// Strip the last Montgomery factor **R** → coefficients are in *standard*
28    /// domain.
29    Standard,
30    /// Keep one Montgomery factor **R** → coefficients stay in Montgomery
31    /// domain (ML-DSA).
32    Montgomery,
33}
34
35/// Extended trait for NTT-enabled moduli
36pub trait NttModulus: Modulus {
37    /// Primitive root of unity (generator)
38    const ZETA: u32;
39
40    /// Precomputed twiddle factors for forward NTT
41    /// CRITICAL: For ML-DSA, these are stored in MONTGOMERY domain (ζ·R mod q)
42    /// exactly as in the FIPS-204 reference implementation.
43    /// Do NOT convert them again - that would give ζ·R² mod q!
44    const ZETAS: &'static [u32];
45
46    /// N^-1 mod Q for final scaling in inverse NTT
47    /// This should be in Montgomery form: (N^-1 * R) mod Q
48    const N_INV: u32;
49
50    /// Montgomery parameter R = 2^32 mod Q
51    const MONT_R: u32;
52
53    /// -Q^-1 mod 2^32 for Montgomery reduction (sometimes called NEG_QINV or MONT_QINV)
54    const NEG_QINV: u32;
55
56    /// Twist factors ψ_i = ω^(bitrev(i)) in STANDARD domain (length N)
57    /// These are the N-th roots of the primitive 2N-th root of unity
58    /// Required for twisted/negacyclic NTT (ML-DSA)
59    /// NOTE: FIPS-204 reference implementation does NOT use these!
60    const PSIS: &'static [u32];
61
62    /// Inverse twist factors ψ_i^(-1) in STANDARD domain (length N)
63    /// Required for inverse twisted/negacyclic NTT (ML-DSA)
64    /// NOTE: FIPS-204 reference implementation does NOT use these!
65    const INV_PSIS: &'static [u32];
66
67    /// How the coefficients should be post-processed after the inverse NTT.
68    ///
69    /// * `Standard`   → standard coefficient domain
70    /// * `Montgomery` → ML-DSA style (`invntt_tomont`)
71    const POST_INVNTT_MODE: PostInvNtt = PostInvNtt::Standard;
72}
73
74/// Example: ML-DSA parameter sets
75#[derive(Clone, Debug)]
76pub struct MlDsa44Params;
77
78impl Modulus for MlDsa44Params {
79    const Q: u32 = 8380417; // 2^23 - 2^13 + 1
80    const N: usize = 256;
81
82    // Barrett constants for Q = 8380417
83    // k=55 (formula would give 24+32=56, but 55 passes proof and saves a cycle of shift)
84    // mu = floor(2^55 / 8380417) = 4_299_165_187
85    const BARRETT_MU: u128 = 4_299_165_187;
86    const BARRETT_K: u32 = 55;
87}
88
89// -----------------------------------------------------------------------------
90// FIPS-204 forward-NTT twiddle table  (Montgomery domain, q = 8 380 417)
91//
92// • Used by Algorithm 41 (DIF, "forward NTT")
93// • **Row-major / block-first** ordering:
94//       len = 128 →  1 twiddle   (index 0)
95//       len =  64 →  2 twiddles  (indices 1..2)
96//       len =  32 →  4 twiddles  (3..6)
97//       len =  16 →  8 twiddles  (7..14)
98//       len =   8 → 16 twiddles  (15..30)
99//       len =   4 → 32 twiddles  (31..62)
100//       len =   2 → 64 twiddles  (63..126)
101//       len =   1 →128 twiddles  (127..254)
102//   (= 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = 255 total)
103//
104// • MUST be consumed by a **block-first loop**
105//     for start in 0, 2·len, … { zeta = ZETAS[k++] ; … for j = … }
106//   — *do **not** use an offset-first (column-major) loop with this table!*
107// -----------------------------------------------------------------------------
108
109const ML_DSA_ZETAS: [u32; 255] = [
110    25847, 5771523, 7861508, 237124, 7602457, 7504169, 466468, 1826347, 2353451, 8021166, 6288512,
111    3119733, 5495562, 3111497, 2680103, 2725464, 1024112, 7300517, 3585928, 7830929, 7260833,
112    2619752, 6271868, 6262231, 4520680, 6980856, 5102745, 1757237, 8360995, 4010497, 280005,
113    2706023, 95776, 3077325, 3530437, 6718724, 4788269, 5842901, 3915439, 4519302, 5336701,
114    3574422, 5512770, 3539968, 8079950, 2348700, 7841118, 6681150, 6736599, 3505694, 4558682,
115    3507263, 6239768, 6779997, 3699596, 811944, 531354, 954230, 3881043, 3900724, 5823537, 2071892,
116    5582638, 4450022, 6851714, 4702672, 5339162, 6927966, 3475950, 2176455, 6795196, 7122806,
117    1939314, 4296819, 7380215, 5190273, 5223087, 4747489, 126922, 3412210, 7396998, 2147896,
118    2715295, 5412772, 4686924, 7969390, 5903370, 7709315, 7151892, 8357436, 7072248, 7998430,
119    1349076, 1852771, 6949987, 5037034, 264944, 508951, 3097992, 44288, 7280319, 904516, 3958618,
120    4656075, 8371839, 1653064, 5130689, 2389356, 8169440, 759969, 7063561, 189548, 4827145,
121    3159746, 6529015, 5971092, 8202977, 1315589, 1341330, 1285669, 6795489, 7567685, 6940675,
122    5361315, 4499357, 4751448, 3839961, 2091667, 3407706, 2316500, 3817976, 5037939, 2244091,
123    5933984, 4817955, 266997, 2434439, 7144689, 3513181, 4860065, 4621053, 7183191, 5187039,
124    900702, 1859098, 909542, 819034, 495491, 6767243, 8337157, 7857917, 7725090, 5257975, 2031748,
125    3207046, 4823422, 7855319, 7611795, 4784579, 342297, 286988, 5942594, 4108315, 3437287,
126    5038140, 1735879, 203044, 2842341, 2691481, 5790267, 1265009, 4055324, 1247620, 2486353,
127    1595974, 4613401, 1250494, 2635921, 4832145, 5386378, 1869119, 1903435, 7329447, 7047359,
128    1237275, 5062207, 6950192, 7929317, 1312455, 3306115, 6417775, 7100756, 1917081, 5834105,
129    7005614, 1500165, 777191, 2235880, 3406031, 7838005, 5548557, 6709241, 6533464, 5796124,
130    4656147, 594136, 4603424, 6366809, 2432395, 2454455, 8215696, 1957272, 3369112, 185531,
131    7173032, 5196991, 162844, 1616392, 3014001, 810149, 1652634, 4686184, 6581310, 5341501,
132    3523897, 3866901, 269760, 2213111, 7404533, 1717735, 472078, 7953734, 1723600, 6577327,
133    1910376, 6712985, 7276084, 8119771, 4546524, 5441381, 6144432, 7959518, 6094090, 183443,
134    7403526, 1612842, 4834730, 7826001, 3919660, 8332111, 7018208, 3937738, 1400424, 7534263,
135    1976782,
136];
137
138/// ML-DSA twist factors ψ_i = ω^(bitrev(i)) in STANDARD domain
139/// These are the 512-th roots of unity needed for the twisted NTT
140/// NOTE: The FIPS-204 reference implementation does NOT use these!
141const ML_DSA_PSIS: [u32; 256] = [
142    1, 4808194, 3765607, 3761513, 5178923, 5496691, 5234739, 5178987, 7778734, 3542485, 2682288,
143    2129892, 3764867, 7375178, 557458, 7159240, 5010068, 4317364, 2663378, 6705802, 4855975,
144    7946292, 676590, 7044481, 5152541, 1714295, 2453983, 1460718, 7737789, 4795319, 2815639,
145    2283733, 3602218, 3182878, 2740543, 4793971, 5269599, 2101410, 3704823, 1159875, 394148,
146    928749, 1095468, 4874037, 2071829, 4361428, 3241972, 2156050, 3415069, 1759347, 7562881,
147    4805951, 3756790, 6444618, 6663429, 4430364, 5483103, 3192354, 556856, 3870317, 2917338,
148    1853806, 3345963, 1858416, 3073009, 1277625, 5744944, 3852015, 4183372, 5157610, 5258977,
149    8106357, 2508980, 2028118, 1937570, 4564692, 2811291, 5396636, 7270901, 4158088, 1528066,
150    482649, 1148858, 5418153, 7814814, 169688, 2462444, 5046034, 4213992, 4892034, 1987814,
151    5183169, 1736313, 235407, 5130263, 3258457, 5801164, 1787943, 5989328, 6125690, 3482206,
152    4197502, 7080401, 6018354, 7062739, 2461387, 3035980, 621164, 3901472, 7153756, 2925816,
153    3374250, 1356448, 5604662, 2683270, 5601629, 4912752, 2312838, 7727142, 7921254, 348812,
154    8052569, 1011223, 6026202, 4561790, 6458164, 6143691, 1744507, 1753, 6444997, 5720892, 6924527,
155    2660408, 6600190, 8321269, 2772600, 1182243, 87208, 636927, 4415111, 4423672, 6084020, 5095502,
156    4663471, 8352605, 822541, 1009365, 5926272, 6400920, 1596822, 4423473, 4620952, 6695264,
157    4969849, 2678278, 4611469, 4829411, 635956, 8129971, 5925040, 4234153, 6607829, 2192938,
158    6653329, 2387513, 4768667, 8111961, 5199961, 3747250, 2296099, 1239911, 4541938, 3195676,
159    2642980, 1254190, 8368000, 2998219, 141835, 8291116, 2513018, 7025515, 613238, 7070156,
160    6161950, 7921677, 6458423, 4040196, 4908348, 2039144, 6500539, 7561656, 6201452, 6757063,
161    2105286, 6006015, 6346610, 586241, 7200804, 527981, 5637006, 6903432, 1994046, 2491325,
162    6987258, 507927, 7192532, 7655613, 6545891, 5346675, 8041997, 2647994, 3009748, 5767564,
163    4148469, 749577, 4357667, 3980599, 2569011, 6764887, 1723229, 1665318, 2028038, 1163598,
164    5011144, 3994671, 8368538, 7009900, 3020393, 3363542, 214880, 545376, 7609976, 3105558,
165    7277073, 508145, 7826699, 860144, 3430436, 140244, 6866265, 6195333, 3123762, 2358373, 6187330,
166    5365997, 6663603, 2926054, 7987710, 8077412, 3531229, 4405932, 4606686, 1900052, 7598542,
167    1054478, 7648983,
168];
169
170/// ML-DSA inverse twist factors ψ_i^(-1) in STANDARD domain
171/// NOTE: The FIPS-204 reference implementation does NOT use these!
172const ML_DSA_INV_PSIS: [u32; 256] = [
173    1, 3572223, 4618904, 4614810, 3201430, 3145678, 2883726, 3201494, 1221177, 7822959, 1005239,
174    4615550, 6250525, 5698129, 4837932, 601683, 6096684, 5564778, 3585098, 642628, 6919699,
175    5926434, 6666122, 3227876, 1335936, 7703827, 434125, 3524442, 1674615, 5717039, 4063053,
176    3370349, 6522001, 5034454, 6526611, 5463079, 4510100, 7823561, 5188063, 2897314, 3950053,
177    1716988, 1935799, 4623627, 3574466, 817536, 6621070, 4965348, 6224367, 5138445, 4018989,
178    6308588, 3506380, 7284949, 7451668, 7986269, 7220542, 4675594, 6279007, 3110818, 3586446,
179    5639874, 5197539, 4778199, 6635910, 2236726, 1922253, 3818627, 2354215, 7369194, 327848,
180    8031605, 459163, 653275, 6067579, 3467665, 2778788, 5697147, 2775755, 7023969, 5006167,
181    5454601, 1226661, 4478945, 7759253, 5344437, 5919030, 1317678, 2362063, 1300016, 4182915,
182    4898211, 2254727, 2391089, 6592474, 2579253, 5121960, 3250154, 8145010, 6644104, 3197248,
183    6392603, 3488383, 4166425, 3334383, 5917973, 8210729, 565603, 2962264, 7231559, 7897768,
184    6852351, 4222329, 1109516, 2983781, 5569126, 3815725, 6442847, 6352299, 5871437, 274060,
185    3121440, 3222807, 4197045, 4528402, 2635473, 7102792, 5307408, 731434, 7325939, 781875,
186    6480365, 3773731, 3974485, 4849188, 303005, 392707, 5454363, 1716814, 3014420, 2193087,
187    6022044, 5256655, 2185084, 1514152, 8240173, 4949981, 7520273, 553718, 7872272, 1103344,
188    5274859, 770441, 7835041, 8165537, 5016875, 5360024, 1370517, 11879, 4385746, 3369273, 7216819,
189    6352379, 6715099, 6657188, 1615530, 5811406, 4399818, 4022750, 7630840, 4231948, 2612853,
190    5370669, 5732423, 338420, 3033742, 1834526, 724804, 1187885, 7872490, 1393159, 5889092,
191    6386371, 1476985, 2743411, 7852436, 1179613, 7794176, 2033807, 2374402, 6275131, 1623354,
192    2178965, 818761, 1879878, 6341273, 3472069, 4340221, 1921994, 458740, 2218467, 1310261,
193    7767179, 1354892, 5867399, 89301, 8238582, 5382198, 12417, 7126227, 5737437, 5184741, 3838479,
194    7140506, 6084318, 4633167, 3180456, 268456, 3611750, 5992904, 1727088, 6187479, 1772588,
195    4146264, 2455377, 250446, 7744461, 3551006, 3768948, 5702139, 3410568, 1685153, 3759465,
196    3956944, 6783595, 1979497, 2454145, 7371052, 7557876, 27812, 3716946, 3284915, 2296397,
197    3956745, 3965306, 7743490, 8293209, 7198174, 5607817, 59148, 1780227, 5720009, 1455890,
198    2659525, 1935420, 8378664,
199];
200
201/// General ML-DSA parameter set used by the signature implementation
202#[derive(Clone, Debug)]
203pub struct MlDsaParams;
204
205impl Modulus for MlDsaParams {
206    const Q: u32 = 8380417; // 2^23 - 2^13 + 1
207    const N: usize = 256;
208
209    // Barrett constants for Q = 8380417
210    // k=55 (formula would give 24+32=56, but 55 passes proof and saves a cycle of shift)
211    // mu = floor(2^55 / 8380417) = 4_299_165_187
212    const BARRETT_MU: u128 = 4_299_165_187;
213    const BARRETT_K: u32 = 55;
214}
215
216impl NttModulus for MlDsaParams {
217    const ZETA: u32 = 1753; // primitive 512-th root of unity mod Q
218
219    // Use the ML-DSA zeta table (in MONTGOMERY domain)
220    // These are already in Montgomery form: ζ^(brv(k)) · R mod q
221    // Do NOT convert them again!
222    const ZETAS: &'static [u32] = &ML_DSA_ZETAS;
223
224    /// N^-1 mod Q in Montgomery form: 256^-1 · R mod Q = 16_382
225    /// This is the value used by the reference `invntt_tomont`.
226    /// Calculation: (8_347_681 * 4_193_792) mod 8_380_417 = 16_382
227    /// where 8_347_681 = 256^-1 mod 8_380_417
228    const N_INV: u32 = 16_382;
229
230    /// Montgomery R = 2^32 mod Q = 4_193_792
231    const MONT_R: u32 = 4_193_792;
232
233    /// -Q⁻¹ mod 2³² = 4_236_238_847
234    /// Q = 8380417, Q⁻¹ mod 2³² = 58728449 (0x03802001)
235    /// -Q⁻¹ mod 2³² = 2³² - 58728449 = 4236238847 (0xFC7FDFFF)
236    const NEG_QINV: u32 = 4_236_238_847;
237
238    // Add the twist factors (NOT used by FIPS-204 reference)
239    const PSIS: &'static [u32] = &ML_DSA_PSIS;
240    const INV_PSIS: &'static [u32] = &ML_DSA_INV_PSIS;
241
242    // FIXED: Tests expect standard domain output from inverse NTT
243    const POST_INVNTT_MODE: PostInvNtt = PostInvNtt::Standard;
244}
245
246/// Optional: ML-DSA parameters with Montgomery output
247/// Use this when you need coefficients to stay in Montgomery domain after inverse NTT
248#[derive(Clone, Debug)]
249pub struct MlDsaParamsMont;
250
251impl Modulus for MlDsaParamsMont {
252    const Q: u32 = 8380417;
253    const N: usize = 256;
254    const BARRETT_MU: u128 = 4_299_165_187;
255    const BARRETT_K: u32 = 55;
256}
257
258impl NttModulus for MlDsaParamsMont {
259    const ZETA: u32 = 1753;
260    const ZETAS: &'static [u32] = &ML_DSA_ZETAS;
261    const N_INV: u32 = 16_382;
262    const MONT_R: u32 = 4_193_792;
263    const NEG_QINV: u32 = 4_236_238_847;
264    const PSIS: &'static [u32] = &ML_DSA_PSIS;
265    const INV_PSIS: &'static [u32] = &ML_DSA_INV_PSIS;
266
267    // This variant keeps Montgomery domain output
268    const POST_INVNTT_MODE: PostInvNtt = PostInvNtt::Montgomery;
269}
270
271/// Helper functions for parameter validation
272/// Check if a number is prime (simplified check)
273pub fn is_prime(q: u32) -> bool {
274    if q < 2 {
275        return false;
276    }
277    if q == 2 {
278        return true;
279    }
280    if q % 2 == 0 {
281        return false;
282    }
283
284    let mut i = 3u32;
285    while i <= q / i {
286        if q % i == 0 {
287            return false;
288        }
289        i += 2;
290    }
291    true
292}
293
294/// Check if N is a power of 2
295pub fn is_power_of_two(n: usize) -> bool {
296    n > 0 && (n & (n - 1)) == 0
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn test_ml_dsa_params() {
305        assert_eq!(MlDsa44Params::Q, 8380417);
306        assert_eq!(MlDsa44Params::N, 256);
307        assert!(is_prime(MlDsa44Params::Q));
308        assert_eq!(MlDsa44Params::BARRETT_MU, 4_299_165_187);
309        assert_eq!(MlDsa44Params::BARRETT_K, 55);
310    }
311
312    #[test]
313    fn test_ml_dsa_general_params() {
314        assert_eq!(MlDsaParams::Q, 8380417);
315        assert_eq!(MlDsaParams::N, 256);
316        assert!(is_prime(MlDsaParams::Q));
317        assert!(is_power_of_two(MlDsaParams::N));
318        // FIXED: Now expects Standard mode to match test expectations
319        assert_eq!(MlDsaParams::POST_INVNTT_MODE, PostInvNtt::Standard);
320        assert_eq!(MlDsaParams::BARRETT_MU, 4_299_165_187);
321        assert_eq!(MlDsaParams::BARRETT_K, 55);
322    }
323
324    #[test]
325    fn test_ml_dsa_constant_calculations() {
326        // Verify N_INV = N^-1 * R mod Q
327        // N^-1 mod Q = 8,347,681 (256^-1 mod 8,380,417)
328        // R = 4,193,792 (2^32 mod 8,380,417)
329        // N^-1 * R mod Q = 8,347,681 * 4,193,792 mod 8,380,417 = 16,382
330        let n_inv_std = 8_347_681u64; // 256^-1 mod Q
331        let r = MlDsaParams::MONT_R as u64;
332        let q = MlDsaParams::Q as u64;
333        let expected_n_inv = (n_inv_std * r) % q;
334        assert_eq!(expected_n_inv, 16_382);
335        assert_eq!(MlDsaParams::N_INV as u64, expected_n_inv);
336
337        // Verify NEG_QINV = -Q⁻¹ mod 2³²
338        // Q⁻¹ mod 2³² = 0x03802001 = 58728449
339        // -Q⁻¹ mod 2³² = 2³² - 58728449 = 4236238847
340        let q_inv: u32 = 58728449; // Q⁻¹ mod 2³²
341        let neg_qinv = (1u64 << 32) - (q_inv as u64);
342        assert_eq!(neg_qinv, 4_236_238_847);
343        assert_eq!(MlDsaParams::NEG_QINV as u64, neg_qinv);
344    }
345
346    #[test]
347    fn test_zetas_in_montgomery_domain() {
348        // Verify that the zeta table is in Montgomery domain (column-wise order)
349        // First column-wise zeta should be 25_847
350        assert_eq!(MlDsaParams::ZETAS[0], 25_847);
351
352        // (Optional additional sanity check, matching standard ζ^128·R mod q)
353        let zeta_128_std = 4808194u64; // ζ^128 mod q in standard form
354        let mont_form = (zeta_128_std * MlDsaParams::MONT_R as u64) % MlDsaParams::Q as u64;
355        assert_eq!(mont_form as u32, 25_847);
356    }
357
358    #[test]
359    fn test_twist_factors() {
360        // Verify PSIS and INV_PSIS are inverses
361        assert_eq!(MlDsaParams::PSIS.len(), 256);
362        assert_eq!(MlDsaParams::INV_PSIS.len(), 256);
363
364        // ψ_0 = 1 always
365        assert_eq!(MlDsaParams::PSIS[0], 1);
366        assert_eq!(MlDsaParams::INV_PSIS[0], 1);
367
368        // Check that ψ_i * ψ_i^(-1) ≡ 1 (mod q)
369        let q = MlDsaParams::Q as u64;
370        for i in 0..10 {
371            let psi = MlDsaParams::PSIS[i] as u64;
372            let inv_psi = MlDsaParams::INV_PSIS[i] as u64;
373            let product = (psi * inv_psi) % q;
374            assert_eq!(product, 1, "ψ[{}] * ψ^(-1)[{}] should equal 1", i, i);
375        }
376    }
377}