challenge
challenge is a lightweight Rust CLI around the altcha crate for ALTCHA Proof-of-Work v2.
It is designed for backend and deployment workflows where you want a small executable to:
- create a signed challenge for
GET /challenge
- solve a challenge for local testing or non-browser clients
- verify
challenge + solution in POST /register
- verify ALTCHA Sentinel server-signature payloads
Installation
From crates.io:
cargo install challenge
Enable optional KDF algorithms supported by altcha:
cargo install challenge --features argon2
cargo install challenge --features scrypt
cargo install challenge --features argon2,scrypt
Secrets
For production, set secrets through environment variables or a secret manager:
export ALTCHA_HMAC_SECRET='replace-with-long-random-secret'
export ALTCHA_HMAC_KEY_SECRET='replace-with-another-long-random-secret'
ALTCHA_HMAC_SECRET signs the challenge so clients cannot reduce the difficulty, change the salt, or alter expiry metadata.
ALTCHA_HMAC_KEY_SECRET is used with deterministic challenges created with --counter or --random-counter.
Challenge signatures currently use ALTCHA's default HMAC-SHA-256 signing algorithm.
API flow
1. GET /challenge
Your backend creates a challenge and sends the JSON response to the browser/client:
challenge create \
--cost 5000 \
--expires-in 600 \
--hmac-secret "$ALTCHA_HMAC_SECRET" \
--data action=register \
> challenge.json
Example output shape:
{
"parameters": {
"algorithm": "PBKDF2/SHA-256",
"cost": 5000,
"expiresAt": 1791916800,
"keyLength": 32,
"keyPrefix": "00",
"nonce": "9f7a4c1e2b8d43a6a7c9941e8f2d0b3c",
"salt": "b42f7b0c18d44f12ab09e6a721bb2a91",
"data": {
"action": "register"
}
},
"signature": "hex-hmac-signature"
}
The example JSON files in examples/ are shape examples. Generate real values with challenge create before verifying.
2. Client solves the challenge
For browser integrations, the ALTCHA widget or client code solves the challenge.
For CLI/local testing:
challenge solve --challenge challenge.json > solution.json
--challenge defaults to stdin, so this is equivalent:
challenge solve < challenge.json > solution.json
Example solution shape:
{
"counter": 8241,
"derivedKey": "00f4a9cc3d93b0e45cf6c0d86d35e3f119c7bb77b07fb0e5fd7c51e4f0c733aa",
"time": 418.73
}
The important property is that derivedKey starts with the challenge keyPrefix.
3. POST /register
Your frontend should submit the original challenge and the produced solution together with the registration form:
{
"username": "alice",
"password": "user-password-here",
"altcha": {
"challenge": {
"parameters": {
"algorithm": "PBKDF2/SHA-256",
"cost": 5000,
"expiresAt": 1791916800,
"keyLength": 32,
"keyPrefix": "00",
"nonce": "9f7a4c1e2b8d43a6a7c9941e8f2d0b3c",
"salt": "b42f7b0c18d44f12ab09e6a721bb2a91",
"data": {
"action": "register"
}
},
"signature": "hex-hmac-signature"
},
"solution": {
"counter": 8241,
"derivedKey": "00f4a9cc3d93b0e45cf6c0d86d35e3f119c7bb77b07fb0e5fd7c51e4f0c733aa",
"time": 418.73
}
}
}
Verify that request body:
challenge verify-payload \
--payload register-request.json \
--secret "$ALTCHA_HMAC_SECRET"
Or verify separate files:
challenge verify \
--challenge challenge.json \
--solution solution.json \
--secret "$ALTCHA_HMAC_SECRET"
Output:
{
"verified": true,
"expired": false,
"invalidSignature": false,
"invalidSolution": false,
"time": 1.23
}
Use --fail-on-invalid when you want a non-zero exit code for verified=false:
challenge verify-payload \
--payload register-request.json \
--secret "$ALTCHA_HMAC_SECRET" \
--fail-on-invalid
Deterministic mode
For deterministic challenges, include a counter and a key-signing secret:
challenge create \
--cost 5000 \
--random-counter \
--counter-min 5000 \
--counter-max 10000 \
--expires-in 600 \
--hmac-secret "$ALTCHA_HMAC_SECRET" \
--hmac-key-secret "$ALTCHA_HMAC_KEY_SECRET" \
> challenge.json
Then verify with the same key secret:
challenge verify \
--challenge challenge.json \
--solution solution.json \
--secret "$ALTCHA_HMAC_SECRET" \
--key-secret "$ALTCHA_HMAC_KEY_SECRET"
ALTCHA Sentinel payload verification
For raw JSON:
challenge verify-server \
--payload sentinel-payload.json \
--secret "$ALTCHA_HMAC_SECRET" \
--fail-on-invalid
For the base64-encoded JSON form field value:
challenge verify-server \
--payload sentinel-field.txt \
--base64 \
--secret "$ALTCHA_HMAC_SECRET" \
--fail-on-invalid
Omit --fail-on-invalid if you prefer a zero exit code with a JSON result containing "verified": false.
License
MIT