rustywallet_vanity/
difficulty.rs

1//! Difficulty estimation for vanity patterns.
2
3use crate::address_type::AddressType;
4use crate::pattern::Pattern;
5use std::time::Duration;
6
7/// Difficulty level for a vanity pattern.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum DifficultyLevel {
10    /// Very easy, typically < 1 minute.
11    Easy,
12    /// Medium difficulty, 1-10 minutes.
13    Medium,
14    /// Hard, 10-60 minutes.
15    Hard,
16    /// Very hard, 1-24 hours.
17    VeryHard,
18    /// Extreme, > 24 hours.
19    Extreme,
20}
21
22impl DifficultyLevel {
23    /// Get a human-readable description.
24    pub fn description(&self) -> &'static str {
25        match self {
26            DifficultyLevel::Easy => "Easy (< 1 minute)",
27            DifficultyLevel::Medium => "Medium (1-10 minutes)",
28            DifficultyLevel::Hard => "Hard (10-60 minutes)",
29            DifficultyLevel::VeryHard => "Very Hard (1-24 hours)",
30            DifficultyLevel::Extreme => "Extreme (> 24 hours)",
31        }
32    }
33}
34
35impl std::fmt::Display for DifficultyLevel {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        write!(f, "{}", self.description())
38    }
39}
40
41/// Difficulty estimate for a vanity pattern.
42#[derive(Debug, Clone)]
43pub struct DifficultyEstimate {
44    /// The pattern being estimated.
45    pub pattern: Pattern,
46    /// Probability of finding a match per attempt.
47    pub probability: f64,
48    /// Expected number of attempts to find a match.
49    pub expected_attempts: u64,
50    /// Estimated time to find a match at given rate.
51    pub estimated_time: Duration,
52    /// Difficulty level category.
53    pub difficulty_level: DifficultyLevel,
54}
55
56impl DifficultyEstimate {
57    /// Calculate difficulty estimate for a pattern.
58    pub fn calculate(
59        pattern: &Pattern,
60        address_type: AddressType,
61        case_sensitive: bool,
62        generation_rate: f64,
63    ) -> Self {
64        let expected_attempts = pattern.difficulty(address_type, case_sensitive) as u64;
65        let probability = 1.0 / expected_attempts as f64;
66
67        let estimated_secs = if generation_rate > 0.0 {
68            expected_attempts as f64 / generation_rate
69        } else {
70            f64::INFINITY
71        };
72        let estimated_time = Duration::from_secs_f64(estimated_secs.min(f64::MAX / 2.0));
73
74        let difficulty_level = Self::categorize_difficulty(estimated_time);
75
76        Self {
77            pattern: pattern.clone(),
78            probability,
79            expected_attempts,
80            estimated_time,
81            difficulty_level,
82        }
83    }
84
85    /// Categorize difficulty based on estimated time.
86    fn categorize_difficulty(estimated_time: Duration) -> DifficultyLevel {
87        let secs = estimated_time.as_secs();
88        if secs < 60 {
89            DifficultyLevel::Easy
90        } else if secs < 600 {
91            DifficultyLevel::Medium
92        } else if secs < 3600 {
93            DifficultyLevel::Hard
94        } else if secs < 86400 {
95            DifficultyLevel::VeryHard
96        } else {
97            DifficultyLevel::Extreme
98        }
99    }
100
101    /// Check if this pattern is practical to search for.
102    pub fn is_practical(&self) -> bool {
103        matches!(
104            self.difficulty_level,
105            DifficultyLevel::Easy | DifficultyLevel::Medium | DifficultyLevel::Hard
106        )
107    }
108
109    /// Get a warning message if the pattern is very difficult.
110    pub fn warning(&self) -> Option<String> {
111        match self.difficulty_level {
112            DifficultyLevel::VeryHard => Some(format!(
113                "Warning: Pattern '{}' is very difficult. Expected time: {:?}",
114                self.pattern, self.estimated_time
115            )),
116            DifficultyLevel::Extreme => Some(format!(
117                "Warning: Pattern '{}' is extremely difficult and may take days or longer. Expected time: {:?}",
118                self.pattern, self.estimated_time
119            )),
120            _ => None,
121        }
122    }
123}
124
125impl std::fmt::Display for DifficultyEstimate {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        write!(
128            f,
129            "Pattern: {}, Difficulty: {}, Expected attempts: {}, Estimated time: {:?}",
130            self.pattern, self.difficulty_level, self.expected_attempts, self.estimated_time
131        )
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn test_difficulty_levels() {
141        assert_eq!(
142            DifficultyEstimate::categorize_difficulty(Duration::from_secs(30)),
143            DifficultyLevel::Easy
144        );
145        assert_eq!(
146            DifficultyEstimate::categorize_difficulty(Duration::from_secs(300)),
147            DifficultyLevel::Medium
148        );
149        assert_eq!(
150            DifficultyEstimate::categorize_difficulty(Duration::from_secs(1800)),
151            DifficultyLevel::Hard
152        );
153    }
154
155    #[test]
156    fn test_difficulty_estimate() {
157        let pattern = Pattern::prefix("1A").unwrap();
158        let estimate =
159            DifficultyEstimate::calculate(&pattern, AddressType::P2PKH, true, 1_000_000.0);
160
161        // 1 char after prefix = ~58 attempts at 1M/sec = very fast
162        assert_eq!(estimate.difficulty_level, DifficultyLevel::Easy);
163        assert!(estimate.is_practical());
164    }
165
166    #[test]
167    fn test_hard_pattern() {
168        let pattern = Pattern::prefix("1Bitcoin").unwrap();
169        let estimate =
170            DifficultyEstimate::calculate(&pattern, AddressType::P2PKH, true, 1_000_000.0);
171
172        // 7 chars = 58^7 = huge number
173        assert!(matches!(
174            estimate.difficulty_level,
175            DifficultyLevel::VeryHard | DifficultyLevel::Extreme
176        ));
177    }
178}