# altcha-lib-cpp C++17 library and CLI implementing the [ALTCHA](https://altcha.org) Proof-of-Work v2 protocol. ## Overview ALTCHA PoW v2 is a brute-force counter-based key derivation challenge. A solver iterates a counter, combines it with a nonce, feeds it into a KDF alongside a salt, and checks the resulting key against a required hex prefix. The first counter whose derived key starts with that prefix is the solution. **Supported KDF algorithms:** | Algorithm | Identifier | |---|---| | SHA-256 / SHA-384 / SHA-512 | `SHA-256`, `SHA-384`, `SHA-512` | | PBKDF2-HMAC-SHA-256/384/512 | `PBKDF2/SHA-256`, `PBKDF2/SHA-384`, `PBKDF2/SHA-512` | | Scrypt | `SCRYPT` | | Argon2id | `ARGON2ID` | ## Dependencies - **OpenSSL** (libcrypto) — SHA, PBKDF2, Scrypt, HMAC - **libargon2** — Argon2id - C++17 standard library (threads) - [nlohmann/json](https://github.com/nlohmann/json) (bundled as `third_party/json.hpp`) ### Installing dependencies **macOS (Homebrew):** ```sh brew install openssl argon2 ``` **Debian / Ubuntu:** ```sh apt install libssl-dev libargon2-dev ``` **Windows (vcpkg):** ```sh vcpkg install openssl argon2 cmake -B build -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake ``` ## Building ```sh cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build ``` This produces: - `build/libaltcha.a` — static library - `build/altcha` — CLI ### Running tests ```sh ctest --test-dir build -V ``` ## Integration ### CMake FetchContent (recommended) ```cmake include(FetchContent) FetchContent_Declare( altcha GIT_REPOSITORY https://github.com/altcha-org/altcha-lib-cpp.git GIT_TAG main ) FetchContent_MakeAvailable(altcha) target_link_libraries(your_target PRIVATE altcha) ``` ### CMake subdirectory (vendored) ```cmake add_subdirectory(third_party/altcha-lib-cpp) target_link_libraries(your_target PRIVATE altcha) ``` ### System install + find_package Install to the system (or a custom prefix): ```sh cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local cmake --build build cmake --install build ``` Then in your project: ```cmake find_package(altcha REQUIRED) target_link_libraries(your_target PRIVATE altcha::altcha) ``` Note: when using `find_package`, link against `altcha::altcha` (namespaced target). ### Include the header ```cpp #include ``` ## Library API Include the single public header: ```cpp #include ``` Link against `libaltcha.a`, OpenSSL (`-lcrypto`), and Argon2 (`-largon2`). ### Creating a challenge ```cpp altcha::CreateChallengeOptions opts; opts.algorithm = "PBKDF2/SHA-256"; opts.cost = 5000; opts.hmacSignatureSecret = "server-secret"; // signs the challenge opts.expiresAt = time(nullptr) + 600; // expires in 10 minutes altcha::Challenge ch = altcha::createChallenge(opts); std::string json = altcha::challengeToJson(ch, /*pretty=*/true); ``` **Deterministic mode** — pre-solve at a known counter (useful for testing or server-side difficulty tuning): ```cpp opts.counter = 5000; // key prefix will be the derived key at this counter altcha::Challenge ch = altcha::createChallenge(opts); ``` **Fast-path verification** — sign the derived key so verification doesn't need to re-run the KDF: ```cpp opts.hmacKeySignatureSecret = "key-secret"; altcha::Challenge ch = altcha::createChallenge(opts); // ch.parameters.keySignature is now populated ``` ### Solving a challenge **Single-threaded:** ```cpp altcha::Challenge ch = altcha::challengeFromJson(jsonString); altcha::SolveChallengeOptions solveOpts; solveOpts.timeout = 90000; // ms, 0 = no timeout std::optional sol = altcha::solveChallenge(ch, solveOpts); if (sol) { std::cout << altcha::solutionToJson(*sol, true) << "\n"; } ``` **Multi-threaded:** ```cpp // 0 = use hardware_concurrency() std::optional sol = altcha::solveChallengeWorkers(ch, /*numThreads=*/0); ``` **Aborting early:** ```cpp volatile bool abort = false; // set abort = true from another thread or signal handler to stop the solver auto sol = altcha::solveChallenge(ch, {}, &abort); ``` ### Verifying a solution ```cpp altcha::VerifyResult result = altcha::verifySolution( ch, *sol, "server-secret", // hmacSignatureSecret (required) "key-secret" // hmacKeySignatureSecret (optional, enables fast path) ); if (result.verified) { // accepted } else if (result.expired) { // challenge expired } else if (result.invalidSignature) { // challenge was tampered with } else if (result.invalidSolution) { // wrong counter / derived key } ``` ### Server signature verification When using the [ALTCHA Sentinel](https://altcha.org/docs/v2/sentinel), the payload contains a signed `ServerSignaturePayload`. Use `verifyServerSignature` to validate it on your server. ```cpp // Decode the payload from JSON (e.g. sent by the client from the API response) altcha::ServerSignaturePayload payload = altcha::serverSignaturePayloadFromJson(jsonString); altcha::VerifyServerSignatureResult result = altcha::verifyServerSignature(payload, "hmac-secret"); if (result.verified) { // Signature is valid and the solution was accepted auto& vd = result.verificationData; if (vd.score.has_value()) std::cout << "spam score: " << *vd.score << "\n"; } else if (result.expired) { // verificationData.expire is in the past } else if (result.invalidSignature) { // HMAC mismatch – wrong secret or tampered payload } else if (result.invalidSolution) { // payload.verified == false or verificationData.verified == false } ``` `VerifyServerSignatureResult` extends the basic `VerifyResult` fields with: | Field | Type | Description | |---|---|---| | `hasVerificationData` | `bool` | true when `verificationData` was parsed successfully | | `verificationData` | `ServerSignatureVerificationData` | parsed fields from the URL-encoded payload | `ServerSignatureVerificationData` fields: | Field | Type | |---|---| | `classification` | `optional` | | `email` | `optional` | | `expire` | `optional` (unix seconds) | | `fields` | `optional>` | | `fieldsHash` | `optional` | | `id` | `optional` | | `ipAddress` | `optional` | | `reasons` | `optional>` | | `score` | `optional` | | `time` | `optional` | | `verified` | `optional` | | `extra` | `map` (unknown keys) | ### Parsing verification data Parse a URL-encoded `verificationData` string independently: ```cpp altcha::ServerSignatureVerificationData vd = altcha::parseVerificationData("verified=true&expire=1735689600&score=1.5"); ``` ### Verifying form field hashes When the ALTCHA widget is configured to hash form fields, verify the hash server-side: ```cpp std::map form = { {"email", "user@example.com"}, {"message", "hello"}, }; bool ok = altcha::verifyFieldsHash( form, *payload.verificationData.fields, // e.g. {"email", "message"} *payload.verificationData.fieldsHash, "SHA-256" // default, matches widget configuration ); ``` ### JSON helpers ```cpp // Challenge altcha::Challenge ch = altcha::challengeFromJson(str); std::string str = altcha::challengeToJson(ch, /*pretty=*/false); // Solution altcha::Solution sol = altcha::solutionFromJson(str); std::string str = altcha::solutionToJson(sol, /*pretty=*/false); // Server signature payload altcha::ServerSignaturePayload p = altcha::serverSignaturePayloadFromJson(str); ``` ## CLI ### Solve a challenge ```sh altcha [--workers N] ``` `--workers` defaults to `1`. Set a higher value to use multiple threads. ```sh altcha challenge.json # single worker (default) altcha challenge.json --workers 4 # use 4 workers ``` Output (JSON to stdout): ```json { "counter": 1000, "derivedKey": "bb88101a4ee7fa94f960da326245942087e7ad4c2ed14ff13dca5eba53264d2a", "time": 41.5 } ``` ### Create a challenge ```sh altcha --create [options] ``` | Option | Description | Default | |---|---|---| | `--algorithm ALGO` | KDF algorithm | `PBKDF2/SHA-256` | | `--cost N` | KDF cost / iterations | `50000` | | `--key-length N` | Derived key length in bytes | `32` | | `--memory-cost N` | Memory cost (Scrypt/Argon2) | `0` | | `--parallelism N` | Parallelism (Scrypt/Argon2) | `0` | | `--expires-in N` | Expiry in seconds from now | none | | `--secret S` | HMAC secret for challenge signature | none | | `--key-secret S` | HMAC secret for key signature | none | | `--counter N` | Deterministic mode: pre-solve at counter N | none | ```sh altcha --create \ --algorithm PBKDF2/SHA-256 \ --cost 5000 \ --expires-in 600 \ --secret server-secret \ --counter 5000 ``` ### Verify a solution ```sh altcha --verify --secret [--key-secret ] ``` ```sh altcha challenge.json --verify solution.json --secret server-secret ``` Output (JSON to stdout): ```json { "verified": true, "expired": false, "invalidSignature": false, "invalidSolution": false, "time": 2.3 } ``` ## License MIT