use core::cmp;
use sha3::{Digest, Sha3_512};
#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
const DIGEST_LEN: usize = 64;
pub const RESEED_INTERVAL: u64 = 10_000;
#[derive(Debug)]
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
pub struct HashDrbg {
v: [u8; DIGEST_LEN],
c: [u8; DIGEST_LEN],
reseed_counter: u64,
}
impl HashDrbg {
pub fn instantiate(entropy_input: &[u8]) -> Self {
let v: [u8; DIGEST_LEN] = sha3_512(entropy_input);
let c: [u8; DIGEST_LEN] = sha3_512_chain(&[&[0x00], v.as_ref()]);
Self { v, c, reseed_counter: 1 }
}
pub fn reseed(&mut self, entropy_input: &[u8]) {
self.v = sha3_512_chain(&[&[0x01], self.v.as_ref(), entropy_input]);
self.c = sha3_512_chain(&[&[0x00], self.v.as_ref()]);
self.reseed_counter = 1;
}
pub fn generate(&mut self, out: &mut [u8]) -> bool {
if self.reseed_counter > RESEED_INTERVAL {
return false;
}
#[cfg(feature = "zeroize")]
let mut m = Zeroizing::new(self.v);
#[cfg(not(feature = "zeroize"))]
let mut m = self.v;
let mut offset = 0;
while offset < out.len() {
#[cfg(feature = "zeroize")]
let w = Zeroizing::new(sha3_512(m.as_ref()));
#[cfg(not(feature = "zeroize"))]
let w = sha3_512(m.as_ref());
let n = cmp::min(out.len() - offset, DIGEST_LEN);
out[offset..offset + n].copy_from_slice(&w[..n]);
offset += n;
add_one_be(m.as_mut_slice());
}
#[cfg(feature = "zeroize")]
let h = Zeroizing::new(sha3_512_chain(&[&[0x03], self.v.as_ref()]));
#[cfg(not(feature = "zeroize"))]
let h = sha3_512_chain(&[&[0x03], self.v.as_ref()]);
add_be(&mut self.v, h.as_slice());
add_be(&mut self.v, &self.c);
#[cfg(feature = "zeroize")]
let mut padded_counter = Zeroizing::new([0u8; DIGEST_LEN]);
#[cfg(not(feature = "zeroize"))]
let mut padded_counter = [0u8; DIGEST_LEN];
padded_counter[DIGEST_LEN - 8..].copy_from_slice(&self.reseed_counter.to_be_bytes());
add_be(&mut self.v, padded_counter.as_slice());
self.reseed_counter += 1;
true
}
}
#[cfg(not(feature = "zeroize"))]
impl Drop for HashDrbg {
fn drop(&mut self) {
for b in self.v.iter_mut().chain(self.c.iter_mut()) {
unsafe { core::ptr::write_volatile(b, 0) };
}
unsafe { core::ptr::write_volatile(&mut self.reseed_counter, 0) };
}
}
#[inline]
fn sha3_512(data: &[u8]) -> [u8; DIGEST_LEN] {
let mut h = Sha3_512::new();
h.update(data);
let digest = h.finalize();
let mut out = [0u8; DIGEST_LEN];
out.copy_from_slice(&digest);
out
}
#[inline]
fn sha3_512_chain(parts: &[&[u8]]) -> [u8; DIGEST_LEN] {
let mut h = Sha3_512::new();
for part in parts {
h.update(part);
}
let digest = h.finalize();
let mut out = [0u8; DIGEST_LEN];
out.copy_from_slice(&digest);
out
}
fn add_one_be(arr: &mut [u8]) {
for byte in arr.iter_mut().rev() {
let (new_val, overflow) = byte.overflowing_add(1);
*byte = new_val;
if !overflow {
break;
}
}
}
fn add_be(dest: &mut [u8], src: &[u8]) {
let mut carry = 0u16;
for (d, s) in dest.iter_mut().rev().zip(src.iter().rev()) {
let sum = *d as u16 + *s as u16 + carry;
*d = sum as u8;
carry = sum >> 8;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn instantiate_and_generate_differ() {
let entropy = [0x42u8; DIGEST_LEN];
let mut drbg = HashDrbg::instantiate(&entropy);
let mut out1 = [0u8; 32];
assert!(drbg.generate(&mut out1), "first generate must succeed");
let mut out2 = [0u8; 32];
assert!(drbg.generate(&mut out2), "second generate must succeed");
assert_ne!(out1, out2, "successive outputs must differ");
assert!(out1.iter().any(|&b| b != 0), "output must not be all-zero");
}
#[test]
fn different_seeds_produce_different_output() {
let mut drbg1 = HashDrbg::instantiate(&[0x11u8; DIGEST_LEN]);
let mut drbg2 = HashDrbg::instantiate(&[0x22u8; DIGEST_LEN]);
let mut out1 = [0u8; DIGEST_LEN];
let mut out2 = [0u8; DIGEST_LEN];
drbg1.generate(&mut out1);
drbg2.generate(&mut out2);
assert_ne!(out1, out2);
}
#[test]
fn reseed_resets_counter_and_changes_output() {
let entropy1 = [0x11u8; DIGEST_LEN];
let entropy2 = [0x22u8; DIGEST_LEN];
let mut drbg = HashDrbg::instantiate(&entropy1);
let mut before = [0u8; DIGEST_LEN];
drbg.generate(&mut before);
drbg.reseed(&entropy2);
assert_eq!(drbg.reseed_counter, 1, "counter must reset after reseed");
let mut after = [0u8; DIGEST_LEN];
assert!(drbg.generate(&mut after));
assert_ne!(before, after, "output must change after reseed");
}
#[test]
fn multi_block_generate_produces_distinct_blocks() {
let mut drbg = HashDrbg::instantiate(&[0x55u8; DIGEST_LEN]);
let mut out = [0u8; DIGEST_LEN * 8]; assert!(drbg.generate(&mut out));
assert!(out.iter().any(|&b| b != 0));
assert_ne!(
&out[..DIGEST_LEN],
&out[DIGEST_LEN..DIGEST_LEN * 2],
"consecutive blocks must differ"
);
}
#[test]
fn generate_succeeds_at_reseed_interval() {
let mut drbg = HashDrbg::instantiate(&[0xAAu8; DIGEST_LEN]);
drbg.reseed_counter = RESEED_INTERVAL;
let mut out = [0u8; 32];
assert!(drbg.generate(&mut out), "must succeed when counter == RESEED_INTERVAL");
assert_eq!(drbg.reseed_counter, RESEED_INTERVAL + 1);
}
#[test]
fn generate_fails_after_reseed_interval() {
let mut drbg = HashDrbg::instantiate(&[0xAAu8; DIGEST_LEN]);
drbg.reseed_counter = RESEED_INTERVAL + 1;
let mut out = [0u8; 32];
assert!(!drbg.generate(&mut out), "must fail when counter > RESEED_INTERVAL");
}
#[test]
fn add_one_be_basic() {
let mut arr = [0u8; 4];
add_one_be(&mut arr);
assert_eq!(arr, [0, 0, 0, 1]);
}
#[test]
fn add_one_be_carry_propagation() {
let mut arr = [0, 0, 0, 0xFF];
add_one_be(&mut arr);
assert_eq!(arr, [0, 0, 1, 0]);
}
#[test]
fn add_one_be_wraparound() {
let mut arr = [0xFF; 4];
add_one_be(&mut arr);
assert_eq!(arr, [0; 4], "must wrap around (mod 2^32)");
}
#[test]
fn add_be_no_carry() {
let mut dest = [0, 0, 0, 5];
add_be(&mut dest, &[0, 0, 0, 10]);
assert_eq!(dest, [0, 0, 0, 15]);
}
#[test]
fn add_be_with_carry() {
let mut dest = [0, 0, 0, 250];
add_be(&mut dest, &[0, 0, 0, 10]);
assert_eq!(dest, [0, 0, 1, 4]); }
#[test]
fn add_be_wraparound() {
let mut dest = [0xFF; 4];
add_be(&mut dest, &[0, 0, 0, 1]);
assert_eq!(dest, [0; 4], "must wrap around (mod 2^32)");
}
#[test]
#[cfg(feature = "zeroize")]
fn drbg_state_is_zeroed_on_explicit_zeroize() {
use zeroize::Zeroize;
let mut drbg = HashDrbg::instantiate(&[0xBBu8; DIGEST_LEN]);
let mut buf = [0u8; 32];
drbg.generate(&mut buf);
assert!(drbg.v.iter().any(|&b| b != 0), "v must be non-zero before zeroize");
assert!(drbg.c.iter().any(|&b| b != 0), "c must be non-zero before zeroize");
drbg.zeroize();
assert!(drbg.v.iter().all(|&b| b == 0), "v must be zeroed after explicit zeroize");
assert!(drbg.c.iter().all(|&b| b == 0), "c must be zeroed after explicit zeroize");
assert_eq!(drbg.reseed_counter, 0, "counter must be zeroed after explicit zeroize");
}
}