#[cfg(not(feature = "shake256"))]
use alloc::string::String;
use alloc::vec::Vec;
use core::sync::atomic::Ordering;
use lib_q_core::{
Error,
Nonce,
Result,
};
use portable_atomic::AtomicU64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonceConfig {
pub secure_generation: bool,
pub nonce_size: usize,
}
impl Default for NonceConfig {
fn default() -> Self {
Self {
secure_generation: true,
nonce_size: 16, }
}
}
impl NonceConfig {
pub fn strict() -> Self {
Self {
secure_generation: true,
nonce_size: 16,
}
}
pub fn permissive() -> Self {
Self {
secure_generation: true,
nonce_size: 16,
}
}
}
pub struct NonceManager {
config: NonceConfig,
counter: AtomicU64,
}
impl NonceManager {
pub fn new() -> Self {
Self::with_config(NonceConfig::default())
}
pub fn with_config(config: NonceConfig) -> Self {
Self {
config,
counter: AtomicU64::new(0),
}
}
pub fn generate_nonce(&self) -> Result<Nonce> {
if self.config.secure_generation {
self.generate_secure_nonce()
} else {
self.generate_counter_nonce()
}
}
fn generate_secure_nonce(&self) -> Result<Nonce> {
#[cfg(not(feature = "shake256"))]
{
self.counter.fetch_add(1, Ordering::SeqCst);
Err(Error::RandomGenerationFailed {
operation: String::from(
"lib-q-aead secure nonce generation requires the `shake256` feature (it \
pulls in lib-q-random for OS/hardware entropy); there is no \
non-cryptographic fallback",
),
})
}
#[cfg(feature = "shake256")]
{
self.counter.fetch_add(1, Ordering::SeqCst);
let mut nonce_data = alloc::vec![0u8; self.config.nonce_size];
lib_q_random::fill_entropy(&mut nonce_data).map_err(|e| {
Error::RandomGenerationFailed {
operation: alloc::format!(
"lib-q-aead secure nonce generation: entropy source unavailable: {e}"
),
}
})?;
if nonce_data.iter().all(|&b| b == 0) {
nonce_data[0] = 1; }
if nonce_data.iter().all(|&b| b == 0xFF) {
nonce_data[0] = 0xFE; }
Ok(Nonce::new(nonce_data))
}
}
fn generate_counter_nonce(&self) -> Result<Nonce> {
let counter = self.counter.fetch_add(1, Ordering::SeqCst);
let mut nonce_data = Vec::with_capacity(self.config.nonce_size);
for i in 0..self.config.nonce_size {
let byte = ((counter.wrapping_mul(0x9E3779B9u64.wrapping_add(i as u64))) >> 24) as u8;
nonce_data.push(byte);
}
if nonce_data.iter().all(|&b| b == 0) {
nonce_data[0] = 1; }
if nonce_data.iter().all(|&b| b == 0xFF) {
nonce_data[0] = 0xFE; }
Ok(Nonce::new(nonce_data))
}
pub fn validate_nonce(&self, nonce: &Nonce) -> Result<()> {
self.validate_nonce_format(nonce)
}
fn validate_nonce_format(&self, nonce: &Nonce) -> Result<()> {
let nonce_bytes = nonce.as_bytes();
if nonce_bytes.len() != self.config.nonce_size {
return Err(Error::InvalidNonceSize {
expected: self.config.nonce_size,
actual: nonce_bytes.len(),
});
}
if nonce_bytes.iter().all(|&b| b == 0) {
return Err(Error::InvalidNonceSize {
expected: 1,
actual: 0,
});
}
if nonce_bytes.iter().all(|&b| b == 0xFF) {
return Err(Error::InvalidNonceSize {
expected: 1,
actual: 0,
});
}
Ok(())
}
pub fn get_counter(&self) -> u64 {
self.counter.load(Ordering::SeqCst)
}
pub fn reset_counter(&self) {
self.counter.store(0, Ordering::SeqCst);
}
}
impl Default for NonceManager {
fn default() -> Self {
Self::new()
}
}
static NONCE_MANAGER: NonceManager = NonceManager {
config: NonceConfig {
secure_generation: true,
nonce_size: 16,
},
counter: AtomicU64::new(0),
};
pub fn get_nonce_manager() -> &'static NonceManager {
&NONCE_MANAGER
}
pub fn generate_nonce() -> Result<Nonce> {
get_nonce_manager().generate_nonce()
}
pub fn validate_nonce(nonce: &Nonce) -> Result<()> {
get_nonce_manager().validate_nonce(nonce)
}
pub mod utils {
use super::*;
pub fn nonce_from_counter(counter: u64, nonce_size: usize) -> Nonce {
let mut nonce_data = Vec::with_capacity(nonce_size);
nonce_data.extend_from_slice(&counter.to_le_bytes());
nonce_data.resize(nonce_size, 0);
Nonce::new(nonce_data)
}
pub fn nonce_from_random(random_data: &[u8], nonce_size: usize) -> Result<Nonce> {
if random_data.len() < nonce_size {
return Err(Error::InvalidNonceSize {
expected: nonce_size,
actual: random_data.len(),
});
}
let nonce_data = random_data[..nonce_size].to_vec();
Ok(Nonce::new(nonce_data))
}
}
#[cfg(test)]
mod tests {
#[cfg(not(feature = "std"))]
use alloc::vec;
use super::*;
#[test]
fn test_nonce_config_defaults() {
let config = NonceConfig::default();
assert!(config.secure_generation);
assert_eq!(config.nonce_size, 16);
}
#[test]
fn test_nonce_config_strict() {
let config = NonceConfig::strict();
assert!(config.secure_generation);
assert_eq!(config.nonce_size, 16);
}
#[test]
fn test_nonce_config_permissive() {
let config = NonceConfig::permissive();
assert!(config.secure_generation);
assert_eq!(config.nonce_size, 16);
}
#[cfg(feature = "shake256")]
#[test]
fn test_permissive_config_first_nonces_differ() {
let a = NonceManager::with_config(NonceConfig::permissive());
let b = NonceManager::with_config(NonceConfig::permissive());
let nonce_a = a.generate_nonce().unwrap();
let nonce_b = b.generate_nonce().unwrap();
assert_ne!(
nonce_a.as_bytes(),
nonce_b.as_bytes(),
"NonceConfig::permissive() produced identical first nonces from two independent, \
freshly constructed NonceManagers — it must not route to the deterministic \
counter-derived nonce path"
);
}
#[cfg(not(feature = "shake256"))]
#[test]
fn test_secure_nonce_fails_closed_without_entropy_feature() {
let manager = NonceManager::new();
let err = manager
.generate_nonce()
.expect_err("entropy-less build must not produce a 'secure' nonce");
assert!(
matches!(err, Error::RandomGenerationFailed { .. }),
"expected RandomGenerationFailed, got {err:?}"
);
}
#[cfg(feature = "shake256")]
#[test]
fn test_secure_nonce_succeeds_with_entropy_feature() {
let manager = NonceManager::new();
assert!(manager.generate_nonce().is_ok());
}
#[test]
fn test_nonce_manager_creation() {
let manager = NonceManager::new();
assert_eq!(manager.get_counter(), 0);
}
#[test]
fn test_nonce_manager_with_config() {
let config = NonceConfig::strict();
let manager = NonceManager::with_config(config);
assert_eq!(manager.get_counter(), 0);
}
#[cfg(feature = "shake256")]
#[test]
fn test_generate_secure_nonce() {
let manager = NonceManager::new();
let nonce1 = manager.generate_nonce().unwrap();
let nonce2 = manager.generate_nonce().unwrap();
assert_eq!(nonce1.as_bytes().len(), 16);
assert_eq!(nonce2.as_bytes().len(), 16);
assert_ne!(nonce1.as_bytes(), nonce2.as_bytes());
}
#[test]
fn test_generate_counter_nonce() {
let config = NonceConfig {
secure_generation: false,
..Default::default()
};
let manager = NonceManager::with_config(config);
let nonce1 = manager.generate_nonce().unwrap();
let nonce2 = manager.generate_nonce().unwrap();
assert_eq!(nonce1.as_bytes().len(), 16);
assert_eq!(nonce2.as_bytes().len(), 16);
assert_ne!(nonce1.as_bytes(), nonce2.as_bytes());
assert_eq!(manager.get_counter(), 2);
}
#[test]
fn test_validate_nonce_format() {
let manager = NonceManager::new();
let nonce = Nonce::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
assert!(manager.validate_nonce(&nonce).is_ok());
let zero_nonce = Nonce::new(vec![0u8; 16]);
assert!(manager.validate_nonce(&zero_nonce).is_err());
let ones_nonce = Nonce::new(vec![0xFFu8; 16]);
assert!(manager.validate_nonce(&ones_nonce).is_err());
let wrong_size_nonce = Nonce::new(vec![1, 2, 3, 4]);
assert!(manager.validate_nonce(&wrong_size_nonce).is_err());
}
#[test]
fn validate_nonce_is_stateless_and_does_not_consume_the_nonce() {
let manager = NonceManager::new();
let nonce = Nonce::new(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
assert!(manager.validate_nonce(&nonce).is_ok());
assert!(
manager.validate_nonce(&nonce).is_ok(),
"validate_nonce rejected a nonce it had already accepted — it must check format only, \
with no memory of what it has seen"
);
}
#[cfg(feature = "shake256")]
#[test]
fn test_counter_operations() {
let manager = NonceManager::new();
assert_eq!(manager.get_counter(), 0);
let _nonce1 = manager.generate_nonce().unwrap();
assert_eq!(manager.get_counter(), 1);
let _nonce2 = manager.generate_nonce().unwrap();
assert_eq!(manager.get_counter(), 2);
manager.reset_counter();
assert_eq!(manager.get_counter(), 0);
}
#[cfg(feature = "shake256")]
#[test]
fn test_global_nonce_functions() {
let nonce1 = generate_nonce().unwrap();
let nonce2 = generate_nonce().unwrap();
assert_eq!(nonce1.as_bytes().len(), 16);
assert_eq!(nonce2.as_bytes().len(), 16);
assert_ne!(nonce1.as_bytes(), nonce2.as_bytes());
assert!(validate_nonce(&nonce1).is_ok());
assert!(validate_nonce(&nonce2).is_ok());
assert!(validate_nonce(&nonce1).is_ok());
}
#[test]
fn nonce_manager_carries_no_tracking_state() {
use core::mem::size_of;
assert_eq!(
size_of::<NonceManager>(),
size_of::<NonceConfig>() + size_of::<AtomicU64>(),
"NonceManager grew a field. If that field is a used-nonce tracker, read the module \
docs first: a bounded tracker cannot detect a replay older than its window, and the \
two previous attempts failed in opposite directions."
);
}
#[test]
fn test_nonce_utils() {
let nonce1 = utils::nonce_from_counter(42, 16);
assert_eq!(nonce1.as_bytes().len(), 16);
let random_data = vec![
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
];
let nonce2 = utils::nonce_from_random(&random_data, 16).unwrap();
assert_eq!(nonce2.as_bytes().len(), 16);
}
#[cfg(feature = "shake256")]
#[test]
fn test_fresh_nonce_managers_produce_different_first_nonce() {
const DRAWS: usize = 2000;
let mut nonces: Vec<Vec<u8>> = Vec::with_capacity(DRAWS);
for _ in 0..DRAWS {
let manager = NonceManager::new();
let nonce = manager.generate_nonce().unwrap();
nonces.push(nonce.as_bytes().to_vec());
}
nonces.sort();
let has_collision = nonces.windows(2).any(|pair| pair[0] == pair[1]);
assert!(
!has_collision,
"two fresh NonceManagers produced identical first nonces out of {DRAWS} draws \
(non-cryptographic/predictable generator)"
);
}
#[cfg(all(feature = "std", feature = "shake256", not(target_arch = "wasm32")))]
#[test]
fn test_secure_nonce_is_predictable_from_public_clock_and_counter() {
use std::collections::hash_map::DefaultHasher;
use std::hash::{
Hash,
Hasher,
};
use std::time::{
SystemTime,
UNIX_EPOCH,
};
let now_before = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let manager = NonceManager::new();
let nonce = manager.generate_nonce().unwrap();
let now_after = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let counter: u64 = 0;
let mut predicted = false;
for now in now_before..=now_after {
let mut hasher = DefaultHasher::new();
now.hash(&mut hasher);
counter.hash(&mut hasher);
let seed = hasher.finish();
let mut candidate = Vec::with_capacity(16);
for i in 0..16u64 {
let mut byte_hasher = DefaultHasher::new();
(seed + i).hash(&mut byte_hasher);
candidate.push((byte_hasher.finish() & 0xFF) as u8);
}
if candidate == nonce.as_bytes() {
predicted = true;
break;
}
}
assert!(
!predicted,
"the 'secure' nonce was fully reproducible from public information (a wall-clock \
bracket spanning {} candidate nanoseconds + the always-zero starting counter) — it \
carries no real entropy",
now_after.saturating_sub(now_before) + 1
);
}
}