1use crate::crypto::sha3_256;
2use crate::error::{QVError, QVResult};
3
4pub struct MutationChain {
8 state: [u8; 32],
9 counter: u64,
10}
11
12impl MutationChain {
13 pub fn new(seed: [u8; 32]) -> Self {
15 MutationChain { state: seed, counter: 0 }
16 }
17
18 pub fn from_state(state: [u8; 32], counter: u64) -> Self {
20 MutationChain { state, counter }
21 }
22
23 pub fn advance(&mut self) -> [u8; 32] {
26 let mut input = [0u8; 40];
27 input[..32].copy_from_slice(&self.state);
28 input[32..].copy_from_slice(&self.counter.to_be_bytes());
29 self.state = sha3_256(&input);
30 self.counter += 1;
31 self.state
32 }
33
34 pub fn current_counter(&self) -> u64 { self.counter }
35 pub fn current_state(&self) -> &[u8; 32] { &self.state }
36
37 pub fn check_token_counter(&self, token_ctr: u64) -> QVResult<()> {
40 if token_ctr <= self.counter {
41 Err(QVError::ReplayDetected { token: token_ctr, chain: self.counter })
42 } else {
43 Ok(())
44 }
45 }
46}
47
48pub fn certify_entropy(data: &[u8]) -> QVResult<()> {
52 if data.len() < 8 {
53 return Ok(()); }
55 let total = data.len() - 3;
57 let mut seen = std::collections::HashSet::new();
58 for i in 0..total {
59 let gram: [u8; 4] = data[i..i + 4].try_into().unwrap();
60 seen.insert(gram);
61 }
62 let ratio = seen.len() as f64 / total as f64;
63 if ratio < 0.85 {
64 Err(QVError::LowEntropy(ratio))
65 } else {
66 Ok(())
67 }
68}