1use std::{
20 process::{exit, ExitCode},
21 sync::{mpsc::channel, Arc},
22 thread::available_parallelism,
23};
24
25use arg::Args;
26use darkfi::{util::cli::ProgressInc, ANSI_LOGO};
27use darkfi_money_contract::{model::TokenId, MoneyFunction};
28use darkfi_sdk::crypto::{
29 contract_id::MONEY_CONTRACT_ID,
30 keypair::{Address, Network, StandardAddress},
31 poseidon_hash, BaseBlind, ContractId, FuncRef, PublicKey, SecretKey,
32};
33use rand::rngs::OsRng;
34use rayon::iter::ParallelIterator;
35
36const ABOUT: &str =
37 concat!("vanityaddr ", env!("CARGO_PKG_VERSION"), '\n', env!("CARGO_PKG_DESCRIPTION"));
38
39const USAGE: &str = r#"
40Usage: vanityaddr [OPTIONS] <PREFIX> <PREFIX> ...
41
42Arguments:
43 <PREFIX> Prefixes to search
44
45Options:
46 -c Make the search case-sensitive
47 -t Number of threads to use (defaults to number of available CPUs)
48 -A Search for an address
49 -C Search for a Contract ID
50 -T Search for a Token ID
51 -n <network> Network to search (mainnet/testnet, default=mainnet)
52"#;
53
54fn usage() {
55 print!("{ANSI_LOGO}{ABOUT}\n{USAGE}");
56}
57
58struct DrkAddr {
59 pub address: Address,
60 pub _public: PublicKey,
61 pub secret: SecretKey,
62}
63
64struct DrkToken {
65 pub token_id: TokenId,
66 pub secret: SecretKey,
67 pub blind: BaseBlind,
68}
69
70struct DrkContract {
71 pub contract_id: ContractId,
72 pub secret: SecretKey,
73}
74
75trait Prefixable {
76 fn new(network: Network) -> Self;
77 fn to_string(&self) -> String;
78 fn _get_secret(&self) -> SecretKey;
79
80 fn starts_with(&self, prefix: &str, case_sensitive: bool) -> bool {
81 if case_sensitive {
82 self.to_string().starts_with(prefix)
83 } else {
84 self.to_string().to_lowercase().starts_with(prefix.to_lowercase().as_str())
85 }
86 }
87
88 fn starts_with_any(&self, prefixes: &[String], case_sensitive: bool) -> bool {
89 prefixes.iter().any(|prefix| self.starts_with(prefix, case_sensitive))
90 }
91}
92
93impl Prefixable for DrkAddr {
94 fn new(network: Network) -> Self {
95 let secret = SecretKey::random(&mut OsRng);
96 let public = PublicKey::from_secret(secret);
97 let address = StandardAddress::from_public(network, public).into();
98 Self { address, _public: public, secret }
99 }
100
101 fn to_string(&self) -> String {
102 let mut a = self.address.to_string();
103 a.remove(0);
104 a.to_string()
105 }
106
107 fn _get_secret(&self) -> SecretKey {
108 self.secret
109 }
110}
111
112impl Prefixable for DrkToken {
113 fn new(_network: Network) -> Self {
114 let secret = SecretKey::random(&mut OsRng);
116 let blind = BaseBlind::random(&mut OsRng);
117
118 let func_id = FuncRef {
120 contract_id: *MONEY_CONTRACT_ID,
121 func_code: MoneyFunction::AuthTokenMintV1 as u8,
122 }
123 .to_func_id();
124
125 let (auth_x, auth_y) = PublicKey::from_secret(secret).xy();
127 let user_data = poseidon_hash([auth_x, auth_y]);
128
129 let token_id = TokenId::derive_from(func_id.inner(), user_data, blind.inner());
131
132 Self { token_id, secret, blind }
133 }
134
135 fn to_string(&self) -> String {
136 self.token_id.to_string()
137 }
138
139 fn _get_secret(&self) -> SecretKey {
140 self.secret
141 }
142}
143
144impl Prefixable for DrkContract {
145 fn new(_network: Network) -> Self {
146 let secret = SecretKey::random(&mut OsRng);
147 let contract_id = ContractId::derive(secret);
148 Self { contract_id, secret }
149 }
150
151 fn to_string(&self) -> String {
152 self.contract_id.to_string()
153 }
154
155 fn _get_secret(&self) -> SecretKey {
156 self.secret
157 }
158}
159
160fn main() -> ExitCode {
161 let argv;
162 let mut hflag = false;
163 let mut cflag = false;
164 let mut addrflag = false;
165 let mut toknflag = false;
166 let mut ctrcflag = false;
167 let mut nflag = false;
168 let mut nvalue = "mainnet".to_string();
169
170 let mut n_threads = available_parallelism().unwrap().get();
171
172 {
173 let mut args = Args::new().with_cb(|args, flag| match flag {
174 'c' => cflag = true,
175 'A' => addrflag = true,
176 'T' => toknflag = true,
177 'C' => ctrcflag = true,
178 't' => n_threads = args.eargf().parse::<usize>().unwrap(),
179 'n' => {
180 nflag = true;
181 nvalue = args.eargf().to_string();
182 }
183 _ => hflag = true,
184 });
185
186 argv = args.parse();
187 }
188
189 if hflag || argv.is_empty() {
190 usage();
191 return ExitCode::FAILURE
192 }
193
194 let network = match nvalue.as_str() {
195 "mainnet" => Network::Mainnet,
196 "testnet" => Network::Testnet,
197 _ => {
198 eprintln!("Invalid network. Use 'testnet' or 'mainnet'.");
199 return ExitCode::FAILURE
200 }
201 };
202
203 if (addrflag as u8 + toknflag as u8 + ctrcflag as u8) != 1 {
204 eprintln!("The search flags are mutually exclusive. Use only one of -A/-C/-T.");
205 return ExitCode::FAILURE
206 }
207
208 for (idx, prefix) in argv.iter().enumerate() {
210 match bs58::decode(prefix).into_vec() {
211 Ok(_) => {}
212 Err(e) => {
213 eprintln!("Error: Invalid base58 for prefix #{idx}: {e}");
214 return ExitCode::FAILURE
215 }
216 }
217 }
218
219 let (tx, rx) = channel();
221 ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel"))
222 .expect("Error setting SIGINT handler");
223
224 let progress = Arc::new(ProgressInc::new());
226
227 let progress_ = progress.clone();
229 let rayon_pool = rayon::ThreadPoolBuilder::new().num_threads(n_threads).build().unwrap();
230 rayon_pool.spawn(move || {
231 if addrflag {
232 let addr = rayon::iter::repeat(DrkAddr::new)
233 .inspect(|_| progress_.inc(1))
234 .map(|create| create(network))
235 .find_any(|address| address.starts_with_any(&argv, cflag))
236 .expect("Failed to find an address match");
237
238 let attempts = progress_.position();
242 progress_.finish_and_clear();
243
244 println!(
245 "{{\"address\":\"{}\",\"attempts\":{attempts},\"secret\":\"{}\"}}",
246 addr.address, addr.secret,
247 );
248 }
249
250 if toknflag {
251 let tid = rayon::iter::repeat(DrkToken::new)
252 .inspect(|_| progress_.inc(1))
253 .map(|create| create(network))
254 .find_any(|token_id| token_id.starts_with_any(&argv, cflag))
255 .expect("Failed to find a token ID match");
256
257 let attempts = progress_.position();
258 progress_.finish_and_clear();
259
260 println!(
261 "{{\"token_id\":\"{}\",\"attempts\":{attempts},\"secret\":\"{}\",\"blind\":\"{}\"}}",
262 tid.token_id, tid.secret, tid.blind
263 );
264 }
265
266 if ctrcflag {
267 let cid = rayon::iter::repeat(DrkContract::new)
268 .inspect(|_| progress_.inc(1))
269 .map(|create| create(network))
270 .find_any(|contract_id| contract_id.starts_with_any(&argv, cflag))
271 .expect("Failed to find a contract ID match");
272
273 let attempts = progress_.position();
274 progress_.finish_and_clear();
275
276 println!(
277 "{{\"contract_id\":\"{attempts}\",\"attempts\":{},\"secret\":\"{}\"}}",
278 cid.contract_id, cid.secret,
279 );
280 }
281
282 exit(0);
283 });
284
285 rx.recv().expect("Could not receive from channel");
287 progress.finish_and_clear();
288 eprintln!("\r\x1b[2KCaught SIGINT, exiting...");
289 ExitCode::FAILURE
290}