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,

  1. 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