Krackpot points your GPU at Bitcoin Puzzle 71, a wallet whose creator funded it on purpose in 2015 as a public, solvable challenge. The search runs in your browser through WebGPU. No install, no signup, no backend. This page covers how I built it, and it is honest about the odds, because the odds are the whole story.

Where this came from

For years I wanted to build a small educational tool that made one abstract idea concrete: how fast a weak password falls. Type four characters, watch a dictionary run or a brute-force sweep tear through it in seconds.

That idea turned into a browser project I never released. A password cracker for legacy ZipCrypto files, the old and weak ZIP encryption, the kind of thing you would reach for to recover your own locked archive. I built it mostly to race the same brute force against itself across three engines: WebGPU, WebAssembly, and plain Web Workers. The GPU version was the one worth keeping. Then it sat in a folder.

Krackpot is where that WebGPU brute-force engine finally went somewhere. I pointed it at a Bitcoin key instead of a password. The same machine that shreds a four-character password in seconds cannot make a dent in a real 256-bit key. Puzzle 71 lives in a keyspace of 270, and as the next section shows, a single GPU would take roughly a million years to expect a hit. My "how fast do passwords fall" demo became its own opposite: a wall that brute force never climbs.

The odds first

Han Solo said "never tell me the odds." I am going to tell you the odds.

Han Solo in the Millennium Falcon cockpit, captioned: never tell me the odds.

Puzzle 71's key sits somewhere in a range of 270 keys, about 1.18 sextillion. A GPU that checks tens of millions of keys a second still needs hundreds of thousands of years, on average, to sweep that range. These rates come from the live app, not from FLOPS math:

MEASURED IN-APPMEDIAN TO SWEEP 2⁷⁰
RTX 4070~44M keys/sec~430,000 yrs
RTX 3070~22.5M keys/sec~830,000 yrs
Apple M3 Max~26M keys/sec~720,000 yrs
MacBook Air M1~5.95M keys/sec~3,100,000 yrs

These figures are the uniform-sweep midpoint (half the range divided by the rate). Krackpot actually draws chunks at random with replacement, so time-to-find is geometric: the true median is about ln2 times the full-range mean, roughly 1.4x these numbers, and the same order of magnitude either way.

So it is a lottery. One machine will almost certainly never win. A million machines searching at once pulls the median down to about a year. I say this everywhere on the site, because in crypto the fastest way to look like a scam is to be shy about the math.

The pipeline

A Bitcoin address is the end of a one-way chain, and Krackpot runs that whole chain on the GPU for each candidate key:

  1. Private key: a 256-bit integer k.
  2. Public key: the curve point k * G on secp256k1, in compressed form (a 0x02/0x03 prefix byte plus the 32-byte X coordinate).
  3. SHA-256 of the 33-byte compressed public key.
  4. RIPEMD-160 of that SHA-256 digest. The 20-byte result is the public key hash (PKH).
  5. Compare the PKH against the puzzle address's decoded PKH. A match is a solved key.

None of those primitives ship with WGSL, the WebGPU shading language, so each had to be built up from 32-bit integers. I did not write this crypto by hand. It is LLM-assisted, and I lean on the deterministic test vectors below to trust it, not on having personally typed every carry.

256-bit math on a 32-bit machine

WGSL has no big integers. You get 32-bit unsigned integers and little else. So a 256-bit field element becomes eight u32 limbs, and every add, multiply, and reduction modulo the secp256k1 prime is spelled out limb by limb, with the carries propagated explicitly. Elliptic-curve point arithmetic sits on top of that, and the two hash functions on top of their own 32-bit word operations. One wrong bit anywhere and the search still runs, still looks busy, and checks the wrong keys forever. The test suite further down exists precisely because that failure is invisible.

The speed trick: no full scalar multiply per key

The obvious version computes k * G for every candidate. That is roughly 256 doublings and 128 additions per key, and it would drop throughput by 50 to 100 times. Krackpot never does it in the hot loop.

Within a chunk the candidate keys run in sequence: k, k+1, k+2. Their public keys run P, P+G, P+2G. Adding G is a single point addition, far cheaper than a fresh scalar multiply, so the search leans on that:

  • The CPU computes the chunk's base public key P = k * G once per chunk, in JavaScript, and uploads it.
  • Each GPU thread owns a slice of consecutive keys. It jumps to the start of its slice with a small precomputed table of multiples of G (22 entries, so a few additions get it there), then walks the slice at one point addition per key.

That drops the per-key cost from a full scalar multiply to one addition, which is what lets a browser tab reach tens of millions of keys a second.

Batch inversion with Montgomery's trick

Point addition in affine coordinates needs a modular inverse, the most expensive field operation there is, since it is a full Fermat exponentiation. One per key would eat all the savings above. So each thread stays in Jacobian coordinates, which put the inverse off, gathers a batch of 48 points, and inverts all 48 Z-coordinates at once with Montgomery's trick: one Fermat inverse and about three multiplies per element instead of 48 separate inverses. The batch caps at 48 for a second reason too. Safari's WGSL compiler limits function-scope variables to 8 KB, and the on-device arrays have to fit under that.

Endianness, the boundary that quietly ruins everything

secp256k1 scalars and the hash inputs and outputs are big-endian by convention. WGSL storage buffers are little-endian on the wire. Cross that boundary wrong and every address the pipeline derives is garbage. Krackpot fixes the byte order at every CPU-to-GPU and integer-to-hash handoff, and the pubkey serialization that feeds SHA-256 does its big-endian packing explicitly in the shader. The compressed-pubkey rule belongs to this same trap: the puzzle addresses derive from compressed keys, so an uncompressed pipeline would never land a hit no matter how fast it ran.

The GPU watchdog, and why dispatches stay small

The operating system watches the GPU. If one compute dispatch ties it up for more than about two seconds, the OS Timeout Detection and Recovery watchdog kills the GPU context and the page goes down with it. So Krackpot never submits a dispatch that could run that long. It calibrates itself instead:

  • The first dispatch on an unfamiliar device is deliberately tiny, one workgroup's worth, about 3,072 keys, so it cannot trip the watchdog before there is any timing data.
  • Every dispatch is timed. The size climbs toward a 250 ms target and halves the moment a dispatch crosses 500 ms, which keeps it well short of the two-second cliff.
  • The calibrated size is saved to localStorage, so a returning device starts at its known-good value.
  • If a dispatch loses the device anyway (watchdog, driver crash, tab suspend), the coordinator shrinks the size, persists the smaller value, re-requests the device, rebuilds the pipeline, and picks up on a fresh random chunk. I treat device loss as routine, not as an error.

Chunks come fresh from a cryptographic RNG (crypto.getRandomValues) on every dispatch, never sequentially. That buys two things. A backgrounded or interrupted tab just resumes on a new random chunk with nothing to check-point, and separate searchers do not all pile into the same stretch of the range. It costs something too: uncoordinated searchers overlap, so a real fleet needs up to about twice the work of a perfectly coordinated one. I skipped pooling on purpose, and that overlap is the price I decided to pay for it.

The test suite that has to pass before any search

Since a one-bit error is invisible while it runs, the search will not start until a deterministic self-test passes on your exact GPU and driver. It pushes known private keys through the full shader pipeline and checks the resulting addresses against answers I know independently:

Private key 1 has to produce 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH.
Private key 3 has to produce 1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb.
Starting from the public key for 1 and doing one point addition (G + G = 2G) has to produce the address for key 2, 1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP. This one exercises the incremental +G hot path, which is separate code from a full scalar multiply and can break on its own.

The gate is enforced, not a suggestion. On Start the app fingerprints your adapter plus the shader source. If nothing has changed since the last pass, it goes straight to searching. If anything is new, whether a first visit, a code change, a driver or browser update, or a different GPU, it runs the whole suite inline and unlocks the search only on a clean sweep.

When a GPU passes the check but still cannot really run it

Some mobile GPUs get a WebGPU device, clear part of the check, and are still not worth using. The Adreno line in recent Android phones is the clearest case. A Dawn/Adreno shader-compiler bug fails some diagnostic shaders outright, and a real search dispatch trips the watchdog even at the smallest size, so calibration bottoms out and throughput falls to about 119 keys a second. The math is not the problem there. The cross-checked search kernel computes correctly on that hardware. The device simply cannot run this workload at a useful speed, so the capability check is built to refuse it quickly rather than let the tab grind.

The other refusal you will actually hit is desktop Linux on a Chromium browser with Vulkan turned off, where WebGPU has no backend to run on. That one is fixable, so instead of a flat "not supported" the app works out which flags you need and tells you.

No backend, and what happens if you win

The search itself pulls in no libraries. The claim-time dependencies do load with the page, so you will see a request to esm.sh in the network tab before you press start, but nothing about your searching leaves the tab. The app is a static, offline-capable progressive web app. After one online visit it caches its whole shell, the HTML, the JavaScript, the self-hosted fonts, and the icons, and from then on it runs fully offline, search included.

If a key ever turns up, the recovered private key never leaves your browser, and the claim transaction is built and signed locally with @scure/btc-signer. It is never broadcast to the public mempool, and that is deliberate. A puzzle spend reveals the public key, and because the key sits in a known narrow range, anyone watching the mempool can recover it with Pollard's kangaroo in seconds to minutes and rebroadcast a higher-fee transaction that takes the coins. This has really happened, more than once, to earlier puzzle solvers.

So the claim is submitted privately instead, to two pools at once. The browser posts to Rebar Shield and to MARA Slipstream in parallel, both of which hand a transaction to miners without it touching the public network, so the key is only exposed once it is already in a block. Two pools rather than one because I tested this: Shield accepted my dry-run transaction and then did not get it mined for sixty-four blocks, while cheaper transactions to the same service confirmed. Accepted is not the same as mined.

The two get slightly different transactions, which is deliberate. Shield is paid through a dedicated output, so its copy carries a zero on-chain fee. Slipstream wants a normal fee rate, so its copy pays one. Since both spend the same coins they conflict, and only one can ever confirm. Either way you get the same 6 BTC.

As a further backup it encrypts the signed transactions to me over Nostr so I can submit them privately too. The private key and the raw signed hex are shown to you first regardless, and offline the claim is queued and relayed once you are back online. If both submissions fail, you are told to submit the hex yourself through a private service, never a public explorer.

That transaction pays a fixed 6 BTC to an address you enter and sends the remainder to me. The split shows on the page before you can press Start. Up front it is a fair fee. Found out later it would look like a scam, so I put it up front, here and in the app.

One machine will almost certainly never win. Someone still has to. That is the whole pitch, and the math on this page is the reason to believe it.