#[cfg(feature = "advance")]
use crate::rng::HealthTests;
use crate::rng::OsRng;
use sha3::{Digest, Sha3_512};
#[cfg(feature = "advance")]
use std::fs::File;
#[cfg(feature = "advance")]
use std::io::Read;
#[cfg(feature = "exp_network_rng")]
use std::net::UdpSocket;
#[cfg(feature = "advance")]
use std::process;
#[cfg(feature = "advance")]
use std::thread;
#[cfg(feature = "advance")]
use std::time::{Instant, SystemTime};
#[cfg(feature = "zeroize")]
use zeroize::Zeroizing;
#[cfg(target_arch = "x86")]
use std::arch::x86::_rdtsc;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::_rdtsc;
#[cfg(all(feature = "build_separator", feature = "advance"))]
const BUILD_SEPARATOR: &[u8; 64] =
include_bytes!(concat!(env!("OUT_DIR"), "/build_separator.bin"));
pub fn get_blended_entropy() -> [u8; 64] {
let mut hasher = Sha3_512::new();
{
let mut os_rng = OsRng::new().expect("Failed to open /dev/urandom");
#[cfg(feature = "zeroize")]
let mut buf = Zeroizing::new([0u8; 64]);
#[cfg(not(feature = "zeroize"))]
let mut buf = [0u8; 64];
os_rng.fill_bytes(buf.as_mut()).expect("Failed to read from /dev/urandom");
hasher.update(buf.as_ref());
}
#[cfg(feature = "advance")]
{
hasher.update(gather_cpu_jitter());
hasher.update(gather_system_entropy());
}
hasher.update(gather_simd_jitter());
hasher.update(gather_network_jitter());
hasher.update(gather_hardware_rng());
hasher.update(gather_perf_events());
let mut result = [0u8; 64];
result.copy_from_slice(&hasher.finalize());
result
}
#[cfg(feature = "advance")]
const JITTER_MEM_SIZE: usize = 256 * 1024;
#[cfg(feature = "advance")]
const JITTER_MEM_ACCESSES: usize = 64;
#[cfg(feature = "advance")]
const JITTER_FOLD_ROUNDS: usize = 64;
#[cfg(feature = "advance")]
const JITTER_OVERSAMPLE: u32 = 64;
#[cfg(feature = "advance")]
const JITTER_WARMUP_ROUNDS: u32 = 64;
#[cfg(feature = "advance")]
const JITTER_MAX_STUCK_PCT: u32 = 50;
#[cfg(feature = "advance")]
#[derive(Debug)]
struct DeltaTracker {
last_delta: u64,
last_delta2: u64,
stuck_count: u32,
total_count: u32,
}
#[cfg(feature = "advance")]
impl DeltaTracker {
fn new() -> Self {
Self { last_delta: 0, last_delta2: 0, stuck_count: 0, total_count: 0 }
}
fn is_stuck(&mut self, current_delta: u64) -> bool {
let delta2 = current_delta.wrapping_sub(self.last_delta);
let delta3 = delta2.wrapping_sub(self.last_delta2);
self.last_delta = current_delta;
self.last_delta2 = delta2;
self.total_count += 1;
let stuck = current_delta == 0 || delta2 == 0 || delta3 == 0;
if stuck {
self.stuck_count += 1;
}
stuck
}
fn assert_healthy(&self) {
if self.total_count > 10
&& (self.stuck_count * 100) / self.total_count > JITTER_MAX_STUCK_PCT
{
panic!(
"CPU jitter stuck-sample rate {}/{} ({:.0}%) exceeds {}% — \
timing source is deterministic or compromised",
self.stuck_count,
self.total_count,
self.stuck_count as f64 / self.total_count as f64 * 100.0,
JITTER_MAX_STUCK_PCT,
);
}
}
}
#[cfg(feature = "advance")]
#[inline(always)]
fn xoshiro128ss(s: &mut [u32; 4]) -> u32 {
let result = (s[1].wrapping_mul(5)).rotate_left(7).wrapping_mul(9);
let t = s[1] << 9;
s[2] ^= s[0];
s[3] ^= s[1];
s[1] ^= s[2];
s[0] ^= s[3];
s[2] ^= t;
s[3] = s[3].rotate_left(11);
result
}
#[cfg(feature = "advance")]
#[inline(never)]
fn memory_noise(mem_block: &mut [u8], prng: &mut [u32; 4]) {
let mask = mem_block.len() - 1; for _ in 0..JITTER_MEM_ACCESSES {
let idx = xoshiro128ss(prng) as usize & mask;
unsafe {
let ptr = mem_block.as_mut_ptr().add(idx);
let val = core::ptr::read_volatile(ptr);
core::ptr::write_volatile(ptr, val.wrapping_add(1));
}
}
}
#[cfg(feature = "advance")]
#[inline(never)]
fn cpu_fold_noise(seed: u64) -> u64 {
let mut fold = seed;
for i in 0..JITTER_FOLD_ROUNDS as u64 {
fold = fold.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(i);
fold ^= fold >> 17;
fold ^= fold << 13;
fold ^= fold >> 7;
if fold & 0x80 != 0 {
fold = fold.wrapping_add(fold >> 3);
} else {
fold ^= fold.wrapping_mul(fold | 1);
}
fold = fold.wrapping_add(fold.wrapping_div(1 | (i.wrapping_mul(fold) & 0xFFFF)));
}
std::hint::black_box(fold)
}
#[cfg(feature = "advance")]
fn gather_cpu_jitter() -> [u8; 64] {
#[cfg(feature = "zeroize")]
let mut output = Zeroizing::new([0u8; 64]);
#[cfg(not(feature = "zeroize"))]
let mut output = [0u8; 64];
let mut health = HealthTests::new(
crate::rng::JITTER_RCT_CUTOFF,
crate::rng::JITTER_APT_CUTOFF,
crate::rng::JITTER_AUTO_LOWER_BOUND,
crate::rng::JITTER_AUTO_UPPER_BOUND,
crate::rng::JITTER_SYMBOL_MIN_UNIQUE,
);
let mut deltas = DeltaTracker::new();
let mut mem_block = vec![0u8; JITTER_MEM_SIZE];
let mut prng: [u32; 4] = [0x8e93_eec0, 0xce65_608a, 0xa8d4_6b46, 0xe83c_ef69];
{
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let tsc1 = unsafe { _rdtsc() };
let tsc2 = unsafe { _rdtsc() };
prng[0] ^= tsc1 as u32;
prng[1] ^= (tsc1 >> 32) as u32;
prng[2] ^= tsc2 as u32;
prng[3] ^= (tsc2 >> 32) as u32;
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
let t0 = Instant::now();
std::hint::black_box(cpu_fold_noise(42));
let d1 = t0.elapsed().as_nanos() as u32;
let t1 = Instant::now();
std::hint::black_box(cpu_fold_noise(84));
let d2 = t1.elapsed().as_nanos() as u32;
prng[0] ^= d1;
prng[1] ^= d2;
prng[2] ^= d1.rotate_left(16);
prng[3] ^= d2.rotate_left(16);
}
}
for _ in 0..JITTER_WARMUP_ROUNDS {
let t1 = Instant::now();
memory_noise(&mut mem_block, &mut prng);
cpu_fold_noise(prng[0] as u64);
let delta = t1.elapsed().as_nanos() as u64;
let _ = deltas.is_stuck(delta);
health.process_sample((delta & 0xFF) as u8);
}
let mut final_hash = Sha3_512::new();
for byte in output.iter_mut() {
let mut pool = Sha3_512::new();
let mut valid_samples = 0u32;
while valid_samples < JITTER_OVERSAMPLE {
let t1 = Instant::now();
memory_noise(&mut mem_block, &mut prng);
let fold_result = cpu_fold_noise(prng[0] as u64);
let delta_ns = t1.elapsed().as_nanos() as u64;
let raw_byte = (delta_ns & 0xFF) as u8;
health.process_sample(raw_byte);
pool.update(delta_ns.to_le_bytes());
pool.update(fold_result.to_le_bytes());
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let tsc = unsafe { _rdtsc() };
pool.update(tsc.to_le_bytes());
}
if deltas.is_stuck(delta_ns) {
continue;
}
valid_samples += 1;
}
deltas.assert_healthy();
#[cfg(feature = "zeroize")]
let digest = Zeroizing::new({
let d = pool.finalize();
let mut arr = [0u8; 64];
arr.copy_from_slice(&d);
arr
});
#[cfg(not(feature = "zeroize"))]
let digest = {
let d = pool.finalize();
let mut arr = [0u8; 64];
arr.copy_from_slice(&d);
arr
};
*byte = digest[0];
final_hash.update(digest.as_ref());
}
final_hash.update(&*output);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let tsc = unsafe { _rdtsc() };
final_hash.update(tsc.to_le_bytes());
}
let now_ns = SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
final_hash.update(now_ns.to_le_bytes());
output.copy_from_slice(&final_hash.finalize());
*output
}
#[cfg(feature = "advance")]
fn gather_system_entropy() -> [u8; 64] {
let mut h = Sha3_512::new();
h.update(env!("CARGO_PKG_VERSION").as_bytes());
#[cfg(feature = "build_separator")]
h.update(BUILD_SEPARATOR);
if let Ok(d) = SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
h.update(d.as_nanos().to_le_bytes());
}
let mono_ns = std::time::Instant::now();
h.update(format!("{mono_ns:?}").as_bytes());
#[cfg(target_os = "linux")]
{
let mut ts: libc::timespec = unsafe { std::mem::zeroed() };
if unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut ts) } == 0 {
h.update(ts.tv_sec.to_le_bytes());
h.update(ts.tv_nsec.to_le_bytes());
}
if unsafe { libc::clock_gettime(libc::CLOCK_TAI, &mut ts) } == 0 {
h.update(ts.tv_sec.to_le_bytes());
h.update(ts.tv_nsec.to_le_bytes());
}
}
h.update(format!("{:?}", thread::current().id()).as_bytes());
h.update(process::id().to_le_bytes());
let stack_var = 0usize;
h.update((&stack_var as *const usize as usize).to_le_bytes());
for path in &[
"/proc/uptime",
"/proc/loadavg",
"/proc/meminfo",
"/proc/stat",
"/proc/interrupts",
"/proc/net/dev",
"/proc/self/schedstat",
"/proc/net/snmp",
"/proc/self/statm",
"/proc/self/schedstat",
"/proc/self/status",
] {
if let Ok(mut f) = File::open(path) {
let mut buf = [0u8; 256];
if let Ok(n) = f.read(&mut buf) {
h.update(&buf[..n]);
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let tsc = unsafe { _rdtsc() };
h.update(tsc.to_le_bytes());
}
let mut result = [0u8; 64];
result.copy_from_slice(&h.finalize());
result
}
#[cfg(feature = "exp_network_rng")]
fn gather_network_jitter() -> [u8; 64] {
use std::time::Instant;
let mut result = [0u8; 64];
let mut health = crate::rng::HealthTests::new(
35, 325, 10, 20000, 2, );
if let Ok(socket) = UdpSocket::bind("0.0.0.0:0")
&& socket.set_nonblocking(true).is_ok()
{
let mut hasher = Sha3_512::new();
let mut recv_buf = [0u8; 512];
let mut dns_payload: [u8; 17] = [
0x13, 0x37, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x01,
];
let servers = ["9.9.9.9:53", "8.8.8.8:53", "1.1.1.1:53", "208.67.222.222:53"];
let mut server_idx = 0usize;
for byte in result.iter_mut() {
let mut accumulator = 0u8;
let mut bits_collected = 0u32;
let mut get_raw_sample = |h: &mut Sha3_512| -> u8 {
let start = Instant::now();
let elapsed = start.elapsed().as_nanos();
dns_payload[0] = (elapsed & 0xFF) as u8;
dns_payload[1] = ((elapsed >> 8) & 0xFF) as u8;
let _ = socket.send_to(&dns_payload, servers[server_idx % servers.len()]);
server_idx += 1;
let _ = socket.recv_from(&mut recv_buf);
let elapsed_post = start.elapsed().as_nanos();
h.update(elapsed_post.to_le_bytes()); (elapsed_post & 0xFF) as u8
};
while bits_collected < 8 {
let b1 = get_raw_sample(&mut hasher) & 1;
let b2 = get_raw_sample(&mut hasher) & 1;
if b1 != b2 {
health.process_sample(b1);
accumulator = (accumulator << 1) | b1;
bits_collected += 1;
}
}
*byte = accumulator;
}
hasher.update(result);
result.copy_from_slice(&hasher.finalize());
}
result
}
#[cfg(not(feature = "exp_network_rng"))]
fn gather_network_jitter() -> [u8; 64] {
[0u8; 64]
}
#[cfg(all(feature = "exp_simd_rng", any(target_arch = "x86", target_arch = "x86_64")))]
fn gather_simd_jitter() -> [u8; 64] {
use std::arch::x86_64::{
__m256i, _mm256_add_epi64, _mm256_loadu_si256, _mm256_mul_epi32, _mm256_set1_epi64x,
_mm256_storeu_si256, _mm256_xor_si256,
};
use std::time::Instant;
let mut jitter = [0u8; 64];
if std::is_x86_feature_detected!("avx2") {
#[target_feature(enable = "avx2")]
unsafe fn inner(jitter: &mut [u8; 64]) {
let mut health = crate::rng::HealthTests::new(
5000, 5000, 10, 20_000, 2,
);
let mut mem_block = vec![0i64; 256 * 1024];
let mut lfsr: u16 = 0xACE1;
for byte in jitter.iter_mut() {
let mut accumulator = 0u8;
let mut bits_collected = 0u32;
let mut get_raw_sample = || -> u8 {
let start = Instant::now();
let start_tsc = unsafe { _rdtsc() };
let mut v1 = _mm256_set1_epi64x(0xDEAD_BEEF_CAFE_BABEu64 as i64);
let mut v2 = _mm256_set1_epi64x(0x1234_5678_90AB_CDEFu64 as i64);
for _ in 0..256 {
v1 = _mm256_add_epi64(v1, v2);
v2 = _mm256_xor_si256(v2, v1);
v1 = _mm256_mul_epi32(v1, v2);
lfsr = (lfsr >> 1) ^ (-(lfsr as i16 & 1) as u16 & 0xB400);
let idx = (lfsr as usize) % (mem_block.len() - 4);
unsafe {
let mem_vec =
_mm256_loadu_si256(mem_block.as_ptr().add(idx) as *const __m256i);
v1 = _mm256_add_epi64(v1, mem_vec);
_mm256_storeu_si256(
mem_block.as_mut_ptr().add(idx) as *mut __m256i,
v1,
);
}
}
let mut out = [0u64; 4];
unsafe {
_mm256_storeu_si256(out.as_mut_ptr() as *mut __m256i, v1);
}
std::hint::black_box(out);
std::hint::black_box(&mut mem_block);
let elapsed_ns = start.elapsed().as_nanos() as u64;
let elapsed_tsc = unsafe { _rdtsc() }.wrapping_sub(start_tsc);
((elapsed_ns ^ elapsed_tsc) & 0xFF) as u8
};
while bits_collected < 8 {
let b1 = get_raw_sample() & 1;
let b2 = get_raw_sample() & 1;
if b1 != b2 {
health.process_sample(b1);
accumulator = (accumulator << 1) | b1;
bits_collected += 1;
}
}
let mut h = Sha3_512::new();
h.update([accumulator]);
let d = h.finalize();
*byte = d[0] ^ d[1];
}
}
unsafe { inner(&mut jitter) };
}
jitter
}
#[cfg(not(all(feature = "exp_simd_rng", any(target_arch = "x86", target_arch = "x86_64"))))]
fn gather_simd_jitter() -> [u8; 64] {
[0u8; 64]
}
#[cfg(all(feature = "advance", any(target_arch = "x86", target_arch = "x86_64")))]
fn gather_hardware_rng() -> [u8; 64] {
let mut h = Sha3_512::new();
const ITERS: usize = 64 / core::mem::size_of::<usize>();
if std::is_x86_feature_detected!("rdseed") {
for _ in 0..ITERS {
let mut val: usize = 0;
let mut ok: u8;
for _ in 0..100 {
unsafe {
core::arch::asm!(
"rdseed {val}",
"setc {ok}",
val = out(reg) val,
ok = out(reg_byte) ok,
);
}
if ok != 0 {
h.update(val.to_le_bytes());
break;
}
core::hint::spin_loop();
}
}
}
if std::is_x86_feature_detected!("rdrand") {
for _ in 0..ITERS {
let mut val: usize = 0;
let mut ok: u8;
for _ in 0..10 {
unsafe {
core::arch::asm!(
"rdrand {val}",
"setc {ok}",
val = out(reg) val,
ok = out(reg_byte) ok,
);
}
if ok != 0 {
h.update(val.to_le_bytes());
break;
}
core::hint::spin_loop();
}
}
}
let mut result = [0u8; 64];
result.copy_from_slice(&h.finalize());
result
}
#[cfg(not(all(feature = "advance", any(target_arch = "x86", target_arch = "x86_64"))))]
fn gather_hardware_rng() -> [u8; 64] {
[0u8; 64]
}
#[cfg(all(feature = "advance", target_os = "linux"))]
fn gather_perf_events() -> [u8; 64] {
let mut h = Sha3_512::new();
#[repr(C)]
#[derive(Default)]
struct perf_event_attr {
pub type_: u32,
pub size: u32,
pub config: u64,
pub sample_period_or_freq: u64,
pub sample_type: u64,
pub read_format: u64,
pub flags: u64,
pub wakeup_events_or_watermark: u32,
pub bp_type: u32,
pub bp_addr: u64,
pub bp_len: u64,
pub branch_sample_type: u64,
pub sample_regs_user: u64,
pub sample_stack_user: u32,
pub clockid: i32,
pub sample_regs_intr: u64,
pub aux_watermark: u32,
pub sample_max_stack: u16,
pub __reserved_2: u16,
}
let events = [0, 1, 3, 5];
for &event_id in &events {
let mut attr: perf_event_attr = Default::default();
attr.type_ = 0; attr.size = std::mem::size_of::<perf_event_attr>() as u32;
attr.config = event_id as u64;
attr.flags = (1 << 0) | (1 << 3) | (1 << 4);
let fd = unsafe {
libc::syscall(
libc::SYS_perf_event_open,
&attr as *const _ as *const libc::c_void,
0isize, -1isize, -1isize, 0isize, )
};
if fd >= 0 {
let ioc_enable = 0x2400;
let ioc_disable = 0x2401;
unsafe { libc::ioctl(fd as i32, ioc_enable, 0) };
for _ in 0..1000 {
core::hint::spin_loop();
}
unsafe { libc::ioctl(fd as i32, ioc_disable, 0) };
let mut count: u64 = 0;
if unsafe {
libc::read(fd as i32, &mut count as *mut _ as *mut libc::c_void, 8)
} == 8 {
h.update(count.to_le_bytes());
}
unsafe { libc::close(fd as i32) };
}
}
let mut result = [0u8; 64];
result.copy_from_slice(&h.finalize());
result
}
#[cfg(not(all(feature = "advance", target_os = "linux")))]
fn gather_perf_events() -> [u8; 64] {
[0u8; 64]
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
#[cfg(feature = "advance")]
fn cpu_jitter_produces_unique_outputs() {
let mut seen = HashSet::new();
for i in 0..5 {
let j = gather_cpu_jitter();
eprintln!("Jitter[{i}]: {j:02x?}");
assert!(seen.insert(j.to_vec()), "CRITICAL: duplicate CPU jitter output");
}
}
#[test]
#[cfg(feature = "advance")]
fn cpu_jitter_monobit_proportion() {
let mut ones = 0u32;
let mut total = 0u32;
for _ in 0..10 {
let j = gather_cpu_jitter();
for &b in &j {
ones += b.count_ones();
total += 8;
}
}
let p = ones as f64 / total as f64;
eprintln!("Monobit proportion of 1s: {p:.4} (ideal 0.5000)");
assert!(p > 0.40 && p < 0.60, "monobit test failed: {p}");
}
#[test]
#[cfg(feature = "advance")]
fn system_entropy_produces_unique_outputs() {
let mut seen = HashSet::new();
for i in 0..5 {
let s = gather_system_entropy();
eprintln!("SysEnt[{i}]: {:02x?}", &s[..16]);
assert!(seen.insert(s.to_vec()), "CRITICAL: duplicate system entropy");
std::thread::sleep(std::time::Duration::from_millis(1));
}
}
#[test]
fn blended_entropy_produces_unique_outputs() {
let mut seen = HashSet::new();
for i in 0..5 {
let b = get_blended_entropy();
eprintln!("Blended[{i}]: {:02x?}", &b[..16]);
assert!(seen.insert(b.to_vec()), "CRITICAL: duplicate blended entropy");
}
}
}