GPU / CUDA · secp256k1 · high-performance search

A key-space search engine
built from zero, and proven
before it was made fast.

MasterKey is a Bitcoin private-key search engine written from scratch for SIMT architectures. Every mathematical layer — field arithmetic, elliptic-curve operations, the Hash160 pipeline, Base58Check — was validated bit-exact against independent cryptographic oracles before a single performance optimization was accepted. From the first functionally correct build to today, throughput grew ~320×.

Rate depends entirely on the card, so no figure on this page appears without one: ~5.81 G/s on an RTX 5090, ~2.46 G/s in production on an RTX 4070 Ti SUPER, ~590 M/s on an RTX 2060, ~142 M/s on a Pascal-era GTX 1080. Eleven cards in total, across five NVIDIA architectures, all measured on real runs — never extrapolated.

production run — RTX 4070 Ti SUPER
MasterKey   by: Mr.Luis

$ masterkey_cli -m vanity -v 1PWo3Je \
      -r 400000000000000000:7fffffffffffffffff -R \
      -t 553648128 --no-glv --out-tag 2

[init]  field arithmetic ....... 64-bit limbs
[init]  batch inversion ........ safegcd / divstep62
[init]  center generation ...... precomputed offset table
[init]  center buffer layout ... struct-of-arrays
[run ]  throughput ............. 2,442 Mkeys/s
[run ]  keys tested ............ 758.01 T  (3.6 d continuous)
5.81 G/s
peak — RTX 5090
2.46 G/s
production — 4070 Ti SUPER
320×
over first correct build
0
re-verification failures

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.

private key k256 bits, drawn inside the search range
secp256k1 scalar multiplication · P = k·G
EC point P = (x, y)compressed-only
SHA-256 → RIPEMD-160, message build via __byte_perm
hash160 — 20 bytes
match check — Bloom pre-filter + binary search, or byte-range compare
Base58Check encode — on match only
Bitcoin address

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

LayerValidation oracleCoverage
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

first correct build
7.72 M/sbaseline
end of phase 1
~1,300 M/s168×
+ offset-table centers
~2,322 M/s301×
+ SoA center buffer
~2,462 M/s320×

What produced the gains

OptimizationOriginMeasured gain
Candidate grouping — batched modular inversionread from KeyHunt-Cuda, ported with own arithmetic~17.6×
Kernel split — EC vs. hashown diagnosis via cudaEvent~3.8×
Offset-table center generationown diagnosis — stage was 32.1% of hop time1.71–1.81×
Compressed-only modescope decision1.66–1.83×
Native 64-bit limb field arithmeticown rewrite+26.6%
__noinline__ on hash/match functionsread from KeyHunt-Cuda+22.1%
Block size 256 → 128 threadsoccupancy math + measurement+14–15%
SHA-256 message build via __byte_permread from KeyHunt-Cuda, independently validated+8.9%
Struct-of-arrays center bufferown 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.

GPUArchitectureSMClock ThroughputMk/s per SM·GHzrelative
RTX 4090Ada Lovelace 1282.52 GHz 4,324–4,494 13.93
RTX 4070 Ti SUPERAda Lovelace 662.61 GHz 2,440–2,464 14.16
RTX 2060 12GBTuring 341.85 GHz 591–607 9.49
Titan XpPascal 301.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).

GPUArchitectureThroughput TDPMk/s per Wrelative
RTX 5090Blackwell 5,814 ~520 W~11.2
RTX 4090Ada Lovelace 4,928 450 W10.95
RTX 5080Blackwell 3,336 360 W9.27
RTX 4070 Ti SUPERAda Lovelace 2,526 285 W8.86
RTX 5060 TiBlackwell 1,327 180 W8.12
RTX 5060Blackwell 1,109 145 W7.65
RTX 4060Ada Lovelace 900 115 W7.83
RTX 3060Ampere 737 170 W4.34
RTX 2060Turing 590
RTX 3050Ampere 451 100 W4.51
GTX 1080Pascal 142 200 W0.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.

ModeMatches againstCheck strategy
addressBitcoin addressesBloom pre-filter + binary search over a sorted target list — designed for large lists
rmd160raw hash160 targetssame path, skipping Base58 decoding of the target list
xpointraw EC X coordinateno hashing at all — used when the target is already known as a curve point
vanityBase58 address prefixbyte-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.

masterkey_cli — options
-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.