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 fn sign_with_context(
105 &self,
106 algorithm: Algorithm,
107 secret_key: &SigSecretKey,
108 message: &[u8],
109 context: &[u8],
110 randomness: Option<&[u8]>,
111 ) -> Result<Vec<u8>> {
112 if context.is_empty() {
113 return self.sign(algorithm, secret_key, message, randomness);
114 }
115 Err(crate::error::Error::NotImplemented {
116 feature: "signing context not supported by this provider".into(),
117 })
118 }
119
120 fn verify_with_context(
126 &self,
127 algorithm: Algorithm,
128 public_key: &SigPublicKey,
129 message: &[u8],
130 context: &[u8],
131 signature: &[u8],
132 ) -> Result<bool> {
133 if context.is_empty() {
134 return self.verify(algorithm, public_key, message, signature);
135 }
136 Err(crate::error::Error::NotImplemented {
137 feature: "signing context not supported by this provider".into(),
138 })
139 }
140}
141
142#[cfg(feature = "alloc")]
144pub trait HashOperations {
145 fn hash(&self, algorithm: Algorithm, data: &[u8]) -> Result<Vec<u8>>;
146}
147
148#[cfg(feature = "alloc")]
155pub trait AeadOperations {
156 fn encrypt(
157 &self,
158 algorithm: Algorithm,
159 key: &AeadKey,
160 nonce: &Nonce,
161 plaintext: &[u8],
162 associated_data: Option<&[u8]>,
163 ) -> Result<Vec<u8>>;
164 fn decrypt(
165 &self,
166 algorithm: Algorithm,
167 key: &AeadKey,
168 nonce: &Nonce,
169 ciphertext: &[u8],
170 associated_data: Option<&[u8]>,
171 ) -> Result<Vec<u8>>;
172}
173
174pub trait CryptoProvider: Send + Sync {
176 #[cfg(feature = "alloc")]
177 fn kem(&self) -> Option<&dyn KemOperations>;
178 #[cfg(feature = "alloc")]
179 fn signature(&self) -> Option<&dyn SignatureOperations>;
180 #[cfg(feature = "alloc")]
181 fn hash(&self) -> Option<&dyn HashOperations>;
182 #[cfg(feature = "alloc")]
183 fn aead(&self) -> Option<&dyn AeadOperations>;
184}
185
186#[cfg(feature = "alloc")]
192pub use crate::contexts::KemContext;
193
194pub struct Utils;
213
214impl Utils {
215 #[cfg(feature = "rand")]
222 pub fn random_bytes(length: usize) -> Result<Vec<u8>> {
223 const MIN_RANDOM_SIZE: usize = 1;
224 const MAX_RANDOM_SIZE: usize = 1024 * 1024; if !(MIN_RANDOM_SIZE..=MAX_RANDOM_SIZE).contains(&length) {
226 return Err(crate::error::Error::RandomBytesLengthInvalid {
227 min: MIN_RANDOM_SIZE,
228 max: MAX_RANDOM_SIZE,
229 requested: length,
230 });
231 }
232
233 let mut bytes = alloc::vec![0u8; length];
234
235 let mut rng = rand::rng();
237 rng.fill_bytes(&mut bytes);
238
239 Ok(bytes)
241 }
242
243 #[cfg(all(feature = "getrandom", not(feature = "rand")))]
244 #[cfg(feature = "alloc")]
245 pub fn random_bytes(length: usize) -> Result<Vec<u8>> {
246 const MIN_RANDOM_SIZE: usize = 1;
247 const MAX_RANDOM_SIZE: usize = 1024 * 1024; if !(MIN_RANDOM_SIZE..=MAX_RANDOM_SIZE).contains(&length) {
249 return Err(crate::error::Error::RandomBytesLengthInvalid {
250 min: MIN_RANDOM_SIZE,
251 max: MAX_RANDOM_SIZE,
252 requested: length,
253 });
254 }
255
256 let mut bytes = alloc::vec![0u8; length];
257
258 getrandom::fill(&mut bytes).map_err(|_| crate::error::Error::RandomGenerationFailed {
264 operation: String::from("random_bytes"),
265 })?;
266
267 Ok(bytes)
269 }
270
271 #[cfg(all(feature = "getrandom", not(feature = "rand")))]
272 #[cfg(not(feature = "alloc"))]
273 pub fn random_bytes(length: usize) -> Result<&'static [u8]> {
274 const MIN_RANDOM_SIZE: usize = 1;
275 const MAX_RANDOM_SIZE: usize = 1024; if !(MIN_RANDOM_SIZE..=MAX_RANDOM_SIZE).contains(&length) {
277 return Err(crate::error::Error::RandomBytesLengthInvalid {
278 min: MIN_RANDOM_SIZE,
279 max: MAX_RANDOM_SIZE,
280 requested: length,
281 });
282 }
283
284 #[cfg(target_arch = "wasm32")]
289 {
290 return Err(crate::error::Error::RandomGenerationFailed {
292 operation: "random_bytes",
293 });
294 }
295
296 #[cfg(not(target_arch = "wasm32"))]
297 {
298 return Err(crate::error::Error::RandomGenerationFailed {
301 operation: "random_bytes",
302 });
303 }
304 }
305
306 #[cfg(not(any(feature = "rand", feature = "getrandom")))]
307 #[cfg(feature = "alloc")]
308 pub fn random_bytes(_length: usize) -> Result<Vec<u8>> {
309 Err(crate::error::Error::RandomGenerationFailed {
310 operation: String::from("random_bytes"),
311 })
312 }
313
314 #[cfg(not(any(feature = "rand", feature = "getrandom")))]
315 #[cfg(not(feature = "alloc"))]
316 pub fn random_bytes(_length: usize) -> Result<&'static [u8]> {
317 Err(crate::error::Error::RandomGenerationFailed {
318 operation: "random_bytes",
319 })
320 }
321
322 #[cfg(feature = "alloc")]
324 pub fn bytes_to_hex(bytes: &[u8]) -> String {
325 let mut hex = String::new();
326 for &byte in bytes {
327 hex.push_str(&format!("{:02x}", byte));
328 }
329 hex
330 }
331
332 #[cfg(not(feature = "alloc"))]
333 pub fn bytes_to_hex(_bytes: &[u8]) -> &'static str {
334 "hex conversion not available in no_std without alloc"
335 }
336
337 #[cfg(feature = "alloc")]
344 pub fn hex_to_bytes(hex: &str) -> Result<Vec<u8>> {
345 use crate::error::HexDecodeError;
346
347 let hex = hex.trim();
348
349 if !hex.len().is_multiple_of(2) {
350 return Err(crate::error::Error::HexDecode(HexDecodeError::OddLength {
351 char_count: hex.len(),
352 }));
353 }
354
355 let mut bytes = Vec::with_capacity(hex.len() / 2);
356 for i in (0..hex.len()).step_by(2) {
357 let byte = u8::from_str_radix(&hex[i..i + 2], 16).map_err(|_| {
358 crate::error::Error::HexDecode(HexDecodeError::InvalidDigit {
359 pair_start: i,
360 char_count: hex.len(),
361 })
362 })?;
363 bytes.push(byte);
364 }
365
366 Ok(bytes)
367 }
368
369 #[cfg(not(feature = "alloc"))]
370 pub fn hex_to_bytes(_hex: &str) -> Result<&'static [u8]> {
371 Err(crate::error::Error::MemoryAllocationFailed {
372 operation: "hex_to_bytes",
373 })
374 }
375
376 pub fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
378 if a.len() != b.len() {
379 return false;
380 }
381 a.ct_eq(b).into()
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 #[cfg(feature = "alloc")]
389 use crate::contexts::{
390 HashContext,
391 SignatureContext,
392 };
393
394 #[test]
395 fn test_provider_architecture() {
396 #[cfg(feature = "std")]
397 {
398 let mut ctx = KemContext::with_default_provider();
400
401 let result = ctx.generate_keypair(Algorithm::MlKem512, None);
403 assert!(result.is_err());
404
405 match result {
406 Err(crate::error::Error::NotImplemented { feature }) => {
407 assert!(
408 feature.contains(
409 "ML-KEM implementations are provided by the main lib-q crate"
410 )
411 );
412 }
413 Err(crate::error::Error::ProviderNotConfigured { operation }) => {
414 assert_eq!(operation, "KEM");
415 }
416 _ => panic!("Expected NotImplemented or ProviderNotConfigured"),
417 }
418 }
419
420 #[cfg(feature = "alloc")]
422 {
423 let mut ctx = KemContext::new();
424 let result = ctx.generate_keypair(Algorithm::MlKem512, None);
425 assert!(result.is_err());
426
427 if let Err(crate::error::Error::ProviderNotConfigured { operation }) = result {
428 assert_eq!(operation, "KEM");
429 } else {
430 panic!("Expected ProviderNotConfigured error, got different error type");
431 }
432 }
433 }
434
435 #[test]
436 fn test_algorithm_security_levels() {
437 assert_eq!(Algorithm::MlKem512.security_level(), 1);
447 assert_eq!(Algorithm::MlKem768.security_level(), 3);
448 assert_eq!(Algorithm::MlKem1024.security_level(), 5);
449 assert_eq!(Algorithm::MlDsa44.security_level(), 1);
450 assert_eq!(Algorithm::MlDsa65.security_level(), 3);
451 assert_eq!(Algorithm::MlDsa87.security_level(), 5);
452 }
453
454 #[test]
455 fn test_algorithm_categories() {
456 assert_eq!(Algorithm::MlKem512.category(), AlgorithmCategory::Kem);
457 assert_eq!(Algorithm::MlDsa44.category(), AlgorithmCategory::Signature);
458 assert_eq!(Algorithm::Shake256.category(), AlgorithmCategory::Hash);
459 }
460
461 #[test]
462 #[cfg(feature = "alloc")]
463 fn test_kem_context() {
464 let mut ctx = KemContext::new();
465 let result = ctx.generate_keypair(Algorithm::MlKem512, None);
466 assert!(result.is_err());
467 if let Err(crate::error::Error::ProviderNotConfigured { operation }) = result {
468 assert_eq!(operation, "KEM");
469 } else {
470 panic!("Expected ProviderNotConfigured error");
471 }
472 }
473
474 #[test]
475 #[cfg(feature = "alloc")]
476 fn test_signature_context() {
477 let mut ctx = SignatureContext::new();
478 let result = ctx.generate_keypair(Algorithm::MlDsa65, None);
479 assert!(result.is_err());
480 if let Err(crate::error::Error::ProviderNotConfigured { operation }) = result {
481 assert_eq!(operation, "signature");
482 } else {
483 panic!("Expected ProviderNotConfigured error");
484 }
485 }
486
487 #[test]
488 #[cfg(feature = "alloc")]
489 fn test_hash_context() {
490 let mut ctx = HashContext::new();
491 let result = ctx.hash(Algorithm::Shake256, b"test");
492 assert!(result.is_err());
493 if let Err(crate::error::Error::ProviderNotConfigured { operation }) = result {
494 assert_eq!(operation, "hash");
495 } else {
496 panic!("Expected ProviderNotConfigured error");
497 }
498 }
499
500 #[test]
501 fn test_utils() {
502 #[cfg(feature = "getrandom")]
503 {
504 let bytes = Utils::random_bytes(32).unwrap();
505 assert_eq!(bytes.len(), 32);
506 }
507
508 #[cfg(feature = "alloc")]
509 {
510 let hex = Utils::bytes_to_hex(&[0x01, 0x23, 0x45, 0x67]);
511 assert_eq!(hex, "01234567");
512
513 let decoded = Utils::hex_to_bytes(&hex).unwrap();
514 assert_eq!(decoded, alloc::vec![0x01, 0x23, 0x45, 0x67]);
515 }
516 }
517
518 #[test]
519 fn test_random_bytes_generation() {
520 match Utils::random_bytes(32) {
522 Ok(bytes1) => {
523 let bytes2 = Utils::random_bytes(32).expect("Should generate random bytes");
524 assert_eq!(bytes1.len(), 32);
525 assert_eq!(bytes2.len(), 32);
526
527 assert_ne!(
530 bytes1, bytes2,
531 "Random bytes should be different on subsequent calls"
532 );
533
534 let all_zero1 = bytes1.iter().all(|&b| b == 0);
536 let all_zero2 = bytes2.iter().all(|&b| b == 0);
537
538 assert!(!all_zero1, "Random bytes should not all be zero");
539 assert!(!all_zero2, "Random bytes should not all be zero");
540 }
541 Err(crate::error::Error::RandomGenerationFailed { .. }) => {
542 }
545 Err(e) => {
546 panic!("Unexpected error: {:?}", e);
547 }
548 }
549 }
550
551 #[test]
552 fn test_constant_time_compare() {
553 assert!(Utils::constant_time_compare(b"hello", b"hello"));
554 assert!(!Utils::constant_time_compare(b"hello", b"world"));
555 assert!(!Utils::constant_time_compare(b"hello", b"hell"));
556 }
557
558 #[cfg(feature = "getrandom")]
559 #[test]
560 fn test_random_bytes_entropy_quality() {
561 const NUM_SAMPLES: usize = 1000;
563 const BYTE_LENGTH: usize = 32;
564
565 let mut byte_counts = [0u32; 256];
566 let mut total_bytes = 0u32;
567
568 for _ in 0..NUM_SAMPLES {
569 let bytes = Utils::random_bytes(BYTE_LENGTH).expect("Should generate random bytes");
570 for &byte in &bytes {
571 byte_counts[byte as usize] += 1;
572 total_bytes += 1;
573 }
574 }
575
576 let zero_count = byte_counts.iter().filter(|&&count| count == 0).count();
578 assert!(
579 zero_count < 50,
580 "Too many byte values are missing from random generation"
581 );
582
583 let expected_per_byte = total_bytes as f64 / 256.0;
586 let chi_sq: f64 = byte_counts
587 .iter()
588 .map(|&count| {
589 let d = count as f64 - expected_per_byte;
590 d * d / expected_per_byte
591 })
592 .sum();
593 const NU: f64 = 255.0;
594 let z =
595 ((chi_sq / NU).powf(1.0 / 3.0) - (1.0 - 2.0 / (9.0 * NU))) / (2.0 / (9.0 * NU)).sqrt();
596 assert!(
597 z <= 5.0,
598 "Random bytes show poor entropy distribution (chi-square z = {})",
599 z
600 );
601 }
602
603 #[cfg(feature = "getrandom")]
604 #[test]
605 fn test_random_bytes_uniformity() {
606 const NUM_SAMPLES: usize = 10000;
608 const BYTE_LENGTH: usize = 16;
609
610 let mut all_bytes = alloc::vec![0u8; NUM_SAMPLES * BYTE_LENGTH];
611 let mut offset = 0;
612
613 for _ in 0..NUM_SAMPLES {
614 let bytes = Utils::random_bytes(BYTE_LENGTH).expect("Should generate random bytes");
615 all_bytes[offset..offset + BYTE_LENGTH].copy_from_slice(&bytes);
616 offset += BYTE_LENGTH;
617 }
618
619 let mut max_run_length = 0;
622 let mut current_run_length = 1;
623
624 for i in 1..all_bytes.len() {
625 if all_bytes[i] == all_bytes[i - 1] {
626 current_run_length += 1;
627 max_run_length = max_run_length.max(current_run_length);
628 } else {
629 current_run_length = 1;
630 }
631 }
632
633 assert!(
635 max_run_length <= 4,
636 "Random bytes show suspicious patterns (run length: {})",
637 max_run_length
638 );
639 }
640
641 #[cfg(any(feature = "rand", all(feature = "getrandom", feature = "alloc")))]
642 #[test]
643 fn test_random_bytes_size_limits() {
644 const MAX_SIZE: usize = 1024 * 1024; assert_eq!(
646 Utils::random_bytes(0),
647 Err(crate::error::Error::RandomBytesLengthInvalid {
648 min: 1,
649 max: MAX_SIZE,
650 requested: 0,
651 }),
652 "zero length"
653 );
654
655 assert!(
656 Utils::random_bytes(MAX_SIZE).is_ok(),
657 "Should accept maximum size"
658 );
659 assert_eq!(
660 Utils::random_bytes(MAX_SIZE + 1),
661 Err(crate::error::Error::RandomBytesLengthInvalid {
662 min: 1,
663 max: MAX_SIZE,
664 requested: MAX_SIZE + 1,
665 }),
666 "oversized request"
667 );
668
669 for size in [1, 16, 32, 64, 128, 256, 512, 1024] {
671 let bytes = Utils::random_bytes(size).expect("Should generate random bytes");
672 assert_eq!(bytes.len(), size, "Should generate exactly {} bytes", size);
673 }
674 }
675
676 #[test]
677 #[cfg(feature = "alloc")]
678 fn test_hex_to_bytes_decode_errors() {
679 use crate::error::{
680 Error,
681 HexDecodeError,
682 };
683
684 assert_eq!(
685 Utils::hex_to_bytes("123").unwrap_err(),
686 Error::HexDecode(HexDecodeError::OddLength { char_count: 3 })
687 );
688 assert_eq!(
689 Utils::hex_to_bytes("12g3").unwrap_err(),
690 Error::HexDecode(HexDecodeError::InvalidDigit {
691 pair_start: 2,
692 char_count: 4,
693 })
694 );
695 }
696}