Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
melonask avatar

Altcha

  • 10 installs
  • Updated May 4, 2026
  • melonask/altcha-skills

Helps with ai & agent building tasks.

About

altcha is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.

  • altcha
  • AI & Agent Building
  • AI-coding skill

Altcha by the numbers

  • 10 all-time installs (skills.sh)
  • Ranked #11,937 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/melonask/altcha-skills --skill altcha

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs10
Last updatedMay 4, 2026
Repositorymelonask/altcha-skills

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

Altcha — Rust Proof-of-Work Bot Protection Library

Altcha is the official Rust implementation of the ALTCHA Proof-of-Work (v2) protocol. It provides cryptographic challenge-response verification to protect web applications, forms, and APIs from bots and spam — without relying on CAPTCHAs that degrade user experience. The library is wire-compatible with the JavaScript ALTCHA widget, so a Rust backend can serve challenges that any ALTCHA JS frontend widget can solve.

This skill covers the altcha crate (v0.1.0+, MIT license, crate name: altcha). A separate community crate altcha-lib-rs exists but is less complete — focus on the official altcha crate from the altcha-org GitHub organization.

Crate Architecture

The crate is a single crate with no sub-crates. All public items are re-exported at the crate root:

altcha (crate root)
├── create_challenge()           — Generate a new PoW challenge
├── solve_challenge()            — Solve a challenge (find the counter)
├── verify_solution()            — Verify a client-submitted solution
├── sign_challenge()             — Manually sign challenge parameters
├── verify_server_signature()    — Verify ALTCHA Sentinel payloads
├── verify_fields_hash()         — Verify form field integrity hashes
├── parse_verification_data()    — Parse URL-encoded verification data
├── Error / Result               — Error types
├── HmacAlgorithm                — HMAC algorithm enum (SHA-256/384/512)
├── CreateChallengeOptions       — Challenge creation configuration
├── Challenge / ChallengeParameters — Challenge data structures
├── Solution / Payload           — Solution and combined payload types
├── SolveChallengeOptions        — Solver configuration
├── VerifySolutionOptions        — Verification configuration
├── VerifySolutionResult         — Verification outcome
├── ServerSignaturePayload       — ALTCHA Sentinel payload
├── ServerSignatureVerificationData — Parsed verification data
└── VerifyServerSignatureResult  — Server signature verification outcome

Quick Start: Minimal Dependency Setup

Add the crate to your Cargo.toml. PBKDF2 and iterative SHA algorithms are always available without feature flags. Enable optional features for Argon2id and scrypt:

# Cargo.toml

# Basic: PBKDF2 + SHA only (sufficient for most use cases)
[dependencies]
altcha = "0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
rand = "0.9"

# Full: all algorithms including Argon2id and scrypt, plus base64 for Sentinel
[dependencies]
altcha = { version = "0", features = ["argon2", "scrypt"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
base64 = "0.22"
rand = "0.9"

Feature Flags

FeatureDefaultEnablesAlgorithm
(none)--PBKDF2/SHA-256, PBKDF2/SHA-384, PBKDF2/SHA-512, SHA-256, SHA-384, SHA-512Key derivation via PBKDF2 or iterative hashing
argon2noArgon2id KDFMemory-hard algorithm, requires memory_cost
scryptnoscrypt KDFMemory-hard algorithm, requires memory_cost

Quick Reference: Which Guide Do I Need?

  • Full PoW flow (create → solve → verify) → Read references/core-pow-flow.md
  • Server integration patterns (Axum, Actix, etc.) → Read references/server-integration.md
  • ALTCHA JS widget (HTML, React, Vue, Next.js) → Read references/frontend-integration.md
  • All algorithms, cost tuning, and difficulty → Read references/algorithms-config.md
  • ALTCHA Sentinel server signature verification → Read references/server-signature.md
  • Deterministic mode and key signatures → Read references/deterministic-mode.md
  • Form field hash verification → Read references/fields-hash.md
  • All public types, error handling, serde → Read references/types-errors.md

Core Patterns at a Glance

1. Basic PoW Flow (Server Creates Challenge, Client Solves, Server Verifies)

This is the fundamental ALTCHA pattern. The server generates a challenge, the client (usually a JS widget) solves it by brute-forcing a counter value, and the server verifies the solution.

use altcha::{
    create_challenge, solve_challenge, verify_solution,
    CreateChallengeOptions, SolveChallengeOptions, VerifySolutionOptions,
};

fn main() -> altcha::Result<()> {
    // --- SERVER SIDE: Create a signed challenge ---
    let challenge = create_challenge(CreateChallengeOptions {
        algorithm: "PBKDF2/SHA-256".to_string(),
        cost: 5_000,
        hmac_signature_secret: Some("my-server-secret".to_string()),
        ..Default::default()
    })?;

    // The challenge JSON is sent to the client (usually via an API endpoint)
    let challenge_json = serde_json::to_string(&challenge)?;
    println!("Challenge: {}", challenge_json);

    // --- CLIENT SIDE: Solve the challenge ---
    // The client iterates counters until the derived key starts with the prefix
    let solution = solve_challenge(SolveChallengeOptions::new(&challenge))?
        .expect("solution found within timeout");

    // --- SERVER SIDE: Verify the submitted solution ---
    let result = verify_solution(VerifySolutionOptions::new(
        &challenge,
        &solution,
        "my-server-secret", // must match the secret used to create the challenge
    ))?;

    println!("Verified: {}", result.verified);
    assert!(result.verified);

    Ok(())
}

2. Challenge with Expiration

Set expires_at to a Unix timestamp to make challenges expire after a time window. Expired challenges always fail verification regardless of whether the solution is mathematically correct.

use altcha::*;

fn main() -> altcha::Result<()> {
    let expires_at = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs() + 600; // 10 minutes from now

    let challenge = create_challenge(CreateChallengeOptions {
        algorithm: "PBKDF2/SHA-256".to_string(),
        cost: 5_000,
        expires_at: Some(expires_at),
        hmac_signature_secret: Some("secret".to_string()),
        ..Default::default()
    })?;

    // ... solve and verify as before ...
    let result = verify_solution(VerifySolutionOptions::new(
        &challenge, &solution, "secret",
    ))?;

    if result.expired {
        println!("Challenge expired — generate a new one");
    }

    Ok(())
}

3. Using Different Algorithms

Each algorithm has different cost parameters and difficulty characteristics. The cost field means different things depending on the algorithm — see references/algorithms-config.md for details.

use altcha::*;

// SHA-256 iterative hashing — lighter CPU work, simpler
let sha_challenge = create_challenge(CreateChallengeOptions {
    algorithm: "SHA-256".to_string(),
    cost: 1_000_000, // number of hash iterations
    ..Default::default()
})?;

// PBKDF2/SHA-256 — the default, good balance of security and speed
let pbkdf2_challenge = create_challenge(CreateChallengeOptions {
    algorithm: "PBKDF2/SHA-256".to_string(),
    cost: 50_000, // number of PBKDF2 iterations
    ..Default::default()
})?;

// SCRYPT — memory-hard, much harder to optimize with GPUs/ASICs
// Requires feature = "scrypt" in Cargo.toml
let scrypt_challenge = create_challenge(CreateChallengeOptions {
    algorithm: "SCRYPT".to_string(),
    cost: 16384,        // N parameter (must be power of 2)
    memory_cost: Some(8), // r parameter (block size)
    ..Default::default()
})?;

// ARGON2ID — most modern memory-hard algorithm
// Requires feature = "argon2" in Cargo.toml
let argon2_challenge = create_challenge(CreateChallengeOptions {
    algorithm: "ARGON2ID".to_string(),
    cost: 2,                    // time cost (iterations)
    memory_cost: Some(65536),   // memory in KiB (64 MB)
    parallelism: Some(2),       // parallelism factor
    ..Default::default()
})?;

4. Deterministic Mode with Key Signatures

Deterministic mode lets the server skip re-deriving the key during verification, making verification significantly faster. The server pre-computes the expected key prefix from a counter value and signs it with a separate key signature secret.

use altcha::*;
use rand::Rng;

fn main() -> altcha::Result<()> {
    let counter = rand::rng().random_range(5_000..=10_000);

    // Create challenge with both secrets
    let challenge = create_challenge(CreateChallengeOptions {
        algorithm: "PBKDF2/SHA-256".to_string(),
        cost: 5_000,
        counter: Some(counter),
        expires_at: Some(
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs() + 600,
        ),
        hmac_signature_secret: Some("my-hmac-secret".to_string()),
        hmac_key_signature_secret: Some("my-key-secret".to_string()),
        ..Default::default()
    })?;

    // ... client solves the challenge ...

    // Verify with the key signature secret for fast-path verification
    let result = verify_solution(VerifySolutionOptions {
        hmac_key_signature_secret: Some("my-key-secret".to_string()),
        ..VerifySolutionOptions::new(&challenge, &solution, "my-hmac-secret")
    })?;

    assert!(result.verified);
    Ok(())
}

5. ALTCHA Sentinel Server Signature Verification

ALTCHA Sentinel is a server-side service that analyzes submissions for spam/bot patterns. It returns a signed payload that your Rust backend can verify cryptographically.

use altcha::{verify_server_signature, ServerSignaturePayload, parse_verification_data};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};

fn handle_sentinel_payload(encoded_payload: &str) -> altcha::Result<()> {
    // Decode the base64 payload from the hidden form field
    let decoded = BASE64.decode(encoded_payload)
        .map_err(|e| altcha::Error::InvalidParameters(format!("base64 decode: {}", e)))?;
    let payload: ServerSignaturePayload = serde_json::from_slice(&decoded)?;

    // Verify the server signature
    let result = verify_server_signature(&payload, "my-hmac-secret")?;

    if result.verified {
        if let Some(data) = &result.verification_data {
            println!("Classification: {:?}", data.classification);
            println!("Score: {:?}", data.score);
            println!("Reasons: {:?}", data.reasons);
            println!("Email: {:?}", data.email);

            // Check if the submission should be rejected
            if data.classification.as_deref() == Some("BAD") {
                println!("Bot detected — reject submission");
            }
        }
    }

    Ok(())
}

6. Fields Hash Verification

Protect form fields from client-side tampering. The server generates an HMAC hash of selected field values. On form submission, the server re-computes the hash and compares — if the client modified any field, the hash won't match.

use altcha::{verify_fields_hash, HmacAlgorithm};
use std::collections::HashMap;

fn verify_form_integrity() -> bool {
    let mut form_data = HashMap::new();
    form_data.insert("email".to_string(), "user@example.com".to_string());
    form_data.insert("name".to_string(), "John Doe".to_string());
    form_data.insert("message".to_string(), "Hello world!".to_string());

    let fields = vec!["email".to_string(), "name".to_string()];
    let expected_hash = "a1b2c3d4..."; // HMAC hash provided by the client (originally computed by server)

    verify_fields_hash(&form_data, &fields, expected_hash, Some(&HmacAlgorithm::Sha256))
}

Key Concepts

How ALTCHA Proof-of-Work Works

ALTCHA is a challenge-response protocol designed to make automated submissions expensive without requiring users to solve puzzles:

1. Server generates a challenge: a random salt + nonce, an algorithm, a cost (difficulty), and a key_prefix (e.g., "00" meaning the derived key must start with "00"). 2. Client receives the challenge and brute-forces a counter value. For each counter, it derives a key using the specified algorithm. When the derived key starts with the required prefix, it submits the counter and derived_key back. 3. Server verifies by either re-deriving the key with the submitted counter (standard mode) or checking the HMAC key signature (deterministic mode). This confirms the client actually performed the work.

The difficulty is controlled by the key_prefix and cost parameters. A longer prefix (e.g., "0000") exponentially increases the expected number of iterations.

Supported Algorithm Strings

Algorithm StringKDFFeature FlagCost Meaning
"PBKDF2/SHA-256"PBKDF2-HMAC-SHA-256— (always)Number of iterations
"PBKDF2/SHA-384"PBKDF2-HMAC-SHA-384Number of iterations
"PBKDF2/SHA-512"PBKDF2-HMAC-SHA-512Number of iterations
"SHA-256"Iterative SHA-256Number of hash iterations
"SHA-384"Iterative SHA-384Number of hash iterations
"SHA-512"Iterative SHA-512Number of hash iterations
"SCRYPT"scryptscryptN (CPU/memory cost, power of 2)
"ARGON2ID"Argon2idargon2Time cost (iterations)

Serialization & Wire Compatibility

All challenge, solution, and payload types implement Serialize/Deserialize with camelCase JSON field names matching the JavaScript ALTCHA library exactly. This means you can serve challenges from a Rust backend and verify them with the official ALTCHA JS widget on the frontend — they are fully wire-compatible.

Key field name mappings: key_length"keyLength", key_prefix"keyPrefix", expires_at"expiresAt", derived_key"derivedKey", verification_data"verificationData".

Security Properties

  • Constant-time comparison: All HMAC and hash comparisons use the subtle crate to prevent timing attacks.
  • HMAC signing: Challenge payloads can be HMAC-signed so the server can detect tampering. An unsigned challenge is insecure because a client could modify the parameters.
  • Expiration: Challenges support expires_at timestamps. Verification always checks expiration first — expired challenges are rejected before any cryptographic verification.
  • No async required: All functions are synchronous and CPU-bound. In async web frameworks, wrap solver calls in tokio::task::spawn_blocking to avoid blocking the async runtime.

Default Values for CreateChallengeOptions

When using Default::default() or ..Default::default():

  • algorithm: "PBKDF2/SHA-256"
  • cost: 100_000 (100K PBKDF2 iterations)
  • key_length: 32 bytes
  • key_prefix: "00" (derived key must start with "00")
  • hmac_algorithm: HmacAlgorithm::Sha256
  • All other fields: None or empty

Common Pitfalls

1. Forgetting to sign challenges — Always set hmac_signature_secret when creating challenges. Without signing, a malicious client could modify challenge parameters (e.g., reduce cost to 1) and submit a trivial solution.

2. Mismatched secrets — The hmac_signature_secret used in create_challenge() must be the same string passed to verify_solution(). The hmac_key_signature_secret must also match between creation and verification if using deterministic mode.

3. Blocking the async runtimesolve_challenge() is CPU-bound and can take seconds. In async Rust (Axum, Actix, Tokio), always wrap it: let solution = tokio::task::spawn_blocking(move || solve_challenge(opts)).await.expect("spawn_blocking failed")?;

4. Scrypt cost must be a power of 2 — The cost parameter for scrypt is the N parameter and must be a power of 2 (e.g., 1024, 4096, 16384, 32768). Other values will cause an error.

5. Argon2id requires memory_cost — Unlike PBKDF2/SHA where memory_cost is optional, Argon2id mandates memory_cost (in KiB). Recommended minimum is 65536 (64 MB).

6. Key prefix length — The default key_prefix of "00" means approximately 1 in 256 attempts will succeed on average. Use "000" for ~1/4096 or "0000" for ~1/65536 difficulty. Longer prefixes mean longer solve times for legitimate users.

Reference Files

For detailed information on any topic, read the appropriate reference file:

  • references/core-pow-flow.md — Complete PoW lifecycle: create_challenge, solve_challenge, verify_solution, Payload struct, solver timeout and counter stepping
  • references/server-integration.md — Axum endpoint examples (GET /challenge, POST /verify), payload type detection (PoW vs Sentinel), Actix-web patterns, error responses
  • references/frontend-integration.md - ALTCHA JS widget (HTML, React, Vue, Next.js)
  • references/algorithms-config.md — Deep dive into all 8 algorithms, cost parameter tuning, difficulty calibration, memory_cost and parallelism settings, performance benchmarks
  • references/server-signature.md — ALTCHA Sentinel integration: ServerSignaturePayload, verify_server_signature, parse_verification_data, verification data fields (classification, score, reasons), base64 decoding
  • references/deterministic-mode.md — Deterministic challenges with counter and hmac_key_signature_secret, fast-path verification, sign_challenge function, when to use deterministic vs standard mode
  • references/fields-hash.md — verify_fields_hash usage, protecting form fields from tampering, algorithm options (SHA-256/384/512), HashMap form data patterns
  • references/types-errors.md — Complete reference for all public structs, enums, Error variants, HmacAlgorithm, serde configuration, type aliases, constructor methods (SolveChallengeOptions::new, VerifySolutionOptions::new)

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.