# ALTCHA Elixir Library A lightweight Elixir library for creating and verifying [ALTCHA](https://altcha.org) challenges. ## Compatibility - Elixir 1.17+ (OTP 26+) ## Examples - [Phoenix integration guide](/guides/phoenix.md) — widget setup, the `Altcha.Plug.Challenge` endpoint, LiveView hook, and verification - [`examples/server/`](/examples/server/) - [`examples/argon2.exs`](/examples/argon2.exs) ## Installation ```elixir defp deps do [ {:altcha, "~> 2.0"} ] end ``` ## Versions The library supports two proof-of-work versions: | Module | Algorithm | Use with | |--------|-----------|----------| | `Altcha.V1` | Hash-based (`SHA-256` etc.) | ALTCHA widget v1 and v2 (still accepted by v3) | | `Altcha.V2` | Key-derivation-based (`PBKDF2`, iterative `SHA`) | ALTCHA widget v3 | The top-level `Altcha` module delegates to `Altcha.V1` for backward compatibility. --- ## V2 Usage ### Creating a challenge ```elixir challenge = Altcha.V2.create_challenge(%Altcha.V2.CreateChallengeOptions{ algorithm: "PBKDF2/SHA-256", cost: 10_000, hmac_signature_secret: "your-secret" }) # Send as JSON to the client Jason.encode!(challenge) ``` For deterministic mode (random counter, faster server-side verification): ```elixir challenge = Altcha.V2.create_challenge(%Altcha.V2.CreateChallengeOptions{ algorithm: "PBKDF2/SHA-256", cost: 5_000, counter: Enum.random(5_000..10_000), expires_at: DateTime.to_unix(DateTime.utc_now(), :second) + 600, hmac_signature_secret: "your-secret", hmac_key_signature_secret: "your-key-secret" }) ``` With `counter` and `hmac_key_signature_secret`, the server pre-computes the expected derived key and signs it. Verification then checks the HMAC of the submitted key instead of re-running the key derivation. ### Verifying a solution The client submits a Base64-encoded JSON payload containing the challenge and solution: ```elixir # Decode the client payload payload = Altcha.V2.decode_payload(params["altcha"]) result = Altcha.V2.verify_solution(%Altcha.V2.VerifySolutionOptions{ challenge: payload.challenge, solution: payload.solution, hmac_signature_secret: "your-secret", hmac_key_signature_secret: "your-key-secret" # optional }) result.verified # true / false result.expired # true if the challenge has expired result.invalid_signature # true if the challenge was tampered with result.invalid_solution # true if the solution is incorrect ``` ### Verifying a server signature ```elixir {result, verification_data} = Altcha.V2.verify_server_signature(params["altcha"], "your-secret") result.verified # true / false verification_data["email"] # values parsed from verificationData verification_data["verified"] # boolean verification_data["expire"] # integer Unix timestamp ``` `verify_server_signature/2` accepts a `%Altcha.V2.ServerSignaturePayload{}` struct, a plain map, a raw JSON string, or a Base64-encoded JSON string. ### Verifying form fields hash ```elixir Altcha.V2.verify_fields_hash(form_data, ["email", "message"], fields_hash, "SHA-256") ``` ### Supported algorithms | Algorithm string | Description | |-----------------|-------------| | `"SHA-256"` / `"SHA-384"` / `"SHA-512"` | Iterative SHA hashing (built-in) | | `"PBKDF2/SHA-256"` / `"PBKDF2/SHA-384"` / `"PBKDF2/SHA-512"` | PBKDF2 (requires OTP 24+) | | Custom | Pass a `derive_key_fn` option | ### Custom key derivation For algorithms not built-in (e.g. Scrypt, Argon2id), provide a `derive_key_fn`: ```elixir Altcha.V2.create_challenge(%Altcha.V2.CreateChallengeOptions{ algorithm: "SCRYPT", cost: 16_384, memory_cost: 8, derive_key_fn: fn params, salt, password -> # return derived key as binary end, hmac_signature_secret: "your-secret" }) ``` --- ## V1 Usage ```elixir challenge = Altcha.V1.create_challenge(%Altcha.V1.ChallengeOptions{ hmac_key: "your-secret", max_number: 100_000, expires: DateTime.to_unix(DateTime.utc_now(), :second) + 600 }) # Verify a solution submitted by the client Altcha.V1.verify_solution(params["altcha"], "your-secret") # Verify a server signature {verified, verification_data} = Altcha.V1.verify_server_signature(params["altcha"], "your-secret") # Verify form fields hash Altcha.V1.verify_fields_hash(form_data, ["email", "message"], fields_hash, :sha256) ``` --- ## License MIT