Skip to main content

quantica/ml_dsa/
mod.rs

1//! ML-DSA: Module-Lattice-Based Digital Signature Standard (FIPS 204).
2//!
3//! This crate implements the ML-DSA (formerly CRYSTALS-Dilithium) digital signature
4//! scheme as specified in FIPS 204. ML-DSA is a post-quantum lattice-based signature
5//! scheme built on the hardness of the Module Learning With Errors (M-LWE) and
6//! Module Short Integer Solution (M-SIS) problems.
7//!
8//! Three parameter sets are provided, corresponding to NIST security levels 2, 3, and 5:
9//!
10//! - [`MlDsa44Scheme`] -- ML-DSA-44 (security level 2, ~128-bit classical security)
11//! - [`MlDsa65Scheme`] -- ML-DSA-65 (security level 3, ~192-bit classical security)
12//! - [`MlDsa87Scheme`] -- ML-DSA-87 (security level 5, ~256-bit classical security)
13//!
14//! # Examples
15//!
16//! ```rust
17//! use quantica::ml_dsa::{MlDsa44Scheme, OsRng, MlDsa};
18//!
19//! let mut rng = OsRng;
20//! let (pk, sk) = MlDsa44Scheme::keygen(&mut rng).unwrap();
21//! let msg = b"Hello, post-quantum world!";
22//! let sig = MlDsa44Scheme::sign(&sk, msg, b"", &mut rng).unwrap();
23//! let valid = MlDsa44Scheme::verify(&pk, msg, b"", &sig).unwrap();
24//! assert!(valid);
25//! ```
26
27/// Decomposition, rounding, and hint functions for signatures.
28pub mod decompose;
29/// Core ML-DSA key generation, signing, and verification algorithms.
30pub mod dsa;
31/// Encoding and decoding of keys, signatures, and polynomials.
32pub mod encode;
33/// Number Theoretic Transform for polynomial arithmetic.
34pub mod ntt;
35/// ML-DSA parameter sets and constants (FIPS 204, Table 1).
36pub mod params;
37/// Cryptographic random number generation trait and OS-backed implementation.
38pub mod rng;
39/// Sampling algorithms for matrix, secret, and masking generation.
40pub mod sample;
41/// Keccak/SHA-3/SHAKE hash function implementations (FIPS 202).
42pub mod sha3;
43
44/// First-order arithmetic masking for ML-DSA secret polynomials
45/// (DPA / template-attack countermeasure). Available with the
46/// `sca-protected` Cargo feature.
47#[cfg(feature = "sca-protected")]
48pub mod masked;
49
50/// Fisher-Yates shuffled NTT for ML-DSA secret polynomials
51/// (SPA / trace-alignment countermeasure). Available with the
52/// `sca-protected` Cargo feature.
53#[cfg(feature = "sca-protected")]
54pub mod shuffle;
55
56/// Small polynomial representation (i16, 512 B/poly) for secret vectors.
57/// Uses the ML-KEM NTT (q=3329) for multiplications. Available with
58/// the `small-secret` Cargo feature.
59#[cfg(feature = "small-secret")]
60pub mod smallpoly;
61
62/// Compressed polynomial storage (3 bytes/coeff, 768 B/poly) and
63/// compressed challenge (68 bytes + schoolbook multiply).
64/// Available with `compressed-poly` or `compressed-challenge`.
65#[cfg(any(feature = "compressed-poly", feature = "compressed-challenge"))]
66pub mod compressed;
67
68use alloc::vec::Vec;
69use core::marker::PhantomData;
70
71pub use params::{MlDsa44, MlDsa65, MlDsa87, Params};
72pub use rng::CryptoRng;
73#[cfg(feature = "std")]
74pub use rng::OsRng;
75
76use crate::secret::SecretBytes;
77
78// =====================================================================
79// Typed key / signature wrappers
80// =====================================================================
81
82/// ML-DSA **verifying key** (the public half of a key pair).
83///
84/// Type-tagged with the parameter set `P`. Public material — no
85/// zeroization is performed on drop.
86pub struct VerifyingKey<P: Params> {
87    bytes: Vec<u8>,
88    _marker: PhantomData<P>,
89}
90
91impl<P: Params> VerifyingKey<P> {
92    /// Wrap a raw byte slice. Length is validated against
93    /// [`Params::PK_LEN`].
94    pub fn from_bytes(bytes: &[u8]) -> Result<Self, MlDsaError> {
95        if bytes.len() != P::PK_LEN {
96            return Err(MlDsaError::InvalidPublicKey);
97        }
98        Ok(Self {
99            bytes: bytes.to_vec(),
100            _marker: PhantomData,
101        })
102    }
103
104    /// Borrow the encoded verifying key as a byte slice.
105    pub fn as_bytes(&self) -> &[u8] {
106        &self.bytes
107    }
108
109    /// Length in bytes (always [`Params::PK_LEN`]).
110    pub fn len(&self) -> usize {
111        self.bytes.len()
112    }
113}
114
115impl<P: Params> AsRef<[u8]> for VerifyingKey<P> {
116    fn as_ref(&self) -> &[u8] {
117        &self.bytes
118    }
119}
120
121impl<P: Params> core::ops::Deref for VerifyingKey<P> {
122    type Target = [u8];
123    fn deref(&self) -> &[u8] {
124        &self.bytes
125    }
126}
127
128impl<P: Params> Clone for VerifyingKey<P> {
129    fn clone(&self) -> Self {
130        Self {
131            bytes: self.bytes.clone(),
132            _marker: PhantomData,
133        }
134    }
135}
136
137/// ML-DSA **signing key** (the private half of a key pair).
138///
139/// Backed by [`SecretBytes`] — wipes its memory on [`Drop`] via
140/// `silentops::ct_zeroize`. Type-tagged with `P` to prevent
141/// cross-parameter-set use.
142pub struct SigningKey<P: Params> {
143    bytes: SecretBytes,
144    _marker: PhantomData<P>,
145}
146
147impl<P: Params> SigningKey<P> {
148    /// Wrap a raw byte slice. Length is validated against
149    /// [`Params::SK_LEN`].
150    pub fn from_bytes(bytes: &[u8]) -> Result<Self, MlDsaError> {
151        if bytes.len() != P::SK_LEN {
152            return Err(MlDsaError::InvalidSecretKey);
153        }
154        Ok(Self {
155            bytes: SecretBytes::from_slice(bytes),
156            _marker: PhantomData,
157        })
158    }
159
160    /// Borrow the encoded signing key as a byte slice.
161    pub fn as_bytes(&self) -> &[u8] {
162        self.bytes.as_bytes()
163    }
164
165    /// Length in bytes (always [`Params::SK_LEN`]).
166    pub fn len(&self) -> usize {
167        self.bytes.len()
168    }
169}
170
171impl<P: Params> AsRef<[u8]> for SigningKey<P> {
172    fn as_ref(&self) -> &[u8] {
173        self.bytes.as_bytes()
174    }
175}
176
177impl<P: Params> core::ops::Deref for SigningKey<P> {
178    type Target = [u8];
179    fn deref(&self) -> &[u8] {
180        self.bytes.as_bytes()
181    }
182}
183
184/// ML-DSA **signature**. Type-tagged with the parameter set `P`.
185///
186/// Signatures are public material (transmitted alongside the message)
187/// and are not zeroized.
188pub struct Signature<P: Params> {
189    bytes: Vec<u8>,
190    _marker: PhantomData<P>,
191}
192
193impl<P: Params> Signature<P> {
194    /// Wrap a raw byte slice. Length is validated against
195    /// [`Params::SIG_LEN`].
196    pub fn from_bytes(bytes: &[u8]) -> Result<Self, MlDsaError> {
197        if bytes.len() != P::SIG_LEN {
198            return Err(MlDsaError::InvalidSignature);
199        }
200        Ok(Self {
201            bytes: bytes.to_vec(),
202            _marker: PhantomData,
203        })
204    }
205
206    /// Borrow the encoded signature as a byte slice.
207    pub fn as_bytes(&self) -> &[u8] {
208        &self.bytes
209    }
210
211    /// Length in bytes (always [`Params::SIG_LEN`]).
212    pub fn len(&self) -> usize {
213        self.bytes.len()
214    }
215}
216
217impl<P: Params> AsRef<[u8]> for Signature<P> {
218    fn as_ref(&self) -> &[u8] {
219        &self.bytes
220    }
221}
222
223impl<P: Params> core::ops::Deref for Signature<P> {
224    type Target = [u8];
225    fn deref(&self) -> &[u8] {
226        &self.bytes
227    }
228}
229
230impl<P: Params> Clone for Signature<P> {
231    fn clone(&self) -> Self {
232        Self {
233            bytes: self.bytes.clone(),
234            _marker: PhantomData,
235        }
236    }
237}
238
239/// Error types for ML-DSA operations.
240///
241/// Each variant corresponds to a specific failure mode that can occur during
242/// key generation, signing, or verification.
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub enum MlDsaError {
245    /// Random number generation failed.
246    ///
247    /// Returned when the underlying RNG (e.g., `/dev/urandom`) cannot provide bytes.
248    RngFailure,
249    /// Invalid public key (wrong length or format).
250    ///
251    /// The provided public key does not have the expected byte length for the
252    /// chosen parameter set.
253    InvalidPublicKey,
254    /// Invalid secret key (wrong length or format).
255    ///
256    /// The provided secret key does not have the expected byte length for the
257    /// chosen parameter set.
258    InvalidSecretKey,
259    /// Invalid signature (wrong length or format).
260    ///
261    /// The provided signature does not have the expected byte length for the
262    /// chosen parameter set, or its internal encoding is malformed.
263    InvalidSignature,
264    /// Context string too long (> 255 bytes).
265    ///
266    /// FIPS 204 limits the optional context string to at most 255 bytes.
267    ContextTooLong,
268    /// Signature verification failed.
269    VerificationFailed,
270}
271
272impl core::fmt::Display for MlDsaError {
273    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
274        match self {
275            MlDsaError::RngFailure => write!(f, "RNG failure"),
276            MlDsaError::InvalidPublicKey => write!(f, "Invalid public key"),
277            MlDsaError::InvalidSecretKey => write!(f, "Invalid secret key"),
278            MlDsaError::InvalidSignature => write!(f, "Invalid signature"),
279            MlDsaError::ContextTooLong => write!(f, "Context string too long"),
280            MlDsaError::VerificationFailed => write!(f, "Signature verification failed"),
281        }
282    }
283}
284
285#[cfg(feature = "std")]
286impl std::error::Error for MlDsaError {}
287
288/// Generic ML-DSA interface parameterized by security level.
289///
290/// This struct provides the high-level API for ML-DSA key generation, signing,
291/// and verification. The type parameter `P` selects the parameter set
292/// ([`MlDsa44`], [`MlDsa65`], or [`MlDsa87`]).
293///
294/// All methods are stateless; [`MlDsa`] carries no runtime data and exists only
295/// to bind the parameter set at the type level.
296pub struct MlDsa<P: Params> {
297    _marker: PhantomData<P>,
298}
299
300impl<P: Params> MlDsa<P> {
301    /// Generate a new ML-DSA key pair.
302    ///
303    /// Implements Algorithm 1 of FIPS 204 (ML-DSA.KeyGen). Draws 32 random
304    /// bytes from `rng` and derives a public key / secret key pair.
305    ///
306    /// Returns `(pk, sk)` where `pk` has length [`Self::PK_LEN`] and `sk` has
307    /// length [`Self::SK_LEN`].
308    ///
309    /// # Errors
310    ///
311    /// Returns [`MlDsaError::RngFailure`] if the RNG cannot provide bytes.
312    ///
313    /// # Examples
314    ///
315    /// ```rust
316    /// use quantica::ml_dsa::{MlDsa, MlDsa44, OsRng};
317    ///
318    /// let mut rng = OsRng;
319    /// let (pk, sk) = MlDsa::<MlDsa44>::keygen(&mut rng).unwrap();
320    /// assert_eq!(pk.len(), MlDsa::<MlDsa44>::PK_LEN);
321    /// assert_eq!(sk.len(), MlDsa::<MlDsa44>::SK_LEN);
322    /// ```
323    pub fn keygen(rng: &mut dyn CryptoRng) -> Result<(VerifyingKey<P>, SigningKey<P>), MlDsaError> {
324        let (pk_v, sk_v) = dsa::keygen::<P>(rng)?;
325        Ok((
326            VerifyingKey {
327                bytes: pk_v,
328                _marker: PhantomData,
329            },
330            SigningKey {
331                bytes: SecretBytes::from_vec(sk_v),
332                _marker: PhantomData,
333            },
334        ))
335    }
336
337    /// Deterministic key generation from a 32-byte seed.
338    ///
339    /// Implements Algorithm 6 of FIPS 204 (ML-DSA.KeyGen_internal). This is
340    /// primarily useful for testing with known-answer vectors.
341    ///
342    /// - `xi`: 32-byte random seed.
343    ///
344    /// Returns `(pk, sk)`.
345    pub fn keygen_internal(xi: &[u8; 32]) -> (Vec<u8>, Vec<u8>) {
346        dsa::keygen_internal::<P>(xi)
347    }
348
349    /// Sign a message with an optional context string.
350    ///
351    /// Implements Algorithm 2 of FIPS 204 (ML-DSA.Sign). Uses **hedged signing**:
352    /// 32 random bytes are drawn from `rng` and mixed with the secret key material
353    /// to produce the per-signature nonce. This provides resilience against
354    /// fault attacks compared to purely deterministic signing.
355    ///
356    /// The signing algorithm uses a rejection sampling loop internally: candidate
357    /// signatures are generated until one passes all norm checks, so execution
358    /// time may vary.
359    ///
360    /// - `sk`: secret key (must be [`Self::SK_LEN`] bytes).
361    /// - `msg`: message to sign (arbitrary length).
362    /// - `ctx`: optional context string (at most 255 bytes).
363    /// - `rng`: source of randomness for hedged signing.
364    ///
365    /// Returns a signature of length [`Self::SIG_LEN`].
366    ///
367    /// # Errors
368    ///
369    /// - [`MlDsaError::InvalidSecretKey`] if `sk` has the wrong length.
370    /// - [`MlDsaError::ContextTooLong`] if `ctx` exceeds 255 bytes.
371    /// - [`MlDsaError::RngFailure`] if the RNG cannot provide bytes.
372    ///
373    /// # Examples
374    ///
375    /// ```rust
376    /// use quantica::ml_dsa::{MlDsa, MlDsa44, OsRng};
377    ///
378    /// let mut rng = OsRng;
379    /// let (pk, sk) = MlDsa::<MlDsa44>::keygen(&mut rng).unwrap();
380    /// let sig = MlDsa::<MlDsa44>::sign(&sk, b"message", b"", &mut rng).unwrap();
381    /// assert_eq!(sig.len(), MlDsa::<MlDsa44>::SIG_LEN);
382    /// ```
383    pub fn sign(
384        sk: &SigningKey<P>,
385        msg: &[u8],
386        ctx: &[u8],
387        rng: &mut dyn CryptoRng,
388    ) -> Result<Signature<P>, MlDsaError> {
389        let sig_v = dsa::sign::<P>(sk.as_bytes(), msg, ctx, rng)?;
390        Ok(Signature {
391            bytes: sig_v,
392            _marker: PhantomData,
393        })
394    }
395
396    /// Deterministic signing (internal / testing use).
397    ///
398    /// Implements Algorithm 7 of FIPS 204 (ML-DSA.Sign_internal). The caller
399    /// supplies the pre-formatted message `m_prime` and a 32-byte `rnd` value
400    /// directly. Setting `rnd` to all zeros produces fully deterministic
401    /// signatures; using random bytes produces hedged signatures.
402    ///
403    /// - `sk`: encoded secret key.
404    /// - `m_prime`: pre-formatted message (`0x00 || len(ctx) || ctx || msg`).
405    /// - `rnd`: 32-byte randomness (all zeros for deterministic mode).
406    ///
407    /// # Errors
408    ///
409    /// Returns [`MlDsaError::InvalidSecretKey`] if `sk` has the wrong length.
410    pub fn sign_internal(sk: &[u8], m_prime: &[u8], rnd: &[u8; 32]) -> Result<Vec<u8>, MlDsaError> {
411        dsa::sign_internal::<P>(sk, m_prime, rnd)
412    }
413
414    /// Verify a signature on a message with an optional context string.
415    ///
416    /// Implements Algorithm 3 of FIPS 204 (ML-DSA.Verify).
417    ///
418    /// - `pk`: public key (must be [`Self::PK_LEN`] bytes).
419    /// - `msg`: the signed message.
420    /// - `ctx`: the context string used during signing (at most 255 bytes).
421    /// - `sig`: the signature (must be [`Self::SIG_LEN`] bytes).
422    ///
423    /// Returns `Ok(true)` if the signature is valid, `Ok(false)` if verification
424    /// fails (invalid signature content), or an `Err` for structural issues.
425    ///
426    /// # Errors
427    ///
428    /// - [`MlDsaError::InvalidPublicKey`] if `pk` has the wrong length.
429    /// - [`MlDsaError::InvalidSignature`] if `sig` has the wrong length.
430    /// - [`MlDsaError::ContextTooLong`] if `ctx` exceeds 255 bytes.
431    ///
432    /// # Examples
433    ///
434    /// ```rust
435    /// use quantica::ml_dsa::{MlDsa, MlDsa44, OsRng};
436    ///
437    /// let mut rng = OsRng;
438    /// let (pk, sk) = MlDsa::<MlDsa44>::keygen(&mut rng).unwrap();
439    /// let sig = MlDsa::<MlDsa44>::sign(&sk, b"msg", b"", &mut rng).unwrap();
440    /// assert!(MlDsa::<MlDsa44>::verify(&pk, b"msg", b"", &sig).unwrap());
441    /// ```
442    pub fn verify(pk: &VerifyingKey<P>, msg: &[u8], ctx: &[u8], sig: &Signature<P>) -> Result<bool, MlDsaError> {
443        dsa::verify::<P>(pk.as_bytes(), msg, ctx, sig.as_bytes())
444    }
445
446    /// Verify a signature (internal / testing use).
447    ///
448    /// Implements Algorithm 8 of FIPS 204 (ML-DSA.Verify_internal). The caller
449    /// supplies the pre-formatted message `m_prime` directly.
450    ///
451    /// - `pk`: encoded public key.
452    /// - `m_prime`: pre-formatted message.
453    /// - `sig`: encoded signature.
454    ///
455    /// Returns `Ok(true)` on success or `Ok(false)` when the signature is invalid.
456    pub fn verify_internal(pk: &[u8], m_prime: &[u8], sig: &[u8]) -> Result<bool, MlDsaError> {
457        dsa::verify_internal::<P>(pk, m_prime, sig)
458    }
459
460    /// Public key length in bytes for this parameter set.
461    pub const PK_LEN: usize = P::PK_LEN;
462
463    /// Secret key length in bytes for this parameter set.
464    pub const SK_LEN: usize = P::SK_LEN;
465
466    /// Signature length in bytes for this parameter set.
467    pub const SIG_LEN: usize = P::SIG_LEN;
468}
469
470/// Convenience alias for ML-DSA-44 (NIST security level 2).
471pub type MlDsa44Scheme = MlDsa<MlDsa44>;
472/// Convenience alias for ML-DSA-65 (NIST security level 3).
473pub type MlDsa65Scheme = MlDsa<MlDsa65>;
474/// Convenience alias for ML-DSA-87 (NIST security level 5).
475pub type MlDsa87Scheme = MlDsa<MlDsa87>;