Skip to content

Commit 16c32f2

Browse files
hanno-beckerrod-chapman
authored andcommitted
CBMC: Refine bounds for input and output of base multiplication
Previously, the base multiplication would assume that one of its inputs is bound by 4096 in absolute value, but make no assumptions about the other input ("b-input" henceforth) and its mulcache. This commit refines the bounds slightly, as follows: - The b-input is assumed to be bound by MLK_NTT_BOUND in absolute value. This comes for free since all values for b _are_ results of the NTT. - The b-cache-input is assumed to be bound by MLKEM_Q in absolute value. With those additional bounds in place, it can be showed that the result of the base multiplication is below INT16_MAX/2 in absolute value. Accordingly, this can be added as a precondition for the inverse NTT. Those refined bounds can help in subsequent commits to improve the reduction strategy inside the inverse NTT. For the native AVX2 backend, the new output bound for the mulcache forces an explicit zeroization of the mulcache. This is not ideal since the cache is in fact entirely unused, but the performance penalty should be marginal (if the compiler can't eliminate the zeroization in the first place). Signed-off-by: Hanno Becker <[email protected]>
1 parent 97bfbfa commit 16c32f2

File tree

5 files changed

+56
-34
lines changed

5 files changed

+56
-34
lines changed

mlkem/indcpa.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,11 @@ void mlk_gen_matrix(mlk_polymat a, const uint8_t seed[MLKEM_SYMBYTES],
294294
* - mlk_polymat a: Input matrix. Must be in NTT domain
295295
* and have coefficients of absolute value < 4096.
296296
* - mlk_polyvec v: Input polynomial vector. Must be in NTT
297-
* domain.
297+
* domain and have coefficients of absolute value
298+
* < MLK_NTT_BOUND.
298299
* - mlk_polyvec vc: Mulcache for v, computed via
299-
* mlk_polyvec_mulcache_compute().
300+
* mlk_polyvec_mulcache_compute(). Must have coefficients
301+
* of absolute value < MLKEM_Q.
300302
*
301303
* Specification: Implements @[FIPS203, Section 2.4.7, Eq (2.12), (2.13)]
302304
*
@@ -309,14 +311,22 @@ __contract__(
309311
requires(memory_no_alias(v, sizeof(mlk_polyvec)))
310312
requires(memory_no_alias(vc, sizeof(mlk_polyvec_mulcache)))
311313
requires(forall(k0, 0, MLKEM_K * MLKEM_K,
312-
array_bound(a[k0].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)))
313-
assigns(object_whole(out)))
314+
array_bound(a[k0].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)))
315+
requires(forall(k1, 0, MLKEM_K,
316+
array_abs_bound(v[k1].coeffs, 0, MLKEM_N, MLK_NTT_BOUND)))
317+
requires(forall(k2, 0, MLKEM_K,
318+
array_abs_bound(vc[k2].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
319+
assigns(memory_slice(out, sizeof(mlk_polyvec)))
320+
ensures(forall(k3, 0, MLKEM_K,
321+
array_abs_bound(out[k3].coeffs, 0, MLKEM_N, INT16_MAX/2))))
314322
{
315323
unsigned i;
316324
for (i = 0; i < MLKEM_K; i++)
317325
__loop__(
318-
assigns(i, object_whole(out))
319-
invariant(i <= MLKEM_K))
326+
assigns(i, memory_slice(out, sizeof(mlk_polyvec)))
327+
invariant(i <= MLKEM_K)
328+
invariant(forall(k, 0, i,
329+
array_abs_bound(out[k].coeffs, 0, MLKEM_N, INT16_MAX/2))))
320330
{
321331
mlk_polyvec_basemul_acc_montgomery_cached(&out[i], &a[MLKEM_K * i], v, vc);
322332
}

mlkem/native/api.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ __contract__(
143143
static MLK_INLINE void mlk_intt_native(int16_t p[MLKEM_N])
144144
__contract__(
145145
requires(memory_no_alias(p, sizeof(int16_t) * MLKEM_N))
146+
requires(array_abs_bound(p, 0, MLKEM_N, INT16_MAX/2))
146147
assigns(memory_slice(p, sizeof(int16_t) * MLKEM_N))
147148
ensures(array_abs_bound(p, 0, MLKEM_N, MLK_INVNTT_BOUND))
148149
);
@@ -243,6 +244,7 @@ __contract__(
243244
requires(memory_no_alias(b_cache, sizeof(int16_t) * 2 * (MLKEM_N / 2)))
244245
requires(array_bound(a, 0, 2 * MLKEM_N, 0, MLKEM_UINT12_LIMIT))
245246
assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N))
247+
ensures(array_abs_bound(r, 0, MLKEM_N, INT16_MAX/2))
246248
);
247249
#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_K == 2 */
248250

@@ -276,6 +278,7 @@ __contract__(
276278
requires(memory_no_alias(b_cache, sizeof(int16_t) * 3 * (MLKEM_N / 2)))
277279
requires(array_bound(a, 0, 3 * MLKEM_N, 0, MLKEM_UINT12_LIMIT))
278280
assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N))
281+
ensures(array_abs_bound(r, 0, MLKEM_N, INT16_MAX/2))
279282
);
280283
#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_K == 3 */
281284

@@ -309,6 +312,7 @@ __contract__(
309312
requires(memory_no_alias(b_cache, sizeof(int16_t) * 4 * (MLKEM_N / 2)))
310313
requires(array_bound(a, 0, 4 * MLKEM_N, 0, MLKEM_UINT12_LIMIT))
311314
assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N))
315+
ensures(array_abs_bound(r, 0, MLKEM_N, INT16_MAX/2))
312316
);
313317
#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_K == 4 */
314318
#endif /* MLK_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED */

mlkem/poly.h

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,16 @@ static MLK_ALWAYS_INLINE int16_t mlk_cast_uint16_to_int16(uint16_t x)
8989
**************************************************/
9090
static MLK_ALWAYS_INLINE int16_t mlk_montgomery_reduce(int32_t a)
9191
__contract__(
92-
requires(a < +(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)) &&
93-
a > -(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)))
94-
/* We don't attempt to express an input-dependent output bound
95-
* as the post-condition here. There are two call-sites for this
96-
* function:
97-
* - The base multiplication: Here, we need no output bound.
98-
* - mlk_fqmul: Here, we inline this function and prove another spec
99-
* for mlk_fqmul which does have a post-condition bound. */
92+
/* This specification is only relevant for Montgomery reduction
93+
* during base multiplication, and the input bound is tailored to that.
94+
* The output bound, albeit weak, allows one addition/subtraction prior
95+
* to risk of overflow; this can be useful for the inverse NTT, for example.
96+
*
97+
* For the use of montgomery_reduce in fqmul, we inline this
98+
* function instead of calling it by contract. */
99+
requires(a <= +(4 * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND) &&
100+
a >= -(4 * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND))
101+
ensures(return_value < (INT16_MAX / 2) && return_value > -(INT16_MAX / 2))
100102
)
101103
{
102104
/* check-magic: 62209 == unsigned_mod(pow(MLKEM_Q, -1, 2^16), 2^16) */
@@ -177,17 +179,13 @@ __contract__(
177179
* - Caches `b_1 * \gamma` in @[FIPS203, Algorithm 12, BaseCaseMultiply, L1]
178180
*
179181
************************************************************/
180-
/*
181-
* NOTE: The default C implementation of this function populates
182-
* the mulcache with values in (-q,q), but this is not needed for the
183-
* higher level safety proofs, and thus not part of the spec.
184-
*/
185182
MLK_INTERNAL_API
186183
void mlk_poly_mulcache_compute(mlk_poly_mulcache *x, const mlk_poly *a)
187184
__contract__(
188185
requires(memory_no_alias(x, sizeof(mlk_poly_mulcache)))
189186
requires(memory_no_alias(a, sizeof(mlk_poly)))
190-
assigns(object_whole(x))
187+
assigns(memory_slice(x, sizeof(mlk_poly_mulcache)))
188+
ensures(array_abs_bound(x->coeffs, 0, MLKEM_N/2, MLKEM_Q))
191189
);
192190

193191
#define mlk_poly_reduce MLK_NAMESPACE(poly_reduce)
@@ -339,6 +337,7 @@ MLK_INTERNAL_API
339337
void mlk_poly_invntt_tomont(mlk_poly *r)
340338
__contract__(
341339
requires(memory_no_alias(r, sizeof(mlk_poly)))
340+
requires(array_abs_bound(r->coeffs, 0, MLKEM_N, INT16_MAX/2))
342341
assigns(memory_slice(r, sizeof(mlk_poly)))
343342
ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND))
344343
);

mlkem/poly_k.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,19 @@ void mlk_polyvec_basemul_acc_montgomery_cached(
150150
{
151151
unsigned i;
152152
mlk_assert_bound_2d(a, MLKEM_K, MLKEM_N, 0, MLKEM_UINT12_LIMIT);
153+
mlk_assert_abs_bound_2d(b, MLKEM_K, MLKEM_N, MLK_NTT_BOUND);
154+
mlk_assert_abs_bound_2d(b_cache, MLKEM_K, MLKEM_N / 2, MLKEM_Q);
153155
for (i = 0; i < MLKEM_N / 2; i++)
154-
__loop__(invariant(i <= MLKEM_N / 2))
156+
__loop__(
157+
invariant(i <= MLKEM_N / 2)
158+
invariant(array_abs_bound(r->coeffs, 0, 2 * i, INT16_MAX/2)))
155159
{
156160
unsigned k;
157161
int32_t t[2] = {0};
158162
for (k = 0; k < MLKEM_K; k++)
159163
__loop__(
160-
invariant(k <= MLKEM_K &&
161-
t[0] <= (int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768 &&
162-
t[0] >= - ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768) &&
163-
t[1] <= ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768) &&
164-
t[1] >= - ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768)))
164+
invariant(k <= MLKEM_K && i <= MLKEM_N / 2)
165+
invariant(array_abs_bound(t, 0, 2, k * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND + 1)))
165166
{
166167
t[0] += (int32_t)a[k].coeffs[2 * i + 1] * b_cache[k].coeffs[i];
167168
t[0] += (int32_t)a[k].coeffs[2 * i] * b[k].coeffs[2 * i];

mlkem/poly_k.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,11 @@ MLK_INTERNAL_API
347347
void mlk_polyvec_invntt_tomont(mlk_polyvec r)
348348
__contract__(
349349
requires(memory_no_alias(r, sizeof(mlk_polyvec)))
350+
requires(forall(k0, 0, MLKEM_K,
351+
array_abs_bound(r[k0].coeffs, 0, MLKEM_N, INT16_MAX/2)))
350352
assigns(object_whole(r))
351353
ensures(forall(j, 0, MLKEM_K,
352-
array_abs_bound(r[j].coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND)))
354+
array_abs_bound(r[j].coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND)))
353355
);
354356

355357
#define mlk_polyvec_basemul_acc_montgomery_cached \
@@ -362,7 +364,11 @@ __contract__(
362364
*
363365
* Bounds:
364366
* - Every coefficient of a is assumed to be in [0..4095]
365-
* - No bounds guarantees for the coefficients in the result.
367+
* - Every coefficient of b is assumed to be bound by
368+
* MLK_NTT_BOUND in absolute value.
369+
* - Every coefficient of b_cache is assumed to be bound by
370+
* MLKEM_Q in absolute value.
371+
* - The output bounds are below INT16_MAX/2 in absolute value.
366372
*
367373
* Arguments: - mlk_poly *r: pointer to output polynomial
368374
* - const mlk_polyvec a: pointer to first input polynomial vector
@@ -389,7 +395,12 @@ __contract__(
389395
requires(memory_no_alias(b_cache, sizeof(mlk_polyvec_mulcache)))
390396
requires(forall(k1, 0, MLKEM_K,
391397
array_bound(a[k1].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)))
392-
assigns(object_whole(r))
398+
requires(forall(k2, 0, MLKEM_K,
399+
array_abs_bound(b[k2].coeffs, 0, MLKEM_N, MLK_NTT_BOUND)))
400+
requires(forall(k3, 0, MLKEM_K,
401+
array_abs_bound(b_cache[k3].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
402+
assigns(memory_slice(r, sizeof(mlk_poly)))
403+
ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, INT16_MAX/2))
393404
);
394405

395406
#define mlk_polyvec_mulcache_compute MLK_NAMESPACE_K(polyvec_mulcache_compute)
@@ -417,17 +428,14 @@ __contract__(
417428
* - Caches `b_1 * \gamma` in @[FIPS203, Algorithm 12, BaseCaseMultiply, L1]
418429
*
419430
************************************************************/
420-
/*
421-
* NOTE: The default C implementation of this function populates
422-
* the mulcache with values in (-q,q), but this is not needed for the
423-
* higher level safety proofs, and thus not part of the spec.
424-
*/
425431
MLK_INTERNAL_API
426432
void mlk_polyvec_mulcache_compute(mlk_polyvec_mulcache x, const mlk_polyvec a)
427433
__contract__(
428434
requires(memory_no_alias(x, sizeof(mlk_polyvec_mulcache)))
429435
requires(memory_no_alias(a, sizeof(mlk_polyvec)))
430436
assigns(object_whole(x))
437+
ensures(forall(k0, 0, MLKEM_K,
438+
array_abs_bound(x[k0].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
431439
);
432440

433441
#define mlk_polyvec_reduce MLK_NAMESPACE_K(polyvec_reduce)

0 commit comments

Comments
 (0)