Skip to content

optimize pubkey tweaking by using vartime jacobian->affine conversion (~10% speedup)#1844

Open
theStack wants to merge 2 commits intobitcoin-core:masterfrom
theStack:pubkey-tweak-use_vartime_group_to_affine
Open

optimize pubkey tweaking by using vartime jacobian->affine conversion (~10% speedup)#1844
theStack wants to merge 2 commits intobitcoin-core:masterfrom
theStack:pubkey-tweak-use_vartime_group_to_affine

Conversation

@theStack
Copy link
Copy Markdown
Contributor

@theStack theStack commented Apr 11, 2026

This PR has a very similar theme and motivation as #1843, but is significantly simpler.

Tweaks are not considered sensitive data, and the point multiplications are not performed in constant-time either, so we can use the variable-time variant for converting to affine coordinates (ge_set_gej_var) as well to improve the performance. On my arm64 machine, this shows a ~10% speedup for both the additive and multiplicative public key tweaking API functions:

master branch

$ ./build/bin/bench tweak
Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

ec_pk_tweak_add               ,    16.3       ,    16.4       ,    16.7
ec_pk_tweak_mul               ,    19.7       ,    19.8       ,    19.8

PR branch

$ ./build/bin/bench tweak
Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

ec_pk_tweak_add               ,    14.6       ,    14.7       ,    14.9
ec_pk_tweak_mul               ,    18.1       ,    18.1       ,    18.2

The reduced execution time roughly matches the difference between fe_inv and fe_inv_var (see internal benchmarks), ~1.6 us on my machine.

Note that the improved code path for additive tweaking (function secp256k1_eckey_pubkey_tweak_add) is also used in the following API functions, so they should all benefit from it:

Tweaks are not considered sensitive data, and the point multiplications
are not performed in constant-time either, so we can use the
variable-time variant for converting to affine coordinates (ge_set_gej_var)
as well to improve the performance. On my arm64 machine, this shows a
~10% speedup for both the additive and multiplicative public key tweaking
API functions:

----- Before (prior this commit): -----
```
$ ./build/bin/bench tweak
Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

ec_pk_tweak_add               ,    16.3       ,    16.4       ,    16.7
ec_pk_tweak_mul               ,    19.7       ,    19.8       ,    19.8
```

----- After (this commit): -----
```
$ ./build/bin/bench tweak
Benchmark                     ,    Min(us)    ,    Avg(us)    ,    Max(us)

ec_pk_tweak_add               ,    14.6       ,    14.7       ,    14.9
ec_pk_tweak_mul               ,    18.1       ,    18.1       ,    18.2
```

The reduced execution time roughly matches the difference between
`fe_inv` and `fe_inv_var` (see internal benchmarks), ~1.6 us on my machine.
@theStack theStack changed the title optimize pubkey tweaking by using vartime jabobian->affine conversion (~10% speedup) optimize pubkey tweaking by using vartime jacobian->affine conversion (~10% speedup) Apr 11, 2026
Copy link
Copy Markdown
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 5a9e0a1

nit:

diff --git a/src/bench.c b/src/bench.c
index c971b94..d1f550f 100644
--- a/src/bench.c
+++ b/src/bench.c
@@ -170,7 +170,7 @@ static void bench_tweak_setup(void* arg) {
     }
 }
 
-static void bench_pubkey_tweak_add(void *arg, int iters) {
+static void bench_pubkey_tweak_add_run(void *arg, int iters) {
     int i;
     bench_data *data = (bench_data*)arg;
 
@@ -183,7 +183,7 @@ static void bench_pubkey_tweak_add(void *arg, int iters) {
     }
 }
 
-static void bench_pubkey_tweak_mul(void *arg, int iters) {
+static void bench_pubkey_tweak_mul_run(void *arg, int iters) {
     int i;
     bench_data *data = (bench_data*)arg;
 
@@ -303,8 +303,8 @@ 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);
-    if (d || have_flag(argc, argv, "ec") || have_flag(argc, argv, "tweak") || have_flag(argc, argv, "ec_pk_tweak_mul")) run_benchmark("ec_pk_tweak_mul", bench_pubkey_tweak_mul, bench_tweak_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_run, bench_tweak_setup, NULL, &data, 10, iters);
+    if (d || have_flag(argc, argv, "ec") || have_flag(argc, argv, "tweak") || have_flag(argc, argv, "ec_pk_tweak_mul")) run_benchmark("ec_pk_tweak_mul", bench_pubkey_tweak_mul_run, bench_tweak_setup, NULL, &data, 10, iters);
 
     secp256k1_context_destroy(data.ctx);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants