use super::fork::ForkGuard;
use super::{Error, NONCE_SIZE};
const NONCE_MASK_96: u128 = (1_u128 << 96) - 1;
pub(crate) struct NonceGen {
base: u128,
counter: u64,
fork_guard: ForkGuard,
}
impl NonceGen {
pub(crate) fn new() -> Result<Self, Error> {
Ok(Self {
base: os_salt()?,
counter: 0,
fork_guard: ForkGuard::capture(),
})
}
pub(crate) fn next(&mut self) -> Result<[u8; NONCE_SIZE], Error> {
if !self.fork_guard.unchanged() {
self.resalt()?;
}
let nonce = nonce_value(self.base, self.counter);
self.counter = self.counter.wrapping_add(1);
if self.counter == 0 {
self.resalt()?;
}
Ok(nonce)
}
fn resalt(&mut self) -> Result<(), Error> {
self.base = os_salt()?;
self.counter = 0;
self.fork_guard = ForkGuard::capture();
Ok(())
}
}
fn nonce_value(base: u128, counter: u64) -> [u8; NONCE_SIZE] {
let value = base.wrapping_add(u128::from(counter)) & NONCE_MASK_96;
let bytes = value.to_le_bytes();
let mut nonce = [0_u8; NONCE_SIZE];
nonce.copy_from_slice(&bytes[..NONCE_SIZE]);
nonce
}
fn os_salt() -> Result<u128, Error> {
let mut bytes = [0_u8; 16];
getrandom::fill(&mut bytes[..NONCE_SIZE]).map_err(|_| Error::OsEntropy)?;
Ok(u128::from_le_bytes(bytes) & NONCE_MASK_96)
}
#[cfg(kani)]
mod kani_proofs {
use super::{nonce_value, NONCE_MASK_96};
#[kani::proof]
fn nonce_value_is_injective_in_counter() {
let base: u128 = kani::any();
kani::assume(base <= NONCE_MASK_96);
let c1: u64 = kani::any();
let c2: u64 = kani::any();
kani::assume(c1 != c2);
assert!(nonce_value(base, c1) != nonce_value(base, c2));
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::unwrap_used)]
use super::NonceGen;
#[test]
fn distinct_instances_draw_distinct_salts() {
let mut a = NonceGen::new().expect("OS entropy available in tests");
let mut b = NonceGen::new().expect("OS entropy available in tests");
assert_ne!(
a.next().expect("nonce a"),
b.next().expect("nonce b"),
"two generator instances produced the same first nonce (constant salt?)"
);
let mut c = NonceGen::new().expect("OS entropy available in tests");
assert_ne!(c.next().unwrap(), c.next().unwrap());
}
}