joby / smol-pow
Stateless proof of work challenge generation, solving, and verification using HMAC, SHA256 and simple nonce iteration.
Requires
- php: >=8.3
Requires (Dev)
- php: >=8.3
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.1
README
A simple and stateless proof of work system using HMAC-signed state and a SHA256 matching challenge and designed to allow developers to drop in PoW to existing web applications with minimal friction. The main purpose is to allow an entirely static HTML/JS page to do a simple PoW and then bounce back to the specified URL, without ever needing to store information on the server about the challenge.
Installation
composer require joby-lol/smol-query
Challenge structure
Each challenge can be passed from the server to the verification page as a single string. It is designed to be put in the URL hash, so that client-side JS can access it but it will not appear in most traffic logs.
Each challenge string is a base64-encoded JSON array with the following fields (in order):
- algorithm name (string)
- challenge nonce (random string)
- difficulty (integer)
- expiry timestamp (integer)
- return URL (string)
- HMAC signature of the above fields using the given algorithm and a server-supplied secret key (string)
Once solved, the solution can be sent back to the server to be verified by setting a cookie named smolpow containing the solution and the original challenge string, separated by a pipe character, and redirecting the client back to the return URL.
PoW algorithm
The proof of work algorithm itself is very simple: generate a string which, when appended to the given challenge nonce and hashed using SHA-256, results in a hash which starts with a certain number of zero bits (specified by the difficulty value). This can be done in a few different ways, but the basic idea is to try different strings until a valid one is found. Solutions are required to be at least 8 characters long but less than 32 characters long.
Implementation
To use smolPoW in your application, you need to:
- On any page that should be inaccessible for a bot, generate a challenge according to the above format.
- Redirect to a page on the same domain as the return URL using the challenge as a URL fragment.
- Include smolpow.js in that page and call smolPoW.run() on page load.
- On success, smolPoW will set a cookie containing the solution and the original challenge string, separated by a pipe character, and redirect the client back to the return URL.
- On failure, smolPoW will display an error message and you should likely provide a way to retry. Do not redirect on failure.
- On the target page, you must verify the solution contained in the cookie and that the challenge is valid before completing the requested action.
Requirements
Fully tested on PHP 8.3+, static analysis for PHP 8.1+. No external dependencies.
License
MIT License - See LICENSE file for details.