Skip to content

v2.2.0

Latest

Choose a tag to compare

@ovx ovx released this 03 Oct 10:09

v2.2.0

This release brings Altcha.V2 in line with the JS reference implementation (altcha-lib), so challenges, solutions and server signatures verify the same way in Elixir and JS. It also makes verification robust against malformed client input.

Behaviour changes

  • Empty secrets now raise. Altcha.V2.verify_solution/1 and Altcha.V2.verify_server_signature/2 raise ArgumentError when the HMAC secret is nil or "", as the JS reference does. Previously "" silently returned invalid_signature: true.
  • Altcha.Plug.Challenge raises on a nil or empty :hmac_signature_secret. A string secret is checked in init/1. A function or {module, function, args} secret is checked on each request, which catches an empty environment variable. Previously the plug served unsigned challenges that could never verify.
  • Altcha.V2.create_challenge/1 raises on a non-hex key_prefix. Previously it produced a challenge that no solver could match.
  • key_prefix_length is capped at half the key length, which is also the default, so the precomputed prefix never reveals more than half of the key.
  • Empty secrets in create_challenge/1 mean "not set", as in JS. With hmac_signature_secret: "" it returns an unsigned challenge, and with hmac_key_signature_secret: "" it adds no keySignature.

JS compatibility fixes

  • Signatures: the JSON that gets signed is now byte-identical to JS JSON.stringify(sortKeys(parameters)). That covers number formatting (1.0 → 1, 1e21 → 1e+21), key order ("9" before "10", then UTF-16 order) and string escaping. Challenges whose data contains floats now survive the widget's JSON round trip, and challenges signed by JS with such data now verify.
  • Challenge expiry: a challenge expires as soon as expiresAt has passed, with no extra second of grace. expiresAt: 0 means it never expires.
  • Server signature expiry: expire=0 means it never expires, and a decimal expire is now checked.
  • Key prefix: the prefix is always lowercase hex and matches keys regardless of case. Previously an uppercase prefix such as "0A" rejected the solver's own solution.
  • Key signature fallback: an empty keySignature or key-signature secret makes verify re-derive the key instead of failing.
  • Counter: the solution counter follows JS number rules, so a JSON 7.0 verifies the same as 7.
  • Solve timeout: solve_challenge/1 treats timeout: 0, and the new timeout: :infinity, as no time limit.

Robustness fixes

verify_solution/1, verify_server_signature/2 and decode_payload/1 no longer raise on malformed client input:

  • Bad derived key: a non-hex, odd-length, missing or non-string derivedKey gives invalid_solution: true.
  • Bad counter: a non-numeric counter gives invalid_solution: true.
  • Bad signature: a non-string signature gives invalid_signature: true.
  • Bad server-signature payload: a missing payload, one that isn't JSON or isn't a JSON object, or one with a missing or non-string verificationData gives a failed result with nil verification data.
  • decode_payload/1 returns nil for a nil or non-string payload.
  • Values ending in a newline: a verificationData value such as "5\n" is kept as a string. Previously the whole verification data was discarded and a valid payload failed to verify.
  • Junk prefix in solve: solve_challenge/1 returns nil for a challenge with a non-hex key_prefix instead of raising or running until the timeout.