Skip to content

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 crate.altcha;

Represents a server-generated challenge sent to the client.

FieldTypeDescription
algorithmstringAlways "SHA-256"
challengestringSHA-256 hash the client must match
maxnumberintUpper bound of the search space
saltstringRandom salt with embedded metadata
signaturestringHMAC-SHA256 signature for verification

Represents a client’s solution to a challenge.

FieldTypeDescription
algorithmstringAlways "SHA-256"
challengestringThe challenge hash that was solved
numberintThe solution number found by the client
saltstringThe salt from the original challenge
signaturestringThe signature from the original challenge

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
);
ParameterDefaultDescription
hmacKeyrequiredSecret key used for HMAC signing
maxNumber1_000_000Upper bound for the random number (controls difficulty)
saltnullCustom salt; auto-generated if null
number-1Fixed solution number; randomly chosen if negative
expires0Expiry 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 expiry
auto challenge = createChallenge("my-secret-key", 500_000, null, -1, 60);

Verifies an encoded payload submitted by the client.

bool verifySolution(
string encodedPayload,
string hmacKey,
bool checkExpires = true,
long minSolveMs = 0
);
ParameterDefaultDescription
encodedPayloadrequiredBase64-encoded JSON payload from the client
hmacKeyrequiredSame secret key used to create the challenge
checkExpirestrueWhether to reject expired challenges
minSolveMs0Minimum 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 checkExpires is 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 time
bool valid = verifySolution(clientPayload, "my-secret-key", true, 2000);

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 JSON

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.

Generates a 16-character key derived from a salt via SHA-256. Useful for deduplication or rate limiting by challenge.

string nonceKey(string salt);

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.