The application leverages the bitcoinj library to generate a vanity bitcoin address. A vanity address is simply a bitcoin address that contains a personalized string. I would like some critical feedback, specifically code correctness, code smells, and my usage of futures
Sample Invocation:
java -jar vanitas-1.0-SNAPSHOT-jar-with-dependencies.jar 1Love
Output:
Searching for a bitcoin address that contains: 1Love Found in 1 minutes Address: 1LoveEbV9B5iRzKU63PKh1tXNk7vh865B7 Private Key: 45777959218638374115925337441855471702901360693577031567674250991838132852058
If you would like to build and execute the application, it's available at GitHub.
Vanitas.java
package com.gmail.lifeofreilly.vanitas;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.MoreExecutors;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
import org.apache.log4j.Logger;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import javax.annotation.ParametersAreNonnullByDefault;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
class Vanitas {
private static final Logger log = Logger.getLogger(Vanitas.class);
private static final NetworkParameters NET_PARAMS = MainNetParams.get(); //use production bitcoin network
/**
* An application for generating a vanity bitcoin address.
* As a best practice you should not reuse bitcoin addresses.
* Address reuse harms your privacy, as well as the privacy of others.
* For more information see: https://en.bitcoin.it/wiki/Address_reuse
*
* @param args required argument. The desired bitcoin address substring.
*/
public static void main(String[] args) {
final String targetPhrase;
if (args.length == 1) {
targetPhrase = args[0];
try {
generateAddress(targetPhrase);
System.out.println("Searching for a bitcoin address that contains: " + targetPhrase);
System.out.println("Status is available at: " + System.getProperty("user.dir") + "/logs/error.log");
} catch (IllegalArgumentException ex) {
System.out.println("Your target phrase '" + targetPhrase + "' contains illegal characters. " +
"Please see: https://en.bitcoin.it/wiki/Address#Address_validation");
System.exit(-1);
}
} else {
System.out.println("Invalid number of arguments. Usage: Vanitas [phrase]");
System.exit(-1);
}
}
/**
* Establishes a thread pool based on the number of available processors, then executes an AddressGenerator for each.
* The resulting bitcoin address and the associated private key will be written to the standard output stream.
*
* @param targetPhrase The desired bitcoin address substring.
*/
private static void generateAddress(final String targetPhrase) {
final int cores = Runtime.getRuntime().availableProcessors();
log.info("Searching for a bitcoin address that contains: " + targetPhrase);
log.info("Number of threads that will be used: " + cores);
final ListeningExecutorService execService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(cores));
final long timeStart = System.nanoTime();
for (int i = 0; i < cores; i++) {
Callable<ECKey> callable = new AddressGenerator(targetPhrase, NET_PARAMS);
ListenableFuture<ECKey> future = execService.submit(callable);
Futures.addCallback(future, new FutureCallback<ECKey>() {
@Override
public void onSuccess(ECKey key) {
if (key.toAddress(NET_PARAMS).toString().contains(targetPhrase)) {
System.out.println("Found in " + MINUTES.convert((System.nanoTime() - timeStart), NANOSECONDS) + " minutes");
System.out.println("Address: " + key.toAddress(NET_PARAMS));
System.out.println("Private Key: " + key.getPrivKey());
}
execService.shutdownNow();
}
@Override
@ParametersAreNonnullByDefault
public void onFailure(Throwable thrown) {
log.error(thrown.getMessage());
}
});
}
}
}
AddressGenerator.java
package com.gmail.lifeofreilly.vanitas;
import com.google.common.base.CharMatcher;
import org.apache.log4j.Logger;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.concurrent.Callable;
class AddressGenerator implements Callable<ECKey> {
private static final Logger log = Logger.getLogger(AddressGenerator.class);
private static final int BTC_ADDRESS_MAX_LENGTH = 35;
private final NetworkParameters netParams;
private long attempts;
private String targetPhrase;
/**
* Sole constructor for AddressGenerator
*
* @param targetPhrase the desired bitcoin address substring
* @param netParams the target bitcoin network e.g production or testnet
*/
public AddressGenerator(final String targetPhrase, final NetworkParameters netParams) {
this.netParams = netParams;
if (isValidBTCAddressSubstring(targetPhrase)) {
this.targetPhrase = targetPhrase;
} else {
throw new IllegalArgumentException("The requested phrase is not a valid bitcoin address substring.");
}
}
/**
* Attempts to compute a bitcoin address that contains the target phrase.
*
* @return An ECKey which represents an elliptic curve public key (bitcoin address) and private key
* @throws Exception
*/
@Override
public ECKey call() throws Exception {
ECKey key;
do {
key = new ECKey();
attempts++;
logAttempts();
} while (!(key.toAddress(netParams).toString().contains(targetPhrase)) &&
!Thread.currentThread().isInterrupted());
log.debug("Exiting thread " + Thread.currentThread().getName() +
", Attempts made: " + NumberFormat.getNumberInstance(Locale.US).format(attempts));
return key;
}
/**
* Logs progress every 1M attempts
*/
private void logAttempts() {
if (attempts % 1000000 == 0) {
log.debug("Thread " + Thread.currentThread().getName() + " is still working, # of attempts: " +
NumberFormat.getNumberInstance(Locale.US).format(attempts));
}
}
/**
* Verifies that the requested phrase represents a valid bitcoin address substring
*
* @param substring the requested phrase
* @return true if the requested phrase is a valid bitcoin address substring
*/
private static boolean isValidBTCAddressSubstring(final String substring) {
boolean validity = true;
if (!CharMatcher.JAVA_LETTER_OR_DIGIT.matchesAllOf(substring) ||
substring.length() > BTC_ADDRESS_MAX_LENGTH ||
CharMatcher.anyOf("OIl0").matchesAnyOf(substring)) {
validity = false;
}
return validity;
}
}