patternjavaMinor
Multi-threaded bitcoin vanity address generator written in Java
Viewed 0 times
addressmultiwrittenjavageneratorvanitybitcointhreaded
Problem
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:
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
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
Solution
Java 8 Streams have a number of features that make them ideal for parallel processing, and for reducing more complicated class structures down to simpler functions. You can use this to your advantage and it makes a big difference in this particular case.
Consider your problem, you want to generate many ECKey instances until one of them matches a particular phrase. You want to monitor your progress, and you want to stop when you find one. You also want to do this all in parallel, and as efficiently as practical.
There's really just one trick to know in this instance, when applying streams, and that is using an infinite stream of increasing values as an "infinite loop". This can be modeled as:
That will create a stream of long values that increases from 0 (and loops, and so on... it will create the stream
Now, you can 'peek' in to a stream, and see the current value, and do things with it, so, we can have a progress function:
Every time
Now, here we have a stream of increasing long values, and we can report progress, so let's convert that stream of counts, to a stream of
That last line basically ignores the count, but swaps it for a key.
Now we filter out keys that don't match our criteria:
So, we have thrown out all keys other than ones that have our phrase in it... now all we need to do is to return the first one we find....
That findAny returns an Optional, which may, or may not contain a key, but it will only ever get there if it did find a key, because the stream is infinite.... So, we can just use it as our successful find.
But, what about the multi-threaded requirement? Well, that's easy, just add
Wrap that all up in a nice function, and you get code like:
No need for the Guava structures for concurrency, no need for execution services, no need for Callables, etc.
Using the tools you already have available is always a good option when writing maintainable code.
Consider your problem, you want to generate many ECKey instances until one of them matches a particular phrase. You want to monitor your progress, and you want to stop when you find one. You also want to do this all in parallel, and as efficiently as practical.
There's really just one trick to know in this instance, when applying streams, and that is using an infinite stream of increasing values as an "infinite loop". This can be modeled as:
LongStream.iterate(0, i -> i + 1);That will create a stream of long values that increases from 0 (and loops, and so on... it will create the stream
0, 1, 2, 3, 4, 5, 6, 7, .....Now, you can 'peek' in to a stream, and see the current value, and do things with it, so, we can have a progress function:
private static final void progress(long count) {
if (count % 1000000 == 0) {
System.out.println("Handling generation " + count);
}
}Every time
count is a multiple of a million, report it. You can incorporate this in to a stream as follows:LongStream.iterate(0, i -> i + 1)
.peek(count -> progress(count))Now, here we have a stream of increasing long values, and we can report progress, so let's convert that stream of counts, to a stream of
ECKey values...LongStream.iterate(0, i -> i + 1)
.peek(count -> progress(count))
.mapToObj(count -> new ECKey())That last line basically ignores the count, but swaps it for a key.
Now we filter out keys that don't match our criteria:
LongStream.iterate(0, i -> i + 1)
.peek(count -> progress(count))
.mapToObj(count -> new ECKey())
.filter(key -> key.toAddress(NET_PARAMS).toString().contains(targetPhrase))So, we have thrown out all keys other than ones that have our phrase in it... now all we need to do is to return the first one we find....
LongStream.iterate(0, i -> i + 1)
.peek(count -> progress(count))
.mapToObj(count -> new ECKey())
.filter(key -> key.toAddress(NET_PARAMS).toString().contains(targetPhrase))
.findAny()That findAny returns an Optional, which may, or may not contain a key, but it will only ever get there if it did find a key, because the stream is infinite.... So, we can just use it as our successful find.
But, what about the multi-threaded requirement? Well, that's easy, just add
parallel() as the first stream step. That will use all available CPU's and run part of the stream on each.Wrap that all up in a nice function, and you get code like:
private static void generateAddress(final String targetPhrase) {
validateBTCAddressSubstring(targetPhrase);
final long timeStart = System.nanoTime();
Optional found = LongStream.iterate(0L, c -> c + 1)
.parallel()
.peek(i -> progress(i))
.mapToObj(ignore -> new ECKey())
.filter(key -> key.toAddress(NET_PARAMS).toString().contains(targetPhrase))
.findAny();
long time = System.nanoTime() - timeStart;
ECKey key = found.get();
System.out.println("Found in " + MINUTES.convert(time, NANOSECONDS) + " minutes");
System.out.println("Address: " + key.toAddress(NET_PARAMS));
System.out.println("Private Key: " + key.getPrivKey());
}
private static final void progress(long count) {
if (count % 1000000 == 0) {
System.out.println("Handling generation " + count);
}
}
private static void validateBTCAddressSubstring(final String substring) {
// no lIO0
if (!substring.matches("^[a-km-zA-HJ-NP-Z1-9]+$") ||
substring.length() > BTC_ADDRESS_MAX_LENGTH) {
throw new IllegalArgumentException("target phrase '" + substring + "' is not valid in an address.");
}
}No need for the Guava structures for concurrency, no need for execution services, no need for Callables, etc.
Using the tools you already have available is always a good option when writing maintainable code.
Code Snippets
LongStream.iterate(0, i -> i + 1);private static final void progress(long count) {
if (count % 1000000 == 0) {
System.out.println("Handling generation " + count);
}
}LongStream.iterate(0, i -> i + 1)
.peek(count -> progress(count))LongStream.iterate(0, i -> i + 1)
.peek(count -> progress(count))
.mapToObj(count -> new ECKey())LongStream.iterate(0, i -> i + 1)
.peek(count -> progress(count))
.mapToObj(count -> new ECKey())
.filter(key -> key.toAddress(NET_PARAMS).toString().contains(targetPhrase))Context
StackExchange Code Review Q#102439, answer score: 5
Revisions (0)
No revisions yet.