diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c index 4030e0263f..6e838ed5f4 100644 --- a/src/bench_ecmult.c +++ b/src/bench_ecmult.c @@ -309,7 +309,7 @@ int main(int argc, char **argv) { } data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*16; + scratch_size = secp256k1_strauss_scratch_size(POINTS); if (!have_flag(argc, argv, "simple")) { data.scratch = secp256k1_scratch_space_create(data.ctx, scratch_size); } else { diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h index bbc820c77c..a105680bab 100644 --- a/src/ecmult_impl.h +++ b/src/ecmult_impl.h @@ -347,9 +347,61 @@ static void secp256k1_ecmult(secp256k1_gej *r, const secp256k1_gej *a, const sec secp256k1_ecmult_strauss_wnaf(&state, r, 1, a, na, ng); } +static size_t secp256k1_ecmult_sum_array(size_t *array, size_t n) { + size_t i = 0; + size_t sum = 0; + for (i = 0; i < n; i++) { + sum += array[i]; + } + return sum; +} + +/** + * If the as_allocated argument is 0, this function will return the sum of the + * sizes of the individual parts. Otherwise it will return the size that is + * actually allocated with secp256k1_scratch_alloc which is greater or equal. + */ +static size_t secp256k1_strauss_scratch_size_raw(size_t n_points, int as_allocated) { + size_t sizes[STRAUSS_SCRATCH_OBJECTS]; + sizes[0] = n_points * sizeof(secp256k1_gej); + sizes[1] = n_points * sizeof(secp256k1_scalar); + sizes[2] = n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe); + sizes[3] = n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge); + sizes[4] = n_points * sizeof(struct secp256k1_strauss_point_state); + + if (as_allocated) { + size_t size; + int ret = secp256k1_scratch_alloc_size(&size, sizes, STRAUSS_SCRATCH_OBJECTS); + /* The n_points argument is not greater than + * ECMULT_MAX_POINTS_PER_BATCH. As long as it is not too large, + * scratch_alloc_size does not fail. */ + VERIFY_CHECK(ECMULT_MAX_POINTS_PER_BATCH == 5000000 && ret); + return size; + } else { + return secp256k1_ecmult_sum_array(sizes, STRAUSS_SCRATCH_OBJECTS); + } +} + +/* Returns the scratch size required for a given number of points (excluding + * base point G) as it would be allocated on a scratch space. */ static size_t secp256k1_strauss_scratch_size(size_t n_points) { - static const size_t point_size = (sizeof(secp256k1_ge) + sizeof(secp256k1_fe)) * ECMULT_TABLE_SIZE(WINDOW_A) + sizeof(struct secp256k1_strauss_point_state) + sizeof(secp256k1_gej) + sizeof(secp256k1_scalar); - return n_points*point_size; + return secp256k1_strauss_scratch_size_raw(n_points, 1); +} + +static int secp256k1_ecmult_strauss_batch_allocate(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, size_t n_points, secp256k1_gej **points, secp256k1_scalar **scalars, struct secp256k1_strauss_state *state) { + /* We allocate STRAUSS_SCRATCH_OBJECTS objects on the scratch space. If these + * allocations change, make sure to update the STRAUSS_SCRATCH_OBJECTS + * constant and strauss_scratch_size accordingly. */ + *points = (secp256k1_gej*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_gej)); + *scalars = (secp256k1_scalar*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_scalar)); + state->aux = (secp256k1_fe*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe)); + state->pre_a = (secp256k1_ge*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge)); + state->ps = (struct secp256k1_strauss_point_state*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(struct secp256k1_strauss_point_state)); + return points != NULL + && scalars != NULL + && state->aux != NULL + && state->pre_a != NULL + && state->ps != NULL; } static int secp256k1_ecmult_strauss_batch(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n_points, size_t cb_offset) { @@ -363,17 +415,7 @@ static int secp256k1_ecmult_strauss_batch(const secp256k1_callback* error_callba if (inp_g_sc == NULL && n_points == 0) { return 1; } - - /* We allocate STRAUSS_SCRATCH_OBJECTS objects on the scratch space. If these - * allocations change, make sure to update the STRAUSS_SCRATCH_OBJECTS - * constant and strauss_scratch_size accordingly. */ - points = (secp256k1_gej*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_gej)); - scalars = (secp256k1_scalar*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(secp256k1_scalar)); - state.aux = (secp256k1_fe*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_fe)); - state.pre_a = (secp256k1_ge*)secp256k1_scratch_alloc(error_callback, scratch, n_points * ECMULT_TABLE_SIZE(WINDOW_A) * sizeof(secp256k1_ge)); - state.ps = (struct secp256k1_strauss_point_state*)secp256k1_scratch_alloc(error_callback, scratch, n_points * sizeof(struct secp256k1_strauss_point_state)); - - if (points == NULL || scalars == NULL || state.aux == NULL || state.pre_a == NULL || state.ps == NULL) { + if (!secp256k1_ecmult_strauss_batch_allocate(error_callback, scratch, n_points, &points, &scalars, &state)) { secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint); return 0; } @@ -396,8 +438,27 @@ static int secp256k1_ecmult_strauss_batch_single(const secp256k1_callback* error return secp256k1_ecmult_strauss_batch(error_callback, scratch, r, inp_g_sc, cb, cbdata, n, 0); } +/* Returns the maximum number of points in addition to G that can be used with a + * given scratch space. If a scratch space has exactly + * `strauss_scratch_size(n_points)` left, then + * `strauss_max_points(cb, scratch) = n_points`. */ static size_t secp256k1_strauss_max_points(const secp256k1_callback* error_callback, secp256k1_scratch *scratch) { - return secp256k1_scratch_max_allocation(error_callback, scratch, STRAUSS_SCRATCH_OBJECTS) / secp256k1_strauss_scratch_size(1); + /* Call max_allocation with 0 objects because otherwise it would assume + * worst case padding but in this function we want to be exact. */ + size_t max_alloc = secp256k1_scratch_max_allocation(error_callback, scratch, 0); + size_t unpadded_single_size = secp256k1_strauss_scratch_size_raw(1, 0); + size_t n_points = max_alloc / unpadded_single_size; + if (n_points > 0 + && max_alloc < secp256k1_strauss_scratch_size(n_points)) { + /* If there's not enough space after alignment is taken into + * account, it suffices to decrease n_points by one. This is because + * the maximum padding required is less than an entry. */ + n_points -= 1; + VERIFY_CHECK(max_alloc >= secp256k1_strauss_scratch_size(n_points)); + VERIFY_CHECK(max_alloc - secp256k1_scratch_max_allocation(error_callback, scratch, STRAUSS_SCRATCH_OBJECTS) < unpadded_single_size); + } + + return n_points; } /** Convert a number to WNAF notation. @@ -630,14 +691,82 @@ SECP256K1_INLINE static void secp256k1_ecmult_endo_split(secp256k1_scalar *s1, s } } +static size_t secp256k1_pippenger_scratch_size_constant(int bucket_window) { + /* 4 objects are accounted for in pippenger_scratch_size_points */ + enum { N_SIZES = PIPPENGER_SCRATCH_OBJECTS - 4 }; + int ret; + size_t size; + size_t sizes[N_SIZES]; + sizes[0] = sizeof(struct secp256k1_pippenger_state); + sizes[1] = sizeof(secp256k1_gej) << bucket_window; + + ret = secp256k1_scratch_alloc_size(&size, sizes, N_SIZES); + /* The inputs to scratch_alloc_size are constant */ + VERIFY_CHECK(ret); + return size; +} + +static SECP256K1_INLINE size_t secp256k1_pippenger_entries(size_t n_points) { + return 2*n_points + 2; +} + +/** + * Returns the scratch size required for a given number of points excluding + * base point G and excluding the parts not dependent on the number of points. + * If called with 0 n_points it'll return the size required for the base point + * G. If the as_allocated argument is 0, this function will return the sum of + * the sizes of the individual parts. Otherwise it will return the size that is + * actually allocated with secp256k1_scratch_alloc which is greater or equal. + */ +static size_t secp256k1_pippenger_scratch_size_points(size_t n_points, int bucket_window, int as_allocated) { + size_t entries = secp256k1_pippenger_entries(n_points); + /* 2 objects are accounted for in pippenger_scratch_size_constant */ + enum { N_SIZES = PIPPENGER_SCRATCH_OBJECTS - 2 }; + size_t sizes[N_SIZES]; + sizes[0] = entries * sizeof(secp256k1_ge); + sizes[1] = entries * sizeof(secp256k1_scalar); + sizes[2] = entries * sizeof(struct secp256k1_pippenger_point_state); + sizes[3] = entries * WNAF_SIZE(bucket_window+1) * sizeof(int); + if (as_allocated) { + size_t size; + int ret = secp256k1_scratch_alloc_size(&size, sizes, N_SIZES); + /* The n_points argument is not greater than + * ECMULT_MAX_POINTS_PER_BATCH. As long as it is not too large, + * scratch_alloc_size does not fail. */ + VERIFY_CHECK(ECMULT_MAX_POINTS_PER_BATCH == 5000000 && ret); + return size; + } else { + return secp256k1_ecmult_sum_array(sizes, N_SIZES); + } +} + /** * Returns the scratch size required for a given number of points (excluding - * base point G) without considering alignment. + * base point G) as it would be allocated on a scratch space. */ static size_t secp256k1_pippenger_scratch_size(size_t n_points, int bucket_window) { - size_t entries = 2*n_points + 2; - size_t entry_size = sizeof(secp256k1_ge) + sizeof(secp256k1_scalar) + sizeof(struct secp256k1_pippenger_point_state) + (WNAF_SIZE(bucket_window+1)+1)*sizeof(int); - return (sizeof(secp256k1_gej) << bucket_window) + sizeof(struct secp256k1_pippenger_state) + entries * entry_size; + return secp256k1_pippenger_scratch_size_constant(bucket_window) + + secp256k1_pippenger_scratch_size_points(n_points, bucket_window, 1); +} + +static int secp256k1_ecmult_pippenger_batch_allocate(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, size_t entries, int bucket_window, secp256k1_ge **points, secp256k1_scalar **scalars, secp256k1_gej **buckets, struct secp256k1_pippenger_state **state_space) { + /* We allocate PIPPENGER_SCRATCH_OBJECTS objects on the scratch space. If + * these allocations change, make sure to update the + * PIPPENGER_SCRATCH_OBJECTS constant and pippenger_scratch_size + * accordingly. */ + *points = (secp256k1_ge *) secp256k1_scratch_alloc(error_callback, scratch, entries * sizeof(secp256k1_ge)); + *scalars = (secp256k1_scalar *) secp256k1_scratch_alloc(error_callback, scratch, entries * sizeof(secp256k1_scalar)); + *state_space = (struct secp256k1_pippenger_state *) secp256k1_scratch_alloc(error_callback, scratch, sizeof(struct secp256k1_pippenger_state)); + if (*points == NULL || *scalars == NULL || *state_space == NULL) { + return 0; + } + (*state_space)->ps = (struct secp256k1_pippenger_point_state *) secp256k1_scratch_alloc(error_callback, scratch, entries * sizeof(struct secp256k1_pippenger_point_state)); + (*state_space)->wnaf_na = (int *) secp256k1_scratch_alloc(error_callback, scratch, entries * WNAF_SIZE(bucket_window+1) * sizeof(int)); + *buckets = (secp256k1_gej *) secp256k1_scratch_alloc(error_callback, scratch, sizeof(secp256k1_gej) << bucket_window); + if ((*state_space)->ps == NULL || (*state_space)->wnaf_na == NULL || *buckets == NULL) { + return 0; + } + return 1; } static int secp256k1_ecmult_pippenger_batch(const secp256k1_callback* error_callback, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n_points, size_t cb_offset) { @@ -645,7 +774,7 @@ static int secp256k1_ecmult_pippenger_batch(const secp256k1_callback* error_call /* Use 2(n+1) with the endomorphism, when calculating batch * sizes. The reason for +1 is that we add the G scalar to the list of * other scalars. */ - size_t entries = 2*n_points + 2; + size_t entries = secp256k1_pippenger_entries(n_points); secp256k1_ge *points; secp256k1_scalar *scalars; secp256k1_gej *buckets; @@ -660,22 +789,7 @@ static int secp256k1_ecmult_pippenger_batch(const secp256k1_callback* error_call return 1; } bucket_window = secp256k1_pippenger_bucket_window(n_points); - - /* We allocate PIPPENGER_SCRATCH_OBJECTS objects on the scratch space. If - * these allocations change, make sure to update the - * PIPPENGER_SCRATCH_OBJECTS constant and pippenger_scratch_size - * accordingly. */ - points = (secp256k1_ge *) secp256k1_scratch_alloc(error_callback, scratch, entries * sizeof(*points)); - scalars = (secp256k1_scalar *) secp256k1_scratch_alloc(error_callback, scratch, entries * sizeof(*scalars)); - state_space = (struct secp256k1_pippenger_state *) secp256k1_scratch_alloc(error_callback, scratch, sizeof(*state_space)); - if (points == NULL || scalars == NULL || state_space == NULL) { - secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint); - return 0; - } - state_space->ps = (struct secp256k1_pippenger_point_state *) secp256k1_scratch_alloc(error_callback, scratch, entries * sizeof(*state_space->ps)); - state_space->wnaf_na = (int *) secp256k1_scratch_alloc(error_callback, scratch, entries*(WNAF_SIZE(bucket_window+1)) * sizeof(int)); - buckets = (secp256k1_gej *) secp256k1_scratch_alloc(error_callback, scratch, (1<ps == NULL || state_space->wnaf_na == NULL || buckets == NULL) { + if (!secp256k1_ecmult_pippenger_batch_allocate(error_callback, scratch, entries, bucket_window, &points, &scalars, &buckets, &state_space)) { secp256k1_scratch_apply_checkpoint(error_callback, scratch, scratch_checkpoint); return 0; } @@ -721,13 +835,17 @@ static int secp256k1_ecmult_pippenger_batch_single(const secp256k1_callback* err return secp256k1_ecmult_pippenger_batch(error_callback, scratch, r, inp_g_sc, cb, cbdata, n, 0); } -/** - * Returns the maximum number of points in addition to G that can be used with - * a given scratch space. The function ensures that fewer points may also be - * used. +/* Returns the (near) maximum number of points in addition to G that can be + * used with a given scratch space. It may not return the actual maximum number + * of points possible. Otherwise, fewer points would not fit into the scratch + * space in general. If a scratch space has exactly + * `pippenger_scratch_size(n_points)` left, then + * `pippenger_max_points(cb, scratch) <= n_points`. */ static size_t secp256k1_pippenger_max_points(const secp256k1_callback* error_callback, secp256k1_scratch *scratch) { - size_t max_alloc = secp256k1_scratch_max_allocation(error_callback, scratch, PIPPENGER_SCRATCH_OBJECTS); + /* Call max_allocation with 0 objects because otherwise it would assume + * worst case padding but in this function we want to be exact. */ + size_t max_alloc = secp256k1_scratch_max_allocation(error_callback, scratch, 0); int bucket_window; size_t res = 0; @@ -735,17 +853,29 @@ static size_t secp256k1_pippenger_max_points(const secp256k1_callback* error_cal size_t n_points; size_t max_points = secp256k1_pippenger_bucket_window_inv(bucket_window); size_t space_for_points; - size_t space_overhead; - size_t entry_size = sizeof(secp256k1_ge) + sizeof(secp256k1_scalar) + sizeof(struct secp256k1_pippenger_point_state) + (WNAF_SIZE(bucket_window+1)+1)*sizeof(int); + size_t space_constant; + /* Compute entry size without taking alignment into account */ + size_t entry_size = secp256k1_pippenger_scratch_size_points(0, bucket_window, 0); - entry_size = 2*entry_size; - space_overhead = (sizeof(secp256k1_gej) << bucket_window) + entry_size + sizeof(struct secp256k1_pippenger_state); - if (space_overhead > max_alloc) { + space_constant = secp256k1_pippenger_scratch_size_constant(bucket_window); + if (space_constant + entry_size > max_alloc) { break; } - space_for_points = max_alloc - space_overhead; + space_for_points = max_alloc - space_constant; + + /* Compute an upper bound for the number excluding the base point G. + * It's an upper bound because alignment is not taken into account. */ + n_points = space_for_points / entry_size - 1; + if (n_points > 0 + && space_for_points < secp256k1_pippenger_scratch_size_points(n_points, bucket_window, 1)) { + /* If there's not enough space after alignment is taken into + * account, it suffices to decrease n_points by one. This is because + * the maximum padding required is less than an entry. */ + n_points -= 1; + VERIFY_CHECK(max_alloc - secp256k1_scratch_max_allocation(error_callback, scratch, PIPPENGER_SCRATCH_OBJECTS) < entry_size); + VERIFY_CHECK(space_for_points >= secp256k1_pippenger_scratch_size_points(n_points, bucket_window, 1)); + } - n_points = space_for_points/entry_size; n_points = n_points > max_points ? max_points : n_points; if (n_points > res) { res = n_points; @@ -856,4 +986,20 @@ static int secp256k1_ecmult_multi_var(const secp256k1_callback* error_callback, return 1; } +/** + * Returns the optimal scratch space size for a given number of points + * excluding base point G. + */ +static size_t secp256k1_ecmult_multi_scratch_size(size_t n_points) { + if (n_points > ECMULT_MAX_POINTS_PER_BATCH) { + n_points = ECMULT_MAX_POINTS_PER_BATCH; + } + if (n_points >= ECMULT_PIPPENGER_THRESHOLD) { + int bucket_window = secp256k1_pippenger_bucket_window(n_points); + return secp256k1_pippenger_scratch_size(n_points, bucket_window); + } else { + return secp256k1_strauss_scratch_size(n_points); + } +} + #endif /* SECP256K1_ECMULT_IMPL_H */ diff --git a/src/scratch.h b/src/scratch.h index 9dcb7581f6..db0be3c6aa 100644 --- a/src/scratch.h +++ b/src/scratch.h @@ -33,10 +33,17 @@ static size_t secp256k1_scratch_checkpoint(const secp256k1_callback* error_callb * undoing all allocations since that point. */ static void secp256k1_scratch_apply_checkpoint(const secp256k1_callback* error_callback, secp256k1_scratch* scratch, size_t checkpoint); -/** Returns the maximum allocation the scratch space will allow */ +/** Returns the maximum allocation the scratch space will allow. If you do not + * care about padding, you can call this function with zero `n_objects`. */ static size_t secp256k1_scratch_max_allocation(const secp256k1_callback* error_callback, const secp256k1_scratch* scratch, size_t n_objects); -/** Returns a pointer into the most recently allocated frame, or NULL if there is insufficient available space */ +/** Returns a pointer into the most recently allocated frame, or NULL if there is insufficient available space. */ static void *secp256k1_scratch_alloc(const secp256k1_callback* error_callback, secp256k1_scratch* scratch, size_t n); +/** Computes the amount that will be allocated if `scratch_alloc` is called + * `n_sizes` times, each time `i` with the `n` argument set to `sizes[i]`. If + * the function returns 1, the computed amount is stored in `alloc_size`. + * Otherwise, the function returns 0 (which happens if the size overflows). */ +static int secp256k1_scratch_alloc_size(size_t *alloc_size, size_t *sizes, size_t n_sizes); + #endif diff --git a/src/scratch_impl.h b/src/scratch_impl.h index 688e18eb66..fa507ec297 100644 --- a/src/scratch_impl.h +++ b/src/scratch_impl.h @@ -70,17 +70,21 @@ static size_t secp256k1_scratch_max_allocation(const secp256k1_callback* error_c return scratch->max_size - scratch->alloc_size - objects * (ALIGNMENT - 1); } +static int secp256k1_scratch_round_size(size_t *size) { + /* Check that rounding does not wrap around */ + if (ROUND_TO_ALIGN(*size) < *size) { + return 0; + } + *size = ROUND_TO_ALIGN(*size); + return 1; +} + static void *secp256k1_scratch_alloc(const secp256k1_callback* error_callback, secp256k1_scratch* scratch, size_t size) { void *ret; - size_t rounded_size; - rounded_size = ROUND_TO_ALIGN(size); - /* Check that rounding did not wrap around */ - if (rounded_size < size) { + if (!secp256k1_scratch_round_size(&size)) { return NULL; } - size = rounded_size; - if (secp256k1_memcmp_var(scratch->magic, "scratch", 8) != 0) { secp256k1_callback_call(error_callback, "invalid scratch space"); return NULL; @@ -96,4 +100,21 @@ static void *secp256k1_scratch_alloc(const secp256k1_callback* error_callback, s return ret; } +static int secp256k1_scratch_alloc_size(size_t *alloc_size, size_t *sizes, size_t n_sizes) { + size_t i; + + *alloc_size = 0; + for (i = 0; i < n_sizes; i++) { + size_t rounded_size = sizes[i]; + if (!secp256k1_scratch_round_size(&rounded_size)) { + return 0; + } + if (*alloc_size + rounded_size < *alloc_size) { + return 0; + } + *alloc_size += rounded_size; + } + return 1; +} + #endif diff --git a/src/tests.c b/src/tests.c index 8b3ac8140e..c98647c194 100644 --- a/src/tests.c +++ b/src/tests.c @@ -350,6 +350,7 @@ void run_context_tests(int use_prealloc) { void run_scratch_tests(void) { const size_t adj_alloc = ((500 + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT; + int i = 0; int32_t ecount = 0; size_t checkpoint; size_t checkpoint_2; @@ -402,6 +403,40 @@ void run_scratch_tests(void) { secp256k1_scratch_apply_checkpoint(&none->error_callback, scratch, (size_t) -1); /* this is just wildly invalid */ CHECK(ecount == 2); + /* test alloc_size */ + CHECK(scratch->alloc_size == 0); + for (i = 0; i < 100; i++) { + size_t sizes[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + static const size_t N_SIZES = sizeof(sizes)/sizeof(sizes[0]); + size_t j; + size_t alloc_size; + int ret; + + sizes[0] = i; + checkpoint = secp256k1_scratch_checkpoint(&none->error_callback, scratch); + for (j = 0; j < N_SIZES; j++) { + CHECK(secp256k1_scratch_alloc(&none->error_callback, scratch, sizes[j]) != NULL); + } + ret = secp256k1_scratch_alloc_size(&alloc_size, sizes, N_SIZES); + CHECK(ret); + CHECK(alloc_size == scratch->alloc_size); + secp256k1_scratch_apply_checkpoint(&none->error_callback, scratch, checkpoint); + } + /* Test alloc_size with overflowing values. Some overflows only happen if + ALIGNMENT is greater than 1. */ + { + size_t sizes[2] = { SIZE_MAX, SIZE_MAX }; + size_t alloc_size; + CHECK(ALIGNMENT <= 1 || !secp256k1_scratch_alloc_size(&alloc_size, sizes, 1)); + CHECK(ALIGNMENT <= 1 || !secp256k1_scratch_alloc_size(&alloc_size, sizes, 2)); + sizes[0] = 0; + CHECK(secp256k1_scratch_alloc_size(&alloc_size, sizes, 1)); + CHECK(ALIGNMENT <= 1 || !secp256k1_scratch_alloc_size(&alloc_size, sizes, 2)); + sizes[0] = SIZE_MAX/2+1; sizes[1] = SIZE_MAX/2+1; + CHECK(secp256k1_scratch_alloc_size(&alloc_size, sizes, 1)); + CHECK(!secp256k1_scratch_alloc_size(&alloc_size, sizes, 2)); + } + /* try to use badly initialized scratch space */ secp256k1_scratch_space_destroy(none, scratch); memset(&local_scratch, 0, sizeof(local_scratch)); @@ -4282,6 +4317,40 @@ void test_ecmult_multi_batch_single(secp256k1_ecmult_multi_func ecmult_multi) { secp256k1_scratch_destroy(&ctx->error_callback, scratch_empty); } +void test_ecmult_multi_strauss_scratch_size(void) { + size_t n_points; + + for(n_points = 0; n_points < ECMULT_PIPPENGER_THRESHOLD*2; n_points++) { + size_t scratch_size = secp256k1_strauss_scratch_size(n_points); + secp256k1_scratch *scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size); + CHECK(n_points == secp256k1_strauss_max_points(&ctx->error_callback, scratch)); + { + secp256k1_gej *points; + secp256k1_scalar *scalars; + struct secp256k1_strauss_state state; + size_t checkpoint = secp256k1_scratch_checkpoint(&ctx->error_callback, scratch); + CHECK(secp256k1_ecmult_strauss_batch_allocate(&ctx->error_callback, scratch, n_points, &points, &scalars, &state)); + CHECK(scratch->alloc_size == scratch_size); + secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, checkpoint); + } + secp256k1_scratch_destroy(&ctx->error_callback, scratch); + } +} + +/* Spot check that any scratch space is large enough to fit + * `strauss_max_points(scratch)` many points. */ +void test_ecmult_multi_strauss_max_points(void) { + size_t scratch_size = secp256k1_strauss_scratch_size_raw(1, 0); + size_t max_scratch_size = secp256k1_strauss_scratch_size_raw(1, 1) + 1; + for (; scratch_size < max_scratch_size; scratch_size++) { + secp256k1_scratch *scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size); + size_t n_points = secp256k1_strauss_max_points(&ctx->error_callback, scratch); + CHECK(secp256k1_scratch_max_allocation(&ctx->error_callback, scratch, 0) == scratch_size); + CHECK(scratch_size >= secp256k1_strauss_scratch_size(n_points)); + secp256k1_scratch_destroy(&ctx->error_callback, scratch); + } +} + void test_secp256k1_pippenger_bucket_window_inv(void) { int i; @@ -4300,18 +4369,20 @@ void test_secp256k1_pippenger_bucket_window_inv(void) { /** * Probabilistically test the function returning the maximum number of possible points - * for a given scratch space. + * for a given scratch space. This works by trying various different scratch sizes and + * checking that the resulting max_points actually fit into the scratch space. */ void test_ecmult_multi_pippenger_max_points(void) { size_t scratch_size = secp256k1_testrand_bits(8); - size_t max_size = secp256k1_pippenger_scratch_size(secp256k1_pippenger_bucket_window_inv(PIPPENGER_MAX_BUCKET_WINDOW-1)+512, 12); + /* Pick a max scratch size that permits using enough points to require the + * maximum bucket window */ + size_t max_points = secp256k1_pippenger_bucket_window_inv(PIPPENGER_MAX_BUCKET_WINDOW-1)+512; + size_t max_size = secp256k1_pippenger_scratch_size(max_points, PIPPENGER_MAX_BUCKET_WINDOW); secp256k1_scratch *scratch; size_t n_points_supported; int bucket_window = 0; - for(; scratch_size < max_size; scratch_size+=256) { - size_t i; - size_t total_alloc; + for(; scratch_size < max_size; scratch_size += 256) { size_t checkpoint; scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size); CHECK(scratch != NULL); @@ -4322,13 +4393,18 @@ void test_ecmult_multi_pippenger_max_points(void) { continue; } bucket_window = secp256k1_pippenger_bucket_window(n_points_supported); - /* allocate `total_alloc` bytes over `PIPPENGER_SCRATCH_OBJECTS` many allocations */ - total_alloc = secp256k1_pippenger_scratch_size(n_points_supported, bucket_window); - for (i = 0; i < PIPPENGER_SCRATCH_OBJECTS - 1; i++) { - CHECK(secp256k1_scratch_alloc(&ctx->error_callback, scratch, 1)); - total_alloc--; + { + /* Check that n_points_supported actually fit into the scratch + * space and that pippenger_scratch_size matches what's actually + * allocated */ + secp256k1_ge *points; + secp256k1_scalar *scalars; + secp256k1_gej *buckets; + struct secp256k1_pippenger_state *state_space; + size_t entries = secp256k1_pippenger_entries(n_points_supported); + CHECK(secp256k1_ecmult_pippenger_batch_allocate(&ctx->error_callback, scratch, entries, bucket_window, &points, &scalars, &buckets, &state_space)); + CHECK(scratch->alloc_size == secp256k1_pippenger_scratch_size(n_points_supported, bucket_window)); } - CHECK(secp256k1_scratch_alloc(&ctx->error_callback, scratch, total_alloc)); secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, checkpoint); secp256k1_scratch_destroy(&ctx->error_callback, scratch); } @@ -4426,21 +4502,15 @@ void test_ecmult_multi_batching(void) { /* Test with space for 1 point in pippenger. That's not enough because * ecmult_multi selects strauss which requires more memory. It should * therefore select the simple algorithm. */ - scratch = secp256k1_scratch_create(&ctx->error_callback, secp256k1_pippenger_scratch_size(1, 1) + PIPPENGER_SCRATCH_OBJECTS*ALIGNMENT); + scratch = secp256k1_scratch_create(&ctx->error_callback, secp256k1_pippenger_scratch_size(1, 1)); CHECK(secp256k1_ecmult_multi_var(&ctx->error_callback, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); secp256k1_gej_add_var(&r, &r, &r2, NULL); CHECK(secp256k1_gej_is_infinity(&r)); secp256k1_scratch_destroy(&ctx->error_callback, scratch); for(i = 1; i <= n_points; i++) { - if (i > ECMULT_PIPPENGER_THRESHOLD) { - int bucket_window = secp256k1_pippenger_bucket_window(i); - size_t scratch_size = secp256k1_pippenger_scratch_size(i, bucket_window); - scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size + PIPPENGER_SCRATCH_OBJECTS*ALIGNMENT); - } else { - size_t scratch_size = secp256k1_strauss_scratch_size(i); - scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT); - } + size_t scratch_size = secp256k1_ecmult_multi_scratch_size(i); + scratch = secp256k1_scratch_create(&ctx->error_callback, scratch_size); CHECK(secp256k1_ecmult_multi_var(&ctx->error_callback, scratch, &r, &scG, ecmult_multi_callback, &data, n_points)); secp256k1_gej_add_var(&r, &r, &r2, NULL); CHECK(secp256k1_gej_is_infinity(&r)); @@ -4450,10 +4520,21 @@ void test_ecmult_multi_batching(void) { free(pt); } +void test_ecmult_multi_scratch_size(void) { + /* test ECMULT_MAX_POINTS_PER_BATCH limit */ + size_t n_points = ECMULT_MAX_POINTS_PER_BATCH + 1; + size_t scratch_size = secp256k1_ecmult_multi_scratch_size(n_points); + int expected_bucket_window = secp256k1_pippenger_bucket_window(n_points - 1); + size_t expected_scratch_size = secp256k1_pippenger_scratch_size(n_points - 1, expected_bucket_window); + CHECK(expected_scratch_size == scratch_size); +} + void run_ecmult_multi_tests(void) { secp256k1_scratch *scratch; int64_t todo = (int64_t)320 * count; + test_ecmult_multi_strauss_scratch_size(); + test_ecmult_multi_strauss_max_points(); test_secp256k1_pippenger_bucket_window_inv(); test_ecmult_multi_pippenger_max_points(); scratch = secp256k1_scratch_create(&ctx->error_callback, 819200); @@ -4469,12 +4550,13 @@ void run_ecmult_multi_tests(void) { secp256k1_scratch_destroy(&ctx->error_callback, scratch); /* Run test_ecmult_multi with space for exactly one point */ - scratch = secp256k1_scratch_create(&ctx->error_callback, secp256k1_strauss_scratch_size(1) + STRAUSS_SCRATCH_OBJECTS*ALIGNMENT); + scratch = secp256k1_scratch_create(&ctx->error_callback, secp256k1_strauss_scratch_size(1)); test_ecmult_multi(scratch, secp256k1_ecmult_multi_var); secp256k1_scratch_destroy(&ctx->error_callback, scratch); test_ecmult_multi_batch_size_helper(); test_ecmult_multi_batching(); + test_ecmult_multi_scratch_size(); } void test_wnaf(const secp256k1_scalar *number, int w) {