Skip to content

Commit 1ed3c4a

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 <[email protected]>
1 parent 8dba13b commit 1ed3c4a

File tree

8 files changed

+92
-59
lines changed

8 files changed

+92
-59
lines changed

mlkem/indcpa.c

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,11 @@ void mlk_gen_matrix(mlk_polyvec *a, const uint8_t seed[MLKEM_SYMBYTES],
282282
* - mlk_polyvec a[MLKEM_K]: Input matrix. Must be in NTT domain
283283
* and have coefficients of absolute value < 4096.
284284
* - mlk_polyvec *v: Input polynomial vector. Must be in NTT
285-
* domain.
285+
* domain and have coefficients of absolute value
286+
* < MLK_NTT_BOUND.
286287
* - mlk_polyvec *vc: Mulcache for v, computed via
287-
* mlk_polyvec_mulcache_compute().
288+
* mlk_polyvec_mulcache_compute(). Must have coefficients
289+
* of absolute value < MLKEM_Q.
288290
*
289291
* Specification: Implements [FIPS 203, Section 2.4.7, Eq (2.12), (2.13)]
290292
*
@@ -299,13 +301,16 @@ __contract__(
299301
requires(forall(k0, 0, MLKEM_K,
300302
forall(k1, 0, MLKEM_K,
301303
array_bound(a[k0].vec[k1].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT))))
302-
assigns(object_whole(out)))
304+
requires(forall(k2, 0, MLKEM_K,
305+
array_abs_bound(v->vec[k2].coeffs, 0, MLKEM_N, MLK_NTT_BOUND)))
306+
requires(forall(k3, 0, MLKEM_K,
307+
array_abs_bound(vc->vec[k3].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
308+
assigns(object_whole(out))
309+
ensures(forall(k4, 0, MLKEM_K,
310+
array_abs_bound(out->vec[k4].coeffs, 0, MLKEM_N, INT16_MAX/2))))
303311
{
304312
unsigned i;
305313
for (i = 0; i < MLKEM_K; i++)
306-
__loop__(
307-
assigns(i, object_whole(out))
308-
invariant(i <= MLKEM_K))
309314
{
310315
mlk_polyvec_basemul_acc_montgomery_cached(&out->vec[i], &a[i], v, vc);
311316
}

mlkem/native/api.h

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ __contract__(
140140
static MLK_INLINE void mlk_intt_native(int16_t p[MLKEM_N])
141141
__contract__(
142142
requires(memory_no_alias(p, sizeof(int16_t) * MLKEM_N))
143+
requires(array_abs_bound(p, 0, MLKEM_N, INT16_MAX/2))
143144
assigns(memory_slice(p, sizeof(int16_t) * MLKEM_N))
144145
ensures(array_abs_bound(p, 0, MLKEM_N, MLK_INVNTT_BOUND))
145146
);
@@ -205,7 +206,8 @@ static MLK_INLINE void mlk_poly_mulcache_compute_native(
205206
__contract__(
206207
requires(memory_no_alias(cache, sizeof(int16_t) * (MLKEM_N / 2)))
207208
requires(memory_no_alias(mlk_poly, sizeof(int16_t) * MLKEM_N))
208-
assigns(object_whole(cache))
209+
assigns(memory_slice(cache, sizeof(int16_t) * MLKEM_N / 2))
210+
ensures(array_abs_bound(cache, 0, MLKEM_N/2, MLKEM_Q))
209211
);
210212
#endif /* MLK_USE_NATIVE_POLY_MULCACHE_COMPUTE */
211213

@@ -246,10 +248,17 @@ __contract__(
246248
* requires(array_bound(a, 0, 2 * MLKEM_N, 0, MLKEM_UINT12_LIMIT))
247249
* ```
248250
*/
249-
requires(forall(kN, 0, 2, \
250-
array_bound(&((int16_t(*)[MLKEM_N])(a))[kN][0], 0, MLKEM_N, \
251+
requires(forall(k0, 0, 2, \
252+
array_bound(&((int16_t(*)[MLKEM_N])(a))[k0][0], 0, MLKEM_N, \
251253
0, MLKEM_UINT12_LIMIT)))
254+
requires(forall(k1, 0, 2, \
255+
array_abs_bound(&((int16_t(*)[MLKEM_N])(b))[k1][0], 0, MLKEM_N, \
256+
MLK_NTT_BOUND)))
257+
requires(forall(k2, 0, 2, \
258+
array_abs_bound(&((int16_t(*)[MLKEM_N/2])(b_cache))[k2][0], 0, MLKEM_N/2, \
259+
MLKEM_Q)))
252260
assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N))
261+
ensures(array_abs_bound(r, 0, MLKEM_N, INT16_MAX/2))
253262
);
254263
#endif /* MLK_MULTILEVEL_BUILD_WITH_SHARED || MLKEM_K == 2 */
255264

@@ -289,10 +298,17 @@ __contract__(
289298
* requires(array_bound(a, 0, 3 * MLKEM_N, 0, MLKEM_UINT12_LIMIT))
290299
* ```
291300
*/
292-
requires(forall(kN, 0, 3, \
293-
array_bound(&((int16_t(*)[MLKEM_N])(a))[kN][0], 0, MLKEM_N, \
301+
requires(forall(k0, 0, 3, \
302+
array_bound(&((int16_t(*)[MLKEM_N])(a))[k0][0], 0, MLKEM_N, \
294303
0, MLKEM_UINT12_LIMIT)))
304+
requires(forall(k1, 0, 3, \
305+
array_abs_bound(&((int16_t(*)[MLKEM_N])(b))[k1][0], 0, MLKEM_N, \
306+
MLK_NTT_BOUND)))
307+
requires(forall(k2, 0, 3, \
308+
array_abs_bound(&((int16_t(*)[MLKEM_N/2])(b_cache))[k2][0], 0, MLKEM_N/2, \
309+
MLKEM_Q)))
295310
assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N))
311+
ensures(array_abs_bound(r, 0, MLKEM_N, INT16_MAX/2))
296312
);
297313
#endif /* MLK_MULTILEVEL_BUILD_WITH_SHARED || MLKEM_K == 3 */
298314

@@ -332,10 +348,17 @@ __contract__(
332348
* requires(array_bound(a, 0, 4 * MLKEM_N, 0, MLKEM_UINT12_LIMIT))
333349
* ```
334350
*/
335-
requires(forall(kN, 0, 4, \
336-
array_bound(&((int16_t(*)[MLKEM_N])(a))[kN][0], 0, MLKEM_N, \
351+
requires(forall(k0, 0, 4, \
352+
array_bound(&((int16_t(*)[MLKEM_N])(a))[k0][0], 0, MLKEM_N, \
337353
0, MLKEM_UINT12_LIMIT)))
354+
requires(forall(k1, 0, 4, \
355+
array_abs_bound(&((int16_t(*)[MLKEM_N])(b))[k1][0], 0, MLKEM_N, \
356+
MLK_NTT_BOUND)))
357+
requires(forall(k2, 0, 4, \
358+
array_abs_bound(&((int16_t(*)[MLKEM_N/2])(b_cache))[k2][0], 0, MLKEM_N/2, \
359+
MLKEM_Q)))
338360
assigns(memory_slice(r, sizeof(int16_t) * MLKEM_N))
361+
ensures(array_abs_bound(r, 0, MLKEM_N, INT16_MAX/2))
339362
);
340363
#endif /* MLK_MULTILEVEL_BUILD_WITH_SHARED || MLKEM_K == 4 */
341364
#endif /* MLK_USE_NATIVE_POLYVEC_BASEMUL_ACC_MONTGOMERY_CACHED */

mlkem/native/x86_64/meta.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ static MLK_INLINE void mlk_poly_tomont_native(int16_t data[MLKEM_N])
7575
static MLK_INLINE void mlk_poly_mulcache_compute_native(
7676
int16_t x[MLKEM_N / 2], const int16_t y[MLKEM_N])
7777
{
78-
/* AVX2 backend does not use mulcache */
78+
/* AVX2 backend does not use mulcache, but the contracts require
79+
* that its < MLKEM_Q in absolute value, so we just zero it. */
80+
memset(x, 0, sizeof(int16_t) * MLKEM_N / 2);
7981
((void)y);
80-
((void)x);
8182
}
8283

8384
#if defined(MLK_MULTILEVEL_BUILD_WITH_SHARED) || MLKEM_K == 2

mlkem/poly.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -259,14 +259,6 @@ void mlk_poly_mulcache_compute(mlk_poly_mulcache *x, const mlk_poly *a)
259259
x->coeffs[2 * i + 0] = mlk_fqmul(a->coeffs[4 * i + 1], zetas[64 + i]);
260260
x->coeffs[2 * i + 1] = mlk_fqmul(a->coeffs[4 * i + 3], -zetas[64 + i]);
261261
}
262-
263-
/*
264-
* This bound is true for the C implementation, but not needed
265-
* in the higher level bounds reasoning. It is thus omitted
266-
* them from the spec to not unnecessarily constrain native
267-
* implementations, but checked here nonetheless.
268-
*/
269-
mlk_assert_abs_bound(x, MLKEM_N / 2, MLKEM_Q);
270262
}
271263
#else /* MLK_USE_NATIVE_POLY_MULCACHE_COMPUTE */
272264
MLK_INTERNAL_API

mlkem/poly.h

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,16 @@ static MLK_ALWAYS_INLINE int16_t mlk_cast_uint16_to_int16(uint16_t x)
7979
**************************************************/
8080
static MLK_ALWAYS_INLINE int16_t mlk_montgomery_reduce(int32_t a)
8181
__contract__(
82-
requires(a < +(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)) &&
83-
a > -(INT32_MAX - (((int32_t)1 << 15) * MLKEM_Q)))
84-
/* We don't attempt to express an input-dependent output bound
85-
* as the post-condition here. There are two call-sites for this
86-
* function:
87-
* - The base multiplication: Here, we need no output bound.
88-
* - mlk_fqmul: Here, we inline this function and prove another spec
89-
* for mlk_fqmul which does have a post-condition bound. */
82+
/* This specification is only relevant for Montgomery reduction
83+
* during base multiplication, and the input bound is tailored to that.
84+
* The output bound, albeit weak, allows one addition/subtraction prior
85+
* to risk of overflow; this can be useful for the inverse NTT, for example.
86+
*
87+
* For the use of montgomery_reduce in fqmul, we inline this
88+
* function instead of calling it by contract. */
89+
requires(a <= +(4 * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND) &&
90+
a >= -(4 * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND))
91+
ensures(return_value < (INT16_MAX / 2) && return_value > -(INT16_MAX / 2))
9092
)
9193
{
9294
/* check-magic: 62209 == unsigned_mod(pow(MLKEM_Q, -1, 2^16), 2^16) */
@@ -167,17 +169,13 @@ __contract__(
167169
* - Caches `b_1 * \gamma` in [FIPS 203, Algorithm 12, BaseCaseMultiply, L1]
168170
*
169171
************************************************************/
170-
/*
171-
* NOTE: The default C implementation of this function populates
172-
* the mulcache with values in (-q,q), but this is not needed for the
173-
* higher level safety proofs, and thus not part of the spec.
174-
*/
175172
MLK_INTERNAL_API
176173
void mlk_poly_mulcache_compute(mlk_poly_mulcache *x, const mlk_poly *a)
177174
__contract__(
178175
requires(memory_no_alias(x, sizeof(mlk_poly_mulcache)))
179176
requires(memory_no_alias(a, sizeof(mlk_poly)))
180-
assigns(object_whole(x))
177+
assigns(memory_slice(x, sizeof(mlk_poly_mulcache)))
178+
ensures(array_abs_bound(x->coeffs, 0, MLKEM_N/2, MLKEM_Q))
181179
);
182180

183181
#define mlk_poly_reduce MLK_NAMESPACE(poly_reduce)
@@ -329,6 +327,7 @@ MLK_INTERNAL_API
329327
void mlk_poly_invntt_tomont(mlk_poly *r)
330328
__contract__(
331329
requires(memory_no_alias(r, sizeof(mlk_poly)))
330+
requires(array_abs_bound(r->coeffs, 0, MLKEM_N, INT16_MAX/2))
332331
assigns(memory_slice(r, sizeof(mlk_poly)))
333332
ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND))
334333
);

mlkem/poly_k.c

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,26 +130,30 @@ void mlk_polyvec_basemul_acc_montgomery_cached(
130130
{
131131
unsigned i;
132132
mlk_assert_bound_2d(a, MLKEM_K, MLKEM_N, 0, MLKEM_UINT12_LIMIT);
133+
mlk_assert_abs_bound_2d(b, MLKEM_K, MLKEM_N, MLK_NTT_BOUND);
134+
mlk_assert_abs_bound_2d(b_cache, MLKEM_K, MLKEM_N / 2, MLKEM_Q);
133135
for (i = 0; i < MLKEM_N / 2; i++)
134-
__loop__(invariant(i <= MLKEM_N / 2))
136+
__loop__(
137+
invariant(i <= MLKEM_N / 2)
138+
invariant(array_abs_bound(r->coeffs, 0, 2 * i, INT16_MAX/2)))
135139
{
136140
unsigned k;
137-
int32_t t[2] = {0};
141+
int32_t t0 = 0, t1 = 0;
138142
for (k = 0; k < MLKEM_K; k++)
139143
__loop__(
140-
invariant(k <= MLKEM_K &&
141-
t[0] <= (int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768 &&
142-
t[0] >= - ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768) &&
143-
t[1] <= ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768) &&
144-
t[1] >= - ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * 32768)))
144+
invariant(k <= MLKEM_K && i <= MLKEM_N / 2 &&
145+
t0 <= ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND) &&
146+
t0 >= - ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND) &&
147+
t1 <= ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND) &&
148+
t1 >= - ((int32_t) k * 2 * MLKEM_UINT12_LIMIT * MLK_NTT_BOUND)))
145149
{
146-
t[0] += (int32_t)a->vec[k].coeffs[2 * i + 1] * b_cache->vec[k].coeffs[i];
147-
t[0] += (int32_t)a->vec[k].coeffs[2 * i] * b->vec[k].coeffs[2 * i];
148-
t[1] += (int32_t)a->vec[k].coeffs[2 * i] * b->vec[k].coeffs[2 * i + 1];
149-
t[1] += (int32_t)a->vec[k].coeffs[2 * i + 1] * b->vec[k].coeffs[2 * i];
150+
t0 += (int32_t)a->vec[k].coeffs[2 * i + 1] * b_cache->vec[k].coeffs[i];
151+
t0 += (int32_t)a->vec[k].coeffs[2 * i] * b->vec[k].coeffs[2 * i];
152+
t1 += (int32_t)a->vec[k].coeffs[2 * i] * b->vec[k].coeffs[2 * i + 1];
153+
t1 += (int32_t)a->vec[k].coeffs[2 * i + 1] * b->vec[k].coeffs[2 * i];
150154
}
151-
r->coeffs[2 * i + 0] = mlk_montgomery_reduce(t[0]);
152-
r->coeffs[2 * i + 1] = mlk_montgomery_reduce(t[1]);
155+
r->coeffs[2 * i + 0] = mlk_montgomery_reduce(t0);
156+
r->coeffs[2 * i + 1] = mlk_montgomery_reduce(t1);
153157
}
154158
}
155159

mlkem/poly_k.h

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,11 @@ MLK_INTERNAL_API
342342
void mlk_polyvec_invntt_tomont(mlk_polyvec *r)
343343
__contract__(
344344
requires(memory_no_alias(r, sizeof(mlk_polyvec)))
345+
requires(forall(k0, 0, MLKEM_K,
346+
array_abs_bound(r->vec[k0].coeffs, 0, MLKEM_N, INT16_MAX/2)))
345347
assigns(object_whole(r))
346-
ensures(forall(j, 0, MLKEM_K,
347-
array_abs_bound(r->vec[j].coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND)))
348+
ensures(forall(k1, 0, MLKEM_K,
349+
array_abs_bound(r->vec[k1].coeffs, 0, MLKEM_N, MLK_INVNTT_BOUND)))
348350
);
349351

350352
#define mlk_polyvec_basemul_acc_montgomery_cached \
@@ -357,7 +359,11 @@ __contract__(
357359
*
358360
* Bounds:
359361
* - Every coefficient of a is assumed to be in [0..4095]
360-
* - No bounds guarantees for the coefficients in the result.
362+
* - Every coefficient of b is assumed to be bound by
363+
* MLK_NTT_BOUND in absolute value.
364+
* - Every coefficient of b_cache is assumed to be bound by
365+
* MLKEM_Q in absolute value.
366+
* - The output bounds are below INT16_MAX/2 in absolute value.
361367
*
362368
* Arguments: - mlk_poly *r: pointer to output polynomial
363369
* - const mlk_polyvec *a: pointer to first input polynomial vector
@@ -384,7 +390,12 @@ __contract__(
384390
requires(memory_no_alias(b_cache, sizeof(mlk_polyvec_mulcache)))
385391
requires(forall(k1, 0, MLKEM_K,
386392
array_bound(a->vec[k1].coeffs, 0, MLKEM_N, 0, MLKEM_UINT12_LIMIT)))
387-
assigns(object_whole(r))
393+
requires(forall(k2, 0, MLKEM_K,
394+
array_abs_bound(b->vec[k2].coeffs, 0, MLKEM_N, MLK_NTT_BOUND)))
395+
requires(forall(k3, 0, MLKEM_K,
396+
array_abs_bound(b_cache->vec[k3].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
397+
assigns(memory_slice(r, sizeof(mlk_poly)))
398+
ensures(array_abs_bound(r->coeffs, 0, MLKEM_N, INT16_MAX/2))
388399
);
389400

390401
#define mlk_polyvec_mulcache_compute MLK_NAMESPACE_K(polyvec_mulcache_compute)
@@ -412,17 +423,14 @@ __contract__(
412423
* - Caches `b_1 * \gamma` in [FIPS 203, Algorithm 12, BaseCaseMultiply, L1]
413424
*
414425
************************************************************/
415-
/*
416-
* NOTE: The default C implementation of this function populates
417-
* the mulcache with values in (-q,q), but this is not needed for the
418-
* higher level safety proofs, and thus not part of the spec.
419-
*/
420426
MLK_INTERNAL_API
421427
void mlk_polyvec_mulcache_compute(mlk_polyvec_mulcache *x, const mlk_polyvec *a)
422428
__contract__(
423429
requires(memory_no_alias(x, sizeof(mlk_polyvec_mulcache)))
424430
requires(memory_no_alias(a, sizeof(mlk_polyvec)))
425431
assigns(object_whole(x))
432+
ensures(forall(k0, 0, MLKEM_K,
433+
array_abs_bound(x->vec[k0].coeffs, 0, MLKEM_N/2, MLKEM_Q)))
426434
);
427435

428436
#define mlk_polyvec_reduce MLK_NAMESPACE_K(polyvec_reduce)

proofs/cbmc/matvec_mul/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ DEFINES +=
1313
INCLUDES +=
1414

1515
REMOVE_FUNCTION_BODY +=
16+
UNWINDSET += mlk_matvec_mul.0:4
1617

1718
PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
1819
PROJECT_SOURCES += $(SRCDIR)/mlkem/indcpa.c

0 commit comments

Comments
 (0)