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

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