crypto/fipsmodule/ml_kem/mlkem/indcpa.c (267 lines of code) (raw):
/*
* Copyright (c) 2024-2025 The mlkem-native project authors
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "cbmc.h"
#include "debug.h"
#include "indcpa.h"
#include "poly.h"
#include "poly_k.h"
#include "randombytes.h"
#include "sampling.h"
#include "symmetric.h"
/* Level namespacing
* This is to facilitate building multiple instances
* of mlkem-native (e.g. with varying security levels)
* within a single compilation unit. */
#define mlk_pack_pk MLK_ADD_LEVEL(mlk_pack_pk)
#define mlk_unpack_pk MLK_ADD_LEVEL(mlk_unpack_pk)
#define mlk_pack_sk MLK_ADD_LEVEL(mlk_pack_sk)
#define mlk_unpack_sk MLK_ADD_LEVEL(mlk_unpack_sk)
#define mlk_pack_ciphertext MLK_ADD_LEVEL(mlk_pack_ciphertext)
#define mlk_unpack_ciphertext MLK_ADD_LEVEL(mlk_unpack_ciphertext)
#define mlk_matvec_mul MLK_ADD_LEVEL(mlk_matvec_mul)
/* End of level namespacing */
/*************************************************
* Name: mlk_pack_pk
*
* Description: Serialize the public key as concatenation of the
* serialized vector of polynomials pk
* and the public seed used to generate the matrix A.
*
* Arguments: uint8_t *r: pointer to the output serialized public key
* mlk_polyvec pk: pointer to the input public-key mlk_polyvec.
* Must have coefficients within [0,..,q-1].
* const uint8_t *seed: pointer to the input public seed
*
* Specification:
* Implements [FIPS 203, Algorithm 13 (K-PKE.KeyGen), L19]
*
**************************************************/
static void mlk_pack_pk(uint8_t r[MLKEM_INDCPA_PUBLICKEYBYTES], mlk_polyvec pk,
const uint8_t seed[MLKEM_SYMBYTES])
{
mlk_assert_bound_2d(pk, MLKEM_K, MLKEM_N, 0, MLKEM_Q);
mlk_polyvec_tobytes(r, pk);
memcpy(r + MLKEM_POLYVECBYTES, seed, MLKEM_SYMBYTES);
}
/*************************************************
* Name: mlk_unpack_pk
*
* Description: De-serialize public key from a byte array;
* approximate inverse of mlk_pack_pk
*
* Arguments: - mlk_polyvec pk: pointer to output public-key polynomial
* vector Coefficients will be normalized to [0,..,q-1].
* - uint8_t *seed: pointer to output seed to generate matrix A
* - const uint8_t *packedpk: pointer to input serialized public
* key.
*
* Specification:
* Implements [FIPS 203, Algorithm 14 (K-PKE.Encrypt), L2-3]
*
**************************************************/
static void mlk_unpack_pk(mlk_polyvec pk, uint8_t seed[MLKEM_SYMBYTES],
const uint8_t packedpk[MLKEM_INDCPA_PUBLICKEYBYTES])
{
mlk_polyvec_frombytes(pk, packedpk);
memcpy(seed, packedpk + MLKEM_POLYVECBYTES, MLKEM_SYMBYTES);
/* NOTE: If a modulus check was conducted on the PK, we know at this
* point that the coefficients of `pk` are unsigned canonical. The
* specifications and proofs, however, do _not_ assume this, and instead
* work with the easily provable bound by MLKEM_UINT12_LIMIT. */
}
/*************************************************
* Name: mlk_pack_sk
*
* Description: Serialize the secret key
*
* Arguments: - uint8_t *r: pointer to output serialized secret key
* - mlk_polyvec sk: pointer to input vector of polynomials
* (secret key)
*
* Specification:
* Implements [FIPS 203, Algorithm 13 (K-PKE.KeyGen), L20]
*
**************************************************/
static void mlk_pack_sk(uint8_t r[MLKEM_INDCPA_SECRETKEYBYTES], mlk_polyvec sk)
{
mlk_assert_bound_2d(sk, MLKEM_K, MLKEM_N, 0, MLKEM_Q);
mlk_polyvec_tobytes(r, sk);
}
/*************************************************
* Name: mlk_unpack_sk
*
* Description: De-serialize the secret key; inverse of mlk_pack_sk
*
* Arguments: - mlk_polyvec sk: pointer to output vector of polynomials
* (secret key)
* - const uint8_t *packedsk: pointer to input serialized secret
* key
*
* Specification:
* Implements [FIPS 203, Algorithm 15 (K-PKE.Decrypt), L5]
*
**************************************************/
static void mlk_unpack_sk(mlk_polyvec sk,
const uint8_t packedsk[MLKEM_INDCPA_SECRETKEYBYTES])
{
mlk_polyvec_frombytes(sk, packedsk);
}
/*************************************************
* Name: mlk_pack_ciphertext
*
* Description: Serialize the ciphertext as concatenation of the
* compressed and serialized vector of polynomials b
* and the compressed and serialized polynomial v
*
* Arguments: uint8_t *r: pointer to the output serialized ciphertext
* mlk_poly *pk: pointer to the input vector of polynomials b
* mlk_poly *v: pointer to the input polynomial v
*
* Specification:
* Implements [FIPS 203, Algorithm 14 (K-PKE.Encrypt), L22-23]
*
**************************************************/
static void mlk_pack_ciphertext(uint8_t r[MLKEM_INDCPA_BYTES], mlk_polyvec b,
mlk_poly *v)
{
mlk_polyvec_compress_du(r, b);
mlk_poly_compress_dv(r + MLKEM_POLYVECCOMPRESSEDBYTES_DU, v);
}
/*************************************************
* Name: mlk_unpack_ciphertext
*
* Description: De-serialize and decompress ciphertext from a byte array;
* approximate inverse of mlk_pack_ciphertext
*
* Arguments: - mlk_polyvec b: pointer to the output vector of polynomials b
* - mlk_poly *v: pointer to the output polynomial v
* - const uint8_t *c: pointer to the input serialized ciphertext
*
* Specification:
* Implements [FIPS 203, Algorithm 15 (K-PKE.Decrypt), L1-4]
*
**************************************************/
static void mlk_unpack_ciphertext(mlk_polyvec b, mlk_poly *v,
const uint8_t c[MLKEM_INDCPA_BYTES])
{
mlk_polyvec_decompress_du(b, c);
mlk_poly_decompress_dv(v, c + MLKEM_POLYVECCOMPRESSEDBYTES_DU);
}
#if !defined(MLK_USE_NATIVE_NTT_CUSTOM_ORDER)
/* This namespacing is not done at the top to avoid a naming conflict
* with native backends, which are currently not yet namespaced. */
#define mlk_poly_permute_bitrev_to_custom \
MLK_ADD_LEVEL(mlk_poly_permute_bitrev_to_custom)
static MLK_INLINE void mlk_poly_permute_bitrev_to_custom(int16_t data[MLKEM_N])
__contract__(
/* We don't specify that this should be a permutation, but only
* that it does not change the bound established at the end of mlk_gen_matrix. */
requires(memory_no_alias(data, sizeof(int16_t) * MLKEM_N))
requires(array_bound(data, 0, MLKEM_N, 0, MLKEM_Q))
assigns(memory_slice(data, sizeof(mlk_poly)))
ensures(array_bound(data, 0, MLKEM_N, 0, MLKEM_Q))) { ((void)data); }
#endif /* !MLK_USE_NATIVE_NTT_CUSTOM_ORDER */
/* Reference: `gen_matrix()` in the reference implementation.
* - We use a special subroutine to generate 4 polynomials
* at a time, to be able to leverage batched Keccak-f1600
* implementations. The reference implementation generates
* one matrix entry a time.
*
* Not static for benchmarking */
MLK_INTERNAL_API
void mlk_gen_matrix(mlk_polymat a, const uint8_t seed[MLKEM_SYMBYTES],
int transposed)
{
unsigned i, j;
/*
* We generate four separate seed arrays rather than a single one to work
* around limitations in CBMC function contracts dealing with disjoint slices
* of the same parent object.
*/
MLK_ALIGN uint8_t seed_ext[4][MLK_ALIGN_UP(MLKEM_SYMBYTES + 2)];
for (j = 0; j < 4; j++)
{
memcpy(seed_ext[j], seed, MLKEM_SYMBYTES);
}
/* Sample 4 matrix entries a time. */
for (i = 0; i < (MLKEM_K * MLKEM_K / 4) * 4; i += 4)
{
uint8_t x, y;
for (j = 0; j < 4; j++)
{
x = (i + j) / MLKEM_K;
y = (i + j) % MLKEM_K;
if (transposed)
{
seed_ext[j][MLKEM_SYMBYTES + 0] = x;
seed_ext[j][MLKEM_SYMBYTES + 1] = y;
}
else
{
seed_ext[j][MLKEM_SYMBYTES + 0] = y;
seed_ext[j][MLKEM_SYMBYTES + 1] = x;
}
}
/*
* This call writes across mlk_polyvec boundaries for K=2 and K=3.
* This is intentional and safe.
*/
mlk_poly_rej_uniform_x4(&a[i], seed_ext);
}
/* For MLKEM_K == 3, sample the last entry individually. */
if (i < MLKEM_K * MLKEM_K)
{
uint8_t x, y;
x = i / MLKEM_K;
y = i % MLKEM_K;
if (transposed)
{
seed_ext[0][MLKEM_SYMBYTES + 0] = x;
seed_ext[0][MLKEM_SYMBYTES + 1] = y;
}
else
{
seed_ext[0][MLKEM_SYMBYTES + 0] = y;
seed_ext[0][MLKEM_SYMBYTES + 1] = x;
}
mlk_poly_rej_uniform(&a[i], seed_ext[0]);
i++;
}
mlk_assert(i == MLKEM_K * MLKEM_K);
/*
* The public matrix is generated in NTT domain. If the native backend
* uses a custom order in NTT domain, permute A accordingly.
*/
for (i = 0; i < MLKEM_K * MLKEM_K; i++)
{
mlk_poly_permute_bitrev_to_custom(a[i].coeffs);
}
/* Specification: Partially implements
* [FIPS 203, Section 3.3, Destruction of intermediate values] */
mlk_zeroize(seed_ext, sizeof(seed_ext));
}
/*************************************************
* Name: mlk_matvec_mul
*
* Description: Computes matrix-vector product in NTT domain,
* via Montgomery multiplication.
*
* Arguments: - mlk_polyvec out: Pointer to output polynomial vector
* - mlk_polymat a: Input matrix. Must be in NTT domain
* and have coefficients of absolute value < 4096.
* - mlk_polyvec v: Input polynomial vector. Must be in NTT
* domain.
* - mlk_polyvec vc: Mulcache for v, computed via
* mlk_polyvec_mulcache_compute().
*
* Specification: Implements [FIPS 203, Section 2.4.7, Eq (2.12), (2.13)]
*
**************************************************/
static void mlk_matvec_mul(mlk_polyvec out, const mlk_polymat a,
const mlk_polyvec v, const mlk_polyvec_mulcache vc)
__contract__(
requires(memory_no_alias(out, sizeof(mlk_polyvec)))
requires(memory_no_alias(a, sizeof(mlk_polymat)))
requires(memory_no_alias(v, sizeof(mlk_polyvec)))
requires(memory_no_alias(vc, sizeof(mlk_polyvec_mulcache)))
requires(forall(k0, 0, MLKEM_K * MLKEM_K,
array_bound(a[k0].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)))
assigns(object_whole(out)))
{
unsigned i;
for (i = 0; i < MLKEM_K; i++)
__loop__(
assigns(i, object_whole(out))
invariant(i <= MLKEM_K))
{
mlk_polyvec_basemul_acc_montgomery_cached(&out[i], &a[MLKEM_K * i], v, vc);
}
}
/* Reference: `indcpa_keypair_derand()` in the reference implementation.
* - We use x4-batched versions of `poly_getnoise` to leverage
* batched x4-batched Keccak-f1600.
* - We use a different implementation of `gen_matrix()` which
* uses x4-batched Keccak-f1600 (see `mlk_gen_matrix()` above).
* - We use a mulcache to speed up matrix-vector multiplication.
* - We include buffer zeroization.
*/
MLK_INTERNAL_API
void mlk_indcpa_keypair_derand(uint8_t pk[MLKEM_INDCPA_PUBLICKEYBYTES],
uint8_t sk[MLKEM_INDCPA_SECRETKEYBYTES],
const uint8_t coins[MLKEM_SYMBYTES])
{
MLK_ALIGN uint8_t buf[2 * MLKEM_SYMBYTES];
const uint8_t *publicseed = buf;
const uint8_t *noiseseed = buf + MLKEM_SYMBYTES;
mlk_polymat a;
mlk_polyvec e, pkpv, skpv;
mlk_polyvec_mulcache skpv_cache;
MLK_ALIGN uint8_t coins_with_domain_separator[MLKEM_SYMBYTES + 1];
/* Concatenate coins with MLKEM_K for domain separation of security levels */
memcpy(coins_with_domain_separator, coins, MLKEM_SYMBYTES);
coins_with_domain_separator[MLKEM_SYMBYTES] = MLKEM_K;
mlk_hash_g(buf, coins_with_domain_separator, MLKEM_SYMBYTES + 1);
/*
* Declassify the public seed.
* Required to use it in conditional-branches in rejection sampling.
* This is needed because all output of randombytes is marked as secret
* (=undefined)
*/
MLK_CT_TESTING_DECLASSIFY(publicseed, MLKEM_SYMBYTES);
mlk_gen_matrix(a, publicseed, 0 /* no transpose */);
#if MLKEM_K == 2
mlk_poly_getnoise_eta1_4x(&skpv[0], &skpv[1], &e[0], &e[1], noiseseed, 0, 1,
2, 3);
#elif MLKEM_K == 3
/*
* Only the first three output buffers are needed.
* The laster parameter is a dummy that's overwritten later.
*/
mlk_poly_getnoise_eta1_4x(&skpv[0], &skpv[1], &skpv[2],
&pkpv[0] /* irrelevant */, noiseseed, 0, 1, 2,
0xFF /* irrelevant */);
/* Same here */
mlk_poly_getnoise_eta1_4x(&e[0], &e[1], &e[2], &pkpv[0] /* irrelevant */,
noiseseed, 3, 4, 5, 0xFF /* irrelevant */);
#elif MLKEM_K == 4
mlk_poly_getnoise_eta1_4x(&skpv[0], &skpv[1], &skpv[2], &skpv[3], noiseseed,
0, 1, 2, 3);
mlk_poly_getnoise_eta1_4x(&e[0], &e[1], &e[2], &e[3], noiseseed, 4, 5, 6, 7);
#endif
mlk_polyvec_ntt(skpv);
mlk_polyvec_ntt(e);
mlk_polyvec_mulcache_compute(skpv_cache, skpv);
mlk_matvec_mul(pkpv, a, skpv, skpv_cache);
mlk_polyvec_tomont(pkpv);
mlk_polyvec_add(pkpv, e);
mlk_polyvec_reduce(pkpv);
mlk_polyvec_reduce(skpv);
mlk_pack_sk(sk, skpv);
mlk_pack_pk(pk, pkpv, publicseed);
/* Specification: Partially implements
* [FIPS 203, Section 3.3, Destruction of intermediate values] */
mlk_zeroize(buf, sizeof(buf));
mlk_zeroize(coins_with_domain_separator, sizeof(coins_with_domain_separator));
mlk_zeroize(a, sizeof(a));
mlk_zeroize(&e, sizeof(e));
mlk_zeroize(&skpv, sizeof(skpv));
mlk_zeroize(&skpv_cache, sizeof(skpv_cache));
}
/* Reference: `indcpa_enc()` in the reference implementation.
* - We use x4-batched versions of `poly_getnoise` to leverage
* batched x4-batched Keccak-f1600.
* - We use a different implementation of `gen_matrix()` which
* uses x4-batched Keccak-f1600 (see `mlk_gen_matrix()` above).
* - We use a mulcache to speed up matrix-vector multiplication.
* - We include buffer zeroization.
*/
MLK_INTERNAL_API
void mlk_indcpa_enc(uint8_t c[MLKEM_INDCPA_BYTES],
const uint8_t m[MLKEM_INDCPA_MSGBYTES],
const uint8_t pk[MLKEM_INDCPA_PUBLICKEYBYTES],
const uint8_t coins[MLKEM_SYMBYTES])
{
MLK_ALIGN uint8_t seed[MLKEM_SYMBYTES];
mlk_polymat at;
mlk_polyvec sp, pkpv, ep, b;
mlk_poly v, k, epp;
mlk_polyvec_mulcache sp_cache;
mlk_unpack_pk(pkpv, seed, pk);
mlk_poly_frommsg(&k, m);
/*
* Declassify the public seed.
* Required to use it in conditional-branches in rejection sampling.
* This is needed because in re-encryption the publicseed originated from sk
* which is marked undefined.
*/
MLK_CT_TESTING_DECLASSIFY(seed, MLKEM_SYMBYTES);
mlk_gen_matrix(at, seed, 1 /* transpose */);
#if MLKEM_K == 2
mlk_poly_getnoise_eta1122_4x(&sp[0], &sp[1], &ep[0], &ep[1], coins, 0, 1, 2,
3);
mlk_poly_getnoise_eta2(&epp, coins, 4);
#elif MLKEM_K == 3
/*
* In this call, only the first three output buffers are needed.
* The last parameter is a dummy that's overwritten later.
*/
mlk_poly_getnoise_eta1_4x(&sp[0], &sp[1], &sp[2], &b[0], coins, 0, 1, 2,
0xFF);
/* The fourth output buffer in this call _is_ used. */
mlk_poly_getnoise_eta2_4x(&ep[0], &ep[1], &ep[2], &epp, coins, 3, 4, 5, 6);
#elif MLKEM_K == 4
mlk_poly_getnoise_eta1_4x(&sp[0], &sp[1], &sp[2], &sp[3], coins, 0, 1, 2, 3);
mlk_poly_getnoise_eta2_4x(&ep[0], &ep[1], &ep[2], &ep[3], coins, 4, 5, 6, 7);
mlk_poly_getnoise_eta2(&epp, coins, 8);
#endif
mlk_polyvec_ntt(sp);
mlk_polyvec_mulcache_compute(sp_cache, sp);
mlk_matvec_mul(b, at, sp, sp_cache);
mlk_polyvec_basemul_acc_montgomery_cached(&v, pkpv, sp, sp_cache);
mlk_polyvec_invntt_tomont(b);
mlk_poly_invntt_tomont(&v);
mlk_polyvec_add(b, ep);
mlk_poly_add(&v, &epp);
mlk_poly_add(&v, &k);
mlk_polyvec_reduce(b);
mlk_poly_reduce(&v);
mlk_pack_ciphertext(c, b, &v);
/* Specification: Partially implements
* [FIPS 203, Section 3.3, Destruction of intermediate values] */
mlk_zeroize(seed, sizeof(seed));
mlk_zeroize(&sp, sizeof(sp));
mlk_zeroize(&sp_cache, sizeof(sp_cache));
mlk_zeroize(&b, sizeof(b));
mlk_zeroize(&v, sizeof(v));
mlk_zeroize(at, sizeof(at));
mlk_zeroize(&k, sizeof(k));
mlk_zeroize(&ep, sizeof(ep));
mlk_zeroize(&epp, sizeof(epp));
}
/* Reference: `indcpa_dec()` in the reference implementation.
* - We use a mulcache for the scalar product.
* - We include buffer zeroization. */
MLK_INTERNAL_API
void mlk_indcpa_dec(uint8_t m[MLKEM_INDCPA_MSGBYTES],
const uint8_t c[MLKEM_INDCPA_BYTES],
const uint8_t sk[MLKEM_INDCPA_SECRETKEYBYTES])
{
mlk_polyvec b, skpv;
mlk_poly v, sb;
mlk_polyvec_mulcache b_cache;
mlk_unpack_ciphertext(b, &v, c);
mlk_unpack_sk(skpv, sk);
mlk_polyvec_ntt(b);
mlk_polyvec_mulcache_compute(b_cache, b);
mlk_polyvec_basemul_acc_montgomery_cached(&sb, skpv, b, b_cache);
mlk_poly_invntt_tomont(&sb);
mlk_poly_sub(&v, &sb);
mlk_poly_reduce(&v);
mlk_poly_tomsg(m, &v);
/* Specification: Partially implements
* [FIPS 203, Section 3.3, Destruction of intermediate values] */
mlk_zeroize(&skpv, sizeof(skpv));
mlk_zeroize(&b, sizeof(b));
mlk_zeroize(&b_cache, sizeof(b_cache));
mlk_zeroize(&v, sizeof(v));
mlk_zeroize(&sb, sizeof(sb));
}
/* To facilitate single-compilation-unit (SCU) builds, undefine all macros.
* Don't modify by hand -- this is auto-generated by scripts/autogen. */
#undef mlk_pack_pk
#undef mlk_unpack_pk
#undef mlk_pack_sk
#undef mlk_unpack_sk
#undef mlk_pack_ciphertext
#undef mlk_unpack_ciphertext
#undef mlk_matvec_mul
#undef mlk_poly_permute_bitrev_to_custom