crypto/fipsmodule/ml_kem/mlkem/poly.h (110 lines of code) (raw):
/*
* Copyright (c) 2024-2025 The mlkem-native project authors
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef MLK_POLY_H
#define MLK_POLY_H
#include <stddef.h>
#include <stdint.h>
#include "cbmc.h"
#include "common.h"
#include "debug.h"
#include "verify.h"
/* Absolute exclusive upper bound for the output of the inverse NTT */
#define MLK_INVNTT_BOUND (8 * MLKEM_Q)
/* Absolute exclusive upper bound for the output of the forward NTT */
#define MLK_NTT_BOUND (8 * MLKEM_Q)
/*
* Elements of R_q = Z_q[X]/(X^n + 1). Represents polynomial
* coeffs[0] + X*coeffs[1] + X^2*coeffs[2] + ... + X^{n-1}*coeffs[n-1]
*/
typedef struct
{
int16_t coeffs[MLKEM_N];
} MLK_ALIGN mlk_poly;
/*
* INTERNAL presentation of precomputed data speeding up
* the base multiplication of two polynomials in NTT domain.
*/
typedef struct
{
int16_t coeffs[MLKEM_N >> 1];
} mlk_poly_mulcache;
/*************************************************
* Name: mlk_cast_uint16_to_int16
*
* Description: Cast uint16 value to int16
*
* Returns:
* input x in 0 .. 32767: returns value unchanged
* input x in 32768 .. 65535: returns (x - 65536)
**************************************************/
#ifdef CBMC
#pragma CPROVER check push
#pragma CPROVER check disable "conversion"
#endif
static MLK_ALWAYS_INLINE int16_t mlk_cast_uint16_to_int16(uint16_t x)
{
/*
* PORTABILITY: This relies on uint16_t -> int16_t
* being implemented as the inverse of int16_t -> uint16_t,
* which is implementation-defined (C99 6.3.1.3 (3))
* CBMC (correctly) fails to prove this conversion is OK,
* so we have to suppress that check here
*/
return (int16_t)x;
}
#ifdef CBMC
#pragma CPROVER check pop
#endif
/*************************************************
* Name: mlk_montgomery_reduce
*
* Description: Generic Montgomery reduction; given a 32-bit integer a, computes
* 16-bit integer congruent to a * R^-1 mod q, where R=2^16
*
* Arguments: - int32_t a: input integer to be reduced, of absolute value
* smaller or equal to INT32_MAX - 2^15 * MLKEM_Q.
*
* Returns: integer congruent to a * R^-1 modulo q, with absolute value
* <= ceil(|a| / 2^16) + (MLKEM_Q + 1)/2
*
**************************************************/
static MLK_ALWAYS_INLINE int16_t mlk_montgomery_reduce(int32_t a)
__contract__(
requires(a < +(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)) &&
a > -(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)))
/* We don't attempt to express an input-dependent output bound
* as the post-condition here. There are two call-sites for this
* function:
* - The base multiplication: Here, we need no output bound.
* - mlk_fqmul: Here, we inline this function and prove another spec
* for mlk_fqmul which does have a post-condition bound. */
)
{
/* check-magic: 62209 == unsigned_mod(pow(MLKEM_Q, -1, 2^16), 2^16) */
const uint32_t QINV = 62209;
/* Compute a*q^{-1} mod 2^16 in unsigned representatives */
const uint16_t a_reduced = a & UINT16_MAX;
const uint16_t a_inverted = (a_reduced * QINV) & UINT16_MAX;
/* Lift to signed canonical representative mod 2^16. */
const int16_t t = mlk_cast_uint16_to_int16(a_inverted);
int32_t r;
mlk_assert(a < +(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)) &&
a > -(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)));
r = a - ((int32_t)t * MLKEM_Q);
/*
* PORTABILITY: Right-shift on a signed integer is, strictly-speaking,
* implementation-defined for negative left argument. Here,
* we assume it's sign-preserving "arithmetic" shift right. (C99 6.5.7 (5))
*/
r = r >> 16;
/* Bounds: |r >> 16| <= ceil(|r| / 2^16)
* <= ceil(|a| / 2^16 + MLKEM_Q / 2)
* <= ceil(|a| / 2^16) + (MLKEM_Q + 1) / 2
*
* (Note that |a >> n| = ceil(|a| / 2^16) for negative a)
*/
return (int16_t)r;
}
#define mlk_poly_tomont MLK_NAMESPACE(poly_tomont)
/*************************************************
* Name: mlk_poly_tomont
*
* Description: Inplace conversion of all coefficients of a polynomial
* from normal domain to Montgomery domain
*
* Bounds: Output < q in absolute value.
*
* Arguments: - mlk_poly *r: pointer to input/output polynomial
*
* Specification: Internal normalization required in `mlk_indcpa_keypair_derand`
* as part of matrix-vector multiplication
* [FIPS 203, Algorithm 13, K-PKE.KeyGen, L18].
*
**************************************************/
MLK_INTERNAL_API
void mlk_poly_tomont(mlk_poly *r)
__contract__(
requires(memory_no_alias(r, sizeof(mlk_poly)))
assigns(memory_slice(r, sizeof(mlk_poly)))
ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_Q))
);
#define mlk_poly_mulcache_compute MLK_NAMESPACE(poly_mulcache_compute)
/************************************************************
* Name: mlk_poly_mulcache_compute
*
* Description: Computes the mulcache for a polynomial in NTT domain
*
* The mulcache of a degree-2 polynomial b := b0 + b1*X
* in Fq[X]/(X^2-zeta) is the value b1*zeta, needed when
* computing products of b in Fq[X]/(X^2-zeta).
*
* The mulcache of a polynomial in NTT domain -- which is
* a 128-tuple of degree-2 polynomials in Fq[X]/(X^2-zeta),
* for varying zeta, is the 128-tuple of mulcaches of those
* polynomials.
*
* Arguments: - x: Pointer to mulcache to be populated
* - a: Pointer to input polynomial
*
* Specification:
* - Caches `b_1 * \gamma` in [FIPS 203, Algorithm 12, BaseCaseMultiply, L1]
*
************************************************************/
/*
* NOTE: The default C implementation of this function populates
* the mulcache with values in (-q,q), but this is not needed for the
* higher level safety proofs, and thus not part of the spec.
*/
MLK_INTERNAL_API
void mlk_poly_mulcache_compute(mlk_poly_mulcache *x, const mlk_poly *a)
__contract__(
requires(memory_no_alias(x, sizeof(mlk_poly_mulcache)))
requires(memory_no_alias(a, sizeof(mlk_poly)))
assigns(object_whole(x))
);
#define mlk_poly_reduce MLK_NAMESPACE(poly_reduce)
/*************************************************
* Name: mlk_poly_reduce
*
* Description: Converts polynomial to _unsigned canonical_ representatives.
*
* The input coefficients can be arbitrary integers in int16_t.
* The output coefficients are in [0,1,...,MLKEM_Q-1].
*
* Arguments: - mlk_poly *r: pointer to input/output polynomial
*
* Specification: Normalizes on unsigned canoncial representatives
* ahead of calling [FIPS 203, Compress_d, Eq (4.7)].
* This is not made explicit in FIPS 203.
*
**************************************************/
/*
* NOTE: The semantics of mlk_poly_reduce() is different in
* the reference implementation, which requires
* signed canonical output data. Unsigned canonical
* outputs are better suited to the only remaining
* use of mlk_poly_reduce() in the context of (de)serialization.
*/
MLK_INTERNAL_API
void mlk_poly_reduce(mlk_poly *r)
__contract__(
requires(memory_no_alias(r, sizeof(mlk_poly)))
assigns(memory_slice(r, sizeof(mlk_poly)))
ensures(array_bound(r->coeffs, 0, MLKEM_N, 0, MLKEM_Q))
);
#define mlk_poly_add MLK_NAMESPACE(poly_add)
/************************************************************
* Name: mlk_poly_add
*
* Description: Adds two polynomials in place
*
* Arguments: - r: Pointer to input-output polynomial to be added to.
* - b: Pointer to input polynomial that should be added
* to r. Must be disjoint from r.
*
* The coefficients of r and b must be so that the addition does
* not overflow. Otherwise, the behaviour of this function is undefined.
*
* Specification:
* - [FIPS 203, 2.4.5, Arithmetic With Polynomials and NTT Representations]
* - Used in [FIPS 203, Algorithm 14 (K-PKE.Encrypt), L21]
*
************************************************************/
/*
* NOTE: The reference implementation uses a 3-argument mlk_poly_add.
* We specialize to the accumulator form to avoid reasoning about aliasing.
*/
MLK_INTERNAL_API
void mlk_poly_add(mlk_poly *r, const mlk_poly *b)
__contract__(
requires(memory_no_alias(r, sizeof(mlk_poly)))
requires(memory_no_alias(b, sizeof(mlk_poly)))
requires(forall(k0, 0, MLKEM_N, (int32_t) r->coeffs[k0] + b->coeffs[k0] <= INT16_MAX))
requires(forall(k1, 0, MLKEM_N, (int32_t) r->coeffs[k1] + b->coeffs[k1] >= INT16_MIN))
ensures(forall(k, 0, MLKEM_N, r->coeffs[k] == old(*r).coeffs[k] + b->coeffs[k]))
assigns(memory_slice(r, sizeof(mlk_poly)))
);
#define mlk_poly_sub MLK_NAMESPACE(poly_sub)
/*************************************************
* Name: mlk_poly_sub
*
* Description: Subtract two polynomials; no modular reduction is performed
*
* Arguments: - mlk_poly *r: Pointer to input-output polynomial to be added to.
* - const mlk_poly *b: Pointer to second input polynomial
*
* Specification:
* - [FIPS 203, 2.4.5, Arithmetic With Polynomials and NTT Representations]
* - Used in [FIPS 203, Algorithm 15, K-PKE.Decrypt, L6]
*
**************************************************/
/*
* NOTE: The reference implementation uses a 3-argument mlk_poly_sub.
* We specialize to the accumulator form to avoid reasoning about aliasing.
*/
MLK_INTERNAL_API
void mlk_poly_sub(mlk_poly *r, const mlk_poly *b)
__contract__(
requires(memory_no_alias(r, sizeof(mlk_poly)))
requires(memory_no_alias(b, sizeof(mlk_poly)))
requires(forall(k0, 0, MLKEM_N, (int32_t) r->coeffs[k0] - b->coeffs[k0] <= INT16_MAX))
requires(forall(k1, 0, MLKEM_N, (int32_t) r->coeffs[k1] - b->coeffs[k1] >= INT16_MIN))
ensures(forall(k, 0, MLKEM_N, r->coeffs[k] == old(*r).coeffs[k] - b->coeffs[k]))
assigns(object_whole(r))
);
#define mlk_poly_ntt MLK_NAMESPACE(poly_ntt)
/*************************************************
* Name: mlk_poly_ntt
*
* Description: Computes negacyclic number-theoretic transform (NTT) of
* a polynomial in place.
*
* The input is assumed to be in normal order and
* coefficient-wise bound by MLKEM_Q in absolute value.
*
* The output polynomial is in bitreversed order, and
* coefficient-wise bound by MLK_NTT_BOUND in absolute value.
*
* (NOTE: Sometimes the input to the NTT is actually smaller,
* which gives better bounds.)
*
* Arguments: - mlk_poly *p: pointer to in/output polynomial
*
* Specification: Implements [FIPS 203, Algorithm 9, NTT]
*
**************************************************/
MLK_INTERNAL_API
void mlk_poly_ntt(mlk_poly *r)
__contract__(
requires(memory_no_alias(r, sizeof(mlk_poly)))
requires(array_abs_bound(r->coeffs, 0, MLKEM_N, MLKEM_Q))
assigns(memory_slice(r, sizeof(mlk_poly)))
ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLK_NTT_BOUND))
);
#define mlk_poly_invntt_tomont MLK_NAMESPACE(poly_invntt_tomont)
/*************************************************
* Name: mlk_poly_invntt_tomont
*
* Description: Computes inverse of negacyclic number-theoretic transform (NTT)
* of a polynomial in place;
* inputs assumed to be in bitreversed order, output in normal
* order
*
* The input is assumed to be in bitreversed order, and can
* have arbitrary coefficients in int16_t.
*
* The output polynomial is in normal order, and
* coefficient-wise bound by MLK_INVNTT_BOUND in absolute value.
*
* Arguments: - uint16_t *a: pointer to in/output polynomial
*
* Specification: Implements composition of [FIPS 203, Algorithm 10, NTT^{-1}]
* and elementwise modular multiplication with a suitable
* Montgomery factor introduced during the base multiplication.
*
**************************************************/
MLK_INTERNAL_API
void mlk_poly_invntt_tomont(mlk_poly *r)
__contract__(
requires(memory_no_alias(r, sizeof(mlk_poly)))
assigns(memory_slice(r, sizeof(mlk_poly)))
ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND))
);
#endif /* !MLK_POLY_H */