Skip to content

Commit 36b22c9

Browse files
committed
Use scratch space dependent batching in ecmult_multi
1 parent 355a38f commit 36b22c9

File tree

3 files changed

+191
-54
lines changed

3 files changed

+191
-54
lines changed

src/ecmult.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ typedef int (secp256k1_ecmult_multi_callback)(secp256k1_scalar *sc, secp256k1_ge
3434

3535
/**
3636
* Multi-multiply: R = inp_g_sc * G + sum_i ni * Ai.
37+
* Chooses the right algorithm for a given number of points and scratch space
38+
* size. Resets and overwrites the given scratch space. If the points do not
39+
* fit in the scratch space the algorithm is repeatedly run with batches of
40+
* points.
3741
* Returns: 1 on success (including when inp_g_sc is NULL and n is 0)
3842
* 0 if there is not enough scratch space for a single point or
3943
* callback returns 0

src/ecmult_impl.h

Lines changed: 120 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#define SECP256K1_ECMULT_IMPL_H
99

1010
#include <string.h>
11+
#include <stdint.h>
1112

1213
#include "group.h"
1314
#include "scalar.h"
@@ -55,13 +56,21 @@
5556
#define PIPPENGER_SCRATCH_OBJECTS 6
5657
#define STRAUSS_SCRATCH_OBJECTS 6
5758

59+
#define PIPPENGER_MAX_BUCKET_WINDOW 12
60+
5861
/* Minimum number of points for which pippenger_wnaf is faster than strauss wnaf */
5962
#ifdef USE_ENDOMORPHISM
6063
#define ECMULT_PIPPENGER_THRESHOLD 96
6164
#else
6265
#define ECMULT_PIPPENGER_THRESHOLD 156
6366
#endif
6467

68+
#ifdef USE_ENDOMORPHISM
69+
#define ECMULT_MAX_POINTS_PER_BATCH 5000000
70+
#else
71+
#define ECMULT_MAX_POINTS_PER_BATCH 10000000
72+
#endif
73+
6574
/** Fill a table 'prej' with precomputed odd multiples of a. Prej will contain
6675
* the values [1*a,3*a,...,(2*n-1)*a], so it space for n values. zr[0] will
6776
* contain prej[0].z / a.z. The other zr[i] values = prej[i].z / prej[i-1].z.
@@ -545,6 +554,10 @@ static int secp256k1_ecmult_strauss_batch_single(const secp256k1_ecmult_context
545554
return secp256k1_ecmult_strauss_batch(actx, scratch, r, inp_g_sc, cb, cbdata, n, 0);
546555
}
547556

557+
static size_t secp256k1_strauss_max_points(secp256k1_scratch *scratch) {
558+
return secp256k1_scratch_max_allocation(scratch, STRAUSS_SCRATCH_OBJECTS) / secp256k1_strauss_scratch_size(1);
559+
}
560+
548561
/** Convert a number to WNAF notation.
549562
* The number becomes represented by sum(2^{wi} * wnaf[i], i=0..WNAF_SIZE(w)+1) - return_val.
550563
* It has the following guarantees:
@@ -724,7 +737,7 @@ static int secp256k1_pippenger_bucket_window(size_t n) {
724737
} else if (n <= 28600) {
725738
return 11;
726739
} else {
727-
return 12;
740+
return PIPPENGER_MAX_BUCKET_WINDOW;
728741
}
729742
#else
730743
if (n <= 2) {
@@ -750,11 +763,48 @@ static int secp256k1_pippenger_bucket_window(size_t n) {
750763
} else if (n <= 35000) {
751764
return 11;
752765
} else {
753-
return 12;
766+
return PIPPENGER_MAX_BUCKET_WINDOW;
754767
}
755768
#endif
756769
}
757770

771+
/**
772+
* Returns the maximum optimal number of points for a bucket_window.
773+
*/
774+
static size_t secp256k1_pippenger_bucket_window_inv(int bucket_window) {
775+
switch(bucket_window) {
776+
#ifdef USE_ENDOMORPHISM
777+
case 1: return 4;
778+
case 2: return 8;
779+
case 3: return 40;
780+
case 4: return 117;
781+
case 5: return 280;
782+
case 6: return 480;
783+
case 7: return 2560;
784+
case 8: return 2560;
785+
case 9: return 9200;
786+
case 10: return 17400;
787+
case 11: return 28600;
788+
case PIPPENGER_MAX_BUCKET_WINDOW: return SIZE_MAX;
789+
#else
790+
case 1: return 2;
791+
case 2: return 9;
792+
case 3: return 42;
793+
case 4: return 100;
794+
case 5: return 280;
795+
case 6: return 610;
796+
case 7: return 1920;
797+
case 8: return 3400;
798+
case 9: return 10240;
799+
case 10: return 19000;
800+
case 11: return 35000;
801+
case PIPPENGER_MAX_BUCKET_WINDOW: return SIZE_MAX;
802+
#endif
803+
}
804+
return 0;
805+
}
806+
807+
758808
#ifdef USE_ENDOMORPHISM
759809
SECP256K1_INLINE static void secp256k1_ecmult_endo_split(secp256k1_scalar *s1, secp256k1_scalar *s2, secp256k1_ge *p1, secp256k1_ge *p2) {
760810
secp256k1_scalar tmp = *s1;
@@ -865,11 +915,53 @@ static int secp256k1_ecmult_pippenger_batch_single(const secp256k1_ecmult_contex
865915
return secp256k1_ecmult_pippenger_batch(actx, scratch, r, inp_g_sc, cb, cbdata, n, 0);
866916
}
867917

868-
#define MAX_BATCH_SIZE 1024
918+
/**
919+
* Returns the maximum number of points in addition to G that can be used with
920+
* a given scratch space. The function ensures that fewer points may also be
921+
* used.
922+
*/
923+
static size_t secp256k1_pippenger_max_points(secp256k1_scratch *scratch) {
924+
size_t max_alloc = secp256k1_scratch_max_allocation(scratch, PIPPENGER_SCRATCH_OBJECTS);
925+
int bucket_window;
926+
size_t res = 0;
927+
928+
for (bucket_window = 1; bucket_window <= PIPPENGER_MAX_BUCKET_WINDOW; bucket_window++) {
929+
size_t n_points;
930+
size_t max_points = secp256k1_pippenger_bucket_window_inv(bucket_window);
931+
size_t space_for_points;
932+
size_t space_overhead;
933+
size_t entry_size = sizeof(secp256k1_ge) + sizeof(secp256k1_scalar) + sizeof(struct secp256k1_pippenger_point_state) + (WNAF_SIZE(bucket_window+1)+1)*sizeof(int);
934+
935+
#ifdef USE_ENDOMORPHISM
936+
entry_size = 2*entry_size;
937+
#endif
938+
space_overhead = ((1<<bucket_window) * sizeof(secp256k1_gej) + entry_size + sizeof(struct secp256k1_pippenger_state));
939+
if (space_overhead > max_alloc) {
940+
break;
941+
}
942+
space_for_points = max_alloc - space_overhead;
943+
944+
n_points = space_for_points/entry_size;
945+
n_points = n_points > max_points ? max_points : n_points;
946+
if (n_points > res) {
947+
res = n_points;
948+
}
949+
if (n_points < max_points) {
950+
/* A larger bucket_window may support even more points. But if we
951+
* would choose that then the caller couldn't safely use any number
952+
* smaller than what this function returns */
953+
break;
954+
}
955+
}
956+
return res;
957+
}
958+
869959
typedef int (*secp256k1_ecmult_multi_func)(const secp256k1_ecmult_context*, secp256k1_scratch*, secp256k1_gej*, const secp256k1_scalar*, secp256k1_ecmult_multi_callback cb, void*, size_t);
870960
static int secp256k1_ecmult_multi_var(const secp256k1_ecmult_context *ctx, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n) {
871961
size_t i;
872962

963+
int (*f)(const secp256k1_ecmult_context*, secp256k1_scratch*, secp256k1_gej*, const secp256k1_scalar*, secp256k1_ecmult_multi_callback cb, void*, size_t, size_t);
964+
size_t max_points;
873965
size_t n_batches;
874966
size_t n_batch_points;
875967

@@ -883,25 +975,36 @@ static int secp256k1_ecmult_multi_var(const secp256k1_ecmult_context *ctx, secp2
883975
return 1;
884976
}
885977

886-
if(n <= ECMULT_PIPPENGER_THRESHOLD) {
887-
if(!secp256k1_ecmult_strauss_batch(ctx, scratch, r, inp_g_sc, cb, cbdata, n, 0)) {
978+
max_points = secp256k1_pippenger_max_points(scratch);
979+
if (max_points == 0) {
980+
return 0;
981+
} else if (max_points > ECMULT_MAX_POINTS_PER_BATCH) {
982+
max_points = ECMULT_MAX_POINTS_PER_BATCH;
983+
}
984+
n_batches = (n+max_points-1)/max_points;
985+
n_batch_points = (n+n_batches-1)/n_batches;
986+
987+
if (n_batch_points >= ECMULT_PIPPENGER_THRESHOLD) {
988+
f = secp256k1_ecmult_pippenger_batch;
989+
} else {
990+
max_points = secp256k1_strauss_max_points(scratch);
991+
if (max_points == 0) {
888992
return 0;
889993
}
890-
} else {
891-
n_batches = (n+MAX_BATCH_SIZE-1)/MAX_BATCH_SIZE;
994+
n_batches = (n+max_points-1)/max_points;
892995
n_batch_points = (n+n_batches-1)/n_batches;
893-
for(i = 0; i < n_batches; i++) {
894-
size_t nbp = n < n_batch_points ? n : n_batch_points;
895-
size_t offset = n_batch_points*i;
896-
secp256k1_gej tmp;
897-
if(!secp256k1_ecmult_pippenger_batch(ctx, scratch, &tmp, i == 0 ? inp_g_sc : NULL, cb, cbdata, nbp, offset)) {
898-
return 0;
899-
}
900-
secp256k1_gej_add_var(r, r, &tmp, NULL);
901-
n -= nbp;
996+
f = secp256k1_ecmult_strauss_batch;
997+
}
998+
for(i = 0; i < n_batches; i++) {
999+
size_t nbp = n < n_batch_points ? n : n_batch_points;
1000+
size_t offset = n_batch_points*i;
1001+
secp256k1_gej tmp;
1002+
if (!f(ctx, scratch, &tmp, i == 0 ? inp_g_sc : NULL, cb, cbdata, nbp, offset)) {
1003+
return 0;
9021004
}
1005+
secp256k1_gej_add_var(r, r, &tmp, NULL);
1006+
n -= nbp;
9031007
}
904-
9051008
return 1;
9061009
}
9071010

src/tests.c

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2783,8 +2783,56 @@ void test_ecmult_multi(secp256k1_scratch *scratch, secp256k1_ecmult_multi_func e
27832783
}
27842784
}
27852785

2786+
void test_secp256k1_pippenger_bucket_window_inv(void) {
2787+
int i;
2788+
2789+
CHECK(secp256k1_pippenger_bucket_window_inv(0) == 0);
2790+
for(i = 1; i <= PIPPENGER_MAX_BUCKET_WINDOW; i++) {
2791+
#ifdef USE_ENDOMORPHISM
2792+
/* Bucket_window of 8 is not used with endo */
2793+
if (i == 8) {
2794+
continue;
2795+
}
2796+
#endif
2797+
CHECK(secp256k1_pippenger_bucket_window(secp256k1_pippenger_bucket_window_inv(i)) == i);
2798+
if (i != PIPPENGER_MAX_BUCKET_WINDOW) {
2799+
CHECK(secp256k1_pippenger_bucket_window(secp256k1_pippenger_bucket_window_inv(i)+1) > i);
2800+
}
2801+
}
2802+
}
2803+
2804+
/**
2805+
* Probabilistically test the function returning the maximum number of possible points
2806+
* for a given scratch space.
2807+
*/
2808+
void test_ecmult_multi_pippenger_max_points(void) {
2809+
size_t scratch_size = secp256k1_rand_int(256);
2810+
size_t max_size = secp256k1_pippenger_scratch_size(secp256k1_pippenger_bucket_window_inv(PIPPENGER_MAX_BUCKET_WINDOW-1)+512, 12);
2811+
secp256k1_scratch *scratch;
2812+
size_t n_points_supported;
2813+
int bucket_window = 0;
2814+
2815+
for(; scratch_size < max_size; scratch_size+=256) {
2816+
scratch = secp256k1_scratch_create(&ctx->error_callback, 0, scratch_size);
2817+
CHECK(scratch != NULL);
2818+
n_points_supported = secp256k1_pippenger_max_points(scratch);
2819+
if (n_points_supported == 0) {
2820+
secp256k1_scratch_destroy(scratch);
2821+
continue;
2822+
}
2823+
bucket_window = secp256k1_pippenger_bucket_window(n_points_supported);
2824+
CHECK(secp256k1_scratch_resize(scratch, secp256k1_pippenger_scratch_size(n_points_supported, bucket_window), PIPPENGER_SCRATCH_OBJECTS));
2825+
secp256k1_scratch_destroy(scratch);
2826+
}
2827+
CHECK(bucket_window == PIPPENGER_MAX_BUCKET_WINDOW);
2828+
}
2829+
2830+
/**
2831+
* Run secp256k1_ecmult_multi_var with num points and a scratch space restricted to
2832+
* 1 <= i <= num points.
2833+
*/
27862834
void test_ecmult_multi_batching(void) {
2787-
static const int n_points = 3*MAX_BATCH_SIZE;
2835+
static const int n_points = 2*ECMULT_PIPPENGER_THRESHOLD;
27882836
secp256k1_scalar scG;
27892837
secp256k1_scalar szero;
27902838
secp256k1_scalar *sc = (secp256k1_scalar *)checked_malloc(&ctx->error_callback, sizeof(secp256k1_scalar) * n_points);
@@ -2795,18 +2843,21 @@ void test_ecmult_multi_batching(void) {
27952843
int i;
27962844
secp256k1_scratch *scratch;
27972845

2798-
int test_n_points[] = { MAX_BATCH_SIZE, MAX_BATCH_SIZE + 1, MAX_BATCH_SIZE + 2, 2*MAX_BATCH_SIZE, 2*MAX_BATCH_SIZE+1, 3*MAX_BATCH_SIZE };
27992846
secp256k1_gej_set_infinity(&r2);
28002847
secp256k1_scalar_set_int(&szero, 0);
28012848

2802-
/* Get random scalars and group elements */
2849+
/* Get random scalars and group elements and compute result */
28032850
random_scalar_order(&scG);
28042851
secp256k1_ecmult(&ctx->ecmult_ctx, &r2, &r2, &szero, &scG);
28052852
for(i = 0; i < n_points; i++) {
28062853
secp256k1_ge ptg;
2854+
secp256k1_gej ptgj;
28072855
random_group_element_test(&ptg);
2856+
secp256k1_gej_set_ge(&ptgj, &ptg);
28082857
pt[i] = ptg;
28092858
random_scalar_order(&sc[i]);
2859+
secp256k1_ecmult(&ctx->ecmult_ctx, &ptgj, &ptgj, &sc[i], NULL);
2860+
secp256k1_gej_add_var(&r2, &r2, &ptgj, NULL);
28102861
}
28112862
data.sc = sc;
28122863
data.pt = pt;
@@ -2822,10 +2873,8 @@ void test_ecmult_multi_batching(void) {
28222873
CHECK(!secp256k1_ecmult_multi_var(&ctx->ecmult_ctx, scratch, &r, &scG, ecmult_multi_callback, &data, 1));
28232874
secp256k1_scratch_destroy(scratch);
28242875

2825-
/* Run secp256k1_ecmult_multi_var with i points and a scratch space
2826-
* restricted to i points. */
2827-
for(i = 1; i <= ECMULT_PIPPENGER_THRESHOLD+2; i++) {
2828-
secp256k1_gej ptgj;
2876+
secp256k1_gej_neg(&r2, &r2);
2877+
for(i = 1; i <= n_points; i++) {
28292878
if (i > ECMULT_PIPPENGER_THRESHOLD) {
28302879
int bucket_window = secp256k1_pippenger_bucket_window(i);
28312880
size_t scratch_size = secp256k1_pippenger_scratch_size(i, bucket_window);
@@ -2834,48 +2883,29 @@ void test_ecmult_multi_batching(void) {
28342883
size_t scratch_size = secp256k1_strauss_scratch_size(i);
28352884
scratch = secp256k1_scratch_create(&ctx->error_callback, 0, scratch_size + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT);
28362885
}
2837-
CHECK(secp256k1_ecmult_multi_var(&ctx->ecmult_ctx, scratch, &r, &scG, ecmult_multi_callback, &data, i));
2838-
2839-
/* compute running result */
2840-
secp256k1_gej_set_ge(&ptgj, &pt[i-1]);
2841-
secp256k1_ecmult(&ctx->ecmult_ctx, &ptgj, &ptgj, &sc[i-1], NULL);
2842-
secp256k1_gej_add_var(&r2, &r2, &ptgj, NULL);
2843-
2844-
secp256k1_gej_neg(&r, &r);
2886+
CHECK(secp256k1_ecmult_multi_var(&ctx->ecmult_ctx, scratch, &r, &scG, ecmult_multi_callback, &data, n_points));
28452887
secp256k1_gej_add_var(&r, &r, &r2, NULL);
28462888
CHECK(secp256k1_gej_is_infinity(&r));
28472889
secp256k1_scratch_destroy(scratch);
28482890
}
2849-
2850-
scratch = secp256k1_scratch_create(&ctx->error_callback, 0, secp256k1_strauss_scratch_size(n_points) + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT);
2851-
2852-
for(i = 0; i < (int)(sizeof(test_n_points) / sizeof(test_n_points[0])); i++) {
2853-
secp256k1_gej ptgj;
2854-
CHECK(secp256k1_ecmult_multi_var(&ctx->ecmult_ctx, scratch, &r, &scG, ecmult_multi_callback, &data, test_n_points[i]-1));
2855-
secp256k1_gej_set_infinity(&r2);
2856-
secp256k1_gej_add_var(&r2, &r2, &r, NULL);
2857-
CHECK(secp256k1_ecmult_multi_var(&ctx->ecmult_ctx, scratch, &r, &scG, ecmult_multi_callback, &data, test_n_points[i]));
2858-
secp256k1_gej_set_ge(&ptgj, &pt[test_n_points[i]-1]);
2859-
secp256k1_ecmult(&ctx->ecmult_ctx, &ptgj, &ptgj, &sc[test_n_points[i]-1], NULL);
2860-
secp256k1_gej_add_var(&r2, &r2, &ptgj, NULL);
2861-
2862-
secp256k1_gej_neg(&r, &r);
2863-
secp256k1_gej_add_var(&r, &r, &r2, NULL);
2864-
CHECK(secp256k1_gej_is_infinity(&r));
2865-
}
2866-
2867-
secp256k1_scratch_destroy(scratch);
28682891
free(sc);
28692892
free(pt);
28702893
}
28712894

28722895
void run_ecmult_multi_tests(void) {
28732896
secp256k1_scratch *scratch;
28742897

2898+
test_secp256k1_pippenger_bucket_window_inv();
2899+
test_ecmult_multi_pippenger_max_points();
28752900
scratch = secp256k1_scratch_create(&ctx->error_callback, 0, 819200);
2876-
test_ecmult_multi(scratch, &secp256k1_ecmult_multi_var);
2877-
test_ecmult_multi(scratch, &secp256k1_ecmult_pippenger_batch_single);
2878-
test_ecmult_multi(scratch, &secp256k1_ecmult_strauss_batch_single);
2901+
test_ecmult_multi(scratch, secp256k1_ecmult_multi_var);
2902+
test_ecmult_multi(scratch, secp256k1_ecmult_pippenger_batch_single);
2903+
test_ecmult_multi(scratch, secp256k1_ecmult_strauss_batch_single);
2904+
secp256k1_scratch_destroy(scratch);
2905+
2906+
/* Run test_ecmult_multi with space for exactly one point */
2907+
scratch = secp256k1_scratch_create(&ctx->error_callback, 0, secp256k1_strauss_scratch_size(1) + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT);
2908+
test_ecmult_multi(scratch, secp256k1_ecmult_multi_var);
28792909
secp256k1_scratch_destroy(scratch);
28802910

28812911
test_ecmult_multi_batching();

0 commit comments

Comments
 (0)