Skip to content

HashWX ​

HashWX is Cap's default proof of work. Where SHA-256 uses one fixed hash function, HashWX generates a new one for every block of nonces, built from integer operations and branches that GPUs run poorly. It was designed by tevador, who also wrote RandomX, and his design document explains how it works. Cap uses his reference WebAssembly build.

It's the default for new Standalone keys. In the core library it's opt-in.

Why not SHA-256 ​

What matters for bot protection is how many challenges an attacker clears per hour, not how long one takes. A GPU runs a fixed function like SHA-256 on thousands of lanes at once. Cap used RSW time-lock puzzles before HashWX, which can't be parallelised within one puzzle, but a GPU still runs thousands of separate puzzles side by side. Measured against a consumer GPU:

AlgorithmCPU, Ryzen 3700X, 16 threadsGPU, RTX 5060 TiGPU advantage
SHA-25641 MH/s6150 MH/s~150x
RSW26 H/s4400 H/s~170x
HashWX2.8 MH/s5.8 MH/s~2x

The figures are tevador's, from his own unpublished CUDA implementation. Cap reproduced the RSW row: an M3 does 2.11 H/s per thread, which works out to 26 H/s on 16 Ryzen threads.

RSW is deprecated. Existing keys keep working, but don't pick it for new ones.

Cost ​

Server time per challenge on one Apple M3 core, median of 200 mints and 40 verifications:

ProtocolMintVerifyTotal
HashWX, 1 challenge14 µs40 µs54 µs
HashWX, 4 sub-challenges (default)20 µs129 µs149 µs
SHA-256, 50 challenges at difficulty 44 µs83 µs87 µs
RSW, t = 75,0001522 µs14 µs1536 µs

Difficulty doesn't change these numbers, since verifying is one hash per sub-challenge however long it took to find.

On the client, one challenge has a random solve time: the same difficulty can take 30 ms for one visitor and 3 s for the next. Cap splits the difficulty into four sub-challenges by default, which evens it out. Measured through the widget in Chrome on an 8-core M3, 72 solves each, with the two difficulties set for about the same median:

1 challenge, d = 1,330,0004 sub-challenges, d = 1,000,000
Median536 ms490 ms
p901447 ms778 ms
Slowest of 722378 ms1490 ms

The right column is the default. Against a Standalone key it measured a 578 ms median and a 0.9 s p90. Splitting doesn't change what an attacker pays, since the expected work is d hashes either way.

Browsers ​

The WebAssembly build runs at about 60% of native speed. Measured on the same M3:

Browser1 worker8 workersInterpreted fallback
Chrome 153440 KH/s2050 KH/s94 KH/s
Firefox 156420 KH/s1850 KH/s105 KH/s
Safari 27.2410 KH/s1480 KH/s105 KH/s

Safari reaches about 70% of Chrome's speed on all cores, so tune difficulty against Safari. At the default, that's about 0.5 s on Chrome and 0.7 s on Safari.

If you measure this yourself, use release builds with the tab in front. Playwright's Firefox runs all WebAssembly three to six times slower than real Firefox, and a background tab can end up on efficiency cores.

Phones ​

The default difficulty on real phones through BrowserStack, 15 solves each and 30 on the Vivo, each starting from a freshly loaded page:

DeviceOSAll coresMedianSlowest
Galaxy S24Android 141238 KH/s1.1 s1.8 s
Pixel 9Android 15837 KH/s1.4 s2.0 s
Pixel 6Android 12746 KH/s1.9 s2.4 s
iPhone 15iOS 17not measured1.9 s4.3 s
iPhone 12iOS 17620 KH/s2.0 s3.3 s
iPhone 13iOS 15678 KH/s2.2 s4.1 s
iPhone SE 2022iOS 15588 KH/s2.4 s4.0 s
Redmi Note 11Android 11456 KH/s2.4 s4.8 s
Galaxy M32Android 11444 KH/s2.8 s6.1 s
Vivo Y21Android 11316 KH/s5.9 s11.0 s

Each solve includes two round trips to the test server, 80 to 190 ms each. A page that had already been running HashWX for a minute solved 24% to 29% faster, so benchmarks that warm up first look better than what visitors get.

If most of your traffic is mobile, lower the difficulty. Solve time scales with it, so 500,000 roughly halves the solving part of these numbers.

HashWX needs WebAssembly, and iOS 15 or newer on iPhones. For browsers without WebAssembly, use SHA-256, which has a JavaScript fallback.

Limits ​

Custom hardware would still beat a CPU. An FPGA or ASIC built for HashWX costs millions to design, which makes no sense for solving CAPTCHAs, but that's an economic argument and not a cryptographic one.

It doesn't stop people paid to solve challenges by hand either. Pair it with instrumentation so a real browser is required too.