rustywallet_batch/
fast_gen.rs

1//! Fast key generation using optimized RNG.
2//!
3//! This module provides high-performance key generation by using
4//! ChaCha20 RNG instead of OS RNG for better throughput.
5
6use rand::SeedableRng;
7use rand_chacha::ChaCha20Rng;
8use rayon::prelude::*;
9use rustywallet_keys::private_key::PrivateKey;
10use secp256k1::{Secp256k1, SecretKey};
11
12/// Fast key generator using ChaCha20 RNG.
13///
14/// This generator achieves much higher throughput than OS RNG
15/// by using a cryptographically secure but faster PRNG.
16pub struct FastKeyGenerator {
17    /// Number of keys to generate
18    count: usize,
19    /// Whether to use parallel processing
20    parallel: bool,
21    /// Chunk size for parallel processing
22    chunk_size: usize,
23}
24
25impl FastKeyGenerator {
26    /// Create a new fast key generator.
27    pub fn new(count: usize) -> Self {
28        Self {
29            count,
30            parallel: true,
31            chunk_size: 10_000,
32        }
33    }
34
35    /// Set whether to use parallel processing.
36    pub fn parallel(mut self, enabled: bool) -> Self {
37        self.parallel = enabled;
38        self
39    }
40
41    /// Set the chunk size for parallel processing.
42    pub fn chunk_size(mut self, size: usize) -> Self {
43        self.chunk_size = size;
44        self
45    }
46
47    /// Generate keys using fast RNG.
48    pub fn generate(self) -> Vec<PrivateKey> {
49        if self.parallel {
50            self.generate_parallel()
51        } else {
52            self.generate_sequential()
53        }
54    }
55
56    /// Generate keys sequentially with fast RNG.
57    fn generate_sequential(self) -> Vec<PrivateKey> {
58        let secp = Secp256k1::new();
59        let mut rng = ChaCha20Rng::from_entropy();
60        
61        (0..self.count)
62            .map(|_| {
63                let (secret_key, _) = secp.generate_keypair(&mut rng);
64                secret_key_to_private_key(secret_key)
65            })
66            .collect()
67    }
68
69    /// Generate keys in parallel with fast RNG.
70    fn generate_parallel(self) -> Vec<PrivateKey> {
71        let num_chunks = self.count.div_ceil(self.chunk_size);
72        
73        (0..num_chunks)
74            .into_par_iter()
75            .flat_map(|chunk_idx| {
76                let start = chunk_idx * self.chunk_size;
77                let end = (start + self.chunk_size).min(self.count);
78                let chunk_count = end - start;
79                
80                // Each thread gets its own secp context and RNG
81                let secp = Secp256k1::new();
82                // Seed RNG with entropy + chunk index for uniqueness
83                let mut seed = [0u8; 32];
84                seed[..8].copy_from_slice(&chunk_idx.to_le_bytes());
85                // Mix in some entropy
86                use rand::RngCore;
87                let mut temp_rng = rand::rngs::OsRng;
88                temp_rng.fill_bytes(&mut seed[8..]);
89                
90                let mut rng = ChaCha20Rng::from_seed(seed);
91                
92                (0..chunk_count)
93                    .map(|_| {
94                        let (secret_key, _) = secp.generate_keypair(&mut rng);
95                        secret_key_to_private_key(secret_key)
96                    })
97                    .collect::<Vec<_>>()
98            })
99            .collect()
100    }
101}
102
103/// Convert secp256k1 SecretKey to PrivateKey.
104fn secret_key_to_private_key(secret_key: SecretKey) -> PrivateKey {
105    let bytes = secret_key.secret_bytes();
106    PrivateKey::from_bytes(bytes).expect("SecretKey should always be valid")
107}
108
109/// Generate keys using incremental method (EC point addition).
110///
111/// This is extremely fast for sequential key generation as it only
112/// requires one EC point addition per key instead of full key generation.
113pub struct IncrementalKeyGenerator {
114    /// Starting key value
115    start: [u8; 32],
116    /// Number of keys to generate
117    count: usize,
118    /// Step size
119    step: u64,
120}
121
122impl IncrementalKeyGenerator {
123    /// Create a new incremental generator starting from a random key.
124    pub fn new(count: usize) -> Self {
125        let start_key = PrivateKey::random();
126        Self {
127            start: start_key.to_bytes(),
128            count,
129            step: 1,
130        }
131    }
132
133    /// Create from a specific starting key.
134    pub fn from_key(key: &PrivateKey, count: usize) -> Self {
135        Self {
136            start: key.to_bytes(),
137            count,
138            step: 1,
139        }
140    }
141
142    /// Set the step size.
143    pub fn step(mut self, step: u64) -> Self {
144        self.step = step;
145        self
146    }
147
148    /// Generate keys incrementally.
149    pub fn generate(self) -> Vec<PrivateKey> {
150        let mut current = self.start;
151        let mut keys = Vec::with_capacity(self.count);
152        
153        for _ in 0..self.count {
154            if let Ok(key) = PrivateKey::from_bytes(current) {
155                keys.push(key);
156            }
157            // Increment current by step
158            add_to_bytes(&mut current, self.step);
159        }
160        
161        keys
162    }
163}
164
165/// Add a u64 value to a 32-byte big-endian number.
166fn add_to_bytes(bytes: &mut [u8; 32], value: u64) {
167    let value_bytes = value.to_be_bytes();
168    let mut carry: u64 = 0;
169    
170    // Add to the last 8 bytes
171    for i in (24..32).rev() {
172        let idx = 31 - i;
173        let v = if idx < 8 { value_bytes[7 - idx] } else { 0 };
174        let sum = bytes[i] as u64 + v as u64 + carry;
175        bytes[i] = sum as u8;
176        carry = sum >> 8;
177    }
178    
179    // Propagate carry
180    for i in (0..24).rev() {
181        if carry == 0 {
182            break;
183        }
184        let sum = bytes[i] as u64 + carry;
185        bytes[i] = sum as u8;
186        carry = sum >> 8;
187    }
188    
189    // Handle overflow (wrap around to 1)
190    if carry > 0 || !PrivateKey::is_valid(bytes) {
191        *bytes = [0u8; 32];
192        bytes[31] = 1;
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199    use std::collections::HashSet;
200    use std::time::Instant;
201
202    #[test]
203    fn test_fast_generator_sequential() {
204        let keys = FastKeyGenerator::new(1000)
205            .parallel(false)
206            .generate();
207        
208        assert_eq!(keys.len(), 1000);
209        
210        // All keys should be unique
211        let hex_keys: HashSet<_> = keys.iter().map(|k| k.to_hex()).collect();
212        assert_eq!(hex_keys.len(), 1000);
213    }
214
215    #[test]
216    fn test_fast_generator_parallel() {
217        let keys = FastKeyGenerator::new(10_000)
218            .parallel(true)
219            .chunk_size(1000)
220            .generate();
221        
222        assert_eq!(keys.len(), 10_000);
223        
224        // All keys should be unique
225        let hex_keys: HashSet<_> = keys.iter().map(|k| k.to_hex()).collect();
226        assert_eq!(hex_keys.len(), 10_000);
227    }
228
229    #[test]
230    fn test_incremental_generator() {
231        let base = PrivateKey::from_hex(
232            "0000000000000000000000000000000000000000000000000000000000000001"
233        ).unwrap();
234        
235        let keys = IncrementalKeyGenerator::from_key(&base, 5).generate();
236        
237        assert_eq!(keys.len(), 5);
238        assert_eq!(keys[0].to_hex(), "0000000000000000000000000000000000000000000000000000000000000001");
239        assert_eq!(keys[1].to_hex(), "0000000000000000000000000000000000000000000000000000000000000002");
240        assert_eq!(keys[2].to_hex(), "0000000000000000000000000000000000000000000000000000000000000003");
241    }
242
243    #[test]
244    fn test_performance_comparison() {
245        let count = 10_000;
246        
247        // Fast generator
248        let start = Instant::now();
249        let keys = FastKeyGenerator::new(count)
250            .parallel(true)
251            .generate();
252        let fast_elapsed = start.elapsed();
253        
254        assert_eq!(keys.len(), count);
255        
256        let fast_rate = count as f64 / fast_elapsed.as_secs_f64();
257        println!("Fast generator: {:.0} keys/sec", fast_rate);
258        
259        // Should be reasonably fast (lower threshold for test environment)
260        assert!(fast_rate > 1_000.0, "Fast generator should exceed 1K keys/sec in test mode");
261    }
262}