Skip to main content

saorsa_pqc/api/
sig.rs

1// Rust 1.92+ raises unused_assignments on struct fields read via getter methods
2// when used with derive macros like Zeroize. This is a known false positive.
3#![allow(unused_assignments)]
4
5//! ML-DSA (Module-Lattice-Based Digital Signature Algorithm) API
6//!
7//! Provides a simple interface to FIPS 204 ML-DSA for quantum-resistant digital signatures
8//! without requiring users to manage RNG or internal details.
9//!
10//! # Examples
11//!
12//! ## Basic Signature and Verification
13//! ```rust,no_run
14//! use saorsa_pqc::api::sig::{ml_dsa_65, MlDsaVariant};
15//!
16//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
17//! // Create ML-DSA instance with 192-bit security
18//! let dsa = ml_dsa_65();
19//!
20//! // Generate a signing keypair
21//! let (public_key, secret_key) = dsa.generate_keypair()?;
22//!
23//! // Sign a message
24//! let message = b"Important document to sign";
25//! let signature = dsa.sign(&secret_key, message)?;
26//!
27//! // Verify the signature
28//! let is_valid = dsa.verify(&public_key, message, &signature)?;
29//! assert!(is_valid);
30//! # Ok(())
31//! # }
32//! ```
33//!
34//! ## Document Signing with Context
35//! ```rust,no_run
36//! use saorsa_pqc::api::sig::{MlDsa, MlDsaVariant};
37//!
38//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
39//! let dsa = MlDsa::new(MlDsaVariant::MlDsa87); // Maximum security
40//! let (public_key, secret_key) = dsa.generate_keypair()?;
41//!
42//! // Sign with additional context for domain separation
43//! let document = b"Contract #12345";
44//! let context = b"legal-documents-v1";
45//! let signature = dsa.sign_with_context(&secret_key, document, context)?;
46//!
47//! // Verify with the same context
48//! let valid = dsa.verify_with_context(&public_key, document, &signature, context)?;
49//! assert!(valid);
50//! # Ok(())
51//! # }
52//! ```
53//!
54//! ## Security Levels
55//! - ML-DSA-44: NIST Level 2 (~128-bit classical, ~90-bit quantum)
56//! - ML-DSA-65: NIST Level 3 (~192-bit classical, ~128-bit quantum)
57//! - ML-DSA-87: NIST Level 5 (~256-bit classical, ~170-bit quantum)
58
59use super::errors::{PqcError, PqcResult};
60use rand_core::OsRng;
61use zeroize::{Zeroize, ZeroizeOnDrop};
62
63// Import FIPS implementations
64use fips204::traits::{KeyGen, SerDes, Signer, Verifier};
65use fips204::{ml_dsa_44, ml_dsa_65, ml_dsa_87};
66
67/// ML-DSA algorithm variants
68///
69/// Selects the security level and performance characteristics for ML-DSA operations.
70/// Higher security levels provide more protection but require larger keys and signatures.
71///
72/// # Examples
73/// ```rust,no_run
74/// use saorsa_pqc::api::sig::MlDsaVariant;
75///
76/// // Choose based on security requirements
77/// let standard = MlDsaVariant::MlDsa65;     // Recommended for most uses
78/// let lightweight = MlDsaVariant::MlDsa44;  // For constrained environments
79/// let maximum = MlDsaVariant::MlDsa87;      // For highest security needs
80///
81/// println!("Public key size: {} bytes", standard.public_key_size());
82/// println!("Signature size: {} bytes", standard.signature_size());
83/// println!("Security: {}", standard.security_level());
84/// ```
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum MlDsaVariant {
87    /// ML-DSA-44: NIST Level 2 security (~128-bit classical)
88    /// - Public key: 1312 bytes
89    /// - Secret key: 2560 bytes
90    /// - Signature: 2420 bytes
91    MlDsa44,
92    /// ML-DSA-65: NIST Level 3 security (~192-bit classical) [RECOMMENDED]
93    /// - Public key: 1952 bytes
94    /// - Secret key: 4032 bytes
95    /// - Signature: 3309 bytes
96    MlDsa65,
97    /// ML-DSA-87: NIST Level 5 security (~256-bit classical)
98    /// - Public key: 2592 bytes
99    /// - Secret key: 4896 bytes
100    /// - Signature: 4627 bytes
101    MlDsa87,
102}
103
104// Manual implementation of Zeroize for MlDsaVariant (no-op since it contains no sensitive data)
105impl zeroize::Zeroize for MlDsaVariant {
106    fn zeroize(&mut self) {
107        // No sensitive data to zeroize in an enum variant selector
108    }
109}
110
111impl MlDsaVariant {
112    /// Get the public key size in bytes
113    #[must_use]
114    pub const fn public_key_size(&self) -> usize {
115        match self {
116            Self::MlDsa44 => 1312,
117            Self::MlDsa65 => 1952,
118            Self::MlDsa87 => 2592,
119        }
120    }
121
122    /// Get the secret key size in bytes
123    #[must_use]
124    pub const fn secret_key_size(&self) -> usize {
125        match self {
126            Self::MlDsa44 => 2560,
127            Self::MlDsa65 => 4032,
128            Self::MlDsa87 => 4896,
129        }
130    }
131
132    /// Get the signature size in bytes
133    #[must_use]
134    pub const fn signature_size(&self) -> usize {
135        match self {
136            Self::MlDsa44 => 2420,
137            Self::MlDsa65 => 3309,
138            Self::MlDsa87 => 4627,
139        }
140    }
141
142    /// Get the security level description
143    #[must_use]
144    pub const fn security_level(&self) -> &'static str {
145        match self {
146            Self::MlDsa44 => "NIST Level 2 (~128-bit)",
147            Self::MlDsa65 => "NIST Level 3 (~192-bit)",
148            Self::MlDsa87 => "NIST Level 5 (~256-bit)",
149        }
150    }
151
152    /// Maximum context length (255 bytes for all variants)
153    pub const MAX_CONTEXT_LENGTH: usize = 255;
154}
155
156/// ML-DSA public key
157///
158/// Contains the public verification key for ML-DSA signatures.
159/// This key can be freely shared and is used to verify signatures.
160///
161/// # Examples
162/// ```rust,no_run
163/// use saorsa_pqc::api::sig::{ml_dsa_65, MlDsaPublicKey, MlDsaVariant};
164///
165/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
166/// let dsa = ml_dsa_65();
167/// let (public_key, _) = dsa.generate_keypair()?;
168///
169/// // Export for distribution
170/// let key_bytes = public_key.to_bytes();
171/// println!("Public key size: {} bytes", key_bytes.len());
172///
173/// // Import from bytes
174/// let imported = MlDsaPublicKey::from_bytes(MlDsaVariant::MlDsa65, &key_bytes)?;
175/// assert_eq!(public_key.variant(), imported.variant());
176/// # Ok(())
177/// # }
178/// ```
179#[derive(Clone, Zeroize, ZeroizeOnDrop)]
180pub struct MlDsaPublicKey {
181    #[zeroize(skip)]
182    variant: MlDsaVariant,
183    bytes: Vec<u8>,
184}
185
186impl MlDsaPublicKey {
187    /// Get the variant of this key
188    #[must_use]
189    pub const fn variant(&self) -> MlDsaVariant {
190        self.variant
191    }
192
193    /// Export the public key as bytes
194    #[must_use]
195    pub fn to_bytes(&self) -> Vec<u8> {
196        self.bytes.clone()
197    }
198
199    /// Import a public key from bytes
200    ///
201    /// # Errors
202    ///
203    /// Returns an error if the byte slice has an incorrect length for the specified variant.
204    pub fn from_bytes(variant: MlDsaVariant, bytes: &[u8]) -> PqcResult<Self> {
205        if bytes.len() != variant.public_key_size() {
206            return Err(PqcError::InvalidKeySize {
207                expected: variant.public_key_size(),
208                got: bytes.len(),
209            });
210        }
211
212        // Validate by trying to deserialize
213        match variant {
214            MlDsaVariant::MlDsa44 => {
215                let _ = ml_dsa_44::PublicKey::try_from_bytes(bytes.try_into().map_err(|_| {
216                    PqcError::InvalidKeySize {
217                        expected: variant.public_key_size(),
218                        got: bytes.len(),
219                    }
220                })?)
221                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
222            }
223            MlDsaVariant::MlDsa65 => {
224                let _ = ml_dsa_65::PublicKey::try_from_bytes(bytes.try_into().map_err(|_| {
225                    PqcError::InvalidKeySize {
226                        expected: variant.public_key_size(),
227                        got: bytes.len(),
228                    }
229                })?)
230                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
231            }
232            MlDsaVariant::MlDsa87 => {
233                let _ = ml_dsa_87::PublicKey::try_from_bytes(bytes.try_into().map_err(|_| {
234                    PqcError::InvalidKeySize {
235                        expected: variant.public_key_size(),
236                        got: bytes.len(),
237                    }
238                })?)
239                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
240            }
241        }
242
243        Ok(Self {
244            variant,
245            bytes: bytes.to_vec(),
246        })
247    }
248}
249
250/// ML-DSA secret key
251///
252/// Contains the private signing key for ML-DSA signatures.
253/// This key must be kept secret and is automatically zeroized when dropped.
254///
255/// # Security Considerations
256/// - Never expose secret keys in logs or error messages
257/// - Store securely (encrypted at rest)
258/// - Use secure channels for transmission
259/// - Consider hardware security modules (HSMs) for production
260///
261/// # Examples
262/// ```rust,no_run
263/// use saorsa_pqc::api::sig::{ml_dsa_65, MlDsaSecretKey, MlDsaVariant};
264///
265/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
266/// let dsa = ml_dsa_65();
267/// let (_, secret_key) = dsa.generate_keypair()?;
268///
269/// // Serialize for secure storage
270/// let key_bytes = secret_key.to_bytes();
271/// // Store key_bytes securely (encrypted)
272///
273/// // Later, restore from secure storage
274/// let restored = MlDsaSecretKey::from_bytes(MlDsaVariant::MlDsa65, &key_bytes)?;
275/// # Ok(())
276/// # }
277/// ```
278#[derive(Clone, Zeroize, ZeroizeOnDrop)]
279pub struct MlDsaSecretKey {
280    #[zeroize(skip)]
281    variant: MlDsaVariant,
282    bytes: Vec<u8>,
283}
284
285impl MlDsaSecretKey {
286    /// Get the variant of this key
287    #[must_use]
288    pub const fn variant(&self) -> MlDsaVariant {
289        self.variant
290    }
291
292    /// Export the secret key as bytes (handle with care!)
293    #[must_use]
294    pub fn to_bytes(&self) -> Vec<u8> {
295        self.bytes.clone()
296    }
297
298    /// Import a secret key from bytes
299    ///
300    /// # Errors
301    ///
302    /// Returns an error if the byte slice has an incorrect length for the specified variant.
303    pub fn from_bytes(variant: MlDsaVariant, bytes: &[u8]) -> PqcResult<Self> {
304        if bytes.len() != variant.secret_key_size() {
305            return Err(PqcError::InvalidKeySize {
306                expected: variant.secret_key_size(),
307                got: bytes.len(),
308            });
309        }
310
311        // Validate by trying to deserialize
312        match variant {
313            MlDsaVariant::MlDsa44 => {
314                let _ = ml_dsa_44::PrivateKey::try_from_bytes(bytes.try_into().map_err(|_| {
315                    PqcError::InvalidKeySize {
316                        expected: variant.secret_key_size(),
317                        got: bytes.len(),
318                    }
319                })?)
320                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
321            }
322            MlDsaVariant::MlDsa65 => {
323                let _ = ml_dsa_65::PrivateKey::try_from_bytes(bytes.try_into().map_err(|_| {
324                    PqcError::InvalidKeySize {
325                        expected: variant.secret_key_size(),
326                        got: bytes.len(),
327                    }
328                })?)
329                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
330            }
331            MlDsaVariant::MlDsa87 => {
332                let _ = ml_dsa_87::PrivateKey::try_from_bytes(bytes.try_into().map_err(|_| {
333                    PqcError::InvalidKeySize {
334                        expected: variant.secret_key_size(),
335                        got: bytes.len(),
336                    }
337                })?)
338                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
339            }
340        }
341
342        Ok(Self {
343            variant,
344            bytes: bytes.to_vec(),
345        })
346    }
347}
348
349/// ML-DSA signature
350///
351/// A quantum-resistant digital signature produced by ML-DSA.
352/// Signatures are non-deterministic (include randomness) for enhanced security.
353///
354/// # Size Requirements
355/// - ML-DSA-44: 2420 bytes
356/// - ML-DSA-65: 3309 bytes
357/// - ML-DSA-87: 4627 bytes
358///
359/// # Examples
360/// ```rust,no_run
361/// use saorsa_pqc::api::sig::{ml_dsa_65, MlDsaSignature, MlDsaVariant};
362///
363/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
364/// let dsa = ml_dsa_65();
365/// let (public_key, secret_key) = dsa.generate_keypair()?;
366///
367/// // Create signature
368/// let message = b"Document to sign";
369/// let signature = dsa.sign(&secret_key, message)?;
370///
371/// // Serialize for transmission
372/// let sig_bytes = signature.to_bytes();
373/// assert_eq!(sig_bytes.len(), 3309); // ML-DSA-65 signature size
374///
375/// // Deserialize received signature
376/// let received_sig = MlDsaSignature::from_bytes(MlDsaVariant::MlDsa65, &sig_bytes)?;
377///
378/// // Verify
379/// assert!(dsa.verify(&public_key, message, &received_sig)?);
380/// # Ok(())
381/// # }
382/// ```
383#[derive(Clone, Zeroize, ZeroizeOnDrop)]
384pub struct MlDsaSignature {
385    #[zeroize(skip)]
386    variant: MlDsaVariant,
387    bytes: Vec<u8>,
388}
389
390impl MlDsaSignature {
391    /// Get the variant of this signature
392    #[must_use]
393    pub const fn variant(&self) -> MlDsaVariant {
394        self.variant
395    }
396
397    /// Export the signature as bytes
398    #[must_use]
399    pub fn to_bytes(&self) -> Vec<u8> {
400        self.bytes.clone()
401    }
402
403    /// Import a signature from bytes
404    ///
405    /// # Errors
406    ///
407    /// Returns an error if the byte slice has an incorrect length for the specified variant.
408    pub fn from_bytes(variant: MlDsaVariant, bytes: &[u8]) -> PqcResult<Self> {
409        if bytes.len() != variant.signature_size() {
410            return Err(PqcError::InvalidSignatureSize {
411                expected: variant.signature_size(),
412                got: bytes.len(),
413            });
414        }
415
416        Ok(Self {
417            variant,
418            bytes: bytes.to_vec(),
419        })
420    }
421}
422
423/// ML-DSA main API
424///
425/// The main interface for ML-DSA digital signature operations.
426/// This struct provides methods for key generation, signing, and verification
427/// according to NIST FIPS 204 standard.
428///
429/// # Examples
430///
431/// ## Basic Usage
432/// ```rust,no_run
433/// use saorsa_pqc::api::sig::{MlDsa, MlDsaVariant};
434///
435/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
436/// // Create instance with chosen security level
437/// let dsa = MlDsa::new(MlDsaVariant::MlDsa65);
438///
439/// // Generate keys
440/// let (public_key, secret_key) = dsa.generate_keypair()?;
441///
442/// // Sign and verify
443/// let message = b"Important message";
444/// let signature = dsa.sign(&secret_key, message)?;
445/// assert!(dsa.verify(&public_key, message, &signature)?);
446/// # Ok(())
447/// # }
448/// ```
449///
450/// ## With Context for Domain Separation
451/// ```rust,no_run
452/// use saorsa_pqc::api::sig::{MlDsa, MlDsaVariant};
453///
454/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
455/// let dsa = MlDsa::new(MlDsaVariant::MlDsa87);
456/// let (public_key, secret_key) = dsa.generate_keypair()?;
457///
458/// // Use context to prevent cross-protocol attacks
459/// let message = b"Transaction #42";
460/// let context = b"blockchain-v2";
461///
462/// let signature = dsa.sign_with_context(&secret_key, message, context)?;
463/// let valid = dsa.verify_with_context(&public_key, message, &signature, context)?;
464/// assert!(valid);
465///
466/// // Different context will fail verification
467/// let wrong_context = b"blockchain-v1";
468/// let invalid = dsa.verify_with_context(&public_key, message, &signature, wrong_context)?;
469/// assert!(!invalid);
470/// # Ok(())
471/// # }
472/// ```
473pub struct MlDsa {
474    variant: MlDsaVariant,
475}
476
477impl MlDsa {
478    /// Create a new ML-DSA instance with the specified variant
479    ///
480    /// # Arguments
481    /// * `variant` - The ML-DSA parameter set to use (44, 65, or 87)
482    ///
483    /// # Example
484    /// ```rust,no_run
485    /// use saorsa_pqc::api::sig::{MlDsa, MlDsaVariant};
486    ///
487    /// let dsa_44 = MlDsa::new(MlDsaVariant::MlDsa44);   // NIST Level 2
488    /// let dsa_65 = MlDsa::new(MlDsaVariant::MlDsa65);   // NIST Level 3
489    /// let dsa_87 = MlDsa::new(MlDsaVariant::MlDsa87);   // NIST Level 5
490    /// ```
491    #[must_use]
492    pub const fn new(variant: MlDsaVariant) -> Self {
493        Self { variant }
494    }
495
496    /// Generate a new key pair
497    ///
498    /// Creates a new ML-DSA key pair using the system's secure random number generator.
499    ///
500    /// # Returns
501    /// A tuple containing:
502    /// - `MlDsaPublicKey`: The public key for signature verification
503    /// - `MlDsaSecretKey`: The secret key for signing
504    ///
505    /// # Errors
506    /// Returns an error if key generation fails (extremely rare with proper RNG).
507    ///
508    /// # Example
509    /// ```rust,no_run
510    /// use saorsa_pqc::api::sig::ml_dsa_65;
511    ///
512    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
513    /// let dsa = ml_dsa_65();
514    /// let (public_key, secret_key) = dsa.generate_keypair()?;
515    ///
516    /// println!("Public key: {} bytes", public_key.to_bytes().len());
517    /// println!("Secret key: {} bytes", secret_key.to_bytes().len());
518    /// # Ok(())
519    /// # }
520    /// ```
521    #[allow(clippy::large_stack_frames)]
522    pub fn generate_keypair(&self) -> PqcResult<(MlDsaPublicKey, MlDsaSecretKey)> {
523        match self.variant {
524            MlDsaVariant::MlDsa44 => {
525                let (pk, sk) = ml_dsa_44::try_keygen_with_rng(&mut OsRng)
526                    .map_err(|e| PqcError::KeyGenerationFailed(e.to_string()))?;
527                Ok((
528                    MlDsaPublicKey {
529                        variant: self.variant,
530                        bytes: pk.into_bytes().to_vec(),
531                    },
532                    MlDsaSecretKey {
533                        variant: self.variant,
534                        bytes: sk.into_bytes().to_vec(),
535                    },
536                ))
537            }
538            MlDsaVariant::MlDsa65 => {
539                let (pk, sk) = ml_dsa_65::try_keygen_with_rng(&mut OsRng)
540                    .map_err(|e| PqcError::KeyGenerationFailed(e.to_string()))?;
541                Ok((
542                    MlDsaPublicKey {
543                        variant: self.variant,
544                        bytes: pk.into_bytes().to_vec(),
545                    },
546                    MlDsaSecretKey {
547                        variant: self.variant,
548                        bytes: sk.into_bytes().to_vec(),
549                    },
550                ))
551            }
552            MlDsaVariant::MlDsa87 => {
553                let (pk, sk) = ml_dsa_87::try_keygen_with_rng(&mut OsRng)
554                    .map_err(|e| PqcError::KeyGenerationFailed(e.to_string()))?;
555                Ok((
556                    MlDsaPublicKey {
557                        variant: self.variant,
558                        bytes: pk.into_bytes().to_vec(),
559                    },
560                    MlDsaSecretKey {
561                        variant: self.variant,
562                        bytes: sk.into_bytes().to_vec(),
563                    },
564                ))
565            }
566        }
567    }
568
569    /// Generate a deterministic key pair from a 32-byte seed
570    ///
571    /// Creates an ML-DSA key pair deterministically from the provided seed value.
572    /// The same seed will always produce the same key pair, as specified by FIPS 204.
573    ///
574    /// # Security
575    ///
576    /// `xi` **must** be a 32-byte value with full entropy (e.g. output of a CSPRNG
577    /// or a key-derivation function such as HKDF/BLAKE3). A predictable or
578    /// low-entropy seed produces a predictable key pair. Treat the seed with the
579    /// same confidentiality as a private key.
580    ///
581    /// Prefer [`generate_keypair`](Self::generate_keypair) for use-cases that do
582    /// not require reproducibility.
583    ///
584    /// # Arguments
585    /// * `xi` - A 32-byte seed value with full entropy
586    ///
587    /// # Returns
588    /// A tuple containing:
589    /// - `MlDsaPublicKey`: The public key for signature verification
590    /// - `MlDsaSecretKey`: The secret key for signing
591    ///
592    /// # Example
593    /// ```rust,no_run
594    /// use saorsa_pqc::api::sig::ml_dsa_65;
595    ///
596    /// // In production, use a CSPRNG or KDF — not a fixed value.
597    /// let dsa = ml_dsa_65();
598    /// let seed = [42u8; 32];
599    /// let (pk1, sk1) = dsa.generate_keypair_from_seed(&seed);
600    /// let (pk2, sk2) = dsa.generate_keypair_from_seed(&seed);
601    /// assert_eq!(pk1.to_bytes(), pk2.to_bytes());
602    /// ```
603    #[must_use]
604    #[allow(clippy::large_stack_frames)]
605    pub fn generate_keypair_from_seed(&self, xi: &[u8; 32]) -> (MlDsaPublicKey, MlDsaSecretKey) {
606        match self.variant {
607            MlDsaVariant::MlDsa44 => {
608                let (pk, sk) = ml_dsa_44::KG::keygen_from_seed(xi);
609                (
610                    MlDsaPublicKey {
611                        variant: self.variant,
612                        bytes: pk.into_bytes().to_vec(),
613                    },
614                    MlDsaSecretKey {
615                        variant: self.variant,
616                        bytes: sk.into_bytes().to_vec(),
617                    },
618                )
619            }
620            MlDsaVariant::MlDsa65 => {
621                let (pk, sk) = ml_dsa_65::KG::keygen_from_seed(xi);
622                (
623                    MlDsaPublicKey {
624                        variant: self.variant,
625                        bytes: pk.into_bytes().to_vec(),
626                    },
627                    MlDsaSecretKey {
628                        variant: self.variant,
629                        bytes: sk.into_bytes().to_vec(),
630                    },
631                )
632            }
633            MlDsaVariant::MlDsa87 => {
634                let (pk, sk) = ml_dsa_87::KG::keygen_from_seed(xi);
635                (
636                    MlDsaPublicKey {
637                        variant: self.variant,
638                        bytes: pk.into_bytes().to_vec(),
639                    },
640                    MlDsaSecretKey {
641                        variant: self.variant,
642                        bytes: sk.into_bytes().to_vec(),
643                    },
644                )
645            }
646        }
647    }
648
649    /// Sign a message
650    ///
651    /// Creates a digital signature for the given message using the secret key.
652    /// The signature includes randomness for enhanced security against side-channel attacks.
653    ///
654    /// # Arguments
655    /// * `secret_key` - The secret signing key
656    /// * `message` - The message to sign (can be any length)
657    ///
658    /// # Returns
659    /// A signature that can be verified with the corresponding public key
660    ///
661    /// # Errors
662    /// - `InvalidInput`: If the secret key variant doesn't match
663    /// - `SigningFailed`: If the signing operation fails
664    ///
665    /// # Example
666    /// ```rust,no_run
667    /// use saorsa_pqc::api::sig::ml_dsa_65;
668    ///
669    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
670    /// let dsa = ml_dsa_65();
671    /// let (public_key, secret_key) = dsa.generate_keypair()?;
672    ///
673    /// // Sign any size message
674    /// let message = b"This message can be any length";
675    /// let signature = dsa.sign(&secret_key, message)?;
676    ///
677    /// // Verify the signature
678    /// assert!(dsa.verify(&public_key, message, &signature)?);
679    /// # Ok(())
680    /// # }
681    /// ```
682    pub fn sign(&self, secret_key: &MlDsaSecretKey, message: &[u8]) -> PqcResult<MlDsaSignature> {
683        self.sign_with_context(secret_key, message, b"")
684    }
685
686    /// Sign a message with context
687    ///
688    /// Creates a signature with an additional context string for domain separation.
689    /// This prevents signatures from being valid across different protocols or applications.
690    ///
691    /// # Arguments
692    /// * `secret_key` - The secret signing key
693    /// * `message` - The message to sign
694    /// * `context` - Domain separation context (max 255 bytes)
695    ///
696    /// # Security Note
697    /// Using context strings is recommended when the same keys are used in multiple
698    /// protocols to prevent cross-protocol signature attacks.
699    ///
700    /// # Errors
701    /// - `InvalidInput`: If the secret key variant doesn't match
702    /// - `ContextTooLong`: If context exceeds 255 bytes
703    /// - `SigningFailed`: If the signing operation fails
704    ///
705    /// # Example
706    /// ```rust,no_run
707    /// use saorsa_pqc::api::sig::ml_dsa_65;
708    ///
709    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
710    /// let dsa = ml_dsa_65();
711    /// let (public_key, secret_key) = dsa.generate_keypair()?;
712    ///
713    /// // Sign with application-specific context
714    /// let invoice = b"Invoice #2024-001";
715    /// let context = b"accounting-system-v3";
716    /// let signature = dsa.sign_with_context(&secret_key, invoice, context)?;
717    ///
718    /// // Must verify with same context
719    /// assert!(dsa.verify_with_context(&public_key, invoice, &signature, context)?);
720    /// # Ok(())
721    /// # }
722    /// ```
723    pub fn sign_with_context(
724        &self,
725        secret_key: &MlDsaSecretKey,
726        message: &[u8],
727        context: &[u8],
728    ) -> PqcResult<MlDsaSignature> {
729        if secret_key.variant != self.variant {
730            return Err(PqcError::InvalidInput(format!(
731                "Key variant {:?} doesn't match DSA variant {:?}",
732                secret_key.variant, self.variant
733            )));
734        }
735
736        if context.len() > MlDsaVariant::MAX_CONTEXT_LENGTH {
737            return Err(PqcError::ContextTooLong {
738                max: MlDsaVariant::MAX_CONTEXT_LENGTH,
739                got: context.len(),
740            });
741        }
742
743        match self.variant {
744            MlDsaVariant::MlDsa44 => {
745                let sk = ml_dsa_44::PrivateKey::try_from_bytes(
746                    secret_key.bytes.as_slice().try_into().map_err(|_| {
747                        PqcError::InvalidKeySize {
748                            expected: self.variant.secret_key_size(),
749                            got: secret_key.bytes.len(),
750                        }
751                    })?,
752                )
753                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
754
755                let sig = sk
756                    .try_sign_with_rng(&mut OsRng, message, context)
757                    .map_err(|e| PqcError::SigningFailed(e.to_string()))?;
758
759                Ok(MlDsaSignature {
760                    variant: self.variant,
761                    bytes: sig.to_vec(),
762                })
763            }
764            MlDsaVariant::MlDsa65 => {
765                let sk = ml_dsa_65::PrivateKey::try_from_bytes(
766                    secret_key.bytes.as_slice().try_into().map_err(|_| {
767                        PqcError::InvalidKeySize {
768                            expected: self.variant.secret_key_size(),
769                            got: secret_key.bytes.len(),
770                        }
771                    })?,
772                )
773                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
774
775                let sig = sk
776                    .try_sign_with_rng(&mut OsRng, message, context)
777                    .map_err(|e| PqcError::SigningFailed(e.to_string()))?;
778
779                Ok(MlDsaSignature {
780                    variant: self.variant,
781                    bytes: sig.to_vec(),
782                })
783            }
784            MlDsaVariant::MlDsa87 => {
785                let sk = ml_dsa_87::PrivateKey::try_from_bytes(
786                    secret_key.bytes.as_slice().try_into().map_err(|_| {
787                        PqcError::InvalidKeySize {
788                            expected: self.variant.secret_key_size(),
789                            got: secret_key.bytes.len(),
790                        }
791                    })?,
792                )
793                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
794
795                let sig = sk
796                    .try_sign_with_rng(&mut OsRng, message, context)
797                    .map_err(|e| PqcError::SigningFailed(e.to_string()))?;
798
799                Ok(MlDsaSignature {
800                    variant: self.variant,
801                    bytes: sig.to_vec(),
802                })
803            }
804        }
805    }
806
807    /// Verify a signature
808    ///
809    /// Verifies that a signature was created by the holder of the secret key
810    /// corresponding to the provided public key.
811    ///
812    /// # Arguments
813    /// * `public_key` - The public verification key
814    /// * `message` - The original message that was signed
815    /// * `signature` - The signature to verify
816    ///
817    /// # Returns
818    /// - `Ok(true)` if the signature is valid
819    /// - `Ok(false)` if the signature is invalid
820    /// - `Err(_)` if verification cannot be performed (wrong key type, etc.)
821    ///
822    /// # Errors
823    ///
824    /// Returns an error if the signature verification process fails due to
825    /// incompatible key types or internal verification errors.
826    ///
827    /// # Example
828    /// ```rust,no_run
829    /// use saorsa_pqc::api::sig::ml_dsa_65;
830    ///
831    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
832    /// let dsa = ml_dsa_65();
833    /// let (public_key, secret_key) = dsa.generate_keypair()?;
834    ///
835    /// let message = b"Authenticate this";
836    /// let signature = dsa.sign(&secret_key, message)?;
837    ///
838    /// // Valid signature
839    /// assert!(dsa.verify(&public_key, message, &signature)?);
840    ///
841    /// // Modified message fails
842    /// let wrong_message = b"Authenticate that";
843    /// assert!(!dsa.verify(&public_key, wrong_message, &signature)?);
844    /// # Ok(())
845    /// # }
846    /// ```
847    pub fn verify(
848        &self,
849        public_key: &MlDsaPublicKey,
850        message: &[u8],
851        signature: &MlDsaSignature,
852    ) -> PqcResult<bool> {
853        self.verify_with_context(public_key, message, signature, b"")
854    }
855
856    /// Verify a signature with context
857    ///
858    /// Verifies a signature that was created with a context string.
859    /// The same context must be provided for successful verification.
860    ///
861    /// # Arguments
862    /// * `public_key` - The public verification key
863    /// * `message` - The original message
864    /// * `signature` - The signature to verify
865    /// * `context` - The context string used during signing
866    ///
867    /// # Returns
868    /// - `Ok(true)` if the signature is valid with the given context
869    /// - `Ok(false)` if the signature is invalid or context doesn't match
870    /// - `Err(_)` if verification cannot be performed
871    ///
872    /// # Errors
873    ///
874    /// Returns an error if the signature verification process fails due to
875    /// incompatible key types, invalid context, or internal verification errors.
876    ///
877    /// # Example
878    /// ```rust,no_run
879    /// use saorsa_pqc::api::sig::ml_dsa_65;
880    ///
881    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
882    /// let dsa = ml_dsa_65();
883    /// let (public_key, secret_key) = dsa.generate_keypair()?;
884    ///
885    /// let message = b"Protocol message";
886    /// let context = b"protocol-v1";
887    /// let signature = dsa.sign_with_context(&secret_key, message, context)?;
888    ///
889    /// // Correct context succeeds
890    /// assert!(dsa.verify_with_context(&public_key, message, &signature, context)?);
891    ///
892    /// // Wrong context fails
893    /// let wrong_context = b"protocol-v2";
894    /// assert!(!dsa.verify_with_context(&public_key, message, &signature, wrong_context)?);
895    /// # Ok(())
896    /// # }
897    /// ```
898    pub fn verify_with_context(
899        &self,
900        public_key: &MlDsaPublicKey,
901        message: &[u8],
902        signature: &MlDsaSignature,
903        context: &[u8],
904    ) -> PqcResult<bool> {
905        if public_key.variant != self.variant {
906            return Err(PqcError::InvalidInput(format!(
907                "Key variant {:?} doesn't match DSA variant {:?}",
908                public_key.variant, self.variant
909            )));
910        }
911
912        if signature.variant != self.variant {
913            return Err(PqcError::InvalidInput(format!(
914                "Signature variant {:?} doesn't match DSA variant {:?}",
915                signature.variant, self.variant
916            )));
917        }
918
919        if context.len() > MlDsaVariant::MAX_CONTEXT_LENGTH {
920            return Err(PqcError::ContextTooLong {
921                max: MlDsaVariant::MAX_CONTEXT_LENGTH,
922                got: context.len(),
923            });
924        }
925
926        match self.variant {
927            MlDsaVariant::MlDsa44 => {
928                let pk = ml_dsa_44::PublicKey::try_from_bytes(
929                    public_key.bytes.as_slice().try_into().map_err(|_| {
930                        PqcError::InvalidKeySize {
931                            expected: self.variant.public_key_size(),
932                            got: public_key.bytes.len(),
933                        }
934                    })?,
935                )
936                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
937
938                let sig_array: [u8; 2420] =
939                    signature.bytes.as_slice().try_into().map_err(|_| {
940                        PqcError::InvalidSignatureSize {
941                            expected: self.variant.signature_size(),
942                            got: signature.bytes.len(),
943                        }
944                    })?;
945
946                Ok(pk.verify(message, &sig_array, context))
947            }
948            MlDsaVariant::MlDsa65 => {
949                let pk = ml_dsa_65::PublicKey::try_from_bytes(
950                    public_key.bytes.as_slice().try_into().map_err(|_| {
951                        PqcError::InvalidKeySize {
952                            expected: self.variant.public_key_size(),
953                            got: public_key.bytes.len(),
954                        }
955                    })?,
956                )
957                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
958
959                let sig_array: [u8; 3309] =
960                    signature.bytes.as_slice().try_into().map_err(|_| {
961                        PqcError::InvalidSignatureSize {
962                            expected: self.variant.signature_size(),
963                            got: signature.bytes.len(),
964                        }
965                    })?;
966
967                Ok(pk.verify(message, &sig_array, context))
968            }
969            MlDsaVariant::MlDsa87 => {
970                let pk = ml_dsa_87::PublicKey::try_from_bytes(
971                    public_key.bytes.as_slice().try_into().map_err(|_| {
972                        PqcError::InvalidKeySize {
973                            expected: self.variant.public_key_size(),
974                            got: public_key.bytes.len(),
975                        }
976                    })?,
977                )
978                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
979
980                let sig_array: [u8; 4627] =
981                    signature.bytes.as_slice().try_into().map_err(|_| {
982                        PqcError::InvalidSignatureSize {
983                            expected: self.variant.signature_size(),
984                            got: signature.bytes.len(),
985                        }
986                    })?;
987
988                Ok(pk.verify(message, &sig_array, context))
989            }
990        }
991    }
992}
993
994/// Convenience function to create ML-DSA-65 (recommended default)
995///
996/// ML-DSA-65 provides NIST Level 3 security (~192-bit classical security),
997/// which is suitable for most applications and offers a good balance
998/// between security and performance.
999///
1000/// # Why ML-DSA-65?
1001/// - Quantum resistance equivalent to 128-bit quantum security
1002/// - Moderate key and signature sizes
1003/// - Good performance on modern hardware
1004/// - Recommended by NIST for general use
1005///
1006/// # Example
1007/// ```rust,no_run
1008/// use saorsa_pqc::api::sig::ml_dsa_65;
1009///
1010/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1011/// // Quick setup with recommended parameters
1012/// let dsa = ml_dsa_65();
1013///
1014/// // Use just like any MlDsa instance
1015/// let (public_key, secret_key) = dsa.generate_keypair()?;
1016/// let message = b"Sign this";
1017/// let signature = dsa.sign(&secret_key, message)?;
1018/// assert!(dsa.verify(&public_key, message, &signature)?);
1019/// # Ok(())
1020/// # }
1021/// ```
1022///
1023/// For other security levels, use:
1024/// - `MlDsa::new(MlDsaVariant::MlDsa44)` for NIST Level 2 (128-bit classical)
1025/// - `MlDsa::new(MlDsaVariant::MlDsa87)` for NIST Level 5 (256-bit classical)
1026#[must_use]
1027pub const fn ml_dsa_65() -> MlDsa {
1028    MlDsa::new(MlDsaVariant::MlDsa65)
1029}
1030
1031/// Convenience function to create ML-DSA-44 (lightweight option)
1032///
1033/// ML-DSA-44 provides NIST Level 2 security (~128-bit classical security),
1034/// suitable for applications with strict size or performance constraints.
1035///
1036/// # Example
1037/// ```rust,no_run
1038/// use saorsa_pqc::api::sig::ml_dsa_44;
1039///
1040/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1041/// let dsa = ml_dsa_44();
1042/// let (public_key, secret_key) = dsa.generate_keypair()?;
1043/// # Ok(())
1044/// # }
1045/// ```
1046#[must_use]
1047pub const fn ml_dsa_44() -> MlDsa {
1048    MlDsa::new(MlDsaVariant::MlDsa44)
1049}
1050
1051/// Convenience function to create ML-DSA-87 (maximum security)
1052///
1053/// ML-DSA-87 provides NIST Level 5 security (~256-bit classical security),
1054/// suitable for applications requiring the highest level of security.
1055///
1056/// # Example
1057/// ```rust,no_run
1058/// use saorsa_pqc::api::sig::ml_dsa_87;
1059///
1060/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1061/// let dsa = ml_dsa_87();
1062/// let (public_key, secret_key) = dsa.generate_keypair()?;
1063/// # Ok(())
1064/// # }
1065/// ```
1066#[must_use]
1067pub const fn ml_dsa_87() -> MlDsa {
1068    MlDsa::new(MlDsaVariant::MlDsa87)
1069}
1070
1071#[cfg(test)]
1072#[allow(clippy::indexing_slicing)]
1073#[allow(clippy::unwrap_used, clippy::expect_used)]
1074mod tests {
1075    use super::*;
1076
1077    #[test]
1078    fn test_ml_dsa_65_sign_verify() {
1079        let dsa = ml_dsa_65();
1080        let (pk, sk) = dsa.generate_keypair().unwrap();
1081
1082        let message = b"Test message";
1083        let sig = dsa.sign(&sk, message).unwrap();
1084
1085        assert!(dsa.verify(&pk, message, &sig).unwrap());
1086
1087        // Wrong message should fail
1088        assert!(!dsa.verify(&pk, b"Wrong message", &sig).unwrap());
1089    }
1090
1091    #[test]
1092    fn test_all_variants() {
1093        for variant in [
1094            MlDsaVariant::MlDsa44,
1095            MlDsaVariant::MlDsa65,
1096            MlDsaVariant::MlDsa87,
1097        ] {
1098            let dsa = MlDsa::new(variant);
1099            let (pk, sk) = dsa.generate_keypair().unwrap();
1100
1101            let message = b"Test message for all variants";
1102            let sig = dsa.sign(&sk, message).unwrap();
1103
1104            assert!(dsa.verify(&pk, message, &sig).unwrap());
1105        }
1106    }
1107
1108    #[test]
1109    fn test_with_context() {
1110        let dsa = ml_dsa_65();
1111        let (pk, sk) = dsa.generate_keypair().unwrap();
1112
1113        let message = b"Test message";
1114        let context = b"test context";
1115        let sig = dsa.sign_with_context(&sk, message, context).unwrap();
1116
1117        // Correct context verifies
1118        assert!(dsa
1119            .verify_with_context(&pk, message, &sig, context)
1120            .unwrap());
1121
1122        // Wrong context fails
1123        assert!(!dsa
1124            .verify_with_context(&pk, message, &sig, b"wrong context")
1125            .unwrap());
1126    }
1127
1128    #[test]
1129    fn test_serialization() {
1130        let dsa = ml_dsa_65();
1131        let (pk, sk) = dsa.generate_keypair().unwrap();
1132
1133        // Serialize and deserialize keys
1134        let pk_bytes = pk.to_bytes();
1135        let sk_bytes = sk.to_bytes();
1136
1137        let pk2 = MlDsaPublicKey::from_bytes(MlDsaVariant::MlDsa65, &pk_bytes).unwrap();
1138        let sk2 = MlDsaSecretKey::from_bytes(MlDsaVariant::MlDsa65, &sk_bytes).unwrap();
1139
1140        // Use deserialized keys
1141        let message = b"Test";
1142        let sig = dsa.sign(&sk2, message).unwrap();
1143        assert!(dsa.verify(&pk2, message, &sig).unwrap());
1144    }
1145
1146    #[test]
1147    fn test_seeded_keygen_deterministic() {
1148        let seed = [42u8; 32];
1149        for variant in [
1150            MlDsaVariant::MlDsa44,
1151            MlDsaVariant::MlDsa65,
1152            MlDsaVariant::MlDsa87,
1153        ] {
1154            let dsa = MlDsa::new(variant);
1155            let (pk1, sk1) = dsa.generate_keypair_from_seed(&seed);
1156            let (pk2, sk2) = dsa.generate_keypair_from_seed(&seed);
1157
1158            assert_eq!(
1159                pk1.to_bytes(),
1160                pk2.to_bytes(),
1161                "Public keys must match for {:?}",
1162                variant
1163            );
1164            assert_eq!(
1165                sk1.to_bytes(),
1166                sk2.to_bytes(),
1167                "Secret keys must match for {:?}",
1168                variant
1169            );
1170
1171            // Signing with seeded key should verify
1172            let message = b"Deterministic test";
1173            let sig = dsa.sign(&sk1, message).unwrap();
1174            assert!(dsa.verify(&pk1, message, &sig).unwrap());
1175
1176            // Serialization roundtrip for seeded keys
1177            let pk_restored = MlDsaPublicKey::from_bytes(variant, &pk1.to_bytes()).unwrap();
1178            let sk_restored = MlDsaSecretKey::from_bytes(variant, &sk1.to_bytes()).unwrap();
1179            let sig2 = dsa.sign(&sk_restored, message).unwrap();
1180            assert!(dsa.verify(&pk_restored, message, &sig2).unwrap());
1181        }
1182    }
1183
1184    #[test]
1185    fn test_seeded_keygen_different_seeds() {
1186        let dsa = ml_dsa_65();
1187        let (pk1, _) = dsa.generate_keypair_from_seed(&[1u8; 32]);
1188        let (pk2, _) = dsa.generate_keypair_from_seed(&[2u8; 32]);
1189        assert_ne!(
1190            pk1.to_bytes(),
1191            pk2.to_bytes(),
1192            "Different seeds must produce different keys"
1193        );
1194    }
1195
1196    #[test]
1197    fn test_context_too_long() {
1198        let dsa = ml_dsa_65();
1199        let (_, sk) = dsa.generate_keypair().unwrap();
1200
1201        let message = b"Test";
1202        let long_context = vec![0u8; 256]; // Too long
1203
1204        let result = dsa.sign_with_context(&sk, message, &long_context);
1205        assert!(matches!(result, Err(PqcError::ContextTooLong { .. })));
1206    }
1207}