Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions mldsa/src/ct.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,37 @@ static MLD_ALWAYS_INLINE uint32_t mld_cast_int32_to_uint32(int32_t x)
return mld_cast_int64_to_uint32((int64_t)x);
}

/*************************************************
* Name: mld_ct_cmask_nonzero_u32
*
* Description: Return 0 if input is zero, and -1 otherwise.
*
* Arguments: uint32_t x: Value to be converted into a mask
*
**************************************************/
static MLD_INLINE uint32_t mld_ct_cmask_nonzero_u32(uint32_t x)
__contract__(ensures(return_value == ((x == 0) ? 0 : 0xFFFFFFFF)))
{
int64_t tmp = mld_value_barrier_i64(-((int64_t)x));
tmp >>= 32;
return mld_cast_int64_to_uint32(tmp);
}

/*************************************************
* Name: mld_ct_cmask_nonzero_u8
*
* Description: Return 0 if input is zero, and -1 otherwise.
*
* Arguments: uint8_t x: Value to be converted into a mask
*
**************************************************/
static MLD_INLINE uint8_t mld_ct_cmask_nonzero_u8(uint8_t x)
__contract__(ensures(return_value == ((x == 0) ? 0 : 0xFF)))
{
uint32_t mask = mld_ct_cmask_nonzero_u32((uint32_t)x);
return (uint8_t)(mask & 0xFF);
}

/*************************************************
* Name: mld_ct_sel_int32
*
Expand All @@ -205,16 +236,33 @@ static MLD_ALWAYS_INLINE uint32_t mld_cast_int32_to_uint32(int32_t x)
**************************************************/
static MLD_INLINE int32_t mld_ct_sel_int32(int32_t a, int32_t b, uint32_t cond)
__contract__(
requires(cond == 0x0 || cond == 0xFFFFFFFF)
ensures(return_value == (cond ? a : b))
)
{
uint32_t au = mld_cast_int32_to_uint32(a);
uint32_t bu = mld_cast_int32_to_uint32(b);
uint32_t res = bu ^ (mld_value_barrier_u32(cond) & (au ^ bu));
uint32_t res = bu ^ (mld_ct_cmask_nonzero_u32(cond) & (au ^ bu));
return mld_cast_uint32_to_int32(res);
}

/*************************************************
* Name: mld_ct_sel_uint8
*
* Description: Functionally equivalent to cond ? a : b,
* but implemented with guards against
* compiler-introduced branches.
*
* Arguments: uint8_t a: First alternative
* uint8_t b: Second alternative
* uuint8_t cond: Condition variable.
*
**************************************************/
static MLD_INLINE uint8_t mld_ct_sel_uint8(uint8_t a, uint8_t b, uint8_t cond)
__contract__(ensures(return_value == (cond ? a : b)))
{
return b ^ (mld_ct_cmask_nonzero_u8(cond) & (a ^ b));
}

/*************************************************
* Name: mld_ct_cmask_neg_i32
*
Expand Down Expand Up @@ -295,6 +343,36 @@ __contract__(
return (uint8_t)((mld_value_barrier_u32((uint32_t)r) ^ s) ^ s);
}

/*************************************************
* Name: mld_ct_cmov_zero
*
* Description: Copy len bytes from x to r if b is zero;
* don't modify x if b is non-zero.
* assumes two's complement representation of negative integers.
* Runs in constant time.
*
* Arguments: uint8_t *r: pointer to output byte array
* const uint8_t *x: pointer to input byte array
* size_t len: Amount of bytes to be copied
* uint8_t b: Condition value.
*
**************************************************/
static MLD_INLINE void mld_ct_cmov_zero(uint8_t *r, const uint8_t *x,
size_t len, uint8_t b)
__contract__(
requires(len <= MLD_MAX_BUFFER_SIZE)
requires(memory_no_alias(r, len))
requires(memory_no_alias(x, len))
assigns(memory_slice(r, len)))
{
size_t i;
for (i = 0; i < len; i++)
__loop__(invariant(i <= len))
{
r[i] = mld_ct_sel_uint8(r[i], x[i], b);
}
}

/*************************************************
* Name: mld_zeroize
*
Expand Down
68 changes: 20 additions & 48 deletions mldsa/src/sign.c
Original file line number Diff line number Diff line change
Expand Up @@ -782,7 +782,6 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen,
const uint8_t pk[CRYPTO_PUBLICKEYBYTES],
int externalmu)
{
unsigned int i;
int res;
MLD_ALIGN uint8_t buf[MLDSA_K * MLDSA_POLYW1_PACKEDBYTES];
MLD_ALIGN uint8_t rho[MLDSA_SEEDBYTES];
Expand Down Expand Up @@ -853,28 +852,8 @@ int crypto_sign_verify_internal(const uint8_t *sig, size_t siglen,
mld_H(c2, MLDSA_CTILDEBYTES, mu, MLDSA_CRHBYTES, buf,
MLDSA_K * MLDSA_POLYW1_PACKEDBYTES, NULL, 0);

/* Constant time: All data in verification is usually considered public.
* However, in our constant-time tests we do not declassify the message and
* context string.
* The following conditional is the only place in verification whose run-time
* depends on the message. As all that can be leakaged here is the output of
* a hash call (that should behave like a random oracle), it is safe to
* declassify here even with a secret message.
*/
MLD_CT_TESTING_DECLASSIFY(c2, sizeof(c2));
for (i = 0; i < MLDSA_CTILDEBYTES; ++i)
__loop__(
invariant(i <= MLDSA_CTILDEBYTES)
)
{
if (c[i] != c2[i])
{
res = -1;
goto cleanup;
}
}

res = 0;
/* Return 0 if c == c2, -1 otherwise */
res = mld_ct_sel_int32(-1, 0, mld_ct_memcmp(c, c2, MLDSA_CTILDEBYTES));

cleanup:
/* @[FIPS204, Section 3.6.3] Destruction of intermediate values. */
Expand Down Expand Up @@ -938,38 +917,31 @@ int crypto_sign_open(uint8_t *m, size_t *mlen, const uint8_t *sm, size_t smlen,
const uint8_t pk[CRYPTO_PUBLICKEYBYTES])
{
size_t i;
int result;
size_t mlen2;

if (smlen < CRYPTO_BYTES)
{
goto badsig;
*mlen = 0;
mld_memset(m, 0, smlen);
return -1;
}

*mlen = smlen - CRYPTO_BYTES;
if (crypto_sign_verify(sm, CRYPTO_BYTES, sm + CRYPTO_BYTES, *mlen, ctx,
ctxlen, pk))
{
goto badsig;
}
else
mlen2 = smlen - CRYPTO_BYTES;
*mlen = 0;
result = crypto_sign_verify(sm, CRYPTO_BYTES, sm + CRYPTO_BYTES, mlen2, ctx,
ctxlen, pk);
for (i = 0; i < mlen2; ++i)
__loop__(
assigns(i, memory_slice(m, mlen2))
invariant(i <= mlen2)
)
{
/* All good, copy msg, return 0 */
for (i = 0; i < *mlen; ++i)
__loop__(
assigns(i, memory_slice(m, *mlen))
invariant(i <= *mlen)
)
{
m[i] = sm[CRYPTO_BYTES + i];
}
return 0;
m[i] = mld_ct_sel_uint8(0, sm[CRYPTO_BYTES + i], (uint8_t)result);
}

badsig:
/* Signature verification failed */
*mlen = 0;
mld_memset(m, 0, smlen);

return -1;
mld_ct_cmov_zero((uint8_t *)mlen, (uint8_t *)&mlen2, sizeof(size_t),
(uint8_t)result);
return result;
}


Expand Down
2 changes: 2 additions & 0 deletions proofs/cbmc/crypto_sign_verify_internal/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)polyveck_caddq
USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)polyveck_use_hint
USE_FUNCTION_CONTRACTS+=$(MLD_NAMESPACE)polyveck_pack_w1
USE_FUNCTION_CONTRACTS+=mld_zeroize
USE_FUNCTION_CONTRACTS+=mld_ct_memcmp
USE_FUNCTION_CONTRACTS+=mld_ct_sel_int32

APPLY_LOOP_CONTRACTS=on
USE_DYNAMIC_FRAMES=1
Expand Down
55 changes: 55 additions & 0 deletions proofs/cbmc/ct_cmask_nonzero_u32/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright (c) The mldsa-native project authors
# SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT

include ../Makefile_params.common

HARNESS_ENTRY = harness
HARNESS_FILE = ct_cmask_nonzero_u32_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 = mld_ct_cmask_nonzero_u32

DEFINES += -DMLD_CONFIG_NO_ASM_VALUE_BARRIER
INCLUDES +=

REMOVE_FUNCTION_BODY +=
UNWINDSET +=

PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
PROJECT_SOURCES += $(SRCDIR)/mldsa/src/ct.c

CHECK_FUNCTION_CONTRACTS=mld_ct_cmask_nonzero_u32
USE_FUNCTION_CONTRACTS=mld_value_barrier_i64
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 = mld_ct_cmask_nonzero_u32

# 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
# ("mldsa/poly.c") in PROJECT_SOURCES).
# REWRITTEN_SOURCES = $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i
# include ../Makefile.common
# $(PROOFDIR)/<__SOURCE_FILE_BASENAME__>.i_SOURCE = $(SRCDIR)/mldsa/src/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
10 changes: 10 additions & 0 deletions proofs/cbmc/ct_cmask_nonzero_u32/ct_cmask_nonzero_u32_harness.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) The mldsa-native project authors
// SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT

#include "ct.h"

void harness(void)
{
uint32_t cond;
mld_ct_cmask_nonzero_u32(cond);
}
2 changes: 1 addition & 1 deletion proofs/cbmc/ct_sel_int32/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
PROJECT_SOURCES += $(SRCDIR)/mldsa/src/ct.c

CHECK_FUNCTION_CONTRACTS=mld_ct_sel_int32
USE_FUNCTION_CONTRACTS=mld_value_barrier_u32
USE_FUNCTION_CONTRACTS=mld_ct_cmask_nonzero_u32
APPLY_LOOP_CONTRACTS=on
USE_DYNAMIC_FRAMES=1

Expand Down
Loading