Skip to content

Commit

Permalink
Merge pull request #365 from pq-code-package/gen_matrix_entry_x4
Browse files Browse the repository at this point in the history
CBMC: Add spec + proof for gen_matrix_entry_x4 and gen_matrix
  • Loading branch information
hanno-becker authored Nov 8, 2024
2 parents 4fe39f5 + 6833bbf commit 1bbf687
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 31 deletions.
54 changes: 54 additions & 0 deletions cbmc/proofs/gen_matrix/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SPDX-License-Identifier: Apache-2.0

include ../Makefile_params.common

HARNESS_ENTRY = harness
HARNESS_FILE = gen_matrix_harness

# This should be a unique identifier for this proof, and will appear on the
# Litani dashboard. It can be human-readable and contain spaces if you wish.
PROOF_UID = gen_matrix

DEFINES +=
INCLUDES +=

REMOVE_FUNCTION_BODY +=
UNWINDSET += $(MLKEM_NAMESPACE)gen_matrix.0:4 $(MLKEM_NAMESPACE)gen_matrix.1:4 $(MLKEM_NAMESPACE)gen_matrix.2:4

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

CHECK_FUNCTION_CONTRACTS=$(MLKEM_NAMESPACE)gen_matrix
USE_FUNCTION_CONTRACTS=gen_matrix_entry gen_matrix_entry_x4
APPLY_LOOP_CONTRACTS=on
USE_DYNAMIC_FRAMES=1

# Disable any setting of EXTERNAL_SAT_SOLVER, and choose SMT backend instead
EXTERNAL_SAT_SOLVER=
CBMCFLAGS=--smt2

FUNCTION_NAME = $(MLKEM_NAMESPACE)gen_matrix

# If this proof is found to consume huge amounts of RAM, you can set the
# EXPENSIVE variable. With new enough versions of the proof tools, this will
# restrict the number of EXPENSIVE CBMC jobs running at once. See the
# documentation in Makefile.common under the "Job Pools" heading for details.
# EXPENSIVE = true

# This function is large enough to need...
CBMC_OBJECT_BITS = 8

# If you require access to a file-local ("static") function or object to conduct
# your proof, set the following (and do not include the original source file
# ("mlkem/poly.c") in PROJECT_SOURCES).
# REWRITTEN_SOURCES = $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i
# include ../Makefile.common
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_SOURCE = $(SRCDIR)/mlkem/poly.c
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_FUNCTIONS = foo bar
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_OBJECTS = baz
# Care is required with variables on the left-hand side: REWRITTEN_SOURCES must
# be set before including Makefile.common, but any use of variables on the
# left-hand side requires those variables to be defined. Hence, _SOURCE,
# _FUNCTIONS, _OBJECTS is set after including Makefile.common.

include ../Makefile.common
3 changes: 3 additions & 0 deletions cbmc/proofs/gen_matrix/cbmc-proof.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: Apache-2.0

# This file marks this directory as containing a CBMC proof.
30 changes: 30 additions & 0 deletions cbmc/proofs/gen_matrix/gen_matrix_harness.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

/*
* Insert copyright notice
*/

/**
* @file gen_matrix_harness.c
* @brief Implements the proof harness for gen_matrix function.
*/

/*
* Insert project header files that
* - include the declaration of the function
* - include the types needed to declare function arguments
*/
#include <stdint.h>
#include "indcpa.h"

/**
* @brief Starting point for formal analysis
*
*/
void harness(void) {
polyvec *a;
uint8_t *seed;
int transposed;
gen_matrix(a, seed, transposed);
}
54 changes: 54 additions & 0 deletions cbmc/proofs/gen_matrix_entry_x4/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SPDX-License-Identifier: Apache-2.0

include ../Makefile_params.common

HARNESS_ENTRY = harness
HARNESS_FILE = gen_matrix_entry_x4_harness

# This should be a unique identifier for this proof, and will appear on the
# Litani dashboard. It can be human-readable and contain spaces if you wish.
PROOF_UID = gen_matrix_entry_x4

DEFINES +=
INCLUDES +=

REMOVE_FUNCTION_BODY +=
UNWINDSET +=

PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
PROJECT_SOURCES += $(SRCDIR)/mlkem/indcpa.c $(SRCDIR)/fips202/fips202x4.c

CHECK_FUNCTION_CONTRACTS= gen_matrix_entry_x4
USE_FUNCTION_CONTRACTS=$(FIPS202_NAMESPACE)shake128x4_absorb $(FIPS202_NAMESPACE)shake128x4_squeezeblocks $(MLKEM_NAMESPACE)rej_uniform
APPLY_LOOP_CONTRACTS=on
USE_DYNAMIC_FRAMES=1

# Disable any setting of EXTERNAL_SAT_SOLVER, and choose SMT backend instead
EXTERNAL_SAT_SOLVER=
CBMCFLAGS=--smt2

FUNCTION_NAME = $(MLKEM_NAMESPACE)gen_matrix_entry_x4

# If this proof is found to consume huge amounts of RAM, you can set the
# EXPENSIVE variable. With new enough versions of the proof tools, this will
# restrict the number of EXPENSIVE CBMC jobs running at once. See the
# documentation in Makefile.common under the "Job Pools" heading for details.
# EXPENSIVE = true

# This function is large enough to need...
CBMC_OBJECT_BITS = 10

# If you require access to a file-local ("static") function or object to conduct
# your proof, set the following (and do not include the original source file
# ("mlkem/poly.c") in PROJECT_SOURCES).
# REWRITTEN_SOURCES = $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i
# include ../Makefile.common
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_SOURCE = $(SRCDIR)/mlkem/poly.c
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_FUNCTIONS = foo bar
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_OBJECTS = baz
# Care is required with variables on the left-hand side: REWRITTEN_SOURCES must
# be set before including Makefile.common, but any use of variables on the
# left-hand side requires those variables to be defined. Hence, _SOURCE,
# _FUNCTIONS, _OBJECTS is set after including Makefile.common.

include ../Makefile.common
3 changes: 3 additions & 0 deletions cbmc/proofs/gen_matrix_entry_x4/cbmc-proof.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: Apache-2.0

# This file marks this directory as containing a CBMC proof.
32 changes: 32 additions & 0 deletions cbmc/proofs/gen_matrix_entry_x4/gen_matrix_entry_x4_harness.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

/*
* Insert copyright notice
*/

/**
* @file gen_matrix_entry_x4_harness.c
* @brief Implements the proof harness for gen_matrix_entry_x4 function.
*/

/*
* Insert project header files that
* - include the declaration of the function
* - include the types needed to declare function arguments
*/
#include <stdint.h>
#include "poly.h"

// declare here since it's static in non-CBMC builds
void gen_matrix_entry_x4(poly vec[4], uint8_t *seed[4]);

/**
* @brief Starting point for formal analysis
*
*/
void harness(void) {
poly out[4];
uint8_t *seed[4];
gen_matrix_entry_x4(out, seed);
}
25 changes: 23 additions & 2 deletions fips202/fips202x4.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <stddef.h>
#include <stdint.h>
#include "fips202.h"
#include "keccakf1600.h"
#include "namespace.h"

Expand All @@ -14,15 +15,35 @@ void shake128x4_absorb(keccakx4_state *state, const uint8_t *in0,
const uint8_t *in1, const uint8_t *in2,
const uint8_t *in3, size_t inlen);

// clang-format off
#define shake256x4_absorb FIPS202_NAMESPACE(shake256x4_absorb)
void shake256x4_absorb(keccakx4_state *state, const uint8_t *in0,
const uint8_t *in1, const uint8_t *in2,
const uint8_t *in3, size_t inlen);
const uint8_t *in3, size_t inlen)
REQUIRES(IS_FRESH(state, sizeof(keccakx4_state)))
REQUIRES(IS_FRESH(in0, inlen))
REQUIRES(IS_FRESH(in1, inlen))
REQUIRES(IS_FRESH(in2, inlen))
REQUIRES(IS_FRESH(in3, inlen))
ASSIGNS(OBJECT_WHOLE(state));
// clang-format on

// clang-format off
#define shake128x4_squeezeblocks FIPS202_NAMESPACE(shake128x4_squeezeblocks)
void shake128x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2,
uint8_t *out3, size_t nblocks,
keccakx4_state *state);
keccakx4_state *state)
REQUIRES(IS_FRESH(state, sizeof(keccakx4_state)))
REQUIRES(IS_FRESH(out0, nblocks * SHAKE128_RATE))
REQUIRES(IS_FRESH(out1, nblocks * SHAKE128_RATE))
REQUIRES(IS_FRESH(out2, nblocks * SHAKE128_RATE))
REQUIRES(IS_FRESH(out3, nblocks * SHAKE128_RATE))
ASSIGNS(OBJECT_UPTO(out0, nblocks * SHAKE128_RATE),
OBJECT_UPTO(out1, nblocks * SHAKE128_RATE),
OBJECT_UPTO(out2, nblocks * SHAKE128_RATE),
OBJECT_UPTO(out3, nblocks * SHAKE128_RATE),
OBJECT_WHOLE(state));
// clang-format on

#define shake256x4_squeezeblocks FIPS202_NAMESPACE(shake256x4_squeezeblocks)
void shake256x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2,
Expand Down
95 changes: 67 additions & 28 deletions mlkem/indcpa.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,28 @@ static void unpack_ciphertext(polyvec *b, poly *v,

// Generate four A matrix entries from a seed, using rejection
// sampling on the output of a XOF.
static void gen_matrix_entry_x4(poly *vec[4],
uint8_t seed[4][MLKEM_SYMBYTES + 16]) {
// clang-format off
STATIC_TESTABLE
void gen_matrix_entry_x4(poly *vec, uint8_t *seed[4])
REQUIRES(IS_FRESH(vec, sizeof(poly) * 4))
REQUIRES(IS_FRESH(seed, sizeof(uint8_t*) * 4))
REQUIRES(IS_FRESH(seed[0], MLKEM_SYMBYTES + 2))
REQUIRES(IS_FRESH(seed[1], MLKEM_SYMBYTES + 2))
REQUIRES(IS_FRESH(seed[2], MLKEM_SYMBYTES + 2))
REQUIRES(IS_FRESH(seed[3], MLKEM_SYMBYTES + 2))
ASSIGNS(OBJECT_UPTO(vec, sizeof(poly) * 4))
ENSURES(ARRAY_IN_BOUNDS(int, k0, 0, MLKEM_N - 1, vec[0].coeffs, 0, (MLKEM_Q - 1)))
ENSURES(ARRAY_IN_BOUNDS(int, k1, 0, MLKEM_N - 1, vec[1].coeffs, 0, (MLKEM_Q - 1)))
ENSURES(ARRAY_IN_BOUNDS(int, k2, 0, MLKEM_N - 1, vec[2].coeffs, 0, (MLKEM_Q - 1)))
ENSURES(ARRAY_IN_BOUNDS(int, k3, 0, MLKEM_N - 1, vec[3].coeffs, 0, (MLKEM_Q - 1)))
// clang-format on
{
// Temporary buffers for XOF output before rejection sampling
uint8_t bufx[KECCAK_WAY][GEN_MATRIX_NBLOCKS * SHAKE128_RATE];
uint8_t buf0[GEN_MATRIX_NBLOCKS * SHAKE128_RATE];
uint8_t buf1[GEN_MATRIX_NBLOCKS * SHAKE128_RATE];
uint8_t buf2[GEN_MATRIX_NBLOCKS * SHAKE128_RATE];
uint8_t buf3[GEN_MATRIX_NBLOCKS * SHAKE128_RATE];

// Tracks the number of coefficients we have already sampled
unsigned int ctr[KECCAK_WAY];
keccakx4_state statex;
Expand All @@ -162,41 +180,52 @@ static void gen_matrix_entry_x4(poly *vec[4],

// Initially, squeeze heuristic number of GEN_MATRIX_NBLOCKS.
// This should generate the matrix entries with high probability.
shake128x4_squeezeblocks(bufx[0], bufx[1], bufx[2], bufx[3],
GEN_MATRIX_NBLOCKS, &statex);
shake128x4_squeezeblocks(buf0, buf1, buf2, buf3, GEN_MATRIX_NBLOCKS, &statex);
buflen = GEN_MATRIX_NBLOCKS * SHAKE128_RATE;
for (unsigned int j = 0; j < KECCAK_WAY; j++) {
ctr[j] = rej_uniform(vec[j]->coeffs, MLKEM_N, 0, bufx[j], buflen);
}
ctr[0] = rej_uniform(vec[0].coeffs, MLKEM_N, 0, buf0, buflen);
ctr[1] = rej_uniform(vec[1].coeffs, MLKEM_N, 0, buf1, buflen);
ctr[2] = rej_uniform(vec[2].coeffs, MLKEM_N, 0, buf2, buflen);
ctr[3] = rej_uniform(vec[3].coeffs, MLKEM_N, 0, buf3, buflen);

// So long as not all matrix entries have been generated, squeeze
// one more block a time until we're done.
buflen = SHAKE128_RATE;
while (ctr[0] < MLKEM_N || ctr[1] < MLKEM_N || ctr[2] < MLKEM_N ||
ctr[3] < MLKEM_N) {
shake128x4_squeezeblocks(bufx[0], bufx[1], bufx[2], bufx[3], 1, &statex);
for (unsigned j = 0; j < KECCAK_WAY; j++) {
ctr[j] = rej_uniform(vec[j]->coeffs, MLKEM_N, ctr[j], bufx[j], buflen);
ctr[3] < MLKEM_N) // clang-format off
ASSIGNS(ctr, statex, OBJECT_UPTO(vec, sizeof(poly) * 4), OBJECT_WHOLE(buf0),
OBJECT_WHOLE(buf1), OBJECT_WHOLE(buf2), OBJECT_WHOLE(buf3))
INVARIANT(ctr[0] <= MLKEM_N && ctr[1] <= MLKEM_N)
INVARIANT(ctr[2] <= MLKEM_N && ctr[3] <= MLKEM_N)
INVARIANT(ctr[0] > 0 ==> ARRAY_IN_BOUNDS(int, k0, 0, ctr[0] - 1, vec[0].coeffs, 0, (MLKEM_Q - 1)))
INVARIANT(ctr[1] > 0 ==> ARRAY_IN_BOUNDS(int, k1, 0, ctr[1] - 1, vec[1].coeffs, 0, (MLKEM_Q - 1)))
INVARIANT(ctr[2] > 0 ==> ARRAY_IN_BOUNDS(int, k2, 0, ctr[2] - 1, vec[2].coeffs, 0, (MLKEM_Q - 1)))
INVARIANT(ctr[3] > 0 ==> ARRAY_IN_BOUNDS(int, k3, 0, ctr[3] - 1, vec[3].coeffs, 0, (MLKEM_Q - 1)))
// clang-format on
{
shake128x4_squeezeblocks(buf0, buf1, buf2, buf3, 1, &statex);
ctr[0] = rej_uniform(vec[0].coeffs, MLKEM_N, ctr[0], buf0, buflen);
ctr[1] = rej_uniform(vec[1].coeffs, MLKEM_N, ctr[1], buf1, buflen);
ctr[2] = rej_uniform(vec[2].coeffs, MLKEM_N, ctr[2], buf2, buflen);
ctr[3] = rej_uniform(vec[3].coeffs, MLKEM_N, ctr[3], buf3, buflen);
}
}

shake128x4_ctx_release(&statex);
}

// Generate a single A matrix entry from a seed, using rejection
// sampling on the output of a XOF.
STATIC_TESTABLE
void gen_matrix_entry(poly *entry,
uint8_t seed[MLKEM_SYMBYTES + 16]) // clang-format off
uint8_t seed[MLKEM_SYMBYTES + 2]) // clang-format off
REQUIRES(IS_FRESH(entry, sizeof(poly)))
REQUIRES(IS_FRESH(seed, MLKEM_SYMBYTES + 16))
REQUIRES(IS_FRESH(seed, MLKEM_SYMBYTES + 2))
ASSIGNS(OBJECT_UPTO(entry, sizeof(poly)))
ENSURES(ARRAY_IN_BOUNDS(int, k, 0, MLKEM_N - 1, entry->coeffs, 0, (MLKEM_Q - 1)))
{ // clang-format on
shake128ctx state;
uint8_t buf[GEN_MATRIX_NBLOCKS * SHAKE128_RATE];
unsigned int ctr, buflen;

// seed is MLKEM_SYMBYTES + 2 bytes long, but padded to MLKEM_SYMBYTES + 16
shake128_absorb(&state, seed, MLKEM_SYMBYTES + 2);

// Initially, squeeze + sample heuristic number of GEN_MATRIX_NBLOCKS.
Expand Down Expand Up @@ -236,8 +265,15 @@ void gen_matrix_entry(poly *entry,
void gen_matrix(polyvec *a, const uint8_t seed[MLKEM_SYMBYTES],
int transposed) {
int i;
// We need MLKEM_SYMBYTES + 2 bytes per seed, but add padding for alignment
uint8_t seedxy[KECCAK_WAY][MLKEM_SYMBYTES + 16];
// 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.
uint8_t seed0[MLKEM_SYMBYTES + 2] ALIGN;
uint8_t seed1[MLKEM_SYMBYTES + 2] ALIGN;
uint8_t seed2[MLKEM_SYMBYTES + 2] ALIGN;
uint8_t seed3[MLKEM_SYMBYTES + 2] ALIGN;
uint8_t *seedxy[] = {seed0, seed1, seed2, seed3};

for (unsigned j = 0; j < KECCAK_WAY; j++) {
memcpy(seedxy[j], seed, MLKEM_SYMBYTES);
}
Expand All @@ -250,7 +286,6 @@ void gen_matrix(polyvec *a, const uint8_t seed[MLKEM_SYMBYTES],
for (i = 0; i < (MLKEM_K * MLKEM_K / KECCAK_WAY) * KECCAK_WAY;
i += KECCAK_WAY) {
uint8_t x, y;
poly *vec[4];

for (unsigned int j = 0; j < KECCAK_WAY; j++) {
x = (i + j) / MLKEM_K;
Expand All @@ -262,29 +297,33 @@ void gen_matrix(polyvec *a, const uint8_t seed[MLKEM_SYMBYTES],
seedxy[j][MLKEM_SYMBYTES + 0] = y;
seedxy[j][MLKEM_SYMBYTES + 1] = x;
}
vec[j] = &a[x].vec[y];
}

gen_matrix_entry_x4(vec, seedxy);
// This call writes across polyvec boundaries for K=2 and K=3.
// This is intentional and safe.
gen_matrix_entry_x4(&a[0].vec[0] + i, seedxy);
}

// For left over vector, we use single keccak.
for (; i < MLKEM_K * MLKEM_K; i++) {
// For left over polynomial, we use single keccak.
if (i < MLKEM_K * MLKEM_K) {
uint8_t x, y;
x = i / MLKEM_K;
y = i % MLKEM_K;

if (transposed) {
seedxy[0][MLKEM_SYMBYTES + 0] = x;
seedxy[0][MLKEM_SYMBYTES + 1] = y;
seed0[MLKEM_SYMBYTES + 0] = x;
seed0[MLKEM_SYMBYTES + 1] = y;
} else {
seedxy[0][MLKEM_SYMBYTES + 0] = y;
seedxy[0][MLKEM_SYMBYTES + 1] = x;
seed0[MLKEM_SYMBYTES + 0] = y;
seed0[MLKEM_SYMBYTES + 1] = x;
}

gen_matrix_entry(&a[x].vec[y], seedxy[0]);
gen_matrix_entry(&a[0].vec[0] + i, seed0);
i++;
}

ASSERT(i == MLKEM_K * MLKEM_K, "gen_matrix: failed to generate whole matrix");

#if defined(MLKEM_USE_NATIVE_NTT_CUSTOM_ORDER)
// The public matrix is generated in NTT domain. If the native backend
// uses a custom order in NTT domain, permute A accordingly.
Expand Down
Loading

0 comments on commit 1bbf687

Please sign in to comment.