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