Skip to main content

libq/
lib.rs

1//! lib-Q - Post-Quantum Cryptography Library
2//!
3//! A modern, secure cryptography library built exclusively with post-quantum algorithms
4//! from the NIST PQC process (ML-KEM/FIPS 203, ML-DSA/FIPS 204, and SLH-DSA/FIPS 205 are
5//! finalized standards; FN-DSA and HQC are NIST-selected with no FIPS draft published yet;
6//! CB-KEM/Classic McEliece is a round-4 submission NIST did not select — see
7//! `docs/security.md` for the per-algorithm status). Written in Rust with WASM compilation
8//! support.
9//!
10//! # Architecture Principles
11//!
12//! - **Zero Dynamic Allocations**: Stack-only operations for constrained environments
13//! - **Memory Safety**: Automatic zeroization of sensitive data using `Zeroize` trait
14//! - **Constant-Time**: Operations designed to prevent timing attacks
15//! - **Post-Quantum Only**: algorithms tracked by the NIST PQC process for quantum
16//!   resistance (see `docs/security.md` for each algorithm's exact standardization status)
17//! - **Provider Pattern**: Pluggable cryptographic implementations
18//! - **Unified API**: Same interface for Rust crate and WASM usage
19//!
20//! # Security Features
21//!
22//! - **Tiered Security**: NIST categories 1 (128-bit), 3 (192-bit) and 5 (256-bit)
23//! - **Algorithm Diversity**: ML-KEM, HQC, ML-DSA, FN-DSA, Saturnin, Romulus (N/M)
24//! - **Input Validation**: Comprehensive validation of all cryptographic inputs
25//! - **Error Handling**: Secure error messages that don't leak sensitive information
26//! - **AEAD Layer B:** The `libq::aead::context()` / WASM AEAD context path stays **Layer A** (`Result` only). For `lib_q_core::AeadDecryptSemantic::decrypt_semantic`, depend on concrete types from `lib-q-aead`, `lib-q-saturnin`, `lib-q-duplex-aead`, `lib-q-tweak-aead`, or `lib-q-romulus` (see `docs/adr/003-aead-decrypt-layers.md`).
27//!
28//! # Example Usage
29//!
30//! ```rust
31//! use libq::{
32//!     Algorithm,
33//!     HashContext,
34//!     Utils,
35//!     create_hash_context,
36//! };
37//!
38//! fn main() -> Result<(), Box<dyn std::error::Error>> {
39//!     // Initialize hash context
40//!     let mut hash_ctx = create_hash_context();
41//!
42//!     // Hash data via the umbrella-wired `lib-q-hash` provider
43//!     let result = hash_ctx.hash(Algorithm::Shake256, b"Hello, World!");
44//!     match result {
45//!         Ok(hash) => println!("Hash: {}", Utils::bytes_to_hex(&hash)),
46//!         Err(e) => println!("Hash error: {:?}", e),
47//!     }
48//!
49//!     // Generate random bytes
50//!     let random_bytes = Utils::random_bytes(32)?;
51//!     println!("Random bytes: {}", Utils::bytes_to_hex(&random_bytes));
52//!
53//!     // Note: Algorithm operations require feature flags:
54//!     // - For ML-DSA signatures: enable 'ml-dsa' feature
55//!     // - For ML-KEM key exchange: enable 'ml-kem' feature
56//!     // - For FN-DSA signatures: enable 'fn-dsa' feature
57//!     // - For AEAD: use `libq::aead::context()` and enable `saturnin`, `romulus`, or other AEAD features
58//!
59//!     Ok(())
60//! }
61//! ```
62//!
63//! # Feature Flags
64//!
65//! - `std`: Enable standard library features (default)
66//! - `no_std`: Marker feature; this crate uses `#![no_std]` when `std` is off, but path
67//!   dependencies may still enable `std` (see crate README). For embedded builds, prefer
68//!   leaf crates (`lib-q-core`, `lib-q-kem`, …) with `--no-default-features` and `alloc`.
69//! - `wasm`: Enable WebAssembly compilation support
70//! - `ml-kem`: Enable ML-KEM key encapsulation mechanism
71//! - `ml-dsa`: Enable ML-DSA digital signature algorithm
72//! - `slh-dsa`: Enable SLH-DSA (FIPS 205) algorithm metadata and shared types in `lib-q-core`
73//! - `fn-dsa`: Enable FN-DSA digital signature algorithm
74//! - `saturnin`: Enable Saturnin authenticated encryption
75//! - `romulus`: Enable Romulus-N and Romulus-M AEAD (LWC / SKINNY-128-384+)
76//! - `hqc`: Enable HQC key encapsulation mechanism (HQC-128 / HQC-192 / HQC-256)
77//! - `random`: Enable lib-q-random for secure random number generation
78//! - `random-custom-entropy`: Enable custom entropy source support
79//! - `all-algorithms`: Enable all available algorithms
80//! - `zkp`: Expose zero-knowledge / STARK API under `libq::zkp` (STARK core is always linked)
81//! - `zkp-plonky` / `zkp-plonky-*`: Add the Plonky3-derived STARK stack under `libq::zkp::plonky`
82//! - `zkp-parallel`: Rayon-backed parallel proving (STARK)
83//! - `zkp-recursive-experimental`: Experimental recursive proof Merkle path (STARK)
84//! - `security-hardened`: Enable comprehensive security features
85//!
86//! # Security Considerations
87//!
88//! This library is designed with security as the primary concern:
89//! - All sensitive data is automatically zeroized when dropped
90//! - Operations are designed to be constant-time where possible
91//! - Input validation is comprehensive and secure
92//! - Error messages don't leak sensitive information
93//! - Memory allocations are minimized for constrained environments
94//!
95//! # WASM Support
96//!
97//! The library can be compiled to WebAssembly for use in web applications:
98//!
99//! ```bash
100//! wasm-pack build --target web --out-dir pkg
101//! ```
102//!
103//! For `getrandom` on `wasm32-unknown-unknown`, use the same flags as CI:
104//! `CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS='--cfg getrandom_backend="wasm_js" -C panic=abort'`.
105//!
106//! This provides a JavaScript API that mirrors the Rust API where `wasm-bindgen` is enabled.
107
108#![cfg_attr(not(feature = "std"), no_std)]
109#![deny(unsafe_code)]
110#![deny(unused_qualifications)]
111
112#[cfg(not(feature = "std"))]
113extern crate alloc;
114
115#[cfg(all(feature = "alloc", not(feature = "std")))]
116use alloc::boxed::Box;
117
118#[cfg(feature = "alloc")]
119pub mod aead;
120
121// Re-export everything from lib-q-core
122// Re-export the core provider as the main provider
123// Re-export specific types and functions for convenience
124#[cfg(feature = "cb-kem")]
125pub use lib_q_cb_kem::LibQCbKemProvider;
126pub use lib_q_core::{
127    // Context types
128    AeadContext,
129    AeadDecryptSemantic,
130    Algorithm,
131    AlgorithmCategory,
132    DecryptSemanticOutcome,
133    Error,
134    HashContext,
135    KemContext,
136    // Core provider types
137    LibQCryptoProvider as CoreLibQCryptoProvider,
138    Result,
139    SecurityLevel,
140    // Security validation
141    SecurityValidator,
142    SignatureContext,
143    // Version information
144    VERSION,
145    // Algorithm registry
146    algorithms_by_category,
147    algorithms_by_security_level,
148    init,
149    supported_algorithms,
150    version,
151};
152// Re-export specific items from lib-q-core to avoid conflicts
153pub use lib_q_core::{
154    LibQCryptoProvider,
155    Utils,
156    create_kem_context,
157};
158#[cfg(feature = "alloc")]
159pub use lib_q_hash::LibQHashProvider;
160#[cfg(feature = "hqc")]
161pub use lib_q_hqc::LibQHqcProvider;
162// Re-export from other crates for convenience
163#[cfg(any(feature = "ml-kem", feature = "hqc"))]
164pub use lib_q_kem::{
165    LibQKemProvider,
166    available_algorithms,
167};
168// Re-export lib-q-random for random number generation
169#[cfg(feature = "random")]
170pub use lib_q_random::{
171    EntropyQuality,
172    EntropyValidator,
173    LibQRng,
174    new_custom_rng,
175    new_deterministic_rng,
176    new_secure_rng,
177};
178// Note: random-no-std feature integration requires proper feature alignment
179// between lib-q and lib-q-random crates. Currently, the no_std functions
180// are gated behind #[cfg(not(feature = "alloc"))] in lib-q-random, but
181// the main lib-q crate always enables alloc through default features.
182// This integration can be added in a future update.
183#[cfg(feature = "random-custom-entropy")]
184pub use lib_q_random::{
185    custom_entropy::{
186        CustomEntropyConfig,
187        CustomEntropySource,
188        EntropyContext,
189        EntropyQuality as CustomEntropyQuality,
190    },
191    get_custom_entropy_source_info,
192    has_custom_entropy_source,
193    register_custom_entropy_source,
194    unregister_custom_entropy_source,
195};
196/// Legacy boxed `Signature` factory (`std` only; matches `lib-q-sig` / `Box<dyn Signature>`).
197#[cfg(feature = "std")]
198pub use lib_q_sig::create_signature;
199pub use lib_q_sig::{
200    LibQSignatureProvider,
201    available_algorithms as sig_available_algorithms,
202};
203
204/// Create a [`SignatureContext`] with [`LibQSignatureProvider`]
205/// already installed (ML-DSA and SLH-DSA from `lib-q-sig` defaults; FN-DSA when the `fn-dsa`
206/// feature is enabled on this crate).
207///
208/// This is the umbrella-crate entry point: [`lib_q_core::create_signature_context`] returns an
209/// empty context for composition in leaf crates; `libq::create_signature_context` wires the
210/// production signature backend used by this workspace.
211#[cfg(feature = "alloc")]
212pub fn create_signature_context() -> SignatureContext {
213    let provider = LibQSignatureProvider::new()
214        .expect("lib-q-sig LibQSignatureProvider / SecurityValidator initialization");
215    SignatureContext::with_provider(Box::new(provider))
216}
217
218/// Create a [`HashContext`] with [`LibQHashProvider`] installed.
219///
220/// This is the umbrella entry point: [`lib_q_core::create_hash_context`] returns an empty
221/// context; `libq::create_hash_context` wires the hash implementation from `lib-q-hash` (all
222/// registered hash [`Algorithm`] values, `no_std` + `alloc`, and WASM-compatible).
223/// For the same wiring without panicking on setup failure, use
224/// [`lib_q_hash::create_hash_context`] and handle its [`Result`].
225#[cfg(feature = "alloc")]
226pub fn create_hash_context() -> HashContext {
227    let provider = LibQHashProvider::new()
228        .expect("lib-q-hash LibQHashProvider / SecurityValidator initialization");
229    HashContext::with_provider(Box::new(provider))
230}
231
232#[cfg(feature = "zkp")]
233pub mod zkp {
234    //! Zero-knowledge proof types and functions.
235    //!
236    //! Re-exports from `lib-q-zkp` for convenient top-level access.
237
238    pub use lib_q_zkp::api::{
239        MerklePath,
240        prove_membership,
241        prove_preimage,
242        verify_membership,
243        verify_membership_with_depth,
244        verify_preimage,
245    };
246    pub use lib_q_zkp::circuit::{
247        ArithmeticCircuit,
248        CircuitAir,
249        CircuitBuilder,
250    };
251    pub use lib_q_zkp::ip::credential::{
252        IpCredential,
253        compute_credential_commitment,
254        prove_credential_attributes,
255        verify_credential_proof,
256    };
257    /// Plonky3-derived STARK components (batch/uni STARK, Keccak AIR, lookup, multilinear util).
258    ///
259    /// Enable via `zkp-plonky` or a granular `zkp-plonky-*` feature on the `lib-q` crate.
260    #[cfg(any(
261        feature = "zkp-plonky",
262        feature = "zkp-plonky-keccak-air",
263        feature = "zkp-plonky-lookup",
264        feature = "zkp-plonky-uni-stark",
265        feature = "zkp-plonky-batch-stark",
266    ))]
267    pub use lib_q_zkp::plonky;
268    pub use lib_q_zkp::stark::{
269        StarkProver,
270        StarkVerifier,
271        default_config,
272    };
273    pub use lib_q_zkp::{
274        ProofMetadata,
275        ProofType,
276        ZkpField,
277        ZkpProof,
278        ZkpProver,
279        ZkpVerifier,
280    };
281}
282
283// Note: hash, aead, and utils features are handled by individual crates
284// and don't need separate feature flags in the main lib-q crate
285
286// WASM bindings
287#[cfg(feature = "wasm")]
288pub mod wasm {
289    //! WebAssembly bindings for lib-Q
290    //!
291    //! This module provides JavaScript-compatible bindings for use in web applications.
292    //! It integrates with the new modular architecture and provides comprehensive
293    //! cryptographic functionality for web environments.
294
295    // Re-export WASM components from lib-q-core
296    // Import ToString trait and String type for string conversions
297    #[cfg(not(feature = "std"))]
298    use alloc::boxed::Box;
299    #[cfg(not(feature = "std"))]
300    use alloc::string::{
301        String,
302        ToString,
303    };
304    #[cfg(feature = "std")]
305    use std::string::{
306        String,
307        ToString,
308    };
309
310    pub use lib_q_core::wasm::*;
311    use wasm_bindgen::prelude::*;
312
313    /// Initialize the library for WASM usage
314    #[wasm_bindgen]
315    pub fn init_wasm() -> Result<(), JsValue> {
316        lib_q_core::init().map_err(|e| lib_q_core::wasm_common::wasm_js_error("LIB_Q_INIT", e))
317    }
318
319    /// Get the library version
320    #[wasm_bindgen]
321    pub fn get_version() -> String {
322        lib_q_core::version().to_string()
323    }
324
325    /// Check if an algorithm is supported
326    #[wasm_bindgen]
327    pub fn is_algorithm_supported_wasm(algorithm: &str) -> bool {
328        // Use the new provider manager for algorithm support checking
329        let manager = WasmProviderManager::new();
330        manager.is_algorithm_supported(algorithm)
331    }
332
333    /// Get supported algorithms by category
334    #[wasm_bindgen]
335    pub fn get_supported_algorithms_wasm() -> JsValue {
336        // Use the new provider manager for algorithm listing
337        let manager = WasmProviderManager::new();
338        let algorithms = manager.get_all_algorithms();
339        JsValue::from_str(&algorithms)
340    }
341
342    /// Get library information for WASM
343    #[wasm_bindgen]
344    pub fn get_library_info_wasm() -> String {
345        get_library_info()
346    }
347
348    /// Get security recommendations
349    #[wasm_bindgen]
350    pub fn get_security_recommendations_wasm() -> String {
351        let manager = WasmProviderManager::new();
352        manager.get_security_recommendations()
353    }
354
355    /// Get performance benchmarks
356    #[wasm_bindgen]
357    pub fn get_performance_benchmarks_wasm() -> String {
358        let manager = WasmProviderManager::new();
359        manager.get_performance_benchmarks()
360    }
361
362    /// Create a new KEM context for WASM
363    #[wasm_bindgen]
364    pub fn create_kem_context() -> WasmKemContext {
365        WasmKemContext::new()
366    }
367
368    /// Create a new Signature context for WASM backed by `lib-q-sig` (same wiring as native
369    /// `SignatureContext` with [`LibQSignatureProvider`](lib_q_sig::LibQSignatureProvider)).
370    #[wasm_bindgen]
371    pub fn create_signature_context() -> WasmSignatureContext {
372        let provider = lib_q_sig::LibQSignatureProvider::new()
373            .expect("lib-q-sig LibQSignatureProvider / SecurityValidator initialization");
374        WasmSignatureContext::from_signature_context(lib_q_core::SignatureContext::with_provider(
375            Box::new(provider),
376        ))
377    }
378
379    /// Create a new Hash context for WASM backed by `lib-q-hash` (same wiring as
380    /// [`crate::create_hash_context`]).
381    #[wasm_bindgen]
382    pub fn create_hash_context() -> WasmHashContext {
383        let provider = lib_q_hash::LibQHashProvider::new()
384            .expect("lib-q-hash LibQHashProvider / SecurityValidator initialization");
385        WasmHashContext::from_hash_context(lib_q_core::HashContext::with_provider(Box::new(
386            provider,
387        )))
388    }
389
390    /// Create an AEAD context for WASM backed by `lib-q-aead` (same wiring as `libq::aead::context`).
391    #[wasm_bindgen]
392    pub fn create_aead_context() -> WasmAeadContext {
393        WasmAeadContext::from_aead_context(lib_q_core::AeadContext::with_aead_operations(Box::new(
394            lib_q_aead::LibQAeadProvider::new()
395                .expect("lib-q-aead LibQAeadProvider / SecurityValidator initialization"),
396        )))
397    }
398
399    /// Create a new provider manager for WASM
400    #[wasm_bindgen]
401    pub fn create_provider_manager() -> WasmProviderManager {
402        WasmProviderManager::new()
403    }
404
405    /// Generate secure random bytes for WASM
406    #[wasm_bindgen]
407    pub fn generate_random_bytes(length: usize) -> Result<js_sys::Uint8Array, JsValue> {
408        random_bytes(length).map_err(|e| lib_q_core::wasm_common::wasm_js_error("LIB_Q_RANDOM", e))
409    }
410
411    /// Convert bytes to hexadecimal string
412    #[wasm_bindgen]
413    pub fn bytes_to_hex_wasm(data: &js_sys::Uint8Array) -> String {
414        bytes_to_hex(data)
415    }
416
417    /// Convert hexadecimal string to bytes
418    #[wasm_bindgen]
419    pub fn hex_to_bytes_wasm(hex: &str) -> Result<js_sys::Uint8Array, JsValue> {
420        hex_to_bytes(hex).map_err(|e| lib_q_core::wasm_common::wasm_js_error("LIB_Q_HEX", e))
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    #[allow(unused_imports)]
427    use lib_q_core::{
428        CryptoProvider,
429        KemOperations, // Required for trait methods to be in scope
430    };
431    #[cfg(feature = "hqc")]
432    use lib_q_hqc::HqcParams;
433
434    use super::*;
435    #[cfg(feature = "alloc")]
436    use crate::aead;
437
438    #[test]
439    fn test_init() {
440        assert!(init().is_ok());
441    }
442
443    #[test]
444    fn test_version() {
445        assert!(!version().is_empty());
446        assert_eq!(version(), VERSION);
447    }
448
449    #[test]
450    fn test_supported_algorithms() {
451        let algorithms = algorithms_by_category(AlgorithmCategory::Hash);
452        assert!(
453            !algorithms.is_empty(),
454            "Should have at least one hash algorithm"
455        );
456    }
457
458    /// `libq::create_signature_context` must ship with `LibQSignatureProvider` wired so ML-DSA works
459    /// without callers manually calling `set_provider`.
460    #[cfg(feature = "alloc")]
461    #[test]
462    fn test_signature_context_pre_wired_ml_dsa_roundtrip() {
463        let mut ctx = create_signature_context();
464        let keypair = ctx
465            .generate_keypair(Algorithm::MlDsa65, None)
466            .expect("ML-DSA-65 keygen with pre-wired provider");
467        let message = b"lib-q umbrella signature integration";
468        let signature = ctx
469            .sign(Algorithm::MlDsa65, keypair.secret_key(), message, None)
470            .expect("sign");
471        assert!(
472            ctx.verify(
473                Algorithm::MlDsa65,
474                keypair.public_key(),
475                message,
476                signature.as_slice(),
477            )
478            .expect("verify")
479        );
480    }
481
482    #[cfg(all(feature = "alloc", feature = "fn-dsa"))]
483    #[test]
484    fn test_signature_context_fn_dsa512_roundtrip() {
485        let mut ctx = create_signature_context();
486        let keypair = ctx
487            .generate_keypair(Algorithm::FnDsa512, None)
488            .expect("FN-DSA-512 keygen");
489        let message = b"fn-dsa umbrella path";
490        let signature = ctx
491            .sign(Algorithm::FnDsa512, keypair.secret_key(), message, None)
492            .expect("sign");
493        assert!(
494            ctx.verify(
495                Algorithm::FnDsa512,
496                keypair.public_key(),
497                message,
498                signature.as_slice(),
499            )
500            .expect("verify")
501        );
502    }
503
504    #[test]
505    fn test_algorithms_by_security_level() {
506        let level_1_algorithms = algorithms_by_security_level(SecurityLevel::Level1 as u32);
507        assert!(
508            !level_1_algorithms.is_empty(),
509            "Should have at least one Level 1 algorithm"
510        );
511    }
512
513    #[test]
514    fn test_unified_api() {
515        // Test that the unified API works correctly
516        let provider = LibQCryptoProvider::new();
517        assert!(provider.is_ok(), "Provider should be created successfully");
518
519        let provider = provider.unwrap();
520
521        // Test KEM operations - core provider should always return NotImplemented
522        let kem_result = provider
523            .kem()
524            .unwrap()
525            .generate_keypair(Algorithm::MlKem512, None);
526
527        // Core provider always returns NotImplemented for KEM operations
528        assert!(kem_result.is_err());
529        if let Err(Error::NotImplemented { feature }) = kem_result {
530            assert!(
531                feature.contains("ML-KEM implementations are provided by the main lib-q crate")
532            );
533        } else {
534            panic!("Expected NotImplemented error for KEM operations");
535        }
536
537        // Test that lib-q-kem provider works when used directly
538        #[cfg(feature = "ml-kem")]
539        {
540            let kem_provider = LibQKemProvider::new().unwrap();
541            let kem_result = kem_provider.generate_keypair(Algorithm::MlKem512, None);
542            assert!(
543                kem_result.is_ok(),
544                "ML-KEM key generation should succeed with lib-q-kem provider"
545            );
546            let keypair = kem_result.unwrap();
547            assert!(!keypair.public_key().as_bytes().is_empty());
548            assert!(!keypair.secret_key().as_bytes().is_empty());
549        }
550
551        #[cfg(feature = "hqc")]
552        {
553            let kem_provider = LibQKemProvider::new().unwrap();
554            let keypair = kem_provider
555                .generate_keypair(Algorithm::Hqc128, None)
556                .expect("HQC-128 key generation with lib-q-kem provider");
557            assert_eq!(
558                keypair.public_key().as_bytes().len(),
559                lib_q_hqc::Hqc1Params::PUBLIC_KEY_BYTES
560            );
561            assert_eq!(
562                keypair.secret_key().as_bytes().len(),
563                lib_q_hqc::Hqc1Params::SECRET_KEY_BYTES
564            );
565            let (ciphertext, shared1) = kem_provider
566                .encapsulate(Algorithm::Hqc128, &keypair.public_key, None)
567                .expect("HQC-128 encapsulate");
568            assert_eq!(ciphertext.len(), lib_q_hqc::Hqc1Params::CIPHERTEXT_BYTES);
569            let shared2 = kem_provider
570                .decapsulate(Algorithm::Hqc128, &keypair.secret_key, &ciphertext)
571                .expect("HQC-128 decapsulate");
572            assert_eq!(shared1, shared2);
573        }
574
575        // Test signature operations - ML-DSA requires feature flag
576        let sig_result = provider
577            .signature()
578            .unwrap()
579            .generate_keypair(Algorithm::MlDsa65, None);
580        #[cfg(feature = "ml-dsa")]
581        {
582            // Core provider should return NotImplemented for ML-DSA operations
583            // The actual ML-DSA implementation is provided by the main lib-q crate
584            assert!(sig_result.is_err());
585            if let Err(Error::NotImplemented { feature }) = sig_result {
586                assert!(
587                    feature.contains("ML-DSA implementations are provided by the main lib-q crate")
588                );
589            } else {
590                panic!("Expected NotImplemented error for ML-DSA in core provider");
591            }
592        }
593        #[cfg(not(feature = "ml-dsa"))]
594        {
595            assert!(sig_result.is_err());
596            if let Err(Error::NotImplemented { feature }) = sig_result {
597                assert!(
598                    feature.contains("ML-DSA implementations are provided by the main lib-q crate")
599                );
600            } else {
601                panic!("Expected NotImplemented error for ML-DSA without feature flag");
602            }
603        }
604
605        // Test hash operations
606        let hash_result = provider
607            .hash()
608            .unwrap()
609            .hash(Algorithm::Sha3_256, b"test data");
610        // Hash operations should always return NotImplemented since implementations are in separate crates
611        assert!(hash_result.is_err());
612        if let Err(Error::NotImplemented { feature }) = hash_result {
613            assert!(feature.contains("SHA3 implementations are provided by the main lib-q crate"));
614        } else {
615            panic!("Expected NotImplemented error for hash operations");
616        }
617
618        // Test AEAD operations
619        #[cfg(not(feature = "std"))]
620        use alloc::vec;
621
622        use lib_q_core::traits::{
623            AeadKey,
624            Nonce,
625        };
626        // Generate proper random key and nonce that pass security validation
627        let mut key_bytes = vec![0u8; 32];
628        let mut nonce_bytes = vec![0u8; 16]; // Saturnin requires 16-byte nonce
629
630        // Use a simple but valid key pattern that should pass entropy checks
631        for (i, byte) in key_bytes.iter_mut().enumerate() {
632            *byte = (i as u8).wrapping_mul(0x1F).wrapping_add(0x2B);
633        }
634        for (i, byte) in nonce_bytes.iter_mut().enumerate() {
635            *byte = (i as u8).wrapping_mul(0x3D).wrapping_add(0x7E);
636        }
637
638        let key = AeadKey::new(key_bytes);
639        let nonce = Nonce::new(nonce_bytes);
640        let aead_result = provider.aead().unwrap().encrypt(
641            Algorithm::Saturnin,
642            &key,
643            &nonce,
644            b"plaintext",
645            Some(b"associated data"),
646        );
647        // `LibQCryptoProvider::new()` uses `LibQAeadStubProvider` for AEAD; use `libq::aead::context()` or
648        // `lib_q_aead::LibQAeadProvider` for registry-backed AEAD.
649        assert!(aead_result.is_err());
650        match aead_result {
651            Err(Error::NotImplemented { feature }) => {
652                assert!(
653                    feature.contains("LibQAeadProvider") || feature.contains("libq::aead::context")
654                );
655            }
656            Err(e) => {
657                panic!(
658                    "Expected NotImplemented error for AEAD operations, got: {:?}",
659                    e
660                );
661            }
662            Ok(_) => {
663                panic!("Expected error for AEAD operations, but got success");
664            }
665        }
666    }
667
668    #[cfg(feature = "alloc")]
669    #[test]
670    fn test_create_hash_context_sha3_256_roundtrip() {
671        let mut ctx = create_hash_context();
672        let out = ctx
673            .hash(Algorithm::Sha3_256, b"lib-q umbrella hash")
674            .expect("SHA3-256 with pre-wired LibQHashProvider");
675        assert_eq!(out.len(), 32);
676    }
677
678    #[cfg(feature = "alloc")]
679    #[test]
680    fn test_create_aead_context_shake256_roundtrip() {
681        use lib_q_core::traits::{
682            AeadKey,
683            Nonce,
684        };
685
686        let mut key_bytes = vec![0u8; 32];
687        let mut nonce_bytes = vec![0u8; 16];
688        for (i, byte) in key_bytes.iter_mut().enumerate() {
689            *byte = (i as u8).wrapping_mul(0x1F).wrapping_add(0x2B);
690        }
691        for (i, byte) in nonce_bytes.iter_mut().enumerate() {
692            *byte = (i as u8).wrapping_mul(0x3D).wrapping_add(0x7E);
693        }
694
695        let key = AeadKey::new(key_bytes);
696        let nonce = Nonce::new(nonce_bytes);
697        let plaintext = b"hello lib-q aead bridge";
698        let ad = b"associated data";
699
700        let mut ctx = aead::context();
701        let ciphertext = ctx
702            .encrypt(
703                Algorithm::Shake256Aead,
704                &key,
705                &nonce,
706                plaintext.as_slice(),
707                Some(ad.as_slice()),
708            )
709            .expect("SHAKE256-AEAD encrypt");
710
711        let recovered = ctx
712            .decrypt(
713                Algorithm::Shake256Aead,
714                &key,
715                &nonce,
716                &ciphertext,
717                Some(ad.as_slice()),
718            )
719            .expect("SHAKE256-AEAD decrypt");
720
721        assert_eq!(recovered.as_slice(), plaintext.as_slice());
722    }
723
724    #[cfg(all(feature = "alloc", feature = "duplex-sponge-aead"))]
725    #[test]
726    fn test_create_aead_context_duplex_sponge_roundtrip() {
727        use lib_q_core::traits::{
728            AeadKey,
729            Nonce,
730        };
731
732        let mut key_bytes = vec![0u8; 32];
733        let mut nonce_bytes = vec![0u8; 16];
734        for (i, byte) in key_bytes.iter_mut().enumerate() {
735            *byte = (i as u8).wrapping_mul(0x11).wrapping_add(0x3C);
736        }
737        for (i, byte) in nonce_bytes.iter_mut().enumerate() {
738            *byte = (i as u8).wrapping_mul(0x29).wrapping_add(0x71);
739        }
740
741        let key = AeadKey::new(key_bytes);
742        let nonce = Nonce::new(nonce_bytes);
743        let plaintext = b"duplex-sponge roundtrip";
744        let ad = b"ad";
745
746        let mut ctx = aead::context();
747        let ciphertext = ctx
748            .encrypt(
749                Algorithm::DuplexSpongeAead,
750                &key,
751                &nonce,
752                plaintext.as_slice(),
753                Some(ad.as_slice()),
754            )
755            .expect("Duplex-Sponge-AEAD encrypt");
756
757        let recovered = ctx
758            .decrypt(
759                Algorithm::DuplexSpongeAead,
760                &key,
761                &nonce,
762                &ciphertext,
763                Some(ad.as_slice()),
764            )
765            .expect("Duplex-Sponge-AEAD decrypt");
766
767        assert_eq!(recovered.as_slice(), plaintext.as_slice());
768    }
769
770    #[cfg(all(feature = "alloc", feature = "tweak-aead"))]
771    #[test]
772    fn test_create_aead_context_tweak_aead_roundtrip() {
773        use lib_q_core::traits::{
774            AeadKey,
775            Nonce,
776        };
777
778        let mut key_bytes = vec![0u8; 32];
779        let mut nonce_bytes = vec![0u8; 16];
780        for (i, byte) in key_bytes.iter_mut().enumerate() {
781            *byte = (i as u8).wrapping_mul(0x13).wrapping_add(0x2E);
782        }
783        for (i, byte) in nonce_bytes.iter_mut().enumerate() {
784            *byte = (i as u8).wrapping_mul(0x2B).wrapping_add(0x6D);
785        }
786
787        let key = AeadKey::new(key_bytes);
788        let nonce = Nonce::new(nonce_bytes);
789        let plaintext = b"tweak aead roundtrip";
790        let ad = b"ad2";
791
792        let mut ctx = aead::context();
793        let ciphertext = ctx
794            .encrypt(
795                Algorithm::TweakAead,
796                &key,
797                &nonce,
798                plaintext.as_slice(),
799                Some(ad.as_slice()),
800            )
801            .expect("Tweak-AEAD encrypt");
802
803        let recovered = ctx
804            .decrypt(
805                Algorithm::TweakAead,
806                &key,
807                &nonce,
808                &ciphertext,
809                Some(ad.as_slice()),
810            )
811            .expect("Tweak-AEAD decrypt");
812
813        assert_eq!(recovered.as_slice(), plaintext.as_slice());
814    }
815
816    #[cfg(all(feature = "alloc", feature = "romulus"))]
817    #[test]
818    fn test_create_aead_context_romulus_n_roundtrip() {
819        use lib_q_core::traits::{
820            AeadKey,
821            Nonce,
822        };
823
824        let mut key_bytes = vec![0u8; 16];
825        let mut nonce_bytes = vec![0u8; 16];
826        for (i, byte) in key_bytes.iter_mut().enumerate() {
827            *byte = (i as u8).wrapping_mul(0x17).wrapping_add(0x31);
828        }
829        for (i, byte) in nonce_bytes.iter_mut().enumerate() {
830            *byte = (i as u8).wrapping_mul(0x2Du8).wrapping_add(0x6Au8);
831        }
832
833        let key = AeadKey::new(key_bytes);
834        let nonce = Nonce::new(nonce_bytes);
835        let plaintext = b"romulus-n roundtrip";
836        let ad = b"ad-rn";
837
838        let mut ctx = aead::context();
839        let ciphertext = ctx
840            .encrypt(
841                Algorithm::RomulusN,
842                &key,
843                &nonce,
844                plaintext.as_slice(),
845                Some(ad.as_slice()),
846            )
847            .expect("Romulus-N encrypt");
848
849        let recovered = ctx
850            .decrypt(
851                Algorithm::RomulusN,
852                &key,
853                &nonce,
854                &ciphertext,
855                Some(ad.as_slice()),
856            )
857            .expect("Romulus-N decrypt");
858
859        assert_eq!(recovered.as_slice(), plaintext.as_slice());
860    }
861
862    #[cfg(all(feature = "alloc", feature = "romulus"))]
863    #[test]
864    fn test_create_aead_context_romulus_m_roundtrip() {
865        use lib_q_core::traits::{
866            AeadKey,
867            Nonce,
868        };
869
870        let mut key_bytes = vec![0u8; 16];
871        let mut nonce_bytes = vec![0u8; 16];
872        for (i, byte) in key_bytes.iter_mut().enumerate() {
873            *byte = (i as u8).wrapping_mul(0x19).wrapping_add(0x2Fu8);
874        }
875        for (i, byte) in nonce_bytes.iter_mut().enumerate() {
876            *byte = (i as u8).wrapping_mul(0x2Fu8).wrapping_add(0x68u8);
877        }
878
879        let key = AeadKey::new(key_bytes);
880        let nonce = Nonce::new(nonce_bytes);
881        let plaintext = b"romulus-m roundtrip";
882        let ad = b"ad-rm";
883
884        let mut ctx = aead::context();
885        let ciphertext = ctx
886            .encrypt(
887                Algorithm::RomulusM,
888                &key,
889                &nonce,
890                plaintext.as_slice(),
891                Some(ad.as_slice()),
892            )
893            .expect("Romulus-M encrypt");
894
895        let recovered = ctx
896            .decrypt(
897                Algorithm::RomulusM,
898                &key,
899                &nonce,
900                &ciphertext,
901                Some(ad.as_slice()),
902            )
903            .expect("Romulus-M decrypt");
904
905        assert_eq!(recovered.as_slice(), plaintext.as_slice());
906    }
907}