Skip to main content

kraken_api_client/auth/
nonce.rs

1//! Nonce generation for Kraken API authentication.
2//!
3//! Kraken requires a strictly increasing nonce for each authenticated request
4//! to prevent replay attacks.
5
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9/// Trait for providing nonces for authenticated requests.
10///
11/// The nonce must be strictly increasing for each request.
12/// Kraken recommends using a timestamp-based approach.
13pub trait NonceProvider: Send + Sync {
14    /// Generate the next nonce value.
15    ///
16    /// This value must be greater than any previously returned value.
17    fn next_nonce(&self) -> u64;
18}
19
20/// A nonce provider that generates strictly increasing nonces based on time.
21///
22/// Uses microseconds since UNIX epoch, with an atomic counter to ensure
23/// uniqueness even for requests made in the same microsecond.
24pub struct IncreasingNonce {
25    last_nonce: AtomicU64,
26}
27
28impl IncreasingNonce {
29    /// Create a new increasing nonce provider.
30    pub fn new() -> Self {
31        Self {
32            last_nonce: AtomicU64::new(0),
33        }
34    }
35
36    /// Get current time in microseconds since UNIX epoch.
37    fn current_time_micros() -> u64 {
38        SystemTime::now()
39            .duration_since(UNIX_EPOCH)
40            .unwrap_or_default()
41            .as_micros() as u64
42    }
43}
44
45impl Default for IncreasingNonce {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl NonceProvider for IncreasingNonce {
52    fn next_nonce(&self) -> u64 {
53        let time_nonce = Self::current_time_micros();
54
55        // Ensure the nonce is strictly increasing.
56        // Use the max of current time and last + 1.
57        loop {
58            let last = self.last_nonce.load(Ordering::SeqCst);
59            let next = time_nonce.max(last + 1);
60
61            if self
62                .last_nonce
63                .compare_exchange(last, next, Ordering::SeqCst, Ordering::SeqCst)
64                .is_ok()
65            {
66                return next;
67            }
68            // If CAS failed, another thread updated the value. Retry.
69        }
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76    use std::collections::HashSet;
77    use std::thread;
78
79    #[test]
80    fn test_nonce_strictly_increasing() {
81        let provider = IncreasingNonce::new();
82
83        let mut last = 0u64;
84        for _ in 0..1000 {
85            let nonce = provider.next_nonce();
86            assert!(nonce > last, "Nonce must be strictly increasing");
87            last = nonce;
88        }
89    }
90
91    #[test]
92    fn test_nonce_unique_across_threads() {
93        let provider = std::sync::Arc::new(IncreasingNonce::new());
94        let mut handles = vec![];
95
96        for _ in 0..4 {
97            let p = provider.clone();
98            handles.push(thread::spawn(move || {
99                let mut nonces = Vec::new();
100                for _ in 0..1000 {
101                    nonces.push(p.next_nonce());
102                }
103                nonces
104            }));
105        }
106
107        let mut all_nonces = HashSet::new();
108        for handle in handles {
109            let nonces = handle.join().unwrap();
110            for nonce in nonces {
111                assert!(
112                    all_nonces.insert(nonce),
113                    "Nonce must be unique across threads"
114                );
115            }
116        }
117    }
118}