Skip to content

Commit 24553bf

Browse files
committed
Make pippenger_scratch_size and pippenger_max_points more precise by taking correct
alignment padding into account instead of assuming the maximum padding. This also allows to precisely test pippenger_scratch_size against what is actually allocated.
1 parent 83f5f3d commit 24553bf

File tree

2 files changed

+81
-35
lines changed

2 files changed

+81
-35
lines changed

src/ecmult_impl.h

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -982,18 +982,51 @@ SECP256K1_INLINE static void secp256k1_ecmult_endo_split(secp256k1_scalar *s1, s
982982
}
983983
#endif
984984

985+
986+
static size_t secp256k1_pippenger_scratch_size_constant(int bucket_window) {
987+
size_t size = 0;
988+
size += ROUND_TO_ALIGN(sizeof(struct secp256k1_pippenger_state));
989+
size += ROUND_TO_ALIGN(sizeof(secp256k1_gej) << bucket_window);
990+
return size;
991+
}
992+
993+
static SECP256K1_INLINE size_t secp256k1_pippenger_entries(size_t n_points) {
994+
#ifdef USE_ENDOMORPHISM
995+
return 2*n_points + 2;
996+
#else
997+
return n_points + 1;
998+
#endif
999+
}
1000+
1001+
/**
1002+
* Returns the scratch size required for a given number of points excluding
1003+
* base point G and excluding the parts not dependent on the number of points.
1004+
* If called with 0 n_points it'll return the size required for the base point
1005+
* G. If the aligned argument is 0, this function will not take alignment into
1006+
* account. Otherwise it will align the individual parts in the same way as
1007+
* secp256k1_scratch_alloc.
1008+
*/
1009+
static size_t secp256k1_pippenger_scratch_size_points(size_t n_points, int bucket_window, int aligned) {
1010+
size_t entries = secp256k1_pippenger_entries(n_points);
1011+
size_t size = 0;
1012+
size += entries * sizeof(secp256k1_ge);
1013+
size = aligned ? ROUND_TO_ALIGN(size) : size;
1014+
size += entries * sizeof(secp256k1_scalar);
1015+
size = aligned ? ROUND_TO_ALIGN(size) : size;
1016+
size += entries * sizeof(struct secp256k1_pippenger_point_state);
1017+
size = aligned ? ROUND_TO_ALIGN(size) : size;
1018+
size += entries * WNAF_SIZE(bucket_window+1) * sizeof(int);
1019+
size = aligned ? ROUND_TO_ALIGN(size) : size;
1020+
return size;
1021+
}
1022+
9851023
/**
9861024
* Returns the scratch size required for a given number of points (excluding
9871025
* base point G) without considering alignment.
9881026
*/
9891027
static size_t secp256k1_pippenger_scratch_size(size_t n_points, int bucket_window) {
990-
#ifdef USE_ENDOMORPHISM
991-
size_t entries = 2*n_points + 2;
992-
#else
993-
size_t entries = n_points + 1;
994-
#endif
995-
size_t entry_size = sizeof(secp256k1_ge) + sizeof(secp256k1_scalar) + sizeof(struct secp256k1_pippenger_point_state) + (WNAF_SIZE(bucket_window+1)+1)*sizeof(int);
996-
return (sizeof(secp256k1_gej) << bucket_window) + sizeof(struct secp256k1_pippenger_state) + entries * entry_size;
1028+
return secp256k1_pippenger_scratch_size_constant(bucket_window)
1029+
+ secp256k1_pippenger_scratch_size_points(n_points, bucket_window, 1);
9971030
}
9981031

9991032
static int secp256k1_ecmult_pippenger_batch_allocate(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, size_t scratch_checkpoint, size_t entries, int bucket_window, secp256k1_ge **points, secp256k1_scalar **scalars, secp256k1_gej **buckets, struct secp256k1_pippenger_state **state_space) {
@@ -1019,11 +1052,7 @@ static int secp256k1_ecmult_pippenger_batch(const secp256k1_callback* error_call
10191052
/* Use 2(n+1) with the endomorphism, n+1 without, when calculating batch
10201053
* sizes. The reason for +1 is that we add the G scalar to the list of
10211054
* other scalars. */
1022-
#ifdef USE_ENDOMORPHISM
1023-
size_t entries = 2*n_points + 2;
1024-
#else
1025-
size_t entries = n_points + 1;
1026-
#endif
1055+
size_t entries = secp256k1_pippenger_entries(n_points);
10271056
secp256k1_ge *points;
10281057
secp256k1_scalar *scalars;
10291058
secp256k1_gej *buckets;
@@ -1095,27 +1124,39 @@ static int secp256k1_ecmult_pippenger_batch_single(const secp256k1_callback* err
10951124
* used.
10961125
*/
10971126
static size_t secp256k1_pippenger_max_points(const secp256k1_callback* error_callback, secp256k1_scratch *scratch) {
1098-
size_t max_alloc = secp256k1_scratch_max_allocation(error_callback, scratch, PIPPENGER_SCRATCH_OBJECTS);
1127+
/* Call max_allocation with 0 objects because it would assume worst case
1128+
* padding but in this function we want to get an exact number (and
1129+
* pippenger_scratch_size_points will already account for alignment). */
1130+
size_t max_alloc = secp256k1_scratch_max_allocation(error_callback, scratch, 0);
10991131
int bucket_window;
11001132
size_t res = 0;
11011133

11021134
for (bucket_window = 1; bucket_window <= PIPPENGER_MAX_BUCKET_WINDOW; bucket_window++) {
11031135
size_t n_points;
11041136
size_t max_points = secp256k1_pippenger_bucket_window_inv(bucket_window);
11051137
size_t space_for_points;
1106-
size_t space_overhead;
1107-
size_t entry_size = sizeof(secp256k1_ge) + sizeof(secp256k1_scalar) + sizeof(struct secp256k1_pippenger_point_state) + (WNAF_SIZE(bucket_window+1)+1)*sizeof(int);
1138+
size_t space_constant;
1139+
size_t entry_size = secp256k1_pippenger_scratch_size_points(0, bucket_window, 0);
11081140

1109-
#ifdef USE_ENDOMORPHISM
1110-
entry_size = 2*entry_size;
1111-
#endif
1112-
space_overhead = (sizeof(secp256k1_gej) << bucket_window) + entry_size + sizeof(struct secp256k1_pippenger_state);
1113-
if (space_overhead > max_alloc) {
1141+
space_constant = secp256k1_pippenger_scratch_size_constant(bucket_window);
1142+
if (space_constant + entry_size > max_alloc) {
11141143
break;
11151144
}
1116-
space_for_points = max_alloc - space_overhead;
1145+
space_for_points = max_alloc - space_constant;
1146+
1147+
/* Compute an upper bound for the number of points after subtracting
1148+
* space for the base point G. It's an upper bound because alignment is
1149+
* not taken into account. */
1150+
n_points = (space_for_points - entry_size)/entry_size;
1151+
if (n_points > 0
1152+
&& space_for_points < secp256k1_pippenger_scratch_size_points(n_points, bucket_window, 1)) {
1153+
/* If there's not enough space after alignment is taken into
1154+
* account it suffices to decrease n_points by one. This is because
1155+
* the maximum padding required is less than an entry. */
1156+
n_points -= 1;
1157+
VERIFY_CHECK(space_for_points >= secp256k1_pippenger_scratch_size_points(n_points, bucket_window, 1));
1158+
}
11171159

1118-
n_points = space_for_points/entry_size;
11191160
n_points = n_points > max_points ? max_points : n_points;
11201161
if (n_points > res) {
11211162
res = n_points;

src/tests.c

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2961,18 +2961,20 @@ void test_secp256k1_pippenger_bucket_window_inv(void) {
29612961

29622962
/**
29632963
* Probabilistically test the function returning the maximum number of possible points
2964-
* for a given scratch space.
2964+
* for a given scratch space. This works by trying various different scratch sizes and
2965+
* checking that the resulting max_points actually fit into the scratch space.
29652966
*/
29662967
void test_ecmult_multi_pippenger_max_points(void) {
29672968
size_t scratch_size = secp256k1_rand_int(256);
2968-
size_t max_size = secp256k1_pippenger_scratch_size(secp256k1_pippenger_bucket_window_inv(PIPPENGER_MAX_BUCKET_WINDOW-1)+512, 12);
2969+
/* Pick a max scratch size that permits using enough points to require the
2970+
* maximum bucket window */
2971+
size_t max_points = secp256k1_pippenger_bucket_window_inv(PIPPENGER_MAX_BUCKET_WINDOW-1)+512;
2972+
size_t max_size = secp256k1_pippenger_scratch_size(max_points, PIPPENGER_MAX_BUCKET_WINDOW);
29692973
secp256k1_scratch *scratch;
29702974
size_t n_points_supported;
29712975
int bucket_window = 0;
29722976

2973-
for(; scratch_size < max_size; scratch_size+=256) {
2974-
size_t i;
2975-
size_t total_alloc;
2977+
for(; scratch_size < max_size; scratch_size += 32) {
29762978
size_t checkpoint;
29772979
scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size);
29782980
CHECK(scratch != NULL);
@@ -2983,13 +2985,16 @@ void test_ecmult_multi_pippenger_max_points(void) {
29832985
continue;
29842986
}
29852987
bucket_window = secp256k1_pippenger_bucket_window(n_points_supported);
2986-
/* allocate `total_alloc` bytes over `PIPPENGER_SCRATCH_OBJECTS` many allocations */
2987-
total_alloc = secp256k1_pippenger_scratch_size(n_points_supported, bucket_window);
2988-
for (i = 0; i < PIPPENGER_SCRATCH_OBJECTS - 1; i++) {
2989-
CHECK(secp256k1_scratch_alloc(&ctx->error_callback, scratch, 1));
2990-
total_alloc--;
2988+
{
2989+
/* Check that pippenger_scratch_size matches what's actually allocated */
2990+
secp256k1_ge *points;
2991+
secp256k1_scalar *scalars;
2992+
secp256k1_gej *buckets;
2993+
struct secp256k1_pippenger_state *state_space;
2994+
size_t entries = secp256k1_pippenger_entries(n_points_supported);
2995+
CHECK(secp256k1_ecmult_pippenger_batch_allocate(&ctx->error_callback, scratch, 0, entries, bucket_window, &points, &scalars, &buckets, &state_space));
2996+
CHECK(scratch->alloc_size == secp256k1_pippenger_scratch_size(n_points_supported, bucket_window));
29912997
}
2992-
CHECK(secp256k1_scratch_alloc(&ctx->error_callback, scratch, total_alloc));
29932998
secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, checkpoint);
29942999
secp256k1_scratch_destroy(&ctx->error_callback, scratch);
29953000
}
@@ -3087,7 +3092,7 @@ void test_ecmult_multi_batching(void) {
30873092
/* Test with space for 1 point in pippenger. That's not enough because
30883093
* ecmult_multi selects strauss which requires more memory. It should
30893094
* therefore select the simple algorithm. */
3090-
scratch = secp256k1_scratch_create(&ctx->error_callback, secp256k1_pippenger_scratch_size(1, 1) + PIPPENGER_SCRATCH_OBJECTS*ALIGNMENT);
3095+
scratch = secp256k1_scratch_create(&ctx->error_callback, secp256k1_pippenger_scratch_size(1, 1));
30913096
CHECK(secp256k1_ecmult_multi_var(&ctx->error_callback, &ctx->ecmult_ctx, scratch, &r, &scG, ecmult_multi_callback, &data, n_points));
30923097
secp256k1_gej_add_var(&r, &r, &r2, NULL);
30933098
CHECK(secp256k1_gej_is_infinity(&r));
@@ -3097,7 +3102,7 @@ void test_ecmult_multi_batching(void) {
30973102
if (i >= ECMULT_PIPPENGER_THRESHOLD) {
30983103
int bucket_window = secp256k1_pippenger_bucket_window(i);
30993104
size_t scratch_size = secp256k1_pippenger_scratch_size(i, bucket_window);
3100-
scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size + PIPPENGER_SCRATCH_OBJECTS*ALIGNMENT);
3105+
scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size);
31013106
} else {
31023107
size_t scratch_size = secp256k1_strauss_scratch_size(i);
31033108
scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size);

0 commit comments

Comments
 (0)