overview
What MasterKey is
MasterKey is a CUDA engine that brute-forces a bounded region of the secp256k1 private-key space, deriving the full elliptic-curve point and Hash160 for every candidate and testing it against a target — a single address, a large sorted target list, a raw curve point, or a Base58 prefix. It was built as the data-collection instrument for an independent statistical study, which is why total control over sampling, seeding and per-match instrumentation mattered more than reusing an existing tool.
Three established community tools — Keyhunt, BitCrack and KeyHunt-Cuda — were used exclusively as reading references during development, never as a fork or a copy base. Every technique adopted from them was reimplemented from first principles and independently revalidated before being accepted.
Design principles
01Correctness first
No performance optimization is accepted before bit-exact correctness is confirmed against an independent oracle — never against the reference tools, which could carry undiscovered bugs of their own.
02Clean separation
core/ holds the GPU-free reference implementation, gpu/ the CUDA
kernels, cli/ the orchestration layer. Every kernel has a CPU counterpart to be
checked against.
03Measure, never assume
Performance comes from controlled A/B measurement, not from intuition. Hypotheses refuted by real measurement are documented in the same detail as the ones that worked.
architecture
The pipeline
Every candidate travels the same path. Base58Check encoding runs only on a match, so its cost is fully amortized; the hot path is scalar multiplication, the affine walk, and the hash chain.
__byte_permThe grouped hop
The engine's unit of work is a hop: one kernel launch in which each of millions of threads processes a group of 128 consecutive candidates around a single center point, walking ±64 steps by affine point addition. Affine addition needs one modular division per candidate — the expensive operation. Batching those divisions into a single amortized inversion (Montgomery's trick: accumulate products, invert once, distribute back) drops the per-candidate cost from a full inversion to a fraction of one modular multiplication. The inversion itself is a binary GCD, safegcd-style with 62-bit divsteps.
Sequential mode
Each hop advances the whole center set by a fixed jump vector, precomputed once at start. No center is ever rebuilt from scratch — territory is covered contiguously and exhaustively.
Random mode
Each hop starts from a base drawn uniformly inside the range with an independent seed, then covers a contiguous exhaustive window before drawing the next base — unbiased range coverage without systematic repetition of territory.
Convergent engineering
The same batched-inversion technique is the core of mkp224o,
the vanity generator for Tor v3 / ed25519 — independent convergence on the same answer for the
same class of constrained elliptic-curve search problem, confirmed by reading that project's
source.
validation
Proven against independent oracles
Each layer was checked against an audited, widely trusted implementation before any optimization work began on it. This table is the actual validation record of the engine.
| Layer | Validation oracle | Coverage |
|---|---|---|
Field arithmetic (Int256, 32 and 64-bit) |
GMP | 1,000,000+ cases/op — 100% parity |
| Elliptic-curve point operations | libsecp256k1 v0.7.1 (Bitcoin Core's curve library) | 1,000,000+ cases including privkey = 1 and privkey = order−1 |
| Hash160 pipeline | FIPS 180-4 vectors | standard golden vectors |
| Base58Check | publicly known puzzle addresses | byte-by-byte re-verification |
| Binary-GCD modular inversion | GMP | 1,000,000 / 1,000,000 |
| GLV endomorphism (β/λ) | independent Python curve arithmetic + GPU bridge kernel | 20,000 / 20,000 |
| Offset-table center generation | byte-by-byte vs. full scalarMult reference path |
4,325,376 / 4,325,376 identical reproduced on two physically distinct GPUs |
| Struct-of-arrays center buffer | parity suite + external Python ecdsa check |
1,070,753 matches — zero failures a real found key re-derived with no code from this project |
Bugs the validation caught
None of these were found by reading the code. Every one was isolated by an automated test failing:
- generator constantA transcription error in the 256-bit generator point
G— 100% test failure, isolated by comparing against operations using arbitrary points. It happened twice, independently, which is why 256-bit constants are never hand-transcribed in this project anymore. - carry chainA structural bug in a hand-written PTX carry chain: two carries incorrectly interleaved through the same hardware flag.
- jump computationA miscalculated jump between sequential batches, causing overlap instead of coverage of new territory.
- silent result lossA fixed 256-slot per-hop result buffer silently discarding matches past the 256th in a single hop — found while investigating a collection rate ~27× below prediction on a high-frequency prefix.
- range overscanAn overscan bug in the production range mode, walking marginally outside the declared bounds.
Why validation runs against oracles, not against other tools
Design principle 01 says correctness is confirmed against an
independent oracle, never against the reference tools, because those could carry
undiscovered bugs of their own. That was written as a precaution. It is no longer
hypothetical.
KeyHunt-Cuda v1.07, built on Linux with a current toolchain following the project's
own Makefile, produces a binary whose Base58Check encoder emits invalid checksums.
It derives the correct private key, computes the correct hash160, then fails to match its own
target and discards the hit with a warning. Verified on an RTX 4070 Ti against Bitcoin puzzle
#20: the same source, rebuilt only with different optimization flags, writes the key to
Found.txt instead of rejecting it.
If you run KeyHunt-Cuda, check your own build — it takes ten seconds. Run
./KeyHunt -c and look at the five vectors whose addresses start with
1. If any says Failed, that binary finds keys and throws them away.
Two further defects do not depend on how you compiled: the search never stops at the end of
the range you declared (measured: 251,658,240 keys scanned inside a 10,001-key range, with
the tool's own counter reporting 2,516,582% complete), and in random mode the starting key is
drawn from [0, rangeEnd) — the start of your range is ignored.
Full method, evidence and the list of things that were checked and found correct:
keyhunt-cuda-audit.md
(a fuller technical writeup with declared proof levels is at
AUDIT_KEYHUNT_CUDA_2026-08-05_EN.md).
No source file was modified for these tests.
Scope: only the version and build described in that document was audited.
A second audit, same policy: BitCrack
BitCrack is the most cited GPU tool for the Bitcoin puzzle
transaction — ~1,000 GitHub stars, a dedicated bitcointalk.org thread. Popularity is
exactly why it got the same treatment as KeyHunt-Cuda.
Loading more than 16 target addresses fails immediately, every time — not
slower, a hard refusal (Error: invalid argument) before a single key is
scanned. The Bitcoin puzzle transaction has 32 addresses. The cause is a bloom-filter mask
copied into GPU memory with the size of a pointer instead of the size of the value —
confirmed with a minimal, from-scratch CUDA program that isolates just those three lines.
A community member found and correctly fixed the same bug independently in October 2024;
the fix has never been merged.
Resuming a checkpoint with a non-default --stride can hang forever,
silently. The value is written in hex and read back as decimal, so most non-default
strides become zero on resume — the search keeps running at full GPU throughput, counter
climbing, while never covering another key. And BitCrack installs no signal handler at
all: stopping it any way other than letting it finish always costs progress, and without
--continue it costs everything.
Every result this audit was able to produce — 46+ known keys spanning the full 256-bit
range — came back correct. The core cryptography was read line by line against the
specifications with no defect found. What's broken is the orchestration around a correct
engine, the same shape of problem as KeyHunt-Cuda, not the arithmetic.
Full method, evidence and the list of things that were checked and found correct —
including 46+ known keys spanning the full 256-bit range, all correct:
AUDIT_BITCRACK_CUDA_2026-08-15_Port_EN.md.
No source file was modified for these tests.
Scope: BitCrack's CUDA backend, the version and build described in that
document.
A third audit, same policy: CUDACyclone
CUDACyclone is smaller and newer than the other two, but actively
maintained — and its own changelog says a "key skipping" bug was fixed, twice, by two full
kernel rewrites. That history is exactly what calls for verification rather than trust.
The one case its own test suite never covers reproduces the defect cleanly:
privkey=1, the best-known private key that exists, inside a declared range that
trivially contains it. CUDACyclone scans the whole range and reports
KEY NOT FOUND (exhaustive). The cause is a batched modular-inversion product
that silently zeroes out whenever a group's center falls close enough to zero or to the
curve's order — the same class of defect this project's own engine had and fixed in August
2026. Only the group's center survives, because it is checked before the inversion; every
other candidate in the group is silently lost.
In practice this does not touch CUDACyclone's stated use case — Bitcoin puzzle ranges sit far
from both boundaries. It touches exactly whoever tries to validate the tool with small known
keys or at the extremes of the key space, which is precisely the natural sanity check, and
precisely what its own test generator never tries.
Full method, evidence and the list of things that were checked and found correct —
including a clean compute-sanitizer run and correct SIGINT handling:
AUDIT_CUDACYCLONE_2026-08-15_EN.md.
No source file was modified for these tests.
Scope: the version and build described in that document. Five tools have
now been put through this same rubric — the comparative summary, and every individual audit
including this project's own code, are at
COMPARATIVE_AUDIT_FIVE_ENGINES_EN.md.
Check it instead of believing it
249,476 addresses collected by this engine are published together with
the private key that generates each one, at
github.com/Mrluis-Naka/MasterKeyWeb
— raw file VANITYKEYFOUND2.txt (13.7 MB). The keys are
worthless by construction: all of them sit inside [2^70, 2^71), the public search
interval of Bitcoin puzzle #71, and none has ever held a balance.
Published alongside it is verify.py, which re-derives every address
from its key and compares. It carries no third-party dependency and not one line from this
engine — secp256k1 arithmetic, Base58Check and RIPEMD-160 are implemented from the
specifications, so a passing run is evidence about the data rather than about a shared
library. An address list alone would prove nothing; the key is the evidence, because deriving
the address from it only works one way.
The rest of the statistical study's underlying material is published in the same repository:
the frozen per-instance address sets and accumulated corpus, the range-generalisation
censuses, the fixed-bit-line data, the scalar-multiplication trace dataset, the pre-registered
hypothesis with its OpenTimestamps proof, and the analysis scripts that reproduce several of
the study's reported statistics independently against scipy. Full file-by-file
map:
DATA_MANIFEST.md.
performance
One card, 320×:
7.72 M/s → 2.46 G/s
The trajectory below is a single GPU — an RTX 4070 Ti SUPER — measured at each milestone in random mode, so the gains are the engine's and not a hardware upgrade. The first bar is the first functionally correct build; it is not a rendering artifact, it is 0.3% of the current figure. Where the finished engine lands on other cards is in the table below.
SAME CARD THROUGHOUT — RTX 4070 Ti SUPER · random mode · vanity target
What produced the gains
| Optimization | Origin | Measured gain |
|---|---|---|
| Candidate grouping — batched modular inversion | read from KeyHunt-Cuda, ported with own arithmetic | ~17.6× |
| Kernel split — EC vs. hash | own diagnosis via cudaEvent | ~3.8× |
| Offset-table center generation | own diagnosis — stage was 32.1% of hop time | 1.71–1.81× |
| Compressed-only mode | scope decision | 1.66–1.83× |
| Native 64-bit limb field arithmetic | own rewrite | +26.6% |
__noinline__ on hash/match functions | read from KeyHunt-Cuda | +22.1% |
| Block size 256 → 128 threads | occupancy math + measurement | +14–15% |
SHA-256 message build via __byte_perm | read from KeyHunt-Cuda, independently validated | +8.9% |
| Struct-of-arrays center buffer | own coalescence hypothesis | +2.3–2.6% |
Every card measured
There is no single "keys per second" figure for this engine, and any tool that quotes one without naming a card is quoting an accident of which GPU it ran on. Four cards across three NVIDIA architectures were measured on real runs — never estimated from a spec sheet.
| GPU | Architecture | SM | Clock | Throughput | Mk/s per SM·GHz | relative |
|---|---|---|---|---|---|---|
| RTX 4090 | Ada Lovelace | 128 | 2.52 GHz | 4,324–4,494 | 13.93 | |
| RTX 4070 Ti SUPER | Ada Lovelace | 66 | 2.61 GHz | 2,440–2,464 | 14.16 | |
| RTX 2060 12GB | Turing | 34 | 1.85 GHz | 591–607 | 9.49 | |
| Titan Xp | Pascal | 30 | 1.58 GHz | 248–262 | 5.38 |
Throughput in Mkeys/s, random mode, vanity target. The Titan Xp figure is the spread across
four physical cards in one machine; the others are spreads across repeated production runs.
Card variants confirmed by cudaGetDeviceProperties, not inferred from the model
name.
Efficiency does not cross architectures
The rightmost column is the reason each figure carries its GPU. Between two Ada cards the constant holds to within 2% — 13.93 against 14.16 — so a 4090 figure predicts a 4070 Ti SUPER figure well. Across generations it collapses: Turing delivers 67% of Ada's efficiency per SM·GHz, Pascal only 38%. Extrapolating the RTX 2060 from the Ada constant predicted ~780 Mk/s; the measured value was ~600, an error of +23%. For the Pascal Titan Xp the same extrapolation was off by 2.6×. Every card on this page was run before its number was written down.
Every card benchmarked — the full comparison protocol
A later, broader campaign ran the same fixed command against the same narrow range on every card available — the config built specifically for apples-to-apples comparison, not the production spread above. Eleven cards, five NVIDIA architectures, GTX 1080 (Pascal) through RTX 5090 (Blackwell).
| GPU | Architecture | Throughput | TDP | Mk/s per W | relative |
|---|---|---|---|---|---|
| RTX 5090 | Blackwell | 5,814 | ~520 W | ~11.2 | |
| RTX 4090 | Ada Lovelace | 4,928 | 450 W | 10.95 | |
| RTX 5080 | Blackwell | 3,336 | 360 W | 9.27 | |
| RTX 4070 Ti SUPER | Ada Lovelace | 2,526 | 285 W | 8.86 | |
| RTX 5060 Ti | Blackwell | 1,327 | 180 W | 8.12 | |
| RTX 5060 | Blackwell | 1,109 | 145 W | 7.65 | |
| RTX 4060 | Ada Lovelace | 900 | 115 W | 7.83 | |
| RTX 3060 | Ampere | 737 | 170 W | 4.34 | |
| RTX 2060 | Turing | 590 | — | — | |
| RTX 3050 | Ampere | 451 | 100 W | 4.51 | |
| GTX 1080 | Pascal | 142 | 200 W | 0.71 |
Throughput in Mkeys/s, fixed narrow-range command, identical across every row. TDP is the power draw actually observed on the card (provider-imposed caps in several cases — RTX 5060 Ti, RTX 3050 — not the card's rated maximum). RTX 2060 was not power-logged, so no per-watt figure is given for it. These numbers are not directly comparable to the production-spread table above — different command, different range — which is why they are reported as a separate table rather than merged into it.
Two most recent architectures cluster; the drop-off is real
Ada Lovelace and Blackwell sit in the same efficiency band — 7.6 to 11.2 Mk/s per watt, five cards across two architectures and three tiers. Ampere drops to less than half of that, 4.3–4.5, regardless of which specific card. Pascal collapses further still: the GTX 1080 delivers 0.71 Mk/s/W — 6.1× less efficient than the Ampere RTX 3060, and it is not power-limited getting there (it pulled 100 W of a 200 W ceiling, clock pinned, no throttle flag active). The architecture itself is the reason, not the card's power budget.
Negative results, reported with the same rigor
Cross-thread batch inversion (block level: 7× slower; warp level: 17× slower).
Hand-written inline PTX (7.6% slower than nvcc's own uint64_t codegen).
GLV endomorphism applied to scalarMult (≈0 — scalar multiplication had stopped
being the hot path after grouping; the endomorphism was later repurposed successfully for a
different job). Splitting EC and hashing into two already-grouped kernels (nearly 2× slower —
lost coalescence). __launch_bounds__ forcing fewer registers (register spill ate
the occupancy gain). A pre-negated Y table (trading a cheap register subtraction for an extra
global memory read was a bad deal).
The most instructive one: a benchmark comparing the real modular inversion against a
pass-through stub found the real version consistently ~9% faster. The local
array traffic that batched inversion requires (~4 KB/thread, confirmed via
nvcc -Xptxas -v) already dominates the stage at production group size; the
inversion's extra register arithmetic hides that latency through instruction-level
parallelism instead of adding visible cost. Confirmed from a second angle by a clean
crossover as group size varies. Conclusion: don't optimize it — it is already effectively free.
modes & interface
Four search modes, one kernel
All modes share roughly 90% of the same kernel — the grouped hop — and differ only in the final match-check function.
| Mode | Matches against | Check strategy |
|---|---|---|
| address | Bitcoin addresses | Bloom pre-filter + binary search over a sorted target list — designed for large lists |
| rmd160 | raw hash160 targets | same path, skipping Base58 decoding of the target list |
| xpoint | raw EC X coordinate | no hashing at all — used when the target is already known as a curve point |
| vanity | Base58 address prefix | byte-range compare against the computed hash160 — no Bloom needed for the small number of ranges a text prefix produces |
Command-line interface
The flag set deliberately mirrors Keyhunt's, with semantics read directly from its source rather than guessed — so there is no learning curve for anyone already using it.
-m MODE rmd160 | address | xpoint | vanity -f FILE target list file (all modes except vanity) -v PREFIX Base58 address prefix, vanity mode only -r A:B explicit hex range, START or START:END -b BITS range = [2^(BITS-1), 2^BITS) -R random mode — each hop starts at a random base -t N total GPU parallelism — candidates per kernel batch --out-tag T suffix for output files, for parallel instances --block-size threads per block override --no-glv skip the two GLV match variants — keeps every found key strictly inside the declared range --fix-bits pin chosen private-key bit positions to 1 --fix-bits-random N thousands of never-repeating N-bit combinations per launch, via a deterministic rank→combination bijection
Graceful interruption is a first-class feature: a running search can be swapped for a new binary mid-flight without truncating the in-progress results file or duplicating a single record.
research
Why it was built
MasterKey is the instrument of an independent statistical study on the conditional uniformity of Bitcoin addresses under a vanity-prefix filter. Its frozen sample is ~51,000 addresses, drawn from ~760 trillion tested candidates — a scale that is not reachable in reasonable time or cost without the engineering work described above. Production collection continued well past that snapshot, which is why the published corpus below is several times larger.
Using an existing tool was not an option for it, because the study depends on things those tools do not expose: guaranteed independent RNG seeding per process, independent verification of every single match, and per-match instrumentation (hop index, timestamp) that has to be trusted at the level of the sampling process itself.
Engineering record
A full account of the architecture, the validation methodology, and every optimization — including the refuted ones — written to be replicable and auditable. Read it.
Statistical study
A separate manuscript on conditional uniformity under prefix filtering, with a pre-registered hypothesis and timestamped commitment before collection. Pre-registration and its OpenTimestamps proof.
Collected corpus
249,476 addresses published with the key of each — about 3.8 quadrillion candidates tested to produce them, one match every ~15.3 billion keys. Every match independently re-verified outside the engine that produced it. The full corpus, censuses and per-instance sets are in the data manifest.
Method discipline
A working hypothesis in the study — that matches concentrate in the upper half of a given range — was tested against 136,072 unique collected addresses across two GPUs and refuted. It is reported as such. Results that contradict the expectation carry the same weight here as results that confirm it.
responsible use
On dual use
A high-performance search engine for the Bitcoin private-key space is dual-use technology. The same mechanism that collects data inside a bounded, deliberately chosen range — with no relation to funded addresses — could in principle be pointed at addresses holding real value. Stating that plainly is part of the work, not a disclaimer bolted onto it.
- scopeThe ranges and prefixes used in collection have no known relation to funded addresses. The stated and only purpose of development is to serve as a research instrument.
- infrastructureEvery cloud instance used was security-audited before long runs: filesystem permissions, authorized SSH keys, and confirmed absence of third-party access.
- source codeNot published — a final decision, declared explicitly rather than omitted. The engineering detail itself is published in full (architecture, validation, the complete optimization trajectory); the source code alone stays withheld.
- collected dataPublished in full: 249,476 addresses with their private keys, plus an independent verifier, at github.com/Mrluis-Naka/MasterKeyWeb. Releasing the data costs nothing in dual-use terms — the keys lie in a public puzzle interval and hold no value — while making every claim on this page checkable by a third party.
- scale, honestlyA brute-force search of a full 256-bit key space is not made feasible by any factor described here. What changes with a ~320× speedup is the practical reach of statistical sampling inside deliberately bounded ranges — nothing more.