From VanitySearch to 50 GH/s – Inside the 253-256 Scanner
This is the story of how a CUDA kernel originally designed for vanity address generation was transformed into a 50 GH/s scanner for the 253-256 forgotten Satoshi wallets. The code is open-source, the math is public — the only novelty is the integration.
The Starting Point: VanitySearch
VanitySearch is a well-known CUDA tool that finds vanity Bitcoin addresses. On an RTX 4090, it achieves around 3-4 GH/s. The logic is simple: generate a private key, compute the public key, hash it to an address, and compare it to a pattern. It works, but it does a lot of unnecessary work for our use case.
The 253-256 campaign doesn't need vanity patterns. It needs to check whether a generated public key matches any of the 734,000+ public keys in the database. That's a completely different problem — and one that can be solved much faster.
X-Only Pubkey Lookup
Instead of hashing the public key (SHA-256 + RIPEMD-160) and encoding it (Base58 or Bech32), we compare the X coordinate of the compressed public key directly. For secp256k1, the X coordinate uniquely identifies the key for our purposes.
In the code, this is implemented in the _BinarySearchX32Only function:
This removes SHA-256 and RIPEMD-160 entirely from the hot path. The database contains 65-byte records (04 || X || Y), and we compare only the 32 bytes of X.
GLV Endomorphism — 6 Keys per Point Addition
The secp256k1 curve has an endomorphism (λ, β) that allows us to compute two additional public keys from a single point addition. The constants are defined in the code:
For every point P = k·G, we can compute:
P = k·G→ X coordinateqxP_neg = (n-k)·G→ X coordinateqx(negation leaves X unchanged)P_lambda = λ·k·G→ X coordinateλ·qxP_lambda_neg = λ·(n-k)·G→ X coordinateλ·qxP_lambda2 = λ²·k·G→ X coordinateλ²·qxP_lambda2_neg = λ²·(n-k)·G→ X coordinateλ²·qx
In the kernel, this is implemented with #if USE_ENDO:
One point addition → 6 lookups into the database. With the GPU sustaining ~8-9 Gpoint/s, this gives ~48-54 Gkeys/s effective.
Batch Inversion — Reducing Memory Bottlenecks
The bottleneck is memory access, not computation. Each lookup is a random access into a 128 MB prefix index. To amortize this, the kernel processes points in batches:
This reduces global memory reads and increases effective throughput. The 24-bit prefix index is stored in shared memory (LDS) where possible.
24-Bit Prefix Index — 128 MB on GPU
Instead of a full 160-bit hash index, we use a 24-bit prefix (first 3 bytes of the X coordinate). The index is built on the CPU once and uploaded to the GPU:
The index is 16,777,217 entries of 64-bit — ~128 MB. This fits easily on any modern GPU and reduces the search space from O(log n) to O(1) for the prefix lookup.
DEDUP Optimization — No Duplicate Work
The scanner uses a multi-round approach. Each round doubles the number of chunks to avoid scanning the same range twice. The kernel uses grid_mult and grid_off to handle this:
This ensures that when the number of chunks doubles, every second chunk is the same as the previous round. The grid_off offset shifts the starting point to cover the missing chunks. No duplicate work.
Edge Cases and TDR Timeouts
The secp256k1 curve has a prime order n. Edge cases (k=0, λ·k mod n = 0, etc.) are handled in the kernel:
The biggest challenge was TDR (Timeout Detection and Recovery) on Windows. Long-running kernels (>2 seconds) trigger the driver to reset the GPU. The solution: split the workload into smaller chunks, with a maximum of ~4 million keys per kernel launch, and carefully measure execution time.
Multi-GPU Support
The scanner supports multiple GPUs with two modes:
- Full range on each GPU — each GPU scans the entire range independently. Simple, but redundant.
- Split-GPU — divides the range equally between GPUs using the
--split-gpuflag. Each GPU scans a different part of the range.
The split logic divides RLEN (total range length) by the number of GPUs:
Verification — Tested on Known Ranges
The scanner has been tested and verified on multiple known ranges:
- 1-bit range — finds the generator point G (private key = 1).
- Puzzle 45 — correctly identifies the known public key in that range.
- Early 253-256 range — successfully finds the starting point of the range.
All tests confirm the scanner works as expected.
Live Production — With Additional Security
The scanner is currently running in production. For security reasons, the integration details — including the split-key mechanism and server-side logic — are not disclosed. This is to prevent anyone from extracting keys during the finding process.
It's not magic. It's engineering.
Every technique used here has been documented for years:
- GLV endomorphism — Hal Finney, 2003
- Batch inversion — Used in many crypto libraries
- 24-bit prefix index — Standard database optimization
What is new is the integration: combining them into a single, stable, production-ready binary that runs on any CUDA GPU from GTX 9xx to RTX 50xx.
What's Next?
The current speed is 50+ GH/s on RTX 4090. Future work includes:
- Larger database — expanding pubkeys.bin beyond 734,000 entries.
- Even faster lookup — using 32-bit prefix index instead of 24-bit (requires more VRAM).
🔗 GitHub: FastscanGPU 253-256 files
📖 Read more: Puzzle 71 Visualization · Pool Rules · How to Start · Security