Browse Source

Switch to a new scalarmul strategy

The new strategy has the same cost as the old one, but the new one
is right now using only 1-bit windows, and switching to 2-bit windows
(coming soon) will show big savings.
Ian Goldberg 4 years ago
parent
commit
3e7fae6d71
3 changed files with 146 additions and 153 deletions
  1. 113 143
      ecgadget.hpp
  2. 1 0
      pedersen.cpp
  3. 32 10
      scalarmul.cpp

+ 113 - 143
ecgadget.hpp

@@ -182,70 +182,55 @@ public:
   }
 };
 
-// Add nothing or the constant EC point P to the variable EC point
-// (inx,iny) to yield (outx,outy).  The input point must not be the
-// point at infinity.  The input bit do_add controls whether the
-// addition is done.
+// Add 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 which addition
+// is done.
 template<typename FieldT>
-class ec_conditional_constant_add_gadget : public gadget<FieldT> {
+class ec_2_constant_add_gadget : public gadget<FieldT> {
 private:
   pb_variable<FieldT> sumx, sumy;
-  std::vector<ec_constant_add_gadget<FieldT> > adder;
+  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> do_add;
-  const FieldT Px, Py;
+  const pb_variable<FieldT> which;
+  const FieldT P0x, P0y, P1x, P1y;
 
-  ec_conditional_constant_add_gadget(protoboard<FieldT> &pb,
+  ec_2_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> &do_add,
-              const FieldT &Px, const FieldT &Py) :
-    gadget<FieldT>(pb, "ec_conditional_constant_add_gadget"),
-    outx(outx), outy(outy), inx(inx), iny(iny), do_add(do_add),
-    Px(Px), Py(Py)
+              const pb_variable<FieldT> &which,
+              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),
+    P0x(P0x), P0y(P0y), P1x(P1x), P1y(P1y)
   {
     // Allocate variables to protoboard
-    // The strings (like "x") are only for debugging purposes
 	  
-    sumx.allocate(this->pb, "sumx");
-    sumy.allocate(this->pb, "sumy");
-    adder.emplace_back(this->pb, sumx, sumy, inx, iny, Px, Py);
+    addx.assign(pb, which * (P1x-P0x) + P0x);
+    addy.assign(pb, which * (P1y-P0y) + P0y);
+    adder.emplace_back(this->pb, outx, outy, inx, iny, addx, addy);
   }
 
   void generate_r1cs_constraints()
   {
-    // Strategy: we always do the addition, but if do_add = 0, we throw
-    // it away later.
-
     adder[0].generate_r1cs_constraints();
-
-    // Now we want to conditionally move the sum.  We want that
-    // outx = do_add ? sumx : inx
-    // outy = do_add ? sumy : iny
-
-    // so we compute
-    // outx = inx + (sumx - inx) * do_add
-    // outy = iny + (sumy - iny) * do_add
-
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(sumx - inx, do_add, outx - inx));
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(sumy - iny, do_add, outy - iny));
-
   }
 
   void generate_r1cs_witness()
   {
+    addx.evaluate(this->pb);
+    addy.evaluate(this->pb);
     adder[0].generate_r1cs_witness();
-
-    bool move = this->pb.val(do_add) != FieldT(0);
-    this->pb.val(outx) = move ? this->pb.val(sumx) : this->pb.lc_val(inx);
-    this->pb.val(outy) = move ? this->pb.val(sumy) : this->pb.lc_val(iny);
   }
 };
 
+#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
@@ -333,155 +318,125 @@ public:
     this->pb.val(outy) = (a1 || a2) ? this->pb.val(sumy) : this->pb.lc_val(iny);
   }
 };
-
-// Compute a*P as (outx, outy) for a given constant point P, given a
-// as a bit vector.  The _caller_ is responsible for proving that the
-// elements of avec are bits.
+#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
+// responsible for proving that the elements of svec are bits.  The
+// (constant) accumulator excess (AXS) will be updated; when all the
+// computations are complete, AXS should be subtracted from the
+// accumulator A.
 template<typename FieldT>
 class ec_constant_scalarmul_vec_gadget : public gadget<FieldT> {
 private:
-  FieldT Cx, Cy, CPx, CPy;
+  FieldT Cx, Cy;
   pb_variable_array<FieldT> accumx, accumy;
-  std::vector<ec_add_P123_gadget<FieldT> > conddoubleadders;
-  std::vector<ec_conditional_constant_add_gadget<FieldT> > condsingleadders;
-  std::vector<ec_constant_add_gadget<FieldT> > singleadders;
+  std::vector<ec_2_constant_add_gadget<FieldT> > twoadders;
 public:
   const pb_variable<FieldT> outx, outy;
-  const pb_variable_array<FieldT> avec;
+  const pb_variable<FieldT> Ax, Ay;
+  const pb_variable_array<FieldT> svec;
   const FieldT Px, Py;
 
   // Strategy: We compute (as compile-time constants) (powers of 2)
-  // times P, and then conditionally add them into an accumulator.
-  // Because our adder cannot handle the point at infinity O, we start
-  // the accumulator with a value of C, whose discrete log with respect
-  // to P should be unknown, so that we won't encounter O along the way.
-  // (Also, a should not be 0 or the group order.)  We actually start
-  // the accumulator with either C or C+P depending on avec[0], so we
-  // get the first conditional add "for free".  Then we use the
-  // ec_add_P123_gadget to do the conditional adds two at a time (at a
-  // cost of 6 constraints per pair, as opposed to 5 for a single
-  // conditional add).  If the length of avec is even, then there will
-  // be one left over, and we do a single conditional add for that one.
-  // Finally, we add the public point -C.
+  // times P.  Based on each bit of s, we add one of the constant points
+  // C or (2^i * P) + C to the accumulator, and regardless of s, add C
+  // to the excess.
 
   ec_constant_scalarmul_vec_gadget(protoboard<FieldT> &pb,
               const pb_variable<FieldT> &outx,
               const pb_variable<FieldT> &outy,
-              const pb_variable_array<FieldT> &avec,
-              const FieldT &Px, const FieldT &Py) :
+              const pb_variable<FieldT> &Ax,
+              const pb_variable<FieldT> &Ay,
+              const pb_variable_array<FieldT> &svec,
+              const FieldT &Px, const FieldT &Py,
+              FieldT &AXSx, FieldT &AXSy) :
     gadget<FieldT>(pb, "ec_constant_scalarmul_vec_gadget"),
     // Precomputed coordinates of C
     Cx(2),
     Cy("4950745124018817972378217179409499695353526031437053848725554590521829916331"),
-    outx(outx), outy(outy), avec(avec), Px(Px), Py(Py)
+    outx(outx), outy(outy), Ax(Ax), Ay(Ay), svec(svec), Px(Px), Py(Py)
   {
-    size_t numbits = avec.size();
-    accumx.allocate(this->pb, numbits/2+1, "accumx");
-    accumy.allocate(this->pb, numbits/2+1, "accumy");
-
-    ec_add_points(CPx, CPy, Cx, Cy, Px, Py);
+    size_t numbits = svec.size();
+    accumx.allocate(this->pb, numbits-1, "accumx");
+    accumy.allocate(this->pb, numbits-1, "accumy");
 
-    FieldT twoiPx, twoiPy, twoi1Px, twoi1Py, twoi3Px, twoi3Py;
-    size_t i = 1;
-
-    ec_double_point(twoiPx, twoiPy, Px, Py);
+    FieldT twoiPx = Px, twoiPy = Py;
+    size_t i = 0;
 
     while(i < numbits) {
-        // Invariants: i is odd, and twoiP = 2^i * P
-        // Compute twoi1P = 2^{i+1} * P = 2 * twoiP and
-        //         twoi3P = 2^i * 3 * P = 3 * twoiP
-        ec_double_point(twoi1Px, twoi1Py, twoiPx, twoiPy);
-        ec_add_points(twoi3Px, twoi3Py, twoi1Px, twoi1Py, twoiPx, twoiPy);
-
-        if (i == numbits-1) {
-            // There's only one bit of avec left; use a single conditional
-            // add.
-            condsingleadders.emplace_back(this->pb,
-                accumx[(i+1)/2], accumy[(i+1)/2],
-                accumx[(i-1)/2], accumy[(i-1)/2],
-                avec[i],
-                twoiPx, twoiPy);
-        } else {
-            conddoubleadders.emplace_back(this->pb,
-                accumx[(i+1)/2], accumy[(i+1)/2],
-                accumx[(i-1)/2], accumy[(i-1)/2],
-                avec[i], avec[i+1],
-                twoiPx, twoiPy, twoi1Px, twoi1Py, twoi3Px, twoi3Py);
-        }
-
-        ec_double_point(twoiPx, twoiPy, twoi1Px, twoi1Py);
-        i += 2;
+        // 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);
+
+        FieldT newtwoiPx, newtwoiPy, newAXSx, newAXSy;
+        ec_double_point(newtwoiPx, newtwoiPy, twoiPx, twoiPy);
+        twoiPx = newtwoiPx;
+        twoiPy = newtwoiPy;
+        i += 1;
+        ec_add_points(newAXSx, newAXSy, AXSx, AXSy, Cx, Cy);
+        AXSx = newAXSx;
+        AXSy = newAXSy;
     }
-
-    // If numbits is even, the output so far is in accum[(numbits)/2].
-    // If numbits is odd, it is in accum[(numbits-1)/2].  So in either
-    // case, it is in accum[numbits/2].
-    singleadders.emplace_back(this->pb,
-        outx, outy, accumx[numbits/2], accumy[numbits/2],
-        Cx, -Cy);
   }
 
   void generate_r1cs_constraints()
   {
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(Cx + (CPx-Cx) * avec[0], 1, accumx[0]));
-    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(Cy + (CPy-Cy) * avec[0], 1, accumy[0]));
-
-    for (auto&& gadget : conddoubleadders) {
-        gadget.generate_r1cs_constraints();
-    }
-    for (auto&& gadget : condsingleadders) {
-        gadget.generate_r1cs_constraints();
-    }
-    for (auto&& gadget : singleadders) {
+    for (auto&& gadget : twoadders) {
         gadget.generate_r1cs_constraints();
     }
   }
 
   void generate_r1cs_witness()
   {
-    this->pb.val(accumx[0]) = Cx + (CPx-Cx) * this->pb.val(avec[0]);
-    this->pb.val(accumy[0]) = Cy + (CPy-Cy) * this->pb.val(avec[0]);
-
-    for (auto&& gadget : conddoubleadders) {
-        gadget.generate_r1cs_witness();
-    }
-    for (auto&& gadget : condsingleadders) {
-        gadget.generate_r1cs_witness();
-    }
-    for (auto&& gadget : singleadders) {
+    for (auto&& gadget : twoadders) {
         gadget.generate_r1cs_witness();
     }
   }
 };
 
-// Compute a*P as (outx, outy) for a given constant point P, given a
-// as a field element.
+// Compute A + s*P as (outx, outy) for an accumulator A, a given
+// constant point P, and s given as a field element.  The (constant)
+// accumulator excess (AXS) will be updated; when all the computations
+// are complete, AXS should be subtracted from the accumulator A.
 template<typename FieldT>
 class ec_constant_scalarmul_gadget : public gadget<FieldT> {
 private:
-  pb_variable_array<FieldT> avec;
+  pb_variable_array<FieldT> svec;
   std::vector<packing_gadget<FieldT> > packers;
   std::vector<ec_constant_scalarmul_vec_gadget<FieldT> > vecgadget;
 
 public:
-  const pb_variable<FieldT> outx, outy, a;
+  const pb_variable<FieldT> outx, outy;
+  const pb_variable<FieldT> Ax, Ay;
+  const pb_variable<FieldT> s;
   const FieldT Px, Py;
 
   ec_constant_scalarmul_gadget(protoboard<FieldT> &pb,
               const pb_variable<FieldT> &outx,
               const pb_variable<FieldT> &outy,
-              const pb_variable<FieldT> &a,
-              const FieldT &Px, const FieldT &Py) :
+              const pb_variable<FieldT> &Ax,
+              const pb_variable<FieldT> &Ay,
+              const pb_variable<FieldT> &s,
+              const FieldT &Px, const FieldT &Py,
+              FieldT &AXSx, FieldT &AXSy) :
     gadget<FieldT>(pb, "ec_constant_scalarmul_gadget"),
-    outx(outx), outy(outy), a(a), Px(Px), Py(Py)
+    outx(outx), outy(outy), Ax(Ax), Ay(Ay), s(s), Px(Px), Py(Py)
   {
     // Allocate variables to protoboard
     // The strings (like "x") are only for debugging purposes
 	  
     size_t numbits = FieldT::num_bits;
-    avec.allocate(this->pb, numbits, "avec");
-    packers.emplace_back(this->pb, avec, a);
-    vecgadget.emplace_back(this->pb, outx, outy, avec, Px, Py);
+    svec.allocate(this->pb, numbits, "svec");
+    packers.emplace_back(this->pb, svec, s);
+    vecgadget.emplace_back(this->pb, outx, outy, Ax, Ay, svec, Px, Py, AXSx, AXSy);
   }
 
   void generate_r1cs_constraints()
@@ -501,10 +456,10 @@ public:
 template<typename FieldT>
 class ec_pedersen_gadget : public gadget<FieldT> {
 private:
-  pb_variable<FieldT> aoutx, aouty, boutx, bouty;
+  pb_variable<FieldT> accinx, acciny, accmidx, accmidy, accoutx, accouty;
   std::vector<ec_constant_scalarmul_gadget<FieldT> > mulgadgets;
-  std::vector<ec_add_gadget<FieldT> > addgadget;
-  const FieldT Gx, Gy, Hx, Hy;
+  std::vector<ec_constant_add_gadget<FieldT> > addgadget;
+  const FieldT Gx, Gy, Hx, Hy, Ax, Ay;
 
 public:
   const pb_variable<FieldT> outx, outy, a, b;
@@ -516,26 +471,41 @@ public:
               const pb_variable<FieldT> &b) :
     gadget<FieldT>(pb, "ec_pedersen_gadget"),
     outx(outx), outy(outy), a(a), b(b),
-  // Precomputed coordinates of G and H
+  // Precomputed coordinates of G, H, and A
   Gx(0),
   Gy("11977228949870389393715360594190192321220966033310912010610740966317727761886"),
   Hx(1),
-  Hy("21803877843449984883423225223478944275188924769286999517937427649571474907279")
+  Hy("21803877843449984883423225223478944275188924769286999517937427649571474907279"),
+  Ax("7536839002660211356286040193441766649532044555061394833845553337792579131020"),
+  Ay("11391058648720923807988142436733355540810929560298907319389650598553246451302")
   {
     // Allocate variables to protoboard
     // The strings (like "x") are only for debugging purposes
 	  
-    aoutx.allocate(this->pb, "aoutx");
-    aouty.allocate(this->pb, "aouty");
-    boutx.allocate(this->pb, "boutx");
-    bouty.allocate(this->pb, "bouty");
-    mulgadgets.emplace_back(this->pb, aoutx, aouty, a, Gx, Gy);
-    mulgadgets.emplace_back(this->pb, boutx, bouty, b, Hx, Hy);
-    addgadget.emplace_back(this->pb, outx, outy, aoutx, aouty, boutx, bouty);
+    accinx.allocate(this->pb, "accinx");
+    acciny.allocate(this->pb, "acciny");
+    accmidx.allocate(this->pb, "accmidx");
+    accmidy.allocate(this->pb, "accmidy");
+    accoutx.allocate(this->pb, "accoutx");
+    accouty.allocate(this->pb, "accouty");
+
+    // Initialize the accumulator
+    FieldT AXSx = Ax;
+    FieldT AXSy = Ay;
+    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(accinx, 1, Ax));
+    this->pb.add_r1cs_constraint(r1cs_constraint<FieldT>(acciny, 1, Ay));
+
+    // Initialize the gadgets
+    mulgadgets.emplace_back(this->pb, accmidx, accmidy, accinx, acciny, a, Gx, Gy, AXSx, AXSy);
+    mulgadgets.emplace_back(this->pb, accoutx, accouty, accmidx, accmidy, b, Hx, Hy, AXSx, AXSy);
+    // Subtract the accumulator excess to get the result
+    addgadget.emplace_back(this->pb, outx, outy, accoutx, accouty, AXSx, -AXSy);
   }
 
   void generate_r1cs_constraints()
   {
+    this->pb.val(accinx) = Ax;
+    this->pb.val(acciny) = Ay;
     mulgadgets[0].generate_r1cs_constraints();
     mulgadgets[1].generate_r1cs_constraints();
     addgadget[0].generate_r1cs_constraints();

+ 1 - 0
pedersen.cpp

@@ -65,6 +65,7 @@ int main()
 
   cout << "Number of R1CS constraints: " << constraint_system.num_constraints() << endl;
   cout << "Primary (public) input: " << pb.primary_input() << endl;
+  cout << "Auxiliary (private) input length: " << pb.auxiliary_input().size() << endl;
 //  cout << "Auxiliary (private) input: " << pb.auxiliary_input() << endl;
   cout << "Verification status: " << verified << endl;
 

+ 32 - 10
scalarmul.cpp

@@ -24,24 +24,42 @@ int main()
 
   protoboard<FieldT> pb;
   pb_variable<FieldT> outx, outy;
-  pb_variable<FieldT> a, b;
+  pb_variable<FieldT> accinx, acciny, accoutx, accouty;
+  pb_variable<FieldT> s;
+
+  // An accumulator initial value.  Its DL representation with respect
+  // to C and P should be unknown.
+  const FieldT Ax = FieldT("7536839002660211356286040193441766649532044555061394833845553337792579131020");
+  const FieldT Ay = FieldT("11391058648720923807988142436733355540810929560298907319389650598553246451302");
+  FieldT AXSx = Ax;
+  FieldT AXSy = Ay;
+  const FieldT Px = FieldT(0);
+  const FieldT Py = FieldT("11977228949870389393715360594190192321220966033310912010610740966317727761886");
 
   // Allocate variables
 
   outx.allocate(pb, "outx");
   outy.allocate(pb, "outy");
-  a.allocate(pb, "a");
+  accinx.allocate(pb, "accinx");
+  acciny.allocate(pb, "acciny");
+  accoutx.allocate(pb, "accoutx");
+  accouty.allocate(pb, "accouty");
+  s.allocate(pb, "s");
 
   // This sets up the protoboard variables so that the first n of them
   // represent the public input and the rest is private input
 
   pb.set_input_sizes(2);
 
-  // Initialize gadget
-
-  ec_constant_scalarmul_gadget<FieldT> sm(pb, outx, outy, a, FieldT(0), FieldT("11977228949870389393715360594190192321220966033310912010610740966317727761886"));
+  // Initialize the accumulator
+  pb.add_r1cs_constraint(r1cs_constraint<FieldT>(accinx, 1, Ax));
+  pb.add_r1cs_constraint(r1cs_constraint<FieldT>(acciny, 1, Ay));
+  // Initialize the gadget
+  ec_constant_scalarmul_gadget<FieldT> sm(pb, accoutx, accouty, accinx, acciny, s, Px, Py, AXSx, AXSy);
   sm.generate_r1cs_constraints();
-  
+  // Subtract the accumulator excess to get the result
+  ec_constant_add_gadget<FieldT> ad(pb, outx, outy, accoutx, accouty, AXSx, -AXSy);
+
   const r1cs_constraint_system<FieldT> constraint_system = pb.get_constraint_system();
 
   const r1cs_gg_ppzksnark_keypair<default_r1cs_gg_ppzksnark_pp> keypair = r1cs_gg_ppzksnark_generator<default_r1cs_gg_ppzksnark_pp>(constraint_system);
@@ -50,10 +68,13 @@ int main()
 
   cout << "Prover" << endl;
   
-  pb.val(a) = FieldT::random_element();
-  cout << "Computing " << pb.val(a) << "*G" << endl;
+  pb.val(accinx) = Ax;
+  pb.val(acciny) = Ay;
+  pb.val(s) = FieldT::random_element();
+  cout << "Computing " << pb.val(s) << "*G" << endl;
 
   sm.generate_r1cs_witness();
+  ad.generate_r1cs_witness();
 
   const r1cs_gg_ppzksnark_proof<default_r1cs_gg_ppzksnark_pp> proof = r1cs_gg_ppzksnark_prover<default_r1cs_gg_ppzksnark_pp>(keypair.pk, pb.primary_input(), pb.auxiliary_input());
 
@@ -63,7 +84,8 @@ int main()
 
   cout << "Number of R1CS constraints: " << constraint_system.num_constraints() << endl;
   cout << "Primary (public) input: " << pb.primary_input() << endl;
-//  cout << "Auxiliary (private) input: " << pb.auxiliary_input() << endl;
+  cout << "Auxiliary (private) input length: " << pb.auxiliary_input().size() << endl;
+  //cout << "Auxiliary (private) input: " << pb.auxiliary_input() << endl;
   cout << "Verification status: " << verified << endl;
 
   ofstream pkfile("pk_scalarmul");
@@ -76,7 +98,7 @@ int main()
   pffile << proof;
   pffile.close();
 
-  cout << pb.val(a) << "*G" << " = (" << pb.val(outx) << ", " << pb.val(outy) << ")" << endl;
+  cout << pb.val(s) << "*G" << " = (" << pb.val(outx) << ", " << pb.val(outy) << ")" << endl;
 
 
   return 0;