challenge 0.1.0

A lightweight CLI for ALTCHA Proof-of-Work v2 challenges.
Documentation

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