From 7e445e8b5592b3044c9178d4df911c82799218a1 Mon Sep 17 00:00:00 2001 From: primata Date: Tue, 13 May 2025 21:34:18 -0300 Subject: [PATCH 01/11] vanity move address --- .cargo/config.toml | 16 +++++------ Cargo.lock | 13 +++++++++ Cargo.toml | 1 + util/vanity/.gitignore | 1 + util/vanity/Cargo.toml | 13 +++++++++ util/vanity/README.md | 18 ++++++++++++ util/vanity/src/cli.rs | 61 ++++++++++++++++++++++++++++++++++++++++ util/vanity/src/main.rs | 33 ++++++++++++++++++++++ util/vanity/src/miner.rs | 55 ++++++++++++++++++++++++++++++++++++ 9 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 util/vanity/.gitignore create mode 100644 util/vanity/Cargo.toml create mode 100644 util/vanity/README.md create mode 100644 util/vanity/src/cli.rs create mode 100644 util/vanity/src/main.rs create mode 100644 util/vanity/src/miner.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 89c09e073..c6c88fc01 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -29,8 +29,8 @@ rustflags = [ rustflags = [ "--cfg", "tokio_unstable", - "-C", - "link-arg=-fuse-ld=lld", + # "-C", + # "link-arg=-fuse-ld=lld", "-C", "force-frame-pointers=yes", "-C", @@ -43,8 +43,8 @@ rustflags = [ rustflags = [ "--cfg", "tokio_unstable", - "-C", - "link-arg=-fuse-ld=lld", + # "-C", + # "link-arg=-fuse-ld=lld", "-C", "force-frame-pointers=yes", "-C", @@ -70,8 +70,8 @@ rustflags = [ rustflags = [ "--cfg", "tokio_unstable", - "-C", - "link-arg=-fuse-ld=lld", + # "-C", + # "link-arg=-fuse-ld=lld", "-C", "force-frame-pointers=yes", "-C", @@ -85,8 +85,8 @@ rustflags = [ rustflags = [ "--cfg", "tokio_unstable", - "-C", - "link-arg=-fuse-ld=lld", + # "-C", + # "link-arg=-fuse-ld=lld", "-C", "force-frame-pointers=yes", "-C", diff --git a/Cargo.lock b/Cargo.lock index a58be7295..505157804 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17561,6 +17561,19 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vanity" +version = "0.2.1" +dependencies = [ + "aptos-sdk", + "clap 4.5.21", + "hex", + "num_cpus", + "rand 0.7.3", + "rayon", + "thiserror 1.0.69", +] + [[package]] name = "variant_count" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index a5242b82e..27b757b91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ members = [ "util/signing/providers/aws-kms", "util/signing/providers/hashicorp-vault", "util/signing/testing", + "util/vanity", "demo/hsm", "protocol-units/execution/maptos/framework/releases/*", "protocol-units/execution/maptos/framework/migrations/*", diff --git a/util/vanity/.gitignore b/util/vanity/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/util/vanity/.gitignore @@ -0,0 +1 @@ +/target diff --git a/util/vanity/Cargo.toml b/util/vanity/Cargo.toml new file mode 100644 index 000000000..1a8f05964 --- /dev/null +++ b/util/vanity/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "vanity" +version = "0.2.1" +edition = "2021" + +[dependencies] +aptos-sdk = { workspace = true } +rand = { workspace = true } +rayon = "1.7" +hex = "0.4" +clap = { version = "4.5", features = ["derive"] } +thiserror = "1.0" +num_cpus = "1.13" \ No newline at end of file diff --git a/util/vanity/README.md b/util/vanity/README.md new file mode 100644 index 000000000..d02958685 --- /dev/null +++ b/util/vanity/README.md @@ -0,0 +1,18 @@ +# Vanity 🧂⛏️ + +Vanity is a CLI for mining vanity move addresses. + +Vanity is heavely inspired by [Create2Crunch](https://github.com/0age/create2crunch). + +## Installation + +```bash +git clone https://github.com/movementlabsxyz/movement.git +cd movement +# Run it directly +cargo run -p vanity --release -- move --starts-pattern --ends-pattern + +cd util/vanity +# Add it to your path +cargo install --path . +``` diff --git a/util/vanity/src/cli.rs b/util/vanity/src/cli.rs new file mode 100644 index 000000000..c0f054bbd --- /dev/null +++ b/util/vanity/src/cli.rs @@ -0,0 +1,61 @@ +use std::{ops::Deref, str::FromStr}; + +/// Pattern. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(super) struct Pattern(Box); + +impl Deref for Pattern { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Pattern { + pub(super) fn into_bytes(self) -> Result, hex::FromHexError> { + let mut string = self.to_string(); + + if self.len() % 2 != 0 { + string += "0" + }; + + hex::decode(string) + } +} + +/// Pattern errors. +#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)] +pub(super) enum PatternError { + #[error("the pattern's length exceeds 39 characters or the pattern is empty")] + InvalidPatternLength, + #[error("the pattern is not in hexadecimal format")] + NonHexPattern, +} + +impl FromStr for Pattern { + type Err = PatternError; + + fn from_str(s: &str) -> Result { + if s.len() >= 40 || s.is_empty() { + return Err(PatternError::InvalidPatternLength); + } + + if s.chars().any(|c| !c.is_ascii_hexdigit()) { + return Err(PatternError::NonHexPattern); + } + + Ok(Self(s.into())) + } +} + +#[derive(Clone, Debug, clap::Parser)] +#[command(name = "vanity", about = "Vanity is a fast vanity address miner.")] +pub(super) enum Vanity { + Move { + #[clap(long)] + starts_pattern: Option, + #[clap(long)] + ends_pattern: Option, + }, +} \ No newline at end of file diff --git a/util/vanity/src/main.rs b/util/vanity/src/main.rs new file mode 100644 index 000000000..19e8da5e5 --- /dev/null +++ b/util/vanity/src/main.rs @@ -0,0 +1,33 @@ +mod cli; +mod miner; + +use std::str::FromStr; +use clap::Parser; +use cli::{Pattern, Vanity}; +use miner::mine_move_address; +use num_cpus; + +fn main() -> Result<(), Box> { + match Vanity::parse() { + Vanity::Move { starts_pattern, ends_pattern } => { + let starts_pattern_bytes = match starts_pattern { + Some(p) => Pattern::from_str(&p)?.into_bytes()?, + None => vec![], + }; + let ends_pattern_bytes = match ends_pattern { + Some(p) => Pattern::from_str(&p)?.into_bytes()?, + None => vec![], + }; + + let account = mine_move_address( + &starts_pattern_bytes, + &ends_pattern_bytes, + num_cpus::get(), + ); + + println!("Found Move address: {}", account.address()); + println!("Private key (hex): {}", hex::encode(account.private_key().to_bytes())); + return Ok(()); + } + } +} diff --git a/util/vanity/src/miner.rs b/util/vanity/src/miner.rs new file mode 100644 index 000000000..d3c915191 --- /dev/null +++ b/util/vanity/src/miner.rs @@ -0,0 +1,55 @@ +use aptos_sdk::types::{account_address::AccountAddress, LocalAccount}; +use rand::thread_rng; +use rayon::prelude::*; +use rayon::ThreadPoolBuilder; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, +}; +use std::time::Instant; + +fn matches_pattern(addr: &AccountAddress, start: &[u8], end: &[u8]) -> bool { + let addr_bytes = addr.to_vec(); + (start.is_empty() || addr_bytes.starts_with(start)) && + (end.is_empty() || addr_bytes.ends_with(end)) +} + +pub fn mine_move_address( + starts_pattern: &[u8], + ends_pattern: &[u8], + threads: usize, +) -> LocalAccount { + let found = Arc::new(AtomicBool::new(false)); + let result = Arc::new(Mutex::new(None)); + let start_time = Instant::now(); + + let pool = ThreadPoolBuilder::new() + .num_threads(threads) + .build() + .expect("Failed to build thread pool"); + + pool.install(|| { + (0u64..=u64::MAX) + .into_par_iter() + .find_any(|_| { + if found.load(Ordering::Relaxed) { + return false; + } + let mut rng = thread_rng(); + let account = LocalAccount::generate(&mut rng); + if matches_pattern(&account.address(), starts_pattern, ends_pattern) { + let mut lock = result.lock().unwrap(); + *lock = Some(account); + found.store(true, Ordering::Relaxed); + true + } else { + false + } + }); + }); + + println!("✅ Mining completed in {:?}", start_time.elapsed()); + + let final_result = std::mem::take(&mut *result.lock().unwrap()); + final_result.expect("No matching account found") +} From 51575dd28d8a43d16b92959cfd029673188931cf Mon Sep 17 00:00:00 2001 From: primata Date: Tue, 13 May 2025 21:37:51 -0300 Subject: [PATCH 02/11] specify flag issue --- .cargo/config.toml | 16 ++++++++-------- util/vanity/README.md | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index c6c88fc01..89c09e073 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -29,8 +29,8 @@ rustflags = [ rustflags = [ "--cfg", "tokio_unstable", - # "-C", - # "link-arg=-fuse-ld=lld", + "-C", + "link-arg=-fuse-ld=lld", "-C", "force-frame-pointers=yes", "-C", @@ -43,8 +43,8 @@ rustflags = [ rustflags = [ "--cfg", "tokio_unstable", - # "-C", - # "link-arg=-fuse-ld=lld", + "-C", + "link-arg=-fuse-ld=lld", "-C", "force-frame-pointers=yes", "-C", @@ -70,8 +70,8 @@ rustflags = [ rustflags = [ "--cfg", "tokio_unstable", - # "-C", - # "link-arg=-fuse-ld=lld", + "-C", + "link-arg=-fuse-ld=lld", "-C", "force-frame-pointers=yes", "-C", @@ -85,8 +85,8 @@ rustflags = [ rustflags = [ "--cfg", "tokio_unstable", - # "-C", - # "link-arg=-fuse-ld=lld", + "-C", + "link-arg=-fuse-ld=lld", "-C", "force-frame-pointers=yes", "-C", diff --git a/util/vanity/README.md b/util/vanity/README.md index d02958685..5d568094d 100644 --- a/util/vanity/README.md +++ b/util/vanity/README.md @@ -16,3 +16,5 @@ cd util/vanity # Add it to your path cargo install --path . ``` + +*Currently requires ignoring all instances of "-C", "link-arg=-fuse-ld=lld" flags in .cargo/config.toml* \ No newline at end of file From efdf8aed5cd3c3bd24a40058cd8ffc5ea48620ea Mon Sep 17 00:00:00 2001 From: Primata Date: Tue, 13 May 2025 22:06:01 -0300 Subject: [PATCH 03/11] Update util/vanity/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- util/vanity/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/vanity/README.md b/util/vanity/README.md index 5d568094d..684be5ca8 100644 --- a/util/vanity/README.md +++ b/util/vanity/README.md @@ -2,7 +2,7 @@ Vanity is a CLI for mining vanity move addresses. -Vanity is heavely inspired by [Create2Crunch](https://github.com/0age/create2crunch). +Vanity is heavily inspired by [Create2Crunch](https://github.com/0age/create2crunch). ## Installation From 7ae6945e1411315ed73369ed604743e9f2a39319 Mon Sep 17 00:00:00 2001 From: primata Date: Tue, 13 May 2025 22:32:48 -0300 Subject: [PATCH 04/11] workspace deps --- util/vanity/Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/util/vanity/Cargo.toml b/util/vanity/Cargo.toml index 1a8f05964..b8f938241 100644 --- a/util/vanity/Cargo.toml +++ b/util/vanity/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" [dependencies] aptos-sdk = { workspace = true } rand = { workspace = true } -rayon = "1.7" -hex = "0.4" -clap = { version = "4.5", features = ["derive"] } -thiserror = "1.0" -num_cpus = "1.13" \ No newline at end of file +rayon = { workspace = true } +hex = { workspace = true } +clap = { workspace = true } +thiserror = { workspace = true } +num_cpus = { workspace = true } \ No newline at end of file From 31a1cc29009c4b83bdde15e481ae3537e3b2807a Mon Sep 17 00:00:00 2001 From: primata Date: Wed, 14 May 2025 11:25:26 -0300 Subject: [PATCH 05/11] fix: works in nix --- util/vanity/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/util/vanity/README.md b/util/vanity/README.md index 5d568094d..d02958685 100644 --- a/util/vanity/README.md +++ b/util/vanity/README.md @@ -16,5 +16,3 @@ cd util/vanity # Add it to your path cargo install --path . ``` - -*Currently requires ignoring all instances of "-C", "link-arg=-fuse-ld=lld" flags in .cargo/config.toml* \ No newline at end of file From 2785ce8d71e51e15d56b592a8efd1462fe623809 Mon Sep 17 00:00:00 2001 From: primata Date: Wed, 14 May 2025 12:15:23 -0300 Subject: [PATCH 06/11] fix: tracing --- Cargo.lock | 2 ++ util/vanity/Cargo.toml | 4 +++- util/vanity/README.md | 2 +- util/vanity/src/main.rs | 8 ++++++-- util/vanity/src/miner.rs | 3 ++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 505157804..8bb08c5b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17572,6 +17572,8 @@ dependencies = [ "rand 0.7.3", "rayon", "thiserror 1.0.69", + "tracing", + "tracing-subscriber 0.3.18", ] [[package]] diff --git a/util/vanity/Cargo.toml b/util/vanity/Cargo.toml index b8f938241..3630ed3a9 100644 --- a/util/vanity/Cargo.toml +++ b/util/vanity/Cargo.toml @@ -10,4 +10,6 @@ rayon = { workspace = true } hex = { workspace = true } clap = { workspace = true } thiserror = { workspace = true } -num_cpus = { workspace = true } \ No newline at end of file +num_cpus = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } \ No newline at end of file diff --git a/util/vanity/README.md b/util/vanity/README.md index 1b89180d8..8d3320d23 100644 --- a/util/vanity/README.md +++ b/util/vanity/README.md @@ -10,7 +10,7 @@ Vanity is heavily inspired by [Create2Crunch](https://github.com/0age/create2cru git clone https://github.com/movementlabsxyz/movement.git cd movement # Run it directly -cargo run -p vanity --release -- move --starts-pattern --ends-pattern +RUST_LOG=info cargo run -p vanity --release -- move --starts-pattern --ends-pattern cd util/vanity # Add it to your path diff --git a/util/vanity/src/main.rs b/util/vanity/src/main.rs index 19e8da5e5..1e0d2b054 100644 --- a/util/vanity/src/main.rs +++ b/util/vanity/src/main.rs @@ -6,8 +6,12 @@ use clap::Parser; use cli::{Pattern, Vanity}; use miner::mine_move_address; use num_cpus; +use tracing; fn main() -> Result<(), Box> { + use tracing_subscriber::EnvFilter; + tracing_subscriber::fmt().with_writer(std::io::stdout).init(); + match Vanity::parse() { Vanity::Move { starts_pattern, ends_pattern } => { let starts_pattern_bytes = match starts_pattern { @@ -25,8 +29,8 @@ fn main() -> Result<(), Box> { num_cpus::get(), ); - println!("Found Move address: {}", account.address()); - println!("Private key (hex): {}", hex::encode(account.private_key().to_bytes())); + tracing::info!("Found Move address: {}", account.address()); + tracing::info!("Private key (hex): {}", hex::encode(account.private_key().to_bytes())); return Ok(()); } } diff --git a/util/vanity/src/miner.rs b/util/vanity/src/miner.rs index d3c915191..882945457 100644 --- a/util/vanity/src/miner.rs +++ b/util/vanity/src/miner.rs @@ -7,6 +7,7 @@ use std::sync::{ Arc, Mutex, }; use std::time::Instant; +use tracing; fn matches_pattern(addr: &AccountAddress, start: &[u8], end: &[u8]) -> bool { let addr_bytes = addr.to_vec(); @@ -48,7 +49,7 @@ pub fn mine_move_address( }); }); - println!("✅ Mining completed in {:?}", start_time.elapsed()); + tracing::info!("Mining completed in {:?}", start_time.elapsed()); let final_result = std::mem::take(&mut *result.lock().unwrap()); final_result.expect("No matching account found") From b5085df92ffe384588c21ae19375bcdde6da9c7f Mon Sep 17 00:00:00 2001 From: primata Date: Wed, 14 May 2025 23:42:11 -0300 Subject: [PATCH 07/11] revert tracing --- Cargo.lock | 1 - util/vanity/Cargo.toml | 3 +-- util/vanity/src/main.rs | 6 ++---- util/vanity/src/miner.rs | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bb08c5b1..1953a7c40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17573,7 +17573,6 @@ dependencies = [ "rayon", "thiserror 1.0.69", "tracing", - "tracing-subscriber 0.3.18", ] [[package]] diff --git a/util/vanity/Cargo.toml b/util/vanity/Cargo.toml index 3630ed3a9..bc2b41dc7 100644 --- a/util/vanity/Cargo.toml +++ b/util/vanity/Cargo.toml @@ -11,5 +11,4 @@ hex = { workspace = true } clap = { workspace = true } thiserror = { workspace = true } num_cpus = { workspace = true } -tracing = { workspace = true } -tracing-subscriber = { workspace = true } \ No newline at end of file +tracing = { workspace = true } \ No newline at end of file diff --git a/util/vanity/src/main.rs b/util/vanity/src/main.rs index 1e0d2b054..5bd3faaa9 100644 --- a/util/vanity/src/main.rs +++ b/util/vanity/src/main.rs @@ -9,8 +9,6 @@ use num_cpus; use tracing; fn main() -> Result<(), Box> { - use tracing_subscriber::EnvFilter; - tracing_subscriber::fmt().with_writer(std::io::stdout).init(); match Vanity::parse() { Vanity::Move { starts_pattern, ends_pattern } => { @@ -29,8 +27,8 @@ fn main() -> Result<(), Box> { num_cpus::get(), ); - tracing::info!("Found Move address: {}", account.address()); - tracing::info!("Private key (hex): {}", hex::encode(account.private_key().to_bytes())); + println!("Found Move address: {}", account.address()); + println!("Private key (hex): {}", hex::encode(account.private_key().to_bytes())); return Ok(()); } } diff --git a/util/vanity/src/miner.rs b/util/vanity/src/miner.rs index 882945457..015acf8dd 100644 --- a/util/vanity/src/miner.rs +++ b/util/vanity/src/miner.rs @@ -49,7 +49,7 @@ pub fn mine_move_address( }); }); - tracing::info!("Mining completed in {:?}", start_time.elapsed()); + println!("Mining completed in {:?}", start_time.elapsed()); let final_result = std::mem::take(&mut *result.lock().unwrap()); final_result.expect("No matching account found") From 7066565033fc4a2e783c96c5997d29c1dd9592c2 Mon Sep 17 00:00:00 2001 From: primata Date: Mon, 19 May 2025 13:55:29 -0300 Subject: [PATCH 08/11] add cli tests --- util/vanity/Cargo.toml | 10 +++++- util/vanity/src/cli.rs | 73 +++++++++++++++++++++++++++++++++++++++++ util/vanity/src/main.rs | 1 - 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/util/vanity/Cargo.toml b/util/vanity/Cargo.toml index bc2b41dc7..1bf8e8f80 100644 --- a/util/vanity/Cargo.toml +++ b/util/vanity/Cargo.toml @@ -11,4 +11,12 @@ hex = { workspace = true } clap = { workspace = true } thiserror = { workspace = true } num_cpus = { workspace = true } -tracing = { workspace = true } \ No newline at end of file +tracing = { workspace = true } + +[dev-dependencies] +assert_cmd = "2" +predicates = "3" + +[[bin]] +name = "vanity" +path = "src/main.rs" diff --git a/util/vanity/src/cli.rs b/util/vanity/src/cli.rs index c0f054bbd..aa42bee89 100644 --- a/util/vanity/src/cli.rs +++ b/util/vanity/src/cli.rs @@ -58,4 +58,77 @@ pub(super) enum Vanity { #[clap(long)] ends_pattern: Option, }, +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use clap::Parser; // <- THIS is what you're missing! + use assert_cmd::Command; + use predicates::prelude::*; + + #[test] + fn test_valid_pattern() { + let p = Pattern::from_str("abcd").unwrap(); + assert_eq!(&*p, "abcd"); + } + + #[test] + fn test_cli_parsing_starts_pattern() { + let cli = Vanity::parse_from([ + "vanity", + "move", + "--starts-pattern", + "abcd", + ]); + match cli { + Vanity::Move { starts_pattern, ends_pattern } => { + assert_eq!(starts_pattern, Some("abcd".to_string())); + assert_eq!(ends_pattern, None); + } + } + } + + #[test] +fn test_cli_runs_with_starts() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--starts-pattern", "de"]).assert().success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); +} + +#[test] +fn test_cli_runs_with_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--ends-pattern", "f0"]).assert().success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); +} + +#[test] +fn test_cli_runs_with_starts_and_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd + .args(&["move", "--starts-pattern", "de", "--ends-pattern", "f0"]) + .assert() + .success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); +} + } \ No newline at end of file diff --git a/util/vanity/src/main.rs b/util/vanity/src/main.rs index 5bd3faaa9..b7758e22c 100644 --- a/util/vanity/src/main.rs +++ b/util/vanity/src/main.rs @@ -6,7 +6,6 @@ use clap::Parser; use cli::{Pattern, Vanity}; use miner::mine_move_address; use num_cpus; -use tracing; fn main() -> Result<(), Box> { From dc4edb5b60f871eb6d3202bf8d4336dfe4387504 Mon Sep 17 00:00:00 2001 From: primata Date: Tue, 20 May 2025 21:45:40 -0300 Subject: [PATCH 09/11] feat: vanity resource address --- util/vanity/Cargo.toml | 1 + util/vanity/README.md | 2 +- util/vanity/src/cli.rs | 236 ++++++++++++++++++++++++--------------- util/vanity/src/main.rs | 64 ++++++++--- util/vanity/src/miner.rs | 64 +++++++++-- 5 files changed, 252 insertions(+), 115 deletions(-) diff --git a/util/vanity/Cargo.toml b/util/vanity/Cargo.toml index 1bf8e8f80..90e69705e 100644 --- a/util/vanity/Cargo.toml +++ b/util/vanity/Cargo.toml @@ -17,6 +17,7 @@ tracing = { workspace = true } assert_cmd = "2" predicates = "3" + [[bin]] name = "vanity" path = "src/main.rs" diff --git a/util/vanity/README.md b/util/vanity/README.md index 8d3320d23..6c6d18b54 100644 --- a/util/vanity/README.md +++ b/util/vanity/README.md @@ -10,7 +10,7 @@ Vanity is heavily inspired by [Create2Crunch](https://github.com/0age/create2cru git clone https://github.com/movementlabsxyz/movement.git cd movement # Run it directly -RUST_LOG=info cargo run -p vanity --release -- move --starts-pattern --ends-pattern +RUST_LOG=info cargo run -p vanity --release -- move --starts --ends cd util/vanity # Add it to your path diff --git a/util/vanity/src/cli.rs b/util/vanity/src/cli.rs index aa42bee89..eee8d710c 100644 --- a/util/vanity/src/cli.rs +++ b/util/vanity/src/cli.rs @@ -1,134 +1,186 @@ use std::{ops::Deref, str::FromStr}; +use clap::{Parser, Subcommand}; -/// Pattern. +/// Pattern type for hex string filtering. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(super) struct Pattern(Box); +pub struct Pattern(Box); impl Deref for Pattern { - type Target = str; + type Target = str; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl Pattern { - pub(super) fn into_bytes(self) -> Result, hex::FromHexError> { - let mut string = self.to_string(); - - if self.len() % 2 != 0 { - string += "0" - }; - - hex::decode(string) - } + /// Return the raw hex string without trying to decode it. + pub fn as_str(&self) -> &str { + &self.0 + } } -/// Pattern errors. +/// Pattern parsing errors. #[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)] -pub(super) enum PatternError { - #[error("the pattern's length exceeds 39 characters or the pattern is empty")] - InvalidPatternLength, - #[error("the pattern is not in hexadecimal format")] - NonHexPattern, +pub enum PatternError { + #[error("the pattern's length exceeds 39 characters or the pattern is empty")] + InvalidPatternLength, + #[error("the pattern is not in hexadecimal format")] + NonHexPattern, } impl FromStr for Pattern { - type Err = PatternError; - - fn from_str(s: &str) -> Result { - if s.len() >= 40 || s.is_empty() { - return Err(PatternError::InvalidPatternLength); - } - - if s.chars().any(|c| !c.is_ascii_hexdigit()) { - return Err(PatternError::NonHexPattern); - } - - Ok(Self(s.into())) - } + type Err = PatternError; + + fn from_str(s: &str) -> Result { + if s.len() >= 40 || s.is_empty() { + return Err(PatternError::InvalidPatternLength); + } + if s.chars().any(|c| !c.is_ascii_hexdigit()) { + return Err(PatternError::NonHexPattern); + } + Ok(Self(s.into())) + } } -#[derive(Clone, Debug, clap::Parser)] +/// CLI definition for the `vanity` tool. +#[derive(Parser, Debug)] #[command(name = "vanity", about = "Vanity is a fast vanity address miner.")] -pub(super) enum Vanity { - Move { +pub struct Cli { + #[command(subcommand)] + pub command: Vanity, +} + +/// Supported CLI subcommands. +#[derive(Debug, Subcommand)] +pub enum Vanity { + Move { + #[clap(long)] + starts: Option, #[clap(long)] - starts_pattern: Option, + ends: Option, + }, + Resource { #[clap(long)] - ends_pattern: Option, - }, + address: Option, + #[clap(long)] + starts: Option, + #[clap(long)] + ends: Option, + }, } - #[cfg(test)] mod tests { - use super::*; - use std::str::FromStr; - use clap::Parser; // <- THIS is what you're missing! - use assert_cmd::Command; - use predicates::prelude::*; + use super::{Cli, Pattern, Vanity}; + use std::str::FromStr; + use clap::Parser; + use assert_cmd::Command; - #[test] + #[test] fn test_valid_pattern() { let p = Pattern::from_str("abcd").unwrap(); assert_eq!(&*p, "abcd"); } #[test] - fn test_cli_parsing_starts_pattern() { - let cli = Vanity::parse_from([ - "vanity", - "move", - "--starts-pattern", - "abcd", - ]); - match cli { - Vanity::Move { starts_pattern, ends_pattern } => { - assert_eq!(starts_pattern, Some("abcd".to_string())); - assert_eq!(ends_pattern, None); - } - } - } + fn test_cli_parsing_starts() { + let cli = Cli::parse_from([ + "vanity", + "move", + "--starts", + "abcd", + ]); + match cli.command { + Vanity::Move { starts, ends } => { + assert_eq!(starts, Some("abcd".to_string())); + assert_eq!(ends, None); + } + _ => panic!("Expected Move variant"), + } + } #[test] -fn test_cli_runs_with_starts() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd.args(&["move", "--starts-pattern", "de"]).assert().success(); + fn test_resource_parsing_starts() { + let cli = Cli::parse_from([ + "vanity", + "resource", + "--address", + "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", + "--starts", + "abcd", + ]); + match cli.command { + Vanity::Resource { address, starts, ends } => { + assert_eq!(address, Some("0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f".to_string())); + assert_eq!(starts, Some("abcd".to_string())); + assert_eq!(ends, None); + } + _ => panic!("Expected Resource variant"), + } + } - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + #[test] + fn test_cli_runs_with_starts() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--starts", "de"]).assert().success(); - assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); -} + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); -#[test] -fn test_cli_runs_with_ends() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd.args(&["move", "--ends-pattern", "f0"]).assert().success(); + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + } - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + #[test] + fn test_cli_runs_with_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--ends", "f0"]).assert().success(); - assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); -} + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); -#[test] -fn test_cli_runs_with_starts_and_ends() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd - .args(&["move", "--starts-pattern", "de", "--ends-pattern", "f0"]) - .assert() - .success(); + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + #[test] + fn test_cli_runs_with_starts_and_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd + .args(&["move", "--starts", "de", "--ends", "f0"]) + .assert() + .success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } - assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); - assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + #[test] + fn test_resource_runs_with_starts_and_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd + .args(&[ + "resource", + "--address", + "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", + "--starts", + "de", + "--ends", + "f0", + ]) + .assert() + .success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Resource Address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } } - -} \ No newline at end of file diff --git a/util/vanity/src/main.rs b/util/vanity/src/main.rs index b7758e22c..844e0d846 100644 --- a/util/vanity/src/main.rs +++ b/util/vanity/src/main.rs @@ -1,34 +1,72 @@ mod cli; mod miner; +use aptos_sdk::types::account_address::AccountAddress; use std::str::FromStr; use clap::Parser; -use cli::{Pattern, Vanity}; -use miner::mine_move_address; + +use cli::{Cli, Vanity, Pattern}; + +use miner::{mine_move_address, mine_resource_address}; use num_cpus; fn main() -> Result<(), Box> { + let cli = Cli::parse(); - match Vanity::parse() { - Vanity::Move { starts_pattern, ends_pattern } => { - let starts_pattern_bytes = match starts_pattern { - Some(p) => Pattern::from_str(&p)?.into_bytes()?, - None => vec![], + match cli.command { + Vanity::Move { starts, ends } => { + let starts_str = if let Some(s) = starts { + Pattern::from_str(&s)?; + s + } else { + String::new() }; - let ends_pattern_bytes = match ends_pattern { - Some(p) => Pattern::from_str(&p)?.into_bytes()?, - None => vec![], + let ends_str = if let Some(s) = ends { + Pattern::from_str(&s)?; + s + } else { + String::new() }; let account = mine_move_address( - &starts_pattern_bytes, - &ends_pattern_bytes, + &starts_str, + &ends_str, num_cpus::get(), ); println!("Found Move address: {}", account.address()); println!("Private key (hex): {}", hex::encode(account.private_key().to_bytes())); - return Ok(()); + }, + Vanity::Resource { address, starts, ends } => { + let address = match address { + Some(ref a) => AccountAddress::from_str(a)?, + None => return Err("Move address is required".into()), + }; + + let starts_str = if let Some(s) = starts { + Pattern::from_str(&s)?; + s + } else { + String::new() + }; + let ends_str = if let Some(s) = ends { + Pattern::from_str(&s)?; + s + } else { + String::new() + }; + + let (resource_address, seed) = mine_resource_address( + &address, + &starts_str, + &ends_str, + num_cpus::get(), + ); + + println!("Found Resource Address: {}", resource_address); + println!("Seed: {}", hex::encode(seed)); } } + + Ok(()) } diff --git a/util/vanity/src/miner.rs b/util/vanity/src/miner.rs index 015acf8dd..808637561 100644 --- a/util/vanity/src/miner.rs +++ b/util/vanity/src/miner.rs @@ -1,5 +1,6 @@ -use aptos_sdk::types::{account_address::AccountAddress, LocalAccount}; +use aptos_sdk::types::{account_address::{AccountAddress, create_resource_address}, LocalAccount}; use rand::thread_rng; +use rand::Rng; use rayon::prelude::*; use rayon::ThreadPoolBuilder; use std::sync::{ @@ -7,17 +8,19 @@ use std::sync::{ Arc, Mutex, }; use std::time::Instant; -use tracing; -fn matches_pattern(addr: &AccountAddress, start: &[u8], end: &[u8]) -> bool { - let addr_bytes = addr.to_vec(); - (start.is_empty() || addr_bytes.starts_with(start)) && - (end.is_empty() || addr_bytes.ends_with(end)) +fn matches(addr: &AccountAddress, start: &str, end: &str) -> bool { + let addr_hex = hex::encode(addr); + + (start.is_empty() || addr_hex.starts_with(start)) && + (end.is_empty() || addr_hex.ends_with(end)) } + + pub fn mine_move_address( - starts_pattern: &[u8], - ends_pattern: &[u8], + starts: &str, + ends: &str, threads: usize, ) -> LocalAccount { let found = Arc::new(AtomicBool::new(false)); @@ -38,7 +41,7 @@ pub fn mine_move_address( } let mut rng = thread_rng(); let account = LocalAccount::generate(&mut rng); - if matches_pattern(&account.address(), starts_pattern, ends_pattern) { + if matches(&account.address(), starts, ends) { let mut lock = result.lock().unwrap(); *lock = Some(account); found.store(true, Ordering::Relaxed); @@ -54,3 +57,46 @@ pub fn mine_move_address( let final_result = std::mem::take(&mut *result.lock().unwrap()); final_result.expect("No matching account found") } + +pub fn mine_resource_address( + address: &AccountAddress, + starts: &str, + ends: &str, + threads: usize, +) -> (AccountAddress, Vec) { + let found = Arc::new(AtomicBool::new(false)); + let result = Arc::new(Mutex::new(None)); + let start_time = Instant::now(); + + let pool = ThreadPoolBuilder::new() + .num_threads(threads) + .build() + .expect("Failed to build thread pool"); + + pool.install(|| { + (0u64..=u64::MAX) + .into_par_iter() + .find_any(|_| { + if found.load(Ordering::Relaxed) { + return false; + } + let mut rng = thread_rng(); + let mut seed_bytes = [0u8; 32]; // or any size you need + rng.fill(&mut seed_bytes); + let resource_address = create_resource_address(*address, &seed_bytes); + if matches(&resource_address, starts, ends) { + let mut lock = result.lock().unwrap(); + *lock = Some((resource_address, seed_bytes.to_vec())); + found.store(true, Ordering::Relaxed); + true + } else { + false + } + }); + }); + + println!("Mining completed in {:?}", start_time.elapsed()); + + let final_result = std::mem::take(&mut *result.lock().unwrap()); + final_result.expect("No matching account found") +} \ No newline at end of file From 76a8442eab42c04287b4f6fcb208de8c7dd6ba46 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Wed, 21 May 2025 06:17:50 -0400 Subject: [PATCH 10/11] chore: cargo fmt --- util/vanity/src/cli.rs | 300 +++++++++++++++++++-------------------- util/vanity/src/main.rs | 98 ++++++------- util/vanity/src/miner.rs | 148 +++++++++---------- 3 files changed, 264 insertions(+), 282 deletions(-) diff --git a/util/vanity/src/cli.rs b/util/vanity/src/cli.rs index eee8d710c..f30891e74 100644 --- a/util/vanity/src/cli.rs +++ b/util/vanity/src/cli.rs @@ -1,186 +1,184 @@ -use std::{ops::Deref, str::FromStr}; use clap::{Parser, Subcommand}; +use std::{ops::Deref, str::FromStr}; /// Pattern type for hex string filtering. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Pattern(Box); impl Deref for Pattern { - type Target = str; + type Target = str; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl Pattern { - /// Return the raw hex string without trying to decode it. - pub fn as_str(&self) -> &str { - &self.0 - } + /// Return the raw hex string without trying to decode it. + pub fn as_str(&self) -> &str { + &self.0 + } } /// Pattern parsing errors. #[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)] pub enum PatternError { - #[error("the pattern's length exceeds 39 characters or the pattern is empty")] - InvalidPatternLength, - #[error("the pattern is not in hexadecimal format")] - NonHexPattern, + #[error("the pattern's length exceeds 39 characters or the pattern is empty")] + InvalidPatternLength, + #[error("the pattern is not in hexadecimal format")] + NonHexPattern, } impl FromStr for Pattern { - type Err = PatternError; - - fn from_str(s: &str) -> Result { - if s.len() >= 40 || s.is_empty() { - return Err(PatternError::InvalidPatternLength); - } - if s.chars().any(|c| !c.is_ascii_hexdigit()) { - return Err(PatternError::NonHexPattern); - } - Ok(Self(s.into())) - } + type Err = PatternError; + + fn from_str(s: &str) -> Result { + if s.len() >= 40 || s.is_empty() { + return Err(PatternError::InvalidPatternLength); + } + if s.chars().any(|c| !c.is_ascii_hexdigit()) { + return Err(PatternError::NonHexPattern); + } + Ok(Self(s.into())) + } } /// CLI definition for the `vanity` tool. #[derive(Parser, Debug)] #[command(name = "vanity", about = "Vanity is a fast vanity address miner.")] pub struct Cli { - #[command(subcommand)] - pub command: Vanity, + #[command(subcommand)] + pub command: Vanity, } /// Supported CLI subcommands. #[derive(Debug, Subcommand)] pub enum Vanity { - Move { - #[clap(long)] - starts: Option, - #[clap(long)] - ends: Option, - }, - Resource { - #[clap(long)] - address: Option, - #[clap(long)] - starts: Option, - #[clap(long)] - ends: Option, - }, + Move { + #[clap(long)] + starts: Option, + #[clap(long)] + ends: Option, + }, + Resource { + #[clap(long)] + address: Option, + #[clap(long)] + starts: Option, + #[clap(long)] + ends: Option, + }, } #[cfg(test)] mod tests { - use super::{Cli, Pattern, Vanity}; - use std::str::FromStr; - use clap::Parser; - use assert_cmd::Command; - - #[test] - fn test_valid_pattern() { - let p = Pattern::from_str("abcd").unwrap(); - assert_eq!(&*p, "abcd"); - } - - #[test] - fn test_cli_parsing_starts() { - let cli = Cli::parse_from([ - "vanity", - "move", - "--starts", - "abcd", - ]); - match cli.command { - Vanity::Move { starts, ends } => { - assert_eq!(starts, Some("abcd".to_string())); - assert_eq!(ends, None); - } - _ => panic!("Expected Move variant"), - } - } - - #[test] - fn test_resource_parsing_starts() { - let cli = Cli::parse_from([ - "vanity", - "resource", - "--address", - "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", - "--starts", - "abcd", - ]); - match cli.command { - Vanity::Resource { address, starts, ends } => { - assert_eq!(address, Some("0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f".to_string())); - assert_eq!(starts, Some("abcd".to_string())); - assert_eq!(ends, None); - } - _ => panic!("Expected Resource variant"), - } - } - - #[test] - fn test_cli_runs_with_starts() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd.args(&["move", "--starts", "de"]).assert().success(); - - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); - - assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); - } - - #[test] - fn test_cli_runs_with_ends() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd.args(&["move", "--ends", "f0"]).assert().success(); - - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); - - assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); - } - - #[test] - fn test_cli_runs_with_starts_and_ends() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd - .args(&["move", "--starts", "de", "--ends", "f0"]) - .assert() - .success(); - - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); - - assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); - assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); - } - - #[test] - fn test_resource_runs_with_starts_and_ends() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd - .args(&[ - "resource", - "--address", - "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", - "--starts", - "de", - "--ends", - "f0", - ]) - .assert() - .success(); - - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Resource Address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); - - assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); - assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); - } + use super::{Cli, Pattern, Vanity}; + use assert_cmd::Command; + use clap::Parser; + use std::str::FromStr; + + #[test] + fn test_valid_pattern() { + let p = Pattern::from_str("abcd").unwrap(); + assert_eq!(&*p, "abcd"); + } + + #[test] + fn test_cli_parsing_starts() { + let cli = Cli::parse_from(["vanity", "move", "--starts", "abcd"]); + match cli.command { + Vanity::Move { starts, ends } => { + assert_eq!(starts, Some("abcd".to_string())); + assert_eq!(ends, None); + } + _ => panic!("Expected Move variant"), + } + } + + #[test] + fn test_resource_parsing_starts() { + let cli = Cli::parse_from([ + "vanity", + "resource", + "--address", + "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", + "--starts", + "abcd", + ]); + match cli.command { + Vanity::Resource { address, starts, ends } => { + assert_eq!( + address, + Some( + "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f" + .to_string() + ) + ); + assert_eq!(starts, Some("abcd".to_string())); + assert_eq!(ends, None); + } + _ => panic!("Expected Resource variant"), + } + } + + #[test] + fn test_cli_runs_with_starts() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--starts", "de"]).assert().success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + } + + #[test] + fn test_cli_runs_with_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--ends", "f0"]).assert().success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } + + #[test] + fn test_cli_runs_with_starts_and_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--starts", "de", "--ends", "f0"]).assert().success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } + + #[test] + fn test_resource_runs_with_starts_and_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd + .args(&[ + "resource", + "--address", + "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", + "--starts", + "de", + "--ends", + "f0", + ]) + .assert() + .success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Resource Address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } } diff --git a/util/vanity/src/main.rs b/util/vanity/src/main.rs index 844e0d846..5f736ba71 100644 --- a/util/vanity/src/main.rs +++ b/util/vanity/src/main.rs @@ -2,71 +2,63 @@ mod cli; mod miner; use aptos_sdk::types::account_address::AccountAddress; -use std::str::FromStr; use clap::Parser; +use std::str::FromStr; -use cli::{Cli, Vanity, Pattern}; +use cli::{Cli, Pattern, Vanity}; use miner::{mine_move_address, mine_resource_address}; use num_cpus; fn main() -> Result<(), Box> { - let cli = Cli::parse(); + let cli = Cli::parse(); - match cli.command { - Vanity::Move { starts, ends } => { - let starts_str = if let Some(s) = starts { - Pattern::from_str(&s)?; - s - } else { - String::new() - }; - let ends_str = if let Some(s) = ends { - Pattern::from_str(&s)?; - s - } else { - String::new() - }; + match cli.command { + Vanity::Move { starts, ends } => { + let starts_str = if let Some(s) = starts { + Pattern::from_str(&s)?; + s + } else { + String::new() + }; + let ends_str = if let Some(s) = ends { + Pattern::from_str(&s)?; + s + } else { + String::new() + }; - let account = mine_move_address( - &starts_str, - &ends_str, - num_cpus::get(), - ); + let account = mine_move_address(&starts_str, &ends_str, num_cpus::get()); - println!("Found Move address: {}", account.address()); - println!("Private key (hex): {}", hex::encode(account.private_key().to_bytes())); - }, - Vanity::Resource { address, starts, ends } => { - let address = match address { - Some(ref a) => AccountAddress::from_str(a)?, - None => return Err("Move address is required".into()), - }; + println!("Found Move address: {}", account.address()); + println!("Private key (hex): {}", hex::encode(account.private_key().to_bytes())); + } + Vanity::Resource { address, starts, ends } => { + let address = match address { + Some(ref a) => AccountAddress::from_str(a)?, + None => return Err("Move address is required".into()), + }; - let starts_str = if let Some(s) = starts { - Pattern::from_str(&s)?; - s - } else { - String::new() - }; - let ends_str = if let Some(s) = ends { - Pattern::from_str(&s)?; - s - } else { - String::new() - }; + let starts_str = if let Some(s) = starts { + Pattern::from_str(&s)?; + s + } else { + String::new() + }; + let ends_str = if let Some(s) = ends { + Pattern::from_str(&s)?; + s + } else { + String::new() + }; - let (resource_address, seed) = mine_resource_address( - &address, - &starts_str, - &ends_str, - num_cpus::get(), - ); + let (resource_address, seed) = + mine_resource_address(&address, &starts_str, &ends_str, num_cpus::get()); - println!("Found Resource Address: {}", resource_address); - println!("Seed: {}", hex::encode(seed)); - } - } + println!("Found Resource Address: {}", resource_address); + println!("Seed: {}", hex::encode(seed)); + } + } - Ok(()) + Ok(()) } diff --git a/util/vanity/src/miner.rs b/util/vanity/src/miner.rs index 808637561..5eaa2fab6 100644 --- a/util/vanity/src/miner.rs +++ b/util/vanity/src/miner.rs @@ -1,102 +1,94 @@ -use aptos_sdk::types::{account_address::{AccountAddress, create_resource_address}, LocalAccount}; +use aptos_sdk::types::{ + account_address::{create_resource_address, AccountAddress}, + LocalAccount, +}; use rand::thread_rng; use rand::Rng; use rayon::prelude::*; use rayon::ThreadPoolBuilder; use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, + atomic::{AtomicBool, Ordering}, + Arc, Mutex, }; use std::time::Instant; fn matches(addr: &AccountAddress, start: &str, end: &str) -> bool { - let addr_hex = hex::encode(addr); + let addr_hex = hex::encode(addr); - (start.is_empty() || addr_hex.starts_with(start)) && - (end.is_empty() || addr_hex.ends_with(end)) + (start.is_empty() || addr_hex.starts_with(start)) && (end.is_empty() || addr_hex.ends_with(end)) } +pub fn mine_move_address(starts: &str, ends: &str, threads: usize) -> LocalAccount { + let found = Arc::new(AtomicBool::new(false)); + let result = Arc::new(Mutex::new(None)); + let start_time = Instant::now(); + let pool = ThreadPoolBuilder::new() + .num_threads(threads) + .build() + .expect("Failed to build thread pool"); -pub fn mine_move_address( - starts: &str, - ends: &str, - threads: usize, -) -> LocalAccount { - let found = Arc::new(AtomicBool::new(false)); - let result = Arc::new(Mutex::new(None)); - let start_time = Instant::now(); - - let pool = ThreadPoolBuilder::new() - .num_threads(threads) - .build() - .expect("Failed to build thread pool"); + pool.install(|| { + (0u64..=u64::MAX).into_par_iter().find_any(|_| { + if found.load(Ordering::Relaxed) { + return false; + } + let mut rng = thread_rng(); + let account = LocalAccount::generate(&mut rng); + if matches(&account.address(), starts, ends) { + let mut lock = result.lock().unwrap(); + *lock = Some(account); + found.store(true, Ordering::Relaxed); + true + } else { + false + } + }); + }); - pool.install(|| { - (0u64..=u64::MAX) - .into_par_iter() - .find_any(|_| { - if found.load(Ordering::Relaxed) { - return false; - } - let mut rng = thread_rng(); - let account = LocalAccount::generate(&mut rng); - if matches(&account.address(), starts, ends) { - let mut lock = result.lock().unwrap(); - *lock = Some(account); - found.store(true, Ordering::Relaxed); - true - } else { - false - } - }); - }); + println!("Mining completed in {:?}", start_time.elapsed()); - println!("Mining completed in {:?}", start_time.elapsed()); - - let final_result = std::mem::take(&mut *result.lock().unwrap()); - final_result.expect("No matching account found") + let final_result = std::mem::take(&mut *result.lock().unwrap()); + final_result.expect("No matching account found") } pub fn mine_resource_address( - address: &AccountAddress, - starts: &str, - ends: &str, - threads: usize, + address: &AccountAddress, + starts: &str, + ends: &str, + threads: usize, ) -> (AccountAddress, Vec) { - let found = Arc::new(AtomicBool::new(false)); - let result = Arc::new(Mutex::new(None)); - let start_time = Instant::now(); + let found = Arc::new(AtomicBool::new(false)); + let result = Arc::new(Mutex::new(None)); + let start_time = Instant::now(); - let pool = ThreadPoolBuilder::new() - .num_threads(threads) - .build() - .expect("Failed to build thread pool"); + let pool = ThreadPoolBuilder::new() + .num_threads(threads) + .build() + .expect("Failed to build thread pool"); - pool.install(|| { - (0u64..=u64::MAX) - .into_par_iter() - .find_any(|_| { - if found.load(Ordering::Relaxed) { - return false; - } - let mut rng = thread_rng(); - let mut seed_bytes = [0u8; 32]; // or any size you need - rng.fill(&mut seed_bytes); - let resource_address = create_resource_address(*address, &seed_bytes); - if matches(&resource_address, starts, ends) { - let mut lock = result.lock().unwrap(); - *lock = Some((resource_address, seed_bytes.to_vec())); - found.store(true, Ordering::Relaxed); - true - } else { - false - } - }); - }); + pool.install(|| { + (0u64..=u64::MAX).into_par_iter().find_any(|_| { + if found.load(Ordering::Relaxed) { + return false; + } + let mut rng = thread_rng(); + let mut seed_bytes = [0u8; 32]; // or any size you need + rng.fill(&mut seed_bytes); + let resource_address = create_resource_address(*address, &seed_bytes); + if matches(&resource_address, starts, ends) { + let mut lock = result.lock().unwrap(); + *lock = Some((resource_address, seed_bytes.to_vec())); + found.store(true, Ordering::Relaxed); + true + } else { + false + } + }); + }); - println!("Mining completed in {:?}", start_time.elapsed()); + println!("Mining completed in {:?}", start_time.elapsed()); - let final_result = std::mem::take(&mut *result.lock().unwrap()); - final_result.expect("No matching account found") -} \ No newline at end of file + let final_result = std::mem::take(&mut *result.lock().unwrap()); + final_result.expect("No matching account found") +} From ca8b748636b47b41f7627ea8c896727a936845fb Mon Sep 17 00:00:00 2001 From: primata Date: Thu, 22 May 2025 14:12:07 -0300 Subject: [PATCH 11/11] fix: refactor according to liam's desire --- util/vanity/src/cli.rs | 184 ------------------------- util/vanity/src/lib.rs | 286 +++++++++++++++++++++++++++++++++++++++ util/vanity/src/main.rs | 3 +- util/vanity/src/miner.rs | 94 ------------- 4 files changed, 287 insertions(+), 280 deletions(-) delete mode 100644 util/vanity/src/cli.rs create mode 100644 util/vanity/src/lib.rs delete mode 100644 util/vanity/src/miner.rs diff --git a/util/vanity/src/cli.rs b/util/vanity/src/cli.rs deleted file mode 100644 index f30891e74..000000000 --- a/util/vanity/src/cli.rs +++ /dev/null @@ -1,184 +0,0 @@ -use clap::{Parser, Subcommand}; -use std::{ops::Deref, str::FromStr}; - -/// Pattern type for hex string filtering. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Pattern(Box); - -impl Deref for Pattern { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Pattern { - /// Return the raw hex string without trying to decode it. - pub fn as_str(&self) -> &str { - &self.0 - } -} - -/// Pattern parsing errors. -#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)] -pub enum PatternError { - #[error("the pattern's length exceeds 39 characters or the pattern is empty")] - InvalidPatternLength, - #[error("the pattern is not in hexadecimal format")] - NonHexPattern, -} - -impl FromStr for Pattern { - type Err = PatternError; - - fn from_str(s: &str) -> Result { - if s.len() >= 40 || s.is_empty() { - return Err(PatternError::InvalidPatternLength); - } - if s.chars().any(|c| !c.is_ascii_hexdigit()) { - return Err(PatternError::NonHexPattern); - } - Ok(Self(s.into())) - } -} - -/// CLI definition for the `vanity` tool. -#[derive(Parser, Debug)] -#[command(name = "vanity", about = "Vanity is a fast vanity address miner.")] -pub struct Cli { - #[command(subcommand)] - pub command: Vanity, -} - -/// Supported CLI subcommands. -#[derive(Debug, Subcommand)] -pub enum Vanity { - Move { - #[clap(long)] - starts: Option, - #[clap(long)] - ends: Option, - }, - Resource { - #[clap(long)] - address: Option, - #[clap(long)] - starts: Option, - #[clap(long)] - ends: Option, - }, -} - -#[cfg(test)] -mod tests { - use super::{Cli, Pattern, Vanity}; - use assert_cmd::Command; - use clap::Parser; - use std::str::FromStr; - - #[test] - fn test_valid_pattern() { - let p = Pattern::from_str("abcd").unwrap(); - assert_eq!(&*p, "abcd"); - } - - #[test] - fn test_cli_parsing_starts() { - let cli = Cli::parse_from(["vanity", "move", "--starts", "abcd"]); - match cli.command { - Vanity::Move { starts, ends } => { - assert_eq!(starts, Some("abcd".to_string())); - assert_eq!(ends, None); - } - _ => panic!("Expected Move variant"), - } - } - - #[test] - fn test_resource_parsing_starts() { - let cli = Cli::parse_from([ - "vanity", - "resource", - "--address", - "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", - "--starts", - "abcd", - ]); - match cli.command { - Vanity::Resource { address, starts, ends } => { - assert_eq!( - address, - Some( - "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f" - .to_string() - ) - ); - assert_eq!(starts, Some("abcd".to_string())); - assert_eq!(ends, None); - } - _ => panic!("Expected Resource variant"), - } - } - - #[test] - fn test_cli_runs_with_starts() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd.args(&["move", "--starts", "de"]).assert().success(); - - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); - - assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); - } - - #[test] - fn test_cli_runs_with_ends() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd.args(&["move", "--ends", "f0"]).assert().success(); - - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); - - assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); - } - - #[test] - fn test_cli_runs_with_starts_and_ends() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd.args(&["move", "--starts", "de", "--ends", "f0"]).assert().success(); - - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); - - assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); - assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); - } - - #[test] - fn test_resource_runs_with_starts_and_ends() { - let mut cmd = Command::cargo_bin("vanity").unwrap(); - let assert = cmd - .args(&[ - "resource", - "--address", - "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", - "--starts", - "de", - "--ends", - "f0", - ]) - .assert() - .success(); - - let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); - let address_line = output.lines().find(|l| l.contains("Found Resource Address:")).unwrap(); - let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); - - assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); - assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); - } -} diff --git a/util/vanity/src/lib.rs b/util/vanity/src/lib.rs new file mode 100644 index 000000000..2b81214ae --- /dev/null +++ b/util/vanity/src/lib.rs @@ -0,0 +1,286 @@ +pub mod miner { + use aptos_sdk::types::{ + account_address::{create_resource_address, AccountAddress}, + LocalAccount, + }; + use rand::thread_rng; + use rand::Rng; + use rayon::prelude::*; + use rayon::ThreadPoolBuilder; + use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }; + use std::time::Instant; + + fn matches(addr: &AccountAddress, start: &str, end: &str) -> bool { + let addr_hex = hex::encode(addr); + + (start.is_empty() || addr_hex.starts_with(start)) && (end.is_empty() || addr_hex.ends_with(end)) + } + + pub fn mine_move_address(starts: &str, ends: &str, threads: usize) -> LocalAccount { + let found = Arc::new(AtomicBool::new(false)); + let result = Arc::new(Mutex::new(None)); + let start_time = Instant::now(); + + let pool = ThreadPoolBuilder::new() + .num_threads(threads) + .build() + .expect("Failed to build thread pool"); + + pool.install(|| { + (0u64..=u64::MAX).into_par_iter().find_any(|_| { + if found.load(Ordering::Relaxed) { + return false; + } + let mut rng = thread_rng(); + let account = LocalAccount::generate(&mut rng); + if matches(&account.address(), starts, ends) { + let mut lock = result.lock().unwrap(); + *lock = Some(account); + found.store(true, Ordering::Relaxed); + true + } else { + false + } + }); + }); + + println!("Mining completed in {:?}", start_time.elapsed()); + + let final_result = std::mem::take(&mut *result.lock().unwrap()); + final_result.expect("No matching account found") + } + + pub fn mine_resource_address( + address: &AccountAddress, + starts: &str, + ends: &str, + threads: usize, + ) -> (AccountAddress, Vec) { + let found = Arc::new(AtomicBool::new(false)); + let result = Arc::new(Mutex::new(None)); + let start_time = Instant::now(); + + let pool = ThreadPoolBuilder::new() + .num_threads(threads) + .build() + .expect("Failed to build thread pool"); + + pool.install(|| { + (0u64..=u64::MAX).into_par_iter().find_any(|_| { + if found.load(Ordering::Relaxed) { + return false; + } + let mut rng = thread_rng(); + let mut seed_bytes = [0u8; 32]; // or any size you need + rng.fill(&mut seed_bytes); + let resource_address = create_resource_address(*address, &seed_bytes); + if matches(&resource_address, starts, ends) { + let mut lock = result.lock().unwrap(); + *lock = Some((resource_address, seed_bytes.to_vec())); + found.store(true, Ordering::Relaxed); + true + } else { + false + } + }); + }); + + println!("Mining completed in {:?}", start_time.elapsed()); + + let final_result = std::mem::take(&mut *result.lock().unwrap()); + final_result.expect("No matching account found") + } +} + +pub mod cli { + use clap::{Parser, Subcommand}; + use std::{ops::Deref, str::FromStr}; + + /// Pattern type for hex string filtering. + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct Pattern(Box); + + impl Deref for Pattern { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl Pattern { + /// Return the raw hex string without trying to decode it. + pub fn as_str(&self) -> &str { + &self.0 + } + } + + /// Pattern parsing errors. + #[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] + pub enum PatternError { + #[error("the pattern's length exceeds 39 characters or the pattern is empty")] + InvalidPatternLength, + #[error("the pattern is not in hexadecimal format")] + NonHexPattern, + } + + impl FromStr for Pattern { + type Err = PatternError; + + fn from_str(s: &str) -> Result { + if s.len() >= 40 || s.is_empty() { + return Err(PatternError::InvalidPatternLength); + } + if s.chars().any(|c| !c.is_ascii_hexdigit()) { + return Err(PatternError::NonHexPattern); + } + Ok(Self(s.into())) + } + } + + /// CLI definition for the `vanity` tool. + #[derive(Parser, Debug)] + #[command(name = "vanity", about = "Vanity is a fast vanity move & resource address miner.")] + pub struct Cli { + #[command(subcommand)] + pub command: Vanity, + } + + /// Supported CLI subcommands. + #[derive(Debug, Subcommand)] + pub enum Vanity { + /// Generate a Move address with a specific pattern. + Move { + #[clap(long)] + starts: Option, + #[clap(long)] + ends: Option, + }, + /// Generate a Resource address with a specific pattern. + Resource { + #[clap(long)] + address: Option, + #[clap(long)] + starts: Option, + #[clap(long)] + ends: Option, + }, + } + + #[cfg(test)] + mod tests { + use super::{Cli, Pattern, Vanity}; + use assert_cmd::Command; + use clap::Parser; + use std::str::FromStr; + + #[test] + fn test_valid_pattern() { + let p = Pattern::from_str("abcd").unwrap(); + assert_eq!(&*p, "abcd"); + } + + #[test] + fn test_cli_parsing_starts() { + let cli = Cli::parse_from(["vanity", "move", "--starts", "abcd"]); + match cli.command { + Vanity::Move { starts, ends } => { + assert_eq!(starts, Some("abcd".to_string())); + assert_eq!(ends, None); + } + _ => panic!("Expected Move variant"), + } + } + + #[test] + fn test_resource_parsing_starts() { + let cli = Cli::parse_from([ + "vanity", + "resource", + "--address", + "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", + "--starts", + "abcd", + ]); + match cli.command { + Vanity::Resource { address, starts, ends } => { + assert_eq!( + address, + Some( + "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f" + .to_string() + ) + ); + assert_eq!(starts, Some("abcd".to_string())); + assert_eq!(ends, None); + } + _ => panic!("Expected Resource variant"), + } + } + + #[test] + fn test_cli_runs_with_starts() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--starts", "de"]).assert().success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + } + + #[test] + fn test_cli_runs_with_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--ends", "f0"]).assert().success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } + + #[test] + fn test_cli_runs_with_starts_and_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd.args(&["move", "--starts", "de", "--ends", "f0"]).assert().success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Move address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } + + #[test] + fn test_resource_runs_with_starts_and_ends() { + let mut cmd = Command::cargo_bin("vanity").unwrap(); + let assert = cmd + .args(&[ + "resource", + "--address", + "0x5e04c2f5bf1a89d3431d2c047626acbba41c900a69b10e7c24e5b919802c531f", + "--starts", + "de", + "--ends", + "f0", + ]) + .assert() + .success(); + + let output = String::from_utf8(assert.get_output().stdout.clone()).unwrap(); + let address_line = output.lines().find(|l| l.contains("Found Resource Address:")).unwrap(); + let address = address_line.split(':').nth(1).unwrap().trim().trim_start_matches("0x"); + + assert!(address.starts_with("de"), "Address does not start with 'de': {}", address); + assert!(address.ends_with("f0"), "Address does not end with 'f0': {}", address); + } + } + +} \ No newline at end of file diff --git a/util/vanity/src/main.rs b/util/vanity/src/main.rs index 5f736ba71..7b5db5beb 100644 --- a/util/vanity/src/main.rs +++ b/util/vanity/src/main.rs @@ -1,5 +1,4 @@ -mod cli; -mod miner; +use vanity::{cli, miner}; use aptos_sdk::types::account_address::AccountAddress; use clap::Parser; diff --git a/util/vanity/src/miner.rs b/util/vanity/src/miner.rs deleted file mode 100644 index 5eaa2fab6..000000000 --- a/util/vanity/src/miner.rs +++ /dev/null @@ -1,94 +0,0 @@ -use aptos_sdk::types::{ - account_address::{create_resource_address, AccountAddress}, - LocalAccount, -}; -use rand::thread_rng; -use rand::Rng; -use rayon::prelude::*; -use rayon::ThreadPoolBuilder; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex, -}; -use std::time::Instant; - -fn matches(addr: &AccountAddress, start: &str, end: &str) -> bool { - let addr_hex = hex::encode(addr); - - (start.is_empty() || addr_hex.starts_with(start)) && (end.is_empty() || addr_hex.ends_with(end)) -} - -pub fn mine_move_address(starts: &str, ends: &str, threads: usize) -> LocalAccount { - let found = Arc::new(AtomicBool::new(false)); - let result = Arc::new(Mutex::new(None)); - let start_time = Instant::now(); - - let pool = ThreadPoolBuilder::new() - .num_threads(threads) - .build() - .expect("Failed to build thread pool"); - - pool.install(|| { - (0u64..=u64::MAX).into_par_iter().find_any(|_| { - if found.load(Ordering::Relaxed) { - return false; - } - let mut rng = thread_rng(); - let account = LocalAccount::generate(&mut rng); - if matches(&account.address(), starts, ends) { - let mut lock = result.lock().unwrap(); - *lock = Some(account); - found.store(true, Ordering::Relaxed); - true - } else { - false - } - }); - }); - - println!("Mining completed in {:?}", start_time.elapsed()); - - let final_result = std::mem::take(&mut *result.lock().unwrap()); - final_result.expect("No matching account found") -} - -pub fn mine_resource_address( - address: &AccountAddress, - starts: &str, - ends: &str, - threads: usize, -) -> (AccountAddress, Vec) { - let found = Arc::new(AtomicBool::new(false)); - let result = Arc::new(Mutex::new(None)); - let start_time = Instant::now(); - - let pool = ThreadPoolBuilder::new() - .num_threads(threads) - .build() - .expect("Failed to build thread pool"); - - pool.install(|| { - (0u64..=u64::MAX).into_par_iter().find_any(|_| { - if found.load(Ordering::Relaxed) { - return false; - } - let mut rng = thread_rng(); - let mut seed_bytes = [0u8; 32]; // or any size you need - rng.fill(&mut seed_bytes); - let resource_address = create_resource_address(*address, &seed_bytes); - if matches(&resource_address, starts, ends) { - let mut lock = result.lock().unwrap(); - *lock = Some((resource_address, seed_bytes.to_vec())); - found.store(true, Ordering::Relaxed); - true - } else { - false - } - }); - }); - - println!("Mining completed in {:?}", start_time.elapsed()); - - let final_result = std::mem::take(&mut *result.lock().unwrap()); - final_result.expect("No matching account found") -}