C Platform Guide
Overview
metamui-crypto-c/ is a set of C99 static libraries, one per algorithm module, built with CMake. There is no single libmetamui_crypto to link against and no pkg-config file; a program links the module targets it uses. The code blocks in this guide are compiled and run by the tree’s own build (doc_example_* in ctest), and a guard keeps them equal to the sources they quote.
Build
git clone --recurse-submodules <repository>
cd metamui-crypto-c
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
The post-quantum modules (ML-KEM, ML-DSA, and others) compile vendored reference code from the metamui-crypto-reference/ submodules; without them the configure step stops with a message naming the missing directory.
Running the tests
ctest --test-dir build --output-on-failure
The suite includes the upstream known-answer gates (NIST ACVP for ML-KEM and ML-DSA, the Round-3 answer files for Falcon, Wycheproof for AES-GCM, RFC 5869 for HKDF, …) and the ten doc_example_* programs this guide and the API reference quote. A gate that cannot find its answer file fails; it never skips.
Using the library in your project
Add the tree with add_subdirectory and link the targets you need. Each target carries its own public include directory; the post-quantum headers live under the tree’s top-level include/.
add_subdirectory(path/to/metamui-crypto-c)
add_executable(app app.c)
target_link_libraries(app metamui-sha256 metamui-mlkem)
target_include_directories(app PRIVATE path/to/metamui-crypto-c/include)
Hashing
One-shot and streaming SHA-256; both return METAMUI_SHA256_SUCCESS (0) on success:
#include <metamui/sha256.h>
/* One-shot: 32 bytes of digest for `len` bytes of input. Returns 0 on success. */
static int sha256_of(const uint8_t *data, size_t len, uint8_t out[METAMUI_SHA256_DIGEST_SIZE]) {
return metamui_sha256(data, len, out);
}
/* Streaming: the same digest, fed in pieces. */
static int sha256_streamed(const uint8_t *a, size_t a_len, const uint8_t *b, size_t b_len,
uint8_t out[METAMUI_SHA256_DIGEST_SIZE]) {
metamui_sha256_ctx *ctx = metamui_sha256_new();
if (ctx == NULL) return METAMUI_SHA256_ERROR_NO_MEM;
int rc = metamui_sha256_init(ctx);
if (rc == METAMUI_SHA256_SUCCESS) rc = metamui_sha256_update(ctx, a, a_len);
if (rc == METAMUI_SHA256_SUCCESS) rc = metamui_sha256_update(ctx, b, b_len);
if (rc == METAMUI_SHA256_SUCCESS) rc = metamui_sha256_final(ctx, out);
metamui_sha256_free(ctx);
return rc;
}
Post-quantum key exchange (ML-KEM-768)
Fixed-size buffers, three calls, METAMUI_PQC_OK on success:
#include <metamui/mlkem.h>
/* ML-KEM-768 (FIPS 203) with fixed-size buffers. Every call returns a
* metamui_pqc_error_t; METAMUI_PQC_OK is success. */
static int kem_keygen(uint8_t pk[MLKEM768_PUBLICKEYBYTES], uint8_t sk[MLKEM768_SECRETKEYBYTES]) {
return metamui_mlkem768_keypair(pk, sk) == METAMUI_PQC_OK ? 0 : -1;
}
/* Sender: a ciphertext for the receiver's public key, and the shared secret. */
static int kem_encaps(uint8_t ct[MLKEM768_CIPHERTEXTBYTES], uint8_t ss[MLKEM768_SHAREDSECRETBYTES],
const uint8_t pk[MLKEM768_PUBLICKEYBYTES]) {
return metamui_mlkem768_encapsulate(ct, ss, pk) == METAMUI_PQC_OK ? 0 : -1;
}
/* Receiver: the same shared secret from the ciphertext and the secret key.
* A malformed ciphertext yields an implicit-rejection secret, not an error. */
static int kem_decaps(uint8_t ss[MLKEM768_SHAREDSECRETBYTES], const uint8_t ct[MLKEM768_CIPHERTEXTBYTES],
const uint8_t sk[MLKEM768_SECRETKEYBYTES]) {
return metamui_mlkem768_decapsulate(ss, ct, sk) == METAMUI_PQC_OK ? 0 : -1;
}
Authenticated encryption (ChaCha20-Poly1305)
A 32-byte key, a 12-byte nonce that is never reused under that key, a 16-byte tag; decryption returns non-zero when the tag does not authenticate:
#include <metamui/chacha20poly1305.h>
/* Seal: ciphertext is plaintext_len bytes, the tag is 16 more. Never reuse a
* (key, nonce) pair; a 96-bit nonce must be a counter or otherwise unique. */
static int seal(uint8_t *ciphertext, uint8_t tag[METAMUI_CHACHA20POLY1305_TAG_SIZE],
const uint8_t *plaintext, size_t plaintext_len,
const uint8_t *aad, size_t aad_len,
const uint8_t nonce[METAMUI_CHACHA20POLY1305_NONCE_SIZE],
const uint8_t key[METAMUI_CHACHA20POLY1305_KEY_SIZE]) {
return metamui_chacha20poly1305_encrypt(ciphertext, tag, plaintext, plaintext_len, aad, aad_len, nonce, key);
}
/* Open: returns 0 only when the tag authenticates ciphertext and aad; on any
* other return the plaintext buffer must not be used. */
static int open_sealed(uint8_t *plaintext, const uint8_t *ciphertext, size_t ciphertext_len,
const uint8_t tag[METAMUI_CHACHA20POLY1305_TAG_SIZE],
const uint8_t *aad, size_t aad_len,
const uint8_t nonce[METAMUI_CHACHA20POLY1305_NONCE_SIZE],
const uint8_t key[METAMUI_CHACHA20POLY1305_KEY_SIZE]) {
return metamui_chacha20poly1305_decrypt(plaintext, ciphertext, ciphertext_len, tag, aad, aad_len, nonce, key);
}
Memory and errors
- The library allocates nothing on your behalf except the SHA-256 context returned by
metamui_sha256_new, whichmetamui_sha256_freereleases; every other buffer is caller-owned and sized by the header constants. - Check every return value. The convention per module is on the C API Reference page; a failed verification is an ordinary non-success return.
- Clear secrets when you are done with them (
ed25519_secure_clear,x25519_secure_clear,hkdf_secure_clearare provided by their modules;memsetmay be optimised away).
Platform support
The tree is portable C99 and is built and tested on macOS and Linux by the repository’s preflight (c build + ctest). The MSVC build for Windows is tracked as its own work item (issue #188); nothing in this guide has been run there.
See also
- C API Reference
metamui-crypto-c/CMakeLists.txt