Skip to content

Commit 08888cd

Browse files
committed
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 <beckphan@amazon.co.uk>
1 parent d83012d commit 08888cd

File tree

5 files changed

+55
-33
lines changed

5 files changed

+55
-33
lines changed

mlkem/src/indcpa.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,11 @@ void mlk_gen_matrix(mlk_polymat *a, const uint8_t seed[MLKEM_SYMBYTES],
296296
* - mlk_polymat a: Input matrix. Must be in NTT domain
297297
* and have coefficients of absolute value < 4096.
298298
* - mlk_polyvec v: Input polynomial vector. Must be in NTT
299-
* domain.
299+
* domain and have coefficients of absolute value
300+
* < MLK_NTT_BOUND.
300301
* - mlk_polyvec vc: Mulcache for v, computed via
301-
* mlk_polyvec_mulcache_compute().
302+
* mlk_polyvec_mulcache_compute(). Must have coefficients
303+
* of absolute value < MLKEM_Q.
302304
*
303305
* Specification: Implements @[FIPS203, Section 2.4.7, Eq (2.12), (2.13)]
304306
*
@@ -313,13 +315,21 @@ __contract__(
313315
requires(forall(k0, 0, MLKEM_K,
314316
forall(k1, 0, MLKEM_K,
315317
array_bound(a->vec[k0].vec[k1].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT))))
316-
assigns(object_whole(out)))
318+
requires(forall(k1, 0, MLKEM_K,
319+
array_abs_bound(v->vec[k1].coeffs, 0, MLKEM_N, MLK_NTT_BOUND)))
320+
requires(forall(k2, 0, MLKEM_K,
321+
array_abs_bound(vc->vec[k2].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
322+
assigns(memory_slice(out, sizeof(mlk_polyvec)))
323+
ensures(forall(k3, 0, MLKEM_K,
324+
array_abs_bound(out->vec[k3].coeffs, 0, MLKEM_N, INT16_MAX/2))))
317325
{
318326
unsigned i;
319327
for (i = 0; i < MLKEM_K; i++)
320328
__loop__(
321-
assigns(i, object_whole(out))
322-
invariant(i <= MLKEM_K))
329+
assigns(i, memory_slice(out, sizeof(mlk_polyvec)))
330+
invariant(i <= MLKEM_K)
331+
invariant(forall(k, 0, i,
332+
array_abs_bound(out->vec[k].coeffs, 0, MLKEM_N, INT16_MAX/2))))
323333
{
324334
mlk_polyvec_basemul_acc_montgomery_cached(&out->vec[i], &a->vec[i], v, vc);
325335
}

mlkem/src/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
);
@@ -244,6 +245,7 @@ __contract__(
244245
requires(memory_no_alias(b_cache, sizeof(int16_t) * 2 * (MLKEM_N / 2)))
245246
requires(array_bound(a, 0, 2 * MLKEM_N, 0, MLKEM_UINT12_LIMIT))
246247
assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N))
248+
ensures(array_abs_bound(r, 0, MLKEM_N, INT16_MAX/2))
247249
);
248250
#endif /* MLK_CONFIG_MULTILEVEL_WITH_SHARED || MLKEM_K == 2 */
249251

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

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

mlkem/src/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/src/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->vec[k].coeffs[2 * i + 1] * b_cache->vec[k].coeffs[i];
167168
t[0] += (int32_t)a->vec[k].coeffs[2 * i] * b->vec[k].coeffs[2 * i];

mlkem/src/poly_k.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,11 @@ MLK_INTERNAL_API
358358
void mlk_polyvec_invntt_tomont(mlk_polyvec *r)
359359
__contract__(
360360
requires(memory_no_alias(r, sizeof(mlk_polyvec)))
361+
requires(forall(k0, 0, MLKEM_K,
362+
array_abs_bound(r->vec[k0].coeffs, 0, MLKEM_N, INT16_MAX/2)))
361363
assigns(object_whole(r))
362364
ensures(forall(j, 0, MLKEM_K,
363-
array_abs_bound(r->vec[j].coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND)))
365+
array_abs_bound(r->vec[j].coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND)))
364366
);
365367

366368
#define mlk_polyvec_basemul_acc_montgomery_cached \
@@ -373,7 +375,11 @@ __contract__(
373375
*
374376
* Bounds:
375377
* - Every coefficient of a is assumed to be in [0..4095]
376-
* - No bounds guarantees for the coefficients in the result.
378+
* - Every coefficient of b is assumed to be bound by
379+
* MLK_NTT_BOUND in absolute value.
380+
* - Every coefficient of b_cache is assumed to be bound by
381+
* MLKEM_Q in absolute value.
382+
* - The output bounds are below INT16_MAX/2 in absolute value.
377383
*
378384
* Arguments: - mlk_poly *r: pointer to output polynomial
379385
* - const mlk_polyvec a: pointer to first input polynomial vector
@@ -400,7 +406,12 @@ __contract__(
400406
requires(memory_no_alias(b_cache, sizeof(mlk_polyvec_mulcache)))
401407
requires(forall(k1, 0, MLKEM_K,
402408
array_bound(a->vec[k1].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)))
403-
assigns(object_whole(r))
409+
requires(forall(k2, 0, MLKEM_K,
410+
array_abs_bound(b->vec[k2].coeffs, 0, MLKEM_N, MLK_NTT_BOUND)))
411+
requires(forall(k3, 0, MLKEM_K,
412+
array_abs_bound(b_cache->vec[k3].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
413+
assigns(memory_slice(r, sizeof(mlk_poly)))
414+
ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, INT16_MAX/2))
404415
);
405416

406417
#define mlk_polyvec_mulcache_compute MLK_NAMESPACE_K(polyvec_mulcache_compute)
@@ -428,17 +439,14 @@ __contract__(
428439
* - Caches `b_1 * \gamma` in @[FIPS203, Algorithm 12, BaseCaseMultiply, L1]
429440
*
430441
************************************************************/
431-
/*
432-
* NOTE: The default C implementation of this function populates
433-
* the mulcache with values in (-q,q), but this is not needed for the
434-
* higher level safety proofs, and thus not part of the spec.
435-
*/
436442
MLK_INTERNAL_API
437443
void mlk_polyvec_mulcache_compute(mlk_polyvec_mulcache *x, const mlk_polyvec *a)
438444
__contract__(
439445
requires(memory_no_alias(x, sizeof(mlk_polyvec_mulcache)))
440446
requires(memory_no_alias(a, sizeof(mlk_polyvec)))
441447
assigns(object_whole(x))
448+
ensures(forall(k0, 0, MLKEM_K,
449+
array_abs_bound(x->vec[k0].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
442450
);
443451

444452
#define mlk_polyvec_reduce MLK_NAMESPACE_K(polyvec_reduce)

0 commit comments

Comments
 (0)