diff --git a/src/bench.c b/src/bench.c index f561ad1c9d..c7c047c389 100644 --- a/src/bench.c +++ b/src/bench.c @@ -43,8 +43,9 @@ static void help(const char *executable_path, int default_iters) { printf(" ecdsa : all ECDSA algorithms--sign, verify, recovery (if enabled)\n"); printf(" ecdsa_sign : ECDSA siging algorithm\n"); printf(" ecdsa_verify : ECDSA verification algorithm\n"); - printf(" ec : all EC public key algorithms (keygen)\n"); + printf(" ec : all EC public key algorithms (keygen, tweak)\n"); printf(" ec_keygen : EC public key generation\n"); + printf(" ec_pk_tweak_add : EC public key additive tweaking\n"); #ifdef ENABLE_MODULE_RECOVERY printf(" ecdsa_recover : ECDSA public key recovery algorithm\n"); @@ -79,6 +80,8 @@ typedef struct { size_t siglen; unsigned char pubkey[33]; size_t pubkeylen; + secp256k1_pubkey tweaked_pubkey; + unsigned char tweak[32]; } bench_data; static void bench_verify(void* arg, int iters) { @@ -153,6 +156,32 @@ static void bench_keygen_run(void *arg, int iters) { } } +static void bench_tweak_setup(void* arg) { + int i; + bench_data *data = (bench_data*)arg; + unsigned char seckey_one[32] = {0}; + + /* set starting pubkey to the generator point */ + seckey_one[31] = 1; + CHECK(secp256k1_ec_pubkey_create(data->ctx, &data->tweaked_pubkey, seckey_one)); + for (i = 0; i < 32; i++) { + data->tweak[i] = i + 129; + } +} + +static void bench_pubkey_tweak_add(void *arg, int iters) { + int i; + bench_data *data = (bench_data*)arg; + + for (i = 0; i < iters; i++) { + unsigned char pub33[33]; + size_t len = 33; + CHECK(secp256k1_ec_pubkey_tweak_add(data->ctx, &data->tweaked_pubkey, data->tweak)); + CHECK(secp256k1_ec_pubkey_serialize(data->ctx, pub33, &len, &data->tweaked_pubkey, SECP256K1_EC_COMPRESSED)); + memcpy(data->tweak, pub33 + 1, 32); + } +} + #ifdef ENABLE_MODULE_ECDH # include "modules/ecdh/bench_impl.h" @@ -181,8 +210,8 @@ int main(int argc, char** argv) { /* Check for invalid user arguments */ char* valid_args[] = {"ecdsa", "verify", "ecdsa_verify", "sign", "ecdsa_sign", "ecdh", "recover", "ecdsa_recover", "schnorrsig", "schnorrsig_verify", "schnorrsig_sign", "ec", - "keygen", "ec_keygen", "ellswift", "encode", "ellswift_encode", "decode", - "ellswift_decode", "ellswift_keygen", "ellswift_ecdh"}; + "keygen", "ec_keygen", "tweak", "ec_pk_tweak_add", "ellswift", "encode", + "ellswift_encode", "decode", "ellswift_decode", "ellswift_keygen", "ellswift_ecdh"}; int invalid_args = have_invalid_args(argc, argv, valid_args, ARRAY_SIZE(valid_args)); int default_iters = 20000; @@ -261,6 +290,7 @@ int main(int argc, char** argv) { if (d || have_flag(argc, argv, "ecdsa") || have_flag(argc, argv, "sign") || have_flag(argc, argv, "ecdsa_sign")) run_benchmark("ecdsa_sign", bench_sign_run, bench_sign_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "ec") || have_flag(argc, argv, "keygen") || have_flag(argc, argv, "ec_keygen")) run_benchmark("ec_keygen", bench_keygen_run, bench_keygen_setup, NULL, &data, 10, iters); + if (d || have_flag(argc, argv, "ec") || have_flag(argc, argv, "tweak") || have_flag(argc, argv, "ec_pk_tweak_add")) run_benchmark("ec_pk_tweak_add", bench_pubkey_tweak_add, bench_tweak_setup, NULL, &data, 10, iters); secp256k1_context_destroy(data.ctx); diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c index eb546db414..61b7e0dd16 100644 --- a/src/bench_ecmult.c +++ b/src/bench_ecmult.c @@ -108,6 +108,15 @@ static void bench_ecmult_gen(void* arg, int iters) { } } +static void bench_ecmult_gen_var(void* arg, int iters) { + bench_data* data = (bench_data*)arg; + int i; + + for (i = 0; i < iters; ++i) { + secp256k1_ecmult_gen_var(&data->output[i], &data->scalars[(data->offset1+i) % POINTS]); + } +} + static void bench_ecmult_gen_teardown(void* arg, int iters) { bench_data* data = (bench_data*)arg; bench_ecmult_teardown_helper(data, NULL, NULL, &data->offset1, iters); @@ -199,6 +208,8 @@ static void run_ecmult_bench(bench_data* data, int iters) { char str[32]; sprintf(str, "ecmult_gen"); run_benchmark(str, bench_ecmult_gen, bench_ecmult_setup, bench_ecmult_gen_teardown, data, 10, iters); + sprintf(str, "ecmult_gen_var"); + run_benchmark(str, bench_ecmult_gen_var, bench_ecmult_setup, bench_ecmult_gen_teardown, data, 10, iters); sprintf(str, "ecmult_const"); run_benchmark(str, bench_ecmult_const, bench_ecmult_setup, bench_ecmult_const_teardown, data, 10, iters); sprintf(str, "ecmult_const_xonly"); diff --git a/src/eckey_impl.h b/src/eckey_impl.h index 57024e409d..9cee88dd54 100644 --- a/src/eckey_impl.h +++ b/src/eckey_impl.h @@ -61,8 +61,8 @@ static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp25 static int secp256k1_eckey_pubkey_tweak_add(secp256k1_ge *key, const secp256k1_scalar *tweak) { secp256k1_gej pt; - secp256k1_gej_set_ge(&pt, key); - secp256k1_ecmult(&pt, &pt, &secp256k1_scalar_one, tweak); + secp256k1_ecmult_gen_var(&pt, tweak); + secp256k1_gej_add_ge_var(&pt, &pt, key, NULL); if (secp256k1_gej_is_infinity(&pt)) { return 0; diff --git a/src/ecmult_gen.h b/src/ecmult_gen.h index 8bc4f14c31..74942ed5d5 100644 --- a/src/ecmult_gen.h +++ b/src/ecmult_gen.h @@ -138,6 +138,7 @@ static void secp256k1_ecmult_gen_context_clear(secp256k1_ecmult_gen_context* ctx /** Multiply with the generator: R = a*G */ static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context* ctx, secp256k1_gej *r, const secp256k1_scalar *a); +static void secp256k1_ecmult_gen_var(secp256k1_gej *r, const secp256k1_scalar *a); static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const secp256k1_hash_ctx *hash_ctx, const unsigned char *seed32); diff --git a/src/ecmult_gen_compute_table.h b/src/ecmult_gen_compute_table.h index bd41803a87..a6a5614c04 100644 --- a/src/ecmult_gen_compute_table.h +++ b/src/ecmult_gen_compute_table.h @@ -10,5 +10,6 @@ #include "ecmult_gen.h" static void secp256k1_ecmult_gen_compute_table(secp256k1_ge_storage* table, const secp256k1_ge* gen, int blocks, int teeth, int spacing); +static void secp256k1_ecmult_gen_compute_scalar_diff(secp256k1_scalar* diff, int blocks, int teeth, int spacing); #endif /* SECP256K1_ECMULT_GEN_COMPUTE_TABLE_H */ diff --git a/src/ecmult_gen_compute_table_impl.h b/src/ecmult_gen_compute_table_impl.h index 6aa8d84082..060e40231a 100644 --- a/src/ecmult_gen_compute_table_impl.h +++ b/src/ecmult_gen_compute_table_impl.h @@ -105,4 +105,26 @@ static void secp256k1_ecmult_gen_compute_table(secp256k1_ge_storage* table, cons free(prec); } +/* Compute the scalar (2^COMB_BITS - 1) / 2, the difference between the gn argument to + * secp256k1_ecmult_gen, and the scalar whose encoding the table lookup bits are drawn + * from (before applying blinding). */ +static void secp256k1_ecmult_gen_compute_scalar_diff(secp256k1_scalar* diff, int blocks, int teeth, int spacing) { + int i; + int comb_bits = blocks * teeth * spacing; + + /* Compute scalar -1/2. */ + secp256k1_scalar neghalf; + secp256k1_scalar_half(&neghalf, &secp256k1_scalar_one); + secp256k1_scalar_negate(&neghalf, &neghalf); + + /* Compute offset = 2^(COMB_BITS - 1). */ + *diff = secp256k1_scalar_one; + for (i = 0; i < comb_bits - 1; ++i) { + secp256k1_scalar_add(diff, diff, diff); + } + + /* The result is the sum 2^(COMB_BITS - 1) + (-1/2). */ + secp256k1_scalar_add(diff, diff, &neghalf); +} + #endif /* SECP256K1_ECMULT_GEN_COMPUTE_TABLE_IMPL_H */ diff --git a/src/ecmult_gen_impl.h b/src/ecmult_gen_impl.h index 5a954977eb..8e4209f436 100644 --- a/src/ecmult_gen_impl.h +++ b/src/ecmult_gen_impl.h @@ -30,27 +30,6 @@ static void secp256k1_ecmult_gen_context_clear(secp256k1_ecmult_gen_context *ctx secp256k1_fe_clear(&ctx->proj_blind); } -/* Compute the scalar (2^COMB_BITS - 1) / 2, the difference between the gn argument to - * secp256k1_ecmult_gen, and the scalar whose encoding the table lookup bits are drawn - * from (before applying blinding). */ -static void secp256k1_ecmult_gen_scalar_diff(secp256k1_scalar* diff) { - int i; - - /* Compute scalar -1/2. */ - secp256k1_scalar neghalf; - secp256k1_scalar_half(&neghalf, &secp256k1_scalar_one); - secp256k1_scalar_negate(&neghalf, &neghalf); - - /* Compute offset = 2^(COMB_BITS - 1). */ - *diff = secp256k1_scalar_one; - for (i = 0; i < COMB_BITS - 1; ++i) { - secp256k1_scalar_add(diff, diff, diff); - } - - /* The result is the sum 2^(COMB_BITS - 1) + (-1/2). */ - secp256k1_scalar_add(diff, diff, &neghalf); -} - static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context *ctx, secp256k1_gej *r, const secp256k1_scalar *gn) { uint32_t comb_off; secp256k1_ge add; @@ -281,19 +260,78 @@ static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context *ctx, secp25 secp256k1_memclear_explicit(&recoded, sizeof(recoded)); } +/* Variable-time variant for generator point multiplication. + * + * This is essentially a copy of `secp256k1_ecmult_gen`, but with all side-channel + * mitigations (i.e., constant-time code, random scalar blinding, and memory clearing) + * removed. The EC point operation calls (addition, doubling) are replaced with their + * faster variable-time equivalents. This function is stateless and does not require + * a context parameter, enabling its use in internal functions (e.g., `eckey_pubkey_tweak_add`). + * + * Note that the branch for the first table lookup assignment is also removed: since + * the result is initialized to the point at infinity, adding to it with `_gej_add_ge_var` + * is equivalent to a simple copy. */ +static void secp256k1_ecmult_gen_var(secp256k1_gej *r, const secp256k1_scalar *gn) { + uint32_t comb_off; + secp256k1_ge add; + secp256k1_scalar d; + uint32_t recoded[(COMB_BITS + 31) >> 5] = {0}; + int i; + + /* Adjust input scalar for difference and convert to recoded array. */ + secp256k1_scalar_add(&d, &secp256k1_ecmult_gen_scalar_diff, gn); + for (i = 0; i < 8 && i < ((COMB_BITS + 31) >> 5); ++i) { + recoded[i] = secp256k1_scalar_get_bits_limb32(&d, 32 * i, 32); + } + + /* Outer loop: iterate over comb_off from COMB_SPACING - 1 down to 0. */ + secp256k1_gej_set_infinity(r); + comb_off = COMB_SPACING - 1; + while (1) { + uint32_t block; + uint32_t bit_pos = comb_off; + /* Inner loop: for each block, add table entries to the result. */ + for (block = 0; block < COMB_BLOCKS; ++block) { + /* Gather the mask(block)-selected bits of d into bits. They're packed: + * bits[tooth] = d[(block*COMB_TEETH + tooth)*COMB_SPACING + comb_off]. */ + uint32_t bits = 0, sign, abs, tooth; + for (tooth = 0; tooth < COMB_TEETH; ++tooth) { + uint32_t bit = (recoded[bit_pos >> 5] >> (bit_pos & 0x1f)) & 1; + bits |= bit << tooth; + bit_pos += COMB_SPACING; + } + + /* If the top bit of bits is 1, flip them all (corresponding to looking up + * the negated table value), and remember to negate the result in sign. */ + sign = (bits >> (COMB_TEETH - 1)) & 1; + abs = (bits ^ -sign) & (COMB_POINTS - 1); + VERIFY_CHECK(sign == 0 || sign == 1); + VERIFY_CHECK(abs < COMB_POINTS); + + /* Perform lookup, negate if necessary and add to r. */ + secp256k1_ge_from_storage(&add, &secp256k1_ecmult_gen_prec_table[block][abs]); + if (sign) { + secp256k1_fe_negate(&add.y, &add.y, 1); + } + secp256k1_gej_add_ge_var(r, r, &add, NULL); + } + + /* Double the result, except in the last iteration. */ + if (comb_off-- == 0) break; + secp256k1_gej_double_var(r, r, NULL); + } +} + /* Setup blinding values for secp256k1_ecmult_gen. */ static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const secp256k1_hash_ctx *hash_ctx, const unsigned char *seed32) { secp256k1_scalar b; - secp256k1_scalar diff; + const secp256k1_scalar diff = secp256k1_ecmult_gen_scalar_diff; secp256k1_gej gb; secp256k1_fe f; unsigned char nonce32[32]; secp256k1_rfc6979_hmac_sha256 rng; unsigned char keydata[64]; - /* Compute the (2^COMB_BITS - 1)/2 term once. */ - secp256k1_ecmult_gen_scalar_diff(&diff); - if (seed32 == NULL) { /* When seed is NULL, reset the final point and blinding value. */ secp256k1_ge_neg(&ctx->ge_offset, &secp256k1_ge_const_g); diff --git a/src/precompute_ecmult_gen.c b/src/precompute_ecmult_gen.c index a03abdb548..f2bcf83c96 100644 --- a/src/precompute_ecmult_gen.c +++ b/src/precompute_ecmult_gen.c @@ -53,6 +53,21 @@ static void print_table(FILE* fp, int blocks, int teeth) { free(table); } +static void print_scalar_diff(FILE* fp, int blocks, int teeth) { + int spacing = CEIL_DIV(256, blocks * teeth); + secp256k1_scalar diff; + int limb; + + secp256k1_ecmult_gen_compute_scalar_diff(&diff, blocks, teeth, spacing); + fprintf(fp, "#elif (COMB_BLOCKS == %d) && (COMB_TEETH == %d) && (COMB_SPACING == %d)\n", blocks, teeth, spacing); + fprintf(fp, " SECP256K1_SCALAR_CONST("); + for (limb = 7; limb >= 0; limb--) { + fprintf(fp, "0x%08x", secp256k1_scalar_get_bits_var(&diff, limb*32, 32)); + if (limb != 0) fprintf(fp, ","); + } + fprintf(fp, ")\n"); +} + int main(int argc, char **argv) { const char outfile[] = "src/precomputed_ecmult_gen.c"; FILE* fp; @@ -92,9 +107,21 @@ int main(int argc, char **argv) { fprintf(fp, "#else\n"); fprintf(fp, "# error Configuration mismatch, invalid COMB_* parameters. Try deleting precomputed_ecmult_gen.c before the build.\n"); fprintf(fp, "#endif\n"); - fprintf(fp, "};\n"); - fprintf(fp, "#undef S\n"); + fprintf(fp, "#undef S\n\n"); + + fprintf(fp, "const secp256k1_scalar secp256k1_ecmult_gen_scalar_diff =\n"); + fprintf(fp, "#if 0\n"); + for (config = 0; config < ARRAY_SIZE(CONFIGS); ++config) { + print_scalar_diff(fp, CONFIGS[config][0], CONFIGS[config][1]); + } + if (!did_current_config) { + print_scalar_diff(fp, COMB_BLOCKS, COMB_TEETH); + } + fprintf(fp, "#else\n"); + fprintf(fp, "# error Configuration mismatch, invalid COMB_* parameters. Try deleting precomputed_ecmult_gen.c before the build.\n"); + fprintf(fp, "#endif\n"); + fprintf(fp, ";\n"); fclose(fp); return EXIT_SUCCESS; diff --git a/src/precomputed_ecmult_gen.c b/src/precomputed_ecmult_gen.c index 248fb077e5..dec535e863 100644 --- a/src/precomputed_ecmult_gen.c +++ b/src/precomputed_ecmult_gen.c @@ -1777,3 +1777,16 @@ S(ff3d6136,ffac5b0c,bfc6c5c0,c30dc01a,7ea3d56c,20bd3103,b178e3d3,ae180068,eccdc6 #endif }; #undef S + +const secp256k1_scalar secp256k1_ecmult_gen_scalar_diff = +#if 0 +#elif (COMB_BLOCKS == 2) && (COMB_TEETH == 5) && (COMB_SPACING == 26) + SECP256K1_SCALAR_CONST(0x80000000,0x00000000,0x00000000,0x00000009,0x87e0873d,0xdd5f4e3f,0xe1563adf,0xe6691698) +#elif (COMB_BLOCKS == 11) && (COMB_TEETH == 6) && (COMB_SPACING == 4) + SECP256K1_SCALAR_CONST(0x80000000,0x00000000,0x00000000,0x000000a2,0x05e8fb1b,0xb354323d,0xf6b9e8de,0x4cfa8020) +#elif (COMB_BLOCKS == 43) && (COMB_TEETH == 6) && (COMB_SPACING == 1) + SECP256K1_SCALAR_CONST(0x80000000,0x00000000,0x00000000,0x00000001,0xe7f9b4a5,0xf9130fa6,0x6044722c,0xc7ae9e1e) +#else +# error Configuration mismatch, invalid COMB_* parameters. Try deleting precomputed_ecmult_gen.c before the build. +#endif +; diff --git a/src/precomputed_ecmult_gen.h b/src/precomputed_ecmult_gen.h index 00ddce108b..8d9346c1c7 100644 --- a/src/precomputed_ecmult_gen.h +++ b/src/precomputed_ecmult_gen.h @@ -17,8 +17,10 @@ extern "C" { #ifdef EXHAUSTIVE_TEST_ORDER static secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[COMB_BLOCKS][COMB_POINTS]; +static secp256k1_scalar secp256k1_ecmult_gen_scalar_diff; #else SECP256K1_LOCAL_VAR const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[COMB_BLOCKS][COMB_POINTS]; +SECP256K1_LOCAL_VAR const secp256k1_scalar secp256k1_ecmult_gen_scalar_diff; #endif /* defined(EXHAUSTIVE_TEST_ORDER) */ #ifdef __cplusplus diff --git a/src/tests.c b/src/tests.c index 862bef61a7..bc20ecce50 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4600,10 +4600,14 @@ static void test_ecmult_target(const secp256k1_scalar* target, int mode) { secp256k1_ecmult(&p1j, &pj, &n1, &secp256k1_scalar_zero); secp256k1_ecmult(&p2j, &pj, &n2, &secp256k1_scalar_zero); secp256k1_ecmult(&ptj, &pj, target, &secp256k1_scalar_zero); - } else { + } else if (mode == 2) { secp256k1_ecmult_const(&p1j, &p, &n1); secp256k1_ecmult_const(&p2j, &p, &n2); secp256k1_ecmult_const(&ptj, &p, target); + } else { + secp256k1_ecmult_gen_var(&p1j, &n1); + secp256k1_ecmult_gen_var(&p2j, &n2); + secp256k1_ecmult_gen_var(&ptj, target); } /* Add them all up: n1*P + n2*P + target*P = (n1+n2+target)*P = (n1+n1-n1-n2)*P = 0. */ @@ -4620,6 +4624,7 @@ static void run_ecmult_near_split_bound(void) { test_ecmult_target(&scalars_near_split_bounds[j], 0); test_ecmult_target(&scalars_near_split_bounds[j], 1); test_ecmult_target(&scalars_near_split_bounds[j], 2); + test_ecmult_target(&scalars_near_split_bounds[j], 3); } } } @@ -5637,7 +5642,7 @@ static void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar /* Compute x*G in many different ways, serialize it uncompressed, and feed it into acc. */ secp256k1_gej gj, infj; secp256k1_ge r; - secp256k1_gej rj[7]; + secp256k1_gej rj[8]; unsigned char bytes[65]; size_t i; secp256k1_gej_set_ge(&gj, &secp256k1_ge_const_g); @@ -5649,6 +5654,7 @@ static void test_ecmult_accumulate(secp256k1_sha256* acc, const secp256k1_scalar CHECK(secp256k1_ecmult_multi_var(&CTX->error_callback, scratch, &rj[4], x, NULL, NULL, 0)); CHECK(secp256k1_ecmult_multi_var(&CTX->error_callback, scratch, &rj[5], &secp256k1_scalar_zero, test_ecmult_accumulate_cb, (void*)x, 1)); secp256k1_ecmult_const(&rj[6], &secp256k1_ge_const_g, x); + secp256k1_ecmult_gen_var(&rj[7], x); secp256k1_ge_set_gej_var(&r, &rj[0]); for (i = 0; i < ARRAY_SIZE(rj); i++) { CHECK(secp256k1_gej_eq_ge_var(&rj[i], &r)); diff --git a/src/tests_exhaustive.c b/src/tests_exhaustive.c index 68d4bec3f0..a90b2cb4de 100644 --- a/src/tests_exhaustive.c +++ b/src/tests_exhaustive.c @@ -390,6 +390,8 @@ int main(int argc, char** argv) { printf("running tests for core %lu (out of [0..%lu])\n", (unsigned long)this_core, (unsigned long)num_cores - 1); } + /* Recreate the scalar_diff value using the proper COMB parameters (as selected via EXHAUSTIVE_TEST_ORDER) */ + secp256k1_ecmult_gen_compute_scalar_diff(&secp256k1_ecmult_gen_scalar_diff, COMB_BLOCKS, COMB_TEETH, COMB_SPACING); /* Recreate the ecmult{,_gen} tables using the right generator (as selected via EXHAUSTIVE_TEST_ORDER) */ secp256k1_ecmult_gen_compute_table(&secp256k1_ecmult_gen_prec_table[0][0], &secp256k1_ge_const_g, COMB_BLOCKS, COMB_TEETH, COMB_SPACING); secp256k1_ecmult_compute_two_tables(secp256k1_pre_g, secp256k1_pre_g_128, WINDOW_G, &secp256k1_ge_const_g); @@ -414,14 +416,17 @@ int main(int argc, char** argv) { secp256k1_gej_rescale(&groupj[i], &z); } - /* Verify against ecmult_gen */ + /* Verify against ecmult_gen(_var) */ { secp256k1_scalar scalar_i; secp256k1_gej generatedj; + secp256k1_gej generatedj_var; secp256k1_ge generated; secp256k1_scalar_set_int(&scalar_i, i); secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &generatedj, &scalar_i); + secp256k1_ecmult_gen_var(&generatedj_var, &scalar_i); + CHECK(secp256k1_gej_eq_var(&generatedj, &generatedj_var)); secp256k1_ge_set_gej(&generated, &generatedj); CHECK(!secp256k1_ge_is_infinity(&group[i]));