├── .gitattributes ├── images ├── ethereum.png ├── solana.png └── bitcoin.svg ├── src ├── vanity_addr_generator │ ├── mod.rs │ ├── comp.rs │ ├── chain.rs │ └── vanity_addr.rs ├── error.rs ├── keys_and_address │ ├── mod.rs │ ├── btc.rs │ ├── sol.rs │ └── eth.rs ├── lib.rs ├── flags.rs ├── cli.rs ├── main.rs └── file.rs ├── .gitignore ├── dist-workspace.toml ├── .github └── workflows │ ├── rust.yml │ └── release.yml ├── Cargo.toml ├── tests ├── integration_tests.rs └── cli_tests.rs ├── README.md └── LICENSE /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /images/ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emivvvvv/btc-vanity/HEAD/images/ethereum.png -------------------------------------------------------------------------------- /images/solana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Emivvvvv/btc-vanity/HEAD/images/solana.png -------------------------------------------------------------------------------- /src/vanity_addr_generator/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Vanity Address Generation Module 2 | //! 3 | //! This module is the core of btc-vanity. It provides the functionality to generate Bitcoin vanity addresses. 4 | 5 | pub mod chain; 6 | pub mod vanity_addr; 7 | 8 | mod comp; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | *.idea 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | 17 | # Added by cargo 18 | 19 | /target 20 | -------------------------------------------------------------------------------- /dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cargo:."] 3 | 4 | # Config for 'dist' 5 | [dist] 6 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax) 7 | cargo-dist-version = "0.28.7-prerelease.1" 8 | # CI backends to support 9 | ci = "github" 10 | # The installers to generate for each app 11 | installers = [] 12 | # Target platforms to build apps for (Rust target-triple syntax) 13 | targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-pc-windows-msvc", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] 14 | # Features to pass to cargo build 15 | features = ["ethereum"] 16 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build-and-test: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | features: ["", "solana", "ethereum", "all"] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Install Rust 23 | run: rustup update stable 24 | 25 | - name: Run Clippy 26 | run: cargo clippy --all-features -- -D warnings # Check with all features 27 | 28 | - name: Build and Test with Features - ${{ matrix.features }} 29 | run: | 30 | cargo build --verbose ${{ matrix.features && format('--features {0}', matrix.features) || '' }} 31 | cargo test --verbose ${{ matrix.features && format('--features {0}', matrix.features) || '' }} 32 | 33 | - name: Run doc tests with all features 34 | run: cargo test --doc --all-features -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "btc-vanity" 3 | version = "2.1.2" 4 | authors = ["Emirhan TALA "] 5 | description = "A blazingly fast Bitcoin, Ethereum, and Solana vanity address generator." 6 | edition = "2021" 7 | license = "Apache-2.0" 8 | readme = "README.md" 9 | repository = "https://github.com/Emivvvvv/btc-vanity" 10 | documentation = "https://docs.rs/btc-vanity/latest/btc_vanity/index.html" 11 | keywords = ["bitcoin", "ethereum", "solana", "vanity", "address"] 12 | 13 | [dependencies] 14 | rand = "0.9.0-beta.3" 15 | bitcoin = { version = "0.32.5", features = ["rand-std"] } 16 | thiserror = "2.0.11" 17 | clap = "4.5.26" 18 | num-traits = "0.2.19" 19 | regex = "1.11.1" 20 | memx = "0.1.32" 21 | 22 | # Dependencies for Ethereum support 23 | hex = { version = "0.4.3", optional = true } # Make it optional 24 | secp256k1 = { version = "0.30.0", features = ["rand"], optional = true } # Make it optional 25 | sha3 = { version = "0.11.0-pre.4", optional = true } # Make it optional 26 | 27 | # Dependencies for Solana support 28 | solana-sdk = { version = "2.1.10", default-features = false, features = ["full"], optional = true } 29 | openssl = { version = "0.10", features = ["vendored"], optional = true } 30 | aho-corasick = "1.1.3" 31 | 32 | [features] 33 | default = [] 34 | 35 | ethereum = ["hex", "secp256k1", "sha3"] 36 | solana = ["solana-sdk", "openssl"] 37 | all = ["solana", "ethereum"] 38 | 39 | [dev-dependencies] 40 | tempfile = "3.6" 41 | 42 | [profile.test] 43 | opt-level = 3 44 | 45 | # The profile that 'cargo dist' will build with 46 | [profile.dist] 47 | inherits = "release" 48 | lto = "thin" 49 | -------------------------------------------------------------------------------- /images/bitcoin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use thiserror::Error; 3 | 4 | /// A unified error type that encapsulates all possible errors in the btc-vanity application. 5 | #[derive(Error, Debug)] 6 | pub enum VanityError { 7 | #[error("File error: {0}")] 8 | FileError(#[from] io::Error), 9 | 10 | #[error("Keys and address error: {0}")] 11 | KeysAndAddressError(&'static str), 12 | 13 | #[error("Vanity address generator error: {0}")] 14 | VanityGeneratorError(&'static str), 15 | 16 | #[error("Fast mode enabled, input is too long!")] 17 | FastModeEnabled, 18 | 19 | #[error("Input is not Base58 encoded!")] 20 | InputNotBase58, 21 | 22 | #[cfg(feature = "ethereum")] 23 | #[error("Input is not Base16 encoded!")] 24 | InputNotBase16, 25 | 26 | #[error("Invalid Regex!")] 27 | InvalidRegex, 28 | 29 | #[error("Regex is not Base58 encoded!")] 30 | RegexNotBase58, 31 | 32 | #[cfg(feature = "ethereum")] 33 | #[error("Regex is not Base16 encoded!")] 34 | RegexNotBase16, 35 | 36 | #[error("Request too long!")] 37 | RequestTooLong, 38 | 39 | #[cfg(feature = "ethereum")] 40 | #[error("Case sensitive wallet generation is not supported for Ethereum!")] 41 | EthereumCaseSensitiveIsNotSupported, 42 | 43 | #[error("Ethereum support is not enabled. Compile with `--features ethereum`.")] 44 | MissingFeatureEthereum, 45 | 46 | #[error("Solana support is not enabled. Compile with `--features solana`.")] 47 | MissingFeatureSolana, 48 | } 49 | 50 | impl From for VanityError { 51 | fn from(keys_and_address_err: KeysAndAddressError) -> Self { 52 | VanityError::KeysAndAddressError(keys_and_address_err.0) 53 | } 54 | } 55 | 56 | impl From for VanityError { 57 | fn from(vanity_err: VanityGeneratorError) -> Self { 58 | VanityError::VanityGeneratorError(vanity_err.0) 59 | } 60 | } 61 | 62 | /// Struct-based error types for backward compatibility or specific contexts. 63 | #[derive(Error, Debug)] 64 | #[error("Keys and address error: {0}")] 65 | pub struct KeysAndAddressError(pub &'static str); 66 | 67 | #[derive(Error, Debug)] 68 | #[error("Vanity address generator error: {0}")] 69 | pub struct VanityGeneratorError(pub &'static str); 70 | 71 | impl From for VanityGeneratorError { 72 | fn from(keys_and_address_err: KeysAndAddressError) -> Self { 73 | VanityGeneratorError(keys_and_address_err.0) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/keys_and_address/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Key Pair and Address Generation Module 2 | //! 3 | //! This module provides functionality for generating random key pairs and their associated addresses. 4 | //! Supported cryptocurrencies are `Bitcoin`, `Ethereum`, and `Solana`. 5 | 6 | pub mod btc; 7 | #[cfg(feature = "ethereum")] 8 | pub mod eth; 9 | #[cfg(feature = "solana")] 10 | pub mod sol; 11 | 12 | use std::array::from_fn; 13 | 14 | use crate::BATCH_SIZE; 15 | 16 | use bitcoin::{PrivateKey, PublicKey}; 17 | #[cfg(feature = "ethereum")] 18 | use secp256k1::{PublicKey as SecpPublicKey, SecretKey}; 19 | #[cfg(feature = "solana")] 20 | use solana_sdk::signature::Keypair; 21 | 22 | /// A trait to handle generic key pair and address creation. 23 | /// Used in vanity address generation. 24 | /// 25 | /// Implemented by `BitcoinKeyPair`, `EthereumKeyPair`, and `SolanaKeyPair`. 26 | pub trait KeyPairGenerator { 27 | /// Generates a random key pair. 28 | /// 29 | /// # Returns 30 | /// - A new instance of the implementing struct with generated keys and address. 31 | fn generate_random() -> Self 32 | where 33 | Self: Sized; 34 | 35 | /// Retrieves the address associated with the key pair. 36 | fn get_address(&self) -> &String; 37 | 38 | /// Retrieves the address bytes associated with the key pair. 39 | fn get_address_bytes(&self) -> &[u8]; 40 | 41 | fn generate_batch() -> [Self; BATCH_SIZE] 42 | where 43 | Self: Sized, 44 | { 45 | from_fn(|_| Self::generate_random()) 46 | } 47 | 48 | /// Fills an existing array with newly generated key pairs. 49 | /// 50 | /// We simply iterate and overwrite each slot with a call 51 | /// to `Self::generate_random()`. 52 | fn fill_batch(batch_array: &mut [Self; BATCH_SIZE]) 53 | where 54 | Self: Sized, 55 | { 56 | for slot in batch_array.iter_mut() { 57 | *slot = Self::generate_random(); 58 | } 59 | } 60 | } 61 | 62 | /// A struct representing a Bitcoin key pair and its associated address. 63 | /// Implements `KeyPairGenerator` and `Send` traits. 64 | pub struct BitcoinKeyPair { 65 | /// A Bitcoin private key. `bitcoin::PrivateKey` 66 | private_key: PrivateKey, 67 | /// A Bitcoin public key. `bitcoin::PublicKey` 68 | public_key: PublicKey, 69 | /// The compressed Bitcoin address as a `String`. 70 | comp_address: String, 71 | } 72 | 73 | unsafe impl Send for BitcoinKeyPair {} 74 | 75 | /// A struct representing an Ethereum key pair and its associated address. 76 | /// Implements `KeyPairGenerator` and `Send` traits. 77 | #[cfg(feature = "ethereum")] 78 | pub struct EthereumKeyPair { 79 | /// An Ethereum private key. `secp256k1::SecretKey` 80 | private_key: SecretKey, 81 | /// An Ethereum public key. `secp256k1::PublicKey` 82 | public_key: SecpPublicKey, 83 | /// The Ethereum address as a `String`. 84 | address: String, 85 | } 86 | 87 | #[cfg(feature = "ethereum")] 88 | unsafe impl Send for EthereumKeyPair {} 89 | 90 | /// A struct representing a Solana key pair and its associated address. 91 | /// Implements `KeyPairGenerator` and `Send` traits. 92 | #[cfg(feature = "solana")] 93 | pub struct SolanaKeyPair { 94 | /// A Solana `solana_sdk::signer::Keypair` struct. 95 | keypair: Keypair, 96 | /// The Solana address as a `String`. 97 | address: String, 98 | } 99 | 100 | #[cfg(feature = "solana")] 101 | unsafe impl Send for SolanaKeyPair {} 102 | -------------------------------------------------------------------------------- /src/keys_and_address/btc.rs: -------------------------------------------------------------------------------- 1 | //! # Bitcoin Key Pair and Address Generation 2 | //! 3 | //! This module provides functionality to generate Bitcoin key pairs and their associated compressed addresses. 4 | 5 | use std::cell::RefCell; 6 | 7 | use crate::keys_and_address::{BitcoinKeyPair, KeyPairGenerator}; 8 | 9 | use bitcoin::key::rand::rngs::ThreadRng; 10 | use bitcoin::key::{PrivateKey, PublicKey}; 11 | use bitcoin::secp256k1::{rand, All, Secp256k1}; 12 | use bitcoin::Address; 13 | use bitcoin::Network::Bitcoin; 14 | 15 | thread_local! { 16 | static THREAD_LOCAL_SECP256K1: Secp256k1 = Secp256k1::new(); 17 | static THREAD_LOCAL_RNG: RefCell = RefCell::new(rand::thread_rng()); 18 | } 19 | 20 | impl KeyPairGenerator for BitcoinKeyPair { 21 | /// Generates a random Bitcoin key pair and its compressed address. 22 | /// 23 | /// # Returns 24 | /// - A [BitcoinKeyPair] struct containing the private key, public key, and address. 25 | #[inline(always)] 26 | fn generate_random() -> Self { 27 | THREAD_LOCAL_SECP256K1.with(|secp256k1| { 28 | THREAD_LOCAL_RNG.with(|rng| { 29 | let mut rng = rng.borrow_mut(); 30 | let (secret_key, pk) = secp256k1.generate_keypair(&mut *rng); 31 | 32 | let private_key = PrivateKey::new(secret_key, Bitcoin); 33 | let public_key = PublicKey::new(pk); 34 | 35 | BitcoinKeyPair { 36 | private_key, 37 | public_key, 38 | comp_address: Address::p2pkh(public_key, Bitcoin).to_string(), 39 | } 40 | }) 41 | }) 42 | } 43 | 44 | /// Retrieves the compressed Bitcoin address as `String` reference. 45 | #[inline(always)] 46 | fn get_address(&self) -> &String { 47 | &self.comp_address 48 | } 49 | 50 | /// Retrieves the compressed Bitcoin address in byte slice format. 51 | #[inline(always)] 52 | fn get_address_bytes(&self) -> &[u8] { 53 | self.comp_address.as_bytes() 54 | } 55 | } 56 | 57 | impl BitcoinKeyPair { 58 | /// Retrieves the [PrivateKey] reference of the [BitcoinKeyPair]. 59 | pub fn get_private_key(&self) -> &PrivateKey { 60 | &self.private_key 61 | } 62 | 63 | /// Retrieves the [PublicKey] reference of the [BitcoinKeyPair]. 64 | pub fn get_public_key(&self) -> &PublicKey { 65 | &self.public_key 66 | } 67 | 68 | /// Retrieves the compressed address reference. 69 | pub fn get_comp_address(&self) -> &String { 70 | &self.comp_address 71 | } 72 | 73 | /// Retrieves the private key in Wallet Import Format (WIF) as a `String`. 74 | pub fn get_wif_private_key(&self) -> String { 75 | self.private_key.to_wif() 76 | } 77 | 78 | /// Retrieves the compressed public key as a `String`. 79 | pub fn get_comp_public_key(&self) -> String { 80 | self.public_key.to_string() 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use super::*; 87 | use bitcoin::secp256k1::Secp256k1; 88 | 89 | #[test] 90 | fn test_generate_random() { 91 | let secp = Secp256k1::new(); 92 | 93 | // Generate a random key pair and address 94 | let keys_and_address = BitcoinKeyPair::generate_random(); 95 | 96 | // Check if the private key can generate the same public key 97 | let derived_public_key = PublicKey::from_private_key(&secp, &keys_and_address.private_key); 98 | assert_eq!(keys_and_address.public_key, derived_public_key); 99 | 100 | // Check if the derived public key generates the same address 101 | let derived_address = Address::p2pkh(derived_public_key, Bitcoin).to_string(); 102 | assert_eq!(keys_and_address.comp_address, derived_address); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/keys_and_address/sol.rs: -------------------------------------------------------------------------------- 1 | //! # Solana Key Pair and Address Generation 2 | //! 3 | //! This module provides functionality to generate Solana key pairs and their associated addresses. 4 | 5 | use crate::keys_and_address::{KeyPairGenerator, SolanaKeyPair}; 6 | 7 | use rand::{rngs::ThreadRng, RngCore}; 8 | use solana_sdk::bs58; 9 | use solana_sdk::signature::{Keypair, SeedDerivable, Signer}; 10 | use std::cell::RefCell; 11 | 12 | thread_local! { 13 | static THREAD_LOCAL_RNG: RefCell = RefCell::new(rand::rng()); 14 | } 15 | 16 | impl KeyPairGenerator for SolanaKeyPair { 17 | /// Generates a random Solana key pair and its address. 18 | /// 19 | /// # Returns 20 | /// - A [SolanaKeyPair] struct containing the key pair and address. 21 | #[inline(always)] 22 | fn generate_random() -> Self { 23 | THREAD_LOCAL_RNG.with(|rng| { 24 | let mut seed = [0u8; 32]; 25 | rng.borrow_mut().fill_bytes(&mut seed); // Fill the seed with random bytes 26 | let keypair = Keypair::from_seed(&seed).expect("Valid seed"); 27 | let address = keypair.pubkey().to_string(); 28 | 29 | SolanaKeyPair { keypair, address } 30 | }) 31 | } 32 | 33 | /// Retrieves the Solana address as a `String` reference. 34 | #[inline(always)] 35 | fn get_address(&self) -> &String { 36 | &self.address 37 | } 38 | 39 | /// Retrieves the Solana address in byte slice format. 40 | #[inline(always)] 41 | fn get_address_bytes(&self) -> &[u8] { 42 | self.address.as_bytes() 43 | } 44 | } 45 | 46 | impl SolanaKeyPair { 47 | /// Retrieves the key pair as a `solana_sdk::signature::Keypair` reference. 48 | pub fn keypair(&self) -> &Keypair { 49 | &self.keypair 50 | } 51 | 52 | /// Retrieves the private key in Base58 encoding as a `String`. 53 | pub fn get_private_key_as_base58(&self) -> String { 54 | bs58::encode(self.keypair.secret_bytes()).into_string() 55 | } 56 | 57 | /// Retrieves the public key in Base58 encoding as `String` reference. 58 | pub fn get_public_key_as_base58(&self) -> &String { 59 | &self.address 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | 67 | use bs58; 68 | use solana_sdk::signature::Signer; 69 | 70 | #[test] 71 | fn test_generate_random() { 72 | // Generate a random Solana key pair 73 | let key_pair = SolanaKeyPair::generate_random(); 74 | 75 | // Ensure the public key is derived correctly 76 | let derived_public_key = key_pair.keypair.pubkey(); 77 | assert_eq!(key_pair.keypair.pubkey(), derived_public_key); 78 | 79 | // Ensure the address matches the public key 80 | let address = key_pair.get_address(); 81 | assert_eq!(address, &derived_public_key.to_string()); 82 | } 83 | 84 | #[test] 85 | fn test_get_private_key_as_base58() { 86 | let key_pair = SolanaKeyPair::generate_random(); 87 | let private_key_base58 = bs58::encode(key_pair.keypair.secret_bytes()).into_string(); 88 | 89 | assert_eq!(key_pair.get_private_key_as_base58(), private_key_base58); 90 | } 91 | 92 | #[test] 93 | fn test_get_public_key_as_base58() { 94 | let key_pair = SolanaKeyPair::generate_random(); 95 | let public_key_base58 = key_pair.keypair.pubkey().to_string(); 96 | 97 | assert_eq!(*key_pair.get_public_key_as_base58(), public_key_base58); 98 | } 99 | 100 | #[test] 101 | fn test_unique_keypairs() { 102 | // Generate multiple key pairs and ensure they are unique 103 | let key_pair_1 = SolanaKeyPair::generate_random(); 104 | let key_pair_2 = SolanaKeyPair::generate_random(); 105 | 106 | assert_ne!(key_pair_1.keypair.pubkey(), key_pair_2.keypair.pubkey()); 107 | assert_ne!( 108 | key_pair_1.get_private_key_as_base58(), 109 | key_pair_2.get_private_key_as_base58() 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "ethereum")] 2 | use btc_vanity::EthereumKeyPair; 3 | use btc_vanity::{BitcoinKeyPair, VanityAddr, VanityMode}; 4 | #[cfg(feature = "solana")] 5 | use btc_vanity::{KeyPairGenerator, SolanaKeyPair}; 6 | 7 | #[test] 8 | fn test_bitcoin_vanity_address_prefix() { 9 | // Try generating a Bitcoin vanity address with a specific prefix 10 | let result = VanityAddr::generate::( 11 | "tst", // Prefix 12 | 8, // Threads 13 | false, // Case-insensitive 14 | true, // Enable fast mode 15 | VanityMode::Prefix, 16 | ); 17 | 18 | // Assert that the result is successful 19 | assert!(result.is_ok(), "Failed to generate Bitcoin vanity address"); 20 | 21 | // Assert that the generated address starts with the prefix 22 | let keypair = result.unwrap(); 23 | assert!( 24 | keypair 25 | .get_comp_address() 26 | .to_lowercase() 27 | .starts_with("1tst"), 28 | "Generated address does not match prefix" 29 | ); 30 | } 31 | 32 | #[test] 33 | #[cfg(feature = "ethereum")] 34 | fn test_ethereum_vanity_address_prefix() { 35 | // Try generating an Ethereum vanity address with a specific prefix 36 | let result = VanityAddr::generate::( 37 | "123", // Prefix 38 | 8, // Threads 39 | false, // Case-sensitive 40 | true, // Enable fast mode 41 | VanityMode::Prefix, 42 | ); 43 | 44 | // Assert that the result is successful 45 | assert!(result.is_ok(), "Failed to generate Ethereum vanity address"); 46 | 47 | // Assert that the generated address starts with the prefix 48 | let keypair = result.unwrap(); 49 | assert!( 50 | keypair.get_address_with_prefix().starts_with("0x123"), 51 | "Generated Ethereum address does not match prefix" 52 | ); 53 | } 54 | 55 | #[test] 56 | #[cfg(feature = "solana")] 57 | fn test_solana_vanity_address_prefix() { 58 | // Try generating a Solana vanity address with a specific prefix 59 | let result = VanityAddr::generate::( 60 | "abc", // Prefix 61 | 8, // Threads 62 | false, // Case-insensitive 63 | false, // Disable fast mode 64 | VanityMode::Prefix, 65 | ); 66 | 67 | // Assert that the result is successful 68 | assert!(result.is_ok(), "Failed to generate Solana vanity address"); 69 | 70 | // Assert that the generated address starts with the prefix 71 | let keypair = result.unwrap(); 72 | assert!( 73 | keypair 74 | .get_address() 75 | .to_ascii_lowercase() 76 | .starts_with("abc"), 77 | "Generated Solana address does not match prefix" 78 | ); 79 | } 80 | 81 | #[test] 82 | fn test_bitcoin_vanity_address_regex() { 83 | // Regex to match addresses starting with "1A" and ending with "Z" 84 | let regex_pattern = "^1A.*Z$"; 85 | 86 | let result = VanityAddr::generate_regex::(regex_pattern, 8); 87 | 88 | // Assert that the result is successful 89 | assert!( 90 | result.is_ok(), 91 | "Failed to generate Bitcoin vanity address with regex" 92 | ); 93 | 94 | // Assert that the generated address matches the regex 95 | let keypair = result.unwrap(); 96 | let address = keypair.get_comp_address(); 97 | let regex = regex::Regex::new(regex_pattern).unwrap(); 98 | assert!( 99 | regex.is_match(&address), 100 | "Generated address '{}' does not match regex '{}'", 101 | address, 102 | regex_pattern 103 | ); 104 | } 105 | 106 | #[test] 107 | fn test_invalid_regex_handling() { 108 | // Invalid regex pattern 109 | let invalid_regex = "["; 110 | 111 | let result = VanityAddr::generate_regex::(invalid_regex, 8); 112 | 113 | // Assert that the result is an error 114 | assert!( 115 | result.is_err(), 116 | "Expected error for invalid regex, but got success" 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(rustdoc::invalid_html_tags)] 2 | //! # btc-vanity 3 | //! 4 | //! `btc-vanity` is a blazingly fast Rust library and CLI tool designed for generating **vanity cryptocurrency addresses**. 5 | //! Whether you are looking to create Bitcoin, Ethereum, or Solana addresses with specific patterns or substrings, 6 | //! `btc-vanity` offers a customizable and highly performant solution to achieve your goals. 7 | //! 8 | //! With support for **prefix**, **suffix**, **substring**, or even **regex-based patterns**, this library 9 | //! ensures you can generate your desired address with ease. Built with multithreaded support, it maximizes 10 | //! performance to quickly find vanity addresses for various blockchains. 11 | //! 12 | //! ### Example Usages 13 | //! 14 | //! You can easily generate a random Bitcoin keypair and print the private and public keys along with the address: 15 | //! 16 | //! ```rust 17 | //! use btc_vanity::{BitcoinKeyPair, KeyPairGenerator}; 18 | //! 19 | //! let random_address = BitcoinKeyPair::generate_random(); 20 | //! 21 | //! println!("Randomly generated Bitcoin key pair:\n\ 22 | //! private_key (WIF): {}\n\ 23 | //! public_key (compressed): {}\n\ 24 | //! address (compressed): {}\n", 25 | //! random_address.get_wif_private_key(), 26 | //! random_address.get_comp_public_key(), 27 | //! random_address.get_comp_address()); 28 | //! ``` 29 | //! 30 | //! Find a Bitcoin address that contains the substring `emiv` (case-insensitive) using 16 threads: 31 | //! 32 | //! ```rust 33 | //! use btc_vanity::{BitcoinKeyPair, VanityAddr, VanityMode}; 34 | //! 35 | //! let vanity_address: BitcoinKeyPair = VanityAddr::generate( 36 | //! "emiv", // Desired substring 37 | //! 16, // Number of threads 38 | //! false, // Case-insensitive 39 | //! true, // Enable fast mode 40 | //! VanityMode::Anywhere // Match substring anywhere in the address 41 | //! ).unwrap(); 42 | //! 43 | //! println!("Vanity address:\n\ 44 | //! private_key (WIF): {}\n\ 45 | //! public_key (compressed): {}\n\ 46 | //! address (compressed): {}\n", 47 | //! vanity_address.get_wif_private_key(), 48 | //! vanity_address.get_comp_public_key(), 49 | //! vanity_address.get_comp_address()); 50 | //! ``` 51 | //! 52 | //! Create a Bitcoin address with `meow` anywhere in the address (case-sensitive) using 4 threads: 53 | //! 54 | //! ```rust 55 | //! use btc_vanity::{BitcoinKeyPair, VanityAddr, VanityMode}; 56 | //! 57 | //! let vanity_address: BitcoinKeyPair = VanityAddr::generate( 58 | //! "meow", // Desired substring 59 | //! 4, // Number of threads 60 | //! true, // Case-sensitive 61 | //! true, // Enable fast mode 62 | //! VanityMode::Anywhere // Match substring anywhere in the address 63 | //! ).unwrap(); 64 | //! 65 | //! println!("Vanity address:\n\ 66 | //! private_key (WIF): {}\n\ 67 | //! public_key (compressed): {}\n\ 68 | //! address (compressed): {}\n", 69 | //! vanity_address.get_wif_private_key(), 70 | //! vanity_address.get_comp_public_key(), 71 | //! vanity_address.get_comp_address()); 72 | //! ``` 73 | //! 74 | //! Find a Bitcoin address that matches a regex pattern `^1E.ET.*T$` with using 12 threads: 75 | //! 76 | //! ```rust 77 | //! use btc_vanity::{BitcoinKeyPair, VanityAddr}; 78 | //! 79 | //! let vanity_address = VanityAddr::generate_regex::( 80 | //! "^1E.*ET.*T$", // The regex pattern 81 | //! 12 // Number of threads 82 | //! ).unwrap(); 83 | //! 84 | //! println!("Bitcoin regex-matched vanity address:\n\ 85 | //! private_key (WIF): {}\n\ 86 | //! public_key (compressed): {}\n\ 87 | //! address (compressed): {}\n", 88 | //! vanity_address.get_wif_private_key(), 89 | //! vanity_address.get_comp_public_key(), 90 | //! vanity_address.get_comp_address()); 91 | //! ``` 92 | 93 | pub const BATCH_SIZE: usize = 256; 94 | 95 | pub mod cli; 96 | pub mod error; 97 | pub mod file; 98 | pub mod flags; 99 | pub mod keys_and_address; 100 | pub mod vanity_addr_generator; 101 | 102 | #[cfg(feature = "ethereum")] 103 | pub use crate::keys_and_address::EthereumKeyPair; 104 | #[cfg(feature = "solana")] 105 | pub use crate::keys_and_address::SolanaKeyPair; 106 | pub use crate::keys_and_address::{BitcoinKeyPair, KeyPairGenerator}; 107 | pub use vanity_addr_generator::vanity_addr::{VanityAddr, VanityMode}; 108 | -------------------------------------------------------------------------------- /src/keys_and_address/eth.rs: -------------------------------------------------------------------------------- 1 | //! # Ethereum Key Pair and Address Generation 2 | //! 3 | //! This module provides functionality to generate Ethereum key pairs and their associated addresses. 4 | 5 | use std::cell::RefCell; 6 | 7 | use crate::keys_and_address::{EthereumKeyPair, KeyPairGenerator}; 8 | 9 | use hex::encode; 10 | use rand::{rngs::ThreadRng, RngCore}; 11 | use secp256k1::{All, PublicKey, Secp256k1, SecretKey}; 12 | use sha3::{Digest, Keccak256}; 13 | 14 | thread_local! { 15 | static THREAD_LOCAL_SECP256K1: Secp256k1 = Secp256k1::new(); 16 | static THREAD_LOCAL_RNG: RefCell = RefCell::new(rand::rng()); 17 | } 18 | 19 | impl KeyPairGenerator for EthereumKeyPair { 20 | /// Generates a random Ethereum key pair and its address. 21 | /// 22 | /// # Returns 23 | /// - An [EthereumKeyPair] struct containing the private key, public key, and address. 24 | #[inline(always)] 25 | fn generate_random() -> Self { 26 | THREAD_LOCAL_SECP256K1.with(|secp256k1| { 27 | THREAD_LOCAL_RNG.with(|rng| { 28 | let mut secret_bytes = [0u8; 32]; 29 | rng.borrow_mut().fill_bytes(&mut secret_bytes); 30 | 31 | let secret_key = SecretKey::from_byte_array(&secret_bytes) 32 | .expect("32 bytes, within curve order"); 33 | let public_key = PublicKey::from_secret_key(secp256k1, &secret_key); 34 | 35 | // Derive the Ethereum address (Keccak256 hash of the public key, last 20 bytes) 36 | let public_key_bytes = public_key.serialize_uncompressed(); 37 | let public_key_hash = Keccak256::digest(&public_key_bytes[1..]); // Skip the 0x04 prefix 38 | let address = encode(&public_key_hash[12..]); // Use the last 20 bytes 39 | 40 | EthereumKeyPair { 41 | private_key: secret_key, 42 | public_key, 43 | address, 44 | } 45 | }) 46 | }) 47 | } 48 | 49 | /// Retrieves the Ethereum address as `String` reference. 50 | #[inline(always)] 51 | fn get_address(&self) -> &String { 52 | &self.address 53 | } 54 | 55 | /// Retrieves the Ethereum address in byte slice format. 56 | #[inline(always)] 57 | fn get_address_bytes(&self) -> &[u8] { 58 | self.address.as_bytes() 59 | } 60 | } 61 | 62 | impl EthereumKeyPair { 63 | /// Retrieves the private key as a hex-encoded str 64 | pub fn get_private_key_as_hex(&self) -> String { 65 | encode(self.private_key.secret_bytes()) 66 | } 67 | 68 | /// Retrieves the private key as a hex-encoded `String` with the `0x` prefix. 69 | pub fn get_private_key_as_hex_with_prefix(&self) -> String { 70 | format!("0x{}", encode(self.private_key.secret_bytes())) 71 | } 72 | 73 | /// Retrieves the public key as a hex-encoded `String`. 74 | pub fn get_public_key_as_hex(&self) -> String { 75 | encode(self.public_key.serialize_uncompressed()) 76 | } 77 | 78 | /// Retrieves the Ethereum address as a hex-encoded `String` with the `0x` prefix. 79 | pub fn get_address_with_prefix(&self) -> String { 80 | format!("0x{}", self.address) 81 | } 82 | 83 | /// Returns the private key reference as `secp256k1::SecretKey`. 84 | pub fn private_key(&self) -> &SecretKey { 85 | &self.private_key 86 | } 87 | 88 | /// Returns the public key reference as `secp256k1::PublicKey`. 89 | pub fn public_key(&self) -> &PublicKey { 90 | &self.public_key 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | use secp256k1::Secp256k1; 98 | use sha3::{Digest, Keccak256}; 99 | 100 | #[test] 101 | fn test_generate_random() { 102 | let secp = Secp256k1::new(); 103 | 104 | // Generate a random Ethereum key pair and address 105 | let key_pair = EthereumKeyPair::generate_random(); 106 | 107 | // Derive public key from private key 108 | let derived_public_key = PublicKey::from_secret_key(&secp, &key_pair.private_key); 109 | assert_eq!(key_pair.public_key, derived_public_key); 110 | 111 | // Derive Ethereum address from public key 112 | let public_key_bytes = derived_public_key.serialize_uncompressed(); 113 | let public_key_hash = Keccak256::digest(&public_key_bytes[1..]); // Skip the 0x04 prefix 114 | let derived_address = encode(&public_key_hash[12..]); // Use the last 20 bytes 115 | 116 | assert_eq!(key_pair.address, derived_address); 117 | } 118 | 119 | #[test] 120 | fn test_get_public_key_as_hex() { 121 | let key_pair = EthereumKeyPair::generate_random(); 122 | let public_key_hex = key_pair.get_public_key_as_hex(); 123 | 124 | // Verify the public key hex matches the serialized public key 125 | let expected_hex = encode(key_pair.public_key.serialize_uncompressed()); 126 | assert_eq!(public_key_hex, expected_hex); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /tests/cli_tests.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::Write; 3 | use std::process::Command; 4 | 5 | use tempfile::NamedTempFile; 6 | 7 | #[test] 8 | fn test_cli_with_prefix() { 9 | // Run the CLI with a prefix 10 | let result = Command::new("./target/debug/btc-vanity") 11 | .args(["-p", "tst"]) // Prefix flag and prefix 12 | .output() 13 | .expect("Failed to execute CLI command"); 14 | 15 | // Check that the CLI exits successfully 16 | assert!( 17 | result.status.success(), 18 | "CLI failed with error: {}", 19 | String::from_utf8_lossy(&result.stderr) 20 | ); 21 | 22 | // Verify that the output contains the expected prefix 23 | let stdout = String::from_utf8_lossy(&result.stdout); 24 | assert!( 25 | stdout 26 | .to_ascii_lowercase() 27 | .contains("address (compressed): 1tst"), 28 | "CLI output does not contain expected prefix: {}", 29 | stdout 30 | ); 31 | } 32 | 33 | #[test] 34 | fn test_cli_with_regex() { 35 | // Run the CLI with a regex pattern 36 | let result = Command::new("./target/debug/btc-vanity") 37 | .args(["-r", "^1E.*T$"]) // Regex flag and pattern 38 | .output() 39 | .expect("Failed to execute CLI command"); 40 | 41 | // Check that the CLI exits successfully 42 | assert!( 43 | result.status.success(), 44 | "CLI failed with error: {}", 45 | String::from_utf8_lossy(&result.stderr) 46 | ); 47 | 48 | // Verify that the output matches the regex 49 | let stdout = String::from_utf8_lossy(&result.stdout); 50 | assert!(stdout.contains("address (compressed): 1E") && stdout.contains("regex: '^1E.*T$'"),); 51 | } 52 | 53 | #[test] 54 | fn test_cli_with_output_file() { 55 | let output_file = "test_output.txt"; 56 | 57 | // Run the CLI with an output file flag 58 | let result = Command::new("./target/debug/btc-vanity") 59 | .args(["-o", output_file, "tst"]) // Output file flag and pattern 60 | .output() 61 | .expect("Failed to execute CLI command"); 62 | 63 | // Check that the CLI exits successfully 64 | assert!( 65 | result.status.success(), 66 | "CLI failed with error: {}", 67 | String::from_utf8_lossy(&result.stderr) 68 | ); 69 | 70 | // Check that the output file exists 71 | let output = fs::read_to_string(output_file).expect("Failed to read output file"); 72 | assert!( 73 | output 74 | .to_ascii_lowercase() 75 | .contains("address (compressed): 1tst"), 76 | "Output file does not contain expected data: {}", 77 | output 78 | ); 79 | 80 | // Clean up the temporary file 81 | fs::remove_file(output_file).expect("Failed to delete temporary output file"); 82 | } 83 | 84 | #[test] 85 | fn test_cli_with_case_sensitivity() { 86 | // Run the CLI with case-sensitive flag 87 | let result = Command::new("./target/debug/btc-vanity") 88 | .args(["-c", "-a", "TST"]) // Case-sensitive flag and pattern 89 | .output() 90 | .expect("Failed to execute CLI command"); 91 | 92 | // Check that the CLI exits successfully 93 | assert!( 94 | result.status.success(), 95 | "CLI failed with error: {}", 96 | String::from_utf8_lossy(&result.stderr) 97 | ); 98 | 99 | // Verify that the output matches the case-sensitive pattern 100 | let stdout = String::from_utf8_lossy(&result.stdout); 101 | assert!( 102 | stdout.contains("TST"), 103 | "CLI output does not match expected case-sensitive pattern: {}", 104 | stdout 105 | ); 106 | } 107 | 108 | #[test] 109 | fn test_cli_missing_required_arguments() { 110 | // Run the CLI without any arguments 111 | let result = Command::new("./target/debug/btc-vanity") 112 | .output() 113 | .expect("Failed to execute CLI command"); 114 | 115 | // Check that the CLI exits with an error 116 | assert!(!result.status.success(), "CLI succeeded unexpectedly"); 117 | 118 | // Verify that the error message is printed 119 | let stderr = String::from_utf8_lossy(&result.stderr); 120 | assert!( 121 | stderr.contains("the following required arguments were not provided"), 122 | "CLI error message is incorrect: {}", 123 | stderr 124 | ); 125 | } 126 | 127 | #[test] 128 | fn test_cli_with_input_file_tempfile() { 129 | // Create a temporary input file 130 | let mut temp_file = NamedTempFile::new().expect("Failed to create temporary file"); 131 | writeln!(temp_file, "t1 -p\nT2 -c -p").expect("Failed to write to temporary file"); 132 | 133 | // Get the file path 134 | let input_file_path = temp_file.path().to_str().expect("Failed to get file path"); 135 | 136 | // Run the CLI with the input file 137 | let result = Command::new("./target/debug/btc-vanity") 138 | .args(["-i", input_file_path]) // Input file flag 139 | .output() 140 | .expect("Failed to execute CLI command"); 141 | 142 | // Check that the CLI exits successfully 143 | assert!( 144 | result.status.success(), 145 | "CLI failed with error: {}", 146 | String::from_utf8_lossy(&result.stderr) 147 | ); 148 | 149 | // Verify that the output contains the expected results 150 | let stdout = String::from_utf8_lossy(&result.stdout); 151 | assert!( 152 | stdout.to_lowercase().contains("address (compressed): 1t1") 153 | && stdout.contains("address (compressed): 1T2"), 154 | "CLI output does not contain expected results: {}", 155 | stdout 156 | ); 157 | } 158 | -------------------------------------------------------------------------------- /src/flags.rs: -------------------------------------------------------------------------------- 1 | //! # CLI and Input File Flags Module 2 | //! 3 | //! This module handles the extraction and management of flags and configuration options 4 | //! from the command-line interface (CLI) and input files. It provides mechanisms to: 5 | //! - Parse flags and input patterns from the CLI. 6 | //! - Combine and prioritize CLI-level and file-based flags. 7 | 8 | use crate::vanity_addr_generator::chain::Chain; 9 | use crate::VanityMode; 10 | use clap::ArgMatches; 11 | 12 | /// Represents the configuration flags for vanity address generation. 13 | #[derive(Debug, Clone, Default)] 14 | pub struct VanityFlags { 15 | /// The number of threads to use for generation. 16 | pub threads: usize, 17 | /// The name of the output file, if specified. 18 | pub output_file_name: Option, 19 | 20 | /// If `true`, CLI flags override file-based flags. 21 | pub force_flags: bool, 22 | /// If `true`, pattern matching is case-sensitive. 23 | pub is_case_sensitive: bool, 24 | /// If `true`, disables fast mode. Fast mode puts a length limit for the searching string. 25 | pub disable_fast_mode: bool, 26 | /// Specifies the mode of matching (e.g., `prefix`, `suffix`, `anywhere`, `regex`). 27 | pub vanity_mode: Option, 28 | /// Specifies the blockchain type (e.g., `Bitcoin`, `Ethereum`, `Solana`). 29 | pub chain: Option, 30 | } 31 | 32 | /// Enum representing the source of the vanity patterns. 33 | #[derive(Debug)] 34 | pub enum PatternsSource { 35 | /// A single pattern provided directly via the CLI. 36 | SingleString(String), 37 | /// Patterns read from an input file specified by its path. 38 | InputFile(String), 39 | } 40 | 41 | /// Parses CLI arguments into [VanityFlags] and determines the source of the vanity patterns. 42 | /// 43 | /// # Arguments 44 | /// - `matches`: The `ArgMatches` object provided by the `clap` library. 45 | /// 46 | /// # Returns 47 | /// - A tuple containing: 48 | /// - `VanityFlags`: The parsed configuration flags. 49 | /// - `PatternsSource`: The source of the vanity patterns (either a single string or an input file). 50 | /// 51 | /// # Behavior 52 | /// - Determines the blockchain (`chain`) based on flags (e.g., `ethereum`, `solana`, `bitcoin`). 53 | /// - Determines the vanity mode (`vanity_mode`) based on flags (e.g., `regex`, `anywhere`, `suffix`, `prefix`). 54 | /// - Parses the number of threads, defaulting to 16 if not specified. 55 | /// - Detects whether patterns are provided via a single string or an input file. 56 | pub fn parse_cli(matches: ArgMatches) -> (VanityFlags, PatternsSource) { 57 | // 1) Extract chain 58 | let chain = if matches.get_flag("ethereum") { 59 | Some(Chain::Ethereum) 60 | } else if matches.get_flag("solana") { 61 | Some(Chain::Solana) 62 | } else { 63 | Some(Chain::Bitcoin) 64 | }; 65 | 66 | // 2) Extract vanity mode 67 | let vanity_mode = if matches.get_flag("regex") { 68 | Some(VanityMode::Regex) 69 | } else if matches.get_flag("anywhere") { 70 | Some(VanityMode::Anywhere) 71 | } else if matches.get_flag("suffix") { 72 | Some(VanityMode::Suffix) 73 | } else { 74 | Some(VanityMode::Prefix) 75 | }; 76 | 77 | // 3) Threads 78 | let threads = matches 79 | .get_one::("threads") 80 | .unwrap_or(&"16".to_owned()) 81 | .parse::() 82 | .unwrap_or(16); 83 | 84 | // 4) Build CLI-level `VanityFlags` 85 | let cli_flags = VanityFlags { 86 | force_flags: matches.get_flag("force-flags"), 87 | is_case_sensitive: matches.get_flag("case-sensitive"), 88 | disable_fast_mode: matches.get_flag("disable-fast-mode"), 89 | output_file_name: matches.get_one::("output-file").cloned(), 90 | vanity_mode, 91 | chain, 92 | threads, 93 | }; 94 | 95 | // 5) Figure out if user gave a single pattern or a file 96 | if let Some(path) = matches.get_one::("input-file") { 97 | (cli_flags, PatternsSource::InputFile(path.to_string())) 98 | } else { 99 | let string = matches.get_one::("string"); 100 | ( 101 | cli_flags, 102 | PatternsSource::SingleString(string.unwrap_or(&String::new()).to_string()), 103 | ) 104 | } 105 | } 106 | 107 | /// Combines CLI-level flags with file-based flags, giving priority to CLI flags if `force_flags` is set. 108 | /// 109 | /// # Arguments 110 | /// - `file_flags`: The `VanityFlags` object derived from the input file. 111 | /// 112 | /// # Returns 113 | /// - A unified `VanityFlags` object that combines CLI and file flags. 114 | /// 115 | /// # Behavior 116 | /// - If `force_flags` is `true`, the CLI flags override all file-based flags. 117 | /// - Otherwise, the flags are merged, with file-based flags taking precedence where applicable. 118 | /// 119 | /// # Example 120 | /// ```rust 121 | /// use btc_vanity::flags::VanityFlags; 122 | /// 123 | /// let cli_flags = VanityFlags { 124 | /// threads: 8, 125 | /// force_flags: true, 126 | /// ..Default::default() 127 | /// }; 128 | /// let file_flags = VanityFlags { 129 | /// threads: 4, 130 | /// ..Default::default() 131 | /// }; 132 | /// 133 | /// let unified_flags = cli_flags.unify(&file_flags); 134 | /// assert_eq!(unified_flags.threads, 8); // CLI flags take precedence. 135 | /// ``` 136 | impl VanityFlags { 137 | pub fn unify(&self, file_flags: &VanityFlags) -> VanityFlags { 138 | if self.force_flags { 139 | // If CLI has force_flags = true, ignore the file-based flags 140 | self.clone() 141 | } else { 142 | VanityFlags { 143 | threads: self.threads, 144 | output_file_name: file_flags 145 | .output_file_name 146 | .clone() 147 | .or_else(|| self.output_file_name.clone()), 148 | 149 | force_flags: self.force_flags, 150 | is_case_sensitive: file_flags.is_case_sensitive, 151 | disable_fast_mode: file_flags.disable_fast_mode, 152 | 153 | vanity_mode: file_flags.vanity_mode.or(self.vanity_mode), // Use `file_flags` if Some, otherwise fall back to `self`. 154 | 155 | chain: file_flags.chain.or(self.chain), // Use `file_flags` if Some, otherwise fall back to `self`. 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | //! # Cli With Using Clap Crate 2 | //! 3 | //! This module is used for creating a cli app for btc-vanity with using clap crate 4 | //! 5 | //! # Usage 6 | //! 7 | //! ```bash 8 | //! $ btc-vanity --help 9 | //! ``` 10 | //! 11 | //! The CLI tool provides several options to customize your address generation: 12 | //! 13 | //! ```shell 14 | //! $ btc-vanity [OPTIONS] 15 | //! ``` 16 | //! 17 | //! #### Blockchain Selection 18 | //! `--btc`: Generates Bitcoin keypairs and addresses. [default]
19 | //! `--eth`: Generates Ethereum keypairs and addresses.
20 | //! `--sol`: Generates Solana keypairs and addresses.
21 | //! 22 | //! #### General Options 23 | //! `-i, --input-file `: Reads patterns and it's flags from the specified file for vanity address generation, with one pattern per line.
24 | //! `-o, --output-file `: Saves generated wallet details to the specified file, creating it if it doesn’t exist or appending if it does.
25 | //! `-t, --threads `: Sets the number of threads for address generation.
26 | //! `-f, --force-flags`: Forces CLI flags to override any flags specified in the input file, ensuring consistent behavior across all patterns.
27 | //! `-d, --disable-fast`: Disables fast mode to allow longer patterns (5 for BTC and SOL, 16 for ETH), though it may increase search time.
28 | //! 29 | //! #### Matching Options 30 | //! `-p, --prefix`: Matches the pattern as a prefix of the address. [default]
31 | //! `-s, --suffix`: Matches the pattern as a suffix of the address.
32 | //! `-a, --anywhere`: Matches the pattern anywhere in the address.
33 | //! `-r, --regex `: Matches addresses using a regex pattern, supporting advanced customization like anchors and wildcards.
34 | //! `-c, --case-sensitive`: Enables case-sensitive matching, making patterns distinguish between uppercase and lowercase characters.
35 | //! 36 | //! ### Bitcoin CLI Examples 37 | //! 38 | //! Generate a Bitcoin address with prefix `1Emiv` (case-insensitive): 39 | //! 40 | //! ```shell 41 | //! $ btc-vanity Emiv 42 | //! ``` 43 | //! 44 | //! Generate a Bitcoin address containing the substring `test` (case-sensitive): 45 | //! 46 | //! ```shell 47 | //! $ btc-vanity -a -c test 48 | //! ``` 49 | //! 50 | //! Generate a Bitcoin address using a regex pattern `^1E.*T$`: 51 | //! 52 | //! ```shell 53 | //! $ btc-vanity -r "^1E.*T$" 54 | //! ``` 55 | //! 56 | //! Generate multiple Bitcoin addresses and save to wallets.txt: 57 | //! 58 | //! > [!NOTE] 59 | //! > -f flag will override any pattern flags inside the `input-file.txt`. 60 | //! > For example if there line `emiv -s --eth` will become `emiv -p --btc -c`. 61 | //! > The resulting wallet will be printed in `wallets.txt`. 62 | //! 63 | //! ```shell 64 | //! $ btc-vanity -f --btc -p -c -i input-file.txt -o wallets.txt 65 | //! ``` 66 | //! 67 | //! Generate an Ethereum address starting with 0xdead with 8 threads: 68 | //! 69 | //! ```shell 70 | //! $ btc-vanity --eth -t 8 dead 71 | //! ``` 72 | //! 73 | //! Generate a Solana address ending with 123: 74 | //! 75 | //! ```shell 76 | //! $ btc-vanity --sol -s 123 77 | //! ``` 78 | 79 | use clap::{Arg, ArgAction, ArgGroup, Command}; 80 | 81 | /// Runs the clap app to create the CLI 82 | pub fn cli() -> Command { 83 | Command::new(env!("CARGO_PKG_NAME")) 84 | .version(env!("CARGO_PKG_VERSION")) 85 | .about(env!("CARGO_PKG_DESCRIPTION")) 86 | .next_line_help(true) 87 | .arg( 88 | Arg::new("bitcoin") 89 | .long("btc") 90 | .action(ArgAction::SetTrue) 91 | .conflicts_with_all(["ethereum", "solana"]) 92 | .help("Generates Bitcoin keypairs and addresses. [default]") 93 | ) 94 | .arg( 95 | Arg::new("ethereum") 96 | .long("eth") 97 | .action(ArgAction::SetTrue) 98 | .conflicts_with_all(["bitcoin", "solana", "case-sensitive"]) 99 | .help("Generates Ethereum keypairs and addresses.") 100 | ) 101 | .arg( 102 | Arg::new("solana") 103 | .long("sol") 104 | .action(ArgAction::SetTrue) 105 | .conflicts_with_all(["bitcoin", "ethereum"]) 106 | .help("Generates Solana keypairs and addresses.") 107 | ) 108 | .arg( 109 | Arg::new("string") 110 | .index(1) 111 | .required_unless_present_any(["input-file"]) 112 | .help("The string (or regex) used to match Bitcoin vanity addresses."), 113 | ) 114 | .arg( 115 | Arg::new("input-file") 116 | .short('i') 117 | .long("input-file") 118 | .required_unless_present_any(["string"]) 119 | .value_name("FILE") 120 | .help("Reads patterns and it's flags from the specified file for vanity address generation, with one pattern and it's flags per line."), 121 | ) 122 | .arg( 123 | Arg::new("output-file") 124 | .short('o') 125 | .long("output-file") 126 | .value_name("FILE") 127 | .help("Saves generated wallet details to the specified file, creating it if it doesn’t exist or appending if it does."), 128 | ) 129 | .arg( 130 | Arg::new("force-flags") 131 | .short('f') 132 | .long("force-flags") 133 | .action(ArgAction::SetTrue) 134 | .help("Forces CLI flags to override any flags specified in the input file, ensuring consistent behavior across all patterns."), 135 | ) 136 | .group( 137 | ArgGroup::new("pattern") 138 | .args(["prefix", "suffix", "anywhere", "regex"]) 139 | .multiple(false) // Only one pattern type can be used 140 | .required(false), // Not required globally 141 | ) 142 | .arg( 143 | Arg::new("prefix") 144 | .short('p') 145 | .long("prefix") 146 | .action(ArgAction::SetTrue) 147 | .conflicts_with_all(["suffix", "anywhere", "regex"]) 148 | .help("Matches the pattern as a prefix of the address. [default]"), 149 | ) 150 | .arg( 151 | Arg::new("suffix") 152 | .short('s') 153 | .long("suffix") 154 | .action(ArgAction::SetTrue) 155 | .conflicts_with_all(["prefix", "anywhere", "regex"]) 156 | .help("Matches the pattern as a suffix of the address."), 157 | ) 158 | .arg( 159 | Arg::new("anywhere") 160 | .short('a') 161 | .long("anywhere") 162 | .action(ArgAction::SetTrue) 163 | .conflicts_with_all(["prefix", "suffix", "regex"]) 164 | .help("Matches the pattern anywhere in the address."), 165 | ) 166 | .arg( 167 | Arg::new("regex") 168 | .short('r') 169 | .long("regex") 170 | .action(ArgAction::SetTrue) 171 | .conflicts_with_all(["prefix", "suffix", "anywhere"]) 172 | .help("Matches addresses using a regex pattern, supporting advanced customization like anchors and wildcards."), 173 | ) 174 | .arg( 175 | Arg::new("threads") 176 | .short('t') 177 | .long("threads") 178 | .value_name("N") 179 | .default_value("8") 180 | .help("Sets the number of threads for address generation."), 181 | ) 182 | .arg( 183 | Arg::new("case-sensitive") 184 | .short('c') 185 | .long("case-sensitive") 186 | .action(ArgAction::SetTrue) 187 | .help("Enables case-sensitive matching, making patterns distinguish between uppercase and lowercase characters."), 188 | ) 189 | .arg( 190 | Arg::new("disable-fast-mode") 191 | .short('d') 192 | .long("disable-fast") 193 | .action(ArgAction::SetTrue) 194 | .help("Disables fast mode to allow longer patterns (5 for BTC and SOL, 16 for ETH), though it may increase search time."), 195 | ) 196 | } 197 | -------------------------------------------------------------------------------- /src/vanity_addr_generator/comp.rs: -------------------------------------------------------------------------------- 1 | use memx::{memeq, memmem}; 2 | 3 | /// Lookup table for ASCII case conversion 4 | static ASCII_LOWERCASE: [u8; 256] = [ 5 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 6 | 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 7 | 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 8 | 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 9 | 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 10 | 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 11 | 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 12 | 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 13 | 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 14 | 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 15 | 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 16 | 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 17 | 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 18 | ]; 19 | 20 | /// Performs a case-sensitive prefix match using the `memx` crate. 21 | /// 22 | /// # Arguments 23 | /// - `addr`: The target byte slice to check. 24 | /// - `pat`: The prefix byte slice to match against. 25 | /// 26 | /// # Returns 27 | /// - `true` if the beginning of `addr` matches `pat`. 28 | /// - `false` otherwise. 29 | #[inline(always)] 30 | pub fn eq_prefix_memx(addr: &[u8], pat: &[u8]) -> bool { 31 | if addr.len() < pat.len() { 32 | return false; 33 | } 34 | 35 | memeq(&addr[..pat.len()], pat) 36 | } 37 | 38 | /// Performs a case-sensitive suffix match using the `memx` crate. 39 | /// 40 | /// # Arguments 41 | /// - `addr`: The target byte slice to check. 42 | /// - `pat`: The suffix byte slice to match against. 43 | /// 44 | /// # Returns 45 | /// - `true` if the end of `addr` matches `pat`. 46 | /// - `false` otherwise. 47 | #[inline(always)] 48 | pub fn eq_suffix_memx(addr: &[u8], pat: &[u8]) -> bool { 49 | if addr.len() < pat.len() { 50 | return false; 51 | } 52 | 53 | let start = addr.len() - pat.len(); 54 | memeq(&addr[start..], pat) 55 | } 56 | 57 | /// Performs a case-sensitive substring match (anywhere match) using the `memx` crate. 58 | /// 59 | /// # Arguments 60 | /// - `addr`: The target byte slice to check. 61 | /// - `pat`: The byte slice to find within `addr`. 62 | /// 63 | /// # Returns 64 | /// - `true` if `pat` is found anywhere within `addr`. 65 | /// - `false` otherwise. 66 | #[inline(always)] 67 | pub fn contains_memx(addr: &[u8], pat: &[u8]) -> bool { 68 | memmem(addr, pat).is_some() 69 | } 70 | 71 | /// Simple, fast case-insensitive prefix match. 72 | /// 73 | /// # Arguments 74 | /// - `data`: The target byte slice to check. 75 | /// - `pattern`: The prefix byte slice to match against (should be lowercase). 76 | /// 77 | /// # Returns 78 | /// - `true` if the beginning of `data` matches `pattern` (case-insensitively). 79 | /// - `false` otherwise. 80 | #[inline(always)] 81 | pub fn eq_prefix_case_insensitive(data: &[u8], pattern: &[u8]) -> bool { 82 | let pattern_len = pattern.len(); 83 | if data.len() < pattern_len { 84 | return false; 85 | } 86 | 87 | if pattern_len == 0 { 88 | return true; 89 | } 90 | 91 | // Simple, efficient byte-by-byte comparison with lookup table 92 | for i in 0..pattern_len { 93 | if ASCII_LOWERCASE[data[i] as usize] != pattern[i] { 94 | return false; 95 | } 96 | } 97 | true 98 | } 99 | 100 | /// Simple, fast case-insensitive suffix match. 101 | /// 102 | /// # Arguments 103 | /// - `data`: The target byte slice to check. 104 | /// - `pattern`: The suffix byte slice to match against (should be lowercase). 105 | /// 106 | /// # Returns 107 | /// - `true` if the end of `data` matches `pattern` (case-insensitively). 108 | /// - `false` otherwise. 109 | #[inline(always)] 110 | pub fn eq_suffix_case_insensitive(data: &[u8], pattern: &[u8]) -> bool { 111 | let pattern_len = pattern.len(); 112 | if data.len() < pattern_len { 113 | return false; 114 | } 115 | 116 | if pattern_len == 0 { 117 | return true; 118 | } 119 | 120 | let start = data.len() - pattern_len; 121 | 122 | // Simple, efficient byte-by-byte comparison with lookup table 123 | for i in 0..pattern_len { 124 | if ASCII_LOWERCASE[data[start + i] as usize] != pattern[i] { 125 | return false; 126 | } 127 | } 128 | true 129 | } 130 | 131 | /// High-performance case-insensitive substring search with adaptive algorithm selection. 132 | /// Uses different algorithms based on pattern length for optimal performance. 133 | /// 134 | /// # Arguments 135 | /// - `data`: The target byte slice to check. 136 | /// - `pattern`: The byte slice to find within `data` (should be lowercase). 137 | /// 138 | /// # Returns 139 | /// - `true` if `pattern` is found anywhere within `data` (case-insensitively). 140 | /// - `false` otherwise. 141 | #[inline(always)] 142 | pub fn contains_case_insensitive(data: &[u8], pattern: &[u8]) -> bool { 143 | let data_len = data.len(); 144 | let pattern_len = pattern.len(); 145 | 146 | if data_len < pattern_len { 147 | return false; 148 | } 149 | 150 | if pattern_len == 0 { 151 | return true; 152 | } 153 | 154 | // Fast path for single character search - our biggest optimization win 155 | if pattern_len == 1 { 156 | let target = pattern[0]; 157 | return data 158 | .iter() 159 | .any(|&byte| ASCII_LOWERCASE[byte as usize] == target); 160 | } 161 | 162 | // For medium patterns (5-16 bytes), use optimized Boyer-Moore 163 | if pattern_len <= 16 { 164 | // Create bad character table 165 | let mut bad_char = [pattern_len; 256]; 166 | for (i, &byte) in pattern.iter().enumerate() { 167 | bad_char[byte as usize] = pattern_len - 1 - i; 168 | } 169 | 170 | let mut pos = 0; 171 | while pos <= data_len - pattern_len { 172 | let mut j = pattern_len; 173 | let mut found_mismatch = false; 174 | 175 | // Check from the end of the pattern 176 | while j > 0 { 177 | j -= 1; 178 | let data_char = data[pos + j]; 179 | let pattern_char = pattern[j]; 180 | let data_lower = ASCII_LOWERCASE[data_char as usize]; 181 | 182 | if data_lower != pattern_char { 183 | found_mismatch = true; 184 | break; 185 | } 186 | } 187 | 188 | if j == 0 && !found_mismatch { 189 | return true; // Match found 190 | } 191 | 192 | // Use bad character heuristic to skip positions 193 | let bad_char_skip = if pos + pattern_len - 1 < data_len { 194 | bad_char[ASCII_LOWERCASE[data[pos + pattern_len - 1] as usize] as usize] 195 | } else { 196 | 1 197 | }; 198 | pos += bad_char_skip.max(1); 199 | } 200 | 201 | return false; 202 | } 203 | 204 | // For very small (2-4 bytes) or very large (more than 16 bytes) patterns, use simple scan 205 | for start in 0..=(data_len - pattern_len) { 206 | let mut matches = true; 207 | for i in 0..pattern_len { 208 | let data_char = data[start + i]; 209 | let pattern_char = pattern[i]; 210 | let data_lower = ASCII_LOWERCASE[data_char as usize]; 211 | 212 | if data_lower != pattern_char { 213 | matches = false; 214 | break; 215 | } 216 | } 217 | if matches { 218 | return true; 219 | } 220 | } 221 | false 222 | } 223 | 224 | #[cfg(test)] 225 | mod tests { 226 | use super::*; 227 | 228 | #[test] 229 | fn test_case_insensitive_contains() { 230 | let address = "abcDEF123"; 231 | let pattern = "abc"; 232 | let address_bytes = address.as_bytes(); 233 | let pattern_bytes = pattern.as_bytes(); 234 | 235 | let result = contains_case_insensitive(address_bytes, pattern_bytes); 236 | let contains_result = address.to_lowercase().contains(pattern); 237 | 238 | assert_eq!(result, contains_result); 239 | assert!(result); 240 | } 241 | 242 | #[test] 243 | fn test_case_insensitive_contains_hex() { 244 | // Test with Ethereum-like hex addresses 245 | let address = "a1b2c3d4e5f6789abcdef"; 246 | let pattern = "abc"; 247 | let address_bytes = address.as_bytes(); 248 | let pattern_bytes = pattern.as_bytes(); 249 | 250 | let result = contains_case_insensitive(address_bytes, pattern_bytes); 251 | let contains_result = address.to_lowercase().contains(pattern); 252 | 253 | assert_eq!(result, contains_result); 254 | assert!(result); 255 | } 256 | 257 | #[test] 258 | fn test_case_insensitive_contains_no_match() { 259 | let address = "2091ab99a2e6bcd34293eb76aafb55dab7ae2de1"; 260 | let pattern = "abc"; 261 | let address_bytes = address.as_bytes(); 262 | let pattern_bytes = pattern.as_bytes(); 263 | 264 | let result = contains_case_insensitive(address_bytes, pattern_bytes); 265 | let contains_result = address.to_lowercase().contains(pattern); 266 | 267 | assert_eq!(result, contains_result); 268 | assert!(!result); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use btc_vanity::cli::cli; 2 | use btc_vanity::error::VanityError; 3 | use btc_vanity::file::{parse_input_file, write_output_file}; 4 | use btc_vanity::flags::{parse_cli, PatternsSource, VanityFlags}; 5 | use btc_vanity::keys_and_address::BitcoinKeyPair; 6 | use btc_vanity::vanity_addr_generator::chain::Chain; 7 | use btc_vanity::vanity_addr_generator::vanity_addr::{VanityAddr, VanityMode}; 8 | #[cfg(feature = "ethereum")] 9 | use btc_vanity::EthereumKeyPair; 10 | #[cfg(any(feature = "ethereum", feature = "solana"))] 11 | use btc_vanity::KeyPairGenerator; 12 | #[cfg(feature = "solana")] 13 | use btc_vanity::SolanaKeyPair; 14 | 15 | #[cfg(feature = "solana")] 16 | use bitcoin::hex::DisplayHex; 17 | use clap::error::ErrorKind; 18 | use std::path::Path; 19 | use std::process; 20 | use std::time::Instant; 21 | 22 | /// Generates and formats a vanity address depending on the chain. 23 | /// Returns a `Result` where the `Ok(String)` is the final formatted output. 24 | fn generate_vanity_address(pattern: &str, vanity_flags: &VanityFlags) -> Result { 25 | let start = Instant::now(); 26 | 27 | // "Inline" everything in each arm so we get a single `Result` 28 | let out = match vanity_flags.chain.unwrap_or(Chain::Bitcoin) { 29 | Chain::Bitcoin => { 30 | // 1) Generate the Bitcoin vanity 31 | let result: Result = 32 | match vanity_flags.vanity_mode.unwrap_or(VanityMode::Prefix) { 33 | VanityMode::Regex => { 34 | VanityAddr::generate_regex::(pattern, vanity_flags.threads) 35 | } 36 | _ => VanityAddr::generate::( 37 | pattern, 38 | vanity_flags.threads, 39 | vanity_flags.is_case_sensitive, 40 | !vanity_flags.disable_fast_mode, 41 | vanity_flags.vanity_mode.unwrap_or(VanityMode::Prefix), 42 | ), 43 | }; 44 | 45 | // 2) Format the result on success 46 | match result { 47 | Ok(res) => { 48 | let s = format!( 49 | "private_key (wif): {}\n\ 50 | public_key (compressed): {}\n\ 51 | address (compressed): {}\n\n", 52 | res.get_wif_private_key(), 53 | res.get_comp_public_key(), 54 | res.get_comp_address() 55 | ); 56 | Ok(s) 57 | } 58 | Err(e) => Err(e.to_string()), 59 | } 60 | } 61 | 62 | #[cfg(feature = "ethereum")] 63 | Chain::Ethereum => { 64 | // 1) Generate the Ethereum vanity 65 | let result: Result = 66 | match vanity_flags.vanity_mode.unwrap_or(VanityMode::Prefix) { 67 | VanityMode::Regex => { 68 | VanityAddr::generate_regex::(pattern, vanity_flags.threads) 69 | } 70 | _ => VanityAddr::generate::( 71 | pattern, 72 | vanity_flags.threads, 73 | vanity_flags.is_case_sensitive, 74 | !vanity_flags.disable_fast_mode, 75 | vanity_flags.vanity_mode.unwrap_or(VanityMode::Prefix), 76 | ), 77 | }; 78 | 79 | // 2) Format on success 80 | match result { 81 | Ok(res) => { 82 | let private_key_hex = res.get_private_key_as_hex(); 83 | let pub_key_hex = res.get_public_key_as_hex(); 84 | let address = res.get_address(); 85 | 86 | let s = format!( 87 | "private_key (hex): 0x{private_key_hex}\n\ 88 | public_key (hex): 0x{pub_key_hex}\n\ 89 | address: 0x{address}\n\n" 90 | ); 91 | Ok(s) 92 | } 93 | Err(e) => Err(e.to_string()), 94 | } 95 | } 96 | 97 | #[cfg(feature = "solana")] 98 | Chain::Solana => { 99 | // 1) Generate the Solana vanity 100 | let result: Result = 101 | match vanity_flags.vanity_mode.unwrap_or(VanityMode::Prefix) { 102 | VanityMode::Regex => { 103 | VanityAddr::generate_regex::(pattern, vanity_flags.threads) 104 | } 105 | _ => VanityAddr::generate::( 106 | pattern, 107 | vanity_flags.threads, 108 | vanity_flags.is_case_sensitive, 109 | !vanity_flags.disable_fast_mode, 110 | vanity_flags.vanity_mode.unwrap_or(VanityMode::Prefix), 111 | ), 112 | }; 113 | 114 | // 2) Format on success 115 | match result { 116 | Ok(res) => { 117 | // Keypair -> hex 118 | let keypair_bytes = res.keypair().to_bytes(); 119 | let private_key_hex = keypair_bytes.as_hex(); 120 | 121 | let address = res.get_address(); 122 | let s = format!( 123 | "private_key (hex): {private_key_hex}\n\ 124 | address: {address}\n\n" 125 | ); 126 | Ok(s) 127 | } 128 | Err(e) => Err(e.to_string()), 129 | } 130 | } 131 | // This arm handles missing features. 132 | #[cfg(not(feature = "ethereum"))] 133 | Chain::Ethereum => Err(VanityError::MissingFeatureEthereum.to_string()), 134 | #[cfg(not(feature = "solana"))] 135 | Chain::Solana => Err(VanityError::MissingFeatureSolana.to_string()), 136 | }; 137 | 138 | // If we made it here, we have out: Result 139 | match out { 140 | Ok(s) => { 141 | let seconds = start.elapsed().as_secs_f64(); 142 | println!("FOUND IN {seconds:.4} SECONDS!\n"); 143 | Ok(s) 144 | } 145 | Err(e) => Err(format!("Skipping because of error: {e}\n\n")), 146 | } 147 | } 148 | 149 | /// A single function to handle generating and printing/writing a vanity address 150 | /// for a given `pattern` + final `VanityFlags`. 151 | fn handle_item(pattern: &str, flags: &VanityFlags) { 152 | // 1) Some fancy text decorations 153 | // If `flags.vanity_mode` is None, we default to `VanityMode::Prefix`; up to you 154 | let vanity_mode = flags.vanity_mode.unwrap_or(VanityMode::Prefix); 155 | 156 | // For the "case_sensitive_str": 157 | let case_str = if flags.is_case_sensitive { 158 | "(case sensitive)" 159 | } else { 160 | "(case insensitive)" 161 | }; 162 | 163 | // Possibly you have a function like get_decoration_strings(...) 164 | // We can replicate it inline: 165 | let (vanity_mode_str, _case_sensitive_str) = match vanity_mode { 166 | VanityMode::Prefix => ("has the prefix", case_str), 167 | VanityMode::Suffix => ("has the suffix", case_str), 168 | VanityMode::Anywhere => ("has the string", case_str), 169 | VanityMode::Regex => ("matches regex", case_str), 170 | }; 171 | 172 | // 2) Print the "initial search message" 173 | // If chain is None, default to Bitcoin, or handle differently: 174 | let chain = flags.chain.unwrap_or(Chain::Bitcoin); 175 | println!( 176 | "Searching key pair for {:?} chain where the address {}: '{}' {} with {} threads.\n", 177 | chain, vanity_mode_str, pattern, case_str, flags.threads 178 | ); 179 | 180 | // 3) Build "buffer1" 181 | let buffer1 = format!("Key pair whose address {vanity_mode_str}: '{pattern}' {case_str}\n"); 182 | 183 | // 4) Actually generate the address 184 | let result = generate_vanity_address(pattern, flags); 185 | 186 | // 5) Format result or error, then handle output 187 | match result { 188 | Ok(buffer2) => { 189 | // If the user gave an output_file_name, write to that file. 190 | // Otherwise, print to stdout. 191 | if let Some(ref file_path) = flags.output_file_name { 192 | // example from your existing code: 193 | if let Err(e) = 194 | write_output_file(Path::new(file_path), &format!("{buffer1}\n{buffer2}")) 195 | { 196 | eprintln!("Failed to write output: {e}"); 197 | } 198 | } else { 199 | println!("{buffer2}"); 200 | } 201 | } 202 | Err(error_message) => { 203 | eprintln!("{error_message}"); 204 | } 205 | } 206 | } 207 | 208 | fn main() { 209 | let app = cli(); 210 | let (cli_flags, source) = match app.try_get_matches() { 211 | Ok(matches) => parse_cli(matches), 212 | Err(err) => { 213 | if err.kind() == ErrorKind::MissingRequiredArgument { 214 | eprintln!( 215 | "error: the following required arguments were not provided:\n --input-file OR \n" 216 | ); 217 | eprintln!("Usage: btc-vanity [OPTIONS] "); 218 | } else { 219 | eprintln!("{err}"); 220 | } 221 | process::exit(1); 222 | } 223 | }; 224 | 225 | // 4) Decide how we get our pattern(s): 226 | match source { 227 | PatternsSource::SingleString(pattern) => { 228 | // We only have one pattern. "Unify" is trivial because there's no file-based flags 229 | // So just use our CLI flags directly 230 | handle_item(&pattern, &cli_flags); 231 | } 232 | 233 | PatternsSource::InputFile(file_path) => { 234 | // The user specified an input file, so parse each line 235 | let items = match parse_input_file(&file_path) { 236 | Ok(lines) => lines, 237 | Err(e) => { 238 | eprintln!("Error reading file '{file_path}': {e}"); 239 | process::exit(1); 240 | } 241 | }; 242 | 243 | // For each line in the file, unify that line’s flags with the CLI’s flags 244 | for file_item in items { 245 | // Merge the line flags + CLI flags 246 | let final_flags = cli_flags.unify(&file_item.flags); 247 | // Then handle the pattern from that line 248 | handle_item(&file_item.pattern, &final_flags); 249 | } 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # btc-vanity 2 | #### v 2.1.2 3 | A blazingly fast vanity address generator written with the Rust programming language. Supporting Bitcoin (always included), Ethereum, and Solana (via optional features). 4 | 5 | With btc-vanity, you can generate wallets that have custom addresses with prefixes, suffixes, substrings, or even regex patterns. It's designed for **speed**, **flexibility**, and **security**. 6 | 7 | You can easily run the btc-vanity terminal application locally or use it as a library to create your vanity keypairs securely. 8 | 9 | ## Key Features 10 | 11 | **Multi-Chain Support**: 12 | * **Bitcoin:** Always included. 13 | * **Ethereum:** Enabled via the `ethereum` feature. 14 | * **Solana:** Enabled via the `solana` feature. 15 | 16 | **Advanced Customization**: Match prefixes, suffixes, substrings, or regex-based patterns with optional case insensitivity.
17 | **Blazingly Fast Performance**: Fully utilize your hardware with customizable thread counts.
18 | **Batch File Support**: Bulk generate addresses using input files with desired patterns.
19 | 20 | ## Installation 21 | 22 | ### CLI 23 | 24 | Install the binary using `cargo`: 25 | 26 | ```bash 27 | cargo install btc-vanity # Installs Bitcoin support only (default) 28 | cargo install btc-vanity --features ethereum # Installs with Ethereum support 29 | cargo install btc-vanity --features solana # Installs with Solana support 30 | cargo install btc-vanity --features all # Installs with all features (Bitcoin, Ethereum, Solana) 31 | ``` 32 | 33 | **Important:** You *must* use the `--features` flag to enable Ethereum and/or Solana support. If you omit the flag, you'll only get Bitcoin functionality. 34 | 35 | ### Library 36 | 37 | Include `btc-vanity` in your `Cargo.toml`: 38 | 39 | ```toml 40 | [dependencies] 41 | btc-vanity = "2.1.0" # Bitcoin support only 42 | 43 | # For Ethereum support: 44 | # btc-vanity = { version = "2.1.0", features = ["ethereum"] } 45 | 46 | # For Solana support: 47 | # btc-vanity = { version = "2.1.0", features = ["solana"] } 48 | 49 | # For all features: 50 | # btc-vanity = { version = "2.1.0", features = ["all"] } 51 | ``` 52 | 53 | Crate on [crates.io](https://crates.io/crates/btc-vanity)
54 | Documentation on [docs.rs](https://docs.rs/btc-vanity/latest/btc_vanity/index.html) 55 | 56 | ## CLI Usage 57 | 58 | ### Basic CLI Syntax 59 | 60 | The CLI tool provides several options to customize your address generation: 61 | 62 | ```shell 63 | $ btc-vanity [OPTIONS] 64 | ``` 65 | 66 | #### Blockchain Selection 67 | 68 | * `--btc`: Generates Bitcoin keypairs and addresses. (Always available) 69 | * `--eth`: Generates Ethereum keypairs and addresses. (**Requires the `ethereum` feature**) 70 | * `--sol`: Generates Solana keypairs and addresses. (**Requires the `solana` feature**) 71 | 72 | #### General Options 73 | 74 | * `-i, --input-file `: Reads patterns and flags from the specified file (one pattern per line). 75 | * `-o, --output-file `: Saves generated wallet details to the specified file. 76 | * `-t, --threads `: Sets the number of threads for address generation. 77 | * `-f, --force-flags`: Forces CLI flags to override flags in the input file. 78 | * `-d, --disable-fast`: Disables fast mode (allows longer patterns). 79 | 80 | #### Matching Options 81 | 82 | * `-p, --prefix`: Matches the pattern as a prefix (default). 83 | * `-s, --suffix`: Matches the pattern as a suffix. 84 | * `-a, --anywhere`: Matches the pattern anywhere in the address. 85 | * `-r, --regex `: Matches addresses using a regular expression. 86 | * `-c, --case-sensitive`: Enables case-sensitive matching. 87 | 88 | ### Bitcoin CLI Examples 89 | 90 | These examples *always* work, as Bitcoin support is always included. 91 | 92 | Generate a Bitcoin address with prefix `1Emiv` (case-insensitive): 93 | 94 | ```shell 95 | $ btc-vanity Emiv 96 | ``` 97 | 98 | Generate a Bitcoin address containing the substring `test` (case-sensitive): 99 | 100 | ```shell 101 | $ btc-vanity -a -c test 102 | ``` 103 | 104 | Generate a Bitcoin address using a regex pattern `^1E.*T$`: 105 | 106 | ```shell 107 | $ btc-vanity -r "^1E.*T$" 108 | ``` 109 | 110 | Generate multiple Bitcoin addresses and save to `wallets.txt`: 111 | 112 | > [!NOTE] 113 | > -f flag will override any pattern flags inside the `input-file.txt`. 114 | > For example if there line `emiv -s --eth` will become `emiv -p --btc -c`. 115 | > The resulting wallet will be printed in `wallets.txt`. 116 | 117 | ```shell 118 | $ btc-vanity -f --btc -p -c -i input-file.txt -o wallets.txt 119 | ``` 120 | ### Ethereum and Solana CLI Examples 121 | **Important:** These commands *require* that you installed `btc-vanity` with the appropriate features (see Installation section above). 122 | Generate an Ethereum address starting with 0xdead with 8 threads: 123 | ```shell 124 | # Ensure you installed with: cargo install btc-vanity --features ethereum 125 | $ btc-vanity --eth -t 8 dead 126 | ``` 127 | 128 | Generate a Solana address ending with 123: 129 | ```shell 130 | # Ensure you installed with: cargo install btc-vanity --features solana 131 | $ btc-vanity --sol -s 123 132 | ``` 133 | 134 | ## Library Usage 135 | 136 | Here are some usage examples of `btc-vanity` as a library. 137 | 138 | ### Generate a Bitcoin Vanity Address 139 | Find a Bitcoin address that contains the substring `emiv` (case-insensitive) using 16 threads: 140 | ```rust 141 | use btc_vanity::{BitcoinKeyPair, VanityAddr, VanityMode}; 142 | 143 | let vanity_address: BitcoinKeyPair = VanityAddr::generate( 144 | "emiv", // Desired substring 145 | 16, // Number of threads 146 | false, // Case-insensitive 147 | true, // Enable fast mode 148 | VanityMode::Anywhere // Match substring anywhere in the address 149 | ).unwrap(); 150 | 151 | println!("Vanity address:\n\ 152 | private_key (WIF): {}\n\ 153 | public_key (compressed): {}\n\ 154 | address (compressed): {}\n", 155 | vanity_address.get_wif_private_key(), 156 | vanity_address.get_comp_public_key(), 157 | vanity_address.get_comp_address()); 158 | ``` 159 | 160 | #### Generate an Ethereum Vanity Address 161 | **Requires the `ethereum` feature.** 162 | Match an Ethereum address with the prefix `0xdead` using 8 threads: 163 | ```rust 164 | # #[cfg(feature = "ethereum")] // Important: Only compile this example with the feature! 165 | # { 166 | use btc_vanity::{EthereumKeyPair, VanityAddr, VanityMode}; 167 | 168 | let vanity_address: EthereumKeyPair = VanityAddr::generate( 169 | "dead", // Desired prefix 170 | 8, // Number of threads 171 | false, // Case-insensitive (Case sensitivity not supported on ETH generation) 172 | true, // Enable fast mode 173 | VanityMode::Prefix // Match substring at the start 174 | ).unwrap(); 175 | 176 | println!("Ethereum vanity address:\n\ 177 | private_key (hex): {}\n\ 178 | public_key (hex): {}\n\ 179 | address (hex): {}\n", 180 | vanity_address.get_private_key_as_hex(), 181 | vanity_address.get_public_key_as_hex(), 182 | vanity_address.get_address_with_prefix()); 183 | # } 184 | ``` 185 | 186 | #### Generate a Solana Vanity Address 187 | **Requires the `solana` feature.** 188 | 189 | Create a Solana address with `meow` anywhere in the address (case-sensitive) using 4 threads: 190 | 191 | ```rust 192 | # #[cfg(feature = "solana")] // Important: Only compile this example with the feature! 193 | # { 194 | use btc_vanity::{SolanaKeyPair, VanityAddr, VanityMode}; // Removed KeyPairGenerator 195 | 196 | let vanity_address: SolanaKeyPair = VanityAddr::generate( 197 | "meow", // Desired substring 198 | 4, // Number of threads 199 | true, // Case-sensitive 200 | true, // Enable fast mode 201 | VanityMode::Anywhere // Match substring anywhere in the address 202 | ).unwrap(); 203 | 204 | println!("Solana vanity address:\n\ 205 | private_key (Base58): {}\n\ 206 | public_key (Base58): {}\n\ 207 | address (Base58): {}\n", 208 | vanity_address.get_private_key_as_base58(), 209 | vanity_address.get_public_key_as_base58(), 210 | vanity_address.get_address()); 211 | 212 | # } 213 | 214 | ``` 215 | #### Regex Matching for Bitcoin Addresses 216 | Find a Bitcoin address that matches a regex pattern `^1E.ET.*T$` with using 12 threads: 217 | 218 | ```rust 219 | use btc_vanity::{BitcoinKeyPair, VanityAddr}; 220 | 221 | let vanity_address = VanityAddr::generate_regex::( 222 | "^1E.*ET.*T$", // The regex pattern 223 | 12 // Number of threads 224 | ).unwrap(); 225 | 226 | println!("Bitcoin regex-matched vanity address:\n\ 227 | private_key (WIF): {}\n\ 228 | public_key (compressed): {}\n\ 229 | address (compressed): {}\n", 230 | vanity_address.get_wif_private_key(), 231 | vanity_address.get_comp_public_key(), 232 | vanity_address.get_comp_address()); 233 | ``` 234 | 235 | ## Contributing 236 | 237 | Contributions are welcome! If you'd like to improve btc-vanity or add support for additional chains, feel free to open an issue or submit a pull request on GitHub. 238 | 239 | ## Disclaimer 240 | 241 | **USE WITH CAUTION AND UNDERSTANDING** 242 | 243 | btc-vanity is a tool designed to assist users in generating customized vanity Bitcoin, Solana, and Ethereum addresses using the Rust programming language. While btc-vanity aims to provide a secure and efficient method for generating vanity addresses, it is essential to exercise caution and follow the best security practices. 244 | 245 | 1. **Security Awareness**: Generating and using vanity addresses involves the creation of private keys and public addresses. Private keys grant control over the associated funds. It is crucial to understand the risks involved in managing private keys and to never share them with anyone. Keep your private keys stored securely and never expose them to potential threats. 246 | 247 | 2. **Risk of Loss**: Improper use of btc-vanity, mishandling of private keys, or failure to follow security guidelines may result in the loss of Bitcoin, Solana, or Ethereum funds. Always double-check the addresses generated and verify their accuracy before using them for transactions. 248 | 249 | 3. **Verification**: Before utilizing any vanity address generated by btc-vanity, thoroughly verify the integrity of the software and the generated addresses. Only use versions of btc-vanity obtained from reputable sources, such as the official crates.io page. 250 | 251 | 4. **Backup and Recovery**: Maintain proper backups of your private keys and any relevant data. In the event of device failure, loss, or corruption, having secure backups will help prevent irreversible loss of funds. 252 | 253 | 5. **Use at Your Own Risk**: The btc-vanity software is provided "as is," without any warranties or guarantees. The author(s) and contributors of btc-vanity shall not be held responsible for any direct or indirect damages, losses, or liabilities resulting from the use or misuse of this software. 254 | 255 | 6. **Educational Purposes**: btc-vanity is intended for educational and personal use. It is your responsibility to ensure compliance with any legal, regulatory, or tax requirements in your jurisdiction related to Bitcoin, Solana, Ethereum, and cryptocurrency usage. 256 | 257 | By using btc-vanity, you acknowledge and accept the risks associated with generating vanity addresses and handling private keys. It is your responsibility to exercise diligence, follow security best practices, and be aware of potential risks. 258 | 259 | Remember, the security of your cryptocurrency holdings is paramount. Always prioritize the safety and security of your assets. 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | //! # File Reading and Writing Module 2 | //! 3 | //! This module provides functionality for: 4 | //! - Parsing input files containing vanity patterns and flags. 5 | //! - Writing generated vanity wallet details to output files. 6 | 7 | use std::fs::{self, OpenOptions}; 8 | use std::io::{self, Write}; 9 | use std::path::Path; 10 | 11 | use crate::error::VanityError; 12 | use crate::flags::VanityFlags; 13 | use crate::vanity_addr_generator::chain::Chain; 14 | use crate::VanityMode; 15 | 16 | /// Represents a single line item from an input file, 17 | /// containing a vanity pattern and associated flags. 18 | #[derive(Debug, Clone)] 19 | pub struct FileLineItem { 20 | /// The vanity pattern to match (e.g., "emiv"). 21 | pub pattern: String, 22 | /// The associated `VanityFlags` configuration. 23 | pub flags: VanityFlags, 24 | } 25 | 26 | /// Parses a single line from an input file into a [FileLineItem]. 27 | /// 28 | /// # Arguments 29 | /// - `line`: A string slice representing a line from the input file. 30 | /// 31 | /// # Returns 32 | /// - `Some(FileLineItem)` if the line is valid and parsable. 33 | /// - `None` if the line is empty or starts with a comment (`#`). 34 | fn parse_line(line: &str) -> Option { 35 | if line.starts_with('#') { 36 | return None; 37 | } 38 | 39 | let mut tokens = line.split_whitespace(); 40 | 41 | // The first token is the pattern 42 | let pattern = tokens.next()?.to_string(); 43 | // The rest are "flags" 44 | let flags_vec: Vec<&str> = tokens.collect(); 45 | 46 | // If no flags, then everything is default 47 | if flags_vec.is_empty() { 48 | return Some(FileLineItem { 49 | pattern, 50 | flags: VanityFlags { 51 | force_flags: false, 52 | is_case_sensitive: false, 53 | disable_fast_mode: false, 54 | output_file_name: None, 55 | vanity_mode: None, 56 | chain: None, 57 | threads: 16, 58 | }, 59 | }); 60 | } 61 | 62 | let is_case_sensitive = flags_vec.contains(&"-c") || flags_vec.contains(&"--case-sensitive"); 63 | let disable_fast_mode = flags_vec.contains(&"-d") || flags_vec.contains(&"--disable-fast"); 64 | 65 | // chain 66 | let chain = if flags_vec.contains(&"--eth") { 67 | Some(Chain::Ethereum) 68 | } else if flags_vec.contains(&"--sol") { 69 | Some(Chain::Solana) 70 | } else if flags_vec.contains(&"--btc") { 71 | Some(Chain::Bitcoin) 72 | } else { 73 | None 74 | }; 75 | 76 | // vanity mode 77 | let vanity_mode = if flags_vec.contains(&"-r") || flags_vec.contains(&"--regex") { 78 | Some(VanityMode::Regex) 79 | } else if flags_vec.contains(&"-a") || flags_vec.contains(&"--anywhere") { 80 | Some(VanityMode::Anywhere) 81 | } else if flags_vec.contains(&"-s") || flags_vec.contains(&"--suffix") { 82 | Some(VanityMode::Suffix) 83 | } else if flags_vec.contains(&"-p") || flags_vec.contains(&"--prefix") { 84 | Some(VanityMode::Prefix) 85 | } else { 86 | None 87 | }; 88 | 89 | // output file name: look for `-o` or `--output-file` plus the next token 90 | let mut output_file_name: Option = None; 91 | for (i, &flag) in flags_vec.iter().enumerate() { 92 | if flag == "-o" || flag == "--output-file" { 93 | if let Some(next_flag) = flags_vec.get(i + 1) { 94 | output_file_name = Some(next_flag.to_string()); 95 | } 96 | } 97 | } 98 | 99 | Some(FileLineItem { 100 | pattern, 101 | flags: VanityFlags { 102 | force_flags: false, 103 | is_case_sensitive, 104 | disable_fast_mode, 105 | output_file_name, 106 | vanity_mode, 107 | chain, 108 | threads: 0, 109 | }, 110 | }) 111 | } 112 | 113 | /// Reads and parses an input file, converting each line into a [FileLineItem]. 114 | /// 115 | /// # Arguments 116 | /// - `path`: A string slice representing the file path. 117 | /// 118 | /// # Returns 119 | /// - `Ok(Vec)`: A vector of parsed [FileLineItem] objects. 120 | /// - `Err(VanityError)`: An error if the file cannot be read or parsed. 121 | /// 122 | /// # Errors 123 | /// - Returns `VanityError::FileError` if the file cannot be read. 124 | pub fn parse_input_file(path: &str) -> Result, VanityError> { 125 | let contents = fs::read_to_string(path)?; 126 | let mut items = Vec::new(); 127 | 128 | for line in contents.lines() { 129 | let line = line.trim(); 130 | if line.is_empty() || line.starts_with('#') { 131 | continue; 132 | } 133 | 134 | if let Some(item) = parse_line(line) { 135 | items.push(item); 136 | } 137 | } 138 | Ok(items) 139 | } 140 | 141 | /// Writes a string buffer to an output file. If the file does not exist, it will be created. 142 | /// 143 | /// # Arguments 144 | /// - `output_path`: The path to the output file. 145 | /// - `buffer`: The content to write to the file. 146 | /// 147 | /// # Returns 148 | /// - `Ok(())` on successful write. 149 | /// - `Err(VanityError)` if the operation fails. 150 | /// 151 | /// # Errors 152 | /// - Returns `VanityError::FileError` if the operation fails, 153 | /// such as due to invalid input or a write failure. 154 | pub fn write_output_file(output_path: &Path, buffer: &str) -> Result<(), VanityError> { 155 | // Attempt to open the file in append mode 156 | let file_result = OpenOptions::new() 157 | .append(true) 158 | .create(true) 159 | .open(output_path); 160 | let mut file = match file_result { 161 | Ok(file) => file, 162 | Err(e) => { 163 | return Err(VanityError::FileError(io::Error::other(format!( 164 | "Failed to open or create file: {e}" 165 | )))) 166 | } 167 | }; 168 | 169 | // Write the buffer to the file 170 | if let Err(e) = file.write_all(buffer.as_bytes()) { 171 | return Err(VanityError::FileError(io::Error::new( 172 | io::ErrorKind::WriteZero, 173 | format!("Failed to write to file: {e}"), 174 | ))); 175 | } 176 | 177 | Ok(()) 178 | } 179 | 180 | #[cfg(test)] 181 | mod tests { 182 | use super::{parse_input_file, parse_line}; 183 | use crate::VanityMode; 184 | 185 | #[test] 186 | fn test_parse_line_with_valid_flags() { 187 | let line = "test -p -c"; 188 | let item = parse_line(line).expect("Failed to parse valid line"); 189 | 190 | assert_eq!(item.pattern, "test"); 191 | assert_eq!(item.flags.vanity_mode, Some(VanityMode::Prefix)); 192 | assert!(item.flags.is_case_sensitive); 193 | } 194 | 195 | #[test] 196 | fn test_parse_line_with_invalid_flags() { 197 | let line = "test -z"; 198 | let item = parse_line(line).expect("Failed to parse line with invalid flags"); 199 | 200 | assert_eq!(item.pattern, "test"); 201 | assert!(item.flags.vanity_mode.is_none()); 202 | } 203 | 204 | #[test] 205 | fn test_parse_line_with_no_flags() { 206 | let line = "test"; 207 | let item = parse_line(line).expect("Failed to parse line without flags"); 208 | 209 | assert_eq!(item.pattern, "test"); 210 | assert!(item.flags.vanity_mode.is_none()); 211 | assert!(!item.flags.is_case_sensitive); 212 | assert!(!item.flags.disable_fast_mode); 213 | } 214 | 215 | #[test] 216 | fn test_parse_line_with_output_file_flag() { 217 | let line = "test -p -o output.txt"; 218 | let item = parse_line(line).expect("Failed to parse line with output file flag"); 219 | 220 | assert_eq!(item.pattern, "test"); 221 | assert_eq!(item.flags.vanity_mode, Some(VanityMode::Prefix)); 222 | assert_eq!(item.flags.output_file_name, Some("output.txt".to_string())); 223 | } 224 | 225 | #[test] 226 | fn test_parse_empty_line() { 227 | let line = ""; 228 | let item = parse_line(line); 229 | 230 | assert!(item.is_none(), "Empty line should not be parsed"); 231 | } 232 | 233 | #[test] 234 | fn test_parse_comment_line() { 235 | let line = "# This is a comment"; 236 | let item = parse_line(line); 237 | 238 | assert!(item.is_none(), "Comment line should not be parsed"); 239 | } 240 | 241 | #[test] 242 | fn test_parse_input_file_with_valid_lines() { 243 | // Mock file content 244 | let file_content = "test -p\nexample -s\nanywhere -a"; 245 | let file_path = "test_valid_input.txt"; 246 | std::fs::write(file_path, file_content).expect("Failed to create mock input file"); 247 | 248 | // Parse the file 249 | let result = parse_input_file(file_path); 250 | assert!(result.is_ok(), "Failed to parse valid input file"); 251 | 252 | let items = result.unwrap(); 253 | assert_eq!(items.len(), 3); 254 | 255 | assert_eq!(items[0].pattern, "test"); 256 | assert_eq!(items[0].flags.vanity_mode, Some(VanityMode::Prefix)); 257 | 258 | assert_eq!(items[1].pattern, "example"); 259 | assert_eq!(items[1].flags.vanity_mode, Some(VanityMode::Suffix)); 260 | 261 | assert_eq!(items[2].pattern, "anywhere"); 262 | assert_eq!(items[2].flags.vanity_mode, Some(VanityMode::Anywhere)); 263 | 264 | // Clean up 265 | std::fs::remove_file(file_path).expect("Failed to delete mock input file"); 266 | } 267 | 268 | #[test] 269 | fn test_parse_input_file_with_invalid_lines() { 270 | // Mock file content 271 | let file_content = "test -z\nexample --invalid"; 272 | let file_path = "test_invalid_input.txt"; 273 | std::fs::write(file_path, file_content).expect("Failed to create mock input file"); 274 | 275 | // Parse the file 276 | let result = parse_input_file(file_path); 277 | assert!(result.is_ok(), "Failed to parse file with invalid lines"); 278 | 279 | let items = result.unwrap(); 280 | assert_eq!(items.len(), 2); 281 | 282 | assert_eq!(items[0].pattern, "test"); 283 | assert!(items[0].flags.vanity_mode.is_none()); 284 | 285 | assert_eq!(items[1].pattern, "example"); 286 | assert!(items[1].flags.vanity_mode.is_none()); 287 | 288 | // Clean up 289 | std::fs::remove_file(file_path).expect("Failed to delete mock input file"); 290 | } 291 | 292 | #[test] 293 | fn test_parse_input_file_with_empty_lines() { 294 | // Mock file content 295 | let file_content = "\n\n"; 296 | let file_path = "test_empty_lines.txt"; 297 | std::fs::write(file_path, file_content).expect("Failed to create mock input file"); 298 | 299 | // Parse the file 300 | let result = parse_input_file(file_path); 301 | assert!(result.is_ok(), "Failed to parse file with empty lines"); 302 | 303 | let items = result.unwrap(); 304 | assert!( 305 | items.is_empty(), 306 | "Parsed items should be empty for a file with only empty lines" 307 | ); 308 | 309 | // Clean up 310 | std::fs::remove_file(file_path).expect("Failed to delete mock input file"); 311 | } 312 | 313 | #[test] 314 | fn test_parse_input_file_with_invalid_path() { 315 | // Non-existent file path 316 | let file_path = "non_existent_file.txt"; 317 | 318 | // Parse the file 319 | let result = parse_input_file(file_path); 320 | assert!( 321 | result.is_err(), 322 | "Parsing a non-existent file should return an error" 323 | ); 324 | 325 | if let Err(err) = result { 326 | assert!( 327 | err.to_string().contains("No such file"), 328 | "Unexpected error message: {}", 329 | err 330 | ); 331 | } 332 | } 333 | 334 | #[test] 335 | fn test_parse_input_file_with_missing_flags() { 336 | // Mock file content 337 | let file_content = "test\nexample\nmissing_flags"; 338 | let file_path = "test_missing_flags.txt"; 339 | std::fs::write(file_path, file_content).expect("Failed to create mock input file"); 340 | 341 | // Parse the file 342 | let result = parse_input_file(file_path); 343 | assert!(result.is_ok(), "Failed to parse file with missing flags"); 344 | 345 | let items = result.unwrap(); 346 | assert_eq!(items.len(), 3); 347 | 348 | assert_eq!(items[0].pattern, "test"); 349 | assert!(items[0].flags.vanity_mode.is_none()); 350 | 351 | assert_eq!(items[1].pattern, "example"); 352 | assert!(items[1].flags.vanity_mode.is_none()); 353 | 354 | assert_eq!(items[2].pattern, "missing_flags"); 355 | assert!(items[2].flags.vanity_mode.is_none()); 356 | 357 | // Clean up 358 | std::fs::remove_file(file_path).expect("Failed to delete mock input file"); 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by dist: https://github.com/astral-sh/cargo-dist 2 | # 3 | # Copyright 2022-2024, axodotdev 4 | # Copyright 2025 Astral Software Inc. 5 | # SPDX-License-Identifier: MIT or Apache-2.0 6 | # 7 | # CI that: 8 | # 9 | # * checks for a Git Tag that looks like a release 10 | # * builds artifacts with dist (archives, installers, hashes) 11 | # * uploads those artifacts to temporary workflow zip 12 | # * on success, uploads the artifacts to a GitHub Release 13 | # 14 | # Note that the GitHub Release will be created with a generated 15 | # title/body based on your changelogs. 16 | 17 | name: Release 18 | permissions: 19 | "contents": "write" 20 | 21 | # This task will run whenever you push a git tag that looks like a version 22 | # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. 23 | # Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where 24 | # PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION 25 | # must be a Cargo-style SemVer Version (must have at least major.minor.patch). 26 | # 27 | # If PACKAGE_NAME is specified, then the announcement will be for that 28 | # package (erroring out if it doesn't have the given version or isn't dist-able). 29 | # 30 | # If PACKAGE_NAME isn't specified, then the announcement will be for all 31 | # (dist-able) packages in the workspace with that version (this mode is 32 | # intended for workspaces with only one dist-able package, or with all dist-able 33 | # packages versioned/released in lockstep). 34 | # 35 | # If you push multiple tags at once, separate instances of this workflow will 36 | # spin up, creating an independent announcement for each one. However, GitHub 37 | # will hard limit this to 3 tags per commit, as it will assume more tags is a 38 | # mistake. 39 | # 40 | # If there's a prerelease-style suffix to the version, then the release(s) 41 | # will be marked as a prerelease. 42 | on: 43 | pull_request: 44 | push: 45 | tags: 46 | - '**[0-9]+.[0-9]+.[0-9]+*' 47 | 48 | jobs: 49 | # Run 'dist plan' (or host) to determine what tasks we need to do 50 | plan: 51 | runs-on: "ubuntu-22.04" 52 | outputs: 53 | val: ${{ steps.plan.outputs.manifest }} 54 | tag: ${{ !github.event.pull_request && github.ref_name || '' }} 55 | tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} 56 | publishing: ${{ !github.event.pull_request }} 57 | env: 58 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | steps: 60 | - uses: actions/checkout@v4 61 | with: 62 | persist-credentials: false 63 | submodules: recursive 64 | - name: Install dist 65 | # we specify bash to get pipefail; it guards against the `curl` command 66 | # failing. otherwise `sh` won't catch that `curl` returned non-0 67 | shell: bash 68 | run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.7-prerelease.1/cargo-dist-installer.sh | sh" 69 | - name: Cache dist 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: cargo-dist-cache 73 | path: ~/.cargo/bin/dist 74 | # sure would be cool if github gave us proper conditionals... 75 | # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible 76 | # functionality based on whether this is a pull_request, and whether it's from a fork. 77 | # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* 78 | # but also really annoying to build CI around when it needs secrets to work right.) 79 | - id: plan 80 | run: | 81 | dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json 82 | echo "dist ran successfully" 83 | cat plan-dist-manifest.json 84 | echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" 85 | - name: "Upload dist-manifest.json" 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: artifacts-plan-dist-manifest 89 | path: plan-dist-manifest.json 90 | 91 | # Build and packages all the platform-specific things 92 | build-local-artifacts: 93 | name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) 94 | # Let the initial task tell us to not run (currently very blunt) 95 | needs: 96 | - plan 97 | if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} 98 | strategy: 99 | fail-fast: false 100 | # Target platforms/runners are computed by dist in create-release. 101 | # Each member of the matrix has the following arguments: 102 | # 103 | # - runner: the github runner 104 | # - dist-args: cli flags to pass to dist 105 | # - install-dist: expression to run to install dist on the runner 106 | # 107 | # Typically there will be: 108 | # - 1 "global" task that builds universal installers 109 | # - N "local" tasks that build each platform's binaries and platform-specific installers 110 | matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} 111 | runs-on: ${{ matrix.runner }} 112 | container: ${{ matrix.container && matrix.container.image || null }} 113 | env: 114 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 115 | BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json 116 | steps: 117 | - name: enable windows longpaths 118 | run: | 119 | git config --global core.longpaths true 120 | - uses: actions/checkout@v4 121 | with: 122 | persist-credentials: false 123 | submodules: recursive 124 | - name: Install Rust non-interactively if not already installed 125 | if: ${{ matrix.container }} 126 | run: | 127 | if ! command -v cargo > /dev/null 2>&1; then 128 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 129 | echo "$HOME/.cargo/bin" >> $GITHUB_PATH 130 | fi 131 | - name: Install dist 132 | run: ${{ matrix.install_dist.run }} 133 | # Get the dist-manifest 134 | - name: Fetch local artifacts 135 | uses: actions/download-artifact@v4 136 | with: 137 | pattern: artifacts-* 138 | path: target/distrib/ 139 | merge-multiple: true 140 | - name: Install dependencies 141 | run: | 142 | ${{ matrix.packages_install }} 143 | - name: Build artifacts 144 | run: | 145 | # Actually do builds and make zips and whatnot 146 | dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json 147 | echo "dist ran successfully" 148 | - id: cargo-dist 149 | name: Post-build 150 | # We force bash here just because github makes it really hard to get values up 151 | # to "real" actions without writing to env-vars, and writing to env-vars has 152 | # inconsistent syntax between shell and powershell. 153 | shell: bash 154 | run: | 155 | # Parse out what we just built and upload it to scratch storage 156 | echo "paths<> "$GITHUB_OUTPUT" 157 | dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" 158 | echo "EOF" >> "$GITHUB_OUTPUT" 159 | 160 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 161 | - name: "Upload artifacts" 162 | uses: actions/upload-artifact@v4 163 | with: 164 | name: artifacts-build-local-${{ join(matrix.targets, '_') }} 165 | path: | 166 | ${{ steps.cargo-dist.outputs.paths }} 167 | ${{ env.BUILD_MANIFEST_NAME }} 168 | 169 | # Build and package all the platform-agnostic(ish) things 170 | build-global-artifacts: 171 | needs: 172 | - plan 173 | - build-local-artifacts 174 | runs-on: "ubuntu-22.04" 175 | env: 176 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 177 | BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json 178 | steps: 179 | - uses: actions/checkout@v4 180 | with: 181 | persist-credentials: false 182 | submodules: recursive 183 | - name: Install cached dist 184 | uses: actions/download-artifact@v4 185 | with: 186 | name: cargo-dist-cache 187 | path: ~/.cargo/bin/ 188 | - run: chmod +x ~/.cargo/bin/dist 189 | # Get all the local artifacts for the global tasks to use (for e.g. checksums) 190 | - name: Fetch local artifacts 191 | uses: actions/download-artifact@v4 192 | with: 193 | pattern: artifacts-* 194 | path: target/distrib/ 195 | merge-multiple: true 196 | - id: cargo-dist 197 | shell: bash 198 | run: | 199 | dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json 200 | echo "dist ran successfully" 201 | 202 | # Parse out what we just built and upload it to scratch storage 203 | echo "paths<> "$GITHUB_OUTPUT" 204 | jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" 205 | echo "EOF" >> "$GITHUB_OUTPUT" 206 | 207 | cp dist-manifest.json "$BUILD_MANIFEST_NAME" 208 | - name: "Upload artifacts" 209 | uses: actions/upload-artifact@v4 210 | with: 211 | name: artifacts-build-global 212 | path: | 213 | ${{ steps.cargo-dist.outputs.paths }} 214 | ${{ env.BUILD_MANIFEST_NAME }} 215 | # Determines if we should publish/announce 216 | host: 217 | needs: 218 | - plan 219 | - build-local-artifacts 220 | - build-global-artifacts 221 | # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) 222 | if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} 223 | env: 224 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 225 | runs-on: "ubuntu-22.04" 226 | outputs: 227 | val: ${{ steps.host.outputs.manifest }} 228 | steps: 229 | - uses: actions/checkout@v4 230 | with: 231 | persist-credentials: false 232 | submodules: recursive 233 | - name: Install cached dist 234 | uses: actions/download-artifact@v4 235 | with: 236 | name: cargo-dist-cache 237 | path: ~/.cargo/bin/ 238 | - run: chmod +x ~/.cargo/bin/dist 239 | # Fetch artifacts from scratch-storage 240 | - name: Fetch artifacts 241 | uses: actions/download-artifact@v4 242 | with: 243 | pattern: artifacts-* 244 | path: target/distrib/ 245 | merge-multiple: true 246 | - id: host 247 | shell: bash 248 | run: | 249 | dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json 250 | echo "artifacts uploaded and released successfully" 251 | cat dist-manifest.json 252 | echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" 253 | - name: "Upload dist-manifest.json" 254 | uses: actions/upload-artifact@v4 255 | with: 256 | # Overwrite the previous copy 257 | name: artifacts-dist-manifest 258 | path: dist-manifest.json 259 | # Create a GitHub Release while uploading all files to it 260 | - name: "Download GitHub Artifacts" 261 | uses: actions/download-artifact@v4 262 | with: 263 | pattern: artifacts-* 264 | path: artifacts 265 | merge-multiple: true 266 | - name: Cleanup 267 | run: | 268 | # Remove the granular manifests 269 | rm -f artifacts/*-dist-manifest.json 270 | - name: Create GitHub Release 271 | env: 272 | PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" 273 | ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" 274 | ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" 275 | RELEASE_COMMIT: "${{ github.sha }}" 276 | run: | 277 | # Write and read notes from a file to avoid quoting breaking things 278 | echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt 279 | 280 | gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* 281 | 282 | announce: 283 | needs: 284 | - plan 285 | - host 286 | # use "always() && ..." to allow us to wait for all publish jobs while 287 | # still allowing individual publish jobs to skip themselves (for prereleases). 288 | # "host" however must run to completion, no skipping allowed! 289 | if: ${{ always() && needs.host.result == 'success' }} 290 | runs-on: "ubuntu-22.04" 291 | env: 292 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 293 | steps: 294 | - uses: actions/checkout@v4 295 | with: 296 | persist-credentials: false 297 | submodules: recursive 298 | -------------------------------------------------------------------------------- /src/vanity_addr_generator/chain.rs: -------------------------------------------------------------------------------- 1 | //! # Vanity Chain Module 2 | //! 3 | //! This module defines the [VanityChain] trait, which provides chain-specific behavior for 4 | //! generating vanity addresses. It supports `Bitcoin`, `Ethereum`, and `Solana` chains and handles: 5 | //! - Input validation for both plain and regex patterns. 6 | //! - Adjustments to inputs and patterns for chain-specific constraints. 7 | 8 | use crate::error::VanityError; 9 | #[cfg(feature = "ethereum")] 10 | use crate::keys_and_address::EthereumKeyPair; 11 | #[cfg(feature = "solana")] 12 | use crate::keys_and_address::SolanaKeyPair; 13 | use crate::keys_and_address::{BitcoinKeyPair, KeyPairGenerator}; 14 | use crate::VanityMode; 15 | 16 | /// Maximum length constraints for fast mode and general input. 17 | const BASE58_FAST_MODE_MAX: usize = 5; 18 | const BASE58_MAX: usize = 25; 19 | #[cfg(feature = "ethereum")] 20 | const BASE16_FAST_MODE_MAX: usize = 16; 21 | #[cfg(feature = "ethereum")] 22 | const BASE16_MAX: usize = 40; 23 | 24 | const ALLOWED_REGEX_META: &[char] = &[ 25 | '^', '$', '.', '*', '+', '?', '(', ')', '[', ']', '{', '}', '|', '-', 26 | ]; 27 | 28 | /// The `VanityChain` trait defines chain-specific behavior for vanity address generation. 29 | /// 30 | /// This trait is implemented for [BitcoinKeyPair], [EthereumKeyPair], and [SolanaKeyPair] 31 | /// and provides default implementations for input validation and 32 | /// adjustments for chain-specific constraints. 33 | pub trait VanityChain: KeyPairGenerator + Send { 34 | /// Validates a plain input string for the chain. 35 | /// 36 | /// # Arguments 37 | /// - `string`: The input string to validate. 38 | /// - `fast_mode`: Whether fast mode is enabled. 39 | /// 40 | /// # Returns 41 | /// - `Ok(())` if the input is valid. 42 | /// - `Err(VanityError)` if the input is invalid. 43 | /// 44 | /// # Behavior 45 | /// - Rejects inputs that exceed the length limit in fast mode. 46 | /// - Rejects inputs that exceed the max length limit. 47 | /// - Ensures all characters are valid for the specific chain. 48 | /// - Makes additional chain-specific checks if needed. 49 | fn validate_input( 50 | string: &str, 51 | fast_mode: bool, 52 | case_sensitive: bool, 53 | ) -> Result<(), VanityError>; 54 | 55 | /// Validates a regex input string for the chain. 56 | /// 57 | /// # Arguments 58 | /// - `regex_str`: The regex pattern string to validate. 59 | /// 60 | /// # Returns 61 | /// - `Ok(())` if the regex is valid. 62 | /// - `Err(VanityError)` if the regex contains invalid characters. 63 | /// 64 | /// # Behavior 65 | /// - Allows recognized regex meta characters. 66 | /// - Ensures alphanumeric characters are valid for the chain. 67 | /// - Rejects characters that are neither valid regex meta nor chain-specific valid characters. 68 | fn validate_regex_pattern(regex_str: &str) -> Result<(), VanityError>; 69 | 70 | /// Adjusts a plain input string for chain-specific requirements. 71 | /// 72 | /// # Arguments 73 | /// - `input`: The input string to adjust. 74 | /// - `_vanity_mode`: The vanity mode to apply (e.g., prefix adjustment). 75 | /// 76 | /// # Returns 77 | /// - A chain-adjusted input string. 78 | fn adjust_input(input: &str, _vanity_mode: VanityMode) -> String { 79 | input.to_string() 80 | } 81 | 82 | /// Adjusts a regex pattern string for chain-specific requirements. 83 | /// 84 | /// # Arguments 85 | /// - `regex_str`: The regex pattern string to adjust. 86 | /// 87 | /// # Returns 88 | /// - A chain-adjusted regex pattern string. 89 | fn adjust_regex_pattern(regex_str: &str) -> String { 90 | regex_str.to_string() 91 | } 92 | } 93 | 94 | /// Validates a Base58 input string for Bitcoin and Solana chains. 95 | /// 96 | /// # Arguments 97 | /// - `string`: The input string to validate. 98 | /// - `fast_mode`: Whether fast mode is enabled. 99 | /// 100 | /// # Returns 101 | /// - `Ok(())` if the input is valid. 102 | /// - `Err(VanityError)` if the input is invalid. 103 | /// 104 | /// # Behavior 105 | /// - Rejects inputs that exceed the max length limit or are invalid Base58 strings. 106 | fn validate_base58_input(string: &str, fast_mode: bool) -> Result<(), VanityError> { 107 | if string.len() > BASE58_FAST_MODE_MAX && fast_mode { 108 | return Err(VanityError::FastModeEnabled); 109 | } 110 | 111 | if string.len() > BASE58_MAX { 112 | return Err(VanityError::RequestTooLong); 113 | } 114 | 115 | if string.chars().any(|c| !is_valid_base58_char(c)) { 116 | return Err(VanityError::InputNotBase58); 117 | } 118 | 119 | Ok(()) 120 | } 121 | 122 | /// Validates a regex pattern for Base58 for Bitcoin and Solana chains. 123 | /// 124 | /// # Arguments 125 | /// - `regex_str`: The regex pattern string to validate. 126 | /// 127 | /// # Returns 128 | /// - `Ok(())` if the regex is valid. 129 | /// - `Err(VanityError)` if the regex contains invalid characters. 130 | /// 131 | /// # Behavior 132 | /// - Allows regex meta characters and Base58 alphanumeric characters. 133 | /// - Rejects invalid regex meta characters and non-Base58 characters. 134 | fn validate_base58_regex_pattern(regex_str: &str) -> Result<(), VanityError> { 135 | // For each character in the pattern: 136 | for c in regex_str.chars() { 137 | // If it's a recognized regex meta character, allow it. 138 | if ALLOWED_REGEX_META.contains(&c) { 139 | continue; 140 | } 141 | 142 | // If it's alphanumeric, ensure it's valid base58 143 | if c.is_alphanumeric() { 144 | if !is_valid_base58_char(c) { 145 | return Err(VanityError::RegexNotBase58); 146 | } 147 | } else { 148 | // Neither a recognized meta char, nor a valid base58 alphanumeric => reject 149 | return Err(VanityError::InvalidRegex); 150 | } 151 | } 152 | 153 | Ok(()) 154 | } 155 | 156 | impl VanityChain for BitcoinKeyPair { 157 | /// Validates a Base58 input string for Bitcoin-specific vanity address generation. 158 | /// 159 | /// # Arguments 160 | /// - `string`: The input string to validate. 161 | /// - `fast_mode`: Whether fast mode is enabled. 162 | /// - `_case_sensitive`: Unused for Bitcoin and Solana as they are case-insensitive. 163 | /// 164 | /// # Returns 165 | /// - `Ok(())` if the input is valid. 166 | /// - `Err(VanityError)` if the input is invalid. 167 | /// 168 | /// # Behavior 169 | /// - Rejects inputs that exceed the max length limit or the fast mode length limit. 170 | /// - Ensures all characters are valid Base58 characters. 171 | /// 172 | /// # Implementation Notes 173 | /// - The validation relies on the `validate_base58_input` helper function, which encapsulates 174 | fn validate_input( 175 | string: &str, 176 | fast_mode: bool, 177 | _case_sensitive: bool, 178 | ) -> Result<(), VanityError> { 179 | validate_base58_input(string, fast_mode) 180 | } 181 | 182 | /// Validates a regex pattern for Bitcoin-specific vanity address generation. 183 | /// 184 | /// # Arguments 185 | /// - `regex_str`: The regex pattern string to validate. 186 | /// 187 | /// # Returns 188 | /// - `Ok(())` if the regex is valid. 189 | /// - `Err(VanityError)` if the regex contains invalid characters. 190 | /// 191 | /// # Behavior 192 | /// - Allows recognized regex meta characters. 193 | /// - Ensures all alphanumeric characters in the regex are valid Base58. 194 | /// - Rejects invalid regex meta characters or non-Base58 characters. 195 | /// 196 | /// # Implementation Notes 197 | /// - The validation relies on the `validate_base58_regex_pattern` helper function, which 198 | /// encapsulates the Base58-specific regex validation logic. 199 | fn validate_regex_pattern(regex_str: &str) -> Result<(), VanityError> { 200 | validate_base58_regex_pattern(regex_str) 201 | } 202 | 203 | /// Adjusts the input string for Bitcoin-specific vanity address generation. 204 | /// 205 | /// # Arguments 206 | /// - `input`: The input string to adjust. 207 | /// - `vanity_mode`: The vanity mode (e.g., `Prefix`, `Suffix`). 208 | /// 209 | /// # Returns 210 | /// - The adjusted input string, with '1' prepended if in `Prefix` mode. 211 | /// 212 | /// # Behavior 213 | /// - Bitcoin addresses must always start with a '1'. If the `VanityMode` is `Prefix`, 214 | /// the method prepends '1' to the input string. 215 | /// - Other modes (e.g., Suffix or Anywhere) do not modify the input. 216 | fn adjust_input(input: &str, vanity_mode: VanityMode) -> String { 217 | match vanity_mode { 218 | VanityMode::Prefix => format!("1{input}"), 219 | _ => input.to_string(), 220 | } 221 | } 222 | 223 | /// Adjusts a regex pattern for Bitcoin-specific vanity address generation. 224 | /// 225 | /// # Arguments 226 | /// - `regex_str`: The regex pattern to adjust. 227 | /// 228 | /// # Returns 229 | /// - The adjusted regex pattern, ensuring prefix patterns start with `1`. 230 | /// 231 | /// # Example 232 | /// - Input: `^abc` => Output: `^1abc` 233 | /// 234 | /// # Behavior 235 | /// - For regex patterns, if the pattern starts with `^` (indicating a prefix match) but does not start 236 | /// with `^1`, the method inserts `1` after `^` to ensure the regex respects Bitcoin's address format. 237 | fn adjust_regex_pattern(regex_str: &str) -> String { 238 | let mut pattern_str = regex_str.to_string(); 239 | if pattern_str.starts_with('^') && !pattern_str.starts_with("^1") { 240 | pattern_str.insert(1, '1'); 241 | } 242 | pattern_str 243 | } 244 | } 245 | 246 | #[cfg(feature = "ethereum")] 247 | impl VanityChain for EthereumKeyPair { 248 | /// Validates a Base16 input string for Ethereum-specific vanity address generation. 249 | /// 250 | /// # Arguments 251 | /// - `string`: The input string to validate. 252 | /// - `fast_mode`: Whether fast mode is enabled. 253 | /// - `case_sensitive`: Whether the matching is case-sensitive. **Ethereum does not support case-sensitive inputs.** 254 | /// 255 | /// # Returns 256 | /// - `Ok(())` if the input is valid. 257 | /// - `Err(VanityError)` if the input is invalid. 258 | /// 259 | /// # Behavior 260 | /// - Rejects inputs if `case_sensitive` is enabled. 261 | /// - Rejects inputs that exceed the length limit in fast mode. 262 | /// - Rejects inputs that exceed the max length limit. 263 | /// - Ensures all characters are valid Base16 (hexadecimal) characters. 264 | fn validate_input( 265 | string: &str, 266 | fast_mode: bool, 267 | case_sensitive: bool, 268 | ) -> Result<(), VanityError> { 269 | if case_sensitive { 270 | return Err(VanityError::EthereumCaseSensitiveIsNotSupported); 271 | } 272 | 273 | if string.len() > BASE16_FAST_MODE_MAX && fast_mode { 274 | return Err(VanityError::FastModeEnabled); 275 | } 276 | 277 | if string.len() > BASE16_MAX { 278 | return Err(VanityError::RequestTooLong); 279 | } 280 | 281 | // If any character is not base16, reject. 282 | if string.chars().any(|c| !c.is_ascii_hexdigit()) { 283 | return Err(VanityError::InputNotBase16); 284 | } 285 | 286 | Ok(()) 287 | } 288 | 289 | /// Validates a regex pattern for Base16. 290 | /// 291 | /// # Arguments 292 | /// - `regex_str`: The regex pattern to validate. 293 | /// 294 | /// # Returns 295 | /// - `Ok(())` if the pattern is valid. 296 | /// - `Err(VanityError)` if the pattern is invalid. 297 | /// 298 | /// # Errors 299 | /// - Returns `VanityError::RegexNotBase16` for invalid Base16 characters. 300 | /// - Returns `VanityError::InvalidRegex` for unrecognized characters. 301 | /// 302 | /// # Behavior 303 | /// - Allows recognized regex meta characters. 304 | /// - Ensures all alphanumeric characters in the regex are valid Base16 (hexadecimal). 305 | fn validate_regex_pattern(regex_str: &str) -> Result<(), VanityError> { 306 | // For each character in the pattern: 307 | for c in regex_str.chars() { 308 | // If it's a recognized regex meta character, allow it. 309 | if ALLOWED_REGEX_META.contains(&c) { 310 | continue; 311 | } 312 | 313 | // If it's alphanumeric, ensure it's valid base16. 314 | if c.is_alphanumeric() { 315 | if !c.is_ascii_hexdigit() { 316 | return Err(VanityError::RegexNotBase16); 317 | } 318 | } else { 319 | // Neither a recognized meta char, nor a valid base16 alphanumeric => reject. 320 | return Err(VanityError::InvalidRegex); 321 | } 322 | } 323 | 324 | Ok(()) 325 | } 326 | 327 | /// Adjusts a regex pattern for Ethereum-specific vanity address generation. 328 | /// 329 | /// # Arguments 330 | /// - `regex_str`: The regex pattern to adjust. 331 | /// 332 | /// # Returns 333 | /// - The adjusted regex pattern, with `^0x` removed if present. 334 | /// 335 | /// # Example 336 | /// - Input: `^0xabc` => Output: `^abc` 337 | /// 338 | /// # Behavior: 339 | /// - For regex patterns, if the pattern starts with `^0x`, the `0x` is removed for vanity 340 | /// address generation. 341 | fn adjust_regex_pattern(regex_str: &str) -> String { 342 | let mut pattern_str = regex_str.to_string().to_ascii_lowercase(); 343 | if pattern_str.starts_with("^0x") { 344 | pattern_str = pattern_str.replacen("^0x", "^", 1); 345 | } 346 | pattern_str 347 | } 348 | } 349 | 350 | #[cfg(feature = "solana")] 351 | impl VanityChain for SolanaKeyPair { 352 | /// Validates a Base58 input string for Solana-specific vanity address generation. 353 | /// 354 | /// # Arguments 355 | /// - `string`: The input string to validate. 356 | /// - `fast_mode`: Whether fast mode is enabled. 357 | /// - `_case_sensitive`: Unused for Bitcoin and Solana as they are case-insensitive. 358 | /// 359 | /// # Returns 360 | /// - `Ok(())` if the input is valid. 361 | /// - `Err(VanityError)` if the input is invalid. 362 | /// 363 | /// # Behavior 364 | /// - Rejects inputs that exceed the max length limit or the fast mode length limit. 365 | /// - Ensures all characters are valid Base58 characters. 366 | /// 367 | /// # Implementation Notes 368 | /// - The validation relies on the `validate_base58_input` helper function, which encapsulates 369 | fn validate_input( 370 | string: &str, 371 | fast_mode: bool, 372 | _case_sensitive: bool, 373 | ) -> Result<(), VanityError> { 374 | validate_base58_input(string, fast_mode) 375 | } 376 | 377 | /// Validates a regex pattern for Solana-specific vanity address generation. 378 | /// 379 | /// # Arguments 380 | /// - `regex_str`: The regex pattern string to validate. 381 | /// 382 | /// # Returns 383 | /// - `Ok(())` if the regex is valid. 384 | /// - `Err(VanityError)` if the regex contains invalid characters. 385 | /// 386 | /// # Behavior 387 | /// - Allows recognized regex meta characters. 388 | /// - Ensures all alphanumeric characters in the regex are valid Base58. 389 | /// - Rejects invalid regex meta characters or non-Base58 characters. 390 | /// 391 | /// # Implementation Notes 392 | /// - The validation relies on the `validate_base58_regex_pattern` helper function, which 393 | /// encapsulates the Base58-specific regex validation logic. 394 | fn validate_regex_pattern(regex_str: &str) -> Result<(), VanityError> { 395 | validate_base58_regex_pattern(regex_str) 396 | } 397 | } 398 | 399 | /// Returns `true` if `c` is a valid Base58 character for Bitcoin and Solana chains. 400 | /// 401 | /// # Arguments 402 | /// - `c`: The character to validate. 403 | /// 404 | /// # Returns 405 | /// - `true` if the character is valid. 406 | /// - `false` otherwise. 407 | /// 408 | /// # Valid Characters 409 | /// - Digits: `1-9` 410 | /// - Uppercase letters (excluding `I` and `O`) 411 | /// - Lowercase letters (excluding `l`) 412 | pub fn is_valid_base58_char(c: char) -> bool { 413 | match c { 414 | // digits except 0 415 | '1'..='9' => true, 416 | // uppercase letters except I, O 417 | 'A'..='H' | 'J'..='N' | 'P'..='Z' => true, 418 | // lowercase letters except l 419 | 'a'..='k' | 'm'..='z' => true, 420 | _ => false, 421 | } 422 | } 423 | 424 | /// Represents supported blockchain chains. 425 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 426 | pub enum Chain { 427 | #[default] 428 | Bitcoin, 429 | Ethereum, 430 | Solana, 431 | } 432 | 433 | impl std::str::FromStr for Chain { 434 | type Err = String; 435 | 436 | /// Parses a string into a `Chain` variant. 437 | fn from_str(chain: &str) -> Result { 438 | match chain.to_lowercase().as_str() { 439 | "bitcoin" => Ok(Chain::Bitcoin), 440 | "ethereum" => Ok(Chain::Ethereum), 441 | "solana" => Ok(Chain::Solana), 442 | _ => Err(format!("Unsupported chain: {chain}")), 443 | } 444 | } 445 | } 446 | 447 | impl std::fmt::Display for Chain { 448 | /// Converts a `Chain` variant to a string. 449 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 450 | write!( 451 | f, 452 | "{}", 453 | match self { 454 | Chain::Bitcoin => "bitcoin", 455 | Chain::Ethereum => "ethereum", 456 | Chain::Solana => "solana", 457 | } 458 | ) 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /src/vanity_addr_generator/vanity_addr.rs: -------------------------------------------------------------------------------- 1 | //! # Vanity Address Generator Module 2 | //! 3 | //! This module defines the [VanityAddr] and [SearchEngines] structs, which handle the generation 4 | //! of vanity cryptocurrency addresses using custom patterns and regular expressions. It supports: 5 | //! - Validation and adjustment of inputs for specific chains. 6 | //! - Multi-threaded generation of vanity addresses. 7 | //! - Pattern matching using prefix, suffix, anywhere, and regex modes. 8 | 9 | use std::sync::atomic::{AtomicBool, Ordering}; 10 | use std::sync::{mpsc, Arc}; 11 | use std::thread; 12 | 13 | use crate::error::VanityError; 14 | use crate::vanity_addr_generator::chain::VanityChain; 15 | use crate::vanity_addr_generator::comp::{ 16 | contains_case_insensitive, contains_memx, eq_prefix_case_insensitive, eq_prefix_memx, 17 | eq_suffix_case_insensitive, eq_suffix_memx, 18 | }; 19 | use crate::BATCH_SIZE; 20 | 21 | use regex::Regex; 22 | 23 | /// An empty struct that provides functionality for generating vanity addresses. 24 | /// 25 | /// This struct contains only static methods and acts as a logical container for 26 | /// vanity address generation functionality. 27 | pub struct VanityAddr; 28 | 29 | /// Enum to define the matching mode for vanity address generation. 30 | #[derive(Copy, Clone, Debug, PartialEq, Default)] 31 | pub enum VanityMode { 32 | /// Matches addresses that start with the pattern. 33 | #[default] 34 | Prefix, 35 | /// Matches addresses that end with the pattern. 36 | Suffix, 37 | /// Matches addresses that contain the pattern anywhere. 38 | Anywhere, 39 | /// Matches addresses based on a regular expression. 40 | Regex, 41 | } 42 | 43 | impl VanityAddr { 44 | /// Generates a vanity address for a given pattern. 45 | /// 46 | /// # Arguments 47 | /// - `string`: The pattern string to match against addresses. 48 | /// - `threads`: The number of threads to use for address generation. 49 | /// - `case_sensitive`: Whether the matching should be case-sensitive. 50 | /// - `fast_mode`: Whether to enable fast mode (with stricter limits on pattern length). 51 | /// - `vanity_mode`: The mode of matching (e.g., prefix, suffix). 52 | /// 53 | /// # Returns 54 | /// - `Ok(T)` where `T` is a type implementing [VanityChain], containing the generated address. 55 | /// - `Err(VanityError)` if the input is invalid or generation fails. 56 | /// 57 | /// # Behavior 58 | /// - Validates the input string for chain-specific rules. 59 | /// - Adjusts the input string based on the chain and vanity mode. 60 | /// - Uses multiple threads to search for a matching address. 61 | pub fn generate( 62 | string: &str, 63 | threads: usize, 64 | case_sensitive: bool, 65 | fast_mode: bool, 66 | vanity_mode: VanityMode, 67 | ) -> Result { 68 | T::validate_input(string, fast_mode, case_sensitive)?; 69 | let adjusted_string = T::adjust_input(string, vanity_mode); 70 | 71 | if string.is_empty() { 72 | return Ok(T::generate_random()); 73 | } 74 | 75 | Ok(SearchEngines::find_vanity_address::( 76 | adjusted_string, 77 | threads, 78 | case_sensitive, 79 | vanity_mode, 80 | )) 81 | } 82 | 83 | /// Generates a vanity address based on a regular expression. 84 | /// 85 | /// # Arguments 86 | /// - `regex_str`: The regular expression to match against addresses. 87 | /// - `threads`: The number of threads to use for address generation. 88 | /// 89 | /// # Returns 90 | /// - `Ok(T)` where `T` is a type implementing [VanityChain], containing the generated address. 91 | /// - `Err(VanityError)` if the regex is invalid or generation fails. 92 | /// 93 | /// # Behavior 94 | /// - Validates the regular expression for chain-specific rules. 95 | /// - Adjusts the regex pattern based on the chain. 96 | /// - Uses multiple threads to search for a matching address. 97 | pub fn generate_regex( 98 | regex_str: &str, 99 | threads: usize, 100 | ) -> Result { 101 | T::validate_regex_pattern(regex_str)?; 102 | let adjusted_regex = T::adjust_regex_pattern(regex_str); 103 | 104 | if regex_str.is_empty() { 105 | return Ok(T::generate_random()); 106 | } 107 | 108 | SearchEngines::find_vanity_address_regex::(adjusted_regex, threads) 109 | } 110 | } 111 | 112 | /// A helper struct that implements the core logic for searching for vanity addresses. 113 | /// 114 | /// This struct contains static methods for address search using both plain patterns 115 | /// and regular expressions. 116 | pub struct SearchEngines; 117 | 118 | impl SearchEngines { 119 | /// Searches for a vanity address matching the given string pattern. 120 | /// 121 | /// # Arguments 122 | /// - `string`: The string pattern to match against addresses. 123 | /// - `threads`: The number of threads to use for address generation. 124 | /// - `case_sensitive`: Whether the matching should be case-sensitive. 125 | /// - `vanity_mode`: The mode of matching (e.g., prefix, suffix). 126 | /// 127 | /// # Returns 128 | /// - A type implementing [VanityChain] that contains the generated address. 129 | /// 130 | /// # Behavior 131 | /// - Spawns multiple threads to search for a matching address. 132 | /// - Uses an atomic flag to stop all threads once a match is found. 133 | /// - Uses an `mpsc` channel to send the matching address back to the main thread. 134 | fn find_vanity_address( 135 | string: String, 136 | threads: usize, 137 | case_sensitive: bool, 138 | vanity_mode: VanityMode, 139 | ) -> T { 140 | let string_bytes = string.as_bytes(); 141 | let lower_string_bytes = if !case_sensitive { 142 | string_bytes 143 | .iter() 144 | .map(|b| b.to_ascii_lowercase()) 145 | .collect::>() 146 | } else { 147 | vec![] 148 | }; 149 | 150 | let (sender, receiver) = mpsc::channel(); 151 | let found_any = Arc::new(AtomicBool::new(false)); 152 | 153 | for _ in 0..threads { 154 | let sender = sender.clone(); 155 | let found_any = found_any.clone(); 156 | 157 | let thread_string_bytes = string_bytes.to_vec(); 158 | let thread_lower_string_bytes = lower_string_bytes.clone(); 159 | 160 | thread::spawn(move || { 161 | let mut batch: [T; BATCH_SIZE] = T::generate_batch(); 162 | let mut dummy = T::generate_random(); 163 | 164 | // Pre-compute pattern length for efficiency 165 | let pattern_len = if case_sensitive { 166 | thread_string_bytes.len() 167 | } else { 168 | thread_lower_string_bytes.len() 169 | }; 170 | 171 | while !found_any.load(Ordering::Relaxed) { 172 | // Generate a batch of addresses 173 | T::fill_batch(&mut batch); 174 | 175 | // Check each address in the batch with loop unrolling for better performance 176 | let mut i = 0; 177 | while i < BATCH_SIZE { 178 | // Process multiple addresses per iteration to improve cache efficiency 179 | let end = std::cmp::min(i + 8, BATCH_SIZE); 180 | 181 | #[allow(clippy::needless_range_loop)] 182 | for j in i..end { 183 | // Early exit check every few iterations to minimize atomic load overhead 184 | if j.is_multiple_of(4) && found_any.load(Ordering::Relaxed) { 185 | return; 186 | } 187 | 188 | let keys_and_address = &batch[j]; 189 | let address_bytes = keys_and_address.get_address_bytes(); 190 | 191 | // Early length check to avoid expensive pattern matching 192 | if address_bytes.len() < pattern_len { 193 | continue; 194 | } 195 | 196 | let matches = if case_sensitive { 197 | // Uses memx (good for case-sensitive) 198 | match vanity_mode { 199 | VanityMode::Prefix => { 200 | eq_prefix_memx(address_bytes, &thread_string_bytes) 201 | } 202 | VanityMode::Suffix => { 203 | eq_suffix_memx(address_bytes, &thread_string_bytes) 204 | } 205 | VanityMode::Anywhere => { 206 | contains_memx(address_bytes, &thread_string_bytes) 207 | } 208 | VanityMode::Regex => { 209 | unreachable!("Regex mode should not be handled here") 210 | } 211 | } 212 | } else { 213 | // Uses optimized case-insensitive functions 214 | match vanity_mode { 215 | VanityMode::Prefix => eq_prefix_case_insensitive( 216 | address_bytes, 217 | &thread_lower_string_bytes, 218 | ), 219 | VanityMode::Suffix => eq_suffix_case_insensitive( 220 | address_bytes, 221 | &thread_lower_string_bytes, 222 | ), 223 | VanityMode::Anywhere => contains_case_insensitive( 224 | address_bytes, 225 | &thread_lower_string_bytes, 226 | ), 227 | VanityMode::Regex => { 228 | unreachable!("Regex mode should not be handled here") 229 | } 230 | } 231 | }; 232 | 233 | // If match found... 234 | if matches { 235 | // Mark as found (and check if we are the first) 236 | if !found_any.swap(true, Ordering::Relaxed) { 237 | // We're the first thread to set found_any = true 238 | // Attempt to send the result 239 | std::mem::swap(&mut batch[j], &mut dummy); 240 | let _ = sender.send(dummy); 241 | } 242 | // Return immediately: no need to generate more 243 | return; 244 | } 245 | } 246 | 247 | i = end; 248 | } 249 | } 250 | }); 251 | } 252 | 253 | // The main thread just waits for the first successful result. 254 | // As soon as one thread sends over the channel, we have our vanity address. 255 | receiver 256 | .recv() 257 | .expect("Receiver closed before a vanity address was found") 258 | } 259 | 260 | /// Searches for a vanity address matching the given regex pattern. 261 | /// 262 | /// # Arguments 263 | /// - `regex_str`: The regex pattern to match against addresses. 264 | /// - `threads`: The number of threads to use for address generation. 265 | /// 266 | /// # Returns 267 | /// - `Ok(T)` where `T` is a type implementing [VanityChain], containing the generated address. 268 | /// - `Err(VanityError)` if the regex is invalid or generation fails. 269 | /// 270 | /// # Behavior 271 | /// - Spawns multiple threads to search for a matching address. 272 | /// - Uses an atomic flag to stop all threads once a match is found. 273 | /// - Uses an `mpsc` channel to send the matching address back to the main thread. 274 | pub fn find_vanity_address_regex( 275 | regex_str: String, 276 | threads: usize, 277 | ) -> Result { 278 | // Validate the regex syntax 279 | let _test_regex = Regex::new(®ex_str).map_err(|_e| VanityError::InvalidRegex)?; 280 | 281 | let (sender, receiver) = mpsc::channel(); 282 | let found_any = Arc::new(AtomicBool::new(false)); 283 | 284 | for _ in 0..threads { 285 | let sender = sender.clone(); 286 | let found_any = Arc::clone(&found_any); 287 | let regex_clone = regex_str.clone(); 288 | 289 | thread::spawn(move || { 290 | // Compile regex once per thread 291 | let regex = Regex::new(®ex_clone).unwrap(); 292 | let mut batch: [T; BATCH_SIZE] = T::generate_batch(); 293 | let mut dummy = T::generate_random(); 294 | 295 | while !found_any.load(Ordering::Relaxed) { 296 | // Generate a batch of addresses 297 | T::fill_batch(&mut batch); 298 | 299 | // Check each address in the batch 300 | for (i, keys_and_address) in batch.iter().enumerate() { 301 | let address = keys_and_address.get_address(); 302 | if regex.is_match(address) && !found_any.load(Ordering::Relaxed) { 303 | // If a match is found, send it to the main thread 304 | if !found_any.swap(true, Ordering::Relaxed) { 305 | std::mem::swap(&mut batch[i], &mut dummy); 306 | let _ = sender.send(dummy); 307 | return; 308 | } 309 | } 310 | } 311 | } 312 | }); 313 | } 314 | 315 | // The main thread just waits for the first successful result. 316 | // As soon as one thread sends over the channel, we have our vanity address. 317 | Ok(receiver 318 | .recv() 319 | .expect("Receiver closed before a matching address was found")) 320 | } 321 | } 322 | 323 | #[cfg(test)] 324 | mod tests { 325 | use super::{VanityAddr, VanityMode}; 326 | 327 | mod bitcoin_vanity_tests { 328 | use super::*; 329 | use crate::keys_and_address::BitcoinKeyPair; 330 | 331 | #[test] 332 | fn test_generate_vanity_prefix() { 333 | let vanity_string = "et"; 334 | let keys_and_address = VanityAddr::generate::( 335 | vanity_string, 336 | 4, // Use 4 threads 337 | true, // Case-insensitivity 338 | true, // Fast mode (limits string size with 4 characters) 339 | VanityMode::Prefix, // Vanity mode set to Prefix 340 | ) 341 | .unwrap(); 342 | 343 | let vanity_addr_starts_with = "1et"; 344 | assert!(keys_and_address 345 | .get_comp_address() 346 | .starts_with(vanity_addr_starts_with)); 347 | } 348 | 349 | #[test] 350 | fn test_generate_vanity_suffix() { 351 | let vanity_string = "12"; 352 | let keys_and_address = VanityAddr::generate::( 353 | vanity_string, 354 | 4, // Use 4 threads 355 | false, // Case-insensitivity 356 | true, // Fast mode (limits string size with 4 characters) 357 | VanityMode::Suffix, // Vanity mode set to Suffix 358 | ) 359 | .unwrap(); 360 | 361 | assert!(keys_and_address.get_comp_address().ends_with(vanity_string)); 362 | } 363 | 364 | #[test] 365 | fn test_generate_vanity_anywhere() { 366 | let vanity_string = "ab"; 367 | let keys_and_address = VanityAddr::generate::( 368 | vanity_string, 369 | 4, // Use 4 threads 370 | true, // Case-insensitivity 371 | true, // Fast mode (limits string size with 4 characters) 372 | VanityMode::Anywhere, // Vanity mode set to Anywhere 373 | ) 374 | .unwrap(); 375 | 376 | assert!(keys_and_address.get_comp_address().contains(vanity_string)); 377 | } 378 | 379 | #[test] 380 | #[should_panic(expected = "FastModeEnabled")] 381 | fn test_generate_vanity_string_too_long_with_fast_mode() { 382 | let vanity_string = "123456"; // String longer than 5 characters 383 | let _ = VanityAddr::generate::( 384 | vanity_string, 385 | 4, // Use 4 threads 386 | false, // Case-insensitivity 387 | true, // Fast mode (limits string size with 4 characters) 388 | VanityMode::Prefix, // Vanity mode set to Prefix 389 | ) 390 | .unwrap(); 391 | } 392 | 393 | #[test] 394 | #[should_panic(expected = "InputNotBase58")] 395 | fn test_generate_vanity_invalid_base58() { 396 | let vanity_string = "emiO"; // Contains invalid base58 character 'O' 397 | let _ = VanityAddr::generate::( 398 | vanity_string, 399 | 4, // Use 4 threads 400 | false, // Case-insensitivity 401 | true, // Fast mode (limits string size with 4 characters) 402 | VanityMode::Prefix, // Vanity mode set to Prefix 403 | ) 404 | .unwrap(); 405 | } 406 | 407 | #[test] 408 | fn test_generate_regex_et_ends() { 409 | let pattern = "ET$"; 410 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4) 411 | .expect("Failed to generate address for 'ET$'"); 412 | let address = keys_and_address.get_comp_address(); 413 | 414 | // The final pattern is "ET$" => ends with "ET" 415 | assert!( 416 | address.ends_with("ET"), 417 | "Address should end with 'ET': {}", 418 | address 419 | ); 420 | } 421 | 422 | #[test] 423 | fn test_generate_regex_rewrite() { 424 | // Original pattern is '^E' (not '^1'), so the code will insert '1', resulting in '^1E'. 425 | // We expect it eventually to find an address starting with "1E". 426 | let pattern = "^E"; 427 | let keys_and_address = 428 | VanityAddr::generate_regex::(pattern, 4).unwrap(); 429 | let address = keys_and_address.get_comp_address(); 430 | // Now that we know it's '^1E', check the first two characters: 431 | assert!( 432 | address.starts_with("1E"), 433 | "Address should start with '1E': {}", 434 | address 435 | ); 436 | } 437 | 438 | #[test] 439 | fn test_generate_regex_e_any_t() { 440 | // Must start with "1E" (rewritten from "^E") and end with "T". 441 | let pattern = "^E.*T$"; 442 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4) 443 | .expect("Failed to generate address for '^E.*T$'"); 444 | let address = keys_and_address.get_comp_address(); 445 | 446 | // Because of rewriting, the actual pattern used is '^1E.*T$'. 447 | // 1) Check it starts with "1E" 448 | assert!( 449 | address.starts_with("1E"), 450 | "Address should start with '1E': {}", 451 | address 452 | ); 453 | // 2) Check it ends with 'T' 454 | assert!( 455 | address.ends_with('T'), 456 | "Address should end with 'T': {}", 457 | address 458 | ); 459 | } 460 | 461 | #[test] 462 | fn test_generate_regex_e_69_any_t() { 463 | // Must start with "1E", contain "69", and end with "T". 464 | // Rewritten from "^E.*69.*T$" => "^1E.*69.*T$" 465 | let pattern = "^E.*69.*T$"; 466 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4) 467 | .expect("Failed to generate address for '^E.*69.*T$'"); 468 | let address = keys_and_address.get_comp_address(); 469 | 470 | // After rewriting: '^1E.*69.*T$' 471 | assert!( 472 | address.starts_with("1E"), 473 | "Address should start with '1E': {}", 474 | address 475 | ); 476 | assert!( 477 | address.contains("69"), 478 | "Address should contain '69': {}", 479 | address 480 | ); 481 | assert!( 482 | address.ends_with('T'), 483 | "Address should end with 'T': {}", 484 | address 485 | ); 486 | } 487 | 488 | #[test] 489 | #[should_panic(expected = "InvalidRegex")] 490 | fn test_generate_regex_invalid_syntax() { 491 | let pattern = "^(abc"; 492 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 493 | } 494 | 495 | #[test] 496 | #[should_panic(expected = "RegexNotBase58")] 497 | fn test_generate_regex_forbidden_char_zero() { 498 | let pattern = "^0"; 499 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 500 | } 501 | 502 | #[test] 503 | #[should_panic(expected = "RegexNotBase58")] 504 | fn test_generate_regex_forbidden_char_o() { 505 | let pattern = "^O"; 506 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 507 | } 508 | 509 | #[test] 510 | #[should_panic(expected = "RegexNotBase58")] 511 | fn test_generate_regex_forbidden_char_i() { 512 | let pattern = "^I"; 513 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 514 | } 515 | } 516 | 517 | #[cfg(feature = "ethereum")] 518 | mod ethereum_vanity_tests { 519 | use super::*; 520 | use crate::keys_and_address::{EthereumKeyPair, KeyPairGenerator}; 521 | 522 | #[test] 523 | fn test_generate_vanity_prefix() { 524 | let vanity_string = "ab"; 525 | let keys_and_address = VanityAddr::generate::( 526 | vanity_string, 527 | 4, // Use 4 threads 528 | false, // Case-insensitivity 529 | true, // Fast mode 530 | VanityMode::Prefix, // Vanity mode set to Prefix 531 | ) 532 | .unwrap(); 533 | 534 | let expected_prefix = "ab"; 535 | assert!(keys_and_address 536 | .get_address() 537 | .to_lowercase() 538 | .starts_with(expected_prefix)); 539 | } 540 | 541 | #[test] 542 | fn test_generate_vanity_suffix() { 543 | let vanity_string = "123"; 544 | let keys_and_address = VanityAddr::generate::( 545 | vanity_string, 546 | 4, // Use 4 threads 547 | false, // Case-sensitivity 548 | true, // Fast mode 549 | VanityMode::Suffix, // Vanity mode set to Suffix 550 | ) 551 | .unwrap(); 552 | 553 | assert!(keys_and_address.get_address().ends_with(vanity_string)); 554 | } 555 | 556 | #[test] 557 | fn test_generate_vanity_anywhere() { 558 | let vanity_string = "abc"; 559 | let keys_and_address = VanityAddr::generate::( 560 | vanity_string, 561 | 4, // Use 4 threads 562 | false, // Case-insensitivity 563 | true, // Fast mode (limits string size to 16 characters) 564 | VanityMode::Anywhere, // Vanity mode set to Anywhere 565 | ) 566 | .unwrap(); 567 | 568 | assert!(keys_and_address.get_address().contains(vanity_string)); 569 | } 570 | 571 | #[test] 572 | #[should_panic(expected = "FastModeEnabled")] 573 | fn test_generate_vanity_string_too_long_with_fast_mode() { 574 | let vanity_string = "12345678901234567890"; // String longer than 16 characters 575 | let _ = VanityAddr::generate::( 576 | vanity_string, 577 | 4, // Use 4 threads 578 | false, // Case-sensitivity 579 | true, // Fast mode (limits string size to 16 characters) 580 | VanityMode::Prefix, // Vanity mode set to Prefix 581 | ) 582 | .unwrap(); 583 | } 584 | 585 | #[test] 586 | #[should_panic(expected = "InputNotBase16")] 587 | fn test_generate_vanity_invalid_base16() { 588 | let vanity_string = "g123"; // Contains invalid base16 character 'g' 589 | let _ = VanityAddr::generate::( 590 | vanity_string, 591 | 4, // Use 4 threads 592 | false, // Case-sensitivity 593 | true, // Fast mode 594 | VanityMode::Prefix, // Vanity mode set to Prefix 595 | ) 596 | .unwrap(); 597 | } 598 | 599 | #[test] 600 | #[should_panic(expected = "InputNotBase16")] 601 | fn test_generate_vanity_with_prefix() { 602 | let vanity_string = "0xdead"; // Contains invalid base16 character 'x' 603 | let _ = VanityAddr::generate::( 604 | vanity_string, 605 | 4, // Use 4 threads 606 | false, // Case-sensitivity 607 | true, // Fast mode 608 | VanityMode::Prefix, // Vanity mode set to Prefix 609 | ) 610 | .unwrap(); 611 | } 612 | 613 | #[test] 614 | fn test_generate_regex_prefix() { 615 | let pattern = "^ab"; 616 | let keys_and_address = 617 | VanityAddr::generate_regex::(pattern, 4).unwrap(); 618 | let address = keys_and_address.get_address(); 619 | 620 | assert!( 621 | address.starts_with("ab"), 622 | "Address should start with 'ab': {}", 623 | address 624 | ); 625 | } 626 | 627 | #[test] 628 | fn test_generate_regex_suffix() { 629 | let pattern = "cd$"; 630 | let keys_and_address = 631 | VanityAddr::generate_regex::(pattern, 4).unwrap(); 632 | let address = keys_and_address.get_address(); 633 | 634 | assert!( 635 | address.ends_with("cd"), 636 | "Address should end with 'cd': {}", 637 | address 638 | ); 639 | } 640 | 641 | #[test] 642 | fn test_generate_regex_anywhere() { 643 | let pattern = ".*abc.*"; 644 | let keys_and_address = 645 | VanityAddr::generate_regex::(pattern, 4).unwrap(); 646 | let address = keys_and_address.get_address(); 647 | 648 | assert!( 649 | address.contains("abc"), 650 | "Address should contain 'abc': {}", 651 | address 652 | ); 653 | } 654 | 655 | #[test] 656 | #[should_panic(expected = "InvalidRegex")] 657 | fn test_generate_regex_invalid_syntax() { 658 | let pattern = "^(abc"; 659 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 660 | } 661 | 662 | #[test] 663 | #[should_panic(expected = "RegexNotBase16")] 664 | fn test_generate_regex_invalid_characters() { 665 | let pattern = "^gh"; 666 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 667 | } 668 | 669 | #[test] 670 | #[should_panic(expected = "RegexNotBase16")] 671 | fn test_generate_regex_with_prefix() { 672 | let pattern = "^0xdead"; 673 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 674 | } 675 | 676 | #[test] 677 | fn test_generate_regex_complex_pattern() { 678 | let pattern = "^ab.*12$"; 679 | let keys_and_address = 680 | VanityAddr::generate_regex::(pattern, 4).unwrap(); 681 | let address = keys_and_address.get_address(); 682 | 683 | assert!( 684 | address.starts_with("ab"), 685 | "Address should start with 'ab': {}", 686 | address 687 | ); 688 | assert!( 689 | address.ends_with("12"), 690 | "Address should end with '12': {}", 691 | address 692 | ); 693 | } 694 | } 695 | 696 | #[cfg(feature = "solana")] 697 | mod solana_vanity_tests { 698 | use super::*; 699 | use crate::keys_and_address::{KeyPairGenerator, SolanaKeyPair}; 700 | 701 | #[test] 702 | fn test_generate_vanity_prefix() { 703 | let vanity_string = "et"; 704 | let keys_and_address = VanityAddr::generate::( 705 | vanity_string, 706 | 4, // Use 4 threads 707 | true, // Case-insensitivity 708 | true, // Fast mode (limits string size with 44 characters) 709 | VanityMode::Prefix, // Vanity mode set to Prefix 710 | ) 711 | .unwrap(); 712 | 713 | let vanity_addr_starts_with = "et"; 714 | assert!(keys_and_address 715 | .get_address() 716 | .starts_with(vanity_addr_starts_with)); 717 | } 718 | 719 | #[test] 720 | fn test_generate_vanity_suffix() { 721 | let vanity_string = "12"; 722 | let keys_and_address = VanityAddr::generate::( 723 | vanity_string, 724 | 4, // Use 4 threads 725 | false, // Case-insensitivity 726 | true, // Fast mode (limits string size with 44 characters) 727 | VanityMode::Suffix, // Vanity mode set to Suffix 728 | ) 729 | .unwrap(); 730 | 731 | assert!(keys_and_address.get_address().ends_with(vanity_string)); 732 | } 733 | 734 | #[test] 735 | fn test_generate_vanity_anywhere() { 736 | let vanity_string = "ab"; 737 | let keys_and_address = VanityAddr::generate::( 738 | vanity_string, 739 | 4, // Use 4 threads 740 | true, // Case-insensitivity 741 | true, // Fast mode (limits string size with 44 characters) 742 | VanityMode::Anywhere, // Vanity mode set to Anywhere 743 | ) 744 | .unwrap(); 745 | 746 | assert!(keys_and_address.get_address().contains(vanity_string)); 747 | } 748 | 749 | #[test] 750 | #[should_panic(expected = "FastModeEnabled")] 751 | fn test_generate_vanity_string_too_long_with_fast_mode() { 752 | let vanity_string = "123456"; // String longer than 5 characters 753 | let _ = VanityAddr::generate::( 754 | vanity_string, 755 | 4, // Use 4 threads 756 | false, // Case-insensitivity 757 | true, // Fast mode (limits string size with 44 characters) 758 | VanityMode::Prefix, // Vanity mode set to Prefix 759 | ) 760 | .unwrap(); 761 | } 762 | 763 | #[test] 764 | #[should_panic(expected = "InputNotBase58")] 765 | fn test_generate_vanity_invalid_base58() { 766 | let vanity_string = "emiO"; // Contains invalid base58 character 'O' 767 | let _ = VanityAddr::generate::( 768 | vanity_string, 769 | 4, // Use 4 threads 770 | false, // Case-insensitivity 771 | true, // Fast mode (limits string size with 44 characters) 772 | VanityMode::Prefix, // Vanity mode set to Prefix 773 | ) 774 | .unwrap(); 775 | } 776 | 777 | #[test] 778 | fn test_generate_regex_prefix() { 779 | let pattern = "^et"; 780 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 781 | let address = keys_and_address.get_address(); 782 | 783 | assert!( 784 | address.starts_with("et"), 785 | "Address should start with 'et': {}", 786 | address 787 | ); 788 | } 789 | 790 | #[test] 791 | fn test_generate_regex_suffix() { 792 | let pattern = "cd$"; 793 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 794 | let address = keys_and_address.get_address(); 795 | 796 | assert!( 797 | address.ends_with("cd"), 798 | "Address should end with 'cd': {}", 799 | address 800 | ); 801 | } 802 | 803 | #[test] 804 | fn test_generate_regex_anywhere() { 805 | let pattern = ".*ab.*"; 806 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 807 | let address = keys_and_address.get_address(); 808 | 809 | assert!( 810 | address.contains("ab"), 811 | "Address should contain 'ab': {}", 812 | address 813 | ); 814 | } 815 | 816 | #[test] 817 | #[should_panic(expected = "InvalidRegex")] 818 | fn test_generate_regex_invalid_syntax() { 819 | let pattern = "^(abc"; 820 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 821 | } 822 | 823 | #[test] 824 | #[should_panic(expected = "RegexNotBase58")] 825 | fn test_generate_regex_invalid_characters() { 826 | let pattern = "^ghO"; 827 | let _ = VanityAddr::generate_regex::(pattern, 4).unwrap(); 828 | } 829 | 830 | #[test] 831 | fn test_generate_regex_starts_with_e() { 832 | let pattern = "^e"; 833 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 834 | let address = keys_and_address.get_address(); 835 | 836 | assert!( 837 | address.starts_with("e"), 838 | "Address should start with 'e': {}", 839 | address 840 | ); 841 | } 842 | 843 | #[test] 844 | fn test_generate_regex_contains_11() { 845 | let pattern = ".*11.*"; 846 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 847 | let address = keys_and_address.get_address(); 848 | 849 | assert!( 850 | address.contains("11"), 851 | "Address should contain '11': {}", 852 | address 853 | ); 854 | } 855 | 856 | #[test] 857 | fn test_generate_regex_contains_22() { 858 | let pattern = ".*22.*"; 859 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 860 | let address = keys_and_address.get_address(); 861 | 862 | assert!( 863 | address.contains("22"), 864 | "Address should contain '22': {}", 865 | address 866 | ); 867 | } 868 | 869 | #[test] 870 | fn test_generate_regex_ends_with_t() { 871 | let pattern = "t$"; 872 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 873 | let address = keys_and_address.get_address(); 874 | 875 | assert!( 876 | address.ends_with("t"), 877 | "Address should end with 't': {}", 878 | address 879 | ); 880 | } 881 | 882 | #[test] 883 | fn test_generate_regex_complex_sequence() { 884 | let pattern = "11.*22"; 885 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 886 | let address = keys_and_address.get_address(); 887 | 888 | assert!( 889 | address.contains("11") && address.contains("22"), 890 | "Address should contain '11' followed by '22': {}", 891 | address 892 | ); 893 | } 894 | 895 | #[test] 896 | fn test_generate_regex_complex_pattern() { 897 | let pattern = "^e.*9.*t$"; 898 | let keys_and_address = VanityAddr::generate_regex::(pattern, 4).unwrap(); 899 | let address = keys_and_address.get_address(); 900 | 901 | assert!( 902 | address.starts_with("e"), 903 | "Address should start with 'e': {}", 904 | address 905 | ); 906 | assert!( 907 | address.contains("9"), 908 | "Address should contain '9': {}", 909 | address 910 | ); 911 | assert!( 912 | address.ends_with("t"), 913 | "Address should end with 't': {}", 914 | address 915 | ); 916 | } 917 | } 918 | } 919 | --------------------------------------------------------------------------------