Skip to main content

libq/
lib.rs

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