Repository: bitcoin-core/secp256k1
Pull request #580 (2018-12-22 21:33:36)

Add trivial ecmult_multi algorithm which does not require a scratch space
This commit adds a new ecmult_multi algorithm that is automatically selected when `ecmult_multi_var` is called with scratch space set to `NULL`. This is a trivial algorithm that simply multiplies the points with the corresponding scalars and adds them up.

The use case is to allow creating exposed function that uses `ecmult_multi` but without requiring a scratch space argument. For example, in MuSig when computing the combined public key we need to compute a weighted sum of points but we most likely don't care about the performance. And if we do we can still provide a scratch space. Having the option of not providing a scratch space is useful because creating a scratch space is not entirely trivial. One needs to decide on a size and it needs to be destroyed.

real-or-random: utACK 96839b3
jonasnick: squashed
apoelstra: ACK 3f59c4a6511b624ca1803a7126b24de5f8568dd9
outdamud: Thank you 

Sent from my iPhone

> On Feb 11, 2019, at 1:16 PM, Jonas Nick <notifications@github.com> wrote:
> 
> @jonasnick commented on this pull request.
> 
> In src/ecmult_impl.h:
> 
> > @@ -1083,6 +1083,32 @@ static size_t secp256k1_pippenger_max_points(secp256k1_scratch *scratch) {
>      return res;
>  }
>  
> +/* Computes ecmult_multi by simply multiplying and adding each point. Does not
> + * require a scratch space */
> +static int secp256k1_ecmult_multi_var_simple(const secp256k1_ecmult_context *ctx, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n_points) {
> yup, fixed
> 
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub, or mute the thread.

gmaxwell: This should probably get added to the ecmult bench too.

Patch:
diff --git a/src/bench_ecmult.c b/src/bench_ecmult.c
index 57b3bae..6d0ed1f 100644
--- a/src/bench_ecmult.c
+++ b/src/bench_ecmult.c
@@ -139,7 +139,11 @@ int main(int argc, char **argv) {
     secp256k1_gej* pubkeys_gej;
     size_t scratch_size;
 
+    data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
+    scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*16;
+    data.scratch = secp256k1_scratch_space_create(data.ctx, scratch_size);
     data.ecmult_multi = secp256k1_ecmult_multi_var;
+
     if (argc > 1) {
         if(have_flag(argc, argv, "pippenger_wnaf")) {
             printf("Using pippenger_wnaf:\n");
@@ -147,17 +151,19 @@ int main(int argc, char **argv) {
         } else if(have_flag(argc, argv, "strauss_wnaf")) {
             printf("Using strauss_wnaf:\n");
             data.ecmult_multi = secp256k1_ecmult_strauss_batch_single;
+        } else if(have_flag(argc, argv, "simple")) {
+            printf("Using simple algorithm:\n");
+            data.ecmult_multi = secp256k1_ecmult_multi_var;
+            secp256k1_scratch_space_destroy(data.scratch);
+            data.scratch = NULL;
         } else {
             fprintf(stderr, "%s: unrecognized argument '%s'.\n", argv[0], argv[1]);
-            fprintf(stderr, "Use 'pippenger_wnaf', 'strauss_wnaf' or no argument to benchmark a combined algorithm.\n");
+            fprintf(stderr, "Use 'pippenger_wnaf', 'strauss_wnaf', 'simple' or no argument to benchmark a combined algorithm.\n");
             return 1;
         }
     }
 
     /* Allocate stuff */
-    data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
-    scratch_size = secp256k1_strauss_scratch_size(POINTS) + STRAUSS_SCRATCH_OBJECTS*16;
-    data.scratch = secp256k1_scratch_space_create(data.ctx, scratch_size);
     data.scalars = malloc(sizeof(secp256k1_scalar) * POINTS);
     data.seckeys = malloc(sizeof(secp256k1_scalar) * POINTS);
     data.pubkeys = malloc(sizeof(secp256k1_ge) * POINTS);
@@ -188,7 +194,9 @@ int main(int argc, char **argv) {
         }
     }
     secp256k1_context_destroy(data.ctx);
-    secp256k1_scratch_space_destroy(data.scratch);
+    if (data.scratch != NULL) {
+        secp256k1_scratch_space_destroy(data.scratch);
+    }
     free(data.scalars);
     free(data.pubkeys);
     free(data.seckeys);
diff --git a/src/ecmult.h b/src/ecmult.h
index ea1cd8a..3d75a96 100644
--- a/src/ecmult.h
+++ b/src/ecmult.h
@@ -37,7 +37,8 @@ typedef int (secp256k1_ecmult_multi_callback)(secp256k1_scalar *sc, secp256k1_ge
  * Chooses the right algorithm for a given number of points and scratch space
  * size. Resets and overwrites the given scratch space. If the points do not
  * fit in the scratch space the algorithm is repeatedly run with batches of
- * points.
+ * points. If no scratch space is given then a simple algorithm is used that
+ * simply multiplies the points with the corresponding scalars and adds them up.
  * Returns: 1 on success (including when inp_g_sc is NULL and n is 0)
  *          0 if there is not enough scratch space for a single point or
  *          callback returns 0
diff --git a/src/ecmult_impl.h b/src/ecmult_impl.h
index 508bde8..7c37821 100644
--- a/src/ecmult_impl.h
+++ b/src/ecmult_impl.h
@@ -1084,6 +1084,33 @@ static size_t secp256k1_pippenger_max_points(secp256k1_scratch *scratch) {
     return res;
 }
 
+/* Computes ecmult_multi by simply multiplying and adding each point. Does not
+ * require a scratch space */
+static int secp256k1_ecmult_multi_simple_var(const secp256k1_ecmult_context *ctx, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n_points) {
+    size_t point_idx;
+    secp256k1_scalar szero;
+    secp256k1_gej tmpj;
+
+    secp256k1_scalar_set_int(&szero, 0);
+    secp256k1_gej_set_infinity(r);
+    secp256k1_gej_set_infinity(&tmpj);
+    /* r = inp_g_sc*G */
+    secp256k1_ecmult(ctx, r, &tmpj, &szero, inp_g_sc);
+    for (point_idx = 0; point_idx < n_points; point_idx++) {
+        secp256k1_ge point;
+        secp256k1_gej pointj;
+        secp256k1_scalar scalar;
+        if (!cb(&scalar, &point, point_idx, cbdata)) {
+            return 0;
+        }
+        /* r += scalar*point */
+        secp256k1_gej_set_ge(&pointj, &point);
+        secp256k1_ecmult(ctx, &tmpj, &pointj, &scalar, NULL);
+        secp256k1_gej_add_var(r, r, &tmpj, NULL);
+    }
+    return 1;
+}
+
 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);
 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) {
     size_t i;
@@ -1102,6 +1129,9 @@ static int secp256k1_ecmult_multi_var(const secp256k1_ecmult_context *ctx, secp2
         secp256k1_ecmult(ctx, r, r, &szero, inp_g_sc);
         return 1;
     }
+    if (scratch == NULL) {
+        return secp256k1_ecmult_multi_simple_var(ctx, r, inp_g_sc, cb, cbdata, n);
+    }
 
     max_points = secp256k1_pippenger_max_points(scratch);
     if (max_points == 0) {


Test patch:
diff --git a/src/tests.c b/src/tests.c
index 7189ef5..224ddc6 100644
--- a/src/tests.c
+++ b/src/tests.c
@@ -2926,6 +2926,7 @@ void run_ecmult_multi_tests(void) {
     test_ecmult_multi_pippenger_max_points();
     scratch = secp256k1_scratch_create(&ctx->error_callback, 819200);
     test_ecmult_multi(scratch, secp256k1_ecmult_multi_var);
+    test_ecmult_multi(NULL, secp256k1_ecmult_multi_var);
     test_ecmult_multi(scratch, secp256k1_ecmult_pippenger_batch_single);
     test_ecmult_multi(scratch, secp256k1_ecmult_strauss_batch_single);
     secp256k1_scratch_destroy(scratch);