Skip to content

Commit 7b50483

Browse files
committed
Adds a declassify operation to aid constant-time analysis.
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.
1 parent 34a67c7 commit 7b50483

File tree

5 files changed

+42
-12
lines changed

5 files changed

+42
-12
lines changed

.travis.yml

+3-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ language: c
22
os: linux
33
addons:
44
apt:
5-
packages: libgmp-dev
5+
packages:
6+
- libgmp-dev
7+
- valgrind
68
compiler:
79
- clang
810
- gcc
@@ -60,18 +62,10 @@ matrix:
6062
env:
6163
- BIGNUM=no ENDOMORPHISM=yes ASM=x86_64 EXPERIMENTAL=yes ECDH=yes RECOVERY=yes
6264
- VALGRIND=yes EXTRAFLAGS="--disable-openssl-tests CPPFLAGS=-DVALGRIND" BUILD=
63-
addons:
64-
apt:
65-
packages:
66-
- valgrind
6765
- compiler: gcc
6866
env: # The same as above but without endomorphism.
6967
- BIGNUM=no ENDOMORPHISM=no ASM=x86_64 EXPERIMENTAL=yes ECDH=yes RECOVERY=yes
7068
- VALGRIND=yes EXTRAFLAGS="--disable-openssl-tests CPPFLAGS=-DVALGRIND" BUILD=
71-
addons:
72-
apt:
73-
packages:
74-
- valgrind
7569

7670
before_script: ./autogen.sh
7771

Makefile.am

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ libsecp256k1_la_SOURCES = src/secp256k1.c
6969
libsecp256k1_la_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/include -I$(top_srcdir)/src $(SECP_INCLUDES)
7070
libsecp256k1_la_LIBADD = $(SECP_LIBS) $(COMMON_LIB)
7171

72+
if VALGRIND_ENABLED
73+
libsecp256k1_la_CPPFLAGS += -DVALGRIND
74+
endif
75+
7276
noinst_PROGRAMS =
7377
if USE_BENCHMARK
7478
noinst_PROGRAMS += bench_verify bench_sign bench_internal bench_ecmult

configure.ac

+3
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ AC_ARG_WITH([ecmult-gen-precision], [AS_HELP_STRING([--with-ecmult-gen-precision
170170

171171
AC_CHECK_TYPES([__int128])
172172

173+
AC_CHECK_HEADER([valgrind/memcheck.h], [enable_valgrind=yes], [enable_valgrind=no], [])
174+
AM_CONDITIONAL([VALGRIND_ENABLED],[test "$enable_valgrind" = "yes"])
175+
173176
if test x"$enable_coverage" = x"yes"; then
174177
AC_DEFINE(COVERAGE, 1, [Define this symbol to compile out all VERIFY code])
175178
CFLAGS="$CFLAGS -O0 --coverage"

include/secp256k1.h

+2
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,14 @@ typedef int (*secp256k1_nonce_function)(
162162
/** The higher bits contain the actual data. Do not use directly. */
163163
#define SECP256K1_FLAGS_BIT_CONTEXT_VERIFY (1 << 8)
164164
#define SECP256K1_FLAGS_BIT_CONTEXT_SIGN (1 << 9)
165+
#define SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY (1 << 10)
165166
#define SECP256K1_FLAGS_BIT_COMPRESSION (1 << 8)
166167

167168
/** Flags to pass to secp256k1_context_create, secp256k1_context_preallocated_size, and
168169
* secp256k1_context_preallocated_create. */
169170
#define SECP256K1_CONTEXT_VERIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
170171
#define SECP256K1_CONTEXT_SIGN (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
172+
#define SECP256K1_CONTEXT_DECLASSIFY (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY)
171173
#define SECP256K1_CONTEXT_NONE (SECP256K1_FLAGS_TYPE_CONTEXT)
172174

173175
/** Flag to pass to secp256k1_ec_pubkey_serialize. */

src/secp256k1.c

+30-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
#include "hash_impl.h"
2121
#include "scratch_impl.h"
2222

23+
#if defined(VALGRIND)
24+
# include <valgrind/memcheck.h>
25+
#endif
26+
2327
#define ARG_CHECK(cond) do { \
2428
if (EXPECT(!(cond), 0)) { \
2529
secp256k1_callback_call(&ctx->illegal_callback, #cond); \
@@ -66,13 +70,15 @@ struct secp256k1_context_struct {
6670
secp256k1_ecmult_gen_context ecmult_gen_ctx;
6771
secp256k1_callback illegal_callback;
6872
secp256k1_callback error_callback;
73+
int declassify;
6974
};
7075

7176
static const secp256k1_context secp256k1_context_no_precomp_ = {
7277
{ 0 },
7378
{ 0 },
7479
{ secp256k1_default_illegal_callback_fn, 0 },
75-
{ secp256k1_default_error_callback_fn, 0 }
80+
{ secp256k1_default_error_callback_fn, 0 },
81+
0
7682
};
7783
const secp256k1_context *secp256k1_context_no_precomp = &secp256k1_context_no_precomp_;
7884

@@ -132,6 +138,7 @@ secp256k1_context* secp256k1_context_preallocated_create(void* prealloc, unsigne
132138
if (flags & SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) {
133139
secp256k1_ecmult_context_build(&ret->ecmult_ctx, &prealloc);
134140
}
141+
ret->declassify = !!(flags & SECP256K1_FLAGS_BIT_CONTEXT_DECLASSIFY);
135142

136143
return (secp256k1_context*) ret;
137144
}
@@ -215,6 +222,20 @@ void secp256k1_scratch_space_destroy(const secp256k1_context *ctx, secp256k1_scr
215222
secp256k1_scratch_destroy(&ctx->error_callback, scratch);
216223
}
217224

225+
/* Mark memory as no-longer-secret for the purpose of analysing constant-time behaviour
226+
* of the software. This is setup for use with valgrind but could be substituted with
227+
* the appropriate instrumentation for other analysis tools.
228+
*/
229+
static SECP256K1_INLINE void secp256k1_declassify(const secp256k1_context* ctx, void *p, size_t len) {
230+
#if defined(VALGRIND)
231+
if (EXPECT(ctx->declassify,0)) VALGRIND_MAKE_MEM_DEFINED(p, len);
232+
#else
233+
(void)ctx;
234+
(void)p;
235+
(void)len;
236+
#endif
237+
}
238+
218239
static int secp256k1_pubkey_load(const secp256k1_context* ctx, secp256k1_ge* ge, const secp256k1_pubkey* pubkey) {
219240
if (sizeof(secp256k1_ge_storage) == 64) {
220241
/* When the secp256k1_ge_storage type is exactly 64 byte, use its
@@ -474,8 +495,14 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature
474495
break;
475496
}
476497
secp256k1_scalar_set_b32(&non, nonce32, &koverflow);
477-
if (!koverflow && !secp256k1_scalar_is_zero(&non)) {
478-
if (secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, NULL)) {
498+
koverflow |= secp256k1_scalar_is_zero(&non);
499+
/* The nonce is still secret here, but it overflowing or being zero is is less likely than 1:2^255. */
500+
secp256k1_declassify(ctx, &koverflow, sizeof(koverflow));
501+
if (!koverflow) {
502+
ret = secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, NULL);
503+
/* The final signature is no longer a secret, nor is the fact that we were successful or not. */
504+
secp256k1_declassify(ctx, &ret, sizeof(ret));
505+
if (ret) {
479506
break;
480507
}
481508
}

0 commit comments

Comments
 (0)