Skip to main content

lib_q_kem/
lib.rs

1//! lib-Q KEM - Post-quantum Key Encapsulation Mechanisms
2//!
3//! This crate provides implementations of post-quantum key encapsulation mechanisms
4//! following the lib-Q architecture with proper security validation and provider pattern integration.
5//!
6//! ## Architecture
7//!
8//! This implementation follows the lib-Q provider pattern:
9//! - **Provider Pattern**: Implements `KemOperations` trait for integration with lib-q-core
10//! - **Security Validation**: Comprehensive input validation and security checks
11//! - **Algorithm Support**: Full support for NIST-approved KEM algorithms
12//! - **Memory Safety**: Automatic zeroization of sensitive data
13//! - **no_std Support**: Works in constrained environments
14//!
15//! ## Supported Algorithms
16//!
17//! - **ML-KEM**: CRYSTALS-ML-KEM (Levels 1, 3, 4)
18//!
19//! ## Feature Support
20//!
21//! All KEM algorithms support:
22//! - **no_std**: Works in constrained environments with external randomness
23//! - **WASM**: JavaScript-compatible bindings for web environments
24//! - **Security validation**: Comprehensive input validation and security checks
25//! - **Memory safety**: Automatic zeroization of sensitive data
26//!
27//! ## Usage
28//!
29//! ### With std (automatic randomness)
30//! ```rust,ignore
31//! use lib_q_core::{Algorithm, KemContext, create_kem_context};
32//! use lib_q_kem::LibQKemProvider;
33//!
34//! fn main() -> Result<(), Box<dyn std::error::Error>> {
35//!     // Create KEM context with provider
36//!     let mut ctx = create_kem_context();
37//!     ctx.set_provider(Box::new(LibQKemProvider::new()?));
38//!
39//!     // Generate keypair (requires std feature for automatic randomness)
40//!     let keypair = ctx.generate_keypair(Algorithm::MlKem512, None)?;
41//!
42//!     // Encapsulate shared secret
43//!     let (ciphertext, shared_secret) = ctx.encapsulate(Algorithm::MlKem512, &keypair.public_key, None)?;
44//!
45//!     // Decapsulate shared secret
46//!     let decapsulated_secret = ctx.decapsulate(Algorithm::MlKem512, &keypair.secret_key, &ciphertext)?;
47//!     assert_eq!(shared_secret, decapsulated_secret);
48//!     Ok(())
49//! }
50//! ```
51//!
52//! ### Without std (external randomness)
53//! ```rust,ignore
54//! use lib_q_core::{Algorithm, KemContext, create_kem_context};
55//! use lib_q_kem::LibQKemProvider;
56//!
57//! fn main() -> Result<(), Box<dyn std::error::Error>> {
58//!     // Create KEM context with provider
59//!     let mut ctx = create_kem_context();
60//!     ctx.set_provider(Box::new(LibQKemProvider::new()?));
61//!
62//!     // Provide randomness externally (required in no_std environments)
63//!     let key_randomness = [0u8; 32]; // Get from hardware RNG
64//!
65//!     // Generate keypair with external randomness
66//!     let keypair = ctx.generate_keypair(Algorithm::MlKem512, Some(&key_randomness))?;
67//!
68//!     // Encapsulate shared secret
69//!     let (ciphertext, shared_secret) = ctx.encapsulate(Algorithm::MlKem512, &keypair.public_key, None)?;
70//!
71//!     // Decapsulate shared secret
72//!     let decapsulated_secret = ctx.decapsulate(Algorithm::MlKem512, &keypair.secret_key, &ciphertext)?;
73//!     assert_eq!(shared_secret, decapsulated_secret);
74//!     Ok(())
75//! }
76//! ```
77
78#![cfg_attr(not(feature = "std"), no_std)]
79#![deny(unsafe_code)]
80#![deny(unused_qualifications)]
81
82#[cfg(feature = "alloc")]
83extern crate alloc;
84
85// Re-export core types for public use
86pub use lib_q_core::{
87    Algorithm,
88    AlgorithmCategory,
89    Error,
90    Kem,
91    KemContext,
92    KemKeypair,
93    KemOperations,
94    KemPublicKey,
95    KemSecretKey,
96    Result,
97};
98
99// Provider implementation
100pub mod provider;
101
102// Algorithm implementations
103#[cfg(feature = "ml-kem")]
104pub mod ml_kem;
105
106#[cfg(feature = "hqc")]
107pub mod hqc;
108
109// Re-export provider
110#[cfg(feature = "alloc")]
111pub use provider::LibQKemProvider;
112
113/// Get available KEM algorithms with proper NIST naming
114#[cfg(feature = "std")]
115pub fn available_algorithms() -> Vec<&'static str> {
116    let mut algorithms = Vec::new();
117
118    #[cfg(feature = "ml-kem")]
119    {
120        algorithms.extend(["ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"]);
121    }
122
123    #[cfg(feature = "cb-kem")]
124    {
125        algorithms.extend([
126            "CB-KEM-348864",
127            "CB-KEM-460896",
128            "CB-KEM-6688128",
129            "CB-KEM-6960119",
130            "CB-KEM-8192128",
131        ]);
132    }
133
134    #[cfg(feature = "hqc")]
135    {
136        algorithms.extend(["HQC-128", "HQC-192", "HQC-256"]);
137    }
138
139    algorithms
140}
141
142/// Get available KEM algorithms (no_std version)
143#[cfg(not(feature = "std"))]
144pub fn available_algorithms() -> &'static [&'static str] {
145    &[
146        #[cfg(feature = "ml-kem")]
147        "ML-KEM-512",
148        #[cfg(feature = "ml-kem")]
149        "ML-KEM-768",
150        #[cfg(feature = "ml-kem")]
151        "ML-KEM-1024",
152        #[cfg(feature = "cb-kem")]
153        "CB-KEM-348864",
154        #[cfg(feature = "cb-kem")]
155        "CB-KEM-460896",
156        #[cfg(feature = "cb-kem")]
157        "CB-KEM-6688128",
158        #[cfg(feature = "cb-kem")]
159        "CB-KEM-6960119",
160        #[cfg(feature = "cb-kem")]
161        "CB-KEM-8192128",
162        #[cfg(feature = "hqc")]
163        "HQC-128",
164        #[cfg(feature = "hqc")]
165        "HQC-192",
166        #[cfg(feature = "hqc")]
167        "HQC-256",
168    ]
169}
170
171/// Create a KEM instance by algorithm name (legacy compatibility)
172#[cfg(feature = "std")]
173pub fn create_kem(algorithm: &str) -> Result<Box<dyn Kem>> {
174    match algorithm {
175        #[cfg(feature = "ml-kem")]
176        "ml-kem-512" | "ML-KEM-512" => Ok(Box::new(ml_kem::MlKem512Impl::default())),
177        #[cfg(feature = "ml-kem")]
178        "ml-kem-768" | "ML-KEM-768" => Ok(Box::new(ml_kem::MlKem768Impl::default())),
179        #[cfg(feature = "ml-kem")]
180        "ml-kem-1024" | "ML-KEM-1024" => Ok(Box::new(ml_kem::MlKem1024Impl::default())),
181
182        #[cfg(feature = "hqc")]
183        "HQC-128" | "hqc-128" => Ok(Box::new(hqc::Hqc128Impl)),
184        #[cfg(feature = "hqc")]
185        "HQC-192" | "hqc-192" => Ok(Box::new(hqc::Hqc192Impl)),
186        #[cfg(feature = "hqc")]
187        "HQC-256" | "hqc-256" => Ok(Box::new(hqc::Hqc256Impl)),
188
189        _ => Err(Error::InvalidAlgorithm {
190            algorithm: "Unknown algorithm",
191        }),
192    }
193}
194
195/// Create a KEM context for the specified algorithm
196pub fn create_kem_context(algorithm: Algorithm) -> Result<KemContext> {
197    // Validate that this is a KEM algorithm
198    if algorithm.category() != AlgorithmCategory::Kem {
199        return Err(Error::InvalidAlgorithm {
200            algorithm: "Algorithm is not a KEM algorithm",
201        });
202    }
203
204    Ok(KemContext::new())
205}
206
207/// WASM-friendly wrapper for KEM operations
208#[cfg(feature = "wasm")]
209pub mod wasm {
210    use alloc::string::ToString;
211    use alloc::vec::Vec;
212
213    use lib_q_core::{
214        Algorithm,
215        KemKeypair,
216        KemPublicKey,
217        KemSecretKey,
218    };
219    #[allow(unused_imports)]
220    use wasm_bindgen::{
221        JsError,
222        prelude::*,
223    };
224
225    use super::*;
226
227    /// Generate a keypair for the specified algorithm (WASM)
228    #[wasm_bindgen]
229    pub fn generate_keypair(algorithm: Algorithm) -> core::result::Result<KemKeypair, JsError> {
230        let provider = LibQKemProvider::new().map_err(|e| JsError::new(&e.to_string()))?;
231        provider
232            .generate_keypair(algorithm, None)
233            .map_err(|e| JsError::new(&e.to_string()))
234    }
235
236    /// Encapsulate a shared secret (WASM)
237    #[wasm_bindgen]
238    pub fn encapsulate(
239        algorithm: Algorithm,
240        public_key: &KemPublicKey,
241    ) -> core::result::Result<EncapsulationResult, JsError> {
242        let provider = LibQKemProvider::new().map_err(|e| JsError::new(&e.to_string()))?;
243        let (ciphertext, shared_secret) = provider
244            .encapsulate(algorithm, public_key, None)
245            .map_err(|e| JsError::new(&e.to_string()))?;
246        Ok(EncapsulationResult::new(ciphertext, shared_secret))
247    }
248
249    /// Decapsulate a shared secret (WASM)
250    #[wasm_bindgen]
251    pub fn decapsulate(
252        algorithm: Algorithm,
253        secret_key: &KemSecretKey,
254        ciphertext: &[u8],
255    ) -> core::result::Result<Vec<u8>, JsError> {
256        let provider = LibQKemProvider::new().map_err(|e| JsError::new(&e.to_string()))?;
257        provider
258            .decapsulate(algorithm, secret_key, ciphertext)
259            .map_err(|e| JsError::new(&e.to_string()))
260    }
261
262    /// Result of encapsulation operation for WASM
263    #[wasm_bindgen]
264    pub struct EncapsulationResult {
265        ciphertext: Vec<u8>,
266        shared_secret: Vec<u8>,
267    }
268
269    #[wasm_bindgen]
270    impl EncapsulationResult {
271        #[wasm_bindgen(constructor)]
272        pub fn new(ciphertext: Vec<u8>, shared_secret: Vec<u8>) -> Self {
273            Self {
274                ciphertext,
275                shared_secret,
276            }
277        }
278
279        #[wasm_bindgen(getter)]
280        pub fn ciphertext(&self) -> Vec<u8> {
281            self.ciphertext.clone()
282        }
283
284        #[wasm_bindgen(getter)]
285        pub fn shared_secret(&self) -> Vec<u8> {
286            self.shared_secret.clone()
287        }
288    }
289}
290
291#[cfg(all(test, feature = "alloc"))]
292mod tests {
293    use super::*;
294
295    #[test]
296    fn test_available_algorithms() {
297        let algorithms = available_algorithms();
298
299        // Test that we get the expected algorithms based on enabled features
300        #[cfg(feature = "ml-kem")]
301        {
302            assert!(
303                algorithms.contains(&"ML-KEM-512"),
304                "ML-KEM 512 should be available when ml-kem feature is enabled"
305            );
306            assert!(
307                algorithms.contains(&"ML-KEM-768"),
308                "ML-KEM 768 should be available when ml-kem feature is enabled"
309            );
310            assert!(
311                algorithms.contains(&"ML-KEM-1024"),
312                "ML-KEM 1024 should be available when ml-kem feature is enabled"
313            );
314        }
315
316        #[cfg(feature = "cb-kem")]
317        {
318            assert!(
319                algorithms.contains(&"CB-KEM-348864"),
320                "CB-KEM-348864 should be available when cb-kem feature is enabled"
321            );
322            assert!(
323                algorithms.contains(&"CB-KEM-460896"),
324                "CB-KEM-460896 should be available when cb-kem feature is enabled"
325            );
326            assert!(
327                algorithms.contains(&"CB-KEM-6688128"),
328                "CB-KEM-6688128 should be available when cb-kem feature is enabled"
329            );
330            assert!(
331                algorithms.contains(&"CB-KEM-6960119"),
332                "CB-KEM-6960119 should be available when cb-kem feature is enabled"
333            );
334            assert!(
335                algorithms.contains(&"CB-KEM-8192128"),
336                "CB-KEM-8192128 should be available when cb-kem feature is enabled"
337            );
338        }
339
340        #[cfg(feature = "hqc")]
341        {
342            assert!(
343                algorithms.contains(&"HQC-128"),
344                "HQC-128 should be available when hqc feature is enabled"
345            );
346            assert!(
347                algorithms.contains(&"HQC-192"),
348                "HQC-192 should be available when hqc feature is enabled"
349            );
350            assert!(
351                algorithms.contains(&"HQC-256"),
352                "HQC-256 should be available when hqc feature is enabled"
353            );
354        }
355
356        // Test that we have at least one algorithm when any features are enabled
357        #[cfg(any(feature = "ml-kem", feature = "cb-kem", feature = "hqc"))]
358        assert!(
359            !algorithms.is_empty(),
360            "Should have at least one algorithm when features are enabled"
361        );
362
363        // Test that we have no algorithms when no features are enabled
364        #[cfg(not(any(feature = "ml-kem", feature = "cb-kem", feature = "hqc")))]
365        assert!(
366            algorithms.is_empty(),
367            "Should have no algorithms when no features are enabled"
368        );
369
370        // Test that the algorithm count matches expected count
371        let expected_count = {
372            let count = 0;
373            #[cfg(feature = "ml-kem")]
374            let count = count + 3; // ML-KEM-512, ML-KEM-768, ML-KEM-1024
375            #[cfg(feature = "cb-kem")]
376            let count = count + 5; // CB-KEM-348864, CB-KEM-460896, CB-KEM-6688128, CB-KEM-6960119, CB-KEM-8192128
377            #[cfg(feature = "hqc")]
378            let count = count + 3; // HQC-128, HQC-192, HQC-256
379            count
380        };
381
382        assert_eq!(
383            algorithms.len(),
384            expected_count,
385            "Algorithm count should match expected count based on enabled features"
386        );
387    }
388
389    #[test]
390    fn test_create_kem_context() {
391        // Test that context creation works for valid KEM algorithms
392        let result = create_kem_context(Algorithm::MlKem512);
393        assert!(
394            result.is_ok(),
395            "Context creation should succeed for valid KEM algorithm"
396        );
397
398        // The context itself doesn't have providers - those are set up by the main lib-q crate
399        // This test just verifies the basic context creation and structure
400        let mut ctx = result.unwrap();
401
402        // Without a provider, keypair generation should fail with ProviderNotConfigured
403        let keypair_result = ctx.generate_keypair(Algorithm::MlKem512, None);
404        assert!(
405            keypair_result.is_err(),
406            "Keypair generation should fail without provider"
407        );
408        if let Err(err) = keypair_result {
409            assert!(matches!(err, Error::ProviderNotConfigured { .. }));
410        }
411    }
412
413    #[test]
414    fn test_create_kem_context_invalid_algorithm() {
415        let result = create_kem_context(Algorithm::MlDsa65);
416        assert!(result.is_err());
417    }
418
419    #[test]
420    fn test_provider_creation() {
421        let provider = LibQKemProvider::new();
422        assert!(provider.is_ok(), "Provider should be created successfully");
423    }
424
425    #[test]
426    fn test_provider_algorithm_support() {
427        let provider = LibQKemProvider::new().unwrap();
428
429        // Test ML-KEM algorithms
430        #[cfg(feature = "ml-kem")]
431        {
432            let result = provider.generate_keypair(Algorithm::MlKem512, None);
433            // Should either succeed or return NotImplemented (depending on feature flags)
434            match result {
435                Ok(_) => {
436                    // Success case - this is expected with std feature
437                }
438                Err(Error::NotImplemented { .. }) => {
439                    // Expected when std feature is not available
440                }
441                Err(Error::RandomGenerationFailed { .. }) => {
442                    // Expected when std feature is not available for randomness generation
443                }
444                Err(e) => {
445                    panic!("Unexpected error type: {:?}", e);
446                }
447            }
448        }
449
450        // Test unsupported algorithm
451        let result = provider.generate_keypair(Algorithm::Sha3_256, None);
452        assert!(result.is_err());
453        if let Err(Error::InvalidAlgorithm { .. }) = result {
454            // Expected error type
455        } else {
456            panic!("Expected InvalidAlgorithm error for non-KEM algorithm");
457        }
458    }
459
460    #[test]
461    fn test_algorithm_naming_consistency() {
462        let algorithms = available_algorithms();
463
464        // Check that algorithm names follow NIST conventions
465        for algorithm in algorithms {
466            assert!(
467                algorithm.starts_with("ML-KEM-") ||
468                    algorithm.starts_with("CB-KEM-") ||
469                    algorithm.starts_with("HQC-"),
470                "Algorithm name '{}' should follow NIST naming conventions",
471                algorithm
472            );
473        }
474    }
475
476    #[cfg(feature = "std")]
477    #[test]
478    fn test_legacy_compatibility() {
479        #[cfg(feature = "ml-kem")]
480        {
481            // Test legacy algorithm names still work
482            let result = create_kem("ml-kem-512");
483            assert!(result.is_ok(), "Legacy 'ml-kem-512' name should work");
484
485            let result = create_kem("ML-KEM-512");
486            assert!(result.is_ok(), "NIST 'ML-KEM-512' name should work");
487        }
488    }
489}