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/// Atomic counter offset by a lazily initialized OS-entropy seed.
37/// Counter arithmetic wraps modulo 2^64.
38/// Call [`Self::init`] to check entropy availability before the first nonce.
39pub struct AtomicNonceGenerator {
40    counter: AtomicU64,
41    entropy_base: AtomicU64,
42}
43
44impl AtomicNonceGenerator {
45    pub const fn new() -> Self {
46        Self {
47            counter: AtomicU64::new(1),
48            entropy_base: AtomicU64::new(0),
49        }
50    }
51
52    pub fn init(&self) -> Result<(), NonceError> {
53        if self.entropy_base.load(Ordering::Relaxed) == 0 {
54            let mut buf = [0u8; 8];
55            let mut rng = OsRng;
56            rng.try_fill_bytes(&mut buf)
57                .map_err(|e| NonceError(format!("OsRng unavailable: {e}")))?;
58            let entropy = u64::from_le_bytes(buf);
59            let _ =
60                self.entropy_base
61                    .compare_exchange(0, entropy, Ordering::SeqCst, Ordering::Relaxed);
62        }
63        Ok(())
64    }
65
66    #[inline]
67    pub fn next_u64(&self) -> Result<u64, NonceError> {
68        let base = self.entropy_base.load(Ordering::Relaxed);
69        let base = if base == 0 {
70            self.init()?;
71            self.entropy_base.load(Ordering::Relaxed)
72        } else {
73            base
74        };
75
76        let count = self.counter.fetch_add(1, Ordering::Relaxed);
77        Ok(base.wrapping_add(count))
78    }
79}
80
81impl Default for AtomicNonceGenerator {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87#[cfg(test)]
88#[path = "nonce_tests.rs"]
89mod tests;