Skip to content

Commit 2ed54da

Browse files
Merge bitcoin#755: Recovery signing: add to constant time test, and eliminate non ct operators
2860950 Add tests for the cmov implementations (Elichai Turkel) 73596a8 Add ecdsa_sign_recoverable to the ctime tests (Elichai Turkel) 2876af4 Split ecdsa_sign logic into a new function and use it from ecdsa_sign and recovery (Elichai Turkel) Pull request description: Hi, The recovery module was overlooked in bitcoin#708 and bitcoin#710, so this adds it to the `valgrind_ctime_test` and replaces the secret dependent branching with the cmovs, I created a new function `secp256k1_ecdsa_sign_inner` (feel free to bikeshed) which does the logic both for ecdsa_sign and for ecdsa_sign_recoverable, such that next time when things get changed/improved in ecdsa it will affect the recoverable signing too. ACKs for top commit: jonasnick: ACK 2860950 real-or-random: ACK 2860950 read the diff, tested with valgrind including ctime tests Tree-SHA512: 4730301dcb62241d79f18eb8fed7e9ab0e20d1663a788832cb6cf4126baa7075807dc31896764b6f82d52742fdb636abc6b75e4344c6f117305904c628a5ad59
2 parents 5e1c885 + 2860950 commit 2ed54da

File tree

5 files changed

+221
-48
lines changed

5 files changed

+221
-48
lines changed

src/modules/recovery/main_impl.h

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -122,48 +122,15 @@ static int secp256k1_ecdsa_sig_recover(const secp256k1_ecmult_context *ctx, cons
122122

123123
int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) {
124124
secp256k1_scalar r, s;
125-
secp256k1_scalar sec, non, msg;
126-
int recid;
127-
int ret = 0;
128-
int overflow = 0;
125+
int ret, recid;
129126
VERIFY_CHECK(ctx != NULL);
130127
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
131128
ARG_CHECK(msg32 != NULL);
132129
ARG_CHECK(signature != NULL);
133130
ARG_CHECK(seckey != NULL);
134-
if (noncefp == NULL) {
135-
noncefp = secp256k1_nonce_function_default;
136-
}
137131

138-
secp256k1_scalar_set_b32(&sec, seckey, &overflow);
139-
/* Fail if the secret key is invalid. */
140-
if (!overflow && !secp256k1_scalar_is_zero(&sec)) {
141-
unsigned char nonce32[32];
142-
unsigned int count = 0;
143-
secp256k1_scalar_set_b32(&msg, msg32, NULL);
144-
while (1) {
145-
ret = noncefp(nonce32, msg32, seckey, NULL, (void*)noncedata, count);
146-
if (!ret) {
147-
break;
148-
}
149-
secp256k1_scalar_set_b32(&non, nonce32, &overflow);
150-
if (!overflow && !secp256k1_scalar_is_zero(&non)) {
151-
if (secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, &recid)) {
152-
break;
153-
}
154-
}
155-
count++;
156-
}
157-
memset(nonce32, 0, 32);
158-
secp256k1_scalar_clear(&msg);
159-
secp256k1_scalar_clear(&non);
160-
secp256k1_scalar_clear(&sec);
161-
}
162-
if (ret) {
163-
secp256k1_ecdsa_recoverable_signature_save(signature, &r, &s, recid);
164-
} else {
165-
memset(signature, 0, sizeof(*signature));
166-
}
132+
ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, &recid, msg32, seckey, noncefp, noncedata);
133+
secp256k1_ecdsa_recoverable_signature_save(signature, &r, &s, recid);
167134
return ret;
168135
}
169136

src/secp256k1.c

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -467,19 +467,18 @@ static int nonce_function_rfc6979(unsigned char *nonce32, const unsigned char *m
467467
const secp256k1_nonce_function secp256k1_nonce_function_rfc6979 = nonce_function_rfc6979;
468468
const secp256k1_nonce_function secp256k1_nonce_function_default = nonce_function_rfc6979;
469469

470-
int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) {
471-
/* Default initialization here is important so we won't pass uninit values to the cmov in the end */
472-
secp256k1_scalar r = secp256k1_scalar_zero, s = secp256k1_scalar_zero;
470+
static int secp256k1_ecdsa_sign_inner(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) {
473471
secp256k1_scalar sec, non, msg;
474472
int ret = 0;
475473
int is_sec_valid;
476474
unsigned char nonce32[32];
477475
unsigned int count = 0;
478-
VERIFY_CHECK(ctx != NULL);
479-
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
480-
ARG_CHECK(msg32 != NULL);
481-
ARG_CHECK(signature != NULL);
482-
ARG_CHECK(seckey != NULL);
476+
/* Default initialization here is important so we won't pass uninit values to the cmov in the end */
477+
*r = secp256k1_scalar_zero;
478+
*s = secp256k1_scalar_zero;
479+
if (recid) {
480+
*recid = 0;
481+
}
483482
if (noncefp == NULL) {
484483
noncefp = secp256k1_nonce_function_default;
485484
}
@@ -498,7 +497,7 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature
498497
/* The nonce is still secret here, but it being invalid is is less likely than 1:2^255. */
499498
secp256k1_declassify(ctx, &is_nonce_valid, sizeof(is_nonce_valid));
500499
if (is_nonce_valid) {
501-
ret = secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, &r, &s, &sec, &msg, &non, NULL);
500+
ret = secp256k1_ecdsa_sig_sign(&ctx->ecmult_gen_ctx, r, s, &sec, &msg, &non, recid);
502501
/* The final signature is no longer a secret, nor is the fact that we were successful or not. */
503502
secp256k1_declassify(ctx, &ret, sizeof(ret));
504503
if (ret) {
@@ -515,8 +514,25 @@ int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature
515514
secp256k1_scalar_clear(&msg);
516515
secp256k1_scalar_clear(&non);
517516
secp256k1_scalar_clear(&sec);
518-
secp256k1_scalar_cmov(&r, &secp256k1_scalar_zero, !ret);
519-
secp256k1_scalar_cmov(&s, &secp256k1_scalar_zero, !ret);
517+
secp256k1_scalar_cmov(r, &secp256k1_scalar_zero, !ret);
518+
secp256k1_scalar_cmov(s, &secp256k1_scalar_zero, !ret);
519+
if (recid) {
520+
const int zero = 0;
521+
secp256k1_int_cmov(recid, &zero, !ret);
522+
}
523+
return ret;
524+
}
525+
526+
int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *signature, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void* noncedata) {
527+
secp256k1_scalar r, s;
528+
int ret;
529+
VERIFY_CHECK(ctx != NULL);
530+
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
531+
ARG_CHECK(msg32 != NULL);
532+
ARG_CHECK(signature != NULL);
533+
ARG_CHECK(seckey != NULL);
534+
535+
ret = secp256k1_ecdsa_sign_inner(ctx, &r, &s, NULL, msg32, seckey, noncefp, noncedata);
520536
secp256k1_ecdsa_signature_save(signature, &r, &s);
521537
return ret;
522538
}

src/tests.c

Lines changed: 158 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3118,7 +3118,7 @@ void test_ecmult_multi_batching(void) {
31183118
data.pt = pt;
31193119
secp256k1_gej_neg(&r2, &r2);
31203120

3121-
/* Test with empty scratch space. It should compute the correct result using
3121+
/* Test with empty scratch space. It should compute the correct result using
31223122
* ecmult_mult_simple algorithm which doesn't require a scratch space. */
31233123
scratch = secp256k1_scratch_create(&ctx->error_callback, 0);
31243124
CHECK(secp256k1_ecmult_multi_var(&ctx->error_callback, &ctx->ecmult_ctx, scratch, &r, &scG, ecmult_multi_callback, &data, n_points));
@@ -5292,6 +5292,161 @@ void run_memczero_test(void) {
52925292
CHECK(memcmp(buf1, buf2, sizeof(buf1)) == 0);
52935293
}
52945294

5295+
void int_cmov_test(void) {
5296+
int r = INT_MAX;
5297+
int a = 0;
5298+
5299+
secp256k1_int_cmov(&r, &a, 0);
5300+
CHECK(r == INT_MAX);
5301+
5302+
r = 0; a = INT_MAX;
5303+
secp256k1_int_cmov(&r, &a, 1);
5304+
CHECK(r == INT_MAX);
5305+
5306+
a = 0;
5307+
secp256k1_int_cmov(&r, &a, 1);
5308+
CHECK(r == 0);
5309+
5310+
a = 1;
5311+
secp256k1_int_cmov(&r, &a, 1);
5312+
CHECK(r == 1);
5313+
5314+
r = 1; a = 0;
5315+
secp256k1_int_cmov(&r, &a, 0);
5316+
CHECK(r == 1);
5317+
5318+
}
5319+
5320+
void fe_cmov_test(void) {
5321+
static const secp256k1_fe zero = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0);
5322+
static const secp256k1_fe one = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1);
5323+
static const secp256k1_fe max = SECP256K1_FE_CONST(
5324+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL,
5325+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL
5326+
);
5327+
secp256k1_fe r = max;
5328+
secp256k1_fe a = zero;
5329+
5330+
secp256k1_fe_cmov(&r, &a, 0);
5331+
CHECK(memcmp(&r, &max, sizeof(r)) == 0);
5332+
5333+
r = zero; a = max;
5334+
secp256k1_fe_cmov(&r, &a, 1);
5335+
CHECK(memcmp(&r, &max, sizeof(r)) == 0);
5336+
5337+
a = zero;
5338+
secp256k1_fe_cmov(&r, &a, 1);
5339+
CHECK(memcmp(&r, &zero, sizeof(r)) == 0);
5340+
5341+
a = one;
5342+
secp256k1_fe_cmov(&r, &a, 1);
5343+
CHECK(memcmp(&r, &one, sizeof(r)) == 0);
5344+
5345+
r = one; a = zero;
5346+
secp256k1_fe_cmov(&r, &a, 0);
5347+
CHECK(memcmp(&r, &one, sizeof(r)) == 0);
5348+
}
5349+
5350+
void fe_storage_cmov_test(void) {
5351+
static const secp256k1_fe_storage zero = SECP256K1_FE_STORAGE_CONST(0, 0, 0, 0, 0, 0, 0, 0);
5352+
static const secp256k1_fe_storage one = SECP256K1_FE_STORAGE_CONST(0, 0, 0, 0, 0, 0, 0, 1);
5353+
static const secp256k1_fe_storage max = SECP256K1_FE_STORAGE_CONST(
5354+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL,
5355+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL
5356+
);
5357+
secp256k1_fe_storage r = max;
5358+
secp256k1_fe_storage a = zero;
5359+
5360+
secp256k1_fe_storage_cmov(&r, &a, 0);
5361+
CHECK(memcmp(&r, &max, sizeof(r)) == 0);
5362+
5363+
r = zero; a = max;
5364+
secp256k1_fe_storage_cmov(&r, &a, 1);
5365+
CHECK(memcmp(&r, &max, sizeof(r)) == 0);
5366+
5367+
a = zero;
5368+
secp256k1_fe_storage_cmov(&r, &a, 1);
5369+
CHECK(memcmp(&r, &zero, sizeof(r)) == 0);
5370+
5371+
a = one;
5372+
secp256k1_fe_storage_cmov(&r, &a, 1);
5373+
CHECK(memcmp(&r, &one, sizeof(r)) == 0);
5374+
5375+
r = one; a = zero;
5376+
secp256k1_fe_storage_cmov(&r, &a, 0);
5377+
CHECK(memcmp(&r, &one, sizeof(r)) == 0);
5378+
}
5379+
5380+
void scalar_cmov_test(void) {
5381+
static const secp256k1_scalar zero = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 0);
5382+
static const secp256k1_scalar one = SECP256K1_SCALAR_CONST(0, 0, 0, 0, 0, 0, 0, 1);
5383+
static const secp256k1_scalar max = SECP256K1_SCALAR_CONST(
5384+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL,
5385+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL
5386+
);
5387+
secp256k1_scalar r = max;
5388+
secp256k1_scalar a = zero;
5389+
5390+
secp256k1_scalar_cmov(&r, &a, 0);
5391+
CHECK(memcmp(&r, &max, sizeof(r)) == 0);
5392+
5393+
r = zero; a = max;
5394+
secp256k1_scalar_cmov(&r, &a, 1);
5395+
CHECK(memcmp(&r, &max, sizeof(r)) == 0);
5396+
5397+
a = zero;
5398+
secp256k1_scalar_cmov(&r, &a, 1);
5399+
CHECK(memcmp(&r, &zero, sizeof(r)) == 0);
5400+
5401+
a = one;
5402+
secp256k1_scalar_cmov(&r, &a, 1);
5403+
CHECK(memcmp(&r, &one, sizeof(r)) == 0);
5404+
5405+
r = one; a = zero;
5406+
secp256k1_scalar_cmov(&r, &a, 0);
5407+
CHECK(memcmp(&r, &one, sizeof(r)) == 0);
5408+
}
5409+
5410+
void ge_storage_cmov_test(void) {
5411+
static const secp256k1_ge_storage zero = SECP256K1_GE_STORAGE_CONST(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
5412+
static const secp256k1_ge_storage one = SECP256K1_GE_STORAGE_CONST(0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1);
5413+
static const secp256k1_ge_storage max = SECP256K1_GE_STORAGE_CONST(
5414+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL,
5415+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL,
5416+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL,
5417+
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL
5418+
);
5419+
secp256k1_ge_storage r = max;
5420+
secp256k1_ge_storage a = zero;
5421+
5422+
secp256k1_ge_storage_cmov(&r, &a, 0);
5423+
CHECK(memcmp(&r, &max, sizeof(r)) == 0);
5424+
5425+
r = zero; a = max;
5426+
secp256k1_ge_storage_cmov(&r, &a, 1);
5427+
CHECK(memcmp(&r, &max, sizeof(r)) == 0);
5428+
5429+
a = zero;
5430+
secp256k1_ge_storage_cmov(&r, &a, 1);
5431+
CHECK(memcmp(&r, &zero, sizeof(r)) == 0);
5432+
5433+
a = one;
5434+
secp256k1_ge_storage_cmov(&r, &a, 1);
5435+
CHECK(memcmp(&r, &one, sizeof(r)) == 0);
5436+
5437+
r = one; a = zero;
5438+
secp256k1_ge_storage_cmov(&r, &a, 0);
5439+
CHECK(memcmp(&r, &one, sizeof(r)) == 0);
5440+
}
5441+
5442+
void run_cmov_tests(void) {
5443+
int_cmov_test();
5444+
fe_cmov_test();
5445+
fe_storage_cmov_test();
5446+
scalar_cmov_test();
5447+
ge_storage_cmov_test();
5448+
}
5449+
52955450
int main(int argc, char **argv) {
52965451
unsigned char seed16[16] = {0};
52975452
unsigned char run32[32] = {0};
@@ -5431,6 +5586,8 @@ int main(int argc, char **argv) {
54315586
/* util tests */
54325587
run_memczero_test();
54335588

5589+
run_cmov_tests();
5590+
54345591
secp256k1_rand256(run32);
54355592
printf("random run = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", run32[0], run32[1], run32[2], run32[3], run32[4], run32[5], run32[6], run32[7], run32[8], run32[9], run32[10], run32[11], run32[12], run32[13], run32[14], run32[15]);
54365593

src/util.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,18 @@ static SECP256K1_INLINE void memczero(void *s, size_t len, int flag) {
194194
}
195195
}
196196

197+
/** If flag is true, set *r equal to *a; otherwise leave it. Constant-time. Both *r and *a must be initialized and non-negative.*/
198+
static SECP256K1_INLINE void secp256k1_int_cmov(int *r, const int *a, int flag) {
199+
unsigned int mask0, mask1, r_masked, a_masked;
200+
/* Casting a negative int to unsigned and back to int is implementation defined behavior */
201+
VERIFY_CHECK(*r >= 0 && *a >= 0);
202+
203+
mask0 = (unsigned int)flag + ~0u;
204+
mask1 = ~mask0;
205+
r_masked = ((unsigned int)*r & mask0);
206+
a_masked = ((unsigned int)*a & mask1);
207+
208+
*r = (int)(r_masked | a_masked);
209+
}
210+
197211
#endif /* SECP256K1_UTIL_H */

src/valgrind_ctime_test.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
# include "include/secp256k1_ecdh.h"
1313
#endif
1414

15+
#if ENABLE_MODULE_RECOVERY
16+
# include "include/secp256k1_recovery.h"
17+
#endif
18+
1519
int main(void) {
1620
secp256k1_context* ctx;
1721
secp256k1_ecdsa_signature signature;
@@ -24,6 +28,10 @@ int main(void) {
2428
unsigned char key[32];
2529
unsigned char sig[74];
2630
unsigned char spubkey[33];
31+
#if ENABLE_MODULE_RECOVERY
32+
secp256k1_ecdsa_recoverable_signature recoverable_signature;
33+
int recid;
34+
#endif
2735

2836
if (!RUNNING_ON_VALGRIND) {
2937
fprintf(stderr, "This test can only usefully be run inside valgrind.\n");
@@ -67,6 +75,17 @@ int main(void) {
6775
CHECK(ret == 1);
6876
#endif
6977

78+
#if ENABLE_MODULE_RECOVERY
79+
/* Test signing a recoverable signature. */
80+
VALGRIND_MAKE_MEM_UNDEFINED(key, 32);
81+
ret = secp256k1_ecdsa_sign_recoverable(ctx, &recoverable_signature, msg, key, NULL, NULL);
82+
VALGRIND_MAKE_MEM_DEFINED(&recoverable_signature, sizeof(recoverable_signature));
83+
VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret));
84+
CHECK(ret);
85+
CHECK(secp256k1_ecdsa_recoverable_signature_serialize_compact(ctx, sig, &recid, &recoverable_signature));
86+
CHECK(recid >= 0 && recid <= 3);
87+
#endif
88+
7089
VALGRIND_MAKE_MEM_UNDEFINED(key, 32);
7190
ret = secp256k1_ec_seckey_verify(ctx, key);
7291
VALGRIND_MAKE_MEM_DEFINED(&ret, sizeof(ret));

0 commit comments

Comments
 (0)