C API Reference

The C library lives in metamui-crypto-c/ as one static library per module, each with its own header under an include/metamui/ directory. This page documents the modules that a program can link and call today.

Every code block on this page is compiled and run. Each block is the quoted region of a file under metamui-crypto-c/examples/docs/; the tree’s main CMakeLists.txt builds that file as doc_example_<name> and ctest runs its known-answer check, and tools/c_docs_guard.py fails the repository’s preflight if a block and its source drift apart or if the page names a function the library does not define. What you read here links.

Building and linking

The tree’s version is the release line, 1.0.0-rc.7 (METAMUI_VERSION_STRING in <metamui/version.h>; the installed pkg-config files say Version: 1.0.0-rc.7). The release source archive, metamui-crypto-c-1.0.0-rc.7.tar.gz, is not published yet; build from the tree. Configure and build it with CMake (3.16 or newer, a C99 compiler, and the reference submodules initialised for the post-quantum modules):

cd metamui-crypto-c
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failure

To use a module from your own CMake project, add the tree as a subdirectory and link the module’s target; its include directories come with the target. Keep metamui-crypto-reference/ beside metamui-crypto-c/ (a checkout and the source archive both do), since the post-quantum modules compile from it:

add_subdirectory(metamui-crypto-c EXCLUDE_FROM_ALL)
add_executable(app app.c)
target_link_libraries(app PRIVATE metamui::metamui-sha256 metamui::metamui-mlkem)

EXCLUDE_FROM_ALL builds only the libraries app links; without it your build also compiles the tree’s test programs. The metamui:: names are the installed package’s (below), so the same line works against either. ChaCha20-Poly1305 and X25519 are not in the installed package and have no metamui:: name; link them by their tree targets, metamui-chacha20poly1305 and metamui-x25519-module. These libraries are static archives whatever your project’s BUILD_SHARED_LIBS says, and the tree leaves your BUILD_SHARED_LIBS and BUILD_TESTS (variable and cache entry) as you set them. Your ctest lists the tree’s tests next to yours (ctest -R selects your own).

To use an installed copy instead, build and install the distribution component; it carries a CMake package (find_package(metamui-crypto-c CONFIG), imported targets metamui::<target>) and one pkg-config file per library:

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)
target_link_libraries(app PRIVATE metamui::metamui-mlkem)

pkg-config --static --cflags --libs metamui-mlkem gives the same flags without CMake. The component installs the 28 libraries behind the catalog’s C profiles, which covers eight of the ten modules below; ChaCha20-Poly1305 and X25519 have no catalog profile and are not in it (no metamui:: target, no pkg-config file). The tree builds and tests both (doc_example_chacha20poly1305, doc_example_x25519), and a project that adds the tree as a subdirectory links them; an installed copy offers no route to them. Every library is static; link one algorithm family per program where the archives overlap (the three HAETAE levels share internal symbols, and metamui-sha3 overlaps metamui-shake and metamui-keccak256). A plain cmake --install also installs include/metamui/ (including version.h), the module headers whose own CMake files carry an install rule, and five libraries (metamui-crypto, sr25519, falcon512, falcon1024, x25519). The umbrella header <metamui/crypto.h> was removed in #492: its prototypes conflicted with the module headers and most of its declarations had no definition. Include the module header for each algorithm; tools/c_header_guard.py fails preflight when an installed header declares a function the library does not define.

Conventions

Module Header CMake target Conformance gate (ctest)
SHA-256 metamui-sha2/include/metamui/sha256.h metamui-sha256 test_sha256, test_sha2_acvp_kat
BLAKE3 metamui-blake3/include/metamui/blake3.h metamui-blake3-static test_blake3, test_blake3_kat
ChaCha20-Poly1305 metamui-chacha20poly1305/include/metamui/chacha20poly1305.h metamui-chacha20poly1305 test_chacha20poly1305
AES-256-GCM metamui-aes256gcm/include/metamui/aes256gcm.h metamui-aes256gcm test_aes256gcm_wycheproof_kat
Ed25519 metamui-ed25519/include/metamui/ed25519.h metamui-ed25519-static test_ed25519 (RFC 8032 and policy vectors)
X25519 metamui-x25519/include/metamui/x25519.h metamui-x25519-module test_x25519
HKDF metamui-hkdf/include/metamui/hkdf.h metamui-hkdf test_hkdf_rfc5869_kat
ML-KEM-768 include/metamui/mlkem.h metamui-mlkem test_mlkem_upstream_kat (NIST ACVP)
ML-DSA-65 include/metamui/mldsa.h metamui-mldsa test_mldsa_upstream_kat (NIST ACVP)
Falcon-512 include/metamui/falcon512.h falcon512 test_falcon_upstream_kat (NIST Round 3), test_falcon512

Headers under include/metamui/ (ML-KEM, ML-DSA, Falcon) are reached through the tree’s top-level include/; the others come with their module’s target.

SHA-256

FIPS 180-4 SHA-256 and SHA-224, one-shot and streaming (metamui_sha256_new / metamui_sha256_init / metamui_sha256_update / metamui_sha256_final / metamui_sha256_free).

#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;
}

Built as doc_example_sha256 and linked against metamui-sha256.

BLAKE3

BLAKE3 hash, keyed hash and key derivation with any output length; metamui_blake3_init / metamui_blake3_update / metamui_blake3_final stream.

#include <metamui/blake3.h>

/* BLAKE3 is an XOF: ask for any output length. 32 bytes is the default digest. */
static int blake3_of(const uint8_t *data, size_t len, uint8_t *out, size_t out_len) {
    return metamui_blake3_hash(data, len, out, out_len);
}

/* Keyed mode (a 32-byte key) and key derivation (a context string) are the
 * same function family; both return 0 on success. */
static int blake3_mac(const uint8_t key[32], const uint8_t *data, size_t len, uint8_t out[32]) {
    return metamui_blake3_keyed_hash(key, data, len, out, 32);
}

static int blake3_kdf(const char *context, const uint8_t *key_material, size_t key_material_len, uint8_t out[32]) {
    return metamui_blake3_derive_key(context, key_material, key_material_len, out, 32);
}

Built as doc_example_blake3 and linked against metamui-blake3-static.

ChaCha20-Poly1305

RFC 8439 AEAD: 32-byte key, 12-byte nonce, 16-byte tag; metamui_chacha20poly1305_encrypt and metamui_chacha20poly1305_decrypt.

#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);
}

Built as doc_example_chacha20poly1305 and linked against metamui-chacha20poly1305.

AES-256-GCM

SP 800-38D AEAD with the same shape as ChaCha20-Poly1305; an incremental interface (metamui_aes256gcm_init, metamui_aes256gcm_aad, metamui_aes256gcm_encrypt_update, metamui_aes256gcm_decrypt_update, metamui_aes256gcm_final) exists for long messages.

#include <metamui/aes256gcm.h>

/* AES-256-GCM, one shot. Same contract as ChaCha20-Poly1305: a 32-byte key, a
 * 96-bit nonce that is never reused under one key, a 16-byte tag. */
static int gcm_seal(uint8_t *ciphertext, uint8_t tag[METAMUI_AES256GCM_TAG_SIZE],
                    const uint8_t *plaintext, size_t plaintext_len,
                    const uint8_t *aad, size_t aad_len,
                    const uint8_t nonce[METAMUI_AES256GCM_NONCE_SIZE],
                    const uint8_t key[METAMUI_AES256GCM_KEY_SIZE]) {
    return metamui_aes256gcm_encrypt(ciphertext, tag, plaintext, plaintext_len, aad, aad_len, nonce, key);
}

static int gcm_open(uint8_t *plaintext, const uint8_t *ciphertext, size_t ciphertext_len,
                    const uint8_t tag[METAMUI_AES256GCM_TAG_SIZE],
                    const uint8_t *aad, size_t aad_len,
                    const uint8_t nonce[METAMUI_AES256GCM_NONCE_SIZE],
                    const uint8_t key[METAMUI_AES256GCM_KEY_SIZE]) {
    return metamui_aes256gcm_decrypt(plaintext, ciphertext, ciphertext_len, tag, aad, aad_len, nonce, key);
}

Built as doc_example_aes256gcm and linked against metamui-aes256gcm.

Ed25519

RFC 8032 signatures. ed25519_verify follows RFC 8032 as written; ed25519_verify_zip215 applies ZIP-215. No metamui_ prefix on this header.

#include <metamui/ed25519.h>

/* Keys and signatures are fixed-size structs; every call reports an
 * ed25519_error_t and ED25519_SUCCESS (0) means "done" or, for verify, "valid". */
static int ed25519_sign_message(ed25519_signature_t *sig, const ed25519_keypair_t *kp,
                                const uint8_t *msg, size_t msg_len) {
    return ed25519_sign(sig, msg, msg_len, kp->secret_key) == ED25519_SUCCESS ? 0 : -1;
}

static int ed25519_check(const ed25519_signature_t *sig, const uint8_t public_key[ED25519_PUBLIC_KEY_LENGTH],
                         const uint8_t *msg, size_t msg_len) {
    /* RFC 8032 as written: canonical encodings, S < L, the cofactored equation. */
    return ed25519_verify(sig, msg, msg_len, public_key) == ED25519_SUCCESS ? 0 : -1;
}

Built as doc_example_ed25519 and linked against metamui-ed25519-static.

X25519

RFC 7748 Diffie–Hellman. x25519_keypair_generate, x25519_public_from_secret, x25519_diffie_hellman; the all-zero shared secret is rejected as X25519_ERROR_ALL_ZERO_OUTPUT.

#include <metamui/x25519.h>

/* Each side generates a keypair, exchanges public keys, and derives the same
 * 32-byte shared secret. Feed it to a KDF (see HKDF) before using it as a key. */
static int x25519_agree(uint8_t shared[X25519_SHARED_SECRET_LENGTH],
                        const x25519_keypair_t *ours, const x25519_public_key_t *theirs) {
    return x25519_diffie_hellman(shared, &ours->secret_key, theirs) == X25519_SUCCESS ? 0 : -1;
}

Built as doc_example_x25519 and linked against metamui-x25519-module.

HKDF

RFC 5869 over SHA-256, SHA-384 or SHA-512 (HKDF_SHA256, HKDF_SHA384, HKDF_SHA512); hkdf_extract and hkdf_expand are available separately.

#include <metamui/hkdf.h>

/* HKDF (RFC 5869) in one call: extract with `salt`, expand with `info`, write
 * `okm_len` bytes. HKDF_SHA256 output is limited to 255 * 32 bytes. */
static int derive_key(uint8_t *okm, size_t okm_len,
                      const uint8_t *ikm, size_t ikm_len,
                      const uint8_t *salt, size_t salt_len,
                      const uint8_t *info, size_t info_len) {
    return hkdf(HKDF_SHA256, salt, salt_len, ikm, ikm_len, info, info_len, okm, okm_len) == HKDF_SUCCESS ? 0 : -1;
}

Built as doc_example_hkdf and linked against metamui-hkdf.

ML-KEM-768

FIPS 203 key encapsulation; the metamui_mlkem768_* functions take fixed-size buffers sized by the MLKEM768_*BYTES constants. ML-KEM-512 and ML-KEM-1024 have the same three functions.

#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;
}

Built as doc_example_mlkem768 and linked against metamui-mlkem.

ML-DSA-65

FIPS 204 signatures through the *_simple entry points; the 44 and 87 parameter sets follow the same shape. Signing is hedged by default; metamui_mldsa65_sign_deterministic_simple takes a context and gives reproducible bytes.

#include <metamui/mldsa.h>

/* ML-DSA-65 (FIPS 204), the *_simple entry points: keys in fixed-size buffers,
 * a signature of at most MLDSA65_SIGNATUREBYTES whose real length comes back
 * through signature_len. METAMUI_PQC_OK means success, and for verify, valid.
 * Signing is hedged (fresh randomness, so two signatures of one message differ);
 * metamui_mldsa65_sign_deterministic_simple is the reproducible variant. */
static int dsa_keygen(uint8_t pk[MLDSA65_PUBLICKEYBYTES], uint8_t sk[MLDSA65_SECRETKEYBYTES]) {
    return metamui_mldsa65_keypair_simple(pk, sk) == METAMUI_PQC_OK ? 0 : -1;
}

static int dsa_sign(uint8_t sig[MLDSA65_SIGNATUREBYTES], size_t *sig_len,
                    const uint8_t *msg, size_t msg_len, const uint8_t sk[MLDSA65_SECRETKEYBYTES]) {
    *sig_len = MLDSA65_SIGNATUREBYTES;
    return metamui_mldsa65_sign_simple(sig, sig_len, msg, msg_len, sk) == METAMUI_PQC_OK ? 0 : -1;
}

static int dsa_verify(const uint8_t *sig, size_t sig_len,
                      const uint8_t *msg, size_t msg_len, const uint8_t pk[MLDSA65_PUBLICKEYBYTES]) {
    return metamui_mldsa65_verify_simple(sig, sig_len, msg, msg_len, pk) == METAMUI_PQC_OK ? 0 : -1;
}

/* A context string (at most 255 bytes) is bound into the signature: it verifies
 * under the same context only. The calls above use the empty context. */
static int dsa_sign_ctx(uint8_t sig[MLDSA65_SIGNATUREBYTES], size_t *sig_len,
                        const uint8_t *msg, size_t msg_len, const uint8_t *ctx, size_t ctx_len,
                        const uint8_t sk[MLDSA65_SECRETKEYBYTES]) {
    return metamui_mldsa65_sign_hedged_simple(sig, sig_len, msg, msg_len, ctx, ctx_len, sk) == METAMUI_PQC_OK ? 0 : -1;
}

static int dsa_verify_ctx(const uint8_t *sig, size_t sig_len, const uint8_t *msg, size_t msg_len,
                          const uint8_t *ctx, size_t ctx_len, const uint8_t pk[MLDSA65_PUBLICKEYBYTES]) {
    return metamui_mldsa65_verify_ctx_simple(sig, sig_len, msg, msg_len, ctx, ctx_len, pk) == METAMUI_PQC_OK ? 0 : -1;
}

Built as doc_example_mldsa65 and linked against metamui-mldsa.

Falcon-512

Falcon-512 with the Round-3 compressed encoding; metamui_falcon512_sign_padded_simple writes the fixed 666-byte padded form.

#include <metamui/falcon512.h>

/* Falcon-512 (Round 3) with the compressed encoding: signatures vary in
 * length up to FALCON512_SIGNATURE_MAX_SIZE, so pass a buffer of that size and
 * read the real length back. Key generation runs a deep NTRU solve; expect it
 * to take longer than the other schemes on this page. */
static int falcon_keygen(uint8_t pk[FALCON512_PUBLICKEYBYTES], uint8_t sk[FALCON512_SECRETKEYBYTES]) {
    return metamui_falcon512_keypair_simple(pk, sk) == METAMUI_PQC_OK ? 0 : -1;
}

static int falcon_sign(uint8_t sig[FALCON512_SIGNATURE_MAX_SIZE], size_t *sig_len,
                       const uint8_t *msg, size_t msg_len, const uint8_t sk[FALCON512_SECRETKEYBYTES]) {
    *sig_len = FALCON512_SIGNATURE_MAX_SIZE;
    return metamui_falcon512_sign_simple(sig, sig_len, msg, msg_len, sk) == METAMUI_PQC_OK ? 0 : -1;
}

static int falcon_verify(const uint8_t *sig, size_t sig_len,
                         const uint8_t *msg, size_t msg_len, const uint8_t pk[FALCON512_PUBLICKEYBYTES]) {
    return metamui_falcon512_verify_simple(sig, sig_len, msg, msg_len, pk) == METAMUI_PQC_OK ? 0 : -1;
}

Built as doc_example_falcon512 and linked against falcon512.

What is not on this page

Modules the tree builds but this page does not yet document with a compiled example: SHA-512, SHA-3/SHAKE, BLAKE2b/BLAKE2s, HMAC, PBKDF2, Argon2, AES-CTR-DRBG, Sr25519, Falcon-1024, SLH-DSA, the KpqC schemes (SMAUG-T, HAETAE, AIMer, NTRU+), HQC, Classic McEliece, FrodoKEM, LMS/HSS and XMSS. Their headers are under the module directories and their ctest gates in metamui-crypto-c/CMakeLists.txt. Anything this page used to describe that is not listed above — hardware feature probes, memory pools, a secure mode, hex and base64 helpers, a library-wide init call — was never part of the library.

See also