From d0f3123c0c2d4643a0191af6e7c5e18032666605 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Mon, 27 Oct 2025 21:59:46 +0100 Subject: [PATCH 1/9] HACK: Don't run unrelevant stuff in bench_ecmult --- src/bench_ecmult.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c index b2bab65d26..026377dd42 100644 --- a/src/bench_ecmult.c +++ b/src/bench_ecmult.c @@ -366,18 +366,18 @@ int main(int argc, char **argv) { print_output_table_header_row(); /* Initialize offset1 and offset2 */ hash_into_offset(&data, 0); - run_ecmult_bench(&data, iters); + /* run_ecmult_bench(&data, iters); */ - for (i = 1; i <= 8; ++i) { - run_ecmult_multi_bench(&data, i, 1, 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) { + for (p = 3; p <= 11; p +=1) { + for (i = 10; i <= 16; i+=2) { run_ecmult_multi_bench(&data, i << p, 1, iters); } } From 8eca7f652400c0ee1b0a95476709105a8db5cbf5 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Mon, 27 Oct 2025 14:38:59 +0100 Subject: [PATCH 2/9] group/geh: Add homogeneous group representation Even though the formulas are complete, infinity is special cased for performance. --- src/group.h | 20 +++- src/group_impl.h | 298 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 317 insertions(+), 1 deletion(-) diff --git a/src/group.h b/src/group.h index 05ae0d203c..e2a834cddc 100644 --- a/src/group.h +++ b/src/group.h @@ -32,6 +32,16 @@ typedef struct { int infinity; /* whether this represents the point at infinity */ } secp256k1_gej; +/** A group element of the secp256k1 curve, in homogeneous coordinates. + * FIXME Note: For exhastive test mode, secp256k1 is replaced by a small subgroup of a different curve. + */ +typedef struct { + secp256k1_fe x; /* actual X: x/z */ + secp256k1_fe y; /* actual Y: y/z */ + secp256k1_fe z; + int infinity; /* whether this represents the point at infinity */ +} secp256k1_geh; + #define SECP256K1_GEJ_CONST(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) {SECP256K1_FE_CONST((a),(b),(c),(d),(e),(f),(g),(h)), SECP256K1_FE_CONST((i),(j),(k),(l),(m),(n),(o),(p)), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1), 0} #define SECP256K1_GEJ_CONST_INFINITY {SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 0), 1} @@ -50,7 +60,11 @@ typedef struct { #define SECP256K1_GE_Y_MAGNITUDE_MAX 3 #define SECP256K1_GEJ_X_MAGNITUDE_MAX 4 #define SECP256K1_GEJ_Y_MAGNITUDE_MAX 4 -#define SECP256K1_GEJ_Z_MAGNITUDE_MAX 1 +#define SECP256K1_GEJ_Z_MAGNITUDE_MAX 2 /* This would be 1 if it wasn't for secp256k1_gej_set_geh_var. */ +/* FIXME Figure out reasonable values here: */ +#define SECP256K1_GEH_X_MAGNITUDE_MAX 3 +#define SECP256K1_GEH_Y_MAGNITUDE_MAX 4 /* This would be 2 if it wasn't for secp256k1_geh_set_gej_var. */ +#define SECP256K1_GEH_Z_MAGNITUDE_MAX 2 /** Set a group element equal to the point with given X and Y coordinates */ static void secp256k1_ge_set_xy(secp256k1_ge *r, const secp256k1_fe *x, const secp256k1_fe *y); @@ -213,4 +227,8 @@ static void secp256k1_ge_verify(const secp256k1_ge *a); static void secp256k1_gej_verify(const secp256k1_gej *a); #define SECP256K1_GEJ_VERIFY(a) secp256k1_gej_verify(a) +/** Check invariants on a homogeneous group element (no-op unless VERIFY is enabled). */ +static void secp256k1_geh_verify(const secp256k1_geh *a); +#define SECP256K1_GEH_VERIFY(a) secp256k1_geh_verify(a) + #endif /* SECP256K1_GROUP_H */ diff --git a/src/group_impl.h b/src/group_impl.h index 81a24b9d5b..f1c5870703 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -95,6 +95,17 @@ static void secp256k1_gej_verify(const secp256k1_gej *a) { (void)a; } +static void secp256k1_geh_verify(const secp256k1_geh *a) { + SECP256K1_FE_VERIFY(&a->x); + SECP256K1_FE_VERIFY(&a->y); + SECP256K1_FE_VERIFY(&a->z); + SECP256K1_FE_VERIFY_MAGNITUDE(&a->x, SECP256K1_GEH_X_MAGNITUDE_MAX); + SECP256K1_FE_VERIFY_MAGNITUDE(&a->y, SECP256K1_GEH_Y_MAGNITUDE_MAX); + SECP256K1_FE_VERIFY_MAGNITUDE(&a->z, SECP256K1_GEH_Z_MAGNITUDE_MAX); + VERIFY_CHECK(a->infinity == 0 || a->infinity == 1); + (void)a; +} + /* Set r to the affine coordinates of Jacobian point (a.x, a.y, 1/zi). */ static void secp256k1_ge_set_gej_zinv(secp256k1_ge *r, const secp256k1_gej *a, const secp256k1_fe *zi) { secp256k1_fe zi2; @@ -1009,4 +1020,291 @@ static void secp256k1_ge_from_bytes_ext(secp256k1_ge *ge, const unsigned char *d } } +static void secp256k1_geh_set_infinity(secp256k1_geh *r) { + r->infinity = 1; + secp256k1_fe_set_int(&r->x, 0); + secp256k1_fe_set_int(&r->y, 1); + secp256k1_fe_set_int(&r->z, 0); + + SECP256K1_GEH_VERIFY(r); +} + +static int secp256k1_geh_is_infinity(const secp256k1_geh *a) { + SECP256K1_GEH_VERIFY(a); + + return a->infinity; +} + +static void secp256k1_geh_rescale(secp256k1_geh *r, const secp256k1_fe *s) { + /* Operations: 3 mul */ + + SECP256K1_GEH_VERIFY(r); + SECP256K1_FE_VERIFY(s); + + VERIFY_CHECK(!secp256k1_fe_normalizes_to_zero_var(s)); + + secp256k1_fe_mul(&r->x, &r->x, s); /* r->x *= s */ + secp256k1_fe_mul(&r->y, &r->y, s); /* r->y *= s */ + secp256k1_fe_mul(&r->z, &r->z, s); /* r->z *= s */ + + SECP256K1_GEH_VERIFY(r); +} + +static void secp256k1_geh_set_gej_var(secp256k1_geh *r, const secp256k1_gej *a) { + secp256k1_fe zz; + + SECP256K1_GEJ_VERIFY(a); + + if (a->infinity) { + secp256k1_geh_set_infinity(r); + return; + } + r->infinity = 0; + + /* zh = zj^3 */ + secp256k1_fe_sqr(&zz, &a->z); + secp256k1_fe_mul(&r->z, &a->z, &zz); + + /* yh = yj */ + r->y = a->y; + + /* xh = xj * zj*/ + secp256k1_fe_mul(&r->x, &a->x, &a->z); + + SECP256K1_GEH_VERIFY(r); +} + +static void secp256k1_gej_set_geh_var(secp256k1_gej *r, const secp256k1_geh *a) { + secp256k1_fe zz; + + SECP256K1_GEH_VERIFY(a); + + if (secp256k1_geh_is_infinity(a)) { + secp256k1_gej_set_infinity(r); + return; + } + r->infinity = 0; + + r->z = a->z; + secp256k1_fe_mul(&r->x, &a->x, &a->z); + secp256k1_fe_sqr(&zz, &a->z); + secp256k1_fe_mul(&r->y, &a->y, &zz); + + SECP256K1_GEJ_VERIFY(r); +} + +static void secp256k1_geh_add_var(secp256k1_geh *r, const secp256k1_geh *a, const secp256k1_geh *b) { + /* temporaries t0..t4 and intermediate x3, y3, z3 */ + secp256k1_fe t0, t1, t2, t3, t4; + secp256k1_fe x3, y3, z3; + + SECP256K1_GEH_VERIFY(a); + SECP256K1_GEH_VERIFY(b); + + if (secp256k1_geh_is_infinity(a)) { + *r = *b; + return; + } + if (secp256k1_geh_is_infinity(b)) { + *r = *a; + return; + } + + /* 1. t0 <- X1 * X2 */ + secp256k1_fe_mul(&t0, &a->x, &b->x); + + /* 2. t1 <- Y1 * Y2 */ + secp256k1_fe_mul(&t1, &a->y, &b->y); + + /* 3. t2 <- Z1 * Z2 */ + secp256k1_fe_mul(&t2, &a->z, &b->z); + + /* 4. t3 <- X1 + Y1 */ + t3 = a->x; + secp256k1_fe_add(&t3, &a->y); + secp256k1_fe_normalize_weak(&t3); + + /* 5. t4 <- X2 + Y2 */ + t4 = b->x; + secp256k1_fe_add(&t4, &b->y); + + /* 6. t3 <- t3 * t4 */ + secp256k1_fe_mul(&t3, &t3, &t4); + + /* 7. t4 <- t0 + t1 */ + t4 = t0; + secp256k1_fe_add(&t4, &t1); + + /* 8. t3 <- t3 - t4 */ + secp256k1_fe_negate(&t4, &t4, 2); + secp256k1_fe_add(&t3, &t4); + + /* 9. t4 <- Y1 + Z1 */ + t4 = a->y; + secp256k1_fe_add(&t4, &a->z); + + /* 10. x3 <- Y2 + Z2 */ + x3 = b->y; + secp256k1_fe_add(&x3, &b->z); + + /* 11. t4 <- t4 * x3 */ + secp256k1_fe_mul(&t4, &t4, &x3); + + /* 12. x3 <- t1 + t2 */ + x3 = t1; + secp256k1_fe_add(&x3, &t2); + + /* 13. t4 <- t4 - x3 */ + secp256k1_fe_negate(&x3, &x3, 2); + secp256k1_fe_add(&t4, &x3); + + /* 14. x3 <- X1 + Z1 */ + x3 = a->x; + secp256k1_fe_add(&x3, &a->z); + + /* 15. y3 <- X2 + Z2 */ + y3 = b->x; + secp256k1_fe_add(&y3, &b->z); + + /* 16. x3 <- x3 * y3 */ + secp256k1_fe_mul(&x3, &x3, &y3); + + /* 17. y3 <- t0 + t2 */ + y3 = t0; + secp256k1_fe_add(&y3, &t2); + + /* 18. y3 <- x3 - y3 */ + secp256k1_fe_negate(&y3, &y3, 2); + secp256k1_fe_add(&y3, &x3); + + /* 19. x3 <- t0 + t0 */ + /* 20. t0 <- x3 + t0 */ + secp256k1_fe_mul_int(&t0, 3); + + /* 21. t2 <- 3b * t2 */ + secp256k1_fe_mul_int(&t2, 3 * SECP256K1_B); + + /* 22. z3 <- t1 + t2 */ + z3 = t1; + secp256k1_fe_normalize_weak(&t2); + secp256k1_fe_add(&z3, &t2); + + /* 23. t1 <- t1 - t2 */ + secp256k1_fe_negate(&t2, &t2, 1); + secp256k1_fe_add(&t1, &t2); + + /* 24. y3 <- 3b * y3 */ + secp256k1_fe_normalize_weak(&y3); + secp256k1_fe_mul_int(&y3, 3 * SECP256K1_B); + + /* 25. x3 <- t4 * y3 */ + secp256k1_fe_normalize_weak(&y3); + secp256k1_fe_mul(&x3, &t4, &y3); + + /* 26. t2 <- t3 * t1 */ + secp256k1_fe_mul(&t2, &t3, &t1); + + /* 27. x3 <- t2 - x3 */ + secp256k1_fe_negate(&x3, &x3, 1); + secp256k1_fe_add(&x3, &t2); + + /* 28. y3 <- y3 * t0 */ + secp256k1_fe_mul(&y3, &y3, &t0); + + /* 29. t1 <- t1 * z3 */ + secp256k1_fe_mul(&t1, &t1, &z3); + + /* 30. y3 <- t1 + y3 */ + secp256k1_fe_add(&y3, &t1); + + /* 31. t0 <- t0 * t3 */ + secp256k1_fe_mul(&t0, &t0, &t3); + + /* 32. z3 <- z3 * t4 */ + secp256k1_fe_mul(&z3, &z3, &t4); + + /* 33. z3 <- z3 + t0 */ + secp256k1_fe_add(&z3, &t0); + + r->x = x3; + r->y = y3; + r->z = z3; + r->infinity = secp256k1_fe_normalizes_to_zero_var(&z3); + + SECP256K1_GEH_VERIFY(r); +} + +void secp256k1_geh_double_var(secp256k1_geh *r, const secp256k1_geh *a) { + /* temporaries t0..t2 and intermediate x3, y3, z3 */ + secp256k1_fe t0, t1, t2; + secp256k1_fe x3, y3, z3; + + SECP256K1_GEH_VERIFY(a); + + if (secp256k1_geh_is_infinity(a)) { + secp256k1_geh_set_infinity(r); + return; + } + r->infinity = 0; + + /* 1. t0 <- Y * Y */ + secp256k1_fe_sqr(&t0, &a->y); + + /* 2. z3 <- t0 + t0 */ + /* 3. z3 <- z3 + z3 */ + /* 4. z3 <- z3 + z3 */ + z3 = t0; + secp256k1_fe_mul_int(&z3, 8); + + /* 5. t1 <- Y * Z */ + secp256k1_fe_mul(&t1, &a->y, &a->z); + + /* 6. t2 <- Z * Z */ + secp256k1_fe_sqr(&t2, &a->z); + + /* 7. t2 <- b3 * t2 */ + secp256k1_fe_mul_int(&t2, 3 * SECP256K1_B); + + /* 8. x3 <- t2 * z3 */ + secp256k1_fe_normalize_weak(&z3); + secp256k1_fe_normalize_weak(&t2); + secp256k1_fe_mul(&x3, &t2, &z3); + + /* 9. y3 <- t0 + t2 */ + y3 = t0; + secp256k1_fe_add(&y3, &t2); + + /* 10. z3 <- t1 * z3 */ + secp256k1_fe_mul(&z3, &z3, &t1); + + /* 11. t1 <- t2 + t2 */ + /* 12. t2 <- t1 + t2 */ + secp256k1_fe_mul_int(&t2, 3); + + /* 13. t0 <- t0 - t2 */ + secp256k1_fe_negate(&t2, &t2, 3); + secp256k1_fe_add(&t0, &t2); + + /* 14. y3 <- t0 * y3 */ + secp256k1_fe_mul(&y3, &y3, &t0); + + /* 15. y3 <- x3 + y3 */ + secp256k1_fe_add(&y3, &x3); + + /* 16. t1 <- X * Y */ + secp256k1_fe_mul(&t1, &a->x, &a->y); + + /* 17. x3 <- t0 * t1 */ + secp256k1_fe_mul(&x3, &t0, &t1); + + /* 18. x3 <- x3 + x3 */ + secp256k1_fe_mul_int(&x3, 2); + + r->x = x3; + r->y = y3; + r->z = z3; + + SECP256K1_GEH_VERIFY(r); +} + #endif /* SECP256K1_GROUP_IMPL_H */ From 75d0e66958465b8100823c44ccace21cab580ff8 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Tue, 4 Nov 2025 09:25:42 +0100 Subject: [PATCH 3/9] geh: Add testutil functions (TODO: unused) --- src/testutil.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/testutil.h b/src/testutil.h index 480f6a1a0c..b5253f5416 100644 --- a/src/testutil.h +++ b/src/testutil.h @@ -115,6 +115,21 @@ static void testutil_random_gej_test(secp256k1_gej *gej) { testutil_random_ge_jacobian_test(gej, &ge); } +static void testutil_random_ge_homogeneous_test(secp256k1_geh *geh, const secp256k1_ge *ge) { + secp256k1_fe z; + testutil_random_fe_non_zero_test(&z); + secp256k1_fe_mul(&geh->x, &ge->x, &z); + secp256k1_fe_mul(&geh->y, &ge->y, &z); + geh->z = z; + geh->infinity = ge->infinity; +} + +static void testutil_random_geh_test(secp256k1_geh *geh) { + secp256k1_ge ge; + testutil_random_ge_test(&ge); + testutil_random_ge_homogeneous_test(geh, &ge); +} + static void testutil_random_pubkey_test(secp256k1_pubkey *pk) { secp256k1_ge ge; testutil_random_ge_test(&ge); From a995c0e63fca0b1e3ccf62cf744756926dcbcf15 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Tue, 4 Nov 2025 09:32:57 +0100 Subject: [PATCH 4/9] geh: Add some tests --- src/tests.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/tests.c b/src/tests.c index 0ccdbeecf7..f85bbf69dd 100644 --- a/src/tests.c +++ b/src/tests.c @@ -4098,6 +4098,92 @@ static void run_gej(void) { } } +static void test_geh_gej_roundtrip(secp256k1_gej *a) { + secp256k1_geh h; + secp256k1_gej r; + secp256k1_fe s; + + /* gej -> geh -> rescale -> gej */ + secp256k1_geh_set_gej_var(&h, a); + testutil_random_fe_non_zero_test(&s); + secp256k1_geh_rescale(&h, &s); + secp256k1_gej_set_geh_var(&r, &h); + + CHECK(secp256k1_gej_eq_var(a, &r)); +} + +static void run_geh_gej_roundtrip(void) { + int i; + secp256k1_gej a; + + /* Test infinity */ + secp256k1_gej_set_infinity(&a); + test_geh_gej_roundtrip(&a); + + /* Test random points */ + for (i = 0; i < COUNT; i++) { + testutil_random_gej_test(&a); + test_geh_gej_roundtrip(&a); + } +} + +static void test_geh_gej_add_consistency(secp256k1_gej *a, secp256k1_gej *b) { + secp256k1_geh a_h, b_h, sum_h; + secp256k1_gej sum_gej, sum_from_h; + + secp256k1_geh_set_gej_var(&a_h, a); + secp256k1_geh_set_gej_var(&b_h, b); + + secp256k1_geh_add_var(&sum_h, &a_h, &b_h); + secp256k1_gej_add_var(&sum_gej, a, b, NULL); + + secp256k1_gej_set_geh_var(&sum_from_h, &sum_h); + + CHECK(secp256k1_gej_eq_var(&sum_gej, &sum_from_h)); +} + +static void test_geh_gej_double_consistency(secp256k1_gej *a) { + secp256k1_geh a_h, dbl_h; + secp256k1_gej dbl_gej, dbl_from_h; + + secp256k1_geh_set_gej_var(&a_h, a); + + secp256k1_geh_double_var(&dbl_h, &a_h); + secp256k1_gej_double_var(&dbl_gej, a, NULL); + + secp256k1_gej_set_geh_var(&dbl_from_h, &dbl_h); + + CHECK(secp256k1_gej_eq_var(&dbl_gej, &dbl_from_h)); +} + +static void run_geh_gej_consistency(void) { + int i; + secp256k1_gej a, b; + + /* Test infinity cases */ + testutil_random_gej_test(&a); + secp256k1_gej_set_infinity(&b); + + test_geh_gej_add_consistency(&a, &b); + test_geh_gej_add_consistency(&b, &a); + + secp256k1_gej_set_infinity(&a); + test_geh_gej_add_consistency(&a, &b); + + test_geh_gej_double_consistency(&a); + + for (i = 0; i < COUNT; i++) { + testutil_random_gej_test(&a); + testutil_random_gej_test(&b); + + test_geh_gej_add_consistency(&a, &b); + test_geh_gej_add_consistency(&b, &a); + + test_geh_gej_double_consistency(&a); + test_geh_gej_double_consistency(&b); + } +} + static void test_ec_combine(void) { secp256k1_scalar sum = secp256k1_scalar_zero; secp256k1_pubkey data[6]; @@ -7716,6 +7802,9 @@ static const struct tf_test_entry tests_field[] = { static const struct tf_test_entry tests_group[] = { CASE(ge), CASE(gej), + /* TODO CASE(geh), */ + CASE(geh_gej_roundtrip), + CASE(geh_gej_consistency), CASE(group_decompress), }; From 0f210198f4d6c956307e55fb031b3f87c305a65e Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Mon, 27 Oct 2025 15:55:17 +0100 Subject: [PATCH 5/9] geh: Add internal benchmarks (TODO naming) --- src/bench_internal.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/bench_internal.c b/src/bench_internal.c index 8688a4dc77..f3d40f29c5 100644 --- a/src/bench_internal.c +++ b/src/bench_internal.c @@ -42,6 +42,7 @@ typedef struct { secp256k1_fe fe[4]; secp256k1_ge ge[2]; secp256k1_gej gej[2]; + secp256k1_geh geh[2]; unsigned char data[64]; int wnaf[256]; } bench_inv; @@ -94,6 +95,8 @@ static void bench_setup(void* arg) { secp256k1_gej_rescale(&data->gej[0], &data->fe[2]); secp256k1_gej_set_ge(&data->gej[1], &data->ge[1]); secp256k1_gej_rescale(&data->gej[1], &data->fe[3]); + secp256k1_geh_set_gej_var(&data->geh[0], &data->gej[0]); + secp256k1_geh_set_gej_var(&data->geh[1], &data->gej[1]); memcpy(data->data, init[0], 32); memcpy(data->data + 32, init[1], 32); } @@ -272,6 +275,26 @@ static void bench_group_double_var(void* arg, int iters) { } } +static void bench_group_doubleh(void* arg, int iters) { + int i; + bench_inv *data = (bench_inv*)arg; + + for (i = 0; i < iters; i++) { + secp256k1_geh_double_var(&data->geh[0], &data->geh[0]); + } +} + +static void bench_group_geh_gej_roundtrip(void* arg, int iters) { + int i; + bench_inv *data = (bench_inv*)arg; + + for (i = 0; i < iters; i++) { + secp256k1_gej tmpj; + secp256k1_gej_set_geh_var(&tmpj, &data->geh[0]); + secp256k1_geh_set_gej_var(&data->geh[0], &tmpj); + } +} + static void bench_group_add_var(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -281,6 +304,15 @@ static void bench_group_add_var(void* arg, int iters) { } } +static void bench_group_addh(void* arg, int iters) { + int i; + bench_inv *data = (bench_inv*)arg; + + for (i = 0; i < iters; i++) { + secp256k1_geh_add_var(&data->geh[0], &data->geh[0], &data->geh[1]); + } +} + static void bench_group_add_affine(void* arg, int iters) { int i; bench_inv *data = (bench_inv*)arg; @@ -419,7 +451,10 @@ int main(int argc, char **argv) { if (d || have_flag(argc, argv, "field") || have_flag(argc, argv, "sqrt")) run_benchmark("field_sqrt", bench_field_sqrt, bench_setup, NULL, &data, 10, iters); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "double")) run_benchmark("group_double_var", bench_group_double_var, bench_setup, NULL, &data, 10, iters*10); + if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "double")) run_benchmark("group_doubleh", bench_group_doubleh, bench_setup, NULL, &data, 10, iters*10); + if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "double")) run_benchmark("group_rt", bench_group_geh_gej_roundtrip, bench_setup, NULL, &data, 10, iters*10); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_var", bench_group_add_var, bench_setup, NULL, &data, 10, iters*10); + if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_addh", bench_group_addh, bench_setup, NULL, &data, 10, iters*10); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_affine", bench_group_add_affine, bench_setup, NULL, &data, 10, iters*10); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_affine_var", bench_group_add_affine_var, bench_setup, NULL, &data, 10, iters*10); if (d || have_flag(argc, argv, "group") || have_flag(argc, argv, "add")) run_benchmark("group_add_zinv_var", bench_group_add_zinv_var, bench_setup, NULL, &data, 10, iters*10); From 2ccc5e02776b8ae3483adf342b741abd99e48931 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Mon, 27 Oct 2025 14:39:07 +0100 Subject: [PATCH 6/9] pippenger/geh step 1: Use geh in accumulation loop --- src/ecmult_impl.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h index 0b53b3fcb9..60a0dc2c4e 100644 --- a/src/ecmult_impl.h +++ b/src/ecmult_impl.h @@ -519,7 +519,10 @@ static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_wi } for (i = n_wnaf - 1; i >= 0; i--) { + secp256k1_geh running_sumh; + secp256k1_geh rh; secp256k1_gej running_sum; + secp256k1_gej rj; for(j = 0; j < ECMULT_TABLE_SIZE(bucket_window+2); j++) { secp256k1_gej_set_infinity(&buckets[j]); @@ -553,7 +556,8 @@ static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_wi secp256k1_gej_double_var(r, r, NULL); } - secp256k1_gej_set_infinity(&running_sum); + secp256k1_geh_set_infinity(&running_sumh); + secp256k1_geh_set_infinity(&rh); /* Accumulate the sum: bucket[0] + 3*bucket[1] + 5*bucket[2] + 7*bucket[3] + ... * = bucket[0] + bucket[1] + bucket[2] + bucket[3] + ... * + 2 * (bucket[1] + 2*bucket[2] + 3*bucket[3] + ...) @@ -563,11 +567,16 @@ static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_wi * The doubling is done implicitly by deferring the final window doubling (of 'r'). */ for(j = ECMULT_TABLE_SIZE(bucket_window+2) - 1; j > 0; j--) { - secp256k1_gej_add_var(&running_sum, &running_sum, &buckets[j], NULL); - secp256k1_gej_add_var(r, r, &running_sum, NULL); + secp256k1_geh tmp; + secp256k1_geh_set_gej_var(&tmp, &buckets[j]); + secp256k1_geh_add_var(&running_sumh, &running_sumh, &tmp); + secp256k1_geh_add_var(&rh, &rh, &running_sumh); } - + secp256k1_gej_set_geh_var(&running_sum, &running_sumh); secp256k1_gej_add_var(&running_sum, &running_sum, &buckets[0], NULL); + + secp256k1_gej_set_geh_var(&rj, &rh); + secp256k1_gej_add_var(r, r, &rj, NULL); secp256k1_gej_double_var(r, r, NULL); secp256k1_gej_add_var(r, r, &running_sum, NULL); } From 867fe34dd4f642fb966d5ee652e0d77a9adafa41 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Mon, 27 Oct 2025 19:49:06 +0100 Subject: [PATCH 7/9] pippenger/geh step 2: Use geh in entire accumulation --- src/ecmult_impl.h | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h index 60a0dc2c4e..21132644ae 100644 --- a/src/ecmult_impl.h +++ b/src/ecmult_impl.h @@ -497,12 +497,13 @@ struct secp256k1_pippenger_state { * to the point's wnaf[i]. Second, the buckets are added together such that * r += 1*bucket[0] + 3*bucket[1] + 5*bucket[2] + ... */ -static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_window, struct secp256k1_pippenger_state *state, secp256k1_gej *r, const secp256k1_scalar *sc, const secp256k1_ge *pt, size_t num) { +static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_window, struct secp256k1_pippenger_state *state, secp256k1_gej *rj, const secp256k1_scalar *sc, const secp256k1_ge *pt, size_t num) { size_t n_wnaf = WNAF_SIZE(bucket_window+1); size_t np; size_t no = 0; int i; int j; + secp256k1_geh r; for (np = 0; np < num; ++np) { if (secp256k1_scalar_is_zero(&sc[np]) || secp256k1_ge_is_infinity(&pt[np])) { @@ -512,17 +513,15 @@ static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_wi state->ps[no].skew_na = secp256k1_wnaf_fixed(&state->wnaf_na[no*n_wnaf], &sc[np], bucket_window+1); no++; } - secp256k1_gej_set_infinity(r); + secp256k1_geh_set_infinity(&r); if (no == 0) { return 1; } for (i = n_wnaf - 1; i >= 0; i--) { - secp256k1_geh running_sumh; - secp256k1_geh rh; - secp256k1_gej running_sum; - secp256k1_gej rj; + secp256k1_geh running_sum; + secp256k1_geh tmph; for(j = 0; j < ECMULT_TABLE_SIZE(bucket_window+2); j++) { secp256k1_gej_set_infinity(&buckets[j]); @@ -553,11 +552,10 @@ static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_wi } for(j = 0; j < bucket_window; j++) { - secp256k1_gej_double_var(r, r, NULL); + secp256k1_geh_double_var(&r, &r); } - secp256k1_geh_set_infinity(&running_sumh); - secp256k1_geh_set_infinity(&rh); + secp256k1_geh_set_infinity(&running_sum); /* Accumulate the sum: bucket[0] + 3*bucket[1] + 5*bucket[2] + 7*bucket[3] + ... * = bucket[0] + bucket[1] + bucket[2] + bucket[3] + ... * + 2 * (bucket[1] + 2*bucket[2] + 3*bucket[3] + ...) @@ -567,19 +565,18 @@ static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_wi * The doubling is done implicitly by deferring the final window doubling (of 'r'). */ for(j = ECMULT_TABLE_SIZE(bucket_window+2) - 1; j > 0; j--) { - secp256k1_geh tmp; - secp256k1_geh_set_gej_var(&tmp, &buckets[j]); - secp256k1_geh_add_var(&running_sumh, &running_sumh, &tmp); - secp256k1_geh_add_var(&rh, &rh, &running_sumh); + secp256k1_geh_set_gej_var(&tmph, &buckets[j]); + secp256k1_geh_add_var(&running_sum, &running_sum, &tmph); + secp256k1_geh_add_var(&r, &r, &running_sum); } - secp256k1_gej_set_geh_var(&running_sum, &running_sumh); - secp256k1_gej_add_var(&running_sum, &running_sum, &buckets[0], NULL); + secp256k1_geh_set_gej_var(&tmph, &buckets[0]); + secp256k1_geh_add_var(&running_sum, &running_sum, &tmph); - secp256k1_gej_set_geh_var(&rj, &rh); - secp256k1_gej_add_var(r, r, &rj, NULL); - secp256k1_gej_double_var(r, r, NULL); - secp256k1_gej_add_var(r, r, &running_sum, NULL); + secp256k1_geh_double_var(&r, &r); + secp256k1_geh_add_var(&r, &r, &running_sum); } + /* TODO Return geh instead */ + secp256k1_gej_set_geh_var(rj, &r); return 1; } From 0fc73f397bc6a10b723b89d6f2a0dc52e7621748 Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Tue, 28 Oct 2025 13:23:39 +0100 Subject: [PATCH 8/9] pippenger/geh step 3: Go back to gej for doublings --- src/ecmult_impl.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h index 21132644ae..f900aab0f6 100644 --- a/src/ecmult_impl.h +++ b/src/ecmult_impl.h @@ -551,9 +551,23 @@ static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_wi } } - for(j = 0; j < bucket_window; j++) { - secp256k1_geh_double_var(&r, &r); + /* TODO these tricks seem to make things slower in practice. */ + /* if ((size_t)i != n_wnaf - 1) { /\* No reason to double infinity *\/ */ + /* if (bucket_window >= 3) { /\* Switch to gej for many doublings *\/ */ + { + secp256k1_gej rj_tmp; + secp256k1_gej_set_geh_var(&rj_tmp, &r); + for (j = 0; j < bucket_window; j++) { + secp256k1_gej_double_var(&rj_tmp, &rj_tmp, NULL); + } + secp256k1_geh_set_gej_var(&r, &rj_tmp); } + /* } else { */ + /* for (j = 0; j < bucket_window; j++) { */ + /* secp256k1_geh_double_var(&r, &r); */ + /* } */ + /* } */ + /* } */ secp256k1_geh_set_infinity(&running_sum); /* Accumulate the sum: bucket[0] + 3*bucket[1] + 5*bucket[2] + 7*bucket[3] + ... From 9cfba13041266a28043ee83b0a8e26d9f7cfa1ad Mon Sep 17 00:00:00 2001 From: Tim Ruffing Date: Thu, 6 Nov 2025 16:41:21 +0100 Subject: [PATCH 9/9] fixups --- src/ecmult_impl.h | 1 + src/group_impl.h | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h index f900aab0f6..40de041441 100644 --- a/src/ecmult_impl.h +++ b/src/ecmult_impl.h @@ -516,6 +516,7 @@ static int secp256k1_ecmult_pippenger_wnaf(secp256k1_gej *buckets, int bucket_wi secp256k1_geh_set_infinity(&r); if (no == 0) { + /* FIXME value of rj */ return 1; } diff --git a/src/group_impl.h b/src/group_impl.h index f1c5870703..2e7a67e47b 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -1101,6 +1101,10 @@ static void secp256k1_geh_add_var(secp256k1_geh *r, const secp256k1_geh *a, cons SECP256K1_GEH_VERIFY(a); SECP256K1_GEH_VERIFY(b); + /* TODO VERIFY_CHECK(a != b); because otherwise steps 1, 2, 3 violate + * the calling convention of _fe_mul. Or we need to create local copies + * of b->x, b->y, and b->z first. */ + if (secp256k1_geh_is_infinity(a)) { *r = *b; return;