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}