Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function to compute optimal ecmult_multi scratch size for a number of points #638

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bench_ecmult.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
240 changes: 193 additions & 47 deletions src/ecmult_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}
Expand All @@ -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.
Expand Down Expand Up @@ -630,22 +691,90 @@ 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) {
const size_t scratch_checkpoint = secp256k1_scratch_checkpoint(error_callback, scratch);
/* 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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Should the comment go above the definition of the function secp256k1_pippenger_entries instead?

secp256k1_ge *points;
secp256k1_scalar *scalars;
secp256k1_gej *buckets;
Expand All @@ -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<<bucket_window) * sizeof(*buckets));
if (state_space->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;
}
Expand Down Expand Up @@ -721,31 +835,47 @@ 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
Copy link
Contributor

@robot-dreams robot-dreams Nov 29, 2021

Choose a reason for hiding this comment

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

Do you already know how it might fail to be a maximum? (No worries if not, I still want to revisit these details carefully.)

Edit: Could this fail to be a maximum because the constant space used by the buckets decreases when you jump to the next bucket window size?

* 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;

for (bucket_window = 1; bucket_window <= PIPPENGER_MAX_BUCKET_WINDOW; bucket_window++) {
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Style nit (feel free to ignore): Should this be called point_size instead to avoid confusion, since in other places you get 2(n+1) entries from the endomorphism?


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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Style nit (feel free to ignore): Was this check previously here for avoiding underflow (rather than short-circuiting)? If so would it make sense to keep the check as space_constant > max_alloc to make the intent clear?

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));
Copy link
Contributor

Choose a reason for hiding this comment

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

After some discussion with @jonasnick, one of the things I'm not sure about is the added complexity in this function.
On the one hand, this function is accurate now and users of the function can rely on that.
On the other hand, if we just call secp256k1_scratch_max_allocation with PIPPENGER_SCRATCH_OBJECTS instead of 0, we may return a value that is one too small. That's not terrible for performance but it potentially makes the function a little bit harder to use and test because you may need to remember that it is not accurate.

Copy link
Contributor Author

@jonasnick jonasnick Jun 15, 2019

Choose a reason for hiding this comment

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

It seems like both hands are arguments in favor of the change (i.e. calling secp256k1_scratch_max_allocation with 0).

We need to do that for strauss anyway because otherwise

n_points == strauss_max_points(..., scratch_create(strauss_scratch_size(n_points)))

wouldn't hold.

}

n_points = space_for_points/entry_size;
n_points = n_points > max_points ? max_points : n_points;
if (n_points > res) {
res = n_points;
Expand Down Expand Up @@ -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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Approach ACK

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's an approach ACK?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I guess it's a concept ack. I was confused by other meanings of approach :D

Copy link
Contributor

@real-or-random real-or-random Jun 13, 2019

Choose a reason for hiding this comment

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

I'm actively testing bitcoin/bitcoin#16149 here

edit: except that I just write "ACK". All my "ACK"s in this review mean "ACK thorough code inspection"

Copy link
Contributor

Choose a reason for hiding this comment

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

(No action needed) Responding to this 2 year old comment 😅

In Core, it looks like "Approach ACK" means "Concept ACK, and I agree with the approach of this change (but I haven't reviewed the code in detail)":

https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#conceptual-review

}

#endif /* SECP256K1_ECMULT_IMPL_H */
11 changes: 9 additions & 2 deletions src/scratch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading