Skip to main content

acta_maker_sdk/
nonce.rs

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