use std::sync::OnceLock;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
static COOKIE: OnceLock<[u8; 32]> = OnceLock::new();
static NONCE: AtomicU64 = AtomicU64::new(0);
const fn mix(mut x: u64, i: u64) -> u64 {
x = x.wrapping_add(i).wrapping_add(0x9E37_79B9_7F4A_7C15);
x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
x ^ (x >> 31)
}
fn cookie() -> &'static [u8; 32] {
COOKIE.get_or_init(|| {
let stack_marker = 0u8;
let addr = (&raw const stack_marker) as u64;
let wall = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let a = Instant::now();
let b = Instant::now();
let mono = u64::from(b.duration_since(a).subsec_nanos());
let mut seed = addr ^ wall.rotate_left(17) ^ mono.rotate_left(31);
let mut out = [0u8; 32];
for (i, chunk) in out.chunks_mut(8).enumerate() {
seed = mix(seed, i as u64);
chunk.copy_from_slice(&seed.to_le_bytes());
}
out
})
}
fn next_nonce() -> u64 {
NONCE.fetch_add(1, Ordering::Relaxed)
}
fn value_seed(nonce: u64) -> u64 {
let mut seed = nonce;
for (i, chunk) in cookie().chunks(8).enumerate() {
let word = u64::from_le_bytes(chunk.try_into().unwrap());
seed = mix(seed ^ word, i as u64);
}
seed
}
fn xor_with_keystream<const N: usize>(mut value: [u8; N], nonce: u64) -> [u8; N] {
let seed = value_seed(nonce);
for (block, chunk) in value.chunks_mut(8).enumerate() {
let key = mix(seed, block as u64).to_le_bytes();
for (byte, k) in chunk.iter_mut().zip(key.iter()) {
*byte ^= k;
}
}
value
}
#[derive(Clone, Copy)]
pub struct Mangled<const N: usize> {
pub(crate) nonce: u64,
pub(crate) bytes: [u8; N],
}
impl<const N: usize> Mangled<N> {
pub fn new(value: [u8; N]) -> Self {
let nonce = next_nonce();
Self {
nonce,
bytes: xor_with_keystream(value, nonce),
}
}
pub fn reveal_with<R>(&self, f: impl FnOnce(&[u8; N]) -> R) -> R {
struct Scrub<'a, const N: usize>(&'a mut [u8; N]);
impl<const N: usize> Drop for Scrub<'_, N> {
fn drop(&mut self) {
for byte in self.0.iter_mut() {
unsafe { std::ptr::write_volatile(byte, 0) };
}
}
}
let mut plain = xor_with_keystream(self.bytes, self.nonce);
let guard = Scrub(&mut plain);
f(guard.0)
}
}