1use crate::error::Result;
9#[cfg(feature = "alloc")]
10use crate::traits::*;
11
12#[cfg(feature = "alloc")]
13extern crate alloc;
14#[cfg(feature = "alloc")]
15use alloc::{
16 format,
17 string::String,
18 vec::Vec,
19};
20
21#[cfg(feature = "getrandom")]
22#[allow(unused_imports)] use getrandom;
24pub use lib_q_types::{
25 Algorithm,
26 AlgorithmCategory,
27 SecurityLevel,
28};
29#[cfg(any(feature = "getrandom", feature = "rand"))]
42#[allow(unused_imports)]
43use rand_core::Rng;
44use subtle::ConstantTimeEq;
45
46#[cfg(feature = "alloc")]
51pub trait KemOperations {
52 fn generate_keypair(
53 &self,
54 algorithm: Algorithm,
55 randomness: Option<&[u8]>,
56 ) -> Result<KemKeypair>;
57 fn encapsulate(
58 &self,
59 algorithm: Algorithm,
60 public_key: &KemPublicKey,
61 randomness: Option<&[u8]>,
62 ) -> Result<(Vec<u8>, Vec<u8>)>;
63 fn decapsulate(
64 &self,
65 algorithm: Algorithm,
66 secret_key: &KemSecretKey,
67 ciphertext: &[u8],
68 ) -> Result<Vec<u8>>;
69 fn derive_public_key(
70 &self,
71 algorithm: Algorithm,
72 secret_key: &KemSecretKey,
73 ) -> Result<KemPublicKey>;
74}
75
76#[cfg(feature = "alloc")]
78pub trait SignatureOperations {
79 fn generate_keypair(
80 &self,
81 algorithm: Algorithm,
82 randomness: Option<&[u8]>,
83 ) -> Result<SigKeypair>;
84 fn sign(
85 &self,
86 algorithm: Algorithm,
87 secret_key: &SigSecretKey,
88 message: &[u8],
89 randomness: Option<&[u8]>,
90 ) -> Result<Vec<u8>>;
91 fn verify(
92 &self,
93 algorithm: Algorithm,
94 public_key: &SigPublicKey,
95 message: &[u8],
96 signature: &[u8],
97 ) -> Result<bool>;
98}
99
100#[cfg(feature = "alloc")]
102pub trait HashOperations {
103 fn hash(&self, algorithm: Algorithm, data: &[u8]) -> Result<Vec<u8>>;
104}
105
106#[cfg(feature = "alloc")]
113pub trait AeadOperations {
114 fn encrypt(
115 &self,
116 algorithm: Algorithm,
117 key: &AeadKey,
118 nonce: &Nonce,
119 plaintext: &[u8],
120 associated_data: Option<&[u8]>,
121 ) -> Result<Vec<u8>>;
122 fn decrypt(
123 &self,
124 algorithm: Algorithm,
125 key: &AeadKey,
126 nonce: &Nonce,
127 ciphertext: &[u8],
128 associated_data: Option<&[u8]>,
129 ) -> Result<Vec<u8>>;
130}
131
132pub trait CryptoProvider: Send + Sync {
134 #[cfg(feature = "alloc")]
135 fn kem(&self) -> Option<&dyn KemOperations>;
136 #[cfg(feature = "alloc")]
137 fn signature(&self) -> Option<&dyn SignatureOperations>;
138 #[cfg(feature = "alloc")]
139 fn hash(&self) -> Option<&dyn HashOperations>;
140 #[cfg(feature = "alloc")]
141 fn aead(&self) -> Option<&dyn AeadOperations>;
142}
143
144#[cfg(feature = "alloc")]
150pub use crate::contexts::KemContext;
151
152pub struct Utils;
171
172impl Utils {
173 #[cfg(feature = "rand")]
180 pub fn random_bytes(length: usize) -> Result<Vec<u8>> {
181 const MIN_RANDOM_SIZE: usize = 1;
182 const MAX_RANDOM_SIZE: usize = 1024 * 1024; if !(MIN_RANDOM_SIZE..=MAX_RANDOM_SIZE).contains(&length) {
184 return Err(crate::error::Error::RandomBytesLengthInvalid {
185 min: MIN_RANDOM_SIZE,
186 max: MAX_RANDOM_SIZE,
187 requested: length,
188 });
189 }
190
191 let mut bytes = alloc::vec![0u8; length];
192
193 let mut rng = rand::rng();
195 rng.fill_bytes(&mut bytes);
196
197 Ok(bytes)
199 }
200
201 #[cfg(all(feature = "getrandom", not(feature = "rand")))]
202 #[cfg(feature = "alloc")]
203 pub fn random_bytes(length: usize) -> Result<Vec<u8>> {
204 const MIN_RANDOM_SIZE: usize = 1;
205 const MAX_RANDOM_SIZE: usize = 1024 * 1024; if !(MIN_RANDOM_SIZE..=MAX_RANDOM_SIZE).contains(&length) {
207 return Err(crate::error::Error::RandomBytesLengthInvalid {
208 min: MIN_RANDOM_SIZE,
209 max: MAX_RANDOM_SIZE,
210 requested: length,
211 });
212 }
213
214 let mut bytes = alloc::vec![0u8; length];
215
216 getrandom::fill(&mut bytes).map_err(|_| crate::error::Error::RandomGenerationFailed {
222 operation: String::from("random_bytes"),
223 })?;
224
225 Ok(bytes)
227 }
228
229 #[cfg(all(feature = "getrandom", not(feature = "rand")))]
230 #[cfg(not(feature = "alloc"))]
231 pub fn random_bytes(length: usize) -> Result<&'static [u8]> {
232 const MIN_RANDOM_SIZE: usize = 1;
233 const MAX_RANDOM_SIZE: usize = 1024; if !(MIN_RANDOM_SIZE..=MAX_RANDOM_SIZE).contains(&length) {
235 return Err(crate::error::Error::RandomBytesLengthInvalid {
236 min: MIN_RANDOM_SIZE,
237 max: MAX_RANDOM_SIZE,
238 requested: length,
239 });
240 }
241
242 #[cfg(target_arch = "wasm32")]
247 {
248 return Err(crate::error::Error::RandomGenerationFailed {
250 operation: "random_bytes",
251 });
252 }
253
254 #[cfg(not(target_arch = "wasm32"))]
255 {
256 return Err(crate::error::Error::RandomGenerationFailed {
259 operation: "random_bytes",
260 });
261 }
262 }
263
264 #[cfg(not(any(feature = "rand", feature = "getrandom")))]
265 #[cfg(feature = "alloc")]
266 pub fn random_bytes(_length: usize) -> Result<Vec<u8>> {
267 Err(crate::error::Error::RandomGenerationFailed {
268 operation: String::from("random_bytes"),
269 })
270 }
271
272 #[cfg(not(any(feature = "rand", feature = "getrandom")))]
273 #[cfg(not(feature = "alloc"))]
274 pub fn random_bytes(_length: usize) -> Result<&'static [u8]> {
275 Err(crate::error::Error::RandomGenerationFailed {
276 operation: "random_bytes",
277 })
278 }
279
280 #[cfg(feature = "alloc")]
282 pub fn bytes_to_hex(bytes: &[u8]) -> String {
283 let mut hex = String::new();
284 for &byte in bytes {
285 hex.push_str(&format!("{:02x}", byte));
286 }
287 hex
288 }
289
290 #[cfg(not(feature = "alloc"))]
291 pub fn bytes_to_hex(_bytes: &[u8]) -> &'static str {
292 "hex conversion not available in no_std without alloc"
293 }
294
295 #[cfg(feature = "alloc")]
302 pub fn hex_to_bytes(hex: &str) -> Result<Vec<u8>> {
303 use crate::error::HexDecodeError;
304
305 let hex = hex.trim();
306
307 if !hex.len().is_multiple_of(2) {
308 return Err(crate::error::Error::HexDecode(HexDecodeError::OddLength {
309 char_count: hex.len(),
310 }));
311 }
312
313 let mut bytes = Vec::with_capacity(hex.len() / 2);
314 for i in (0..hex.len()).step_by(2) {
315 let byte = u8::from_str_radix(&hex[i..i + 2], 16).map_err(|_| {
316 crate::error::Error::HexDecode(HexDecodeError::InvalidDigit {
317 pair_start: i,
318 char_count: hex.len(),
319 })
320 })?;
321 bytes.push(byte);
322 }
323
324 Ok(bytes)
325 }
326
327 #[cfg(not(feature = "alloc"))]
328 pub fn hex_to_bytes(_hex: &str) -> Result<&'static [u8]> {
329 Err(crate::error::Error::MemoryAllocationFailed {
330 operation: "hex_to_bytes",
331 })
332 }
333
334 pub fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
336 if a.len() != b.len() {
337 return false;
338 }
339 a.ct_eq(b).into()
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 #[cfg(feature = "alloc")]
347 use crate::contexts::{
348 HashContext,
349 SignatureContext,
350 };
351
352 #[test]
353 fn test_provider_architecture() {
354 #[cfg(feature = "std")]
355 {
356 let mut ctx = KemContext::with_default_provider();
358
359 let result = ctx.generate_keypair(Algorithm::MlKem512, None);
361 assert!(result.is_err());
362
363 match result {
364 Err(crate::error::Error::NotImplemented { feature }) => {
365 assert!(
366 feature.contains(
367 "ML-KEM implementations are provided by the main lib-q crate"
368 )
369 );
370 }
371 Err(crate::error::Error::ProviderNotConfigured { operation }) => {
372 assert_eq!(operation, "KEM");
373 }
374 _ => panic!("Expected NotImplemented or ProviderNotConfigured"),
375 }
376 }
377
378 #[cfg(feature = "alloc")]
380 {
381 let mut ctx = KemContext::new();
382 let result = ctx.generate_keypair(Algorithm::MlKem512, None);
383 assert!(result.is_err());
384
385 if let Err(crate::error::Error::ProviderNotConfigured { operation }) = result {
386 assert_eq!(operation, "KEM");
387 } else {
388 panic!("Expected ProviderNotConfigured error, got different error type");
389 }
390 }
391 }
392
393 #[test]
394 fn test_algorithm_security_levels() {
395 assert_eq!(Algorithm::MlKem512.security_level(), 1);
396 assert_eq!(Algorithm::MlKem768.security_level(), 3);
397 assert_eq!(Algorithm::MlKem1024.security_level(), 4);
398 assert_eq!(Algorithm::MlDsa44.security_level(), 1);
399 assert_eq!(Algorithm::MlDsa65.security_level(), 3);
400 assert_eq!(Algorithm::MlDsa87.security_level(), 4);
401 }
402
403 #[test]
404 fn test_algorithm_categories() {
405 assert_eq!(Algorithm::MlKem512.category(), AlgorithmCategory::Kem);
406 assert_eq!(Algorithm::MlDsa44.category(), AlgorithmCategory::Signature);
407 assert_eq!(Algorithm::Shake256.category(), AlgorithmCategory::Hash);
408 }
409
410 #[test]
411 #[cfg(feature = "alloc")]
412 fn test_kem_context() {
413 let mut ctx = KemContext::new();
414 let result = ctx.generate_keypair(Algorithm::MlKem512, None);
415 assert!(result.is_err());
416 if let Err(crate::error::Error::ProviderNotConfigured { operation }) = result {
417 assert_eq!(operation, "KEM");
418 } else {
419 panic!("Expected ProviderNotConfigured error");
420 }
421 }
422
423 #[test]
424 #[cfg(feature = "alloc")]
425 fn test_signature_context() {
426 let mut ctx = SignatureContext::new();
427 let result = ctx.generate_keypair(Algorithm::MlDsa65, None);
428 assert!(result.is_err());
429 if let Err(crate::error::Error::ProviderNotConfigured { operation }) = result {
430 assert_eq!(operation, "signature");
431 } else {
432 panic!("Expected ProviderNotConfigured error");
433 }
434 }
435
436 #[test]
437 #[cfg(feature = "alloc")]
438 fn test_hash_context() {
439 let mut ctx = HashContext::new();
440 let result = ctx.hash(Algorithm::Shake256, b"test");
441 assert!(result.is_err());
442 if let Err(crate::error::Error::ProviderNotConfigured { operation }) = result {
443 assert_eq!(operation, "hash");
444 } else {
445 panic!("Expected ProviderNotConfigured error");
446 }
447 }
448
449 #[test]
450 fn test_utils() {
451 #[cfg(feature = "getrandom")]
452 {
453 let bytes = Utils::random_bytes(32).unwrap();
454 assert_eq!(bytes.len(), 32);
455 }
456
457 #[cfg(feature = "alloc")]
458 {
459 let hex = Utils::bytes_to_hex(&[0x01, 0x23, 0x45, 0x67]);
460 assert_eq!(hex, "01234567");
461
462 let decoded = Utils::hex_to_bytes(&hex).unwrap();
463 assert_eq!(decoded, alloc::vec![0x01, 0x23, 0x45, 0x67]);
464 }
465 }
466
467 #[test]
468 fn test_random_bytes_generation() {
469 match Utils::random_bytes(32) {
471 Ok(bytes1) => {
472 let bytes2 = Utils::random_bytes(32).expect("Should generate random bytes");
473 assert_eq!(bytes1.len(), 32);
474 assert_eq!(bytes2.len(), 32);
475
476 assert_ne!(
479 bytes1, bytes2,
480 "Random bytes should be different on subsequent calls"
481 );
482
483 let all_zero1 = bytes1.iter().all(|&b| b == 0);
485 let all_zero2 = bytes2.iter().all(|&b| b == 0);
486
487 assert!(!all_zero1, "Random bytes should not all be zero");
488 assert!(!all_zero2, "Random bytes should not all be zero");
489 }
490 Err(crate::error::Error::RandomGenerationFailed { .. }) => {
491 }
494 Err(e) => {
495 panic!("Unexpected error: {:?}", e);
496 }
497 }
498 }
499
500 #[test]
501 fn test_constant_time_compare() {
502 assert!(Utils::constant_time_compare(b"hello", b"hello"));
503 assert!(!Utils::constant_time_compare(b"hello", b"world"));
504 assert!(!Utils::constant_time_compare(b"hello", b"hell"));
505 }
506
507 #[cfg(feature = "getrandom")]
508 #[test]
509 fn test_random_bytes_entropy_quality() {
510 const NUM_SAMPLES: usize = 1000;
512 const BYTE_LENGTH: usize = 32;
513
514 let mut byte_counts = [0u32; 256];
515 let mut total_bytes = 0u32;
516
517 for _ in 0..NUM_SAMPLES {
518 let bytes = Utils::random_bytes(BYTE_LENGTH).expect("Should generate random bytes");
519 for &byte in &bytes {
520 byte_counts[byte as usize] += 1;
521 total_bytes += 1;
522 }
523 }
524
525 let zero_count = byte_counts.iter().filter(|&&count| count == 0).count();
527 assert!(
528 zero_count < 50,
529 "Too many byte values are missing from random generation"
530 );
531
532 let expected_per_byte = total_bytes as f64 / 256.0;
535 let chi_sq: f64 = byte_counts
536 .iter()
537 .map(|&count| {
538 let d = count as f64 - expected_per_byte;
539 d * d / expected_per_byte
540 })
541 .sum();
542 const NU: f64 = 255.0;
543 let z =
544 ((chi_sq / NU).powf(1.0 / 3.0) - (1.0 - 2.0 / (9.0 * NU))) / (2.0 / (9.0 * NU)).sqrt();
545 assert!(
546 z <= 5.0,
547 "Random bytes show poor entropy distribution (chi-square z = {})",
548 z
549 );
550 }
551
552 #[cfg(feature = "getrandom")]
553 #[test]
554 fn test_random_bytes_uniformity() {
555 const NUM_SAMPLES: usize = 10000;
557 const BYTE_LENGTH: usize = 16;
558
559 let mut all_bytes = alloc::vec![0u8; NUM_SAMPLES * BYTE_LENGTH];
560 let mut offset = 0;
561
562 for _ in 0..NUM_SAMPLES {
563 let bytes = Utils::random_bytes(BYTE_LENGTH).expect("Should generate random bytes");
564 all_bytes[offset..offset + BYTE_LENGTH].copy_from_slice(&bytes);
565 offset += BYTE_LENGTH;
566 }
567
568 let mut max_run_length = 0;
571 let mut current_run_length = 1;
572
573 for i in 1..all_bytes.len() {
574 if all_bytes[i] == all_bytes[i - 1] {
575 current_run_length += 1;
576 max_run_length = max_run_length.max(current_run_length);
577 } else {
578 current_run_length = 1;
579 }
580 }
581
582 assert!(
584 max_run_length <= 4,
585 "Random bytes show suspicious patterns (run length: {})",
586 max_run_length
587 );
588 }
589
590 #[cfg(any(feature = "rand", all(feature = "getrandom", feature = "alloc")))]
591 #[test]
592 fn test_random_bytes_size_limits() {
593 const MAX_SIZE: usize = 1024 * 1024; assert_eq!(
595 Utils::random_bytes(0),
596 Err(crate::error::Error::RandomBytesLengthInvalid {
597 min: 1,
598 max: MAX_SIZE,
599 requested: 0,
600 }),
601 "zero length"
602 );
603
604 assert!(
605 Utils::random_bytes(MAX_SIZE).is_ok(),
606 "Should accept maximum size"
607 );
608 assert_eq!(
609 Utils::random_bytes(MAX_SIZE + 1),
610 Err(crate::error::Error::RandomBytesLengthInvalid {
611 min: 1,
612 max: MAX_SIZE,
613 requested: MAX_SIZE + 1,
614 }),
615 "oversized request"
616 );
617
618 for size in [1, 16, 32, 64, 128, 256, 512, 1024] {
620 let bytes = Utils::random_bytes(size).expect("Should generate random bytes");
621 assert_eq!(bytes.len(), size, "Should generate exactly {} bytes", size);
622 }
623 }
624
625 #[test]
626 #[cfg(feature = "alloc")]
627 fn test_hex_to_bytes_decode_errors() {
628 use crate::error::{
629 Error,
630 HexDecodeError,
631 };
632
633 assert_eq!(
634 Utils::hex_to_bytes("123").unwrap_err(),
635 Error::HexDecode(HexDecodeError::OddLength { char_count: 3 })
636 );
637 assert_eq!(
638 Utils::hex_to_bytes("12g3").unwrap_err(),
639 Error::HexDecode(HexDecodeError::InvalidDigit {
640 pair_start: 2,
641 char_count: 4,
642 })
643 );
644 }
645}