I'm trying to optimize this vanity ethereum address generator so i can obtain a address with an additional 7-8 custom characters. im new to rust, so if theres any simplification or optimization i missed, please help. i need this to be as absolutely fast as possible, and im not in a good position to use gpu, plus i dont need gpu
[dependencies]
num_cpus = "1.16.0"
secp256k1 = { version = "0.29.0", features = ["std", "rand-std", "global-context"] }
tiny-keccak = { version = "2", features = ["keccak"] }
[profile.release]
strip = true
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
-----vanity.rs-----
use secp256k1::{generate_keypair, rand::thread_rng};
use std::sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}};
use std::thread;
use tiny_keccak::{Hasher, Keccak};
use crate::VanityKeypair;
const ADDRESS_HEX_LENGTH: usize = 40;
const ADDRESS_BYTES_LENGTH: usize = ADDRESS_HEX_LENGTH / 2;
const KECCAK256_HASH_BYTES_LENGTH: usize = 32;
const PUBLIC_KEY_BYTES_START_INDEX: usize = 1;
const ADDRESS_BYTES_START_INDEX: usize = KECCAK256_HASH_BYTES_LENGTH - ADDRESS_BYTES_LENGTH;
const HEX_CHARS_LOWER: &[u8; 16] = b"0123456789abcdef";
fn to_hex<'a>(bytes: &[u8], buf: &'a mut [u8]) -> &'a str {
for i in 0..bytes.len() {
buf[i * 2] = HEX_CHARS_LOWER[(bytes[i] >> 4) as usize];
buf[i * 2 + 1] = HEX_CHARS_LOWER[(bytes[i] & 0xf) as usize];
}
std::str::from_utf8(&buf[0..bytes.len() * 2])
.unwrap()
}
fn is_addr_matching(prefix: &str, suffix: &str, addr: &str) -> bool {
addr.starts_with(prefix) && addr.ends_with(suffix)
}
pub fn vanity_eth_address(starts_with: String, ends_with: String) -> VanityKeypair {
let secret_key = Arc::new(Mutex::new(String::new()));
let public_key = Arc::new(Mutex::new(String::new()));
let num_cpus = num_cpus::get();
let found = Arc::new(AtomicBool::new(false));
let mut threads = Vec::with_capacity(num_cpus);
for _ in 0..num_cpus {
let secret_key = Arc::clone(&secret_key);
let public_key = Arc::clone(&public_key);
let starts_with = starts_with.clone();
let ends_with = ends_with.clone();
let found = found.clone();
threads.push(thread::spawn(move || {
let mut hash = [0u8; KECCAK256_HASH_BYTES_LENGTH];
let mut addr_hex_buf = [0u8; ADDRESS_HEX_LENGTH];
let hasher = Keccak::v256();
while !found.load(Ordering::Acquire) {
let (sk, pk) = generate_keypair(&mut thread_rng());
let pk_bytes = &pk.serialize_uncompressed()[PUBLIC_KEY_BYTES_START_INDEX..];
let mut hasher = hasher.clone();
hasher.update(&pk_bytes);
hasher.finalize(&mut hash);
let addr_bytes = &hash[ADDRESS_BYTES_START_INDEX..];
let addr = to_hex(addr_bytes, &mut addr_hex_buf);
if is_addr_matching(&starts_with, &ends_with, addr) {
found.store(true, Ordering::Release);
let mut public = public_key.lock().unwrap();
*public = addr.to_owned();
let mut secret = secret_key.lock().unwrap();
*secret= sk.display_secret().to_string();
}
}
}));
}
for thread in threads {
thread.join().unwrap();
}
let pk = {
let public = public_key.lock().unwrap();
public.clone()
};
let sk = {
let secret = secret_key.lock().unwrap();
secret.clone()
};
VanityKeypair { pk: format!("0x{}", pk), sk }
}
Or you could eliminate the to_hex conversion completely, and instead convert prefix and suffix from hex to binary before starting your search. Note that if the prefix or suffix has an odd number of hex digits, you’ll need to mask the address before comparing it.
I'm interested in this option, and I convert it from hex to binary by calling as_bytes right?
how do i mask the address?
also for the hasher, i am cloning it, is there a more optimal way of reusing the object? like a thread local?
finally, for the while !found is that acquiring the boolean taking up alot of time? is there a better way to do it, like stopping the other threads externally or something
No, you’ll need to write code that is like to_hex in reverse, translating each pair of hex digits into one output byte. from_str_radix can help.
Since each hex digit encodes just half a byte, the byte with just one corresponding hex digit will need to be masked (using the bitwise & operator) with 0x0f or 0xf0 before comparing it, depending on whether the digit corresponds to the start or end of the byte.
At a glance, cloning the hasher appears to be fine. But you should run this code under a profiler to figure out which parts are actually taking up time.
I don’t know whether the boolean check is a significant cost. But if it is, one option would be to process a batch of addresses after each check, instead of a single address.
I haven't profiled this code, but it seems likely that the majority of time is spent in key generation and hashing. If so, it might not even be worth it to optimize the boolean check or the hex conversion.
do you have any idea how i can have case sensitivity and have the output have a mix of upper and lower, like ethereum addresses have case sensitivity or they call it checksums.
1b0aa1EAd1E457bCa240e48Bb8f4AC70a62aC391
i need to be able to input
DeAd in this program, and it matches it exactly
putting that input in its current state obviously loops the program forever, i can only do all lower
use secp256k1::{generate_keypair, rand::thread_rng};
use std::sync::{atomic::{AtomicBool, Ordering}, Arc, Mutex};
use std::thread;
use tiny_keccak::{Hasher, Keccak};
use crate::VanityKeypair;
const ADDRESS_HEX_LENGTH: usize = 40;
const ADDRESS_BYTES_LENGTH: usize = ADDRESS_HEX_LENGTH / 2;
const KECCAK256_HASH_BYTES_LENGTH: usize = 32;
const PUBLIC_KEY_BYTES_START_INDEX: usize = 1;
const ADDRESS_BYTES_START_INDEX: usize = KECCAK256_HASH_BYTES_LENGTH - ADDRESS_BYTES_LENGTH;
const HEX_CHARS_LOWER: &[u8; 16] = b"0123456789abcdef";
const ITERATIONS_BEFORE_CHECK: usize = 100_000;
fn to_hex<'a>(bytes: &[u8], buf: &'a mut [u8]) -> &'a [u8] {
for i in 0..bytes.len() {
buf[i * 2] = HEX_CHARS_LOWER[(bytes[i] >> 4) as usize];
buf[i * 2 + 1] = HEX_CHARS_LOWER[(bytes[i] & 0xf) as usize];
}
&buf[0..bytes.len() * 2]
}
fn is_addr_matching(prefix: &[u8], suffix: &[u8], addr: &[u8]) -> bool {
addr.starts_with(prefix) && addr.ends_with(suffix)
}
pub fn vanity_eth_address(starts_with: String, ends_with: String) -> VanityKeypair {
let secret_key = Arc::new(Mutex::new(String::new()));
let public_key = Arc::new(Mutex::new(String::new()));
let starts: Vec<u8> = starts_with.into_bytes();
let ends: Vec<u8> = ends_with.into_bytes();
let num_cpus = num_cpus::get();
let found = Arc::new(AtomicBool::new(false));
let mut threads = Vec::with_capacity(num_cpus);
for _ in 0..num_cpus {
let secret_key = Arc::clone(&secret_key);
let public_key = Arc::clone(&public_key);
let starts = starts.clone();
let ends = ends.clone();
let found = Arc::clone(&found);
threads.push(thread::spawn(move || {
let mut hash = [0u8; KECCAK256_HASH_BYTES_LENGTH];
let mut addr_hex_buf = [0u8; ADDRESS_HEX_LENGTH];
let hasher = Keccak::v256();
while !found.load(Ordering::Acquire) {
for _ in 0..ITERATIONS_BEFORE_CHECK {
let (sk, pk) = generate_keypair(&mut thread_rng());
let pk_bytes = &pk.serialize_uncompressed()[PUBLIC_KEY_BYTES_START_INDEX..];
let mut hasher = hasher.clone();
hasher.update(&pk_bytes);
hasher.finalize(&mut hash);
let addr_bytes = &hash[ADDRESS_BYTES_START_INDEX..];
let addr_hex = to_hex(addr_bytes, &mut addr_hex_buf);
if is_addr_matching(&starts, &ends, addr_hex) {
if !found.swap(true, Ordering::Relaxed) {
let mut public = public_key.lock().unwrap();
*public = String::from_utf8(addr_hex.to_vec()).unwrap();
let mut secret = secret_key.lock().unwrap();
*secret = sk.display_secret().to_string();
}
}
}
}
}));
}
for thread in threads {
thread.join().unwrap();
}
let pk = {
let public = public_key.lock().unwrap();
public.clone()
};
let sk = {
let secret = secret_key.lock().unwrap();
secret.clone()
};
VanityKeypair { pk: format!("0x{}", pk), sk }
}
Convert the prefix/suffix strings to lowercase before searching, and match addresses against the lowercase versions.
But also, keep the original strings around. After you find a matching address, replace the start/end of the address (which match the lowercase strings) with the original strings.
thread '' panicked at src/vanity.rs:67:9:
index out of bounds: the len is 32 but the index is 32
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
Aborted (core dumped)
use secp256k1::{generate_keypair, rand::thread_rng};
use std::sync::{atomic::{AtomicBool, Ordering}, Arc, Mutex};
use std::thread;
use tiny_keccak::{Hasher, Keccak};
use crate::VanityKeypair;
const ADDRESS_HEX_LENGTH: usize = 40;
const ADDRESS_BYTES_LENGTH: usize = ADDRESS_HEX_LENGTH / 2;
const KECCAK256_HASH_BYTES_LENGTH: usize = 32;
const PUBLIC_KEY_BYTES_START_INDEX: usize = 1;
const ADDRESS_BYTES_START_INDEX: usize = KECCAK256_HASH_BYTES_LENGTH - ADDRESS_BYTES_LENGTH;
const ITERATIONS_BEFORE_CHECK: usize = 100_000;
const HEX_CHARS_LOWER: &[u8; 16] = b"0123456789abcdef";
fn to_checksum_address<'a>(addr: &[u8], checksum_addr_hex_buf: &'a mut [u8]) -> &'a [u8] {
let mut hash = [0u8; KECCAK256_HASH_BYTES_LENGTH];
let mut hasher = Keccak::v256();
hasher.update(&addr);
hasher.finalize(&mut hash);
let mut buf = [0u8; KECCAK256_HASH_BYTES_LENGTH];
let addr_hash = to_hex(&hash, &mut buf);
let addr_hash_bytes = addr_hash;
for i in 0..addr.len() {
let byte = addr[i];
checksum_addr_hex_buf[i] = if addr_hash_bytes[i] >= 56 {
byte.to_ascii_uppercase()
} else {
byte // Already lowercase.
}
}
&checksum_addr_hex_buf[0..addr.len()]
}
struct AddressPart {
part: String,
part_lower: String,
}
impl From<String> for AddressPart {
fn from(part: String) -> Self {
let part_lower = part.to_lowercase();
Self { part_lower, part }
}
}
fn to_hex<'a>(bytes: &[u8], buf: &'a mut [u8]) -> &'a [u8] {
for i in 0..bytes.len() {
buf[i * 2] = HEX_CHARS_LOWER[(bytes[i] >> 4) as usize];
buf[i * 2 + 1] = HEX_CHARS_LOWER[(bytes[i] & 0xf) as usize];
}
&buf[0..bytes.len() * 2]
}
fn is_addr_matching(prefix: &[u8], suffix: &[u8], addr: &[u8]) -> bool {
addr.starts_with(prefix) && addr.ends_with(suffix)
}
fn contains_uppercase(text: &str) -> bool {
for c in text.chars() {
if c.is_ascii_uppercase() {
return true;
}
}
false
}
pub fn vanity_eth_address(starts_with: String, ends_with: String) -> VanityKeypair {
let secret_key = Arc::new(Mutex::new(String::new()));
let public_key = Arc::new(Mutex::new(String::new()));
let prefix = AddressPart::from(starts_with.clone());
let suffix = AddressPart::from(ends_with.clone());
let lower_starts: Vec<u8> = prefix.part_lower.into_bytes();
let lower_ends: Vec<u8> = suffix.part_lower.into_bytes();
let starts: Vec<u8> = prefix.part.into_bytes();
let ends: Vec<u8> = suffix.part.into_bytes();
let contains_upper = contains_uppercase(&starts_with) | contains_uppercase(&ends_with);
let num_cpus = num_cpus::get();
let found = Arc::new(AtomicBool::new(false));
let mut threads = Vec::with_capacity(num_cpus);
for _ in 0..num_cpus {
let secret_key = Arc::clone(&secret_key);
let public_key = Arc::clone(&public_key);
let lower_starts = lower_starts.clone();
let lower_ends = lower_ends.clone();
let starts = starts.clone();
let ends = ends.clone();
let found = Arc::clone(&found);
threads.push(thread::spawn(move || {
let mut hash = [0u8; KECCAK256_HASH_BYTES_LENGTH];
let mut addr_hex_buf = [0u8; ADDRESS_HEX_LENGTH];
let mut checksum_addr_hex_buf = [0u8; ADDRESS_HEX_LENGTH];
let hasher = Keccak::v256();
while !found.load(Ordering::Acquire) {
for _ in 0..ITERATIONS_BEFORE_CHECK {
let (sk, pk) = generate_keypair(&mut thread_rng());
let pk_bytes = &pk.serialize_uncompressed()[PUBLIC_KEY_BYTES_START_INDEX..];
let mut hasher = hasher.clone();
hasher.update(&pk_bytes);
hasher.finalize(&mut hash);
let addr_bytes = &hash[ADDRESS_BYTES_START_INDEX..];
let addr_hex = to_hex(addr_bytes, &mut addr_hex_buf);
if is_addr_matching(&lower_starts, &lower_ends, addr_hex) {
if contains_upper {
if !found.swap(true, Ordering::Relaxed) {
let checksum_addr = to_checksum_address(&addr_hex, &mut checksum_addr_hex_buf);
if is_addr_matching(&starts, &ends, checksum_addr) {
let mut public = public_key.lock().unwrap();
*public = String::from_utf8(addr_hex.to_vec()).unwrap();
let mut secret = secret_key.lock().unwrap();
*secret = sk.display_secret().to_string();
};
} }
else
if !found.swap(true, Ordering::Relaxed) {
let mut public = public_key.lock().unwrap();
*public = String::from_utf8(addr_hex.to_vec()).unwrap();
let mut secret = secret_key.lock().unwrap();
*secret = sk.display_secret().to_string();
}
}
}
}
}));
}
for thread in threads {
thread.join().unwrap();
}
let pk = {
let public = public_key.lock().unwrap();
public.clone()
};
let sk = {
let secret = secret_key.lock().unwrap();
secret.clone()
};
VanityKeypair { pk: format!("0x{}", pk), sk }
}
I can’t tell what line this is; the code in your comment doesn’t seem to have anything relevant on line 67.
I suspect the problem is that a 32-byte hash will become 64 bytes when converted to hexadecimal, so the buf array here (line 26) needs to be twice as long as the hash array:
let mut buf = [0u8; KECCAK256_HASH_BYTES_LENGTH];
let addr_hash = to_hex(&hash, &mut buf);
In this code, if the second is_addr_matting returns false, then you don’t write to the public and secret variables even though have set found to true:
I’m not sure why this to_checksum_address call is here, or why you are checking is_addr_matching on two different strings. You are taking addr_hex (which has already been hashed and converted to hex), hashing that hex string, and converting this result to hex again. The result will be something totally different from addr_hex, and will generally not have the same prefix or suffix.