A high-performance tool for scanning Bitcoin private keys against a database of ~49M funded address hashes. Supports three execution tiers: custom C accelerator with NEON 4-wide RIPEMD-160 and ARM SHA-256 hardware instructions (~22M keys/sec on 9 cores), Metal GPU with Montgomery batch inverse (~2.3M keys/sec), or pure Python on PyPy (~570K keys/sec with zero compiled dependencies).
I found some speed improvements here in this fork vs the original.
Originally when I downloaded and ran this package, it was testing 64,000 addresses per second with the default architecture.
After about 10 hours of working on speed improvements, I now get over 22 million addresses checked per second (on my laptop).
Some highlights of the architecture redesign:
- moved address lookup to mmap'd binary bloom filter instead of python pickle restore
- moved address lookup to match the data being generated so it doesn't require conversions to check
- removed
coincurvelibrary usage and rewrote everything to be optimized internally for our exact purposes - also created pure-python implementations of all hash and crypto utilities to see of pypy could make it go faster (not really)
- added specialized (i.e. non-general-purpose) hash operations to just calculate the required lengths and sizes of data here
- added ability to resume from previous seeds
- added more utilities for structured guessing from source key formulas
- added some NEON/SSE extensions for more rapid sha256 calculations
- added some GPU helpers for non-sequential lookups
- used more optimized magic crypto math tricks throughout reducing the number of expensive divisions required
# Install (no dependencies required for basic usage)
git clone https://github.com/mattsta/plutus
cd plutus
# build the C accelerator for maximum speed
./build.sh
# Run
python3 plutus.pyEach worker process picks a random 256-bit starting private key, then scans sequentially from that point using elliptic curve point addition: key, key+1, key+2, .... This is ~100x faster than generating independent random keys because point addition (P + G) is a single field operation vs full scalar multiplication for a random key.
The keyspace is 2^256 (~10^77) possible keys. With ~49M funded addresses, the probability of any single key matching is ~10^-71. The tool scans as fast as possible to maximize coverage.
Important: On restart, workers pick NEW random starting points. Previous progress is not resumed. See "Distributed Usage" below for coordination strategies.
Each private key derives one public key, but that public key can be encoded as multiple Bitcoin address formats:
| Type | Prefix | Example | Description | Cost |
|---|---|---|---|---|
| P2PKH | 1... |
1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH |
Original format (2009). hash160(pubkey) with Base58Check encoding. Holds a large share of historical Bitcoin wealth — early adopters, long-held coins. |
Base cost |
| Bech32 | bc1q... |
bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 |
Native SegWit (2017, BIP 141/173). Uses the same hash160(pubkey) as P2PKH, just encoded differently. Now the dominant format for new transactions. |
Free — shares P2PKH's hash |
| P2SH | 3... |
3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN |
Pay-to-Script-Hash (2012, BIP 16). Used for multisig and wrapped SegWit (P2SH-P2WPKH). Hash is hash160(0x0014 || hash160(pubkey)) — requires an extra hash160 computation per key. |
+40% slower |
Default: -t p2pkh,bech32 — checks both P2PKH and Bech32 for free (same hash). Add -t p2pkh,p2sh,bech32 to include P2SH at ~40% performance cost.
The database contains ~49M hash160 values extracted from all three address types. On first run, it's built from the text files in database/ and cached as a binary file (.plutus_cache.bin) with:
- Bloom filter (256MB) indexed by raw 20-byte hash160 — fast probabilistic membership test
- Sorted hex hash160 list for exact verification via binary search on mmap'd data
- SHA-256 integrity checksum — detects corruption
Subsequent runs load the cache in seconds via memory-mapped I/O (zero-copy).
The tool auto-selects the fastest available backend:
| Tier | Requirement | Speed (9-core) | Hot Path |
|---|---|---|---|
| C accelerator | plutus_accel.so built |
~22M keys/sec | Batch Jacobian EC + Montgomery inverse + NEON 4-wide RIPEMD-160 + bloom prefetch, all in one C call |
| Metal GPU | macOS Apple Silicon | ~2.3M keys/sec | Montgomery batch inverse + hash160 + bloom on GPU, double-buffered async pipeline |
| CPython + coincurve | pip install coincurve |
~1M keys/sec | Direct libsecp256k1 CFFI calls + hashlib |
| PyPy pure Python | --native-ec flag |
~570K keys/sec | 100% Python, zero compiled deps, full PyPy JIT |
Use --native-ec to force the pure Python backend (required for PyPy).
Beyond random scanning, Plutus supports targeted strategies that exploit documented weaknesses in how early Bitcoin wallets generated private keys. Sequential strategies run at full C accelerator speed (~22M keys/sec). Non-sequential strategies use the Metal GPU path (~2.3M keys/sec) or coincurve (~200K keys/sec).
| Strategy | Keyspace | Type | Speed | Time to exhaust |
|---|---|---|---|---|
low-entropy --max-bits 32 |
4.3B | Sequential | 12M/s | 6 minutes |
low-entropy --max-bits 40 |
1.1T | Sequential | 12M/s | 25 hours |
timestamp seconds 2009-2013 |
157M | Sequential | 12M/s | 13 seconds |
timestamp milliseconds 2009-2013 |
157B | Sequential | 12M/s | 3.6 hours |
timestamp microseconds 2009-2013 |
157T | Sequential | 12M/s | 152 days |
timestamp nanoseconds 1 day |
86T | Sequential | 12M/s | 83 days |
timestamp --timestamp-mode sha256 |
157M | Non-sequential | 200K/s | 13 minutes |
brain-wallet (10K phrases) |
10K | Non-sequential | 200K/s | instant |
brain-wallet (5K words × 3) |
125B | Non-sequential | 200K/s | 7 days |
weak-mnemonic 2 words |
4.2M | Non-sequential | 1K/s | 70 minutes |
weak-mnemonic 3 words |
8.6B | Non-sequential | 1K/s | 100 CPU-days |
random |
2^256 | Sequential | 12M/s | heat death of universe |
Random 256-bit starting point, sequential scan via point addition. Covers the full keyspace probabilistically. Fastest throughput — uses the C accelerator at full speed.
Scans keys with low bits of entropy: 1, 2, 3, ..., 2^N. Many early wallets used weak random number generators that produced keys in restricted ranges.
python3 plutus.py --strategy low-entropy --max-bits 32 # 4.3B keys, ~6 min
python3 plutus.py --strategy low-entropy --max-bits 40 # 1.1T keys, ~25 hoursPrivate keys derived from Unix timestamps — mimicking weak RNG implementations that used time(), gettimeofday(), or clock_gettime() as entropy sources.
Modes: raw (default) uses timestamps as sequential integer keys at full C accelerator speed. sha256 computes SHA-256(timestamp) per key (non-sequential, slower).
Resolutions: seconds (default), milliseconds, microseconds, nanoseconds. Higher resolution covers more keyspace but takes proportionally longer.
| Resolution | 1 day keyspace | 2009-2013 keyspace | Time (9-core) |
|---|---|---|---|
seconds |
86K | 157M | 13 seconds |
milliseconds |
86M | 157B | 3.6 hours |
microseconds |
86B | 157T | 152 days |
nanoseconds |
86T | 157 quadrillion | impractical (narrow the date range) |
python3 plutus.py --strategy timestamp # seconds, 2009-2013
python3 plutus.py --strategy timestamp --timestamp-resolution milliseconds # ms resolution
python3 plutus.py --strategy timestamp --timestamp-resolution microseconds \
--time-start 2009-01-03 --time-end 2009-02-01 # 1 month at μs
python3 plutus.py --strategy timestamp --timestamp-resolution nanoseconds \
--time-start 2009-01-03 --time-end 2009-01-04 # 1 day at ns
python3 plutus.py --strategy timestamp --timestamp-mode sha256 # SHA-256 hashedTests candidate passphrases using multiple historically accurate key derivation methods from the 2008-2013 Bitcoin era. Different early tools derived keys differently — Plutus tries all known methods for each phrase:
| Method | Description | Era |
|---|---|---|
SHA-256(phrase) |
Standard brain wallet — most common | 2009-present |
SHA-256(SHA-256(phrase)) |
Double-SHA — mimics Bitcoin's internal hashing | 2010-2012 |
SHA-512(phrase)[:32] |
Truncated SHA-512 — some early deterministic wallets | 2011-2013 |
| Raw UTF-8 zero-padded | Very early/naive implementations — phrase bytes as key | 2009-2010 |
SHA-256^10/100/1000(phrase) |
Iterated hashing — "hardened" brain wallets | 2011-2013 |
Each phrase produces ~7 candidate keys (one per method). At ~200K keys/sec this adds negligible overhead since the scalar multiplication dominates.
Supply a phrase file (one per line) for known/suspected passphrases, and/or a wordlist for combinatorial generation:
python3 plutus.py --strategy brain-wallet --phrases known_phrases.txt
python3 plutus.py --strategy brain-wallet --wordlist words.txt --max-words 3
python3 plutus.py --strategy brain-wallet --phrases phrases.txt --wordlist words.txtA 10K-phrase file (~70K keys with all methods) completes in under a second. A 5,000-word wordlist with 3-word combinations (125 billion phrases × 7 methods ≈ 875 billion keys) takes ~50 days.
Short BIP-39 mnemonics. Tests candidate seeds from the standard 2048-word BIP-39 wordlist with fewer words than the standard 12-24. Very slow (~1K/sec due to PBKDF2 with 2048 iterations per mnemonic).
python3 plutus.py --strategy weak-mnemonic --mnemonic-words 2 # 4.2M, ~70 min
python3 plutus.py --strategy weak-mnemonic --mnemonic-words 3 # 8.6B, ~100 CPU-daysRequires the BIP-39 English wordlist at wordlists/english.txt (download).
Chains multiple strategies in sequence. Run the fastest/smallest keyspaces first.
python3 plutus.py --strategy composite \
--strategies "low-entropy,timestamp,brain-wallet" \
--max-bits 32 --phrases phrases.txt| Flag | Default | Description |
|---|---|---|
-s, --strategy |
random |
Key generation strategy |
--max-bits |
40 | low-entropy: bits of entropy (2^N keys) |
--time-start |
2009-01-03 |
timestamp: start date (YYYY-MM-DD) |
--time-end |
2013-12-31 |
timestamp: end date (YYYY-MM-DD) |
--timestamp-mode |
raw |
timestamp: raw (sequential) or sha256 (hashed) |
--timestamp-resolution |
seconds |
timestamp: seconds, milliseconds, microseconds, nanoseconds |
--wordlist |
none | brain-wallet: path to wordlist file |
--phrases |
none | brain-wallet: path to known phrases file (one per line) |
--min-words |
1 | brain-wallet: minimum words per phrase |
--max-words |
4 | brain-wallet: maximum words per phrase |
--mnemonic-words |
3 | weak-mnemonic: BIP-39 word count |
--strategies |
none | composite: comma-separated strategy list |
| Flag | Default | Description |
|---|---|---|
-t, --address-types |
p2pkh,bech32 |
Address types to scan. Add p2sh at ~40% cost. |
-c, --cpu-count |
CPU count - 1 | Number of worker processes |
-n, --max-keys |
0 (unlimited) | Stop after N keys total |
--max-time |
0 (unlimited) | Stop after N seconds |
-o, --output |
plutus.txt |
File for match output |
| Flag | Default | Description |
|---|---|---|
-d, --database |
database/12_26_2025/ |
Path to database directory |
--bloom-size |
256 | Bloom filter size in MB |
--no-cache |
off | Force rebuild from text files |
--rebuild-cache |
off | Build/rebuild cache and exit |
--seed-file |
plutus_seeds.json |
Seed file for resume support |
| Flag | Default | Description |
|---|---|---|
-v, --verbose |
0 | Show generated addresses (slow) |
-q, --quiet |
off | Suppress progress, log matches only |
--log-file |
none | Write logs to file |
--status-interval |
1.0 | Status update interval (seconds) |
| Flag | Description |
|---|---|
--native-ec |
Force pure Python EC math (no coincurve; required for PyPy) |
# Default: P2PKH + Bech32, all cores, C accelerator if available
python3 plutus.py
# Include P2SH addresses (slower)
python3 plutus.py -t p2pkh,p2sh,bech32
# Bounded run: stop after 10M keys or 60 seconds
python3 plutus.py -n 10000000 --max-time 60
# Use 4 cores, quiet mode, log to file
python3 plutus.py -c 4 -q --log-file scan.log
# Pre-build the database cache (for automation)
python3 plutus.py --rebuild-cache
# Run on PyPy (pure Python, no compiled deps)
pypy3 plutus.py --native-ec
# Run speed benchmark
python3 plutus.py time
# Run correctness tests
python3 plutus.py testPlutus automatically saves each worker's position on shutdown (including Ctrl+C) to a seed file (plutus_seeds.json by default). On the next run, workers resume exactly where they left off — no repeated work.
# First run — workers pick random starting points, scan for 60 seconds
python3 plutus.py --max-time 60
# Output: "Seeds saved to plutus_seeds.json (9 workers)"
# Output: "Resume with: python3 plutus.py --seed-file plutus_seeds.json"
# Resume — workers continue from saved positions
python3 plutus.py
# Output: "Loaded 9 worker seeds from plutus_seeds.json"
# Output: "Worker 0: starting at key 0x7bfc45179cd84645..." (matches previous next_key)The seed file is human-readable JSON:
{
"total_keys_checked": 9750528,
"elapsed_seconds": 5.1,
"workers": [
{
"start_key": "0x7bfc45179cd84645...",
"keys_checked": 4885504,
"next_key": "0x7bfc45179cd84645..."
}
]
}To discard saved progress and start fresh with new random keys:
rm plutus_seeds.json # Delete the seed file
python3 plutus.py # New random starting pointsOr use a different seed file:
python3 plutus.py --seed-file run2_seeds.jsonIf you resume with a different --cpu-count than the previous run:
- Fewer workers: Only the first N seeds are used, remaining are discarded
- More workers: Extra workers get new random starting points
- The seed file is overwritten with the new worker set on next shutdown
For multiple machines scanning independently:
- Independent random: Each machine runs normally — the 2^256 keyspace is so vast that random starting points will never overlap in practice
- Partitioned ranges: Use different
--seed-fileper machine. Manually set starting keys in the JSON to partition the keyspace - Progress tracking: Each machine's seed file records
total_keys_checkedand per-worker positions. Collect these to track aggregate coverage
The random approach is statistically sound — the keyspace is ~10^77, funded addresses occupy ~10^7, and overlap probability between any two billion-key scans is essentially zero.
A unified build script detects your platform and builds everything available:
./build.shThis builds:
- CPU C accelerator (
plutus_accel.so) — all platforms, ARM SHA-256 hardware auto-detected - Metal GPU accelerator (
plutus_gpu.metallib+plutus_gpu_bridge.so) — macOS with Apple Silicon only
Or build manually:
# CPU accelerator (all platforms)
cc -O3 -march=native -mtune=native -shared -fPIC -o plutus_accel.so plutus_accel.c
# GPU accelerator (macOS Apple Silicon)
xcrun metal -O2 -c -o plutus_gpu.air plutus_gpu.metal
xcrun metallib -o plutus_gpu.metallib plutus_gpu.air && rm plutus_gpu.air
cc -O3 -shared -fPIC -framework Metal -framework Foundation -o plutus_gpu_bridge.so plutus_gpu_bridge.mThe tool auto-selects the best backend for each workload at runtime:
| Strategy type | Backend selected | Throughput |
|---|---|---|
| Sequential (random, low-entropy, raw timestamps) | CPU C accelerator | ~22M keys/sec (9 cores) |
| Non-sequential with GPU available | Metal GPU | ~2.3M keys/sec (32 GPU cores) |
| Non-sequential without GPU | CPU coincurve | ~200K keys/sec |
Any strategy with --native-ec |
Pure Python | ~570K keys/sec (PyPy) |
For non-sequential strategies (brain wallets, SHA-256 timestamps), the GPU is 11x faster than CPU scalar multiplication. The GPU timestamp kernel generates keys on-GPU with zero CPU→GPU transfer. For sequential strategies, the CPU's point addition optimization is unbeatable.
Profile-guided optimization can provide 5-15% additional throughput:
./build.sh --pgo # 3-phase: instrument → train 30s → rebuild with profilespython3 plutus.py test # End-to-end crypto verification
python3 test_accel_correctness.py # C extension unit tests (field math, hashing, EC)
python3 test_accel.py # C extension integration test (batch scanning)
python3 test_pyhash.py # Pure Python hash verification
python3 test_gpu.py # GPU correctness + benchmark (requires local terminal)When a match is found, it's written to the output file (plutus.txt by default) and a backup (plutus.txt.bak):
hex private key: 0000000000000000000000000000000000000000000000000000000000000001
WIF private key: KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn
public key: 0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
address (p2pkh): 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
blockchain.com: https://www.blockchain.com/btc/address/1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
mempool.space: https://mempool.space/address/1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
Each match includes direct links to blockchain explorers where you can verify the address balance and transaction history.
-
Verify the balance — Open the blockchain explorer link to confirm the address has funds. Many addresses in the database may have been emptied since the database was last updated.
-
Import the private key — The WIF private key (starts with
KorLfor compressed) is the standard import format supported by all Bitcoin wallets:- Electrum: File → New/Restore → Import Bitcoin addresses or private keys → paste the WIF key
- Bitcoin Core:
importprivkey "KwDiBf89Q..." "recovered" falsein the console - BlueWallet (mobile): Add Wallet → Import Wallet → paste WIF key
- Sparrow Wallet: File → New Wallet → Airgapped Hardware Wallet → paste WIF in the Keystore tab
- Coinomi (mobile): Add Wallet → Bitcoin → Restore Wallet → paste WIF key
-
Transfer funds immediately — Once imported, send the balance to an address you fully control. Discovered private keys should be considered compromised since the same key generation weakness that made them discoverable could be exploited by others.
-
Security notes:
- The hex private key is the raw 256-bit secret. Keep it confidential.
- The WIF (Wallet Import Format) is the same key with a checksum, encoded in Base58. This is what wallets accept.
- The public key is safe to share — it cannot be used to derive the private key.
- Always verify on a blockchain explorer before attempting to import or spend.