Skip to main content

gpu_scatter_gather/
lib.rs

1//! GPU Scatter-Gather Wordlist Generator
2//!
3//! The world's fastest wordlist generator using GPU acceleration.
4//!
5//! # Core Innovation
6//!
7//! Instead of sequential odometer iteration (like maskprocessor), this uses
8//! direct index-to-word mapping via mixed-radix arithmetic on the GPU:
9//!
10//! ```text
11//! Index → Mixed-Radix Decomposition → Word
12//! ```
13//!
14//! This enables:
15//! - Massively parallel generation (every thread works independently)
16//! - O(1) random access to any position in the keyspace
17//! - Perfect GPU utilization with coalesced memory access
18//! - 500M-1B+ words/second throughput (3-7x faster than maskprocessor)
19//! - Multi-GPU support for linear scaling (v1.1.0+)
20//!
21//! # Example Usage
22//!
23//! ```rust,no_run
24//! use gpu_scatter_gather::{WordlistGenerator, Charset};
25//!
26//! # fn main() -> anyhow::Result<()> {
27//! let mut generator = WordlistGenerator::builder()
28//!     .charset(1, Charset::from("abc"))
29//!     .charset(2, Charset::from("123"))
30//!     .mask(&[1, 2, 1, 2])  // Pattern: ?1?2?1?2
31//!     .build()?;
32//!
33//! // Generate wordlist
34//! for word in generator.iter() {
35//!     println!("{}", String::from_utf8_lossy(&word));
36//! }
37//! # Ok(())
38//! # }
39//! ```
40
41pub mod charset;
42pub mod keyspace;
43pub mod mask;
44pub mod transpose;
45
46// CUDA-dependent modules (only available with "cuda" feature)
47#[cfg(feature = "cuda")]
48pub mod gpu;
49#[cfg(feature = "cuda")]
50pub mod multigpu;
51
52pub mod bindings;
53
54#[cfg(feature = "cuda")]
55pub mod ffi;
56
57// Re-exports for convenience
58pub use charset::Charset;
59pub use keyspace::{calculate_keyspace, index_to_word};
60pub use mask::Mask;
61
62use anyhow::Result;
63use std::collections::HashMap;
64
65/// Main wordlist generator builder
66pub struct WordlistGeneratorBuilder {
67    charsets: HashMap<usize, Charset>,
68    mask: Option<Vec<usize>>,
69    batch_size: usize,
70}
71
72impl WordlistGeneratorBuilder {
73    /// Create a new builder
74    pub fn new() -> Self {
75        Self {
76            charsets: HashMap::new(),
77            mask: None,
78            batch_size: 10_000_000, // 10M words default batch size
79        }
80    }
81
82    /// Add a charset with the given ID
83    pub fn charset(mut self, id: usize, charset: Charset) -> Self {
84        self.charsets.insert(id, charset);
85        self
86    }
87
88    /// Set the mask pattern (array of charset IDs)
89    pub fn mask(mut self, mask: &[usize]) -> Self {
90        self.mask = Some(mask.to_vec());
91        self
92    }
93
94    /// Set the batch size for GPU generation
95    pub fn batch_size(mut self, size: usize) -> Self {
96        self.batch_size = size;
97        self
98    }
99
100    /// Build the generator (currently CPU-only, GPU support coming)
101    pub fn build(self) -> Result<WordlistGenerator> {
102        let mask = self.mask.ok_or_else(|| anyhow::anyhow!("Mask not set"))?;
103
104        // Validate that all charset IDs in mask exist
105        for &charset_id in &mask {
106            if !self.charsets.contains_key(&charset_id) {
107                anyhow::bail!("Mask references undefined charset ID: {charset_id}");
108            }
109        }
110
111        Ok(WordlistGenerator {
112            charsets: self.charsets,
113            mask,
114            batch_size: self.batch_size,
115        })
116    }
117}
118
119impl Default for WordlistGeneratorBuilder {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125/// Main wordlist generator
126pub struct WordlistGenerator {
127    charsets: HashMap<usize, Charset>,
128    mask: Vec<usize>,
129    #[allow(dead_code)]
130    batch_size: usize,
131}
132
133impl WordlistGenerator {
134    /// Create a new builder
135    pub fn builder() -> WordlistGeneratorBuilder {
136        WordlistGeneratorBuilder::new()
137    }
138
139    /// Calculate the total keyspace size
140    pub fn keyspace_size(&self) -> u128 {
141        // Calculate product of all charset sizes in the mask
142        self.mask
143            .iter()
144            .map(|&id| self.charsets[&id].len() as u128)
145            .product()
146    }
147
148    /// Convert index to word using CPU reference implementation
149    pub fn index_to_word(&self, index: u64) -> Vec<u8> {
150        let mut output = vec![0u8; self.mask.len()];
151        let mut remaining = index;
152
153        // Process positions from right to left (least significant to most significant)
154        for pos in (0..self.mask.len()).rev() {
155            let charset_id = self.mask[pos];
156            let charset = &self.charsets[&charset_id];
157            let charset_size = charset.len() as u64;
158
159            // Mixed-radix decomposition
160            let char_idx = (remaining % charset_size) as usize;
161            output[pos] = charset.as_bytes()[char_idx];
162            remaining /= charset_size;
163        }
164
165        output
166    }
167
168    /// Create an iterator over all words (CPU reference implementation)
169    pub fn iter(&self) -> WordlistIterator<'_> {
170        WordlistIterator {
171            generator: self,
172            current_index: 0,
173            total_keyspace: self.keyspace_size(),
174        }
175    }
176}
177
178/// Iterator over wordlist entries
179pub struct WordlistIterator<'a> {
180    generator: &'a WordlistGenerator,
181    current_index: u64,
182    total_keyspace: u128,
183}
184
185impl<'a> Iterator for WordlistIterator<'a> {
186    type Item = Vec<u8>;
187
188    fn next(&mut self) -> Option<Self::Item> {
189        if self.current_index as u128 >= self.total_keyspace {
190            return None;
191        }
192
193        let word = self.generator.index_to_word(self.current_index);
194        self.current_index += 1;
195        Some(word)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_builder_pattern() {
205        let generator = WordlistGenerator::builder()
206            .charset(1, Charset::from("abc"))
207            .charset(2, Charset::from("123"))
208            .mask(&[1, 2])
209            .build()
210            .unwrap();
211
212        assert_eq!(generator.keyspace_size(), 9); // 3 * 3
213    }
214
215    #[test]
216    fn test_missing_charset() {
217        let result = WordlistGenerator::builder()
218            .charset(1, Charset::from("abc"))
219            .mask(&[1, 2]) // References charset 2 which doesn't exist
220            .build();
221
222        assert!(result.is_err());
223    }
224}