rustywallet_vanity/
pattern.rs1use crate::address_type::AddressType;
4use crate::error::PatternError;
5
6#[derive(Debug, Clone)]
8pub enum Pattern {
9 Prefix(String),
11 Suffix(String),
13 Contains(String),
15}
16
17impl Pattern {
18 pub fn prefix(s: &str) -> Result<Self, PatternError> {
20 if s.is_empty() {
21 return Err(PatternError::EmptyPattern);
22 }
23 Ok(Pattern::Prefix(s.to_string()))
24 }
25
26 pub fn suffix(s: &str) -> Result<Self, PatternError> {
28 if s.is_empty() {
29 return Err(PatternError::EmptyPattern);
30 }
31 Ok(Pattern::Suffix(s.to_string()))
32 }
33
34 pub fn contains(s: &str) -> Result<Self, PatternError> {
36 if s.is_empty() {
37 return Err(PatternError::EmptyPattern);
38 }
39 Ok(Pattern::Contains(s.to_string()))
40 }
41
42 pub fn as_str(&self) -> &str {
44 match self {
45 Pattern::Prefix(s) => s,
46 Pattern::Suffix(s) => s,
47 Pattern::Contains(s) => s,
48 }
49 }
50
51 pub fn matches(&self, address: &str, case_sensitive: bool) -> bool {
53 if case_sensitive {
54 self.matches_case_sensitive(address)
55 } else {
56 self.matches_case_insensitive(address)
57 }
58 }
59
60 fn matches_case_sensitive(&self, address: &str) -> bool {
61 match self {
62 Pattern::Prefix(p) => address.starts_with(p),
63 Pattern::Suffix(s) => address.ends_with(s),
64 Pattern::Contains(c) => address.contains(c),
65 }
66 }
67
68 fn matches_case_insensitive(&self, address: &str) -> bool {
69 let addr_lower = address.to_lowercase();
70 match self {
71 Pattern::Prefix(p) => addr_lower.starts_with(&p.to_lowercase()),
72 Pattern::Suffix(s) => addr_lower.ends_with(&s.to_lowercase()),
73 Pattern::Contains(c) => addr_lower.contains(&c.to_lowercase()),
74 }
75 }
76
77 pub fn validate_for_type(
79 &self,
80 address_type: AddressType,
81 testnet: bool,
82 ) -> Result<(), PatternError> {
83 let pattern_str = self.as_str();
84
85 if let Pattern::Prefix(_) = self {
87 address_type.validate_pattern(pattern_str, testnet)?;
88 } else {
89 let valid_chars = address_type.valid_chars();
91 for c in pattern_str.chars() {
92 let c_lower = c.to_ascii_lowercase();
93 if !valid_chars.contains(c_lower) && !valid_chars.contains(c) {
94 return Err(PatternError::InvalidCharacter(c));
95 }
96 }
97 }
98
99 Ok(())
100 }
101
102 pub fn difficulty(&self, address_type: AddressType, case_sensitive: bool) -> f64 {
105 let pattern_str = self.as_str();
106 let fixed_prefix = address_type.fixed_prefix(false);
107
108 let effective_len = match self {
110 Pattern::Prefix(p) => {
111 if p.len() > fixed_prefix.len() {
112 p.len() - fixed_prefix.len()
113 } else {
114 0
115 }
116 }
117 Pattern::Suffix(s) => s.len(),
118 Pattern::Contains(c) => c.len(),
119 };
120
121 if effective_len == 0 {
122 return 1.0;
123 }
124
125 let alphabet_size: f64 = match address_type {
127 AddressType::P2PKH => 58.0, AddressType::P2WPKH => 32.0, AddressType::P2TR => 32.0, AddressType::Ethereum => 16.0, };
132
133 let case_factor = if case_sensitive {
135 1.0
136 } else {
137 let letter_count = pattern_str.chars().filter(|c| c.is_alphabetic()).count();
140 2.0_f64.powi(letter_count as i32)
141 };
142
143 let base_difficulty = alphabet_size.powi(effective_len as i32);
145
146 let position_factor = match self {
148 Pattern::Prefix(_) => 1.0,
149 Pattern::Suffix(_) => 1.0,
150 Pattern::Contains(_) => 0.5, };
152
153 base_difficulty / case_factor * position_factor
154 }
155}
156
157impl std::fmt::Display for Pattern {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 match self {
160 Pattern::Prefix(p) => write!(f, "prefix:{}", p),
161 Pattern::Suffix(s) => write!(f, "suffix:{}", s),
162 Pattern::Contains(c) => write!(f, "contains:{}", c),
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_prefix_matching() {
173 let pattern = Pattern::prefix("1Love").unwrap();
174
175 assert!(pattern.matches("1LoveXYZ123", true));
176 assert!(!pattern.matches("1loveXYZ123", true)); assert!(pattern.matches("1loveXYZ123", false)); assert!(!pattern.matches("2LoveXYZ123", true));
179 }
180
181 #[test]
182 fn test_suffix_matching() {
183 let pattern = Pattern::suffix("BTC").unwrap();
184
185 assert!(pattern.matches("1abcdefBTC", true));
186 assert!(!pattern.matches("1abcdefbtc", true));
187 assert!(pattern.matches("1abcdefbtc", false));
188 }
189
190 #[test]
191 fn test_contains_matching() {
192 let pattern = Pattern::contains("Love").unwrap();
193
194 assert!(pattern.matches("1abcLoveXYZ", true));
195 assert!(pattern.matches("1LoveXYZ", true));
196 assert!(pattern.matches("1XYZLove", true));
197 }
198
199 #[test]
200 fn test_difficulty_calculation() {
201 let pattern = Pattern::prefix("1A").unwrap();
202 let diff = pattern.difficulty(AddressType::P2PKH, true);
203
204 assert!(diff > 50.0 && diff < 70.0);
206 }
207
208 #[test]
209 fn test_empty_pattern_rejected() {
210 assert!(Pattern::prefix("").is_err());
211 assert!(Pattern::suffix("").is_err());
212 assert!(Pattern::contains("").is_err());
213 }
214}