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 atomic counter XOR'd with entropy base for uniqueness.
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) -> u64 {
70        let base = self.entropy_base.load(Ordering::Relaxed);
71        let base = if base == 0 {
72            if let Err(e) = self.init() {
73                tracing::warn!(
74                    "AtomicNonceGenerator: OsRng unavailable ({e}), nonces will be sequential"
75                );
76            }
77            self.entropy_base.load(Ordering::Relaxed)
78        } else {
79            base
80        };
81
82        let count = self.counter.fetch_add(1, Ordering::Relaxed);
83        base.wrapping_add(count) ^ base.rotate_left((count & 63) as u32)
84    }
85}
86
87impl Default for AtomicNonceGenerator {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93#[cfg(test)]
94#[path = "nonce_tests.rs"]
95mod tests;