diff --git a/cbmc/proofs/poly_tobytes/Makefile b/cbmc/proofs/poly_tobytes/Makefile new file mode 100644 index 000000000..08f47f85d --- /dev/null +++ b/cbmc/proofs/poly_tobytes/Makefile @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: Apache-2.0 + +include ../Makefile_params.common + +HARNESS_ENTRY = harness +HARNESS_FILE = poly_tobytes_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 = poly_tobytes + +DEFINES += +INCLUDES += + +REMOVE_FUNCTION_BODY += +UNWINDSET += + +PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c +PROJECT_SOURCES += $(SRCDIR)/mlkem/poly.c + +CHECK_FUNCTION_CONTRACTS=$(KYBER_NAMESPACE)poly_tobytes +USE_FUNCTION_CONTRACTS= +APPLY_LOOP_CONTRACTS=on +USE_DYNAMIC_FRAMES=1 + +# Disable any setting of EXTERNAL_SAT_SOLVER, and choose SMT backend instead +EXTERNAL_SAT_SOLVER= +CBMCFLAGS=--bitwuzla + +FUNCTION_NAME = $(KYBER_NAMESPACE)poly_tobytes + +# 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 diff --git a/cbmc/proofs/poly_tobytes/cbmc-proof.txt b/cbmc/proofs/poly_tobytes/cbmc-proof.txt new file mode 100644 index 000000000..3d1913ebf --- /dev/null +++ b/cbmc/proofs/poly_tobytes/cbmc-proof.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: Apache-2.0 + +# This file marks this directory as containing a CBMC proof. diff --git a/mlkem/poly.c b/mlkem/poly.c index 5ff1c01a7..4af93da70 100644 --- a/mlkem/poly.c +++ b/mlkem/poly.c @@ -198,17 +198,33 @@ void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]) { #if !defined(MLKEM_USE_NATIVE_POLY_TOBYTES) void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a) { POLY_UBOUND(a, KYBER_Q); - unsigned int i; - uint16_t t0, t1; - for (i = 0; i < KYBER_N / 2; i++) { - t0 = a->coeffs[2 * i]; - t1 = a->coeffs[2 * i + 1]; - // REF-CHANGE: Precondition change, we assume unsigned canonical data - r[3 * i + 0] = (t0 >> 0); - r[3 * i + 1] = (t0 >> 8) | (t1 << 4); - r[3 * i + 2] = (t1 >> 4); - } + for (unsigned int i = 0; i < KYBER_N / 2; i++) + // clang-format off + ASSIGNS(i, OBJECT_WHOLE(r)) + INVARIANT(i >= 0 && i <= KYBER_N / 2) + DECREASES(KYBER_N / 2 - i) + // clang-format on + { + const uint16_t t0 = a->coeffs[2 * i]; + const uint16_t t1 = a->coeffs[2 * i + 1]; + // REF-CHANGE: Precondition change, we assume unsigned canonical data + + // t0 and t1 are both < KYBER_Q, so contain at most 12 bits each of + // significant data, so these can be packed into 24 bits or exactly + // 3 bytes, as follows. + + // Least significant bits 0 - 7 of t0. + r[3 * i + 0] = t0 & 0xFF; + + // Most significant bits 8 - 11 of t0 become the least significant + // nibble of the second byte. The least significant 4 bits + // of t1 become the upper nibble of the second byte. + r[3 * i + 1] = (t0 >> 8) | ((t1 << 4) & 0xF0); + + // Bits 4 - 11 of t1 become the third byte. + r[3 * i + 2] = t1 >> 4; + } } #else /* MLKEM_USE_NATIVE_POLY_TOBYTES */ void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a) { diff --git a/mlkem/poly.h b/mlkem/poly.h index 6eeec2e36..173263ec7 100644 --- a/mlkem/poly.h +++ b/mlkem/poly.h @@ -179,7 +179,12 @@ void poly_decompress(poly *r, const uint8_t a[KYBER_POLYCOMPRESSEDBYTES]) (KYBER_Q - 1))); #define poly_tobytes KYBER_NAMESPACE(poly_tobytes) -void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a); +void poly_tobytes(uint8_t r[KYBER_POLYBYTES], const poly *a) + REQUIRES(a != NULL && IS_FRESH(a, sizeof(poly))) + REQUIRES(ARRAY_IN_BOUNDS(int, k, 0, (KYBER_N - 1), a->coeffs, 0, + (KYBER_Q - 1))) ASSIGNS(OBJECT_WHOLE(r)); + + #define poly_frombytes KYBER_NAMESPACE(poly_frombytes) void poly_frombytes(poly *r, const uint8_t a[KYBER_POLYBYTES]);