Skip to content

Commit 9d560f9

Browse files
committed
Merge #428: Exhaustive recovery
2cee5fd exhaustive tests: add recovery module (Andrew Poelstra) 678b0e5 exhaustive tests: remove erroneous comment from ecdsa_sig_sign (Andrew Poelstra) 03ff8c2 group_impl.h: remove unused `secp256k1_ge_set_infinity` function (Andrew Poelstra) a724d72 configure: add --enable-coverage to set options for coverage analysis (Andrew Poelstra) b595163 recovery: add tests to cover API misusage (Andrew Poelstra) 6f8ae2f ecdh: test NULL-checking of arguments (Andrew Poelstra) 25e3cfb ecdsa_impl: replace scalar if-checks with VERIFY_CHECKs in ecdsa_sig_sign (Andrew Poelstra)
2 parents 8225239 + 2cee5fd commit 9d560f9

File tree

12 files changed

+350
-32
lines changed

12 files changed

+350
-32
lines changed

Makefile.am

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ TESTS =
9393
if USE_TESTS
9494
noinst_PROGRAMS += tests
9595
tests_SOURCES = src/tests.c
96-
tests_CPPFLAGS = -DSECP256K1_BUILD -DVERIFY -I$(top_srcdir)/src -I$(top_srcdir)/include $(SECP_INCLUDES) $(SECP_TEST_INCLUDES)
96+
tests_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/src -I$(top_srcdir)/include $(SECP_INCLUDES) $(SECP_TEST_INCLUDES)
97+
if !ENABLE_COVERAGE
98+
tests_CPPFLAGS += -DVERIFY
99+
endif
97100
tests_LDADD = $(SECP_LIBS) $(SECP_TEST_LIBS) $(COMMON_LIB)
98101
tests_LDFLAGS = -static
99102
TESTS += tests
@@ -102,7 +105,10 @@ endif
102105
if USE_EXHAUSTIVE_TESTS
103106
noinst_PROGRAMS += exhaustive_tests
104107
exhaustive_tests_SOURCES = src/tests_exhaustive.c
105-
exhaustive_tests_CPPFLAGS = -DSECP256K1_BUILD -DVERIFY -I$(top_srcdir)/src $(SECP_INCLUDES)
108+
exhaustive_tests_CPPFLAGS = -DSECP256K1_BUILD -I$(top_srcdir)/src $(SECP_INCLUDES)
109+
if !ENABLE_COVERAGE
110+
exhaustive_tests_CPPFLAGS += -DVERIFY
111+
endif
106112
exhaustive_tests_LDADD = $(SECP_LIBS)
107113
exhaustive_tests_LDFLAGS = -static
108114
TESTS += exhaustive_tests

configure.ac

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ AC_PATH_TOOL(STRIP, strip)
2020
AX_PROG_CC_FOR_BUILD
2121

2222
if test "x$CFLAGS" = "x"; then
23-
CFLAGS="-O3 -g"
23+
CFLAGS="-g"
2424
fi
2525

2626
AM_PROG_CC_C_O
@@ -89,6 +89,11 @@ AC_ARG_ENABLE(benchmark,
8989
[use_benchmark=$enableval],
9090
[use_benchmark=no])
9191

92+
AC_ARG_ENABLE(coverage,
93+
AS_HELP_STRING([--enable-coverage],[enable compiler flags to support kcov coverage analysis]),
94+
[enable_coverage=$enableval],
95+
[enable_coverage=no])
96+
9297
AC_ARG_ENABLE(tests,
9398
AS_HELP_STRING([--enable-tests],[compile tests (default is yes)]),
9499
[use_tests=$enableval],
@@ -154,6 +159,14 @@ AC_COMPILE_IFELSE([AC_LANG_SOURCE([[void myfunc() {__builtin_expect(0,0);}]])],
154159
[ AC_MSG_RESULT([no])
155160
])
156161

162+
if test x"$enable_coverage" = x"yes"; then
163+
AC_DEFINE(COVERAGE, 1, [Define this symbol to compile out all VERIFY code])
164+
CFLAGS="$CFLAGS -O0 --coverage"
165+
LDFLAGS="--coverage"
166+
else
167+
CFLAGS="$CFLAGS -O3"
168+
fi
169+
157170
if test x"$use_ecmult_static_precomputation" != x"no"; then
158171
save_cross_compiling=$cross_compiling
159172
cross_compiling=no
@@ -434,6 +447,7 @@ AC_MSG_NOTICE([Using field implementation: $set_field])
434447
AC_MSG_NOTICE([Using bignum implementation: $set_bignum])
435448
AC_MSG_NOTICE([Using scalar implementation: $set_scalar])
436449
AC_MSG_NOTICE([Using endomorphism optimizations: $use_endomorphism])
450+
AC_MSG_NOTICE([Building for coverage analysis: $enable_coverage])
437451
AC_MSG_NOTICE([Building ECDH module: $enable_module_ecdh])
438452
AC_MSG_NOTICE([Building ECDSA pubkey recovery module: $enable_module_recovery])
439453
AC_MSG_NOTICE([Using jni: $use_jni])
@@ -460,6 +474,7 @@ AC_SUBST(SECP_INCLUDES)
460474
AC_SUBST(SECP_LIBS)
461475
AC_SUBST(SECP_TEST_LIBS)
462476
AC_SUBST(SECP_TEST_INCLUDES)
477+
AM_CONDITIONAL([ENABLE_COVERAGE], [test x"$enable_coverage" = x"yes"])
463478
AM_CONDITIONAL([USE_TESTS], [test x"$use_tests" != x"no"])
464479
AM_CONDITIONAL([USE_EXHAUSTIVE_TESTS], [test x"$use_exhaustive_tests" != x"no"])
465480
AM_CONDITIONAL([USE_BENCHMARK], [test x"$use_benchmark" = x"yes"])

src/ecdsa_impl.h

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,12 @@ static int secp256k1_ecdsa_sig_verify(const secp256k1_ecmult_context *ctx, const
225225
#if defined(EXHAUSTIVE_TEST_ORDER)
226226
{
227227
secp256k1_scalar computed_r;
228-
int overflow = 0;
229228
secp256k1_ge pr_ge;
230229
secp256k1_ge_set_gej(&pr_ge, &pr);
231230
secp256k1_fe_normalize(&pr_ge.x);
232231

233232
secp256k1_fe_get_b32(c, &pr_ge.x);
234-
secp256k1_scalar_set_b32(&computed_r, c, &overflow);
235-
/* we fully expect overflow */
233+
secp256k1_scalar_set_b32(&computed_r, c, NULL);
236234
return secp256k1_scalar_eq(sigr, &computed_r);
237235
}
238236
#else
@@ -285,14 +283,10 @@ static int secp256k1_ecdsa_sig_sign(const secp256k1_ecmult_gen_context *ctx, sec
285283
secp256k1_fe_normalize(&r.y);
286284
secp256k1_fe_get_b32(b, &r.x);
287285
secp256k1_scalar_set_b32(sigr, b, &overflow);
288-
if (secp256k1_scalar_is_zero(sigr)) {
289-
/* P.x = order is on the curve, so technically sig->r could end up zero, which would be an invalid signature.
290-
* This branch is cryptographically unreachable as hitting it requires finding the discrete log of P.x = N.
291-
*/
292-
secp256k1_gej_clear(&rp);
293-
secp256k1_ge_clear(&r);
294-
return 0;
295-
}
286+
/* These two conditions should be checked before calling */
287+
VERIFY_CHECK(!secp256k1_scalar_is_zero(sigr));
288+
VERIFY_CHECK(overflow == 0);
289+
296290
if (recid) {
297291
/* The overflow condition is cryptographically unreachable as hitting it requires finding the discrete log
298292
* of some P where P.x >= order, and only 1 in about 2^127 points meet this criteria.

src/field_10x26_impl.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ static void secp256k1_fe_verify(const secp256k1_fe *a) {
3838
}
3939
VERIFY_CHECK(r == 1);
4040
}
41-
#else
42-
static void secp256k1_fe_verify(const secp256k1_fe *a) {
43-
(void)a;
44-
}
4541
#endif
4642

4743
static void secp256k1_fe_normalize(secp256k1_fe *r) {

src/field_5x52_impl.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ static void secp256k1_fe_verify(const secp256k1_fe *a) {
4949
}
5050
VERIFY_CHECK(r == 1);
5151
}
52-
#else
53-
static void secp256k1_fe_verify(const secp256k1_fe *a) {
54-
(void)a;
55-
}
5652
#endif
5753

5854
static void secp256k1_fe_normalize(secp256k1_fe *r) {

src/group_impl.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,6 @@ static void secp256k1_gej_set_infinity(secp256k1_gej *r) {
200200
secp256k1_fe_clear(&r->z);
201201
}
202202

203-
static void secp256k1_ge_set_infinity(secp256k1_ge *r) {
204-
r->infinity = 1;
205-
secp256k1_fe_clear(&r->x);
206-
secp256k1_fe_clear(&r->y);
207-
}
208-
209203
static void secp256k1_gej_clear(secp256k1_gej *r) {
210204
r->infinity = 0;
211205
secp256k1_fe_clear(&r->x);

src/modules/ecdh/main_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ int secp256k1_ecdh(const secp256k1_context* ctx, unsigned char *result, const se
1616
secp256k1_gej res;
1717
secp256k1_ge pt;
1818
secp256k1_scalar s;
19+
VERIFY_CHECK(ctx != NULL);
1920
ARG_CHECK(result != NULL);
2021
ARG_CHECK(point != NULL);
2122
ARG_CHECK(scalar != NULL);
22-
(void)ctx;
2323

2424
secp256k1_pubkey_load(ctx, &pt, point);
2525
secp256k1_scalar_set_b32(&s, scalar, &overflow);

src/modules/ecdh/tests_impl.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,35 @@
77
#ifndef _SECP256K1_MODULE_ECDH_TESTS_
88
#define _SECP256K1_MODULE_ECDH_TESTS_
99

10+
void test_ecdh_api(void) {
11+
/* Setup context that just counts errors */
12+
secp256k1_context *tctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
13+
secp256k1_pubkey point;
14+
unsigned char res[32];
15+
unsigned char s_one[32] = { 0 };
16+
int32_t ecount = 0;
17+
s_one[31] = 1;
18+
19+
secp256k1_context_set_error_callback(tctx, counting_illegal_callback_fn, &ecount);
20+
secp256k1_context_set_illegal_callback(tctx, counting_illegal_callback_fn, &ecount);
21+
CHECK(secp256k1_ec_pubkey_create(tctx, &point, s_one) == 1);
22+
23+
/* Check all NULLs are detected */
24+
CHECK(secp256k1_ecdh(tctx, res, &point, s_one) == 1);
25+
CHECK(ecount == 0);
26+
CHECK(secp256k1_ecdh(tctx, NULL, &point, s_one) == 0);
27+
CHECK(ecount == 1);
28+
CHECK(secp256k1_ecdh(tctx, res, NULL, s_one) == 0);
29+
CHECK(ecount == 2);
30+
CHECK(secp256k1_ecdh(tctx, res, &point, NULL) == 0);
31+
CHECK(ecount == 3);
32+
CHECK(secp256k1_ecdh(tctx, res, &point, s_one) == 1);
33+
CHECK(ecount == 3);
34+
35+
/* Cleanup */
36+
secp256k1_context_destroy(tctx);
37+
}
38+
1039
void test_ecdh_generator_basepoint(void) {
1140
unsigned char s_one[32] = { 0 };
1241
secp256k1_pubkey point[2];
@@ -68,6 +97,7 @@ void test_bad_scalar(void) {
6897
}
6998

7099
void run_ecdh_tests(void) {
100+
test_ecdh_api();
71101
test_ecdh_generator_basepoint();
72102
test_bad_scalar();
73103
}

src/modules/recovery/main_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ int secp256k1_ecdsa_recover(const secp256k1_context* ctx, secp256k1_pubkey *pubk
179179
ARG_CHECK(pubkey != NULL);
180180

181181
secp256k1_ecdsa_recoverable_signature_load(ctx, &r, &s, &recid, signature);
182-
ARG_CHECK(recid >= 0 && recid < 4);
182+
VERIFY_CHECK(recid >= 0 && recid < 4); /* should have been caught in parse_compact */
183183
secp256k1_scalar_set_b32(&m, msg32, NULL);
184184
if (secp256k1_ecdsa_sig_recover(&ctx->ecmult_ctx, &r, &s, &q, &m, recid)) {
185185
secp256k1_pubkey_save(pubkey, &q);

src/modules/recovery/tests_impl.h

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,146 @@
77
#ifndef _SECP256K1_MODULE_RECOVERY_TESTS_
88
#define _SECP256K1_MODULE_RECOVERY_TESTS_
99

10+
static int recovery_test_nonce_function(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) {
11+
(void) msg32;
12+
(void) key32;
13+
(void) algo16;
14+
(void) data;
15+
16+
/* On the first run, return 0 to force a second run */
17+
if (counter == 0) {
18+
memset(nonce32, 0, 32);
19+
return 1;
20+
}
21+
/* On the second run, return an overflow to force a third run */
22+
if (counter == 1) {
23+
memset(nonce32, 0xff, 32);
24+
return 1;
25+
}
26+
/* On the next run, return a valid nonce, but flip a coin as to whether or not to fail signing. */
27+
memset(nonce32, 1, 32);
28+
return secp256k1_rand_bits(1);
29+
}
30+
31+
void test_ecdsa_recovery_api(void) {
32+
/* Setup contexts that just count errors */
33+
secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
34+
secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
35+
secp256k1_context *vrfy = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY);
36+
secp256k1_context *both = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
37+
secp256k1_pubkey pubkey;
38+
secp256k1_pubkey recpubkey;
39+
secp256k1_ecdsa_signature normal_sig;
40+
secp256k1_ecdsa_recoverable_signature recsig;
41+
unsigned char privkey[32] = { 1 };
42+
unsigned char message[32] = { 2 };
43+
int32_t ecount = 0;
44+
int recid = 0;
45+
unsigned char sig[74];
46+
unsigned char zero_privkey[32] = { 0 };
47+
unsigned char over_privkey[32] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
48+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
49+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
50+
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
51+
52+
secp256k1_context_set_error_callback(none, counting_illegal_callback_fn, &ecount);
53+
secp256k1_context_set_error_callback(sign, counting_illegal_callback_fn, &ecount);
54+
secp256k1_context_set_error_callback(vrfy, counting_illegal_callback_fn, &ecount);
55+
secp256k1_context_set_error_callback(both, counting_illegal_callback_fn, &ecount);
56+
secp256k1_context_set_illegal_callback(none, counting_illegal_callback_fn, &ecount);
57+
secp256k1_context_set_illegal_callback(sign, counting_illegal_callback_fn, &ecount);
58+
secp256k1_context_set_illegal_callback(vrfy, counting_illegal_callback_fn, &ecount);
59+
secp256k1_context_set_illegal_callback(both, counting_illegal_callback_fn, &ecount);
60+
61+
/* Construct and verify corresponding public key. */
62+
CHECK(secp256k1_ec_seckey_verify(ctx, privkey) == 1);
63+
CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1);
64+
65+
/* Check bad contexts and NULLs for signing */
66+
ecount = 0;
67+
CHECK(secp256k1_ecdsa_sign_recoverable(none, &recsig, message, privkey, NULL, NULL) == 0);
68+
CHECK(ecount == 1);
69+
CHECK(secp256k1_ecdsa_sign_recoverable(sign, &recsig, message, privkey, NULL, NULL) == 1);
70+
CHECK(ecount == 1);
71+
CHECK(secp256k1_ecdsa_sign_recoverable(vrfy, &recsig, message, privkey, NULL, NULL) == 0);
72+
CHECK(ecount == 2);
73+
CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1);
74+
CHECK(ecount == 2);
75+
CHECK(secp256k1_ecdsa_sign_recoverable(both, NULL, message, privkey, NULL, NULL) == 0);
76+
CHECK(ecount == 3);
77+
CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, NULL, privkey, NULL, NULL) == 0);
78+
CHECK(ecount == 4);
79+
CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, NULL, NULL, NULL) == 0);
80+
CHECK(ecount == 5);
81+
/* This will fail or succeed randomly, and in either case will not ARG_CHECK failure */
82+
secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, recovery_test_nonce_function, NULL);
83+
CHECK(ecount == 5);
84+
/* These will all fail, but not in ARG_CHECK way */
85+
CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, zero_privkey, NULL, NULL) == 0);
86+
CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, over_privkey, NULL, NULL) == 0);
87+
/* This one will succeed. */
88+
CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1);
89+
CHECK(ecount == 5);
90+
91+
/* Check signing with a goofy nonce function */
92+
93+
/* Check bad contexts and NULLs for recovery */
94+
ecount = 0;
95+
CHECK(secp256k1_ecdsa_recover(none, &recpubkey, &recsig, message) == 0);
96+
CHECK(ecount == 1);
97+
CHECK(secp256k1_ecdsa_recover(sign, &recpubkey, &recsig, message) == 0);
98+
CHECK(ecount == 2);
99+
CHECK(secp256k1_ecdsa_recover(vrfy, &recpubkey, &recsig, message) == 1);
100+
CHECK(ecount == 2);
101+
CHECK(secp256k1_ecdsa_recover(both, &recpubkey, &recsig, message) == 1);
102+
CHECK(ecount == 2);
103+
CHECK(secp256k1_ecdsa_recover(both, NULL, &recsig, message) == 0);
104+
CHECK(ecount == 3);
105+
CHECK(secp256k1_ecdsa_recover(both, &recpubkey, NULL, message) == 0);
106+
CHECK(ecount == 4);
107+
CHECK(secp256k1_ecdsa_recover(both, &recpubkey, &recsig, NULL) == 0);
108+
CHECK(ecount == 5);
109+
110+
/* Check NULLs for conversion */
111+
CHECK(secp256k1_ecdsa_sign(both, &normal_sig, message, privkey, NULL, NULL) == 1);
112+
ecount = 0;
113+
CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, NULL, &recsig) == 0);
114+
CHECK(ecount == 1);
115+
CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, &normal_sig, NULL) == 0);
116+
CHECK(ecount == 2);
117+
CHECK(secp256k1_ecdsa_recoverable_signature_convert(both, &normal_sig, &recsig) == 1);
118+
119+
/* Check NULLs for de/serialization */
120+
CHECK(secp256k1_ecdsa_sign_recoverable(both, &recsig, message, privkey, NULL, NULL) == 1);
121+
ecount = 0;
122+
CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, NULL, &recid, &recsig) == 0);
123+
CHECK(ecount == 1);
124+
CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, NULL, &recsig) == 0);
125+
CHECK(ecount == 2);
126+
CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, &recid, NULL) == 0);
127+
CHECK(ecount == 3);
128+
CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(both, sig, &recid, &recsig) == 1);
129+
130+
CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, NULL, sig, recid) == 0);
131+
CHECK(ecount == 4);
132+
CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, NULL, recid) == 0);
133+
CHECK(ecount == 5);
134+
CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, -1) == 0);
135+
CHECK(ecount == 6);
136+
CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, 5) == 0);
137+
CHECK(ecount == 7);
138+
/* overflow in signature will fail but not affect ecount */
139+
memcpy(sig, over_privkey, 32);
140+
CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(both, &recsig, sig, recid) == 0);
141+
CHECK(ecount == 7);
142+
143+
/* cleanup */
144+
secp256k1_context_destroy(none);
145+
secp256k1_context_destroy(sign);
146+
secp256k1_context_destroy(vrfy);
147+
secp256k1_context_destroy(both);
148+
}
149+
10150
void test_ecdsa_recovery_end_to_end(void) {
11151
unsigned char extra[32] = {0x00};
12152
unsigned char privkey[32];
@@ -241,6 +381,9 @@ void test_ecdsa_recovery_edge_cases(void) {
241381

242382
void run_recovery_tests(void) {
243383
int i;
384+
for (i = 0; i < count; i++) {
385+
test_ecdsa_recovery_api();
386+
}
244387
for (i = 0; i < 64*count; i++) {
245388
test_ecdsa_recovery_end_to_end();
246389
}

0 commit comments

Comments
 (0)