Skip to main content

dilithium/
params.rs

1//! Dilithium parameter sets (FIPS 204 / ML-DSA).
2//!
3//! Three security levels are supported:
4//! - ML-DSA-44 (Dilithium2, NIST Level 2)
5//! - ML-DSA-65 (Dilithium3, NIST Level 3)
6//! - ML-DSA-87 (Dilithium5, NIST Level 5)
7
8/// Polynomial ring degree.
9pub const N: usize = 256;
10
11/// Modulus.
12pub const Q: i32 = 8380417;
13
14/// Montgomery constant: Q^{-1} mod 2^32.
15pub const QINV: i64 = 58728449; // Q * QINV ≡ 1 (mod 2^32)
16
17/// Number of dropped bits from t.
18pub const D: u32 = 13;
19
20/// Root of unity (used in NTT).
21pub const ROOT_OF_UNITY: i32 = 1753;
22
23/// Seed length in bytes.
24pub const SEEDBYTES: usize = 32;
25
26/// CRH output length in bytes.
27pub const CRHBYTES: usize = 64;
28
29/// tr length in bytes.
30pub const TRBYTES: usize = 64;
31
32/// Random bytes for hedged signing.
33pub const RNDBYTES: usize = 32;
34
35/// Maximum K across all modes (Mode5: K=8).
36pub const K_MAX: usize = 8;
37
38/// Maximum L across all modes (Mode5: L=7).
39pub const L_MAX: usize = 7;
40
41/// Packed size for t1 polynomial (10 bits per coefficient).
42pub const POLYT1_PACKEDBYTES: usize = 320;
43
44/// Packed size for t0 polynomial (13 bits per coefficient).
45pub const POLYT0_PACKEDBYTES: usize = 416;
46
47/// ML-DSA / Dilithium security levels (FIPS 204).
48///
49/// The FIPS 204 standard names are ML-DSA-44, ML-DSA-65, and ML-DSA-87.
50/// The Dilithium2/3/5 names are provided for compatibility with the
51/// original CRYSTALS-Dilithium submission.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54pub enum DilithiumMode {
55    /// ML-DSA-44 (NIST Level 2): K=4, L=4
56    Dilithium2,
57    /// ML-DSA-65 (NIST Level 3): K=6, L=5
58    Dilithium3,
59    /// ML-DSA-87 (NIST Level 5): K=8, L=7
60    Dilithium5,
61}
62
63/// FIPS 204 type alias: ML-DSA-44 ≡ Dilithium2.
64pub const ML_DSA_44: DilithiumMode = DilithiumMode::Dilithium2;
65/// FIPS 204 type alias: ML-DSA-65 ≡ Dilithium3.
66pub const ML_DSA_65: DilithiumMode = DilithiumMode::Dilithium3;
67/// FIPS 204 type alias: ML-DSA-87 ≡ Dilithium5.
68pub const ML_DSA_87: DilithiumMode = DilithiumMode::Dilithium5;
69
70/// OID for id-HashML-DSA-44-with-SHA512 (FIPS 204 §6.2).
71pub const HASH_ML_DSA_44_OID: &[u8] = &[
72    0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11,
73];
74/// OID for id-HashML-DSA-65-with-SHA512 (FIPS 204 §6.2).
75pub const HASH_ML_DSA_65_OID: &[u8] = &[
76    0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x12,
77];
78/// OID for id-HashML-DSA-87-with-SHA512 (FIPS 204 §6.2).
79pub const HASH_ML_DSA_87_OID: &[u8] = &[
80    0x06, 0x0B, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x13,
81];
82
83impl DilithiumMode {
84    /// Byte tag for binary serialization (matches NIST level: 2, 3, 5).
85    #[inline]
86    #[must_use]
87    pub const fn mode_tag(self) -> u8 {
88        match self {
89            Self::Dilithium2 => 0x02,
90            Self::Dilithium3 => 0x03,
91            Self::Dilithium5 => 0x05,
92        }
93    }
94
95    /// Recover mode from a serialization tag.
96    #[must_use]
97    pub const fn from_tag(tag: u8) -> Option<Self> {
98        match tag {
99            0x02 => Some(Self::Dilithium2),
100            0x03 => Some(Self::Dilithium3),
101            0x05 => Some(Self::Dilithium5),
102            _ => None,
103        }
104    }
105
106    /// Number of rows in the matrix A.
107    #[inline]
108    #[must_use]
109    pub const fn k(self) -> usize {
110        match self {
111            Self::Dilithium2 => 4,
112            Self::Dilithium3 => 6,
113            Self::Dilithium5 => 8,
114        }
115    }
116
117    /// Number of columns in the matrix A.
118    #[inline]
119    #[must_use]
120    pub const fn l(self) -> usize {
121        match self {
122            Self::Dilithium2 => 4,
123            Self::Dilithium3 => 5,
124            Self::Dilithium5 => 7,
125        }
126    }
127
128    /// Secret key coefficient bound.
129    #[inline]
130    #[must_use]
131    pub const fn eta(self) -> i32 {
132        match self {
133            Self::Dilithium2 => 2,
134            Self::Dilithium3 => 4,
135            Self::Dilithium5 => 2,
136        }
137    }
138
139    /// Number of ±1 coefficients in the challenge polynomial.
140    #[inline]
141    #[must_use]
142    pub const fn tau(self) -> usize {
143        match self {
144            Self::Dilithium2 => 39,
145            Self::Dilithium3 => 49,
146            Self::Dilithium5 => 60,
147        }
148    }
149
150    /// Rejection bound: TAU * ETA.
151    #[inline]
152    #[must_use]
153    pub const fn beta(self) -> i32 {
154        match self {
155            Self::Dilithium2 => 78,
156            Self::Dilithium3 => 196,
157            Self::Dilithium5 => 120,
158        }
159    }
160
161    /// Masking vector coefficient range: coefficients in [-(GAMMA1-1), GAMMA1].
162    #[inline]
163    #[must_use]
164    pub const fn gamma1(self) -> i32 {
165        match self {
166            Self::Dilithium2 => 1 << 17, // 131072
167            Self::Dilithium3 => 1 << 19, // 524288
168            Self::Dilithium5 => 1 << 19,
169        }
170    }
171
172    /// Low-order rounding range: GAMMA2 = (Q-1)/88 or (Q-1)/32.
173    #[inline]
174    #[must_use]
175    pub const fn gamma2(self) -> i32 {
176        match self {
177            Self::Dilithium2 => (Q - 1) / 88, // 95232
178            Self::Dilithium3 => (Q - 1) / 32, // 261888
179            Self::Dilithium5 => (Q - 1) / 32,
180        }
181    }
182
183    /// Maximum number of ones in the hint vector.
184    #[inline]
185    #[must_use]
186    pub const fn omega(self) -> usize {
187        match self {
188            Self::Dilithium2 => 80,
189            Self::Dilithium3 => 55,
190            Self::Dilithium5 => 75,
191        }
192    }
193
194    /// Challenge hash output length in bytes.
195    #[inline]
196    #[must_use]
197    pub const fn ctildebytes(self) -> usize {
198        match self {
199            Self::Dilithium2 => 32,
200            Self::Dilithium3 => 48,
201            Self::Dilithium5 => 64,
202        }
203    }
204
205    /// Packed size for z polynomial.
206    #[inline]
207    #[must_use]
208    pub const fn polyz_packedbytes(self) -> usize {
209        match self {
210            Self::Dilithium2 => 576, // gamma1 = 2^17
211            Self::Dilithium3 => 640, // gamma1 = 2^19
212            Self::Dilithium5 => 640,
213        }
214    }
215
216    /// Packed size for w1 polynomial.
217    #[inline]
218    #[must_use]
219    pub const fn polyw1_packedbytes(self) -> usize {
220        match self {
221            Self::Dilithium2 => 192, // gamma2 = (Q-1)/88
222            Self::Dilithium3 => 128, // gamma2 = (Q-1)/32
223            Self::Dilithium5 => 128,
224        }
225    }
226
227    /// Packed size for eta polynomial.
228    #[inline]
229    #[must_use]
230    pub const fn polyeta_packedbytes(self) -> usize {
231        match self {
232            Self::Dilithium2 => 96,  // eta = 2
233            Self::Dilithium3 => 128, // eta = 4
234            Self::Dilithium5 => 96,
235        }
236    }
237
238    /// Public key size in bytes.
239    #[inline]
240    #[must_use]
241    pub const fn public_key_bytes(self) -> usize {
242        SEEDBYTES + self.k() * POLYT1_PACKEDBYTES
243    }
244
245    /// Secret key size in bytes.
246    #[inline]
247    #[must_use]
248    pub const fn secret_key_bytes(self) -> usize {
249        2 * SEEDBYTES
250            + TRBYTES
251            + self.l() * self.polyeta_packedbytes()
252            + self.k() * self.polyeta_packedbytes()
253            + self.k() * POLYT0_PACKEDBYTES
254    }
255
256    /// Signature size in bytes.
257    #[inline]
258    #[must_use]
259    pub const fn signature_bytes(self) -> usize {
260        self.ctildebytes() + self.l() * self.polyz_packedbytes() + self.omega() + self.k()
261    }
262
263    /// Get the HashML-DSA OID for this mode.
264    #[inline]
265    #[must_use]
266    pub fn hash_oid(self) -> &'static [u8] {
267        match self {
268            Self::Dilithium2 => HASH_ML_DSA_44_OID,
269            Self::Dilithium3 => HASH_ML_DSA_65_OID,
270            Self::Dilithium5 => HASH_ML_DSA_87_OID,
271        }
272    }
273
274    /// FIPS 204 algorithm name.
275    #[must_use]
276    pub fn fips_name(self) -> &'static str {
277        match self {
278            Self::Dilithium2 => "ML-DSA-44",
279            Self::Dilithium3 => "ML-DSA-65",
280            Self::Dilithium5 => "ML-DSA-87",
281        }
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn test_dilithium2_sizes() {
291        let m = DilithiumMode::Dilithium2;
292        assert_eq!(m.public_key_bytes(), 1312);
293        assert_eq!(m.secret_key_bytes(), 2560);
294        assert_eq!(m.signature_bytes(), 2420);
295    }
296
297    #[test]
298    fn test_dilithium3_sizes() {
299        let m = DilithiumMode::Dilithium3;
300        assert_eq!(m.public_key_bytes(), 1952);
301        assert_eq!(m.secret_key_bytes(), 4032);
302        assert_eq!(m.signature_bytes(), 3309);
303    }
304
305    #[test]
306    fn test_dilithium5_sizes() {
307        let m = DilithiumMode::Dilithium5;
308        assert_eq!(m.public_key_bytes(), 2592);
309        assert_eq!(m.secret_key_bytes(), 4896);
310        assert_eq!(m.signature_bytes(), 4627);
311    }
312}