← Blog · 2026-07-30

How to Price a Spam Flood in Compute and Sats

Per-request proof-of-work will not stop a GPU farm. What stops a flood is the volume tax underneath it, and a bypass priced in sats. Here is the mechanism, with numbers.
Zeke

Say your comment section is drowning. A rented box somewhere is pushing a hundred thousand submissions a day at your form, and the usual options both stink. Puzzle captchas punish every real person who shows up. IP blocklists play whack-a-mole against a rotating pool. OAuth walls off half your legitimate traffic behind a Google login they do not want to use. You want a knob that makes the flood expensive without making your visitors miserable.

pow-captcha is one such knob, and it helps to be precise about what it actually charges for. It does not detect humans. It does not detect bots. It prices the act of making a request, and it gives anyone who would rather not pay in compute the option to pay in sats. Those two mechanics are the whole product. Here is how they work and why the obvious third claim, that the puzzle stops bots because it slows them down, is the one to ignore.

The puzzle, and why it alone does nothing

The gate is plain SHA-256. Your server hands the visitor a salt, they grind a nonce until the hash has enough leading zero bits, they send it back. "Enough" is the difficulty in bits, and each added bit doubles the expected work because you are hunting a rarer hash.

I benchmarked the shipped worker with powbench.js, which uses the exact target check production uses, so the difficulty means the same thing. Pure Node crypto, no dependencies, rerun it yourself. On one slow core it warms to about 60,400 hashes per second. At that floor:

bitsexpected hashesmedian solve
1665,5360.33 s
18262,1441.3 s
201,048,5764.1 s
224,194,3046.6 s
2416,777,21633 s

Now look at who holds the hardware, because that is where the "it slows bots down" idea dies. SHA-256 hashrate spreads across four or five orders of magnitude by platform, and the attacker picks the platform. Rough single-stream numbers:

platformrough H/s20-bit solve
mid mobile browser0.5 to 2 M0.5 to 2 s
slow Node floor, 1 core0.06 M17 s
desktop native SIMD5 to 20 M50 to 200 ms
one commodity GPU1 to 5 Gsub-millisecond

The phone user waits one to three seconds. The GPU farm clears the same challenge in under a millisecond and never feels it. Per single request, proof-of-work is regressive: the better-resourced actor always pays less, and the slowest legitimate device pays most. Crank the difficulty to hurt the farm and you make the phone unusable long before the farm notices. So if you are picking a difficulty hoping to filter bots out per request, you are tuning the wrong dial. The per-request cost is not the moat.

Mechanic one: the volume tax

The moat is what happens when you multiply. The salt is issued per challenge by your server, so there is no amortization. A flood of N requests is N separate solves, GPU or not. A GPU-cheap cost per solve is still a cost, and a hundred thousand of them a day is a bill the attacker did not have yesterday. You set the size of that bill with one integer.

This is the property token-based gates do not have. Once OAuth or an Ed25519 passport mints a credential, every admitted request after that costs the attacker zero marginal compute. They pay once at the door and flood for free. Proof-of-work keeps charging on every request forever, because there is nothing to mint and reuse. It does not care who is calling. It meters interaction volume, and volume is exactly what a flood is made of. Against a scraper that wants millions of cheap page loads, a per-request compute floor times millions is real, tunable friction that scales with the attack instead of being defeated once.

Mechanic two: the sat-priced skip

The regressive part still needs an answer, because you do have legitimate high-volume callers, and they are often the ones on the wrong hardware. An integration, an agent, a batch job. Making them grind is the worst outcome.

So pow-captcha ships an L402 Lightning tier. A caller pays a few sats and skips the puzzle entirely, in about 200 milliseconds, with no account and no KYC. That converts the compute tax into a flat micropayment for exactly the actors who would otherwise be punished worst by it. A human on a fast laptop grinds a 16-bit challenge without noticing. A high-volume integrator pays cash instead of CPU. Both get through. Neither has to sign up for anything.

Put the two together and the one-liner is straightforward: set a per-request price in compute, and let anyone who values their time pay it in sats instead. No claim about humanity, no claim that bots pay more per solve. A volume meter with a cash bypass, which is a genuinely useful thing to put in front of a form or an API route.

Setting it in practice

Keep the difficulty low. 16 to 18 bits is the default worth running: sub-second on a phone, and still 65,000 to 260,000 hashes of friction on every request in a flood. Reserve 20 bits and up for confirmed-abuse escalation, and keep it behind the Lightning skip so real high-volume callers are never stuck grinding. The point of the difficulty knob is to size the volume tax, not to out-compute a GPU. You will lose that race every time, and you do not need to win it.

One honesty note on the numbers above: the platform column says "rough" on purpose. The next step is rerunning the benchmark on a real mobile WebCrypto path and a real GPU to replace those estimates with measured cells. Treat the four-orders-of-magnitude spread as certain and the exact figures as approximate until then.

§ § §

Try the meter

pow-captcha is MIT licensed. It ships a proof-of-work gate with a linear volume tax and an L402 Lightning-skip tier. Set the difficulty low, price the bypass in sats, and let the flood pay the bill.

npm install @powforge/captcha Live demo

Refs