use crate::error::PrivacyError;
use crate::types::{EntropyFrame, EntropySource, PrivacyProof, ProofScheme};
use sha2::{Digest, Sha256};
use serde::{Deserialize, Serialize};
extern crate alloc;
use alloc::{format, string::String, vec::Vec};
pub const MIN_ENTROPY_BITS: usize = 256;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MicroNode {
pub id: String,
pub source: EntropySource,
pub h_min: f64,
pub active: bool,
}
pub struct EntropyElasticityManager {
nodes: Vec<MicroNode>,
scale_factor: usize,
}
impl EntropyElasticityManager {
pub fn new(initial_nodes: usize) -> Self {
let nodes = (0..initial_nodes)
.map(|i| MicroNode {
id: format!("micro-{i:04}"),
source: if i % 3 == 0 {
EntropySource::PhysicalQrng
} else {
EntropySource::VirtualChaos
},
h_min: 0.99,
active: true,
})
.collect();
Self { nodes, scale_factor: 1 }
}
pub fn generate_frame(
&self,
chaos_seed: &[u8; 32],
timestamp_ms: u64,
) -> Result<EntropyFrame, PrivacyError> {
let active: Vec<&MicroNode> = self.nodes.iter().filter(|n| n.active).collect();
if active.is_empty() {
return Err(PrivacyError::EntropyScalingFailed("No active micro-nodes".into()));
}
let mut combined = Vec::new();
for node in &active {
let node_entropy = self.node_entropy(node, chaos_seed, timestamp_ms);
combined.extend_from_slice(&node_entropy);
}
let mut hasher = Sha256::new();
hasher.update(&combined);
hasher.update(chaos_seed);
hasher.update(b"entropy-mix-v1");
let mixed: [u8; 32] = hasher.finalize().into();
let hash_5dqeh = self.hash_5dqeh(&mixed);
let h_min = self.estimate_h_min(&mixed);
if h_min < 0.99 {
return Err(PrivacyError::EntropyQualityFailed(h_min));
}
let proof = self.prove_quality(h_min, chaos_seed);
let physical_count = active.iter().filter(|n| n.source == EntropySource::PhysicalQrng).count();
let source = if physical_count > active.len() / 2 {
EntropySource::PhysicalQrng
} else {
EntropySource::VirtualChaos
};
Ok(EntropyFrame {
bytes: mixed.to_vec(),
hash_5dqeh,
h_min,
source,
proof,
})
}
pub fn scale_up(&mut self, additional: usize) {
let start = self.nodes.len();
for i in 0..additional {
self.nodes.push(MicroNode {
id: format!("micro-virt-{:04}", start + i),
source: EntropySource::VirtualChaos,
h_min: 0.97,
active: true,
});
}
self.scale_factor += 1;
}
pub fn active_count(&self) -> usize {
self.nodes.iter().filter(|n| n.active).count()
}
fn node_entropy(&self, node: &MicroNode, chaos_seed: &[u8; 32], ts: u64) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(node.id.as_bytes());
hasher.update(chaos_seed);
hasher.update(ts.to_le_bytes());
hasher.update(match node.source {
EntropySource::PhysicalQrng => b"qrng-v1" as &[u8],
EntropySource::VirtualChaos => b"chaos-v1",
});
hasher.finalize().into()
}
fn hash_5dqeh(&self, input: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(input);
hasher.update(b"5dqeh-v1");
hex::encode(hasher.finalize())
}
#[allow(clippy::manual_clamp)]
fn estimate_h_min(&self, bytes: &[u8]) -> f64 {
if bytes.is_empty() { return 0.0; }
let mut freq = [0u32; 256];
for &b in bytes { freq[b as usize] += 1; }
let max_freq = *freq.iter().max().unwrap_or(&1) as f64;
let n = bytes.len() as f64;
let p_max = max_freq / n;
(-p_max.log2()).max(0.0).min(1.0)
}
fn prove_quality(&self, h_min: f64, chaos_seed: &[u8; 32]) -> PrivacyProof {
let mut hasher = Sha256::new();
hasher.update(h_min.to_le_bytes());
hasher.update(chaos_seed);
hasher.update(b"entropy-quality-v1");
let commitment: [u8; 32] = hasher.finalize().into();
PrivacyProof {
proof_bytes: hex::encode(commitment),
public_inputs: hex::encode(h_min.to_le_bytes()),
scheme: ProofScheme::Snark,
security_bits: 128,
proof_size: 64,
chsh_value: 0.0,
lyapunov: 4.5,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_frame() {
let mgr = EntropyElasticityManager::new(5);
let seed = [0u8; 32];
let frame = mgr.generate_frame(&seed, 1000).unwrap();
assert_eq!(frame.bytes.len(), 32);
assert!(frame.h_min > 0.0);
}
#[test]
fn test_scale_up() {
let mut mgr = EntropyElasticityManager::new(3);
assert_eq!(mgr.active_count(), 3);
mgr.scale_up(5);
assert_eq!(mgr.active_count(), 8);
}
}