Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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/*",
Expand Down
1 change: 1 addition & 0 deletions util/vanity/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
13 changes: 13 additions & 0 deletions util/vanity/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
20 changes: 20 additions & 0 deletions util/vanity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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 <STARTS_PATTERN> --ends-pattern <ENDS_PATTERN>

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*
61 changes: 61 additions & 0 deletions util/vanity/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::{ops::Deref, str::FromStr};

/// Pattern.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(super) struct Pattern(Box<str>);

impl Deref for Pattern {
type Target = str;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl Pattern {
pub(super) fn into_bytes(self) -> Result<Vec<u8>, 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<Self, Self::Err> {
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<String>,
#[clap(long)]
ends_pattern: Option<String>,
},
}
33 changes: 33 additions & 0 deletions util/vanity/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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(());
}
}
}
55 changes: 55 additions & 0 deletions util/vanity/src/miner.rs
Original file line number Diff line number Diff line change
@@ -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")
}
Loading