libcrypt - Unified Cryptography Library
Cryptographic primitives wrapping mbedTLS and libsodium
libcrypt - Unified Cryptography Library
Overview
libcrypt provides all cryptographic primitives used by OctoMY. It wraps two vendored C libraries — mbedTLS 3.6 LTS for RSA/AES/hashing and libsodium 1.0.21 for Ed25519/X25519/XChaCha20-Poly1305 — behind a clean C++ API in the oc_crypt namespace.
This library replaces the former libmbedtls (raw C sources) and libqpolarssl (Qt wrapper) with a single unified library.
Architecture
libcrypt/
├── mbedtls/ # Vendored mbedTLS 3.6.4 LTS (crypto-only, no TLS)
├── psa/ # PSA Crypto API headers (required by mbedTLS 3.6)
├── crypt/ # OctoMY C++ wrapper (oc_crypt namespace)
│ ├── CryptRandom # CSPRNG (CTR-DRBG + entropy)
│ ├── CryptHash # SHA-256, SHA-512, etc.
│ ├── CryptCipher # AES-CBC, AES-GCM, etc.
│ ├── CryptPki # RSA sign/verify/encrypt/decrypt
│ ├── CryptEcc # Ed25519, X25519, XChaCha20-Poly1305
│ └── CryptTypes # Algorithm enums and conversions
├── sodium-prefix/ # Pre-built libsodium 1.0.21 (headers + static lib)
├── build-sodium.sh # Build script for libsodium
└── lib.qbs
Dependency Graph
libfs ──► libcrypt (AES-XTS for encrypted filesystem)
libsec ──► libcrypt (RSA/Ed25519 for key management)
libcomms ──► libsec (KeyStore, ISigningKey for sessions)
API Reference
All classes are in the oc_crypt namespace.
CryptRandom
CSPRNG using mbedTLS CTR-DRBG with platform entropy.
oc_crypt::Random rng;
QByteArray bytes = rng.random(32); // 32 random bytes
int result = rng.update(customSeed); // re-seed
mbedtls_ctr_drbg_context *ctx = rng.context(); // for mbedTLS API calls
CryptHash
Cryptographic hashing (SHA-256, SHA-512, SHA-1, MD5, RIPEMD-160, etc.).
oc_crypt::Hash hasher(oc_crypt::THash::SHA_256);
hasher.update(data1);
hasher.update(data2);
QByteArray digest = hasher.finish();
// One-shot
QByteArray hash = oc_crypt::Hash::hash(data, oc_crypt::THash::SHA_256);
// HMAC
QByteArray mac = oc_crypt::Hash::hmac(key, data, oc_crypt::THash::SHA_256);
CryptCipher
Symmetric encryption (AES-256-CBC, AES-256-GCM, Camellia, etc.).
oc_crypt::Cipher enc(oc_crypt::TCipher::AES_256_CBC, oc_crypt::TCipherDir::ENCRYPT);
enc.setIV(iv);
enc.setKey(key);
QByteArray ciphertext = enc.crypt(plaintext);
oc_crypt::Cipher dec(oc_crypt::TCipher::AES_256_CBC, oc_crypt::TCipherDir::DECRYPT);
dec.setIV(iv);
dec.setKey(key);
QByteArray decrypted = dec.crypt(ciphertext);
CryptPki
RSA public-key cryptography (key generation, sign/verify, encrypt/decrypt, PEM export/import).
oc_crypt::Pki pki;
pki.generateKeyPair(4096);
// Sign
QByteArray signature = pki.sign(message, oc_crypt::THash::SHA_256);
// Verify (with public key only)
oc_crypt::Pki verifier;
verifier.parsePublicKey(pki.getPEMPubkey());
int result = verifier.verify(message, signature, oc_crypt::THash::SHA_256);
// result == 0 means valid
// Encrypt/Decrypt
QByteArray ciphertext = verifier.encrypt(plaintext);
QByteArray decrypted = pki.decrypt(ciphertext);
// PEM export
QByteArray pubPem = pki.getPEMPubkey();
QByteArray privPem = pki.getPEMKey();
CryptEcc
Ed25519 signing, X25519 key exchange, and XChaCha20-Poly1305 AEAD via libsodium.
// Initialize once at startup
oc_crypt::Ecc::initialize();
// Ed25519 key generation
QByteArray pub, sec;
oc_crypt::Ecc::generateEd25519KeyPair(pub, sec);
// pub = 32 bytes, sec = 64 bytes
// Deterministic keygen from seed
QByteArray seed(32, '\x00'); // 32-byte seed
oc_crypt::Ecc::generateEd25519KeyPairFromSeed(seed, pub, sec);
// Sign and verify
QByteArray signature = oc_crypt::Ecc::ed25519Sign(message, sec);
bool valid = oc_crypt::Ecc::ed25519Verify(message, signature, pub);
// X25519 key exchange (convert Ed25519 keys first)
QByteArray xPub = oc_crypt::Ecc::ed25519PublicToX25519(pub);
QByteArray xSec = oc_crypt::Ecc::ed25519SecretToX25519(sec);
QByteArray shared = oc_crypt::Ecc::x25519KeyExchange(myXSec, theirXPub);
// XChaCha20-Poly1305 AEAD
QByteArray key = oc_crypt::Ecc::generateAeadKey(); // 32 bytes
QByteArray nonce = oc_crypt::Ecc::generateAeadNonce(); // 24 bytes
QByteArray ct = oc_crypt::Ecc::aeadEncrypt(plaintext, key, nonce, additionalData);
QByteArray pt = oc_crypt::Ecc::aeadDecrypt(ct, key, nonce, additionalData);
CryptTypes
Algorithm enumerations and conversion utilities.
| Category | Available Algorithms |
|---|---|
Hash (THash) |
SHA_256, SHA_512, SHA_384, SHA_224, SHA_1, MD5, RIPEMD_160 |
Cipher (TCipher) |
AES_128/192/256_ECB/CBC/CFB128/CTR, AES_128/256_GCM, CAMELLIA variants |
Concept-based API (octomy::crypt)
A second, forward-looking API surface lives in the octomy::crypt namespace. Each cryptographic category — AEAD, hashing, KDF, signatures, key exchange, raw block ciphers, erasure coding — is expressed as a C++20 concept. Concrete primitives satisfy the concept; consumer code is templated on it.
This is the API the internal Sentient Global Hive crypto-strategy spec (§CS-4) builds on. It coexists with the oc_crypt namespace above; existing call sites stay on oc_crypt until they migrate.
Concept set
src/libs/libcrypt/crypt/concepts/
├── Aead.hpp # authenticated encryption with associated data
├── Hash.hpp # Hash, IncrementalHash, MerkleHash (refinement chain)
├── Kdf.hpp # key derivation
├── Signature.hpp # detached signature schemes
├── Kex.hpp # key exchange
├── BlockCipher.hpp # raw block primitive (legacy modes)
└── ErasureCodec.hpp # parameterized (n, k) shard codec
The exact concept definitions are reproduced from §CS-4.1; each header carries the canonical text plus a comment describing the eventual concrete satisfiers.
Templated consumer code
Consumers depend on the concept, not on a specific primitive:
#include "crypt/concepts/Aead.hpp"
template<octomy::crypt::Aead A>
QByteArray sealShard(typename A::Key key, typename A::Nonce nonce,
QByteArrayView shardBytes) {
return A::seal(key, nonce, shardBytes, /*aad=*/{});
}
// Caller picks the primitive at the type-name boundary:
auto ct = sealShard<octomy::crypt::ChaCha20Poly1305>(k, n, plaintext);
A runtime selector exists for cases that need it (e.g. activity configuration choosing an algorithm from a stored enum) — see §CS-4.3.
Refinement: Hash → IncrementalHash → MerkleHash
Hash is one-shot. IncrementalHash adds update / finalize and refines Hash. MerkleHash adds deriveChildHash / merkleProof and refines IncrementalHash. A MerkleHash-satisfying type automatically satisfies the other two. The L3 shard codec depends on MerkleHash because per-shard Merkle proofs are part of the on-disk shard format.
Concrete satisfiers landed
| Class | Concept | Backing | Reference |
|---|---|---|---|
ChaCha20Poly1305 |
Aead |
mbedTLS | RFC 8439 |
XChaCha20Poly1305 |
Aead |
Monocypher | draft-irtf-cfrg-xchacha |
Sha256 |
IncrementalHash |
mbedTLS | FIPS-180 |
Sha512 |
IncrementalHash |
mbedTLS | FIPS-180 |
Hkdf<H> |
Kdf |
mbedTLS (templated on a Hash satisfier) | RFC 5869 |
Ed25519 |
Signature |
Monocypher | RFC 8032 |
X25519 |
Kex |
Monocypher | RFC 7748 |
Every satisfier carries a static_assert(Concept<T>) at the bottom of its header — the concept design and the concrete implementation are checked against each other at compile time.
Status
| Phase | Scope | Status |
|---|---|---|
| C0 | Concept headers + concept-instance tests | Landed |
| C1a | AEAD-ratchet primitives (Sha256/512, Hkdf, Ed25519, X25519, XChaCha20-Poly1305) | Landed |
| C1b | RSA (Signature) + AES (BlockCipher) satisfiers |
Pending |
| C2 | Extract Galois-field + Reed-Solomon → ErasureCodec impl |
Pending |
| C3 | Vendor BLAKE3 → MerkleHash satisfier |
Pending |
| C4 | ChaCha20-Poly1305 (RFC 8439) → Aead satisfier |
Landed |
| C5 | Elligator2 | Pending |
| C6 | Residual primitive audit | Pending |
| C7–C8 | OpenSSL retirement | Pending |
| C9 | PQ primitives stocked-unused | Pending |
See the internal crypto-strategy.md spec for the full plan.
Build Requirements
libsodium
libsodium must be built once before compiling. Run the provided script:
cd src/libs/libcrypt
./build-sodium.sh
This downloads libsodium 1.0.21, builds a static library, and installs headers and libsodium.a to sodium-prefix/. The script is idempotent.
mbedTLS
mbedTLS 3.6.4 LTS sources are vendored directly — no separate build step needed. A custom mbedtls_config.h enables only the crypto modules OctoMY uses (no TLS, X.509, or networking).
Namespace
All wrapper classes use oc_crypt:: (not crypt::) to avoid collision with the POSIX crypt() function on Linux.
Test Coverage
- testCryptEcc (16 tests): Ed25519 keygen/sign/verify, X25519 key exchange, XChaCha20-Poly1305 AEAD, invalid input handling
- testKey (6 tests): RSAKey allocation, integrity, signing, hybrid encryption
- testKeyFactory (21 tests): Key creation, serialization, sign/verify, encrypt/decrypt for both RSA and Ed25519 via
ISigningKeyinterface - testKeyStore (4 tests): Key persistence and retrieval
- testCommsSession (3 tests): Session crypto operations
- testCourierEncryption (7 tests): AEAD replay window, corrupted ciphertext, truncation, reorder recovery, key mismatch