All posts
engineeringcomparisons

Proof-of-work CAPTCHA: how it works and where it breaks

A proof-of-work CAPTCHA makes the browser solve a puzzle instead of the human. How it works, why SHA-256 puzzles favour GPUs, why memory-hard Argon2id narrows the gap, and what proof-of-work cannot do alone.

Hauke Jung
|October 12, 2026|
6 min read

A proof-of-work CAPTCHA asks the visitor's browser, not the visitor, to solve a small computational puzzle before a form is accepted. A real person pays a fraction of a second of CPU time they never notice; a bot submitting thousands of forms pays that cost thousands of times over.

That is the whole idea. The rest of this page is how it works in practice, where it is strong, and where it is not enough on its own.

The challenge, solve, verify loop

  1. Challenge. When the page loads, the widget asks the server for a puzzle: a random prefix and a difficulty, expressed as a number of leading zero bits.
  2. Solve. In a Web Worker, the browser tries nonces until hash(prefix + nonce) starts with enough zero bits. There is no shortcut; it is a search, and the expected number of tries doubles with every extra bit.
  3. Verify. The form submits the nonce with the token. Your backend forwards it to the CAPTCHA server, which computes the hash once and checks it. The challenge is single-use, so a solved puzzle cannot be replayed.

Solving takes many hashes; verifying takes one. That asymmetry is what makes the scheme work.

Why it is invisible to people and expensive for bots

A visitor fills in a form for several seconds, so a puzzle that solves in the background during that time costs them nothing they can feel. In Bollwark's defaults, the expected solve on a desktop is about a quarter of a second.

A spammer's economics are different. If each submission costs CPU time, a million submissions cost a lot of CPU time. Proof-of-work does not make abuse impossible; it makes it a line item.

There is no image grid and no distorted text, so nobody is excluded because they cannot see, hear or interpret a challenge.

The SHA-256 problem

Most early proof-of-work CAPTCHAs used SHA-256, because browsers can compute it natively through crypto.subtle. The trouble is that SHA-256 is exactly what GPUs and mining ASICs are built to do. An attacker with a graphics card computes hashes far faster per euro than a visitor's phone, so the cost you set to be painless for the phone is trivial for the attacker. ALTCHA's own documentation rates plain SHA as having "very low" GPU/ASIC resistance (source).

Memory-hard puzzles: why Argon2id narrows the gap

Argon2id, the password-hashing winner, is memory-hard: each hash needs a fixed block of memory, so you cannot parallelise it cheaply on hardware that has lots of compute but little fast memory per core. That pulls the attacker's advantage back towards the visitor's.

Bollwark uses Argon2id by default (SHA-256 is available as an option) with 8 MiB of memory and two iterations per hash. On our CPU measurements, the browser (WebAssembly) is only about 1.3 to 1.5 times slower than native code doing the same hash, so an attacker gains little by skipping the browser. We have not benchmarked GPUs; the argument there is the qualitative one above, that memory-hard hashing removes most of the advantage GPUs have on SHA-256.

The trade-off is the long tail. A solve is a random search, so one visitor in twenty does about three times the expected work, and a low-end phone is several times slower than a desktop. Raising the memory cost raises everyone's cost in step, it does not change the ratio, so we keep the default modest and let operators tune it. The full measurements are in the configuration reference.

What proof-of-work cannot do alone

It is worth being blunt here. Proof-of-work raises the cost of each submission. It does not stop an attacker who is willing to pay that cost: at a 64 MiB memory setting, one submission still costs a funded attacker under two CPU-seconds. A credential-stuffing operation with a budget will pay that.

So proof-of-work has to sit inside a risk system. Bollwark scores every request twice:

  • When the puzzle is issued, it looks at request rate, header anomalies and, if you configure them, IP reputation and TLS fingerprint, and picks a tier: invisible, a checkbox with a harder puzzle, an even harder puzzle, or a 429 refusal.
  • When the form is submitted, it looks at what only exists then: time on page (measured by the server, not claimed by the browser), a honeypot field and a few behaviour counters. A submission can pass, pass with a warning in the log, or be blocked.

The puzzle makes each attempt expensive; the scoring decides who gets the expensive puzzle in the first place.

Proof-of-work CAPTCHAs compared

Project Licence Puzzle Self-host Notes
ALTCHA MIT (widget) PBKDF2 by default; SHA, scrypt and Argon2id supported Yes Hosted Cloud from €47 a month
Friendly Captcha Proprietary service Proof-of-work plus risk signals Enterprise plan only Plans from €9 a month
mCaptcha AGPL-3.0 SHA-256 Yes No stable release yet, per its README
Cap Apache 2.0 Parallel SHA-256 plus time-lock challenges Yes Says it sets no cookies
Bollwark MIT Argon2id by default, SHA-256 optional Yes Two-pass risk scoring, monitor mode

Every row except ours is based on the project's own public pages, linked above.

FAQ

Is a proof-of-work CAPTCHA accessible?

There is nothing to decipher, so it avoids the failure modes of image and audio puzzles. Bollwark's widget is also fully keyboard-operable and announces its state through live regions, and keyboard users carry no scoring penalty.

Does proof-of-work drain phone batteries?

At Bollwark's defaults, measured on an Apple Silicon laptop, the expected solve is about 0.25 seconds and the 95th percentile about 0.8 seconds. A low-end phone can be four to eight times slower than that, so an unlucky visitor on an old phone may wait a few seconds. It runs once per form, and again only if the visitor leaves the page open long enough for the puzzle to expire (five minutes by default).

Can I run one myself?

Yes. Bollwark is one MIT-licensed binary; see the self-hosted proof-of-work CAPTCHA. If you would rather not operate it, there are hosted plans. For how proof-of-work compares with the big three, read Turnstile vs reCAPTCHA vs hCaptcha.

Related Posts

comparisonsprivacy

Looking for an hCaptcha alternative? Here is what changes

hCaptcha's free tier shows most visitors a challenge. Bollwark replaces it with an invisible proof-of-work check, no cookies, and the option to self-host. A side-by-side comparison and a migration guide.

Hauke Jung
October 12, 2026
4 min read
privacycomparisons

Is Google reCAPTCHA GDPR compliant? Not out of the box

reCAPTCHA sets a cookie and loads its code from Google before it can protect anything, so many EU sites gate it behind consent. What that breaks on your forms, and what to use instead.

Hauke Jung
October 12, 2026
7 min read
engineeringprivacy

The self-hosted CAPTCHA that runs in one container

Bollwark is an MIT-licensed, self-hostable proof-of-work CAPTCHA written in Rust. One binary, no cookies, no third-party calls, nothing stored on the visitor's device.

Hauke Jung
October 12, 2026
4 min read