Skip to main content

acta_maker_sdk/
nonce.rs

1use rand::rngs::OsRng;
2use rand::{RngCore, SeedableRng, TryRngCore};
3use rand_xoshiro::Xoshiro256PlusPlus;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6#[derive(Debug)]
7pub struct NonceError(String);
8
9impl std::fmt::Display for NonceError {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        f.write_str(&self.0)
12    }
13}
14
15impl std::error::Error for NonceError {}
16
17pub struct NonceGenerator {
18    rng: Xoshiro256PlusPlus,
19}
20
21impl NonceGenerator {
22    pub fn new() -> Result<Self, NonceError> {
23        let mut seed = [0u8; 32];
24        let mut rng = OsRng;
25        rng.try_fill_bytes(&mut seed)
26            .map_err(|e| NonceError(format!("OsRng unavailable: {e}")))?;
27        Ok(Self {
28            rng: Xoshiro256PlusPlus::from_seed(seed),
29        })
30    }
31
32    pub fn next_u64(&mut self) -> u64 {
33        self.rng.next_u64()
34    }
35}
36
37/// Lock-free atomic nonce generator for concurrent use.
38///
39/// Uses an atomic counter offset by an entropy base. Values are unique until
40/// the `u64` counter wraps.
41/// Call [`init()`](Self::init) once before use to seed from OS entropy.
42pub struct AtomicNonceGenerator {
43    counter: AtomicU64,
44    entropy_base: AtomicU64,
45}
46
47impl AtomicNonceGenerator {
48    pub const fn new() -> Self {
49        Self {
50            counter: AtomicU64::new(1),
51            entropy_base: AtomicU64::new(0),
52        }
53    }
54
55    pub fn init(&self) -> Result<(), NonceError> {
56        if self.entropy_base.load(Ordering::Relaxed) == 0 {
57            let mut buf = [0u8; 8];
58            let mut rng = OsRng;
59            rng.try_fill_bytes(&mut buf)
60                .map_err(|e| NonceError(format!("OsRng unavailable: {e}")))?;
61            let entropy = u64::from_le_bytes(buf);
62            let _ =
63                self.entropy_base
64                    .compare_exchange(0, entropy, Ordering::SeqCst, Ordering::Relaxed);
65        }
66        Ok(())
67    }
68
69    #[inline]
70    pub fn next_u64(&self) -> Result<u64, NonceError> {
71        let base = self.entropy_base.load(Ordering::Relaxed);
72        let base = if base == 0 {
73            self.init()?;
74            self.entropy_base.load(Ordering::Relaxed)
75        } else {
76            base
77        };
78
79        let count = self.counter.fetch_add(1, Ordering::Relaxed);
80        Ok(base.wrapping_add(count))
81    }
82}
83
84impl Default for AtomicNonceGenerator {
85    fn default() -> Self {
86        Self::new()
87    }
88}
89
90#[cfg(test)]
91#[path = "nonce_tests.rs"]
92mod tests;