Skip to main content

lib_q_sig/
provider.rs

1//! lib-Q Signature Provider Implementation
2//!
3//! This module provides the `LibQSignatureProvider` that implements the `SignatureOperations`
4//! trait and routes signature operations to the appropriate algorithm implementations
5//! with comprehensive security validation.
6//!
7//! ## Architecture
8//!
9//! The `LibQSignatureProvider` serves as the central routing hub for all signature operations:
10//! - **Algorithm Routing**: Routes operations to the correct algorithm implementation
11//! - **Security Validation**: Validates all inputs using `SecurityValidator`
12//! - **Feature Flag Handling**: Gracefully handles missing feature flags
13//! - **Provider Integration**: Implements `CryptoProvider` for lib-q-core integration
14//!
15//! ## Security Features
16//!
17//! - **Input Validation**: All inputs are validated before processing
18//! - **Algorithm Category Validation**: Ensures only signature algorithms are processed
19//! - **Key Size Validation**: Validates key sizes against algorithm requirements
20//! - **Randomness Validation**: Validates randomness quality and size
21//! - **Message Validation**: Validates message content and size
22//! - **Signature Validation**: Validates signature format and size
23//!
24//! ## Supported Operations
25//!
26//! - **Key Generation**: Generates keypairs for all supported algorithms
27//! - **Signing**: Creates signatures with proper randomness handling
28//! - **Verification**: Verifies signatures with comprehensive validation
29//!
30//! ## Algorithm Support
31//!
32//! The provider supports three post-quantum signature families, not all of which are
33//! NIST-approved yet:
34//! - ML-DSA (CRYSTALS-ML-DSA): Levels 1, 3, 4 — **NIST-approved**, published as FIPS 204.
35//! - FN-DSA (Falcon): Levels 1, 5 — **NIST-selected** for standardization; FIPS 206 has **not
36//!   been published**, so there is no finalized standard to be compliant with yet.
37//! - SLH-DSA (SPHINCS+): Levels 1, 3, 5 — **NIST-approved**, published as FIPS 205.
38
39#[cfg(feature = "alloc")]
40extern crate alloc;
41#[cfg(all(
42    feature = "alloc",
43    not(feature = "std"),
44    any(
45        not(feature = "ml-dsa"),
46        not(feature = "fn-dsa"),
47        not(feature = "slh-dsa"),
48    ),
49))]
50use alloc::string::ToString;
51#[cfg(feature = "alloc")]
52use alloc::vec::Vec;
53
54#[cfg(feature = "alloc")]
55use lib_q_core::api::{
56    Algorithm,
57    CryptoProvider,
58    SignatureOperations,
59};
60#[cfg(feature = "alloc")]
61use lib_q_core::error::{
62    Error,
63    Result,
64};
65#[cfg(feature = "alloc")]
66use lib_q_core::security::SecurityValidator;
67#[cfg(all(feature = "alloc", any(feature = "ml-dsa", feature = "fn-dsa")))]
68use lib_q_core::traits::Signature;
69#[cfg(feature = "alloc")]
70use lib_q_core::traits::{
71    SigKeypair,
72    SigPublicKey,
73    SigSecretKey,
74};
75
76#[cfg(feature = "fn-dsa")]
77use crate::fn_dsa::{
78    FnDsa,
79    FnDsa512,
80    FnDsa1024,
81};
82// Import algorithm implementations
83#[cfg(feature = "ml-dsa")]
84use crate::ml_dsa::MlDsa;
85#[cfg(feature = "slh-dsa")]
86use crate::slh_dsa::SlhDsa;
87
88/// lib-Q signature provider implementation
89///
90/// This provider implements signature operations for lib-Q, including key generation,
91/// signing, and verification with proper security validation and algorithm routing.
92#[cfg(feature = "alloc")]
93#[derive(Clone)]
94pub struct LibQSignatureProvider {
95    security_validator: SecurityValidator,
96}
97
98#[cfg(feature = "alloc")]
99impl LibQSignatureProvider {
100    /// Create a new signature provider
101    ///
102    /// # Returns
103    ///
104    /// A new instance of LibQSignatureProvider with security validation initialized.
105    ///
106    /// # Errors
107    ///
108    /// Returns an error if the security validator fails to initialize.
109    pub fn new() -> Result<Self> {
110        Ok(Self {
111            security_validator: SecurityValidator::new()?,
112        })
113    }
114
115    /// Get the security validator
116    pub fn security_validator(&self) -> &SecurityValidator {
117        &self.security_validator
118    }
119}
120
121#[cfg(feature = "alloc")]
122impl SignatureOperations for LibQSignatureProvider {
123    fn generate_keypair(
124        &self,
125        algorithm: Algorithm,
126        randomness: Option<&[u8]>,
127    ) -> Result<SigKeypair> {
128        // Validate algorithm category
129        self.security_validator.validate_algorithm_category(
130            algorithm,
131            lib_q_core::api::AlgorithmCategory::Signature,
132        )?;
133
134        // Validate randomness if provided
135        if let Some(rng) = randomness {
136            self.security_validator.validate_randomness(rng)?;
137        }
138
139        // Route to specific algorithm implementation
140        match algorithm {
141            // ML-DSA algorithms
142            #[cfg(feature = "ml-dsa")]
143            Algorithm::MlDsa44 => {
144                let ml_dsa = MlDsa::ml_dsa_44();
145                if let Some(rng) = randomness {
146                    // Use provided randomness
147                    let rng_array: [u8; 32] =
148                        rng.try_into().map_err(|_| Error::InvalidKeySize {
149                            expected: 32,
150                            actual: rng.len(),
151                        })?;
152                    ml_dsa.generate_keypair_with_randomness(rng_array)
153                } else {
154                    ml_dsa.generate_keypair()
155                }
156            }
157            #[cfg(feature = "ml-dsa")]
158            Algorithm::MlDsa65 => {
159                let ml_dsa = MlDsa::ml_dsa_65();
160                if let Some(rng) = randomness {
161                    let rng_array: [u8; 32] =
162                        rng.try_into().map_err(|_| Error::InvalidKeySize {
163                            expected: 32,
164                            actual: rng.len(),
165                        })?;
166                    ml_dsa.generate_keypair_with_randomness(rng_array)
167                } else {
168                    ml_dsa.generate_keypair()
169                }
170            }
171            #[cfg(feature = "ml-dsa")]
172            Algorithm::MlDsa87 => {
173                let ml_dsa = MlDsa::ml_dsa_87();
174                if let Some(rng) = randomness {
175                    let rng_array: [u8; 32] =
176                        rng.try_into().map_err(|_| Error::InvalidKeySize {
177                            expected: 32,
178                            actual: rng.len(),
179                        })?;
180                    ml_dsa.generate_keypair_with_randomness(rng_array)
181                } else {
182                    ml_dsa.generate_keypair()
183                }
184            }
185
186            // FN-DSA algorithms
187            #[cfg(feature = "fn-dsa")]
188            Algorithm::FnDsa => {
189                let fn_dsa = FnDsa::level1();
190                fn_dsa.generate_keypair()
191            }
192            #[cfg(feature = "fn-dsa")]
193            Algorithm::FnDsa512 => {
194                let fn_dsa = FnDsa512::new();
195                fn_dsa.generate_keypair()
196            }
197            #[cfg(feature = "fn-dsa")]
198            Algorithm::FnDsa1024 => {
199                let fn_dsa = FnDsa1024::new();
200                fn_dsa.generate_keypair()
201            }
202
203            // SLH-DSA algorithms
204            #[cfg(feature = "slh-dsa")]
205            Algorithm::SlhDsaSha256128fRobust => {
206                let slh_dsa = SlhDsa::new();
207                slh_dsa.generate_keypair_for_algorithm(algorithm, randomness)
208            }
209            #[cfg(feature = "slh-dsa")]
210            Algorithm::SlhDsaSha256192fRobust => {
211                let slh_dsa = SlhDsa::new();
212                slh_dsa.generate_keypair_for_algorithm(algorithm, randomness)
213            }
214            #[cfg(feature = "slh-dsa")]
215            Algorithm::SlhDsaSha256256fRobust => {
216                let slh_dsa = SlhDsa::new();
217                slh_dsa.generate_keypair_for_algorithm(algorithm, randomness)
218            }
219            #[cfg(feature = "slh-dsa")]
220            Algorithm::SlhDsaShake256128fRobust => {
221                let slh_dsa = SlhDsa::new();
222                slh_dsa.generate_keypair_for_algorithm(algorithm, randomness)
223            }
224            #[cfg(feature = "slh-dsa")]
225            Algorithm::SlhDsaShake256192fRobust => {
226                let slh_dsa = SlhDsa::new();
227                slh_dsa.generate_keypair_for_algorithm(algorithm, randomness)
228            }
229            #[cfg(feature = "slh-dsa")]
230            Algorithm::SlhDsaShake256256fRobust => {
231                let slh_dsa = SlhDsa::new();
232                slh_dsa.generate_keypair_for_algorithm(algorithm, randomness)
233            }
234
235            // Handle missing feature flags
236            #[cfg(not(feature = "ml-dsa"))]
237            Algorithm::MlDsa44 | Algorithm::MlDsa65 | Algorithm::MlDsa87 => {
238                Err(Error::NotImplemented {
239                    feature: "ML-DSA implementations require 'ml-dsa' feature flag".to_string(),
240                })
241            }
242            #[cfg(not(feature = "fn-dsa"))]
243            Algorithm::FnDsa | Algorithm::FnDsa512 | Algorithm::FnDsa1024 => {
244                Err(Error::NotImplemented {
245                    feature: "FN-DSA implementations require 'fn-dsa' feature flag".to_string(),
246                })
247            }
248            #[cfg(not(feature = "slh-dsa"))]
249            Algorithm::SlhDsaSha256128fRobust |
250            Algorithm::SlhDsaSha256192fRobust |
251            Algorithm::SlhDsaSha256256fRobust |
252            Algorithm::SlhDsaShake256128fRobust |
253            Algorithm::SlhDsaShake256192fRobust |
254            Algorithm::SlhDsaShake256256fRobust => Err(Error::NotImplemented {
255                feature: "SLH-DSA implementations require 'slh-dsa' feature flag".to_string(),
256            }),
257
258            _ => Err(Error::InvalidAlgorithm {
259                algorithm: "Algorithm not supported for signature operations",
260            }),
261        }
262    }
263
264    fn sign(
265        &self,
266        algorithm: Algorithm,
267        secret_key: &SigSecretKey,
268        message: &[u8],
269        randomness: Option<&[u8]>,
270    ) -> Result<Vec<u8>> {
271        // Validate algorithm category
272        self.security_validator.validate_algorithm_category(
273            algorithm,
274            lib_q_core::api::AlgorithmCategory::Signature,
275        )?;
276
277        // Validate secret key
278        self.security_validator
279            .validate_secret_key(algorithm, secret_key.as_bytes())?;
280
281        // Validate message
282        self.security_validator
283            .validate_signature_message(message)?;
284
285        // Validate randomness if provided
286        if let Some(rng) = randomness {
287            self.security_validator.validate_randomness(rng)?;
288        }
289
290        // Route to specific algorithm implementation
291        match algorithm {
292            // ML-DSA algorithms
293            #[cfg(feature = "ml-dsa")]
294            Algorithm::MlDsa44 => {
295                let ml_dsa = MlDsa::ml_dsa_44();
296                if let Some(rng) = randomness {
297                    let rng_array: [u8; 32] =
298                        rng.try_into().map_err(|_| Error::InvalidKeySize {
299                            expected: 32,
300                            actual: rng.len(),
301                        })?;
302                    ml_dsa.sign_with_randomness(secret_key, message, rng_array)
303                } else {
304                    ml_dsa.sign(secret_key, message)
305                }
306            }
307            #[cfg(feature = "ml-dsa")]
308            Algorithm::MlDsa65 => {
309                let ml_dsa = MlDsa::ml_dsa_65();
310                if let Some(rng) = randomness {
311                    let rng_array: [u8; 32] =
312                        rng.try_into().map_err(|_| Error::InvalidKeySize {
313                            expected: 32,
314                            actual: rng.len(),
315                        })?;
316                    ml_dsa.sign_with_randomness(secret_key, message, rng_array)
317                } else {
318                    ml_dsa.sign(secret_key, message)
319                }
320            }
321            #[cfg(feature = "ml-dsa")]
322            Algorithm::MlDsa87 => {
323                let ml_dsa = MlDsa::ml_dsa_87();
324                if let Some(rng) = randomness {
325                    let rng_array: [u8; 32] =
326                        rng.try_into().map_err(|_| Error::InvalidKeySize {
327                            expected: 32,
328                            actual: rng.len(),
329                        })?;
330                    ml_dsa.sign_with_randomness(secret_key, message, rng_array)
331                } else {
332                    ml_dsa.sign(secret_key, message)
333                }
334            }
335
336            // FN-DSA algorithms
337            #[cfg(feature = "fn-dsa")]
338            Algorithm::FnDsa => {
339                let fn_dsa = FnDsa::level1();
340                fn_dsa.sign(secret_key, message)
341            }
342            #[cfg(feature = "fn-dsa")]
343            Algorithm::FnDsa512 => {
344                let fn_dsa = FnDsa512::new();
345                fn_dsa.sign(secret_key, message)
346            }
347            #[cfg(feature = "fn-dsa")]
348            Algorithm::FnDsa1024 => {
349                let fn_dsa = FnDsa1024::new();
350                fn_dsa.sign(secret_key, message)
351            }
352
353            // SLH-DSA algorithms
354            #[cfg(feature = "slh-dsa")]
355            Algorithm::SlhDsaSha256128fRobust |
356            Algorithm::SlhDsaSha256192fRobust |
357            Algorithm::SlhDsaSha256256fRobust |
358            Algorithm::SlhDsaShake256128fRobust |
359            Algorithm::SlhDsaShake256192fRobust |
360            Algorithm::SlhDsaShake256256fRobust => {
361                let slh_dsa = SlhDsa::new();
362                slh_dsa.sign_for_algorithm(algorithm, secret_key, message, randomness)
363            }
364
365            // Handle missing feature flags
366            #[cfg(not(feature = "ml-dsa"))]
367            Algorithm::MlDsa44 | Algorithm::MlDsa65 | Algorithm::MlDsa87 => {
368                Err(Error::NotImplemented {
369                    feature: "ML-DSA implementations require 'ml-dsa' feature flag".to_string(),
370                })
371            }
372            #[cfg(not(feature = "fn-dsa"))]
373            Algorithm::FnDsa | Algorithm::FnDsa512 | Algorithm::FnDsa1024 => {
374                Err(Error::NotImplemented {
375                    feature: "FN-DSA implementations require 'fn-dsa' feature flag".to_string(),
376                })
377            }
378            #[cfg(not(feature = "slh-dsa"))]
379            Algorithm::SlhDsaSha256128fRobust |
380            Algorithm::SlhDsaSha256192fRobust |
381            Algorithm::SlhDsaSha256256fRobust |
382            Algorithm::SlhDsaShake256128fRobust |
383            Algorithm::SlhDsaShake256192fRobust |
384            Algorithm::SlhDsaShake256256fRobust => Err(Error::NotImplemented {
385                feature: "SLH-DSA implementations require 'slh-dsa' feature flag".to_string(),
386            }),
387
388            _ => Err(Error::InvalidAlgorithm {
389                algorithm: "Algorithm not supported for signature operations",
390            }),
391        }
392    }
393
394    fn verify(
395        &self,
396        algorithm: Algorithm,
397        public_key: &SigPublicKey,
398        message: &[u8],
399        signature: &[u8],
400    ) -> Result<bool> {
401        // Validate algorithm category
402        self.security_validator.validate_algorithm_category(
403            algorithm,
404            lib_q_core::api::AlgorithmCategory::Signature,
405        )?;
406
407        // Validate public key
408        self.security_validator
409            .validate_public_key(algorithm, public_key.as_bytes())?;
410
411        // Validate message
412        self.security_validator
413            .validate_signature_message(message)?;
414
415        // Validate signature
416        self.security_validator
417            .validate_signature(algorithm, signature)?;
418
419        // Route to specific algorithm implementation
420        match algorithm {
421            // ML-DSA algorithms
422            #[cfg(feature = "ml-dsa")]
423            Algorithm::MlDsa44 => {
424                let ml_dsa = MlDsa::ml_dsa_44();
425                ml_dsa.verify(public_key, message, signature)
426            }
427            #[cfg(feature = "ml-dsa")]
428            Algorithm::MlDsa65 => {
429                let ml_dsa = MlDsa::ml_dsa_65();
430                ml_dsa.verify(public_key, message, signature)
431            }
432            #[cfg(feature = "ml-dsa")]
433            Algorithm::MlDsa87 => {
434                let ml_dsa = MlDsa::ml_dsa_87();
435                ml_dsa.verify(public_key, message, signature)
436            }
437
438            // FN-DSA algorithms
439            #[cfg(feature = "fn-dsa")]
440            Algorithm::FnDsa => {
441                let fn_dsa = FnDsa::level1();
442                fn_dsa.verify(public_key, message, signature)
443            }
444            #[cfg(feature = "fn-dsa")]
445            Algorithm::FnDsa512 => {
446                let fn_dsa = FnDsa512::new();
447                fn_dsa.verify(public_key, message, signature)
448            }
449            #[cfg(feature = "fn-dsa")]
450            Algorithm::FnDsa1024 => {
451                let fn_dsa = FnDsa1024::new();
452                fn_dsa.verify(public_key, message, signature)
453            }
454
455            // SLH-DSA algorithms
456            #[cfg(feature = "slh-dsa")]
457            Algorithm::SlhDsaSha256128fRobust |
458            Algorithm::SlhDsaSha256192fRobust |
459            Algorithm::SlhDsaSha256256fRobust |
460            Algorithm::SlhDsaShake256128fRobust |
461            Algorithm::SlhDsaShake256192fRobust |
462            Algorithm::SlhDsaShake256256fRobust => {
463                let slh_dsa = SlhDsa::new();
464                slh_dsa.verify_for_algorithm(algorithm, public_key, message, signature)
465            }
466
467            // Handle missing feature flags
468            #[cfg(not(feature = "ml-dsa"))]
469            Algorithm::MlDsa44 | Algorithm::MlDsa65 | Algorithm::MlDsa87 => {
470                Err(Error::NotImplemented {
471                    feature: "ML-DSA implementations require 'ml-dsa' feature flag".to_string(),
472                })
473            }
474            #[cfg(not(feature = "fn-dsa"))]
475            Algorithm::FnDsa | Algorithm::FnDsa512 | Algorithm::FnDsa1024 => {
476                Err(Error::NotImplemented {
477                    feature: "FN-DSA implementations require 'fn-dsa' feature flag".to_string(),
478                })
479            }
480            #[cfg(not(feature = "slh-dsa"))]
481            Algorithm::SlhDsaSha256128fRobust |
482            Algorithm::SlhDsaSha256192fRobust |
483            Algorithm::SlhDsaSha256256fRobust |
484            Algorithm::SlhDsaShake256128fRobust |
485            Algorithm::SlhDsaShake256192fRobust |
486            Algorithm::SlhDsaShake256256fRobust => Err(Error::NotImplemented {
487                feature: "SLH-DSA implementations require 'slh-dsa' feature flag".to_string(),
488            }),
489
490            _ => Err(Error::InvalidAlgorithm {
491                algorithm: "Algorithm not supported for signature operations",
492            }),
493        }
494    }
495
496    /// ML-DSA is the only family wired for signing contexts here; every other algorithm keeps
497    /// the trait default (empty context delegates, non-empty context is rejected rather than
498    /// silently dropped).
499    fn sign_with_context(
500        &self,
501        algorithm: Algorithm,
502        secret_key: &SigSecretKey,
503        message: &[u8],
504        context: &[u8],
505        randomness: Option<&[u8]>,
506    ) -> Result<Vec<u8>> {
507        if context.is_empty() {
508            return self.sign(algorithm, secret_key, message, randomness);
509        }
510
511        // Validate algorithm category
512        self.security_validator.validate_algorithm_category(
513            algorithm,
514            lib_q_core::api::AlgorithmCategory::Signature,
515        )?;
516
517        // Validate secret key
518        self.security_validator
519            .validate_secret_key(algorithm, secret_key.as_bytes())?;
520
521        // Validate message
522        self.security_validator
523            .validate_signature_message(message)?;
524
525        // Validate randomness if provided
526        if let Some(rng) = randomness {
527            self.security_validator.validate_randomness(rng)?;
528        }
529
530        #[cfg(feature = "ml-dsa")]
531        {
532            let ml_dsa = match algorithm {
533                Algorithm::MlDsa44 => Some(MlDsa::ml_dsa_44()),
534                Algorithm::MlDsa65 => Some(MlDsa::ml_dsa_65()),
535                Algorithm::MlDsa87 => Some(MlDsa::ml_dsa_87()),
536                _ => None,
537            };
538            if let Some(ml_dsa) = ml_dsa {
539                return if let Some(rng) = randomness {
540                    let rng_array: [u8; 32] =
541                        rng.try_into().map_err(|_| Error::InvalidKeySize {
542                            expected: 32,
543                            actual: rng.len(),
544                        })?;
545                    ml_dsa.sign_with_randomness_and_context(secret_key, message, context, rng_array)
546                } else {
547                    ml_dsa.sign_with_context(secret_key, message, context)
548                };
549            }
550        }
551
552        Err(Error::NotImplemented {
553            feature: "signing contexts are only supported for ML-DSA".to_string(),
554        })
555    }
556
557    /// ML-DSA is the only family wired for signing contexts here; every other algorithm keeps
558    /// the trait default (empty context delegates, non-empty context is rejected rather than
559    /// verified without the context).
560    fn verify_with_context(
561        &self,
562        algorithm: Algorithm,
563        public_key: &SigPublicKey,
564        message: &[u8],
565        context: &[u8],
566        signature: &[u8],
567    ) -> Result<bool> {
568        if context.is_empty() {
569            return self.verify(algorithm, public_key, message, signature);
570        }
571
572        // Validate algorithm category
573        self.security_validator.validate_algorithm_category(
574            algorithm,
575            lib_q_core::api::AlgorithmCategory::Signature,
576        )?;
577
578        // Validate public key
579        self.security_validator
580            .validate_public_key(algorithm, public_key.as_bytes())?;
581
582        // Validate message
583        self.security_validator
584            .validate_signature_message(message)?;
585
586        // Validate signature
587        self.security_validator
588            .validate_signature(algorithm, signature)?;
589
590        #[cfg(feature = "ml-dsa")]
591        {
592            let ml_dsa = match algorithm {
593                Algorithm::MlDsa44 => Some(MlDsa::ml_dsa_44()),
594                Algorithm::MlDsa65 => Some(MlDsa::ml_dsa_65()),
595                Algorithm::MlDsa87 => Some(MlDsa::ml_dsa_87()),
596                _ => None,
597            };
598            if let Some(ml_dsa) = ml_dsa {
599                return ml_dsa.verify_with_context(public_key, message, context, signature);
600            }
601        }
602
603        Err(Error::NotImplemented {
604            feature: "signing contexts are only supported for ML-DSA".to_string(),
605        })
606    }
607}
608
609#[cfg(feature = "alloc")]
610impl CryptoProvider for LibQSignatureProvider {
611    fn kem(&self) -> Option<&dyn lib_q_core::api::KemOperations> {
612        None
613    }
614
615    fn signature(&self) -> Option<&dyn SignatureOperations> {
616        Some(self)
617    }
618
619    fn hash(&self) -> Option<&dyn lib_q_core::api::HashOperations> {
620        None
621    }
622
623    fn aead(&self) -> Option<&dyn lib_q_core::api::AeadOperations> {
624        None
625    }
626}
627
628#[cfg(test)]
629mod tests {
630    use alloc::vec::Vec;
631
632    use super::*;
633
634    #[test]
635    fn test_provider_creation() {
636        let provider = LibQSignatureProvider::new();
637        assert!(provider.is_ok(), "Provider should be created successfully");
638    }
639
640    #[test]
641    fn test_provider_security_validator() {
642        let provider = LibQSignatureProvider::new().unwrap();
643        let _validator = provider.security_validator();
644        // Security validator should be accessible
645        // Security validator is accessible
646    }
647
648    #[test]
649    fn test_provider_unsupported_algorithm() {
650        let provider = LibQSignatureProvider::new().unwrap();
651        let result = provider.generate_keypair(Algorithm::Sha3_256, None);
652        assert!(
653            result.is_err(),
654            "Should return error for unsupported algorithm"
655        );
656
657        if let Err(Error::InvalidAlgorithm { .. }) = result {
658            // Expected error type
659        } else {
660            panic!("Expected InvalidAlgorithm error");
661        }
662    }
663
664    #[test]
665    fn test_provider_feature_flag_handling() {
666        let _provider = LibQSignatureProvider::new().unwrap();
667
668        // Test ML-DSA without feature flag
669        #[cfg(not(feature = "ml-dsa"))]
670        {
671            let result = _provider.generate_keypair(Algorithm::MlDsa65, None);
672            assert!(
673                result.is_err(),
674                "Should return error when feature flag is not enabled"
675            );
676
677            if let Err(Error::NotImplemented { feature }) = result {
678                assert!(
679                    feature.contains("ML-DSA implementations require 'ml-dsa' feature flag"),
680                    "Error should mention feature flag requirement"
681                );
682            } else {
683                panic!("Expected NotImplemented error");
684            }
685        }
686
687        // Test FN-DSA without feature flag
688        #[cfg(not(feature = "fn-dsa"))]
689        {
690            let result = _provider.generate_keypair(Algorithm::FnDsa512, None);
691            assert!(
692                result.is_err(),
693                "Should return error when feature flag is not enabled"
694            );
695
696            if let Err(Error::NotImplemented { feature }) = result {
697                assert!(
698                    feature.contains("FN-DSA implementations require 'fn-dsa' feature flag"),
699                    "Error should mention feature flag requirement"
700                );
701            } else {
702                panic!("Expected NotImplemented error");
703            }
704        }
705
706        // Test SLH-DSA without feature flag
707        #[cfg(not(feature = "slh-dsa"))]
708        {
709            let result = _provider.generate_keypair(Algorithm::SlhDsaSha256128fRobust, None);
710            assert!(
711                result.is_err(),
712                "Should return error when feature flag is not enabled"
713            );
714
715            if let Err(Error::NotImplemented { feature }) = result {
716                assert!(
717                    feature.contains("SLH-DSA implementations require 'slh-dsa' feature flag"),
718                    "Error should mention feature flag requirement"
719                );
720            } else {
721                panic!("Expected NotImplemented error");
722            }
723        }
724    }
725
726    #[test]
727    fn test_provider_algorithm_routing() {
728        let provider = LibQSignatureProvider::new().unwrap();
729
730        // Test that algorithms are properly routed
731        #[cfg(feature = "ml-dsa")]
732        {
733            let result = provider.generate_keypair(Algorithm::MlDsa65, None);
734            // Should either succeed or return NotImplemented (depending on std feature)
735            match result {
736                Ok(_) => {
737                    // Success case - this is expected with std feature
738                }
739                Err(Error::NotImplemented { .. }) => {
740                    // Expected when std feature is not available
741                }
742                Err(Error::RandomGenerationFailed { .. }) => {
743                    // Expected when std feature is not available for randomness generation
744                }
745                Err(e) => {
746                    panic!("Unexpected error type: {:?}", e);
747                }
748            }
749        }
750
751        #[cfg(feature = "fn-dsa")]
752        {
753            let result = provider.generate_keypair(Algorithm::FnDsa512, None);
754            // Should either succeed or return NotImplemented (depending on std feature)
755            match result {
756                Ok(_) => {
757                    // Success case - this is expected with std feature
758                }
759                Err(Error::NotImplemented { .. }) => {
760                    // Expected when std feature is not available
761                }
762                Err(Error::RandomGenerationFailed { .. }) => {
763                    // Expected when std feature is not available for randomness generation
764                }
765                Err(e) => {
766                    panic!("Unexpected error type: {:?}", e);
767                }
768            }
769        }
770
771        #[cfg(feature = "slh-dsa")]
772        {
773            let result = provider.generate_keypair(Algorithm::SlhDsaSha256128fRobust, None);
774            // Should either succeed or return NotImplemented (depending on std feature)
775            match result {
776                Ok(_) => {
777                    // Success case - this is expected with std feature
778                }
779                Err(Error::NotImplemented { .. }) => {
780                    // Expected when std feature is not available
781                }
782                Err(Error::RandomGenerationFailed { .. }) => {
783                    // Expected when std feature is not available for randomness generation
784                }
785                Err(e) => {
786                    panic!("Unexpected error type: {:?}", e);
787                }
788            }
789        }
790    }
791
792    #[test]
793    fn test_provider_sign_rejects_non_signature_algorithm() {
794        let provider = LibQSignatureProvider::new().unwrap();
795        let secret_key = SigSecretKey::new(Vec::new());
796        let result = provider.sign(Algorithm::Sha3_256, &secret_key, b"message", None);
797        assert!(
798            matches!(result, Err(Error::InvalidAlgorithm { .. })),
799            "sign should reject non-signature algorithms before key validation"
800        );
801    }
802
803    #[test]
804    fn test_provider_verify_rejects_non_signature_algorithm() {
805        let provider = LibQSignatureProvider::new().unwrap();
806        let public_key = SigPublicKey::new(Vec::new());
807        let result = provider.verify(Algorithm::Sha3_256, &public_key, b"message", b"sig");
808        assert!(
809            matches!(result, Err(Error::InvalidAlgorithm { .. })),
810            "verify should reject non-signature algorithms before key/signature validation"
811        );
812    }
813
814    #[test]
815    fn test_crypto_provider_exposes_signature_only() {
816        let provider = LibQSignatureProvider::new().unwrap();
817        assert!(provider.signature().is_some());
818        assert!(provider.kem().is_none());
819        assert!(provider.hash().is_none());
820        assert!(provider.aead().is_none());
821    }
822
823    #[cfg(feature = "ml-dsa")]
824    #[test]
825    fn test_provider_ml_dsa44_with_explicit_randomness_round_trip() {
826        use lib_q_core::Utils;
827
828        let provider = LibQSignatureProvider::new().unwrap();
829        let message = b"provider ml-dsa44 explicit randomness";
830        let key_randomness = Utils::random_bytes(32).expect("test randomness generation failed");
831        let signing_randomness =
832            Utils::random_bytes(32).expect("test randomness generation failed");
833
834        let keypair = provider
835            .generate_keypair(Algorithm::MlDsa44, Some(&key_randomness))
836            .expect("key generation with explicit randomness should succeed");
837
838        let signature = provider
839            .sign(
840                Algorithm::MlDsa44,
841                keypair.secret_key(),
842                message,
843                Some(&signing_randomness),
844            )
845            .expect("signing with explicit randomness should succeed");
846
847        let is_valid = provider
848            .verify(
849                Algorithm::MlDsa44,
850                keypair.public_key(),
851                message,
852                &signature,
853            )
854            .expect("verification should succeed");
855        assert!(
856            is_valid,
857            "provider should verify its own ML-DSA-44 signatures"
858        );
859    }
860
861    /// Every ML-DSA parameter set must survive keygen -> sign -> verify through the provider.
862    ///
863    /// Mirrors `fn_dsa_round_trips_through_the_provider_at_every_parameter_set`: the prior
864    /// coverage here only ever exercised ML-DSA-44
865    /// (`test_provider_ml_dsa44_with_explicit_randomness_round_trip`), so a size-table drift on
866    /// ML-DSA-65/87 specifically (as opposed to -44) would not have been caught by any existing
867    /// provider test.
868    #[cfg(feature = "ml-dsa")]
869    #[test]
870    fn ml_dsa_round_trips_through_the_provider_at_every_parameter_set() {
871        let provider = LibQSignatureProvider::new().expect("provider construction should succeed");
872        let message = b"ml-dsa provider round trip";
873
874        for algorithm in [Algorithm::MlDsa44, Algorithm::MlDsa65, Algorithm::MlDsa87] {
875            let keypair = provider
876                .generate_keypair(algorithm, None)
877                .unwrap_or_else(|e| panic!("{algorithm:?} keygen failed: {e:?}"));
878
879            let signature = provider
880                .sign(algorithm, keypair.secret_key(), message, None)
881                .unwrap_or_else(|e| {
882                    panic!("{algorithm:?}: provider rejected a key it just generated: {e:?}")
883                });
884
885            let is_valid = provider
886                .verify(algorithm, keypair.public_key(), message, &signature)
887                .unwrap_or_else(|e| panic!("{algorithm:?} verify errored: {e:?}"));
888            assert!(
889                is_valid,
890                "{algorithm:?}: provider must verify its own signature"
891            );
892        }
893    }
894
895    /// Every FN-DSA parameter set must survive keygen -> sign -> verify through the provider.
896    ///
897    /// Regression test. `lib-q-core`'s hand-maintained key-size table recorded the FN-DSA-1024
898    /// secret key as 2561 bytes when `sign_key_size(10)` derives 2305 -- 2561 is that formula
899    /// evaluated with logn=9's `nbits_fg`. The provider gates on an exact `!=`, so it rejected an
900    /// FN-DSA-1024 key the library had just generated and FN-DSA-1024 signing was dead end to
901    /// end. Nothing caught it because every existing provider test used FnDsa512 only, which is
902    /// why this one is parameterised over BOTH sets rather than just adding a 1024 copy: a size
903    /// table is only checked when something compares it to a real key.
904    #[test]
905    #[cfg(feature = "fn-dsa")]
906    fn fn_dsa_round_trips_through_the_provider_at_every_parameter_set() {
907        let provider = LibQSignatureProvider::new().expect("provider construction should succeed");
908        let message = b"fn-dsa provider round trip";
909
910        for algorithm in [Algorithm::FnDsa512, Algorithm::FnDsa1024] {
911            let keypair = match provider.generate_keypair(algorithm, None) {
912                Ok(kp) => kp,
913                // These arms mirror the other provider tests: without `std` the backend is not
914                // built, and that is not what this test is about.
915                Err(Error::NotImplemented { .. }) | Err(Error::RandomGenerationFailed { .. }) => {
916                    continue;
917                }
918                Err(e) => panic!("{algorithm:?} keygen failed: {e:?}"),
919            };
920
921            let signature = provider
922                .sign(algorithm, keypair.secret_key(), message, None)
923                .unwrap_or_else(|e| {
924                    panic!("{algorithm:?}: provider rejected a key it just generated: {e:?}")
925                });
926
927            let is_valid = provider
928                .verify(algorithm, keypair.public_key(), message, &signature)
929                .unwrap_or_else(|e| panic!("{algorithm:?} verify errored: {e:?}"));
930            assert!(
931                is_valid,
932                "{algorithm:?}: provider must verify its own signature"
933            );
934        }
935    }
936}