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

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