trilbymedia / cap-php
PHP port of the Cap proof-of-work captcha server. Wire-compatible with @cap.js/widget.
0.1.1
2026-04-20 17:13 UTC
Requires
- php: ^8.1
- ext-hash: *
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^10.5
- psr/simple-cache: ^2.0 || ^3.0
Suggests
- psr/simple-cache: To use Psr16Storage with any PSR-16 cache implementation.
README
PHP port of the Cap proof-of-work captcha server.
Wire-compatible with the official @cap.js/widget, so the unmodified JS widget can talk to a PHP-backed endpoint.
- SHA-256 proof-of-work — no tracking, no third-party calls, no API keys
- Small (~500 LOC), no runtime dependencies beyond ext-json / ext-hash
- Pluggable storage: in-memory, filesystem, or any PSR-16 cache
Install
composer require trilbymedia/cap-php
Usage
use TrilbyMedia\Cap\Cap; use TrilbyMedia\Cap\Config; use TrilbyMedia\Cap\Storage\FilesystemStorage; $storage = new FilesystemStorage('/var/lib/cap'); $cap = new Cap(new Config( challengeStorage: $storage, tokenStorage: $storage, )); // In your /challenge endpoint: $result = $cap->createChallenge(); // echo json_encode($result); // In your /redeem endpoint (body: {"token":"...","solutions":[...]}): $result = $cap->redeemChallenge($token, $solutions); // echo json_encode($result); // When validating a form submission that carried a cap token: if ($cap->validateToken($submittedToken)) { // success }
Defaults
| Option | Default | Meaning |
|---|---|---|
| challengeCount | 50 | Number of sub-challenges per captcha |
| challengeSize | 32 | Salt length (hex chars) |
| challengeDifficulty | 4 | Target prefix length (hex chars) |
| expiresMs | 600 000 | Challenge TTL (10 min) |
| token TTL | 20 min | Validation token TTL (not configurable) |
Protocol compatibility
The PRNG and hashing are bit-exact with upstream server/index.js — verified by tests/fixtures/prng-vectors.json, a set of vectors generated directly from the upstream JS implementation.
License
Apache-2.0, matching upstream.