vanityaddr/
main.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2026 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,
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        // Generate the mint authority secret key and blind
115        let secret = SecretKey::random(&mut OsRng);
116        let blind = BaseBlind::random(&mut OsRng);
117
118        // Create the Auth FuncID
119        let func_id = FuncRef {
120            contract_id: *MONEY_CONTRACT_ID,
121            func_code: MoneyFunction::AuthTokenMintV1 as u8,
122        }
123        .to_func_id();
124
125        // Grab the mint authority user data
126        let (auth_x, auth_y) = PublicKey::from_secret(secret).xy();
127        let user_data = poseidon_hash([auth_x, auth_y]);
128
129        // Derive the Token ID
130        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    // Validate search prefixes
209    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    // Handle SIGINT
220    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    // Something fancy
225    let progress = Arc::new(ProgressInc::new());
226
227    // Threadpool
228    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            // The above will keep running until it finds a match or until
239            // the program terminates. Only if a match is found shall the
240            // following code be executed and the program exit successfully:
241            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    // This now blocks and lets our threadpool execute in the background.
286    rx.recv().expect("Could not receive from channel");
287    progress.finish_and_clear();
288    eprintln!("\r\x1b[2KCaught SIGINT, exiting...");
289    ExitCode::FAILURE
290}