Browse Source

Implement the 2-bit window optimization for scalarmults of constant points

With this optimization, each 2-bit window takes 4 constraints, plus the
2 "is a bit" constraints, for a total of 3 constraints per bit, and
a small fixed overhead (4 for scalarmult, 7 for Pedersen).

So constant-point scalarmult is 768 constraints and Pedersen is 1531
constraints.

One could do a similar 3-bit window optimization, but it turns out
to be no better in terms of constraints per bit, and above that it gets
worse.
Ian Goldberg 4 years ago
parent
commit
17ffb8e98d
1 changed files with 122 additions and 110 deletions
  1. 122 110
      ecgadget.hpp

+ 122 - 110
ecgadget.hpp

@@ -184,18 +184,17 @@ public:
 
 // Add the constant EC point P0 or the constant EC point P1 to the
 // variable EC point (inx,iny) to yield (outx,outy).  The input point
-// must not be the point at infinity.  The input bit which controls
+// must not be the point at infinity.  The input bit choice controls
 // which addition is done.
 template<typename FieldT>
 class ec_2_constant_add_gadget : public gadget<FieldT> {
 private:
-  pb_variable<FieldT> sumx, sumy;
   pb_linear_combination<FieldT> addx, addy;
   std::vector<ec_add_gadget<FieldT> > adder;
 public:
   const pb_variable<FieldT> outx, outy;
   const pb_linear_combination<FieldT> inx, iny;
-  const pb_variable<FieldT> which;
+  const pb_variable<FieldT> choice;
   const FieldT P0x, P0y, P1x, P1y;
 
   ec_2_constant_add_gadget(protoboard<FieldT> &pb,
@@ -203,17 +202,17 @@ public:
               const pb_variable<FieldT> &outy,
               const pb_linear_combination<FieldT> &inx,
               const pb_linear_combination<FieldT> &iny,
-              const pb_variable<FieldT> &which,
+              const pb_variable<FieldT> &choice,
               const FieldT &P0x, const FieldT &P0y,
               const FieldT &P1x, const FieldT &P1y) :
     gadget<FieldT>(pb, "ec_2_constant_add_gadget"),
-    outx(outx), outy(outy), inx(inx), iny(iny), which(which),
+    outx(outx), outy(outy), inx(inx), iny(iny), choice(choice),
     P0x(P0x), P0y(P0y), P1x(P1x), P1y(P1y)
   {
     // Allocate variables to protoboard
 
-    addx.assign(pb, which * (P1x-P0x) + P0x);
-    addy.assign(pb, which * (P1y-P0y) + P0y);
+    addx.assign(pb, choice * (P1x-P0x) + P0x);
+    addy.assign(pb, choice * (P1y-P0y) + P0y);
     adder.emplace_back(this->pb, outx, outy, inx, iny, addx, addy);
   }
 
@@ -232,18 +231,17 @@ public:
 
 // Add the constant EC point P0 or the variable EC point P1 to the
 // variable EC point (inx,iny) to yield (outx,outy).  The input point
-// must not be the point at infinity.  The input bit which controls
+// must not be the point at infinity.  The input bit choice controls
 // which addition is done.
 template<typename FieldT>
 class ec_2_1constant_add_gadget : public gadget<FieldT> {
 private:
-  pb_variable<FieldT> sumx, sumy;
   pb_variable<FieldT> addx, addy;
   std::vector<ec_add_gadget<FieldT> > adder;
 public:
   const pb_variable<FieldT> outx, outy;
   const pb_linear_combination<FieldT> inx, iny;
-  const pb_variable<FieldT> which;
+  const pb_variable<FieldT> choice;
   const FieldT P0x, P0y;
   const pb_variable<FieldT> P1x, P1y;
 
@@ -252,13 +250,13 @@ public:
               const pb_variable<FieldT> &outy,
               const pb_linear_combination<FieldT> &inx,
               const pb_linear_combination<FieldT> &iny,
-              const pb_variable<FieldT> &which,
+              const pb_variable<FieldT> &choice,
               const FieldT &P0x,
               const FieldT &P0y,
               const pb_variable<FieldT> &P1x,
               const pb_variable<FieldT> &P1y) :
     gadget<FieldT>(pb, "ec_2_1constant_add_gadget"),
-    outx(outx), outy(outy), inx(inx), iny(iny), which(which),
+    outx(outx), outy(outy), inx(inx), iny(iny), choice(choice),
     P0x(P0x), P0y(P0y), P1x(P1x), P1y(P1y)
   {
     // Allocate variables to protoboard
@@ -270,35 +268,34 @@ public:
 
   void generate_r1cs_constraints()
   {
-    // Set (addx,addy) = which ? (P0x, P0y) : (P1x, P1y)
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(P1x - P0x, which, addx - P0x));
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(P1y - P0y, which, addy - P0y));
+    // Set (addx,addy) = choice ? (P0x, P0y) : (P1x, P1y)
+    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(P1x - P0x, choice, addx - P0x));
+    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(P1y - P0y, choice, addy - P0y));
     adder[0].generate_r1cs_constraints();
   }
 
   void generate_r1cs_witness()
   {
-    bool whichb = this->pb.val(which) != FieldT(0);
-    this->pb.val(addx) = whichb ? this->pb.val(P1x) : P0x;
-    this->pb.val(addy) = whichb ? this->pb.val(P1y) : P0y;
+    bool choiceb = this->pb.val(choice) != FieldT(0);
+    this->pb.val(addx) = choiceb ? this->pb.val(P1x) : P0x;
+    this->pb.val(addy) = choiceb ? this->pb.val(P1y) : P0y;
     adder[0].generate_r1cs_witness();
   }
 };
 
 // Add the variable EC point P0 or the variable EC point P1 to the
 // variable EC point (inx,iny) to yield (outx,outy).  The input point
-// must not be the point at infinity.  The input bit which controls
+// must not be the point at infinity.  The input bit choice controls
 // which addition is done.
 template<typename FieldT>
 class ec_2_add_gadget : public gadget<FieldT> {
 private:
-  pb_variable<FieldT> sumx, sumy;
   pb_variable<FieldT> addx, addy;
   std::vector<ec_add_gadget<FieldT> > adder;
 public:
   const pb_variable<FieldT> outx, outy;
   const pb_linear_combination<FieldT> inx, iny;
-  const pb_variable<FieldT> which;
+  const pb_variable<FieldT> choice;
   const pb_variable<FieldT> P0x, P0y, P1x, P1y;
 
   ec_2_add_gadget(protoboard<FieldT> &pb,
@@ -306,13 +303,13 @@ public:
               const pb_variable<FieldT> &outy,
               const pb_linear_combination<FieldT> &inx,
               const pb_linear_combination<FieldT> &iny,
-              const pb_variable<FieldT> &which,
+              const pb_variable<FieldT> &choice,
               const pb_variable<FieldT> &P0x,
               const pb_variable<FieldT> &P0y,
               const pb_variable<FieldT> &P1x,
               const pb_variable<FieldT> &P1y) :
     gadget<FieldT>(pb, "ec_2_add_gadget"),
-    outx(outx), outy(outy), inx(inx), iny(iny), which(which),
+    outx(outx), outy(outy), inx(inx), iny(iny), choice(choice),
     P0x(P0x), P0y(P0y), P1x(P1x), P1y(P1y)
   {
     // Allocate variables to protoboard
@@ -324,110 +321,78 @@ public:
 
   void generate_r1cs_constraints()
   {
-    // Set (addx,addy) = which ? (P0x, P0y) : (P1x, P1y)
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(P1x - P0x, which, addx - P0x));
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(P1y - P0y, which, addy - P0y));
+    // Set (addx,addy) = choice ? (P0x, P0y) : (P1x, P1y)
+    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(P1x - P0x, choice, addx - P0x));
+    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(P1y - P0y, choice, addy - P0y));
     adder[0].generate_r1cs_constraints();
   }
 
   void generate_r1cs_witness()
   {
-    bool whichb = this->pb.val(which) != FieldT(0);
-    this->pb.val(addx) = whichb ? this->pb.val(P1x) : this->pb.val(P0x);
-    this->pb.val(addy) = whichb ? this->pb.val(P1y) : this->pb.val(P0y);
+    bool choiceb = this->pb.val(choice) != FieldT(0);
+    this->pb.val(addx) = choiceb ? this->pb.val(P1x) : this->pb.val(P0x);
+    this->pb.val(addy) = choiceb ? this->pb.val(P1y) : this->pb.val(P0y);
     adder[0].generate_r1cs_witness();
   }
 };
 
-#if 0
-// Add nothing, or one of the constant EC points P1, P2, or P3 to the EC
-// point (inx,iny) to yield (outx,outy).  The input point must not be
-// the point at infinity.  The two input bits add1 and add2 control what
-// is added.  Typically, P3 will equal P1+P2, in which case this gadget
-// does two conditional constant adds simultaneously in just 6 constraints.
+// Add one of the four constant EC points to the variable EC point
+// (inx,iny) to yield (outx,outy).  The input point must not be the
+// point at infinity.  The input bits choice0 and choice1 control which
+// addition is done (P{2*choice1+choice0} is added).
 template<typename FieldT>
-class ec_add_P123_gadget : public gadget<FieldT> {
+class ec_4_constant_add_gadget : public gadget<FieldT> {
 private:
-  pb_variable<FieldT> lambda, sumx, sumy, move;
+  pb_variable<FieldT> both;
+  pb_linear_combination<FieldT> addx, addy;
+  std::vector<ec_add_gadget<FieldT> > adder;
 public:
   const pb_variable<FieldT> outx, outy;
   const pb_linear_combination<FieldT> inx, iny;
-  const pb_variable<FieldT> add1, add2;
-  const FieldT P1x, P1y, P2x, P2y, P3x, P3y;
+  const pb_variable<FieldT> choice0, choice1;
+  const FieldT P0x, P0y, P1x, P1y, P2x, P2y, P3x, P3y;
 
-  ec_add_P123_gadget(protoboard<FieldT> &pb,
+  ec_4_constant_add_gadget(protoboard<FieldT> &pb,
               const pb_variable<FieldT> &outx,
               const pb_variable<FieldT> &outy,
               const pb_linear_combination<FieldT> &inx,
               const pb_linear_combination<FieldT> &iny,
-              const pb_variable<FieldT> &add1,
-              const pb_variable<FieldT> &add2,
+              const pb_variable<FieldT> &choice0,
+              const pb_variable<FieldT> &choice1,
+              const FieldT &P0x, const FieldT &P0y,
               const FieldT &P1x, const FieldT &P1y,
               const FieldT &P2x, const FieldT &P2y,
-              const FieldT &P3x, const FieldT &P3y) : 
-    gadget<FieldT>(pb, "ec_add_P123_gadget"),
-    outx(outx), outy(outy), inx(inx), iny(iny), add1(add1), add2(add2),
-    P1x(P1x), P1y(P1y), P2x(P2x), P2y(P2y), P3x(P3x), P3y(P3y)
+              const FieldT &P3x, const FieldT &P3y) :
+    gadget<FieldT>(pb, "ec_4_constant_add_gadget"),
+    outx(outx), outy(outy), inx(inx), iny(iny),
+    choice0(choice0), choice1(choice1),
+    P0x(P0x), P0y(P0y), P1x(P1x), P1y(P1y),
+    P2x(P2x), P2y(P2y), P3x(P3x), P3y(P3y)
   {
     // Allocate variables to protoboard
-    // The strings (like "x") are only for debugging purposes
 
-    lambda.allocate(this->pb, "lambda");
-    sumx.allocate(this->pb, "sumx");
-    sumy.allocate(this->pb, "sumy");
-    move.allocate(this->pb, "move");
+    both.allocate(this->pb, "both");
+    addx.assign(this->pb, both * (P3x - P2x - P1x + P0x) + choice1 * (P2x - P0x) + choice0 * (P1x - P0x) + P0x);
+    addy.assign(this->pb, both * (P3y - P2y - P1y + P0y) + choice1 * (P2y - P0y) + choice0 * (P1y - P0y) + P0y);
+    adder.emplace_back(this->pb, outx, outy, inx, iny, addx, addy);
   }
 
   void generate_r1cs_constraints()
   {
-    // Strategy: if add1 = add2 = 0, we compute some nonsense but throw
-    // it away later.  Otherwise, the coordinates of the point to add
-    // are a _linear_ function of add1 and add2 (since P1, P2, and P3
-    // are public constants)
-
-    // In particular, the point to add is ( (P3x - P2x) * add1 + (P3x -
-    // P1x) * add2 + (P1x + P2x - P3x), (P3y - P2y) * add1 + (P3y - P1y) *
-    // add2 + (P1y + P2y - P3y))
-
-    // (addx - inx) * lambda = addy - iny
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>((P3x - P2x) * add1 + (P3x - P1x) * add2 + (P1x + P2x - P3x) - inx, lambda, (P3y - P2y) * add1 + (P3y - P1y) * add2 + (P1y + P2y - P3y) - iny));
-
-    // sumx = lambda^2 - (addx + inx)
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(lambda, lambda, sumx + (P3x - P2x) * add1 + (P3x - P1x) * add2 + (P1x + P2x - P3x) + inx));
-
-    // sumy = lambda * (inx - sumx) - iny
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(lambda, inx - sumx, sumy + iny));
-
-    // Now we want to conditionally move the sum.  We want that
-    // outx = (add1 || add2) ? sumx : inx
-    // outy = (add1 || add2) ? sumy : iny
-
-    // so we compute move = add1 || add2, and then
-    // outx = inx + (sumx - inx) * move
-    // outy = iny + (sumy - iny) * move
-
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(1 - add1, 1 - add2, 1 - move));
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(sumx - inx, move, outx - inx));
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(sumy - iny, move, outy - iny));
-
+    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(choice0, choice1, both));
+    adder[0].generate_r1cs_constraints();
   }
 
   void generate_r1cs_witness()
   {
-    FieldT addxval = (P3x - P2x) * this->pb.val(add1) + (P3x - P1x) * this->pb.val(add2) + (P1x + P2x - P3x);
-    FieldT addyval = (P3y - P2y) * this->pb.val(add1) + (P3y - P1y) * this->pb.val(add2) + (P1y + P2y - P3y);
-    this->pb.val(lambda) = (addyval - this->pb.lc_val(iny)) * (addxval - this->pb.lc_val(inx)).inverse();
-    this->pb.val(sumx) = this->pb.val(lambda).squared() - (addxval + this->pb.lc_val(inx));
-    this->pb.val(sumy) = this->pb.val(lambda) * (this->pb.lc_val(inx) - this->pb.val(sumx)) - this->pb.lc_val(iny);
-
-    bool a1 = this->pb.val(add1) != FieldT(0);
-    bool a2 = this->pb.val(add2) != FieldT(0);
-    this->pb.val(move) = a1 || a2;
-    this->pb.val(outx) = (a1 || a2) ? this->pb.val(sumx) : this->pb.lc_val(inx);
-    this->pb.val(outy) = (a1 || a2) ? this->pb.val(sumy) : this->pb.lc_val(iny);
+    bool c0 = this->pb.val(choice0) != FieldT(0);
+    bool c1 = this->pb.val(choice1) != FieldT(0);
+    this->pb.val(both) = c0 && c1;
+    addx.evaluate(this->pb);
+    addy.evaluate(this->pb);
+    adder[0].generate_r1cs_witness();
   }
 };
-#endif
 
 // Compute A + s*P as (outx, outy) for an accumulator A, a given
 // constant point P, and s given as a bit vector.  The _caller_ is
@@ -440,6 +405,7 @@ class ec_constant_scalarmul_vec_accum_gadget : public gadget<FieldT> {
 private:
   FieldT Cx, Cy;
   pb_variable_array<FieldT> accumx, accumy;
+  std::vector<ec_4_constant_add_gadget<FieldT> > fouradders;
   std::vector<ec_2_constant_add_gadget<FieldT> > twoadders;
 public:
   const pb_variable<FieldT> outx, outy;
@@ -467,29 +433,69 @@ public:
     outx(outx), outy(outy), Ax(Ax), Ay(Ay), svec(svec), Px(Px), Py(Py)
   {
     size_t numbits = svec.size();
-    accumx.allocate(this->pb, numbits-1, "accumx");
-    accumy.allocate(this->pb, numbits-1, "accumy");
+    // See loop comments below: if numbits is odd, we need (numbits-1)/2
+    // slots.  If numbits is even, we need (numbits-2)/2 slots.  So
+    // with integer truncated division, (numbits-1)/2 will be correct
+    // in both cases.  (Well, if numbits is 0 for some reason, we also want
+    // to get 0.)
+    size_t accumslots = 0;
+    if (numbits > 0) {
+        accumslots = (numbits-1)/2;
+    }
+    accumx.allocate(this->pb, accumslots, "accumx");
+    accumy.allocate(this->pb, accumslots, "accumy");
 
     FieldT twoiPx = Px, twoiPy = Py;
-    size_t i = 0;
+    size_t i = 0, accnext = 0;
 
     while(i < numbits) {
         // Invariant: twoiP = 2^i * P
-        FieldT twoiPCx, twoiPCy;
-        ec_add_points(twoiPCx, twoiPCy, twoiPx, twoiPy, Cx, Cy);
-
-        twoadders.emplace_back(this->pb,
-            (i == numbits-1 ? outx : accumx[i]),
-            (i == numbits-1 ? outy : accumy[i]),
-            (i == 0 ? Ax : accumx[i-1]),
-            (i == 0 ? Ay : accumy[i-1]),
-            svec[i], Cx, Cy, twoiPCx, twoiPCy);
+        // Invariant: i is even and accnext = i/2
+
+        if (i == numbits-1) {
+            FieldT twoiPCx, twoiPCy;
+            ec_add_points(twoiPCx, twoiPCy, twoiPx, twoiPy, Cx, Cy);
+
+            twoadders.emplace_back(this->pb,
+                outx, outy,
+                (i == 0 ? Ax : accumx[accnext-1]),
+                (i == 0 ? Ay : accumy[accnext-1]),
+                svec[i], Cx, Cy, twoiPCx, twoiPCy);
+
+            // This makes i odd, but also exits the loop with
+            // i = numbits and accnext = (numbits-1)/2
+            i += 1;
+        } else {
+            // Do two bits at a time
+
+            // We need to compute 2^i * a * P + C for a = 1,2,3
+            FieldT twoi2Px, twoi2Py;
+            FieldT twoi1PCx, twoi1PCy, twoi2PCx, twoi2PCy, twoi3PCx, twoi3PCy;
+
+            ec_add_points(twoi1PCx, twoi1PCy, twoiPx, twoiPy, Cx, Cy);
+            ec_double_point(twoi2Px, twoi2Py, twoiPx, twoiPy);
+            ec_add_points(twoi2PCx, twoi2PCy, twoi2Px, twoi2Py, Cx, Cy);
+            ec_add_points(twoi3PCx, twoi3PCy, twoi2Px, twoi2Py,
+                    twoi1PCx, twoi1PCy);
+
+            fouradders.emplace_back(this->pb,
+                (i == numbits-2 ? outx : accumx[accnext]),
+                (i == numbits-2 ? outy : accumy[accnext]),
+                (i == 0 ? Ax : accumx[accnext-1]),
+                (i == 0 ? Ay : accumy[accnext-1]),
+                svec[i], svec[i+1], Cx, Cy, twoi1PCx, twoi1PCy,
+                twoi2PCx, twoi2PCy, twoi3PCx, twoi3PCy);
+
+            // If i == numbits-2, we write directly to out and not accum above, and
+            // exit the loop with i even and i == numbits and accnext = (numbits-2)/2
+            if (i < numbits - 2) {
+                accnext += 1;
+            }
+            i += 2;
+            ec_double_point(twoiPx, twoiPy, twoi2Px, twoi2Py);
+        }
 
-        FieldT newtwoiPx, newtwoiPy, newAXSx, newAXSy;
-        ec_double_point(newtwoiPx, newtwoiPy, twoiPx, twoiPy);
-        twoiPx = newtwoiPx;
-        twoiPy = newtwoiPy;
-        i += 1;
+        FieldT newAXSx, newAXSy;
         ec_add_points(newAXSx, newAXSy, AXSx, AXSy, Cx, Cy);
         AXSx = newAXSx;
         AXSy = newAXSy;
@@ -498,6 +504,9 @@ public:
 
   void generate_r1cs_constraints()
   {
+    for (auto&& gadget : fouradders) {
+        gadget.generate_r1cs_constraints();
+    }
     for (auto&& gadget : twoadders) {
         gadget.generate_r1cs_constraints();
     }
@@ -505,6 +514,9 @@ public:
 
   void generate_r1cs_witness()
   {
+    for (auto&& gadget : fouradders) {
+        gadget.generate_r1cs_witness();
+    }
     for (auto&& gadget : twoadders) {
         gadget.generate_r1cs_witness();
     }