use crate::Error;
use crate::aes::{Aes256, CTR_BATCH_BLOCKS};
const SEEDLEN: usize = 48;
const MAX_REQUEST: usize = 65536;
const RESEED_INTERVAL: u64 = 1u64 << 48;
#[derive(Debug)]
pub struct CtrDrbg {
aes: Aes256,
key: [u8; 32],
v: [u8; 16],
reseed_counter: u64,
}
impl Drop for CtrDrbg {
fn drop(&mut self) {
self.key.fill(0);
self.v = [0u8; 16];
}
}
impl CtrDrbg {
pub fn new(entropy_input: &[u8], personalization: &[u8]) -> Result<Self, Error> {
if entropy_input.len() < SEEDLEN {
return Err(Error::InvalidInput);
}
let mut seed_material = Vec::with_capacity(entropy_input.len() + personalization.len());
seed_material.extend_from_slice(entropy_input);
seed_material.extend_from_slice(personalization);
let seed = xor_fold(&seed_material);
let mut drbg = Self {
aes: Aes256::new(&[0u8; 32]),
key: [0u8; 32],
v: [0u8; 16],
reseed_counter: 1,
};
drbg.update(&seed);
Ok(drbg)
}
pub fn instantiate_from_os(personalization: &[u8]) -> Result<Self, Error> {
crate::selftest::ensure_passed()?;
let mut seed = [0u8; SEEDLEN];
crate::entropy::fill(&mut seed)?;
entropy_health_tests(&seed)?;
Self::new(&seed, personalization)
}
pub fn reseed(&mut self, entropy_input: &[u8], additional_input: &[u8]) -> Result<(), Error> {
if entropy_input.len() < SEEDLEN {
return Err(Error::InvalidInput);
}
let mut seed_material = Vec::with_capacity(entropy_input.len() + additional_input.len());
seed_material.extend_from_slice(entropy_input);
seed_material.extend_from_slice(additional_input);
let seed = xor_fold(&seed_material);
self.update(&seed);
self.reseed_counter = 1;
Ok(())
}
pub fn generate(&mut self, out: &mut [u8]) -> Result<(), Error> {
self.generate_with_ai(out, None)
}
pub fn generate_with_ai(
&mut self,
out: &mut [u8],
additional_input: Option<&[u8]>,
) -> Result<(), Error> {
if out.len() > MAX_REQUEST {
return Err(Error::RngError);
}
if self.reseed_counter > RESEED_INTERVAL {
return Err(Error::RngError);
}
if let Some(ai) = additional_filter(additional_input) {
let seed = xor_fold(ai);
self.update(&seed);
}
let mut temp = Vec::with_capacity(out.len().div_ceil(16) * 16);
let mut ks = [0u8; CTR_BATCH_BLOCKS * 16];
let mut v = u128::from_be_bytes(self.v);
while temp.len() < out.len() {
v = v.wrapping_add(1);
let base = v.to_be_bytes();
let lo = u32::from_be_bytes(base[12..16].try_into().unwrap()) as u64;
let want = out.len() - temp.len();
let n = want
.div_ceil(16)
.min(CTR_BATCH_BLOCKS)
.min((0x1_0000_0000 - lo) as usize);
self.aes.encrypt_ctr_batch(base, n, &mut ks);
temp.extend_from_slice(&ks[..n * 16]);
v = v.wrapping_add(n as u128 - 1);
}
self.v = v.to_be_bytes();
out.copy_from_slice(&temp[..out.len()]);
zeroize_vec(&mut temp);
let pd = match additional_filter(additional_input) {
Some(ai) => xor_fold(ai),
None => [0u8; SEEDLEN],
};
self.update(&pd);
self.reseed_counter = self.reseed_counter.saturating_add(1);
Ok(())
}
pub fn generate_mixed(&mut self, out: &mut [u8]) -> Result<(), Error> {
crate::selftest::ensure_passed()?;
let mut ai = [0u8; 16];
crate::entropy::fill(&mut ai)?;
self.generate_with_ai(out, Some(&ai))
}
fn update(&mut self, provided_data: &[u8]) {
let mut temp = [0u8; SEEDLEN];
increment_v(&mut self.v);
let lo = u32::from_be_bytes(self.v[12..16].try_into().unwrap());
if lo <= u32::MAX - 2 {
self.aes.encrypt_ctr_batch(self.v, 3, &mut temp);
self.v = u128::from_be_bytes(self.v).wrapping_add(2).to_be_bytes();
} else {
let mut chunk = [0u8; 16];
for part in temp.chunks_mut(16) {
chunk.copy_from_slice(&self.v);
self.aes.encrypt_block(&mut chunk);
part.copy_from_slice(&chunk);
increment_v(&mut self.v);
}
}
for (i, b) in temp.iter_mut().enumerate() {
*b ^= provided_data[i];
}
self.key.copy_from_slice(&temp[..32]);
self.aes = Aes256::new(&self.key);
self.v.copy_from_slice(&temp[32..48]);
temp.fill(0);
}
}
fn xor_fold(seed_material: &[u8]) -> [u8; SEEDLEN] {
let mut out = [0u8; SEEDLEN];
for chunk in seed_material.chunks(SEEDLEN) {
for (i, b) in chunk.iter().enumerate() {
out[i] ^= b;
}
}
out
}
fn additional_filter(ai: Option<&[u8]>) -> Option<&[u8]> {
match ai {
Some(b) if !b.is_empty() => Some(b),
_ => None,
}
}
fn increment_v(v: &mut [u8; 16]) {
for i in (0..16).rev() {
let (nv, ov) = v[i].overflowing_add(1);
v[i] = nv;
if !ov {
break;
}
}
}
fn zeroize_vec(v: &mut [u8]) {
for b in v.iter_mut() {
*b = 0;
}
}
fn entropy_health_tests(seed: &[u8; SEEDLEN]) -> Result<(), Error> {
let mut run = 1u8;
for i in 1..seed.len() {
if seed[i] == seed[i - 1] {
run += 1;
if run >= 3 {
return Err(Error::EntropyFailed);
}
} else {
run = 1;
}
}
let first = seed[0];
if seed.iter().filter(|&&b| b == first).count() >= 6 {
return Err(Error::EntropyFailed);
}
Ok(())
}
#[cfg(test)]
mod health_tests {
use super::*;
#[test]
fn rct_rejects_three_identical() {
let mut seed = [0u8; SEEDLEN];
(0..48u8).for_each(|i| seed[i as usize] = i);
seed[10] = 0xAA;
seed[11] = 0xAA;
seed[12] = 0xAA;
assert_eq!(entropy_health_tests(&seed), Err(Error::EntropyFailed));
seed[12] = 0xAB;
assert!(entropy_health_tests(&seed).is_ok());
let same = [0x11u8; SEEDLEN];
assert_eq!(entropy_health_tests(&same), Err(Error::EntropyFailed));
}
#[test]
fn apt_rejects_high_repetition_of_first_byte() {
let mut seed = [0u8; SEEDLEN];
(0..48u8).for_each(|i| seed[i as usize] = i);
for s in seed.iter_mut().take(6).skip(1) {
*s = 0x00;
}
assert_eq!(entropy_health_tests(&seed), Err(Error::EntropyFailed));
}
}
#[cfg(test)]
mod probe2 {
use super::*;
fn hx(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
const EI: &str = "b5e2af38591a9743e5d3e458848a3998536d3b625e1694be847f95c3bfbda267f08624be4bb6aa496e1b596be523e7c4";
const PS: &str = "0a9a59e7605c0e12fae317bb004aecf1427bda4dca7718801895c38179fd36cd922634c3789a99b9d9c556fe50a41de4";
const EXP_KEY: &str = "ec777c24fe03afe8b6534712400ba6e2dfb1a112d901e7509ba493917cb309b2";
const EXP_V: &str = "10c013b7048a1984667cfa1bc081cfae";
fn hex(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn ps_interpretations() {
let ei = hex(EI);
let ps = hex(PS);
let d = CtrDrbg::new(&ei, &ps).unwrap();
println!(
"(a) new(ei, ps) match: {}",
hx(&d.key) == EXP_KEY && hx(&d.v) == EXP_V
);
let d = CtrDrbg::new(&ei, b"").unwrap();
println!(
"(b) new(ei, '') match: {}",
hx(&d.key) == EXP_KEY && hx(&d.v) == EXP_V
);
let mut x = ei.clone();
for (a, b) in x.iter_mut().zip(ps.iter()) {
*a ^= b;
}
let d = CtrDrbg::new(&x, b"").unwrap();
println!(
"(c) ei xor ps match: {}",
hx(&d.key) == EXP_KEY && hx(&d.v) == EXP_V
);
let d = CtrDrbg::new(&ps, b"").unwrap();
println!(
"(d) ps only match: {}",
hx(&d.key) == EXP_KEY && hx(&d.v) == EXP_V
);
let mut d = CtrDrbg::new(&[0u8; 48], b"").unwrap();
d.update(&{
let mut s = [0u8; SEEDLEN];
s[..48].copy_from_slice(&ei);
s
});
d.update(&{
let mut s = [0u8; SEEDLEN];
s[..48].copy_from_slice(&ps);
s
});
println!(
"(e) upd(ei)+upd(ps) match: {}",
hx(&d.key) == EXP_KEY && hx(&d.v) == EXP_V
);
println!("expect Key = {EXP_KEY}");
println!("expect V = {EXP_V}");
}
}