Skip to content

altcha-org/altcha-lib-cpp

altcha-lib-cpp

C++17 library and CLI implementing the ALTCHA 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 (bundled as third_party/json.hpp)

Installing dependencies

macOS (Homebrew):

brew install openssl argon2

Debian / Ubuntu:

apt install libssl-dev libargon2-dev

Windows (vcpkg):

vcpkg install openssl argon2
cmake -B build -DCMAKE_TOOLCHAIN_FILE=<vcpkg-root>/scripts/buildsystems/vcpkg.cmake

Building

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build

This produces:

  • build/libaltcha.a — static library
  • build/altcha — CLI

Running tests

ctest --test-dir build -V

Integration

CMake FetchContent (recommended)

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)

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):

cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build
cmake --install build

Then in your project:

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

#include <altcha/altcha.hpp>

Library API

Include the single public header:

#include <altcha/altcha.hpp>

Link against libaltcha.a, OpenSSL (-lcrypto), and Argon2 (-largon2).

Creating a challenge

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):

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:

opts.hmacKeySignatureSecret = "key-secret";
altcha::Challenge ch = altcha::createChallenge(opts);
// ch.parameters.keySignature is now populated

Solving a challenge

Single-threaded:

altcha::Challenge ch = altcha::challengeFromJson(jsonString);

altcha::SolveChallengeOptions solveOpts;
solveOpts.timeout = 90000; // ms, 0 = no timeout

std::optional<altcha::Solution> sol = altcha::solveChallenge(ch, solveOpts);
if (sol) {
    std::cout << altcha::solutionToJson(*sol, true) << "\n";
}

Multi-threaded:

// 0 = use hardware_concurrency()
std::optional<altcha::Solution> sol = altcha::solveChallengeWorkers(ch, /*numThreads=*/0);

Aborting early:

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

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, the payload contains a signed ServerSignaturePayload. Use verifyServerSignature to validate it on your server.

// 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<string>
email optional<string>
expire optional<int64_t> (unix seconds)
fields optional<vector<string>>
fieldsHash optional<string>
id optional<string>
ipAddress optional<string>
reasons optional<vector<string>>
score optional<double>
time optional<int64_t>
verified optional<bool>
extra map<string, string> (unknown keys)

Parsing verification data

Parse a URL-encoded verificationData string independently:

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:

std::map<std::string, std::string> 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

// 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

altcha <challenge.json> [--workers N]

--workers defaults to 1. Set a higher value to use multiple threads.

altcha challenge.json              # single worker (default)
altcha challenge.json --workers 4  # use 4 workers

Output (JSON to stdout):

{
  "counter": 1000,
  "derivedKey": "bb88101a4ee7fa94f960da326245942087e7ad4c2ed14ff13dca5eba53264d2a",
  "time": 41.5
}

Create a challenge

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
altcha --create \
  --algorithm PBKDF2/SHA-256 \
  --cost 5000 \
  --expires-in 600 \
  --secret server-secret \
  --counter 5000

Verify a solution

altcha <challenge.json> --verify <solution.json> --secret <hmac_secret> [--key-secret <key_secret>]
altcha challenge.json --verify solution.json --secret server-secret

Output (JSON to stdout):

{
  "verified": true,
  "expired": false,
  "invalidSignature": false,
  "invalidSolution": false,
  "time": 2.3
}

License

MIT

About

C++17 library and CLI implementing the ALTCHA Proof-of-Work v2 protocol.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors