ALTCHA Python Library
The ALTCHA Python Library is a lightweight, zero-dependency library designed for creating and verifying ALTCHA challenges, specifically tailored for Python applications.
Compatibility
- Python 3.9+
Example
Installation
pip install altcha
For Argon2id support (optional):
pip install altcha argon2-cffi
Tests
python -m unittest discover tests
PoW v2
PoW v2 replaces the simple hash-matching approach of v1 with a key derivation function (KDF) proof of work. Instead of finding a number whose hash equals a target, the client must find a counter value whose derived key starts with a required prefix. This enables memory-hard algorithms (Argon2id, scrypt) that are more resistant to GPU/ASIC attacks.
Algorithms
| Algorithm string | KDF | Notes |
|---|---|---|
'SHA-256', 'SHA-384', 'SHA-512' |
Iterated SHA | Fast, for testing / low-security use |
'PBKDF2/SHA-256', 'PBKDF2/SHA-384', 'PBKDF2/SHA-512' |
PBKDF2 | Good default |
'SCRYPT' |
scrypt | Memory-hard |
'ARGON2ID' |
Argon2id | Memory-hard, requires argon2-cffi |
Quick start
from altcha import (
create_challenge,
solve_challenge,
verify_solution,
Payload,
)
HMAC_SECRET = "secret hmac key"
# Server: create a challenge
challenge = create_challenge(
algorithm="PBKDF2/SHA-256",
cost=5_000,
hmac_secret=HMAC_SECRET,
)
# Client: solve the challenge
solution = solve_challenge(challenge)
if solution is None:
raise RuntimeError("Challenge could not be solved in time")
# Client: encode and transmit the payload
payload_b64 = Payload(challenge, solution).to_base64()
# Server: verify
result = verify_solution(payload_b64, HMAC_SECRET)
print(result.verified) # True
Deterministic mode
Pass a counter to create_challenge to pre-solve the challenge. The derived key prefix is embedded in the challenge so the client must find exactly that counter. Combine with hmac_key_secret to enable fast server-side verification without re-deriving the key.
import secrets
counter = secrets.randbelow(5_000) + 5_000
challenge = create_challenge(
algorithm="PBKDF2/SHA-256",
cost=5_000,
counter=counter,
hmac_secret=HMAC_SECRET,
hmac_key_secret="key-signing-secret",
)
solution = solve_challenge(challenge)
if solution is None:
raise RuntimeError("Challenge could not be solved in time")
payload_b64 = Payload(challenge, solution).to_base64()
result = verify_solution(
payload_b64,
HMAC_SECRET,
hmac_key_secret="key-signing-secret", # enables fast path
)
print(result.verified) # True
Expiry
import datetime
challenge = create_challenge(
algorithm="PBKDF2/SHA-256",
cost=5_000,
expires_at=datetime.datetime.now(datetime.timezone.utc)
+ datetime.timedelta(minutes=10),
hmac_secret=HMAC_SECRET,
)
Custom derive_key
Pass your own derive_key function to use a custom or third-party KDF:
def my_derive_key(parameters, salt: bytes, password: bytes) -> bytes: ...
challenge = create_challenge(
algorithm="MY-ALGO",
cost=1,
derive_key=my_derive_key,
hmac_secret=HMAC_SECRET,
)
PoW v1 (legacy)
The original ALTCHA proof of work. The client brute-forces a number n such that hash(salt + n) == challenge. Available under the _v1 / V1 suffix.
API reference
V2
create_challenge(algorithm, cost, *, derive_key, counter, key_length, key_prefix, key_prefix_length, memory_cost, parallelism, expires_at, data, hmac_secret, hmac_key_secret, hmac_algorithm) → Challenge
Create a new v2 proof-of-work challenge.
| Parameter | Type | Default | Description |
|---|---|---|---|
algorithm |
str |
— | KDF algorithm identifier (e.g. 'PBKDF2/SHA-256', 'ARGON2ID', 'SCRYPT', 'SHA-256'). |
cost |
int |
— | Algorithm-specific cost (iterations / passes). |
derive_key |
callable | auto | (parameters, salt: bytes, password: bytes) -> bytes. Defaults to built-in for the algorithm. |
counter |
int |
None |
Pre-solve with this counter (deterministic mode). |
key_length |
int |
32 |
Derived key length in bytes. |
key_prefix |
str |
'00' |
Hex prefix the derived key must start with. |
key_prefix_length |
int |
key_length // 2 |
Bytes of the derived key used as prefix in deterministic mode. |
memory_cost |
int |
None |
Memory cost in KiB (Argon2id / scrypt). |
parallelism |
int |
None |
Parallelism factor (Argon2id / scrypt). |
expires_at |
int | datetime |
None |
Expiry as a Unix timestamp or datetime. |
data |
dict |
None |
Arbitrary metadata embedded in the challenge. |
hmac_secret |
str |
None |
Secret for signing the challenge. If omitted, challenge is unsigned. |
hmac_key_secret |
str |
None |
Secret for signing the derived key (fast verification path). |
hmac_algorithm |
str |
'SHA-256' |
HMAC digest algorithm. |
Returns Challenge.
solve_challenge(challenge, derive_key, *, counter_start, counter_step, timeout) → Solution | None
Solve a v2 challenge by brute-forcing counter values.
| Parameter | Type | Default | Description |
|---|---|---|---|
challenge |
Challenge |
— | The challenge to solve. |
derive_key |
callable | auto | KDF function. Defaults to built-in for the algorithm. |
counter_start |
int |
0 |
First counter value to try. |
counter_step |
int |
1 |
Increment between attempts (use > 1 for partitioned parallel solving). |
timeout |
float |
90.0 |
Maximum seconds to spend. Returns None on timeout. |
Returns Solution or None.
verify_solution(payload, hmac_secret, derive_key, *, hmac_key_secret, hmac_algorithm) → VerifySolutionResult
Verify a v2 challenge solution.
| Parameter | Type | Default | Description |
|---|---|---|---|
payload |
str | Payload |
— | Base64-encoded JSON string or Payload object. |
hmac_secret |
str |
— | Secret used to verify the challenge signature. |
derive_key |
callable | auto | KDF function for re-derivation. |
hmac_key_secret |
str |
None |
Secret for the fast verification path. |
hmac_algorithm |
str |
'SHA-256' |
HMAC digest algorithm. |
Returns VerifySolutionResult with fields:
| Field | Type | Description |
|---|---|---|
verified |
bool |
True if the solution is valid. |
expired |
bool |
True if the challenge has expired. |
invalid_signature |
bool | None |
True if the challenge signature is missing or wrong. |
invalid_solution |
bool | None |
True if the solution is incorrect. |
time |
float |
Time taken for verification in milliseconds. |
error |
str | None |
Set if the payload could not be parsed. |
Built-in derive_key functions
| Function | Algorithm |
|---|---|
derive_key_sha(parameters, salt, password) |
Iterated SHA (SHA-256/384/512) |
derive_key_pbkdf2(parameters, salt, password) |
PBKDF2 (SHA-256/384/512) |
derive_key_scrypt(parameters, salt, password) |
scrypt |
derive_key_argon2id(parameters, salt, password) |
Argon2id (requires argon2-cffi) |
Server Signature Verification
verify_fields_hash(form_data, fields, fields_hash, algorithm) → bool
Verifies the hash of specific form fields.
verify_server_signature(payload, hmac_key) → (bool, ServerSignatureVerificationData | None, str | None)
Verifies an ALTCHA server signature.
verify_server(payload, url, secret, *, headers, timeout, retries, retry_delay, retry_backoff, http_post) → VerifyServerResult
Verifies a payload remotely via the ALTCHA Sentinel /v1/verify/signature API, instead of
checking the HMAC signature locally. Avoids managing the HMAC secret on your server, at the
cost of a network round-trip.
from altcha import verify_server
result = verify_server(
payload, # the payload received from POST /v1/verify
url="https://sentinel.example.com/v1/verify/signature",
secret=API_KEY_SECRET, # optional, checked against the payload's API key
timeout=10,
retries=2,
)
if result.verified:
...
| Parameter | Type | Default | Description |
|---|---|---|---|
payload |
str | ServerSignaturePayload | dict |
— | The payload to verify. |
url |
str |
— | Full URL of the Sentinel /v1/verify/signature endpoint. |
secret |
str |
None |
API key secret, checked against the payload's API key. |
headers |
dict |
None |
Additional headers to send with the request. |
timeout |
float |
10 |
Per-attempt request timeout in seconds. |
retries |
int |
0 |
Number of retry attempts after the first try. |
retry_delay |
float |
0.3 |
Base delay in seconds between retries. |
retry_backoff |
str |
'exponential' |
'fixed' or 'exponential' backoff. |
http_post |
callable | stdlib urllib |
Transport override: (url, body, headers, timeout) -> (status, body). |
Returns VerifyServerResult with fields:
| Field | Type | Description |
|---|---|---|
verified |
bool |
True if Sentinel confirmed the payload is valid. |
reason |
str | None |
Failure reason, if any. |
api_key |
str | None |
The API key associated with the payload, if returned. |
verification_data |
dict | None |
Parsed verification data, if returned. |
V1 (legacy)
create_challenge_v1(options) → ChallengeV1
Creates a new v1 challenge.
ChallengeOptionsV1 parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
algorithm |
str |
'SHA-256' |
Hashing algorithm ('SHA-1', 'SHA-256', 'SHA-512'). |
max_number |
int |
1,000,000 |
Upper bound for the random number. |
salt_length |
int |
12 |
Length of the random salt in bytes. |
hmac_key |
str |
— | Required HMAC key. |
salt |
str |
auto | Optional salt. Random if omitted. |
number |
int |
auto | Optional number. Random if omitted. |
expires |
datetime |
None |
Optional expiration time. |
params |
dict |
None |
Optional URL-encoded query parameters appended to the salt. |
verify_solution_v1(payload, hmac_secret, check_expires) → (bool, str | None)
Verifies a v1 solution payload.
solve_challenge_v1(challenge, salt, algorithm, max_number, start) → SolutionV1 | None
Brute-forces a v1 challenge.
extract_params_v1(payload) → dict
Extracts URL parameters from the payload's salt.
License
MIT
Metadata
Release files for altcha 2.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| altcha-2.2.0.tar.gz | 28.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| altcha-2.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 49.0 kB
Release files / altcha-2.2.0.tar.gz
| Download URL | altcha-2.2.0.tar.gz |
|---|---|
| Size | 28.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
70ebf4bb5c6ffac315aa1e3a1666d5879a3f1131cd736017c0179909c7a07ccf
|
|
BLAKE2b-256 checksum How to use checksums |
860d16cc88ec35d341502c51cb29474c9829f25b521060cfc55861fbf2fac116
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency logRelease files / altcha-2.2.0-py3-none-any.whl
| Download URL | altcha-2.2.0-py3-none-any.whl |
|---|---|
| Size | 21.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5dac48a28833f24cd4f98542be550c3da56e7f67411d8091c64aaceaf302314d
|
|
BLAKE2b-256 checksum How to use checksums |
e9b83ec6e593ef7e867b94660f548cce23e1a50197a77f4fdc53c292c5dbd77a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.
Transparency log