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; a program links the module targets it uses, and an installed tree carries one pkg-config file per module. The tree’s version is the release line, 1.0.0-rc.7 (METAMUI_VERSION_STRING in <metamui/version.h>); the release source archive, metamui-crypto-c-1.0.0-rc.7.tar.gz, is not published yet. 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 include directories. metamui-crypto-c/ needs metamui-crypto-reference/ beside it (a checkout and the source archive both have it) for the post-quantum modules:
add_subdirectory(path/to/metamui-crypto-c EXCLUDE_FROM_ALL)
add_executable(app app.c)
target_link_libraries(app PRIVATE metamui::metamui-sha256 metamui::metamui-mlkem)
A tree outside your source directory also takes a build directory name: add_subdirectory(../metamui-crypto-c metamui-crypto-c EXCLUDE_FROM_ALL). EXCLUDE_FROM_ALL keeps your build to the libraries app links (without it the tree’s test programs are built too). The metamui:: names match the installed package below, so the line does not change when you switch routes. ChaCha20-Poly1305 (below) and X25519 have no metamui:: name; link metamui-chacha20poly1305 and metamui-x25519-module. These libraries are static archives whatever your BUILD_SHARED_LIBS says; the tree does not change that setting or BUILD_TESTS for your project, and your ctest lists the tree’s tests beside yours.
Or install the distribution component into a prefix and consume that:
cmake -S metamui-crypto-c -B build -DCMAKE_BUILD_TYPE=Release -DMETAMUI_LTO=OFF
cmake --build build --target metamui-crypto-c-package
cmake --install build --component metamui-crypto-c --prefix /opt/metamui
find_package(metamui-crypto-c 1 CONFIG REQUIRED) # with CMAKE_PREFIX_PATH=/opt/metamui
add_executable(app app.c)
target_link_libraries(app PRIVATE metamui::metamui-sha256 metamui::metamui-mlkem)
pkg-config --static --cflags --libs metamui-sha256 metamui-mlkem (with PKG_CONFIG_PATH=/opt/metamui/lib/pkgconfig) gives the same flags without CMake. The installed copy carries the libraries behind the catalog’s C profiles; ChaCha20-Poly1305 and X25519 have no catalog profile and are not installed, so they are reachable only through the tree (its own build or add_subdirectory). The C API Reference lists the targets.
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