Skip to content

HashWX-Proof-of-Work ​

HashWX ist Caps Standard-Challenge-Protokoll. Statt einer festen Hash-Funktion wie SHA-256 erzeugt jede Challenge aus einem Seed eine neue Einwegfunktion, gebaut aus Integer-Operationen und Verzweigungen, die so gewählt sind, dass eine GPU sie nicht wesentlich schneller ausführen kann als eine CPU.

Entworfen hat sie tevador, von dem auch RandomX und HashX stammen. Cap liefert den WebAssembly-Referenz-Build mit.

TIP

HashWX ist der Standard für neue Standalone-Keys. In cap-core ist es über die Format-2-API opt-in, dort bleibt SHA-256-Proof-of-Work der Standard. Siehe HashWX-Challenges.

Warum HashWX ​

Das Problem mit SHA-256-Proof-of-Work ist der Durchsatz. Eine GPU fährt dieselbe feste Funktion über Tausende Lanes im Gleichschritt und löst damit weit mehr Challenges pro Sekunde als eine CPU. Genau darauf kommt es beim Bot-Schutz an: Einem Angreifer ist egal, wie lange eine einzelne Challenge dauert, ihn interessiert nur, wie viele er pro Stunde schafft.

Cap hat dafür früher RSW-Time-Lock-Puzzles ausgeliefert. RSW gewinnt bei der Latenz, weil sich sequenzielles Quadrieren innerhalb eines Puzzles nicht parallelisieren lässt, verliert aber klar beim Durchsatz, weil eine GPU Tausende unabhängige Puzzles gleichzeitig rechnet. Gemessen gegen eine Consumer-GPU:

AlgorithmusCPU, Ryzen 3700X, 16 ThreadsGPU, RTX 5060 TiGPU-Vorteil
SHA-25641 MH/s6150 MH/s~150x
RSW26 H/s4400 H/s~170x
HashWX2,8 MH/s5,8 MH/s~2x

Die Zahlen stammen von tevador und beruhen auf seiner eigenen, nicht öffentlichen CUDA-Implementierung. Die RSW-Zeile haben wir unabhängig nachgemessen: Ein M3 schafft 2,112 H/s pro Thread, was für 16 Ryzen-Threads auf 26 H/s hinausläuft.

RSW ist deprecated. Es lässt sich weiterhin pro Key auswählen und bestehende Keys laufen unverändert weiter, für neue Deployments sollte es aber nicht mehr eingesetzt werden.

Kosten ​

Serverkosten pro Challenge, gemessen auf einem Kern eines Apple M3, Median über 200 Erzeugungen und 40 Verifizierungen durch validateChallenge:

ProtokollErzeugungVerifizierungGesamt
HashWX, 1 Challenge14 µs40 µs54 µs
HashWX, 4 Teil-Challenges (Standard)20 µs129 µs149 µs
SHA-256 (50 Challenges, Schwierigkeit 4)4 µs83 µs87 µs
RSW (t = 75.000)1522 µs14 µs1536 µs

Eine einzelne HashWX-Challenge hat von den dreien den günstigsten Roundtrip. Die standardmäßigen vier Teil-Challenges kosten etwa 150 µs, mehr als SHA-256, aber ein Zehntel von RSW, das bei jedem Erzeugen vier echte modulare Exponentiationen bezahlt. Die Schwierigkeit ändert an diesen Zahlen nichts: Verifizieren ist ein Hash pro Teil-Challenge, egal wie schwer er zu finden war.

Die Kosten auf dem Client sind die andere Seite dieses Tauschs. Eine einzelne Challenge hat eine exponentialverteilte Lösungszeit und ist damit eine Lotterie: Dieselbe Schwierigkeit dauert für einen Besucher 30 ms und für den nächsten drei Sekunden. Deshalb teilt Cap die Schwierigkeit standardmäßig auf vier Teil-Challenges auf, was die Lösungszeit gleichmäßiger macht. Gemessen durch das Widget im Release-Chrome auf einem M3 mit 8 Kernen, je 72 Lösungen, beide Schwierigkeiten auf ungefähr denselben Median abgestimmt:

1 Challenge, d = 1.330.0004 Teil-Challenges, d = 1.000.000
Median536 ms490 ms
p901447 ms778 ms
Langsamste von 722378 ms1490 ms

Die rechte Spalte ist der Standard. Gegen einen Standalone-Key ergab er einen Median von 578 ms und ein p90 von 0,9 s.

Bei gegebener Schwierigkeit ändert das Aufteilen nichts daran, was ein Angreifer bezahlt, denn die erwartete Arbeit bleibt d Hashes, egal wie sie aufgeteilt wird. Was sich ändert, ist die Form der Verteilung. Der Median einer einzelnen Challenge liegt bei 0,69 ihres Mittelwerts, vier Teil-Challenges schieben ihn auf etwa 0,92 und kürzen den Ausläufer. Bei gleicher Schwierigkeit wird die typische Lösung also etwa ein Drittel langsamer, und das p90 sinkt um etwa ein Viertel. Die Tabelle hält stattdessen den Median fest, was die Aufteilung auf 25% weniger Schwierigkeit setzt und einem Angreifer 25% weniger Arbeit pro Lösung abverlangt. Der eigene Overhead des Widgets ist gering. Seine Worker prüfen alle 16 ms, ob sie anhalten sollen, und das begrenzt, wie lange eine Übergabe zwischen Teil-Challenges dauern kann.

Browser-Engines ​

Der wasm-Build läuft mit etwa 60% der nativen Geschwindigkeit. Auf einem einzelnen Worker liegen die drei großen Engines dicht beieinander, sobald alle Kerne ausgelastet sind, fällt Safari zurück. Gemessen auf demselben M3 in einer Sitzung, jeder Browser headless oder im Vordergrund:

Engine1 Worker8 WorkerInterpretierter 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

Auf einem Worker liegen alle drei innerhalb von 5% zueinander. Auf acht erreicht Safari etwa 70% der Rate von Chrome, stimme die Schwierigkeit also auf Safari ab: Bei d = 1.000.000 sind das im Mittel etwa 0,5 s auf Chrome und 0,7 s auf Safari.

Wenn du selbst misst, nimm jeweils einen Release-Build des Browsers und lass den Tab im Vordergrund. Das von Playwright mitgelieferte Firefox führt jede WebAssembly-Last drei- bis sechsmal langsamer aus als ein Release-Firefox, nicht nur HashWX, und ein Tab im Hintergrund kann auf den Effizienzkernen landen und jede Zahl halbieren.

Smartphones ​

Der Standard auf echten Smartphones über BrowserStack, je 15 Lösungen und 30 auf dem Vivo. Jeder Lauf begann auf einer frisch geladenen Seite, so wie ein Besucher ankommt. Nachdem die Seite schon eine Minute lang HashWX ausgeführt hatte, brauchte das Pixel 6 pro Lösung 24% weniger Zeit und das Vivo 29% weniger. Ein Benchmark mit Aufwärmphase sieht also besser aus als diese Zahlen.

GerätSystemAlle KerneMedianLangsamste
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 17nicht gemessen1,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

Jede Lösung enthält zwei Roundtrips zum Testserver, im Median 80 bis 190 ms je Gerät. Das Vivo, ein günstiges Android, braucht etwa zehnmal so lange wie der M3-Desktop. Kommt der Großteil deines Traffics von Mobilgeräten, senke die Schwierigkeit. Die Lösungszeit wächst linear mit ihr, 500.000 halbiert also ungefähr den Rechenanteil jeder Zahl hier.

Clients ohne WebAssembly können HashWX gar nicht lösen und bekommen einen Fehler. Musst du sie unterstützen, nimm SHA-256-Proof-of-Work, der hat einen reinen JS-Fallback. Auf iPhones braucht das Widget iOS 15 oder neuer.

Wogegen HashWX nicht schützt ​

Gegen dasselbe wie RSW: eigenes Silizium. Ein FPGA oder ASIC, der dafür gebaut ist, schlägt eine CPU weiterhin. Für CAPTCHA-Farming geht die Rechnung nicht auf, da ein ASIC Millionen an Entwicklungskosten verschlingt, eine kryptografische Garantie ist das aber nicht.

Eine Solving-Farm mit echten Menschen hält es ebenfalls nicht auf, und das wird es nie. Proof-of-Work erhöht die Kosten pro Anfrage. Kombiniere es mit Instrumentation-Challenges, damit zusätzlich eine echte Browser-Umgebung verlangt wird.

Ausprobieren ​

Die cap-core-API ist unter HashWX-Challenges dokumentiert. Das Widget erkennt Format-2-Antworten automatisch, ein Upgrade des Servers genügt also.

In Cap Standalone ist HashWX der Standard für neue Keys und lässt sich pro Key im Dashboard umstellen.