1use serde::{Deserialize, Serialize};
35use std::time::SystemTime;
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct EntropyQuality {
40 pub shannon_entropy: f64,
42 pub min_entropy: f64,
44 pub chi_squared: f64,
46 pub chi_squared_pvalue: f64,
48 pub serial_correlation: f64,
50 pub monte_carlo_pi_error: f64,
52 pub sample_size: usize,
54 pub timestamp: SystemTime,
56 pub health_tests_passed: bool,
58}
59
60impl EntropyQuality {
61 pub fn passes_health_tests(&self) -> bool {
63 self.health_tests_passed
64 && self.shannon_entropy >= 7.5 && self.min_entropy >= 4.0 && self.chi_squared_pvalue >= 0.01 && self.serial_correlation.abs() < 0.1 && self.monte_carlo_pi_error < 0.1 }
70
71 pub fn quality_score(&self) -> f64 {
73 let shannon_score = (self.shannon_entropy / 8.0).min(1.0);
74 let min_entropy_score = (self.min_entropy / 8.0).min(1.0);
75 let chi_squared_score = self.chi_squared_pvalue.min(1.0);
76 let correlation_score = (1.0 - self.serial_correlation.abs()).max(0.0);
77 let pi_score = (1.0 - self.monte_carlo_pi_error).max(0.0);
78
79 (shannon_score + min_entropy_score + chi_squared_score + correlation_score + pi_score) / 5.0
80 }
81}
82
83pub struct EntropySource {
85 source_type: String,
86}
87
88impl EntropySource {
89 pub fn system_rng() -> Self {
91 Self {
92 source_type: "system_rng".to_string(),
93 }
94 }
95
96 pub fn get_bytes(&mut self, count: usize) -> Vec<u8> {
98 use rand::RngCore;
99 let mut rng = rand::thread_rng();
100 let mut bytes = vec![0u8; count];
101 rng.fill_bytes(&mut bytes);
102 bytes
103 }
104
105 pub fn source_type(&self) -> &str {
107 &self.source_type
108 }
109}
110
111pub struct EntropyMonitor {
113 history: Vec<EntropyQuality>,
115 max_history: usize,
117 min_sample_size: usize,
119}
120
121impl Default for EntropyMonitor {
122 fn default() -> Self {
123 Self::new()
124 }
125}
126
127impl EntropyMonitor {
128 pub fn new() -> Self {
130 Self {
131 history: Vec::new(),
132 max_history: 1000,
133 min_sample_size: 256,
134 }
135 }
136
137 pub fn with_max_history(mut self, max: usize) -> Self {
139 self.max_history = max;
140 self
141 }
142
143 pub fn with_min_sample_size(mut self, min: usize) -> Self {
145 self.min_sample_size = min;
146 self
147 }
148
149 pub fn evaluate(&mut self, data: &[u8]) -> Result<EntropyQuality, EntropyError> {
151 if data.len() < self.min_sample_size {
152 return Err(EntropyError::InsufficientData {
153 required: self.min_sample_size,
154 provided: data.len(),
155 });
156 }
157
158 let shannon_entropy = calculate_shannon_entropy(data);
159 let min_entropy = calculate_min_entropy(data);
160 let (chi_squared, chi_squared_pvalue) = calculate_chi_squared(data);
161 let serial_correlation = calculate_serial_correlation(data);
162 let monte_carlo_pi_error = estimate_monte_carlo_pi_error(data);
163
164 let health_tests_passed = shannon_entropy >= 7.0
166 && min_entropy >= 3.0
167 && chi_squared_pvalue >= 0.001
168 && serial_correlation.abs() < 0.2;
169
170 let quality = EntropyQuality {
171 shannon_entropy,
172 min_entropy,
173 chi_squared,
174 chi_squared_pvalue,
175 serial_correlation,
176 monte_carlo_pi_error,
177 sample_size: data.len(),
178 timestamp: SystemTime::now(),
179 health_tests_passed,
180 };
181
182 self.history.push(quality.clone());
184 if self.history.len() > self.max_history {
185 self.history.remove(0);
186 }
187
188 Ok(quality)
189 }
190
191 pub fn history(&self) -> &[EntropyQuality] {
193 &self.history
194 }
195
196 pub fn average_quality_score(&self) -> f64 {
198 if self.history.is_empty() {
199 return 0.0;
200 }
201
202 let sum: f64 = self.history.iter().map(|q| q.quality_score()).sum();
203 sum / self.history.len() as f64
204 }
205
206 pub fn detect_degradation(&self, window_size: usize) -> bool {
208 if self.history.len() < window_size * 2 {
209 return false;
210 }
211
212 let recent_avg = self.history[self.history.len() - window_size..]
213 .iter()
214 .map(|q| q.quality_score())
215 .sum::<f64>()
216 / window_size as f64;
217
218 let older_avg = self.history
219 [self.history.len() - window_size * 2..self.history.len() - window_size]
220 .iter()
221 .map(|q| q.quality_score())
222 .sum::<f64>()
223 / window_size as f64;
224
225 recent_avg < older_avg * 0.9
227 }
228
229 pub fn clear_history(&mut self) {
231 self.history.clear();
232 }
233}
234
235fn calculate_shannon_entropy(data: &[u8]) -> f64 {
237 let mut counts = [0usize; 256];
238 for &byte in data {
239 counts[byte as usize] += 1;
240 }
241
242 let len = data.len() as f64;
243 let mut entropy = 0.0;
244
245 for &count in &counts {
246 if count > 0 {
247 let p = count as f64 / len;
248 entropy -= p * p.log2();
249 }
250 }
251
252 entropy
253}
254
255fn calculate_min_entropy(data: &[u8]) -> f64 {
257 let mut counts = [0usize; 256];
258 for &byte in data {
259 counts[byte as usize] += 1;
260 }
261
262 let max_count = *counts.iter().max().unwrap();
263 let max_probability = max_count as f64 / data.len() as f64;
264
265 -max_probability.log2()
266}
267
268fn calculate_chi_squared(data: &[u8]) -> (f64, f64) {
270 let mut counts = [0usize; 256];
271 for &byte in data {
272 counts[byte as usize] += 1;
273 }
274
275 let expected = data.len() as f64 / 256.0;
276 let mut chi_squared = 0.0;
277
278 for &count in &counts {
279 let diff = count as f64 - expected;
280 chi_squared += (diff * diff) / expected;
281 }
282
283 let df = 255.0;
286 let pvalue = if chi_squared > df {
287 let z = (chi_squared - df) / (2.0 * df).sqrt();
288 0.5 * (1.0 - erf_approx(z / std::f64::consts::SQRT_2))
290 } else {
291 1.0 - (df - chi_squared) / (2.0 * df)
292 };
293
294 (chi_squared, pvalue.clamp(0.0, 1.0))
295}
296
297fn erf_approx(x: f64) -> f64 {
299 let a1 = 0.254829592;
301 let a2 = -0.284496736;
302 let a3 = 1.421413741;
303 let a4 = -1.453152027;
304 let a5 = 1.061405429;
305 let p = 0.3275911;
306
307 let sign = if x < 0.0 { -1.0 } else { 1.0 };
308 let x = x.abs();
309
310 let t = 1.0 / (1.0 + p * x);
311 let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp();
312
313 sign * y
314}
315
316fn calculate_serial_correlation(data: &[u8]) -> f64 {
318 if data.len() < 2 {
319 return 0.0;
320 }
321
322 let n = data.len() as f64;
323 let mean: f64 = data.iter().map(|&x| x as f64).sum::<f64>() / n;
324
325 let mut numerator = 0.0;
326 let mut denominator = 0.0;
327
328 for i in 0..data.len() - 1 {
329 let x1 = data[i] as f64 - mean;
330 let x2 = data[i + 1] as f64 - mean;
331 numerator += x1 * x2;
332 }
333
334 for &byte in data {
335 let x = byte as f64 - mean;
336 denominator += x * x;
337 }
338
339 if denominator == 0.0 {
340 0.0
341 } else {
342 numerator / denominator
343 }
344}
345
346fn estimate_monte_carlo_pi_error(data: &[u8]) -> f64 {
348 if data.len() < 8 {
349 return 1.0; }
351
352 let pairs = data.len() / 4; let mut inside_circle = 0;
354
355 for i in 0..pairs {
356 if i * 4 + 3 >= data.len() {
357 break;
358 }
359
360 let x_bytes = [data[i * 4], data[i * 4 + 1]];
362 let y_bytes = [data[i * 4 + 2], data[i * 4 + 3]];
363
364 let x = u16::from_le_bytes(x_bytes) as f64 / 65536.0;
365 let y = u16::from_le_bytes(y_bytes) as f64 / 65536.0;
366
367 if x * x + y * y <= 1.0 {
368 inside_circle += 1;
369 }
370 }
371
372 let estimated_pi = 4.0 * inside_circle as f64 / pairs as f64;
373 (estimated_pi - std::f64::consts::PI).abs() / std::f64::consts::PI
374}
375
376#[derive(Debug, Clone, PartialEq)]
378pub enum EntropyError {
379 InsufficientData { required: usize, provided: usize },
381 HealthTestFailed(String),
383}
384
385impl std::fmt::Display for EntropyError {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 match self {
388 EntropyError::InsufficientData { required, provided } => {
389 write!(f, "Insufficient data: need {}, got {}", required, provided)
390 }
391 EntropyError::HealthTestFailed(msg) => write!(f, "Health test failed: {}", msg),
392 }
393 }
394}
395
396impl std::error::Error for EntropyError {}
397
398pub type EntropyResult<T> = Result<T, EntropyError>;
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn test_entropy_source() {
407 let mut source = EntropySource::system_rng();
408 let data = source.get_bytes(100);
409 assert_eq!(data.len(), 100);
410 assert_eq!(source.source_type(), "system_rng");
411 }
412
413 #[test]
414 fn test_shannon_entropy_uniform() {
415 let mut data = Vec::new();
417 for i in 0..256 {
418 for _ in 0..10 {
419 data.push(i as u8);
420 }
421 }
422 let entropy = calculate_shannon_entropy(&data);
423 assert!((entropy - 8.0).abs() < 0.01);
424 }
425
426 #[test]
427 fn test_shannon_entropy_zeros() {
428 let data = vec![0u8; 1000];
430 let entropy = calculate_shannon_entropy(&data);
431 assert_eq!(entropy, 0.0);
432 }
433
434 #[test]
435 fn test_min_entropy() {
436 let data = vec![0u8; 100];
438 let min_ent = calculate_min_entropy(&data);
439 assert_eq!(min_ent, 0.0);
440
441 let uniform_data: Vec<u8> = (0..=255).cycle().take(1000).collect();
443 let min_ent = calculate_min_entropy(&uniform_data);
444 assert!(min_ent > 7.0);
445 }
446
447 #[test]
448 fn test_chi_squared_uniform() {
449 let mut data = Vec::new();
450 for i in 0..256 {
451 for _ in 0..10 {
452 data.push(i as u8);
453 }
454 }
455 let (_chi_sq, pvalue) = calculate_chi_squared(&data);
456 assert!(pvalue > 0.01); }
460
461 #[test]
462 fn test_chi_squared_nonuniform() {
463 let data = vec![0u8; 1000];
465 let (_chi_sq, pvalue) = calculate_chi_squared(&data);
466 assert!(pvalue < 0.01); }
468
469 #[test]
470 fn test_serial_correlation() {
471 let alternating: Vec<u8> = (0..1000)
473 .map(|i| if i % 2 == 0 { 0 } else { 255 })
474 .collect();
475 let corr = calculate_serial_correlation(&alternating);
476 assert!(corr.abs() > 0.5);
477
478 let mut source = EntropySource::system_rng();
480 let random = source.get_bytes(1000);
481 let corr = calculate_serial_correlation(&random);
482 assert!(corr.abs() < 0.2);
483 }
484
485 #[test]
486 fn test_monte_carlo_pi() {
487 let mut source = EntropySource::system_rng();
489 let data = source.get_bytes(4000);
490 let error = estimate_monte_carlo_pi_error(&data);
491 assert!(error < 0.2);
493 }
494
495 #[test]
496 fn test_entropy_monitor_evaluate() {
497 let mut monitor = EntropyMonitor::new();
498 let mut source = EntropySource::system_rng();
499 let data = source.get_bytes(1000);
500
501 let quality = monitor.evaluate(&data).unwrap();
502 assert!(quality.shannon_entropy > 7.0);
503 assert!(quality.sample_size == 1000);
504 assert_eq!(monitor.history().len(), 1);
505 }
506
507 #[test]
508 fn test_entropy_monitor_insufficient_data() {
509 let mut monitor = EntropyMonitor::new().with_min_sample_size(100);
510 let data = vec![0u8; 50];
511
512 let result = monitor.evaluate(&data);
513 assert!(result.is_err());
514 assert!(matches!(
515 result.unwrap_err(),
516 EntropyError::InsufficientData { .. }
517 ));
518 }
519
520 #[test]
521 fn test_entropy_quality_score() {
522 let quality = EntropyQuality {
523 shannon_entropy: 7.9,
524 min_entropy: 6.0,
525 chi_squared: 255.0,
526 chi_squared_pvalue: 0.5,
527 serial_correlation: 0.05,
528 monte_carlo_pi_error: 0.05,
529 sample_size: 1000,
530 timestamp: SystemTime::now(),
531 health_tests_passed: true,
532 };
533
534 let score = quality.quality_score();
535 assert!(score > 0.8);
536 }
537
538 #[test]
539 fn test_entropy_quality_passes_health_tests() {
540 let good_quality = EntropyQuality {
541 shannon_entropy: 7.9,
542 min_entropy: 6.0,
543 chi_squared: 255.0,
544 chi_squared_pvalue: 0.5,
545 serial_correlation: 0.05,
546 monte_carlo_pi_error: 0.05,
547 sample_size: 1000,
548 timestamp: SystemTime::now(),
549 health_tests_passed: true,
550 };
551 assert!(good_quality.passes_health_tests());
552
553 let bad_quality = EntropyQuality {
554 shannon_entropy: 3.0, min_entropy: 2.0,
556 chi_squared: 500.0,
557 chi_squared_pvalue: 0.001,
558 serial_correlation: 0.5,
559 monte_carlo_pi_error: 0.5,
560 sample_size: 1000,
561 timestamp: SystemTime::now(),
562 health_tests_passed: false,
563 };
564 assert!(!bad_quality.passes_health_tests());
565 }
566
567 #[test]
568 fn test_entropy_monitor_history() {
569 let mut monitor = EntropyMonitor::new().with_max_history(5);
570 let mut source = EntropySource::system_rng();
571
572 for _ in 0..10 {
573 let data = source.get_bytes(500);
574 let _ = monitor.evaluate(&data);
575 }
576
577 assert_eq!(monitor.history().len(), 5);
578 }
579
580 #[test]
581 fn test_entropy_monitor_average_quality() {
582 let mut monitor = EntropyMonitor::new();
583 let mut source = EntropySource::system_rng();
584
585 for _ in 0..5 {
586 let data = source.get_bytes(500);
587 let _ = monitor.evaluate(&data);
588 }
589
590 let avg = monitor.average_quality_score();
591 assert!(avg > 0.5);
592 }
593
594 #[test]
595 fn test_entropy_monitor_detect_degradation() {
596 let mut monitor = EntropyMonitor::new();
597
598 for _ in 0..10 {
603 let mut data = Vec::new();
605 for i in 0u8..=255u8 {
606 data.push(i);
607 data.push(i);
608 }
609 let _ = monitor.evaluate(&data);
610 }
611
612 for _ in 0..5 {
614 let data = vec![0u8; 512];
615 let _ = monitor.evaluate(&data);
616 }
617
618 assert!(monitor.detect_degradation(3));
621 }
622
623 #[test]
624 fn test_entropy_monitor_clear_history() {
625 let mut monitor = EntropyMonitor::new();
626 let mut source = EntropySource::system_rng();
627 let data = source.get_bytes(500);
628 let _ = monitor.evaluate(&data);
629
630 assert_eq!(monitor.history().len(), 1);
631
632 monitor.clear_history();
633 assert_eq!(monitor.history().len(), 0);
634 }
635
636 #[test]
637 fn test_entropy_quality_serialization() {
638 let quality = EntropyQuality {
639 shannon_entropy: 7.9,
640 min_entropy: 6.0,
641 chi_squared: 255.0,
642 chi_squared_pvalue: 0.5,
643 serial_correlation: 0.05,
644 monte_carlo_pi_error: 0.05,
645 sample_size: 1000,
646 timestamp: SystemTime::now(),
647 health_tests_passed: true,
648 };
649
650 let serialized = crate::codec::encode(&quality).unwrap();
651 let deserialized: EntropyQuality = crate::codec::decode(&serialized).unwrap();
652
653 assert!((deserialized.shannon_entropy - quality.shannon_entropy).abs() < 0.01);
654 assert_eq!(deserialized.sample_size, quality.sample_size);
655 }
656}