DEV Community

Noah Kellner
Noah Kellner

Posted on • Originally published at nforms.eu

Why Proof-of-Work Beats CAPTCHA for Form Protection

Every developer knows the drill. You add a form to your site, bots find it within hours, and suddenly you're dealing with spam submissions. The traditional answer? CAPTCHA.

But CAPTCHAs come with serious trade-offs:

  • Conversion killer: Studies show CAPTCHAs reduce form completions by 12–40%
  • Accessibility nightmare: Visual puzzles are fundamentally inaccessible to screen reader users
  • Privacy concerns: reCAPTCHA sets cookies and sends data to Google's US servers
  • User frustration: Nobody enjoys clicking traffic lights

How Proof-of-Work Changes the Game

Proof-of-Work (PoW) flips the model. Instead of asking humans to prove they're human, it asks browsers to solve a small math problem — a SHA-256 hash challenge.

For humans: Completely invisible. The challenge solves in ~200ms in a background WebWorker. Users never see or interact with anything.

For bots: Computationally expensive at scale. A bot trying to submit 10,000 forms needs 10,000 unique PoW solutions. The economics don't work.

The Technical Implementation

Here's how PoW works for form protection (using the ALTCHA wire format):

  1. Browser requests a challenge from the server
  2. Challenge includes a SHA-256 hash target and a maximum number to search
  3. WebWorker iterates through numbers, hashing each with the challenge salt
  4. When a matching hash is found, the solution is attached to the form submission
  5. Server verifies the solution with a single-use nonce (no replay attacks)

The entire process happens in the background. No UI, no interaction, no cookies.

Composite Scoring: Beyond Just PoW

PoW alone isn't enough. A serious implementation should combine multiple signals into a composite score:

  • PoW verification: Did the browser solve a valid challenge?
  • Timing analysis: Was the form submitted suspiciously fast?
  • Honeypot detection: Did a bot fill a hidden field?
  • AI spam scoring: Does the content look like spam?

Each signal contributes to a final pass/soft-block/reject decision. No single signal is a deal-breaker — and no single bypass breaks the whole system.

What About No-JavaScript Users?

Progressive enhancement matters. If a user has JavaScript disabled:

  • The form still submits normally
  • The server flags it as "no-shield" (suspicious but not blocked)
  • Server-side heuristics (timing, honeypot, AI) still apply
  • Increased rate limiting provides additional protection

No user is locked out. The protection degrades gracefully.

The GDPR Angle

Unlike reCAPTCHA (which sets NID and _GRECAPTCHA cookies and sends data to the US), a PoW-based approach:

  • Sets zero cookies
  • Performs no browser fingerprinting
  • Stores no personal data
  • Can run entirely on EU infrastructure

No cookie consent banner needed. No GDPR risk assessment required. The architecture itself is the compliance strategy.

Try It

I built this approach into nForms — a form backend that combines PoW bot protection with WCAG 2.2 AA form validation in a single script tag:

<script src="https://api.nforms.eu/shield.js"></script>

<form data-nforms-key="YOUR_KEY">
  <input name="email" data-validate="required|email" />
  <button type="submit">Send</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Free plan includes Shield Basic (honeypot + timing). The Developer plan (€9/mo for early users) adds full PoW protection.

The contact form on nforms.eu is a live demo — Shield is active on it.


The bottom line: CAPTCHAs were a necessary evil. Proof-of-Work makes them unnecessary. Your forms get protected, your users get a better experience, and your compliance team gets peace of mind.

The future of form protection is invisible.

Top comments (1)

Collapse
 
zekebuilds profile image
Zeke

Solid writeup. The composite scoring approach is the right call -- PoW alone stops dumb bots, but timing + honeypot + AI scoring is where you actually catch the sophisticated ones.

I've been building something similar and landed on a slightly different tradeoff. Instead of SHA-256, I went with Argon2d for the hash function. It's memory-hard, which means GPUs and ASICs can't parallelize the solving the way they can with SHA-256. The cost per challenge stays roughly equal between a phone and a server farm, which matters when you're thinking about bot operators throwing hardware at it.

The other thing I added was adaptive difficulty -- the server adjusts the challenge based on traffic patterns. During a credential stuffing spike, difficulty goes up automatically. Normal traffic barely notices.

Wrote up the full implementation here if you want to compare approaches: dev.to/zekebuilds/how-i-stopped-fo...

The npm package is @powforge/captcha -- zero deps, 14kb, runs everything in a WebWorker like you described. Would be curious what you think about the Argon2d tradeoff vs pure SHA-256.