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
- Return codes. Hash and AEAD modules return
int:0(METAMUI_SHA256_SUCCESS) on success, a negative code otherwise. Ed25519, X25519 and HKDF return their own enums (ED25519_SUCCESS,X25519_SUCCESS,HKDF_SUCCESS, all0). The post-quantum modules returnmetamui_pqc_error_t, whereMETAMUI_PQC_OKis success. A verification that fails is a non-success return, never a crash. - Buffers are yours. Every function writes into caller-allocated memory sized by the header’s constants (
METAMUI_SHA256_DIGEST_SIZE,METAMUI_CHACHA20POLY1305_TAG_SIZE,MLKEM768_PUBLICKEYBYTES,MLDSA65_SIGNATUREBYTES,FALCON512_SIGNATURE_MAX_SIZE, …). Signatures whose length varies (Falcon) report the real length through asize_t*. - Randomness for key generation and nonces comes from the operating system; the functions that need it (
ed25519_keypair_generate,x25519_keypair_generate, the*_keypair*functions of the post-quantum modules) take no RNG argument. - AEAD nonces are 96 bits and must never repeat under one key; use a counter, or a fresh random value only when the message count per key stays small.
- Thread safety. Contexts (
metamui_sha256_ctx,metamui_blake3_ctx,metamui_aes256gcm_ctx) are not shared between threads; the one-shot functions keep no state.
Headers and link targets
| 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
- C Platform Guide
metamui-crypto-c/CMakeLists.txt— every target and everyctestgatetest-vectors/— the answer files the gates replay