4d92a083bcsha256: speed up writes using multi-block compression (furszy)0753f8b909Add API to override SHA256 compression at runtime (furszy)fdb6a91a5eIntroduce hash context to support pluggable SHA256 compression (furszy) Pull request description: Tackling the long-standing request #702. Right now we ship our own SHA256 implementation, a standard baseline version that does not take advantage of any hardware-optimized instruction, and it cannot be accessed by the embedding application - it is for internal usage only. This means embedding applications often have to implement or include a different version for their use cases, wasting space on constrained environments, and in performance-sensitive setups it forces them to use a slower path than what the platform provides. Many projects already rely on tuned SHA-NI / ARMv8 / or other hardware-optimized code, so always using the baseline implementation we ship within the library is not ideal. These changes allow users to supply their own SHA256 compression function at runtime, while preserving the existing default behavior for everyone else. This is primarily intended for environments where the available SHA256 implementation is detected dynamically and recompiling the library with a different implementation is not feasible (equivalent build-time functionality will come in a follow-up PR). It introduces a new API: ```C89 secp256k1_context_set_sha256_transform_callback(ctx, fn_transform) ``` This function installs the optimized SHA256 compression into the `secp256k1_context`, which is then used by all internal computations. Important: The provided function is verified to be output-equivalent to the original one. As a quick example, using this functionality in Bitcoin-Core will be very straightforward:f68bef06d9ACKs for top commit: real-or-random: ACK4d92a083bcw0xlt: ACK4d92a083bctheStack: ACK4d92a083bcTree-SHA512: 058e2e82071f1ca77254b684458292c621e60d65bbcc5500574429717e7db75bc9f3221129fafd11eb5d33e666a5efec5e9844460d3b194ef3b6b16f2df28fb9
410 lines
15 KiB
C
410 lines
15 KiB
C
/***********************************************************************
|
|
* Copyright (c) 2017 Pieter Wuille *
|
|
* Distributed under the MIT software license, see the accompanying *
|
|
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
|
***********************************************************************/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "secp256k1.c"
|
|
#include "../include/secp256k1.h"
|
|
|
|
#include "util.h"
|
|
#include "hash_impl.h"
|
|
#include "field_impl.h"
|
|
#include "group_impl.h"
|
|
#include "scalar_impl.h"
|
|
#include "ecmult_impl.h"
|
|
#include "bench.h"
|
|
|
|
#define POINTS 32768
|
|
|
|
static void help(const char *executable_path, int default_iters) {
|
|
printf("Benchmark EC multiplication algorithms\n");
|
|
printf("\n");
|
|
printf("The default number of iterations for each benchmark is %d. This can be\n", default_iters);
|
|
printf("customized using the SECP256K1_BENCH_ITERS environment variable.\n");
|
|
printf("\n");
|
|
printf("Usage: %s [args]\n", executable_path);
|
|
printf("The output shows the number of multiplied and summed points right after the\n");
|
|
printf("function name. The letter 'g' indicates that one of the points is the generator.\n");
|
|
printf("The benchmarks are divided by the number of points.\n");
|
|
printf("\n");
|
|
printf("default (ecmult_multi): picks pippenger_wnaf or strauss_wnaf depending on the\n");
|
|
printf(" batch size\n");
|
|
printf("pippenger_wnaf: for all batch sizes\n");
|
|
printf("strauss_wnaf: for all batch sizes\n");
|
|
printf("simple: multiply and sum each point individually\n");
|
|
}
|
|
|
|
typedef struct {
|
|
/* Setup once in advance */
|
|
secp256k1_context* ctx;
|
|
secp256k1_scratch_space* scratch;
|
|
secp256k1_scalar* scalars;
|
|
secp256k1_ge* pubkeys;
|
|
secp256k1_gej* pubkeys_gej;
|
|
secp256k1_scalar* seckeys;
|
|
secp256k1_gej* expected_output;
|
|
secp256k1_ecmult_multi_func ecmult_multi;
|
|
|
|
/* Changes per benchmark */
|
|
size_t count;
|
|
int includes_g;
|
|
|
|
/* Changes per benchmark iteration, used to pick different scalars and pubkeys
|
|
* in each run. */
|
|
size_t offset1;
|
|
size_t offset2;
|
|
|
|
/* Benchmark output. */
|
|
secp256k1_gej* output;
|
|
secp256k1_fe* output_xonly;
|
|
} bench_data;
|
|
|
|
/* Hashes x into [0, POINTS) twice and store the result in offset1 and offset2. */
|
|
static void hash_into_offset(bench_data* data, size_t x) {
|
|
data->offset1 = (x * 0x537b7f6f + 0x8f66a481) % POINTS;
|
|
data->offset2 = (x * 0x7f6f537b + 0x6a1a8f49) % POINTS;
|
|
}
|
|
|
|
/* Check correctness of the benchmark by computing
|
|
* sum(outputs) ?= (sum(scalars_gen) + sum(seckeys)*sum(scalars))*G */
|
|
static void bench_ecmult_teardown_helper(bench_data* data, size_t* seckey_offset, size_t* scalar_offset, size_t* scalar_gen_offset, int iters) {
|
|
int i;
|
|
secp256k1_gej sum_output, tmp;
|
|
secp256k1_scalar sum_scalars;
|
|
|
|
secp256k1_gej_set_infinity(&sum_output);
|
|
secp256k1_scalar_set_int(&sum_scalars, 0);
|
|
for (i = 0; i < iters; ++i) {
|
|
secp256k1_gej_add_var(&sum_output, &sum_output, &data->output[i], NULL);
|
|
if (scalar_gen_offset != NULL) {
|
|
secp256k1_scalar_add(&sum_scalars, &sum_scalars, &data->scalars[(*scalar_gen_offset+i) % POINTS]);
|
|
}
|
|
if (seckey_offset != NULL) {
|
|
secp256k1_scalar s = data->seckeys[(*seckey_offset+i) % POINTS];
|
|
secp256k1_scalar_mul(&s, &s, &data->scalars[(*scalar_offset+i) % POINTS]);
|
|
secp256k1_scalar_add(&sum_scalars, &sum_scalars, &s);
|
|
}
|
|
}
|
|
secp256k1_ecmult_gen(&data->ctx->ecmult_gen_ctx, &tmp, &sum_scalars);
|
|
CHECK(secp256k1_gej_eq_var(&tmp, &sum_output));
|
|
}
|
|
|
|
static void bench_ecmult_setup(void* arg) {
|
|
bench_data* data = (bench_data*)arg;
|
|
/* Re-randomize offset to ensure that we're using different scalars and
|
|
* group elements in each run. */
|
|
hash_into_offset(data, data->offset1);
|
|
}
|
|
|
|
static void bench_ecmult_gen(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
int i;
|
|
|
|
for (i = 0; i < iters; ++i) {
|
|
secp256k1_ecmult_gen(&data->ctx->ecmult_gen_ctx, &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);
|
|
}
|
|
|
|
static void bench_ecmult_const(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
int i;
|
|
|
|
for (i = 0; i < iters; ++i) {
|
|
secp256k1_ecmult_const(&data->output[i], &data->pubkeys[(data->offset1+i) % POINTS], &data->scalars[(data->offset2+i) % POINTS]);
|
|
}
|
|
}
|
|
|
|
static void bench_ecmult_const_teardown(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
bench_ecmult_teardown_helper(data, &data->offset1, &data->offset2, NULL, iters);
|
|
}
|
|
|
|
static void bench_ecmult_const_xonly(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
int i;
|
|
|
|
for (i = 0; i < iters; ++i) {
|
|
const secp256k1_ge* pubkey = &data->pubkeys[(data->offset1+i) % POINTS];
|
|
const secp256k1_scalar* scalar = &data->scalars[(data->offset2+i) % POINTS];
|
|
int known_on_curve = 1;
|
|
secp256k1_ecmult_const_xonly(&data->output_xonly[i], &pubkey->x, NULL, scalar, known_on_curve);
|
|
}
|
|
}
|
|
|
|
static void bench_ecmult_const_xonly_teardown(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
int i;
|
|
|
|
/* verify by comparing with x coordinate of regular ecmult result */
|
|
for (i = 0; i < iters; ++i) {
|
|
const secp256k1_gej* pubkey_gej = &data->pubkeys_gej[(data->offset1+i) % POINTS];
|
|
const secp256k1_scalar* scalar = &data->scalars[(data->offset2+i) % POINTS];
|
|
secp256k1_gej expected_gej;
|
|
secp256k1_ecmult(&expected_gej, pubkey_gej, scalar, NULL);
|
|
CHECK(secp256k1_gej_eq_x_var(&data->output_xonly[i], &expected_gej));
|
|
}
|
|
}
|
|
|
|
static void bench_ecmult_1p(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
int i;
|
|
|
|
for (i = 0; i < iters; ++i) {
|
|
secp256k1_ecmult(&data->output[i], &data->pubkeys_gej[(data->offset1+i) % POINTS], &data->scalars[(data->offset2+i) % POINTS], NULL);
|
|
}
|
|
}
|
|
|
|
static void bench_ecmult_1p_teardown(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
bench_ecmult_teardown_helper(data, &data->offset1, &data->offset2, NULL, iters);
|
|
}
|
|
|
|
static void bench_ecmult_0p_g(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
int i;
|
|
|
|
for (i = 0; i < iters; ++i) {
|
|
secp256k1_ecmult(&data->output[i], NULL, &secp256k1_scalar_zero, &data->scalars[(data->offset1+i) % POINTS]);
|
|
}
|
|
}
|
|
|
|
static void bench_ecmult_0p_g_teardown(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
bench_ecmult_teardown_helper(data, NULL, NULL, &data->offset1, iters);
|
|
}
|
|
|
|
static void bench_ecmult_1p_g(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
int i;
|
|
|
|
for (i = 0; i < iters/2; ++i) {
|
|
secp256k1_ecmult(&data->output[i], &data->pubkeys_gej[(data->offset1+i) % POINTS], &data->scalars[(data->offset2+i) % POINTS], &data->scalars[(data->offset1+i) % POINTS]);
|
|
}
|
|
}
|
|
|
|
static void bench_ecmult_1p_g_teardown(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
bench_ecmult_teardown_helper(data, &data->offset1, &data->offset2, &data->offset1, iters/2);
|
|
}
|
|
|
|
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_const");
|
|
run_benchmark(str, bench_ecmult_const, bench_ecmult_setup, bench_ecmult_const_teardown, data, 10, iters);
|
|
sprintf(str, "ecmult_const_xonly");
|
|
run_benchmark(str, bench_ecmult_const_xonly, bench_ecmult_setup, bench_ecmult_const_xonly_teardown, data, 10, iters);
|
|
/* ecmult with non generator point */
|
|
sprintf(str, "ecmult_1p");
|
|
run_benchmark(str, bench_ecmult_1p, bench_ecmult_setup, bench_ecmult_1p_teardown, data, 10, iters);
|
|
/* ecmult with generator point */
|
|
sprintf(str, "ecmult_0p_g");
|
|
run_benchmark(str, bench_ecmult_0p_g, bench_ecmult_setup, bench_ecmult_0p_g_teardown, data, 10, iters);
|
|
/* ecmult with generator and non-generator point. The reported time is per point. */
|
|
sprintf(str, "ecmult_1p_g");
|
|
run_benchmark(str, bench_ecmult_1p_g, bench_ecmult_setup, bench_ecmult_1p_g_teardown, data, 10, 2*iters);
|
|
}
|
|
|
|
static int bench_ecmult_multi_callback(secp256k1_scalar* sc, secp256k1_ge* ge, size_t idx, void* arg) {
|
|
bench_data* data = (bench_data*)arg;
|
|
if (data->includes_g) ++idx;
|
|
if (idx == 0) {
|
|
*sc = data->scalars[data->offset1];
|
|
*ge = secp256k1_ge_const_g;
|
|
} else {
|
|
*sc = data->scalars[(data->offset1 + idx) % POINTS];
|
|
*ge = data->pubkeys[(data->offset2 + idx - 1) % POINTS];
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void bench_ecmult_multi(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
|
|
int includes_g = data->includes_g;
|
|
int iter;
|
|
int count = data->count;
|
|
iters = iters / data->count;
|
|
|
|
for (iter = 0; iter < iters; ++iter) {
|
|
data->ecmult_multi(&data->ctx->error_callback, data->scratch, &data->output[iter], data->includes_g ? &data->scalars[data->offset1] : NULL, bench_ecmult_multi_callback, arg, count - includes_g);
|
|
data->offset1 = (data->offset1 + count) % POINTS;
|
|
data->offset2 = (data->offset2 + count - 1) % POINTS;
|
|
}
|
|
}
|
|
|
|
static void bench_ecmult_multi_setup(void* arg) {
|
|
bench_data* data = (bench_data*)arg;
|
|
hash_into_offset(data, data->count);
|
|
}
|
|
|
|
static void bench_ecmult_multi_teardown(void* arg, int iters) {
|
|
bench_data* data = (bench_data*)arg;
|
|
int iter;
|
|
iters = iters / data->count;
|
|
/* Verify the results in teardown, to avoid doing comparisons while benchmarking. */
|
|
for (iter = 0; iter < iters; ++iter) {
|
|
secp256k1_gej tmp;
|
|
secp256k1_gej_add_var(&tmp, &data->output[iter], &data->expected_output[iter], NULL);
|
|
CHECK(secp256k1_gej_is_infinity(&tmp));
|
|
}
|
|
}
|
|
|
|
static void generate_scalar(const secp256k1_context *ctx, uint32_t num, secp256k1_scalar* scalar) {
|
|
secp256k1_sha256 sha256;
|
|
unsigned char c[10] = {'e', 'c', 'm', 'u', 'l', 't', 0, 0, 0, 0};
|
|
unsigned char buf[32];
|
|
int overflow = 0;
|
|
c[6] = num;
|
|
c[7] = num >> 8;
|
|
c[8] = num >> 16;
|
|
c[9] = num >> 24;
|
|
secp256k1_sha256_initialize(&sha256);
|
|
secp256k1_sha256_write(secp256k1_get_hash_context(ctx), &sha256, c, sizeof(c));
|
|
secp256k1_sha256_finalize(secp256k1_get_hash_context(ctx), &sha256, buf);
|
|
secp256k1_scalar_set_b32(scalar, buf, &overflow);
|
|
CHECK(!overflow);
|
|
}
|
|
|
|
static void run_ecmult_multi_bench(bench_data* data, size_t count, int includes_g, int num_iters) {
|
|
char str[32];
|
|
size_t iters = 1 + num_iters / count;
|
|
size_t iter;
|
|
|
|
data->count = count;
|
|
data->includes_g = includes_g;
|
|
|
|
/* Compute (the negation of) the expected results directly. */
|
|
hash_into_offset(data, data->count);
|
|
for (iter = 0; iter < iters; ++iter) {
|
|
secp256k1_scalar tmp;
|
|
secp256k1_scalar total = data->scalars[(data->offset1++) % POINTS];
|
|
size_t i = 0;
|
|
for (i = 0; i + 1 < count; ++i) {
|
|
secp256k1_scalar_mul(&tmp, &data->seckeys[(data->offset2++) % POINTS], &data->scalars[(data->offset1++) % POINTS]);
|
|
secp256k1_scalar_add(&total, &total, &tmp);
|
|
}
|
|
secp256k1_scalar_negate(&total, &total);
|
|
secp256k1_ecmult(&data->expected_output[iter], NULL, &secp256k1_scalar_zero, &total);
|
|
}
|
|
|
|
/* Run the benchmark. */
|
|
if (includes_g) {
|
|
sprintf(str, "ecmult_multi_%ip_g", (int)count - 1);
|
|
} else {
|
|
sprintf(str, "ecmult_multi_%ip", (int)count);
|
|
}
|
|
run_benchmark(str, bench_ecmult_multi, bench_ecmult_multi_setup, bench_ecmult_multi_teardown, data, 10, count * iters);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
bench_data data;
|
|
int i, p;
|
|
size_t scratch_size;
|
|
|
|
int default_iters = 10000;
|
|
int iters = get_iters(default_iters);
|
|
if (iters == 0) {
|
|
help(argv[0], default_iters);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
data.ecmult_multi = secp256k1_ecmult_multi_var;
|
|
|
|
if (argc > 1) {
|
|
if(have_flag(argc, argv, "-h")
|
|
|| have_flag(argc, argv, "--help")
|
|
|| have_flag(argc, argv, "help")) {
|
|
help(argv[0], default_iters);
|
|
return EXIT_SUCCESS;
|
|
} else if(have_flag(argc, argv, "pippenger_wnaf")) {
|
|
printf("Using pippenger_wnaf:\n");
|
|
data.ecmult_multi = secp256k1_ecmult_pippenger_batch_single;
|
|
} else if(have_flag(argc, argv, "strauss_wnaf")) {
|
|
printf("Using strauss_wnaf:\n");
|
|
data.ecmult_multi = secp256k1_ecmult_strauss_batch_single;
|
|
} else if(have_flag(argc, argv, "simple")) {
|
|
printf("Using simple algorithm:\n");
|
|
} else {
|
|
fprintf(stderr, "%s: unrecognized argument '%s'.\n\n", argv[0], argv[1]);
|
|
help(argv[0], default_iters);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
|
|
scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT;
|
|
if (!have_flag(argc, argv, "simple")) {
|
|
data.scratch = secp256k1_scratch_space_create(data.ctx, scratch_size);
|
|
} else {
|
|
data.scratch = NULL;
|
|
}
|
|
|
|
/* Allocate stuff */
|
|
data.scalars = malloc(sizeof(secp256k1_scalar) * POINTS);
|
|
data.seckeys = malloc(sizeof(secp256k1_scalar) * POINTS);
|
|
data.pubkeys = malloc(sizeof(secp256k1_ge) * POINTS);
|
|
data.pubkeys_gej = malloc(sizeof(secp256k1_gej) * POINTS);
|
|
data.expected_output = malloc(sizeof(secp256k1_gej) * (iters + 1));
|
|
data.output = malloc(sizeof(secp256k1_gej) * (iters + 1));
|
|
data.output_xonly = malloc(sizeof(secp256k1_fe) * (iters + 1));
|
|
|
|
/* Generate a set of scalars, and private/public keypairs. */
|
|
secp256k1_gej_set_ge(&data.pubkeys_gej[0], &secp256k1_ge_const_g);
|
|
secp256k1_scalar_set_int(&data.seckeys[0], 1);
|
|
for (i = 0; i < POINTS; ++i) {
|
|
generate_scalar(data.ctx, i, &data.scalars[i]);
|
|
if (i) {
|
|
secp256k1_gej_double_var(&data.pubkeys_gej[i], &data.pubkeys_gej[i - 1], NULL);
|
|
secp256k1_scalar_add(&data.seckeys[i], &data.seckeys[i - 1], &data.seckeys[i - 1]);
|
|
}
|
|
}
|
|
secp256k1_ge_set_all_gej_var(data.pubkeys, data.pubkeys_gej, POINTS);
|
|
|
|
|
|
print_output_table_header_row();
|
|
/* Initialize offset1 and offset2 */
|
|
hash_into_offset(&data, 0);
|
|
run_ecmult_bench(&data, iters);
|
|
|
|
for (i = 1; i <= 8; ++i) {
|
|
run_ecmult_multi_bench(&data, i, 1, iters);
|
|
}
|
|
|
|
/* This is disabled with low count of iterations because the loop runs 77 times even with iters=1
|
|
* and the higher it goes the longer the computation takes(more points)
|
|
* So we don't run this benchmark with low iterations to prevent slow down */
|
|
if (iters > 2) {
|
|
for (p = 0; p <= 11; ++p) {
|
|
for (i = 9; i <= 16; ++i) {
|
|
run_ecmult_multi_bench(&data, i << p, 1, iters);
|
|
}
|
|
}
|
|
} else {
|
|
printf("Skipping some benchmarks due to SECP256K1_BENCH_ITERS <= 2\n");
|
|
}
|
|
|
|
if (data.scratch != NULL) {
|
|
secp256k1_scratch_space_destroy(data.ctx, data.scratch);
|
|
}
|
|
secp256k1_context_destroy(data.ctx);
|
|
free(data.scalars);
|
|
free(data.pubkeys);
|
|
free(data.pubkeys_gej);
|
|
free(data.seckeys);
|
|
free(data.output_xonly);
|
|
free(data.output);
|
|
free(data.expected_output);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|