Skip to content

Commit

Permalink
Adds a declassify operation to aid constant-time analysis.
Browse files Browse the repository at this point in the history
ECDSA signing has a retry loop for the exceptionally unlikely case
 that S==0.  S is not a secret at this point and this case is so
 rare that it will never be observed but branching on it will trip
 up tools analysing if the code is constant time with respect to
 secrets.

Derandomized ECDSA can also loop on k being zero or overflowing,
 and while k is a secret these cases are too rare (1:2^255) to
 ever observe and are also of no concern.

This adds a function for marking memory as no-longer-secret and
 sets it up for use with the valgrind memcheck constant-time
 test.
  • Loading branch information
gmaxwell committed Feb 20, 2020
1 parent 34a67c7 commit 7b50483
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 12 deletions.
12 changes: 3 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ language: c
os: linux
addons:
apt:
packages: libgmp-dev
packages:
- libgmp-dev
- valgrind
compiler:
- clang
- gcc
Expand Down Expand Up @@ -60,18 +62,10 @@ matrix:
env:
- BIGNUM=no ENDOMORPHISM=yes ASM=x86_64 EXPERIMENTAL=yes ECDH=yes RECOVERY=yes
- VALGRIND=yes EXTRAFLAGS="--disable-openssl-tests CPPFLAGS=-DVALGRIND" BUILD=
addons:
apt:
packages:
- valgrind
- compiler: gcc
env: # The same as above but without endomorphism.
- BIGNUM=no ENDOMORPHISM=no ASM=x86_64 EXPERIMENTAL=yes ECDH=yes RECOVERY=yes
- VALGRIND=yes EXTRAFLAGS="--disable-openssl-tests CPPFLAGS=-DVALGRIND" BUILD=
addons:
apt:
packages:
- valgrind

before_script: ./autogen.sh

Expand Down
4 changes: 4 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ libsecp256k1_la_SOURCES = src/secp256k1.c
libsecp256k1_la_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/include -I$(top_srcdir)/src $(SECP_INCLUDES)
libsecp256k1_la_LIBADD = $(SECP_LIBS) $(COMMON_LIB)

if VALGRIND_ENABLED
libsecp256k1_la_CPPFLAGS += -DVALGRIND
endif

noinst_PROGRAMS =
if USE_BENCHMARK
noinst_PROGRAMS += bench_verify bench_sign bench_internal bench_ecmult
Expand Down
3 changes: 3 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ AC_ARG_WITH([ecmult-gen-precision], [AS_HELP_STRING([--with-ecmult-gen-precision

AC_CHECK_TYPES([__int128])

AC_CHECK_HEADER([valgrind/memcheck.h], [enable_valgrind=yes], [enable_valgrind=no], [])
AM_CONDITIONAL([VALGRIND_ENABLED],[test "$enable_valgrind" = "yes"])

if test x"$enable_coverage" = x"yes"; then
AC_DEFINE(COVERAGE, 1, [Define this symbol to compile out all VERIFY code])
CFLAGS="$CFLAGS -O0 --coverage"
Expand Down
2 changes: 2 additions & 0 deletions include/secp256k1.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,14 @@ typedef int (*secp256k1_nonce_function)(
/** The higher bits contain the actual data. Do not use directly. */
#define SECP256K1_FLAGS_BIT_CONTEXT_VERIFY (1 << 8)
#define SECP256K1_FLAGS_BIT_CONTEXT_SIGN (1 << 9)
#define SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY (1 << 10)
#define SECP256K1_FLAGS_BIT_COMPRESSION (1 << 8)

/** Flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and
* secp256k1_context_preallocated_create. */
#define SECP256K1_CONTEXT_VERIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
#define SECP256K1_CONTEXT_SIGN (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
#define SECP256K1_CONTEXT_DECLASSIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY)
#define SECP256K1_CONTEXT_NONE (SECP256K1_FLAGS_TYPE_CONTEXT)

/** Flag to pass to secp256k1_ec_pubkey_serialize. */
Expand Down
33 changes: 30 additions & 3 deletions src/secp256k1.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
#include "hash_impl.h"
#include "scratch_impl.h"

#if defined(VALGRIND)
# include <valgrind/memcheck.h>
#endif

#define ARG_CHECK(cond) do { \
if (EXPECT(!(cond), 0)) { \
secp256k1_callback_call(&ctx->illegal_callback, #cond); \
Expand Down Expand Up @@ -66,13 +70,15 @@ struct secp256k1_context_struct {
secp256k1_ecmult_gen_context ecmult_gen_ctx;
secp256k1_callback illegal_callback;
secp256k1_callback error_callback;
int declassify;
};

static const secp256k1_context secp256k1_context_no_precomp_ = {
{ 0 },
{ 0 },
{ secp256k1_default_illegal_callback_fn, 0 },
{ secp256k1_default_error_callback_fn, 0 }
{ secp256k1_default_error_callback_fn, 0 },
0
};
const secp256k1_context *secp256k1_context_no_precomp = &secp256k1_context_no_precomp_;

Expand Down Expand Up @@ -132,6 +138,7 @@ secp256k1_context* secp256k1_context_preallocated_create(void* prealloc, unsigne
if (flags & SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) {
secp256k1_ecmult_context_build(&ret->ecmult_ctx, &prealloc);
}
ret->declassify = !!(flags & SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY);

return (secp256k1_context*) ret;
}
Expand Down Expand Up @@ -215,6 +222,20 @@ void secp256k1_scratch_space_destroy(const secp256k1_context *ctx, secp256k1_scr
secp256k1_scratch_destroy(&ctx->error_callback, scratch);
}

/* Mark memory as no-longer-secret for the purpose of analysing constant-time behaviour
* of the software. This is setup for use with valgrind but could be substituted with
* the appropriate instrumentation for other analysis tools.
*/
static SECP256K1_INLINE void secp256k1_declassify(const secp256k1_context* ctx, void *p, size_t len) {
#if defined(VALGRIND)
if (EXPECT(ctx->declassify,0)) VALGRIND_MAKE_MEM_DEFINED(p, len);
#else
(void)ctx;
(void)p;
(void)len;
#endif
}

static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey) {
if (sizeof(secp256k1_ge_storage) == 64) {
/* When the secp256k1_ge_storage type is exactly 64 byte, use its
Expand Down Expand Up @@ -474,8 +495,14 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature
break;
}
secp256k1_scalar_set_b32(&non, nonce32, &koverflow);
if (!koverflow && !secp256k1_scalar_is_zero(&non)) {
if (secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, NULL)) {
koverflow |= secp256k1_scalar_is_zero(&non);
/* The nonce is still secret here, but it overflowing or being zero is is less likely than 1:2^255. */
secp256k1_declassify(ctx, &koverflow, sizeof(koverflow));
if (!koverflow) {
ret = secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, NULL);
/* The final signature is no longer a secret, nor is the fact that we were successful or not. */
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (ret) {
break;
}
}
Expand Down

0 comments on commit 7b50483

Please sign in to comment.