Education in Futility: WarpWallet Brute Forcing

So, WarpWallet is a so-called brain wallet for Bitcoin. That is, you only have to remember a relatively short password and it generates the corresponding private key for use. It uses a memory and CPU hard set of cryptographic algorithms to ensure that brute-forcing is slowed way down. That is, when generating the private key, it takes considerable time. Their Javascript implementation takes over 10 seconds on my machine.

So the makers of it had challenges setup. By the time I stumbled on it, only the last challenge was left, with 6 months remaining. For that challenge, the reward for cracking an 8-character alphanumeric password was 20 BTC (and BCH and BTG!), which was worth over $100,000 USD at the time of the challenge end date.

Since their Javascript implementation is terribly slow I wondered if anyone had ported it to any other language, and found a Go version-but it was outdated and would not compile. So, as my first exercise in Go, I updated it and got it compiling. Instead of 10+ seconds per keypair generated, it took about 1 second. But, it took input from the command line, so I decided to make a brute forcer that used this newly updated generator. It would feed it the passphrase and salt and store the result (the private key and public key) and then I could parse these results later.

So the basic design was this:

My WarpWallet Brute Forcer (using Go WarpWallet implementation) -> SQL Database

The brute-forcer underwent many revisions. First it checked the history of passphrases to ensure no duplicates were stored, but this eventually took longer than the time to generate private keys, slowing the whole processes down. So it was eliminated (and there was virtually no chance of generating the same passphrase twice, the same odds as finding the correct passphrase).

It also did not store anything at first besides the date and the passphrase. The client checked each public key against the target one and discarded the result. This meant if the client was killed before I could check the output, I was out of luck! Later improvements added the private key, public key, and the hostname of the computer that generated it (as I used all available idle personal computers to do so).

Another misstep was having the Go pipeline switch sleep. First it slept 100 ms if no channel had data or their buffers were full, then I increased it to 250 ms inexplicably, then realized it waits by default. So this was leaving processing power on the table. Removing the sleep command on my main desktop gave a ~20% improvement in performance (from 5.12 to 6.14 keypairs/s on an i7). Below are the contributions from various machines. The IPs at the end are AWS servers, the largest chunk of which was from a c4.xlarge machine over a single day!

Pie chart of computation proportions


And then on January 1st, 2018 the challenge expired. There’re just over 24 million rows, 4.5GB data. It takes a few seconds to test any result. I investigated testing each public address to see if they had a balance but on my local Bitcoin node it takes minutes to scan the blockchain for transactions for newly added addresses. And web APIs rate limit you to where it would take a year or so to test each one. Less if I spread requests out across API providers. So, in the end, I just deleted all the results. It was fun, I learned a lot about Go, cryptocurrency nodes, and I’m ready for the next, hopefully more fruitful, project.