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::{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    /// Sign a message
570    ///
571    /// Creates a digital signature for the given message using the secret key.
572    /// The signature includes randomness for enhanced security against side-channel attacks.
573    ///
574    /// # Arguments
575    /// * `secret_key` - The secret signing key
576    /// * `message` - The message to sign (can be any length)
577    ///
578    /// # Returns
579    /// A signature that can be verified with the corresponding public key
580    ///
581    /// # Errors
582    /// - `InvalidInput`: If the secret key variant doesn't match
583    /// - `SigningFailed`: If the signing operation fails
584    ///
585    /// # Example
586    /// ```rust,no_run
587    /// use saorsa_pqc::api::sig::ml_dsa_65;
588    ///
589    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
590    /// let dsa = ml_dsa_65();
591    /// let (public_key, secret_key) = dsa.generate_keypair()?;
592    ///
593    /// // Sign any size message
594    /// let message = b"This message can be any length";
595    /// let signature = dsa.sign(&secret_key, message)?;
596    ///
597    /// // Verify the signature
598    /// assert!(dsa.verify(&public_key, message, &signature)?);
599    /// # Ok(())
600    /// # }
601    /// ```
602    pub fn sign(&self, secret_key: &MlDsaSecretKey, message: &[u8]) -> PqcResult<MlDsaSignature> {
603        self.sign_with_context(secret_key, message, b"")
604    }
605
606    /// Sign a message with context
607    ///
608    /// Creates a signature with an additional context string for domain separation.
609    /// This prevents signatures from being valid across different protocols or applications.
610    ///
611    /// # Arguments
612    /// * `secret_key` - The secret signing key
613    /// * `message` - The message to sign
614    /// * `context` - Domain separation context (max 255 bytes)
615    ///
616    /// # Security Note
617    /// Using context strings is recommended when the same keys are used in multiple
618    /// protocols to prevent cross-protocol signature attacks.
619    ///
620    /// # Errors
621    /// - `InvalidInput`: If the secret key variant doesn't match
622    /// - `ContextTooLong`: If context exceeds 255 bytes
623    /// - `SigningFailed`: If the signing operation fails
624    ///
625    /// # Example
626    /// ```rust,no_run
627    /// use saorsa_pqc::api::sig::ml_dsa_65;
628    ///
629    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
630    /// let dsa = ml_dsa_65();
631    /// let (public_key, secret_key) = dsa.generate_keypair()?;
632    ///
633    /// // Sign with application-specific context
634    /// let invoice = b"Invoice #2024-001";
635    /// let context = b"accounting-system-v3";
636    /// let signature = dsa.sign_with_context(&secret_key, invoice, context)?;
637    ///
638    /// // Must verify with same context
639    /// assert!(dsa.verify_with_context(&public_key, invoice, &signature, context)?);
640    /// # Ok(())
641    /// # }
642    /// ```
643    pub fn sign_with_context(
644        &self,
645        secret_key: &MlDsaSecretKey,
646        message: &[u8],
647        context: &[u8],
648    ) -> PqcResult<MlDsaSignature> {
649        if secret_key.variant != self.variant {
650            return Err(PqcError::InvalidInput(format!(
651                "Key variant {:?} doesn't match DSA variant {:?}",
652                secret_key.variant, self.variant
653            )));
654        }
655
656        if context.len() > MlDsaVariant::MAX_CONTEXT_LENGTH {
657            return Err(PqcError::ContextTooLong {
658                max: MlDsaVariant::MAX_CONTEXT_LENGTH,
659                got: context.len(),
660            });
661        }
662
663        match self.variant {
664            MlDsaVariant::MlDsa44 => {
665                let sk = ml_dsa_44::PrivateKey::try_from_bytes(
666                    secret_key.bytes.as_slice().try_into().map_err(|_| {
667                        PqcError::InvalidKeySize {
668                            expected: self.variant.secret_key_size(),
669                            got: secret_key.bytes.len(),
670                        }
671                    })?,
672                )
673                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
674
675                let sig = sk
676                    .try_sign_with_rng(&mut OsRng, message, context)
677                    .map_err(|e| PqcError::SigningFailed(e.to_string()))?;
678
679                Ok(MlDsaSignature {
680                    variant: self.variant,
681                    bytes: sig.to_vec(),
682                })
683            }
684            MlDsaVariant::MlDsa65 => {
685                let sk = ml_dsa_65::PrivateKey::try_from_bytes(
686                    secret_key.bytes.as_slice().try_into().map_err(|_| {
687                        PqcError::InvalidKeySize {
688                            expected: self.variant.secret_key_size(),
689                            got: secret_key.bytes.len(),
690                        }
691                    })?,
692                )
693                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
694
695                let sig = sk
696                    .try_sign_with_rng(&mut OsRng, message, context)
697                    .map_err(|e| PqcError::SigningFailed(e.to_string()))?;
698
699                Ok(MlDsaSignature {
700                    variant: self.variant,
701                    bytes: sig.to_vec(),
702                })
703            }
704            MlDsaVariant::MlDsa87 => {
705                let sk = ml_dsa_87::PrivateKey::try_from_bytes(
706                    secret_key.bytes.as_slice().try_into().map_err(|_| {
707                        PqcError::InvalidKeySize {
708                            expected: self.variant.secret_key_size(),
709                            got: secret_key.bytes.len(),
710                        }
711                    })?,
712                )
713                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
714
715                let sig = sk
716                    .try_sign_with_rng(&mut OsRng, message, context)
717                    .map_err(|e| PqcError::SigningFailed(e.to_string()))?;
718
719                Ok(MlDsaSignature {
720                    variant: self.variant,
721                    bytes: sig.to_vec(),
722                })
723            }
724        }
725    }
726
727    /// Verify a signature
728    ///
729    /// Verifies that a signature was created by the holder of the secret key
730    /// corresponding to the provided public key.
731    ///
732    /// # Arguments
733    /// * `public_key` - The public verification key
734    /// * `message` - The original message that was signed
735    /// * `signature` - The signature to verify
736    ///
737    /// # Returns
738    /// - `Ok(true)` if the signature is valid
739    /// - `Ok(false)` if the signature is invalid
740    /// - `Err(_)` if verification cannot be performed (wrong key type, etc.)
741    ///
742    /// # Errors
743    ///
744    /// Returns an error if the signature verification process fails due to
745    /// incompatible key types or internal verification errors.
746    ///
747    /// # Example
748    /// ```rust,no_run
749    /// use saorsa_pqc::api::sig::ml_dsa_65;
750    ///
751    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
752    /// let dsa = ml_dsa_65();
753    /// let (public_key, secret_key) = dsa.generate_keypair()?;
754    ///
755    /// let message = b"Authenticate this";
756    /// let signature = dsa.sign(&secret_key, message)?;
757    ///
758    /// // Valid signature
759    /// assert!(dsa.verify(&public_key, message, &signature)?);
760    ///
761    /// // Modified message fails
762    /// let wrong_message = b"Authenticate that";
763    /// assert!(!dsa.verify(&public_key, wrong_message, &signature)?);
764    /// # Ok(())
765    /// # }
766    /// ```
767    pub fn verify(
768        &self,
769        public_key: &MlDsaPublicKey,
770        message: &[u8],
771        signature: &MlDsaSignature,
772    ) -> PqcResult<bool> {
773        self.verify_with_context(public_key, message, signature, b"")
774    }
775
776    /// Verify a signature with context
777    ///
778    /// Verifies a signature that was created with a context string.
779    /// The same context must be provided for successful verification.
780    ///
781    /// # Arguments
782    /// * `public_key` - The public verification key
783    /// * `message` - The original message
784    /// * `signature` - The signature to verify
785    /// * `context` - The context string used during signing
786    ///
787    /// # Returns
788    /// - `Ok(true)` if the signature is valid with the given context
789    /// - `Ok(false)` if the signature is invalid or context doesn't match
790    /// - `Err(_)` if verification cannot be performed
791    ///
792    /// # Errors
793    ///
794    /// Returns an error if the signature verification process fails due to
795    /// incompatible key types, invalid context, or internal verification errors.
796    ///
797    /// # Example
798    /// ```rust,no_run
799    /// use saorsa_pqc::api::sig::ml_dsa_65;
800    ///
801    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
802    /// let dsa = ml_dsa_65();
803    /// let (public_key, secret_key) = dsa.generate_keypair()?;
804    ///
805    /// let message = b"Protocol message";
806    /// let context = b"protocol-v1";
807    /// let signature = dsa.sign_with_context(&secret_key, message, context)?;
808    ///
809    /// // Correct context succeeds
810    /// assert!(dsa.verify_with_context(&public_key, message, &signature, context)?);
811    ///
812    /// // Wrong context fails
813    /// let wrong_context = b"protocol-v2";
814    /// assert!(!dsa.verify_with_context(&public_key, message, &signature, wrong_context)?);
815    /// # Ok(())
816    /// # }
817    /// ```
818    pub fn verify_with_context(
819        &self,
820        public_key: &MlDsaPublicKey,
821        message: &[u8],
822        signature: &MlDsaSignature,
823        context: &[u8],
824    ) -> PqcResult<bool> {
825        if public_key.variant != self.variant {
826            return Err(PqcError::InvalidInput(format!(
827                "Key variant {:?} doesn't match DSA variant {:?}",
828                public_key.variant, self.variant
829            )));
830        }
831
832        if signature.variant != self.variant {
833            return Err(PqcError::InvalidInput(format!(
834                "Signature variant {:?} doesn't match DSA variant {:?}",
835                signature.variant, self.variant
836            )));
837        }
838
839        if context.len() > MlDsaVariant::MAX_CONTEXT_LENGTH {
840            return Err(PqcError::ContextTooLong {
841                max: MlDsaVariant::MAX_CONTEXT_LENGTH,
842                got: context.len(),
843            });
844        }
845
846        match self.variant {
847            MlDsaVariant::MlDsa44 => {
848                let pk = ml_dsa_44::PublicKey::try_from_bytes(
849                    public_key.bytes.as_slice().try_into().map_err(|_| {
850                        PqcError::InvalidKeySize {
851                            expected: self.variant.public_key_size(),
852                            got: public_key.bytes.len(),
853                        }
854                    })?,
855                )
856                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
857
858                let sig_array: [u8; 2420] =
859                    signature.bytes.as_slice().try_into().map_err(|_| {
860                        PqcError::InvalidSignatureSize {
861                            expected: self.variant.signature_size(),
862                            got: signature.bytes.len(),
863                        }
864                    })?;
865
866                Ok(pk.verify(message, &sig_array, context))
867            }
868            MlDsaVariant::MlDsa65 => {
869                let pk = ml_dsa_65::PublicKey::try_from_bytes(
870                    public_key.bytes.as_slice().try_into().map_err(|_| {
871                        PqcError::InvalidKeySize {
872                            expected: self.variant.public_key_size(),
873                            got: public_key.bytes.len(),
874                        }
875                    })?,
876                )
877                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
878
879                let sig_array: [u8; 3309] =
880                    signature.bytes.as_slice().try_into().map_err(|_| {
881                        PqcError::InvalidSignatureSize {
882                            expected: self.variant.signature_size(),
883                            got: signature.bytes.len(),
884                        }
885                    })?;
886
887                Ok(pk.verify(message, &sig_array, context))
888            }
889            MlDsaVariant::MlDsa87 => {
890                let pk = ml_dsa_87::PublicKey::try_from_bytes(
891                    public_key.bytes.as_slice().try_into().map_err(|_| {
892                        PqcError::InvalidKeySize {
893                            expected: self.variant.public_key_size(),
894                            got: public_key.bytes.len(),
895                        }
896                    })?,
897                )
898                .map_err(|e| PqcError::SerializationError(e.to_string()))?;
899
900                let sig_array: [u8; 4627] =
901                    signature.bytes.as_slice().try_into().map_err(|_| {
902                        PqcError::InvalidSignatureSize {
903                            expected: self.variant.signature_size(),
904                            got: signature.bytes.len(),
905                        }
906                    })?;
907
908                Ok(pk.verify(message, &sig_array, context))
909            }
910        }
911    }
912}
913
914/// Convenience function to create ML-DSA-65 (recommended default)
915///
916/// ML-DSA-65 provides NIST Level 3 security (~192-bit classical security),
917/// which is suitable for most applications and offers a good balance
918/// between security and performance.
919///
920/// # Why ML-DSA-65?
921/// - Quantum resistance equivalent to 128-bit quantum security
922/// - Moderate key and signature sizes
923/// - Good performance on modern hardware
924/// - Recommended by NIST for general use
925///
926/// # Example
927/// ```rust,no_run
928/// use saorsa_pqc::api::sig::ml_dsa_65;
929///
930/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
931/// // Quick setup with recommended parameters
932/// let dsa = ml_dsa_65();
933///
934/// // Use just like any MlDsa instance
935/// let (public_key, secret_key) = dsa.generate_keypair()?;
936/// let message = b"Sign this";
937/// let signature = dsa.sign(&secret_key, message)?;
938/// assert!(dsa.verify(&public_key, message, &signature)?);
939/// # Ok(())
940/// # }
941/// ```
942///
943/// For other security levels, use:
944/// - `MlDsa::new(MlDsaVariant::MlDsa44)` for NIST Level 2 (128-bit classical)
945/// - `MlDsa::new(MlDsaVariant::MlDsa87)` for NIST Level 5 (256-bit classical)
946#[must_use]
947pub const fn ml_dsa_65() -> MlDsa {
948    MlDsa::new(MlDsaVariant::MlDsa65)
949}
950
951/// Convenience function to create ML-DSA-44 (lightweight option)
952///
953/// ML-DSA-44 provides NIST Level 2 security (~128-bit classical security),
954/// suitable for applications with strict size or performance constraints.
955///
956/// # Example
957/// ```rust,no_run
958/// use saorsa_pqc::api::sig::ml_dsa_44;
959///
960/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
961/// let dsa = ml_dsa_44();
962/// let (public_key, secret_key) = dsa.generate_keypair()?;
963/// # Ok(())
964/// # }
965/// ```
966#[must_use]
967pub const fn ml_dsa_44() -> MlDsa {
968    MlDsa::new(MlDsaVariant::MlDsa44)
969}
970
971/// Convenience function to create ML-DSA-87 (maximum security)
972///
973/// ML-DSA-87 provides NIST Level 5 security (~256-bit classical security),
974/// suitable for applications requiring the highest level of security.
975///
976/// # Example
977/// ```rust,no_run
978/// use saorsa_pqc::api::sig::ml_dsa_87;
979///
980/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
981/// let dsa = ml_dsa_87();
982/// let (public_key, secret_key) = dsa.generate_keypair()?;
983/// # Ok(())
984/// # }
985/// ```
986#[must_use]
987pub const fn ml_dsa_87() -> MlDsa {
988    MlDsa::new(MlDsaVariant::MlDsa87)
989}
990
991#[cfg(test)]
992#[allow(clippy::indexing_slicing)]
993#[allow(clippy::unwrap_used, clippy::expect_used)]
994mod tests {
995    use super::*;
996
997    #[test]
998    fn test_ml_dsa_65_sign_verify() {
999        let dsa = ml_dsa_65();
1000        let (pk, sk) = dsa.generate_keypair().unwrap();
1001
1002        let message = b"Test message";
1003        let sig = dsa.sign(&sk, message).unwrap();
1004
1005        assert!(dsa.verify(&pk, message, &sig).unwrap());
1006
1007        // Wrong message should fail
1008        assert!(!dsa.verify(&pk, b"Wrong message", &sig).unwrap());
1009    }
1010
1011    #[test]
1012    fn test_all_variants() {
1013        for variant in [
1014            MlDsaVariant::MlDsa44,
1015            MlDsaVariant::MlDsa65,
1016            MlDsaVariant::MlDsa87,
1017        ] {
1018            let dsa = MlDsa::new(variant);
1019            let (pk, sk) = dsa.generate_keypair().unwrap();
1020
1021            let message = b"Test message for all variants";
1022            let sig = dsa.sign(&sk, message).unwrap();
1023
1024            assert!(dsa.verify(&pk, message, &sig).unwrap());
1025        }
1026    }
1027
1028    #[test]
1029    fn test_with_context() {
1030        let dsa = ml_dsa_65();
1031        let (pk, sk) = dsa.generate_keypair().unwrap();
1032
1033        let message = b"Test message";
1034        let context = b"test context";
1035        let sig = dsa.sign_with_context(&sk, message, context).unwrap();
1036
1037        // Correct context verifies
1038        assert!(dsa
1039            .verify_with_context(&pk, message, &sig, context)
1040            .unwrap());
1041
1042        // Wrong context fails
1043        assert!(!dsa
1044            .verify_with_context(&pk, message, &sig, b"wrong context")
1045            .unwrap());
1046    }
1047
1048    #[test]
1049    fn test_serialization() {
1050        let dsa = ml_dsa_65();
1051        let (pk, sk) = dsa.generate_keypair().unwrap();
1052
1053        // Serialize and deserialize keys
1054        let pk_bytes = pk.to_bytes();
1055        let sk_bytes = sk.to_bytes();
1056
1057        let pk2 = MlDsaPublicKey::from_bytes(MlDsaVariant::MlDsa65, &pk_bytes).unwrap();
1058        let sk2 = MlDsaSecretKey::from_bytes(MlDsaVariant::MlDsa65, &sk_bytes).unwrap();
1059
1060        // Use deserialized keys
1061        let message = b"Test";
1062        let sig = dsa.sign(&sk2, message).unwrap();
1063        assert!(dsa.verify(&pk2, message, &sig).unwrap());
1064    }
1065
1066    #[test]
1067    fn test_context_too_long() {
1068        let dsa = ml_dsa_65();
1069        let (_, sk) = dsa.generate_keypair().unwrap();
1070
1071        let message = b"Test";
1072        let long_context = vec![0u8; 256]; // Too long
1073
1074        let result = dsa.sign_with_context(&sk, message, &long_context);
1075        assert!(matches!(result, Err(PqcError::ContextTooLong { .. })));
1076    }
1077}