Where Bitcoin Keys Actually Die: A Scanner Experiment
A full-length Bitcoin key can come from a very short list of possibilities. A generator starting with a uniform 32-bit seed reaches at most 2³² outputs, even if it prints each as a 256-bit value. Parts 1 and 2 dealt with spaces too large to search. Here I wanted to measure some smaller ones.
The scanner in research/ uses four candidate rules: secure random draws,
patterned integers, single-word brainwallets, and a synthetic weak pseudorandom
number generator (PRNG). For each candidate, it derives compressed and
uncompressed legacy Pay-to-Public-Key-Hash (P2PKH) addresses and screens them
against a dated funded-address snapshot. It reads public data and never
constructs, signs, or broadcasts a transaction.
These runs found zero funded-snapshot matches. Selected known, drained addresses supplied positive controls. The weak-PRNG rule is a small-seed model; it does not reproduce a historical wallet bug.
Tier 0: random control
The baseline generated 1,500,000 cryptographically random keys with
os.urandom and derived three million candidate addresses. It screened them
against an index of 57,205,029 addresses from the October 13, 2026 snapshot
and the scanner’s built-in 16-address control set. Explorer lookups run only
after a flag; this control produced none.
Here is the retained output:
tiers : random_control
screened against: funded-address index 2026-10-13 (57,205,029 addrs) + built-in control set (16 addrs) + online API verification of hits
online lookups: 0 ok, 0 failed
keys checked : 1,500,000 (10^6.2)
hits (ever used) : 0 [control: 0, dump: 0]
hits (funded now): n/a (no online lookups)
elapsed : 54.3s rate 27,637 keys/s
projection : ~10^11.9 keys/year on this core
to cover the *narrowed* 12-word space (2^128 = 10^38.5): ~10^26.6 years
(age of universe ~ 10^10.1 years)
The raw-key rate was 27,637 keys/s with the index loaded. The output’s 12-word projection divides the mnemonic space by that rate. It does not measure mnemonic search, which also needs PBKDF2 and BIP32 derivation. Adding those steps to this pipeline adds work.
The run used CPython 3.13 on Linux. Python documents
that os.urandom uses blocking getrandom() when available and waits for
kernel initialization. This addresses that early-boot handoff; it does not
certify the operating system’s entropy source.
What the index checks
The compact index stores 64-bit prefixes of address hashes. At this size, an absent address has a modeled collision probability of about 3.10 × 10⁻¹² per lookup: 9.30 × 10⁻⁶ expected flags over three million lookups. A flag therefore needs an exact full-address check against the original dump. Today’s explorer history cannot prove membership in an earlier snapshot.
There is an open implementation issue here. The scanner retains an index flag as a dump hit even after a negative explorer response. Future positive flags need separate exact confirmation. These runs had zero flags; with a correctly built index, zero flags also means zero exact matches for the tested addresses. The scanner guide documents the prefix index and this verification limitation.
A streaming inventory found these shapes in the same dump:
| Address shape | Entries | Share |
|---|---|---|
1… (P2PKH-shaped) |
21,198,062 | 37.06% |
3… (P2SH-shaped) |
6,152,068 | 10.75% |
bc1q… (42 chars, P2WPKH-shaped) |
23,492,675 | 41.07% |
bc1q… (62 chars, P2WSH-shaped) |
1,184,245 | 2.07% |
bc1p… (62 chars, P2TR-shaped) |
5,081,683 | 8.88% |
Other bc1… |
96,296 | 0.17% |
These are prefix-and-length classifications, without checksum validation.
The snapshot date comes from the filename. Its gzip SHA-256 is
7117127c3a0e8276e3c6c4ad8f4fc28fc0498886481f9704edf7877be05a1c02.
Only the 1… records have the form this scanner generates. Even treating all
21,198,062 as valid P2PKH targets, an ideal-hash model gives at most about
4.35 × 10⁻³⁵ probability of an exact address match across the random
control’s three million candidates. Per key, checking both forms gives at most
about 2.90 × 10⁻⁴¹.
For comparison, zero flags in 1.5 million independent random-key trials gives an approximate 95% upper bound of 2 × 10⁻⁶ per key by the rule of three. That empirical bound is roughly thirty-five orders of magnitude above the ideal-model probability. A short run cannot validate such a tiny rate. This statistical statement applies to the random control; the other tiers enumerate specified sets.
All results below share the same coverage: two legacy P2PKH forms at the snapshot date. The scanner does not generate the other output types in the table or request current balances for offline misses.
Tier 1: patterned integers
Start with small integers, repeated digits, powers of two and ten, and dates. These are the obvious guesses on the first pages of a key directory. Separate explorer checks found the following histories for uncompressed addresses:
| Guess | Address | Txs | Ever received | Balance at check |
|---|---|---|---|---|
| key = 1 | 1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm | 1475 | 7.849 BTC | 0.000 |
| key = 2 | 1LagHJk2FyCV2VzrNHVqg3gYG4TSYwDV4m | 97 | 0.170 BTC | 0.000 |
| key = 3 | 1NZUP3JAc9JkmbvmoTv7nVgZGtyJjirKV1 | 80 | 0.317 BTC | 0.000 |
| key = 42 | 1HGn3jxoSh8twi4mR3iaNmZr6pbHgjFJEg | 4 | 0.000163 BTC | 0.000 |
| key = 1024 | 17imJe7o4mpq2MMfZ328evDJQfbt6ShvxA | 4 | 0.0071 BTC | 0.000 |
Confirmed-chain figures from Blockstream, rechecked October 16, 2026. Received amounts are rounded; the linked explorers show later state.
key = 1 had received about 7.85 BTC. Every address in the table had a
transaction history and a zero balance when checked. These figures show that
the keys are publicly discoverable; they do not identify who moved the funds
or how quickly.
The full patterned run checked 1,007,440 keys: integers from 1 to 1,000,000, plus the other patterns and dates from the genesis block through October 13,
- It found six control-set addresses, corresponding to keys 1, 2, and 3 in both public-key encodings. The explorer confirmed those were empty. Funded-snapshot flags: zero.
Tier 2: brainwallet phrases
A traditional brainwallet sets private key = SHA-256(passphrase). The hash
produces a full-length value. The secret choices still come from the phrase
selection process, and familiar phrases are easy to try:
| Passphrase | Address | Txs | Ever received | Balance at check |
|---|---|---|---|---|
password |
16ga2uqnF1NqpAuQeeg7sTCAdtDUwDyJav | 45028 | 0.357 BTC | 0.000 |
bitcoin |
1E984zyYbNmeuumzEdqT8VSL8QGJi3byAD | 54 | 0.171 BTC | 0.000 |
satoshi |
1ADJqstUMBB5zFquWg19UqZ7Zc6ePCpzLE | 10 | 0.0039 BTC | 0.000 |
123456 |
1565qkBbLcuFP78f7MFKkK8jtHGDwdSgvX | 12 | 0.0018 BTC | 0.000 |
correct horse battery staple |
1JwSSubhmg6iPtRjtyqhUYYH7bZg3Lfy1T | 4163 | 15.947 BTC | 0.000 |
blockchain |
1bZ3M7i8zLWpGVzpnLy7ci6VWGky1jGBP | 28326 | 0.281 BTC | 0.000 |
Confirmed-chain figures from Blockstream, rechecked October 16, 2026. Received amounts are rounded; the linked explorers show later state.
The xkcd phrase correct horse battery staple had received nearly 16 BTC.
password had 45,028 transactions. A published example phrase has had a
rather unfortunate career as a secret. The transaction counts do not let us
assign an entropy value to an individual phrase; they show its public history.
The dictionary run hashed all 104,334 words in the system dictionary and checked both P2PKH forms. It found six control-set addresses, confirmed empty, and zero funded-snapshot flags. The later explorer checks above are separate from the run’s October 13 snapshot check.
Tier 3: a synthetic small-seed generator
The weak_rng rule seeds Python’s Mersenne Twister, random.Random, with each
integer in a selected range. It is a deliberately constrained model. It does
not implement a mnemonic, PBKDF2, or BIP32, and cannot reconstruct the Milk Sad
or Trust Wallet candidate sets. Those pipelines have different per-candidate
costs and derivation paths.
We exhausted all 16,777,216 seeds in a 24-bit space. Checking both address forms took 675.6 seconds, about 11.3 minutes, at 24,833 keys/s on one core. The run produced zero funded-snapshot flags.
Scaling that indexed workload to all 2³² seeds gives about 48 continuous hours on the same core. This is a projection for the synthetic model, rather than a benchmark of a historical incident. The absence of funded matches does not make a small seed space safe or establish that a real incident had no victims.
Four candidate rules, one snapshot
| Tier | Candidate keys | Funded-snapshot flags | Separate control addresses found |
|---|---|---|---|
| 0: random control | 1,500,000 | 0 | 0 |
| 1: patterned integers | 1,007,440 | 0 | 6 |
| 2: single-word brainwallets | 104,334 | 0 | 6 |
| 3: synthetic weak PRNG | 16,777,216 | 0 | 0 |
The control addresses show that the scanner recognizes selected known cases. The snapshot results establish no matches within the coverage defined above. They do not measure how common weak wallets are or whether other output types controlled by these keys hold funds.
The useful distinction is the work required to enumerate each candidate set. A long key can come from a small, searchable source. Good key-generation randomness addresses that risk, while safe signing and private backups address later failures.
Part 4 examines the documented incidents and adds an experiment with a known synthetic target population. That lets us predict an observable match count and compare it with a completed run.
Reproducing the inputs
The scanner guide explains the run options and index construction. The dictionary hash below was captured after the run: the available file has 104,334 lines and predates it, but the original log did not record its hash. That limits what can be established about the original input bytes.
Run these commands from the root of the companion
entropy-lab repository.
Use a byte-identical wordlist for the same dictionary candidates;
the command rejects a hash mismatch. Random draws, timings, and explorer
responses will differ.
python -m pip install -r requirements.txt
# Throughput control against the built-in address set; no chain check.
python -m research.scan --tier random_control --count 1500000
# Build and verify the dated snapshot index, then screen both P2PKH forms.
python -m research.snapshot_profile Bitcoin_addresses_October_13_2026.txt.gz
python -m research.index build Bitcoin_addresses_October_13_2026.txt.gz funded_2026-10-13.idx
python -m research.index verify funded_2026-10-13.idx
python -m research.scan --tier random_control --count 1500000 \
--index funded_2026-10-13.idx --online-verify
# The three enumerated candidate sets.
python -m research.scan --tier patterned --limit 1000000 --end-date 2026-10-13 \
--index funded_2026-10-13.idx --online-verify
python -m research.scan --tier brainwallet --wordlist /usr/share/dict/words \
--wordlist-sha256 9e66281f7e51445eab6857488ff6e3d768afffadb7fb1adbef5e4617bee4a53b \
--index funded_2026-10-13.idx --online-verify
python -m research.scan --tier weak_rng --seed-bits 24 --count 16777216 \
--index funded_2026-10-13.idx --online-verify
# Check address derivation against known vectors.
python -m research.btckit