feat: the crafter-review skeleton, and its semantics for text, JSON and crypto

What the rest of crafter-review stands on.

- project.cpp: a static crafter.review-lib and the crafter-review
  executable; Crafter.Network without QUIC, pinned, or with --local from
  the checkout next to this one; Crafter.LintRules' lite rules, pinned;
  the version from CRAFTER_REVIEW_VERSION, git describe or 0.0.0-dev.
- :Core and :Driver: the version, PromptVersion 1, --version.
- :Json: nlohmann's ordered_json, which keeps key order and unknown keys,
  so the files every driver on a machine shares survive a rewrite; dumps
  in the indented file layout, the spaced one-line form and the compact
  sorted form the structured-output schema needs byte for byte.
- :Crypto: SHA-256/SHA-1/MD5, HMAC, PBKDF2 through libcrypto; base64 and
  base32 written out, because what they refuse, and with which message,
  is pinned.
- :Unicode, Unicode 16.0.0's tables: whitespace, line breaks, decimal
  digits, printable characters, case mappings and case-insensitive folds.
- :Text: string functions on code points, number parsing, the shortest
  float form and half-even rounding, printed values, shell patterns, URL
  splitting and unquoting, POSIX paths, universal newlines and the
  replacing UTF-8 decoder.

Tests: Crypto (published vectors), Text (293 reference vectors).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 05:29:18 +01:00
commit 36852edf63
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
16 changed files with 7845 additions and 0 deletions

View file

@ -0,0 +1,49 @@
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
export module Crafter.Review:Crypto;
import std;
// Byte strings are std::string throughout: a digest, a key or a nonce is a
// run of bytes, hex or base64 is text, and both travel through the same APIs
// as the text around them. OpenSSL's libcrypto does the hashing; base64 and
// base32 are written out here because what they refuse, and with which
// message, is pinned.
export namespace Crafter::Review {
std::string Sha256(std::string_view data);
std::string Sha1(std::string_view data);
std::string Md5(std::string_view data);
// Lowercase hex.
std::string Sha256Hex(std::string_view data);
std::string Md5Hex(std::string_view data);
std::string HmacSha256(std::string_view key, std::string_view data);
std::string HmacSha1(std::string_view key, std::string_view data);
// PBKDF2-HMAC-SHA256 with a 32-byte result: SCRAM-SHA-256's Hi().
std::string Pbkdf2Sha256(std::string_view password, std::string_view salt, std::uint32_t iterations);
// Lowercase, two digits a byte.
std::string HexEncode(std::string_view bytes);
// Standard alphabet, padded with '=': base64.b64encode.
std::string Base64Encode(std::string_view bytes);
// base64.b64decode(text) without validate=True: characters outside the
// alphabet are dropped first, then bad padding throws
// std::invalid_argument, as it raises binascii.Error.
std::string Base64Decode(std::string_view text);
// base64.b32decode(text): the RFC 4648 alphabet in upper case, the length
// a multiple of 8 and the padding one of the five legal amounts; anything
// else throws std::invalid_argument with binascii's message.
std::string Base32Decode(std::string_view text);
// From the kernel's CSPRNG (getrandom): secrets.token_bytes.
std::string RandomBytes(std::size_t count);
// secrets.token_hex(count): 2 * count lowercase hex digits.
std::string TokenHex(std::size_t count);
// A random (version 4) UUID in its 8-4-4-4-12 form: what `claude
// --session-id` takes.
std::string Uuid4();
// hmac.compare_digest: the time taken does not depend on where the two
// differ.
bool ConstantTimeEqual(std::string_view a, std::string_view b);
}