Skip to content

nqenc(3)

nqenc, nqencd, nqdec, nqkkg, nqkkgd, nqksize, nqkname, nqkfind — ML-KEM and HQC key encapsulation

libnoqtis, -lnoqtis

#include <noqtis.h>

int
nqkkg(nqkalg alg, uint8_t *pk, size_t pklen, uint8_t *sk, size_t sklen);

int
nqkkgd(nqkalg alg, const uint8_t *seed, size_t seedlen, uint8_t *pk, size_t pklen, uint8_t *sk, size_t sklen);

int
nqenc(nqkalg alg, const uint8_t *pk, size_t pklen, uint8_t *ct, size_t ctlen, uint8_t *ss, size_t sslen);

int
nqencd(nqkalg alg, const uint8_t *pk, size_t pklen, const uint8_t *seed, size_t seedlen, uint8_t *ct, size_t ctlen, uint8_t *ss, size_t sslen);

int
nqdec(nqkalg alg, const uint8_t *sk, size_t sklen, const uint8_t *ct, size_t ctlen, uint8_t *ss, size_t sslen);

int
nqksize(nqkalg alg, nqksz *sz);

const char *
nqkname(nqkalg alg);

int
nqkfind(const char *name, nqkalg *alg);

A key encapsulation mechanism lets anyone with a public key make a ciphertext and a 32-byte shared secret, which only the holder of the secret key can recover from the ciphertext. noqtis provides ML-KEM (FIPS 203) and HQC (version 5.0.0 of 2025-08-22). HQC is experimental: NIST selected it in March 2025 but its standard is not final, and the final one may differ in its encoding; keys, ciphertexts and secrets made now may then not interoperate. Use ML-KEM for anything that must last.

nqkalg names the parameter set:

ML-KEM-512 800 1632 768 32
ML-KEM-768 1184 2400 1088 32
ML-KEM-1024 1568 3168 1568 32
HQC-128 2241 2321 4433 32
HQC-192 4514 4602 8978 32
HQC-256 7237 7333 14421 32

() stores the sizes in *sz, an nqksz with the members pk, sk, ct and ss. () returns the name, or NULL for an unknown value; () finds the algorithm whose name is exactly name.

() makes a key pair from the operating system's random source. nqkkgd() makes it from the standard's seed: 64 bytes (d || z) for ML-KEM, 32 bytes for HQC.

() encapsulates to the public key pk: it writes the ciphertext to ct and the shared secret to ss. () takes the encapsulation randomness from seed instead, for test vectors: the 32-byte m for ML-KEM, and m || salt for HQC (32, 40 or 48 bytes for HQC-128, -192, -256). For ML-KEM the public key is checked as FIPS 203 section 7.2 requires.

() recovers the shared secret from ct with the secret key sk; for ML-KEM the key is first checked as section 7.3 requires. A ciphertext of the right length that was not made for this key, or was changed, gives a pseudorandom secret unrelated to the sender's and returns NQOK (implicit rejection, as both standards require). It fails only on malformed sizes, on a secret key whose embedded hash does not match (NQEDEC), and, as ML-KEM decapsulation is masked with fresh randomness, when the operating system's random source fails (NQERNG). Protocols must therefore confirm the secret, for example by using it in an authenticated cipher.

Output buffers may be larger than the algorithm's sizes; only the defined number of bytes is written. Keys and ciphertexts given as input must have exactly the algorithm's sizes. The seed of () may overlap pk or sk; on any error the outputs are cleared.

Every function except nqkname() returns NQOK (0) or a negative error code:

[NQEARG]
alg is not an nqkalg, a pointer is NULL, or a seed does not have the standard's length.
[NQEBUF]
An output buffer is smaller than the algorithm's size.
[NQEDEC]
An input key or ciphertext does not have the algorithm's length, or a key fails the standard's input check.
[NQERNG]
The operating system's random source failed (nqkkg(), nqenc(), and nqdec() for ML-KEM, whose masked decapsulation draws fresh mask shares).
[NQENOSUP]
nqkfind(): no algorithm has that name.

On any failure, the output buffers are zeroed.

The program docs/examples/kem.c of the source tree, with the sizes taken at run time:

nqksz z;
uint8_t *pk, *sk, *ct, a[32], b[32];
int rc = nqksize(NQMLKEM768, &z), same = 0;
if (rc != NQOK || z.ss != sizeof a)
  return 2;
pk = malloc(z.pk);
sk = malloc(z.sk);
ct = malloc(z.ct);
if (pk == NULL || sk == NULL || ct == NULL)
  return 2;
rc = nqkkg(NQMLKEM768, pk, z.pk, sk, z.sk);
if (rc == NQOK)
  rc = nqenc(NQMLKEM768, pk, z.pk, ct, z.ct, a, sizeof a);
if (rc == NQOK)
  rc = nqdec(NQMLKEM768, sk, z.sk, ct, z.ct, b, sizeof b);
if (rc == NQOK)
  same = nqcteq(a, b, sizeof a);
nqwipe(sk, z.sk);
nqwipe(a, sizeof a);
nqwipe(b, sizeof b);

noqtis-dec(1), noqtis-enc(1), noqtis(3), nqcteq(3), nqwipe(3)

FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard, NIST, August 2024. Hamming Quasi-Cyclic (HQC), version 5.0.0, August 2025.

Key generation, encapsulation and decapsulation run in constant time with respect to the seeds, the randomness, the secret key and the shared secret; ML-KEM decapsulation is also first-order masked. In HQC, a secret-derived bit becomes public in the three places its specification allows, and nowhere else.