Help optimize this code? ethereum vanity address gen (easy)

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 }
}

You can eliminate the str::from_utf8 call by changing to_hex to return &[u8]:

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: &str, suffix: &str, addr: &[u8]) -> bool {
    addr.starts_with(prefix.as_bytes()) && addr.ends_with(suffix.as_bytes())
}

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.

is there a possibility to externally terminate the other threads once a match is found

When one thread finds a match, it could abort the entire process after displaying its output.

the program kinda needs to remain running, and make use of the discovered key

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.

cant seem to find the problem

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);

VanityKeypair { pk: "0x", sk: "" }

real 0m5.923s
user 0m43.574s
sys 0m0.161s

dang it

heres some reference that does the job GitHub - bcsongor/tinyvanityeth: 📇 Tiny and fast command line tool to find vanity Ethereum addresses.

i am having trouble porting it over though

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.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.