joerecover
The checking half of the BIP39 search tooling for the 9.1 BTC hunt
The checking half of the BIP39 search tooling built for the 9.1 BTC hunt: joegen generates candidate seed phrases, and joerecover decides whether any of them is the real one. The hunt itself, including where it stands, lives in the writeup linked below.
The pipeline
joerecover reads phrases from standard input, one per line, then validates the BIP39 checksum, derives keys along the common legacy, wrapped-SegWit, and native-SegWit paths, computes the Bitcoin addresses, and looks them up in a memory-mapped database of addresses that have appeared on-chain (btcrecover’s format; mine covers 2011 through 2021 in 8.6 GB). Matches flush to found.txt; progress goes to standard error with a rate and an ETA. The two programs connect the boring way:
word-permutations tokens.txt 2>&1 | joerecover --addressdb addresses.db \
--threads 20 --indices 3 --change
Candidate logic on the left, cryptography on the right, a pipe in between. Before pointing it at billions of possibilities, the whole pipe got tested with planted vectors: a known seed in a tiny address database had to be found, and a valid decoy had to stay missing. A broken search will burn a month with complete confidence.
The throughput wall
The checker is multithreaded (one secp256k1 context per worker, bounded channels, pre-parsed derivation paths) and rejects checksum-invalid phrases at about 990,000 per second on 20 cores. That number needs an enormous asterisk: BIP39 deliberately routes valid phrases through 2,048 rounds of PBKDF2, so checksum-valid ones run at only about 4,400 per second on the same cores. The smarter the generator, the slower the search.
Two options claw some of that back. --indices and --change scan more addresses per phrase while deriving the expensive account keys once, and --complete-last takes a 23-word prefix and computes the exactly eight checksum-valid final words directly (useful when only the tail of a phrase is uncertain). For the heavy tiers I adopted XopMC’s CUDA_Mnemonic_Recovery on an RTX 3090: about 8.4 million candidate tests and 525,000 checksum-valid derivations per second, roughly 120 times the CPU rate, against a Bloom filter built from about 1.19 billion addresses.
A friend inherited 24 handwritten words and a dead hardware wallet. Ten of the words are not in the BIP39 dictionary, and the margin says email.
read the writeup →