extern crate alloc;
use oxicrypto_core::CryptoError;
pub struct NonceSequence<const N: usize> {
nonce: [u8; N],
counter: u64,
}
impl<const N: usize> core::fmt::Debug for NonceSequence<N> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "NonceSequence<{}>(counter={})", N, self.counter)
}
}
impl<const N: usize> NonceSequence<N> {
const PREFIX_LEN: usize = N - 8;
pub fn new(prefix: &[u8]) -> Result<Self, CryptoError> {
if N < 8 {
return Err(CryptoError::InvalidNonce);
}
if prefix.len() != Self::PREFIX_LEN {
return Err(CryptoError::InvalidNonce);
}
let mut nonce = [0u8; N];
nonce[..Self::PREFIX_LEN].copy_from_slice(prefix);
Ok(Self { nonce, counter: 0 })
}
pub fn generate(&mut self) -> Result<[u8; N], CryptoError> {
let current = self.counter;
self.counter = self
.counter
.checked_add(1)
.ok_or(CryptoError::Internal("NonceSequence counter overflow"))?;
let counter_bytes = current.to_be_bytes();
self.nonce[Self::PREFIX_LEN..].copy_from_slice(&counter_bytes);
Ok(self.nonce)
}
#[must_use]
pub fn count(&self) -> u64 {
self.counter
}
}
pub type Nonce12 = NonceSequence<12>;
pub type Nonce24 = NonceSequence<24>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nonce12_sequential_uniqueness() {
let prefix = [0xABu8; 4]; let mut seq = Nonce12::new(&prefix).expect("new");
let nonces: alloc::vec::Vec<[u8; 12]> =
(0..10).map(|_| seq.generate().expect("generate")).collect();
for i in 0..nonces.len() {
for j in i + 1..nonces.len() {
assert_ne!(nonces[i], nonces[j], "nonces[{i}] == nonces[{j}]");
}
}
for n in &nonces {
assert_eq!(&n[..4], &prefix);
}
for (idx, n) in nonces.iter().enumerate() {
let counter = u64::from_be_bytes(n[4..].try_into().expect("slice"));
assert_eq!(counter, idx as u64, "counter byte mismatch at index {idx}");
}
}
#[test]
fn nonce24_sequential_uniqueness() {
let prefix = [0xCDu8; 16]; let mut seq = Nonce24::new(&prefix).expect("new");
let n0 = seq.generate().expect("n0");
let n1 = seq.generate().expect("n1");
assert_ne!(n0, n1);
assert_eq!(&n0[..16], &prefix);
assert_eq!(&n1[..16], &prefix);
}
#[test]
fn nonce12_counter_overflow_detected() {
let prefix = [0u8; 4];
let mut seq = Nonce12::new(&prefix).expect("new");
seq.counter = u64::MAX - 1;
seq.generate().expect("penultimate nonce");
let result = seq.generate();
assert!(
matches!(result, Err(CryptoError::Internal(_))),
"should have detected overflow, got: {:?}",
result
);
}
#[test]
fn nonce12_wrong_prefix_length() {
let result = Nonce12::new(&[0u8; 5]);
assert!(
matches!(result, Err(CryptoError::InvalidNonce)),
"expected InvalidNonce, got: {:?}",
result.as_ref().err()
);
}
#[test]
fn nonce24_wrong_prefix_length() {
let result = Nonce24::new(&[0u8; 8]);
assert!(
matches!(result, Err(CryptoError::InvalidNonce)),
"expected InvalidNonce, got: {:?}",
result.as_ref().err()
);
}
#[test]
fn nonce12_count_tracks_generated() {
let prefix = [0u8; 4];
let mut seq = Nonce12::new(&prefix).expect("new");
assert_eq!(seq.count(), 0);
seq.generate().expect("generate 1");
assert_eq!(seq.count(), 1);
seq.generate().expect("generate 2");
assert_eq!(seq.count(), 2);
}
}