ALTCHA Module
The crate.altcha module implements the ALTCHA proof-of-work challenge protocol. It lets you protect endpoints from spam and abuse by requiring clients to solve a computational challenge before submitting requests — no third-party service needed.
Import
Section titled “Import”import crate.altcha;Data Structures
Section titled “Data Structures”Challenge
Section titled “Challenge”Represents a server-generated challenge sent to the client.
| Field | Type | Description |
|---|---|---|
algorithm | string | Always "SHA-256" |
challenge | string | SHA-256 hash the client must match |
maxnumber | int | Upper bound of the search space |
salt | string | Random salt with embedded metadata |
signature | string | HMAC-SHA256 signature for verification |
Payload
Section titled “Payload”Represents a client’s solution to a challenge.
| Field | Type | Description |
|---|---|---|
algorithm | string | Always "SHA-256" |
challenge | string | The challenge hash that was solved |
number | int | The solution number found by the client |
salt | string | The salt from the original challenge |
signature | string | The signature from the original challenge |
Challenge Creation
Section titled “Challenge Creation”createChallenge
Section titled “createChallenge”Generates a new proof-of-work challenge.
Challenge createChallenge( string hmacKey, int maxNumber = 1_000_000, string salt = null, int number = -1, long expires = 0);| Parameter | Default | Description |
|---|---|---|
hmacKey | required | Secret key used for HMAC signing |
maxNumber | 1_000_000 | Upper bound for the random number (controls difficulty) |
salt | null | Custom salt; auto-generated if null |
number | -1 | Fixed solution number; randomly chosen if negative |
expires | 0 | Expiry timestamp in seconds since epoch; 0 means no expiry |
The salt automatically embeds created and optional expires timestamps as query parameters, used later for verification.
auto challenge = createChallenge("my-secret-key");
// With custom difficulty and 60-second expiryauto challenge = createChallenge("my-secret-key", 500_000, null, -1, 60);Solution Verification
Section titled “Solution Verification”verifySolution
Section titled “verifySolution”Verifies an encoded payload submitted by the client.
bool verifySolution( string encodedPayload, string hmacKey, bool checkExpires = true, long minSolveMs = 0);| Parameter | Default | Description |
|---|---|---|
encodedPayload | required | Base64-encoded JSON payload from the client |
hmacKey | required | Same secret key used to create the challenge |
checkExpires | true | Whether to reject expired challenges |
minSolveMs | 0 | Minimum solve time in milliseconds (anti-bot threshold) |
Returns true if the solution is valid. Returns false if:
- The payload cannot be decoded
- The algorithm is not SHA-256
- The challenge has expired (when
checkExpiresis true) - The solve time is below
minSolveMs - The challenge hash or signature does not match
bool valid = verifySolution(clientPayload, "my-secret-key");
// With minimum 2-second solve timebool valid = verifySolution(clientPayload, "my-secret-key", true, 2000);Payload Encoding
Section titled “Payload Encoding”encodePayload
Section titled “encodePayload”Encodes a challenge and solution number into a base64 JSON string suitable for transmission.
string encodePayload(Challenge c, int number);auto challenge = createChallenge("my-secret-key", 100, null, 42);auto encoded = encodePayload(challenge, 42);// encoded is a base64 string containing the full Payload as JSONextractPayloadSalt
Section titled “extractPayloadSalt”Extracts the salt from an encoded payload without full verification. Useful for logging or nonce tracking.
string extractPayloadSalt(string encodedPayload);Returns an empty string if decoding fails.
nonceKey
Section titled “nonceKey”Generates a 16-character key derived from a salt via SHA-256. Useful for deduplication or rate limiting by challenge.
string nonceKey(string salt);Integration Example
Section titled “Integration Example”A typical server-side flow:
import crate.altcha;import vibe.http.router;
enum hmacKey = "your-secret-key";
void setupRoutes(URLRouter router) { // Endpoint that issues a challenge router.get("/altcha/challenge", (scope req, scope res) { auto challenge = createChallenge(hmacKey, 100_000, null, -1, 120); res.writeJsonBody(challenge); });
// Endpoint that verifies the solution before processing router.post("/submit", (scope req, scope res) { auto altchaPayload = req.json["altcha"].get!string;
if (!verifySolution(altchaPayload, hmacKey)) { res.statusCode = 403; res.writeBody("Invalid ALTCHA solution"); return; }
// Process the legitimate request });}The client solves the challenge by finding a number in [1, maxnumber] where SHA-256(salt + number) == challenge, then submits the result as a base64-encoded payload.