vanityaddr/
main.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use 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, poseidon_hash, BaseBlind, ContractId, FuncRef, PublicKey,
30    SecretKey,
31};
32use rand::rngs::OsRng;
33use rayon::iter::ParallelIterator;
34
35const ABOUT: &str =
36    concat!("vanityaddr ", env!("CARGO_PKG_VERSION"), '\n', env!("CARGO_PKG_DESCRIPTION"));
37
38const USAGE: &str = r#"
39Usage: vanityaddr [OPTIONS] <PREFIX> <PREFIX> ...
40
41Arguments:
42  <PREFIX>    Prefixes to search
43
44Options:
45  -c    Make the search case-sensitive
46  -t    Number of threads to use (defaults to number of available CPUs)
47  -A    Search for an address
48  -C    Search for a Contract ID
49  -T    Search for a Token ID
50"#;
51
52fn usage() {
53    print!("{}{}\n{}", ANSI_LOGO, ABOUT, USAGE);
54}
55
56struct DrkAddr {
57    pub public: PublicKey,
58    pub secret: SecretKey,
59}
60
61struct DrkToken {
62    pub token_id: TokenId,
63    pub secret: SecretKey,
64    pub blind: BaseBlind,
65}
66
67struct DrkContract {
68    pub contract_id: ContractId,
69    pub secret: SecretKey,
70}
71
72trait Prefixable {
73    fn new() -> Self;
74    fn to_string(&self) -> String;
75    fn _get_secret(&self) -> SecretKey;
76
77    fn starts_with(&self, prefix: &str, case_sensitive: bool) -> bool {
78        if case_sensitive {
79            self.to_string().starts_with(prefix)
80        } else {
81            self.to_string().to_lowercase().starts_with(prefix.to_lowercase().as_str())
82        }
83    }
84
85    fn starts_with_any(&self, prefixes: &[String], case_sensitive: bool) -> bool {
86        prefixes.iter().any(|prefix| self.starts_with(prefix, case_sensitive))
87    }
88}
89
90impl Prefixable for DrkAddr {
91    fn new() -> Self {
92        let secret = SecretKey::random(&mut OsRng);
93        let public = PublicKey::from_secret(secret);
94        Self { public, secret }
95    }
96
97    fn to_string(&self) -> String {
98        self.public.to_string()
99    }
100
101    fn _get_secret(&self) -> SecretKey {
102        self.secret
103    }
104}
105
106impl Prefixable for DrkToken {
107    fn new() -> Self {
108        // Generate the mint authority secret key and blind
109        let secret = SecretKey::random(&mut OsRng);
110        let blind = BaseBlind::random(&mut OsRng);
111
112        // Create the Auth FuncID
113        let func_id = FuncRef {
114            contract_id: *MONEY_CONTRACT_ID,
115            func_code: MoneyFunction::AuthTokenMintV1 as u8,
116        }
117        .to_func_id();
118
119        // Grab the mint authority user data
120        let (auth_x, auth_y) = PublicKey::from_secret(secret).xy();
121        let user_data = poseidon_hash([auth_x, auth_y]);
122
123        // Derive the Token ID
124        let token_id = TokenId::derive_from(func_id.inner(), user_data, blind.inner());
125
126        Self { token_id, secret, blind }
127    }
128
129    fn to_string(&self) -> String {
130        self.token_id.to_string()
131    }
132
133    fn _get_secret(&self) -> SecretKey {
134        self.secret
135    }
136}
137
138impl Prefixable for DrkContract {
139    fn new() -> Self {
140        let secret = SecretKey::random(&mut OsRng);
141        let contract_id = ContractId::derive(secret);
142        Self { contract_id, secret }
143    }
144
145    fn to_string(&self) -> String {
146        self.contract_id.to_string()
147    }
148
149    fn _get_secret(&self) -> SecretKey {
150        self.secret
151    }
152}
153
154fn main() -> ExitCode {
155    let argv;
156    let mut hflag = false;
157    let mut cflag = false;
158    let mut addrflag = false;
159    let mut toknflag = false;
160    let mut ctrcflag = false;
161
162    let mut n_threads = available_parallelism().unwrap().get();
163
164    {
165        let mut args = Args::new().with_cb(|args, flag| match flag {
166            'c' => cflag = true,
167            'A' => addrflag = true,
168            'T' => toknflag = true,
169            'C' => ctrcflag = true,
170            't' => n_threads = args.eargf().parse::<usize>().unwrap(),
171            _ => hflag = true,
172        });
173
174        argv = args.parse();
175    }
176
177    if hflag || argv.is_empty() {
178        usage();
179        return ExitCode::FAILURE
180    }
181
182    if (addrflag as u8 + toknflag as u8 + ctrcflag as u8) != 1 {
183        eprintln!("The search flags are mutually exclusive. Use only one of -A/-C/-T.");
184        return ExitCode::FAILURE
185    }
186
187    // Validate search prefixes
188    for (idx, prefix) in argv.iter().enumerate() {
189        match bs58::decode(prefix).into_vec() {
190            Ok(_) => {}
191            Err(e) => {
192                eprintln!("Error: Invalid base58 for prefix #{}: {}", idx, e);
193                return ExitCode::FAILURE
194            }
195        }
196    }
197
198    // Handle SIGINT
199    let (tx, rx) = channel();
200    ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel"))
201        .expect("Error setting SIGINT handler");
202
203    // Something fancy
204    let progress = Arc::new(ProgressInc::new());
205
206    // Threadpool
207    let progress_ = progress.clone();
208    let rayon_pool = rayon::ThreadPoolBuilder::new().num_threads(n_threads).build().unwrap();
209    rayon_pool.spawn(move || {
210        if addrflag {
211            let addr = rayon::iter::repeat(DrkAddr::new)
212                .inspect(|_| progress_.inc(1))
213                .map(|create| create())
214                .find_any(|address| address.starts_with_any(&argv, cflag))
215                .expect("Failed to find an address match");
216
217            // The above will keep running until it finds a match or until
218            // the program terminates. Only if a match is found shall the
219            // following code be executed and the program exit successfully:
220            let attempts = progress_.position();
221            progress_.finish_and_clear();
222
223            println!(
224                "{{\"address\":\"{}\",\"attempts\":{},\"secret\":\"{}\"}}",
225                addr.public, attempts, addr.secret,
226            );
227        }
228
229        if toknflag {
230            let tid = rayon::iter::repeat(DrkToken::new)
231                .inspect(|_| progress_.inc(1))
232                .map(|create| create())
233                .find_any(|token_id| token_id.starts_with_any(&argv, cflag))
234                .expect("Failed to find a token ID match");
235
236            let attempts = progress_.position();
237            progress_.finish_and_clear();
238
239            println!(
240                "{{\"token_id\":\"{}\",\"attempts\":{},\"secret\":\"{}\",\"blind\":\"{}\"}}",
241                tid.token_id, attempts, tid.secret, tid.blind
242            );
243        }
244
245        if ctrcflag {
246            let cid = rayon::iter::repeat(DrkContract::new)
247                .inspect(|_| progress_.inc(1))
248                .map(|create| create())
249                .find_any(|contract_id| contract_id.starts_with_any(&argv, cflag))
250                .expect("Failed to find a contract ID match");
251
252            let attempts = progress_.position();
253            progress_.finish_and_clear();
254
255            println!(
256                "{{\"contract_id\":\"{}\",\"attempts\":{},\"secret\":\"{}\"}}",
257                cid.contract_id, attempts, cid.secret,
258            );
259        }
260
261        exit(0);
262    });
263
264    // This now blocks and lets our threadpool execute in the background.
265    rx.recv().expect("Could not receive from channel");
266    progress.finish_and_clear();
267    eprintln!("\r\x1b[2KCaught SIGINT, exiting...");
268    ExitCode::FAILURE
269}