referrerpolicy=no-referrer-when-downgrade

sc_cli/commands/
vanity.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! implementation of the `vanity` subcommand
20
21use crate::{
22	error, utils, with_crypto_scheme, CryptoSchemeFlag, NetworkSchemeFlag, OutputTypeFlag,
23};
24use clap::Parser;
25use rand::{rngs::OsRng, RngCore};
26use sp_core::crypto::{unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec};
27use sp_runtime::traits::IdentifyAccount;
28use utils::print_from_uri;
29
30/// The `vanity` command
31#[derive(Debug, Clone, Parser)]
32#[command(name = "vanity", about = "Generate a seed that provides a vanity address")]
33pub struct VanityCmd {
34	/// Desired pattern
35	#[arg(long, value_parser = assert_non_empty_string)]
36	pattern: String,
37
38	#[allow(missing_docs)]
39	#[clap(flatten)]
40	network_scheme: NetworkSchemeFlag,
41
42	#[allow(missing_docs)]
43	#[clap(flatten)]
44	output_scheme: OutputTypeFlag,
45
46	#[allow(missing_docs)]
47	#[clap(flatten)]
48	crypto_scheme: CryptoSchemeFlag,
49}
50
51impl VanityCmd {
52	/// Run the command
53	pub fn run(&self) -> error::Result<()> {
54		let formatted_seed = with_crypto_scheme!(
55			self.crypto_scheme.scheme,
56			generate_key(
57				&self.pattern,
58				unwrap_or_default_ss58_version(self.network_scheme.network)
59			),
60		)?;
61
62		with_crypto_scheme!(
63			self.crypto_scheme.scheme,
64			print_from_uri(
65				&formatted_seed,
66				None,
67				self.network_scheme.network,
68				self.output_scheme.output_type,
69			),
70		);
71		Ok(())
72	}
73}
74
75/// genertae a key based on given pattern
76fn generate_key<Pair>(
77	desired: &str,
78	network_override: Ss58AddressFormat,
79) -> Result<String, &'static str>
80where
81	Pair: sp_core::Pair,
82	Pair::Public: IdentifyAccount,
83	<Pair::Public as IdentifyAccount>::AccountId: Ss58Codec,
84{
85	println!("Generating key containing pattern '{}'", desired);
86
87	let top = 45 + (desired.len() * 48);
88	let mut best = 0;
89	let mut seed = Pair::Seed::default();
90	let mut done = 0;
91
92	loop {
93		if done % 100000 == 0 {
94			OsRng.fill_bytes(seed.as_mut());
95		} else {
96			next_seed(seed.as_mut());
97		}
98
99		let p = Pair::from_seed(&seed);
100		let ss58 = p.public().into_account().to_ss58check_with_version(network_override);
101		let score = calculate_score(desired, &ss58);
102		if score > best || desired.len() < 2 {
103			best = score;
104			if best >= top {
105				println!("best: {} == top: {}", best, top);
106				return Ok(utils::format_seed::<Pair>(seed.clone()))
107			}
108		}
109		done += 1;
110
111		if done % good_waypoint(done) == 0 {
112			println!("{} keys searched; best is {}/{} complete", done, best, top);
113		}
114	}
115}
116
117fn good_waypoint(done: u64) -> u64 {
118	match done {
119		0..=1_000_000 => 100_000,
120		1_000_001..=10_000_000 => 1_000_000,
121		10_000_001..=100_000_000 => 10_000_000,
122		100_000_001.. => 100_000_000,
123	}
124}
125
126fn next_seed(seed: &mut [u8]) {
127	for s in seed {
128		match s {
129			255 => {
130				*s = 0;
131			},
132			_ => {
133				*s += 1;
134				break
135			},
136		}
137	}
138}
139
140/// Calculate the score of a key based on the desired
141/// input.
142fn calculate_score(_desired: &str, key: &str) -> usize {
143	for truncate in 0.._desired.len() {
144		let snip_size = _desired.len() - truncate;
145		let truncated = &_desired[0..snip_size];
146		if let Some(pos) = key.find(truncated) {
147			return (47 - pos) + (snip_size * 48)
148		}
149	}
150	0
151}
152
153/// checks that `pattern` is non-empty
154fn assert_non_empty_string(pattern: &str) -> Result<String, &'static str> {
155	if pattern.is_empty() {
156		Err("Pattern must not be empty")
157	} else {
158		Ok(pattern.to_string())
159	}
160}
161
162#[cfg(test)]
163mod tests {
164	use super::*;
165	use sp_core::{
166		crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec},
167		sr25519, Pair,
168	};
169
170	#[test]
171	fn vanity() {
172		let vanity = VanityCmd::parse_from(&["vanity", "--pattern", "j"]);
173		assert!(vanity.run().is_ok());
174	}
175
176	#[test]
177	fn test_generation_with_single_char() {
178		let seed = generate_key::<sr25519::Pair>("ab", default_ss58_version()).unwrap();
179		assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
180			.unwrap()
181			.public()
182			.to_ss58check()
183			.contains("ab"));
184	}
185
186	#[test]
187	fn generate_key_respects_network_override() {
188		let seed =
189			generate_key::<sr25519::Pair>("ab", Ss58AddressFormatRegistry::PolkadotAccount.into())
190				.unwrap();
191		assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
192			.unwrap()
193			.public()
194			.to_ss58check_with_version(Ss58AddressFormatRegistry::PolkadotAccount.into())
195			.contains("ab"));
196	}
197
198	#[test]
199	fn test_score_1_char_100() {
200		let score = calculate_score("j", "5jolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim");
201		assert_eq!(score, 94);
202	}
203
204	#[test]
205	fn test_score_100() {
206		let score = calculate_score("Polkadot", "5PolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim");
207		assert_eq!(score, 430);
208	}
209
210	#[test]
211	fn test_score_50_2() {
212		// 50% for the position + 50% for the size
213		assert_eq!(
214			calculate_score("Polkadot", "5PolkXXXXwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim"),
215			238
216		);
217	}
218
219	#[test]
220	fn test_score_0() {
221		assert_eq!(
222			calculate_score("Polkadot", "5GUWv4bLCchGUHJrzULXnh4JgXsMpTKRnjuXTY7Qo1Kh9uYK"),
223			0
224		);
225	}
226}