Clearmatics Libsnark  0.1
C++ library for zkSNARK proofs
basic_gadgets.tcc
Go to the documentation of this file.
1 /** @file
2  *****************************************************************************
3  * @author This file is part of libsnark, developed by SCIPR Lab
4  * and contributors (see AUTHORS).
5  * @copyright MIT license (see LICENSE file)
6  *****************************************************************************/
7 
8 #ifndef BASIC_GADGETS_TCC_
9 #define BASIC_GADGETS_TCC_
10 
11 #include <libff/common/profiling.hpp>
12 #include <libff/common/utils.hpp>
13 
14 namespace libsnark
15 {
16 
17 template<typename FieldT>
18 void generate_boolean_r1cs_constraint(
19  protoboard<FieldT> &pb,
20  const pb_linear_combination<FieldT> &lc,
21  const std::string &annotation_prefix)
22 /* forces lc to take value 0 or 1 by adding constraint lc * (1-lc) = 0 */
23 {
24  pb.add_r1cs_constraint(
25  r1cs_constraint<FieldT>(lc, 1 - lc, 0),
26  FMT(annotation_prefix, " boolean_r1cs_constraint"));
27 }
28 
29 template<typename FieldT>
30 void generate_r1cs_equals_const_constraint(
31  protoboard<FieldT> &pb,
32  const pb_linear_combination<FieldT> &lc,
33  const FieldT &c,
34  const std::string &annotation_prefix)
35 {
36  pb.add_r1cs_constraint(
37  r1cs_constraint<FieldT>(1, lc, c),
38  FMT(annotation_prefix, " constness_constraint"));
39 }
40 
41 template<typename FieldT>
42 void packing_gadget<FieldT>::generate_r1cs_constraints(
43  const bool enforce_bitness)
44 /* adds constraint result = \sum bits[i] * 2^i */
45 {
46  this->pb.add_r1cs_constraint(
47  r1cs_constraint<FieldT>(1, pb_packing_sum<FieldT>(bits), packed),
48  FMT(this->annotation_prefix, " packing_constraint"));
49 
50  if (enforce_bitness) {
51  for (size_t i = 0; i < bits.size(); ++i) {
52  generate_boolean_r1cs_constraint<FieldT>(
53  this->pb,
54  bits[i],
55  FMT(this->annotation_prefix, " bitness_%zu", i));
56  }
57  }
58 }
59 
60 template<typename FieldT>
61 void packing_gadget<FieldT>::generate_r1cs_witness_from_packed()
62 {
63  packed.evaluate(this->pb);
64  assert(
65  this->pb.lc_val(packed).as_bigint().num_bits() <=
66  bits.size()); // `bits` is large enough to represent this packed value
67  bits.fill_with_bits_of_field_element(this->pb, this->pb.lc_val(packed));
68 }
69 
70 template<typename FieldT>
71 void packing_gadget<FieldT>::generate_r1cs_witness_from_bits()
72 {
73  bits.evaluate(this->pb);
74  this->pb.lc_val(packed) = bits.get_field_element_from_bits(this->pb);
75 }
76 
77 template<typename FieldT>
78 multipacking_gadget<FieldT>::multipacking_gadget(
79  protoboard<FieldT> &pb,
80  const pb_linear_combination_array<FieldT> &bits,
81  const pb_linear_combination_array<FieldT> &packed_vars,
82  const size_t chunk_size,
83  const std::string &annotation_prefix)
84  : gadget<FieldT>(pb, annotation_prefix)
85  , bits(bits)
86  , packed_vars(packed_vars)
87  , chunk_size(chunk_size)
88  , num_chunks(libff::div_ceil(bits.size(), chunk_size))
89 // last_chunk_size(bits.size() - (num_chunks-1) * chunk_size)
90 {
91  assert(packed_vars.size() == num_chunks);
92  for (size_t i = 0; i < num_chunks; ++i) {
93  packers.emplace_back(packing_gadget<FieldT>(
94  this->pb,
95  pb_linear_combination_array<FieldT>(
96  bits.begin() + i * chunk_size,
97  bits.begin() + std::min((i + 1) * chunk_size, bits.size())),
98  packed_vars[i],
99  FMT(this->annotation_prefix, " packers_%zu", i)));
100  }
101 }
102 
103 template<typename FieldT>
104 void multipacking_gadget<FieldT>::generate_r1cs_constraints(
105  const bool enforce_bitness)
106 {
107  for (size_t i = 0; i < num_chunks; ++i) {
108  packers[i].generate_r1cs_constraints(enforce_bitness);
109  }
110 }
111 
112 template<typename FieldT>
113 void multipacking_gadget<FieldT>::generate_r1cs_witness_from_packed()
114 {
115  for (size_t i = 0; i < num_chunks; ++i) {
116  packers[i].generate_r1cs_witness_from_packed();
117  }
118 }
119 
120 template<typename FieldT>
121 void multipacking_gadget<FieldT>::generate_r1cs_witness_from_bits()
122 {
123  for (size_t i = 0; i < num_chunks; ++i) {
124  packers[i].generate_r1cs_witness_from_bits();
125  }
126 }
127 
128 template<typename FieldT> size_t multipacking_num_chunks(const size_t num_bits)
129 {
130  return libff::div_ceil(num_bits, FieldT::capacity());
131 }
132 
133 template<typename FieldT>
134 field_vector_copy_gadget<FieldT>::field_vector_copy_gadget(
135  protoboard<FieldT> &pb,
136  const pb_variable_array<FieldT> &source,
137  const pb_variable_array<FieldT> &target,
138  const pb_linear_combination<FieldT> &do_copy,
139  const std::string &annotation_prefix)
140  : gadget<FieldT>(pb, annotation_prefix)
141  , source(source)
142  , target(target)
143  , do_copy(do_copy)
144 {
145  assert(source.size() == target.size());
146 }
147 
148 template<typename FieldT>
149 void field_vector_copy_gadget<FieldT>::generate_r1cs_constraints()
150 {
151  for (size_t i = 0; i < source.size(); ++i) {
152  this->pb.add_r1cs_constraint(
153  r1cs_constraint<FieldT>(do_copy, source[i] - target[i], 0),
154  FMT(this->annotation_prefix, " copying_check_%zu", i));
155  }
156 }
157 
158 template<typename FieldT>
159 void field_vector_copy_gadget<FieldT>::generate_r1cs_witness()
160 {
161  do_copy.evaluate(this->pb);
162  assert(
163  this->pb.lc_val(do_copy) == FieldT::one() ||
164  this->pb.lc_val(do_copy) == FieldT::zero());
165  if (this->pb.lc_val(do_copy) != FieldT::zero()) {
166  for (size_t i = 0; i < source.size(); ++i) {
167  this->pb.val(target[i]) = this->pb.val(source[i]);
168  }
169  }
170 }
171 
172 template<typename FieldT>
173 bit_vector_copy_gadget<FieldT>::bit_vector_copy_gadget(
174  protoboard<FieldT> &pb,
175  const pb_variable_array<FieldT> &source_bits,
176  const pb_variable_array<FieldT> &target_bits,
177  const pb_linear_combination<FieldT> &do_copy,
178  const size_t chunk_size,
179  const std::string &annotation_prefix)
180  : gadget<FieldT>(pb, annotation_prefix)
181  , source_bits(source_bits)
182  , target_bits(target_bits)
183  , do_copy(do_copy)
184  , chunk_size(chunk_size)
185  , num_chunks(libff::div_ceil(source_bits.size(), chunk_size))
186 {
187  assert(source_bits.size() == target_bits.size());
188 
189  packed_source.allocate(
190  pb, num_chunks, FMT(annotation_prefix, " packed_source"));
191  pack_source.reset(new multipacking_gadget<FieldT>(
192  pb,
193  source_bits,
194  packed_source,
195  chunk_size,
196  FMT(annotation_prefix, " pack_source")));
197 
198  packed_target.allocate(
199  pb, num_chunks, FMT(annotation_prefix, " packed_target"));
200  pack_target.reset(new multipacking_gadget<FieldT>(
201  pb,
202  target_bits,
203  packed_target,
204  chunk_size,
205  FMT(annotation_prefix, " pack_target")));
206 
207  copier.reset(new field_vector_copy_gadget<FieldT>(
208  pb,
209  packed_source,
210  packed_target,
211  do_copy,
212  FMT(annotation_prefix, " copier")));
213 }
214 
215 template<typename FieldT>
216 void bit_vector_copy_gadget<FieldT>::generate_r1cs_constraints(
217  const bool enforce_source_bitness, const bool enforce_target_bitness)
218 {
219  pack_source->generate_r1cs_constraints(enforce_source_bitness);
220  pack_target->generate_r1cs_constraints(enforce_target_bitness);
221 
222  copier->generate_r1cs_constraints();
223 }
224 
225 template<typename FieldT>
226 void bit_vector_copy_gadget<FieldT>::generate_r1cs_witness()
227 {
228  do_copy.evaluate(this->pb);
229  assert(
230  this->pb.lc_val(do_copy) == FieldT::zero() ||
231  this->pb.lc_val(do_copy) == FieldT::one());
232  if (this->pb.lc_val(do_copy) == FieldT::one()) {
233  for (size_t i = 0; i < source_bits.size(); ++i) {
234  this->pb.val(target_bits[i]) = this->pb.val(source_bits[i]);
235  }
236  }
237 
238  pack_source->generate_r1cs_witness_from_bits();
239  pack_target->generate_r1cs_witness_from_bits();
240 }
241 
242 template<typename FieldT>
243 void dual_variable_gadget<FieldT>::generate_r1cs_constraints(
244  const bool enforce_bitness)
245 {
246  consistency_check->generate_r1cs_constraints(enforce_bitness);
247 }
248 
249 template<typename FieldT>
250 void dual_variable_gadget<FieldT>::generate_r1cs_witness_from_packed()
251 {
252  consistency_check->generate_r1cs_witness_from_packed();
253 }
254 
255 template<typename FieldT>
256 void dual_variable_gadget<FieldT>::generate_r1cs_witness_from_bits()
257 {
258  consistency_check->generate_r1cs_witness_from_bits();
259 }
260 
261 template<typename FieldT>
262 void disjunction_gadget<FieldT>::generate_r1cs_constraints()
263 {
264  linear_combination<FieldT> sum = pb_sum(inputs);
265 
266  // inv * sum = output
267  this->pb.add_r1cs_constraint(
268  r1cs_constraint<FieldT>(inv, sum, output),
269  FMT(this->annotation_prefix, " inv*sum=output"));
270 
271  // (1-output) * sum = 0
272  this->pb.add_r1cs_constraint(
273  r1cs_constraint<FieldT>(FieldT::one() - output, sum, FieldT::zero()),
274  FMT(this->annotation_prefix, " (1-output)*sum=0"));
275 }
276 
277 template<typename FieldT>
278 void disjunction_gadget<FieldT>::generate_r1cs_witness()
279 {
280  FieldT sum = FieldT::zero();
281 
282  for (size_t i = 0; i < inputs.size(); ++i) {
283  sum += this->pb.lc_val(inputs[i]);
284  }
285 
286  if (sum.is_zero()) {
287  this->pb.val(inv) = FieldT::zero();
288  this->pb.val(output) = FieldT::zero();
289  } else {
290  this->pb.val(inv) = sum.inverse();
291  this->pb.val(output) = FieldT::one();
292  }
293 }
294 
295 template<typename FieldT>
296 void conjunction_gadget<FieldT>::generate_r1cs_constraints()
297 {
298  /* inv * (n-sum) = 1-output */
299  linear_combination<FieldT> a1, b1, c1;
300  a1.add_term(inv);
301  b1.add_term(ONE, inputs.size());
302  for (size_t i = 0; i < inputs.size(); ++i) {
303  b1.add_term(inputs[i], -1);
304  }
305  c1.add_term(ONE);
306  c1.add_term(output, -1);
307 
308  this->pb.add_r1cs_constraint(
309  r1cs_constraint<FieldT>(a1, b1, c1),
310  FMT(this->annotation_prefix, " inv*(n-sum)=(1-output)"));
311 
312  /* output * (n-sum) = 0 */
313  linear_combination<FieldT> a2, b2, c2;
314  a2.add_term(output);
315  b2.add_term(ONE, inputs.size());
316  for (size_t i = 0; i < inputs.size(); ++i) {
317  b2.add_term(inputs[i], -1);
318  }
319  c2.add_term(ONE, 0);
320 
321  this->pb.add_r1cs_constraint(
322  r1cs_constraint<FieldT>(a2, b2, c2),
323  FMT(this->annotation_prefix, " output*(n-sum)=0"));
324 }
325 
326 template<typename FieldT>
327 void conjunction_gadget<FieldT>::generate_r1cs_witness()
328 {
329  FieldT sum = FieldT(inputs.size());
330 
331  for (size_t i = 0; i < inputs.size(); ++i) {
332  sum -= this->pb.val(inputs[i]);
333  }
334 
335  if (sum.is_zero()) {
336  this->pb.val(inv) = FieldT::zero();
337  this->pb.val(output) = FieldT::one();
338  } else {
339  this->pb.val(inv) = sum.inverse();
340  this->pb.val(output) = FieldT::zero();
341  }
342 }
343 
344 template<typename FieldT>
345 void comparison_gadget<FieldT>::generate_r1cs_constraints()
346 {
347  /*
348  packed(alpha) = 2^n + B - A
349 
350  not_all_zeros = \bigvee_{i=0}^{n-1} alpha_i
351 
352  if B - A > 0, then 2^n + B - A > 2^n,
353  so alpha_n = 1 and not_all_zeros = 1
354  if B - A = 0, then 2^n + B - A = 2^n,
355  so alpha_n = 1 and not_all_zeros = 0
356  if B - A < 0, then 2^n + B - A \in {0, 1, \ldots, 2^n-1},
357  so alpha_n = 0
358 
359  therefore alpha_n = less_or_eq and alpha_n * not_all_zeros = less
360  */
361 
362  /* not_all_zeros to be Boolean, alpha_i are Boolean by packing gadget */
363  generate_boolean_r1cs_constraint<FieldT>(
364  this->pb,
365  not_all_zeros,
366  FMT(this->annotation_prefix, " not_all_zeros"));
367 
368  /* constraints for packed(alpha) = 2^n + B - A */
369  pack_alpha->generate_r1cs_constraints(true);
370  this->pb.add_r1cs_constraint(
371  r1cs_constraint<FieldT>(1, (FieldT(2) ^ n) + B - A, alpha_packed),
372  FMT(this->annotation_prefix, " main_constraint"));
373 
374  /* compute result */
375  all_zeros_test->generate_r1cs_constraints();
376  this->pb.add_r1cs_constraint(
377  r1cs_constraint<FieldT>(less_or_eq, not_all_zeros, less),
378  FMT(this->annotation_prefix, " less"));
379 }
380 
381 template<typename FieldT>
382 void comparison_gadget<FieldT>::generate_r1cs_witness()
383 {
384  A.evaluate(this->pb);
385  B.evaluate(this->pb);
386 
387  /* unpack 2^n + B - A into alpha_packed */
388  this->pb.val(alpha_packed) =
389  (FieldT(2) ^ n) + this->pb.lc_val(B) - this->pb.lc_val(A);
390  pack_alpha->generate_r1cs_witness_from_packed();
391 
392  /* compute result */
393  all_zeros_test->generate_r1cs_witness();
394  this->pb.val(less) = this->pb.val(less_or_eq) * this->pb.val(not_all_zeros);
395 }
396 
397 template<typename FieldT>
398 void inner_product_gadget<FieldT>::generate_r1cs_constraints()
399 {
400  /*
401  S_i = \sum_{k=0}^{i+1} A[i] * B[i]
402  S[0] = A[0] * B[0]
403  S[i+1] - S[i] = A[i] * B[i]
404  */
405  for (size_t i = 0; i < A.size(); ++i) {
406  this->pb.add_r1cs_constraint(
407  r1cs_constraint<FieldT>(
408  A[i],
409  B[i],
410  (i == A.size() - 1 ? result : S[i]) +
411  (i == 0 ? 0 * ONE : -S[i - 1])),
412  FMT(this->annotation_prefix, " S_%zu", i));
413  }
414 }
415 
416 template<typename FieldT>
417 void inner_product_gadget<FieldT>::generate_r1cs_witness()
418 {
419  FieldT total = FieldT::zero();
420  for (size_t i = 0; i < A.size(); ++i) {
421  A[i].evaluate(this->pb);
422  B[i].evaluate(this->pb);
423 
424  total += this->pb.lc_val(A[i]) * this->pb.lc_val(B[i]);
425  this->pb.val(i == A.size() - 1 ? result : S[i]) = total;
426  }
427 }
428 
429 template<typename FieldT>
430 void loose_multiplexing_gadget<FieldT>::generate_r1cs_constraints()
431 {
432  /* \alpha_i (index - i) = 0 */
433  for (size_t i = 0; i < arr.size(); ++i) {
434  this->pb.add_r1cs_constraint(
435  r1cs_constraint<FieldT>(alpha[i], index - i, 0),
436  FMT(this->annotation_prefix, " alpha_%zu", i));
437  }
438 
439  /* 1 * (\sum \alpha_i) = success_flag */
440  linear_combination<FieldT> a, b, c;
441  a.add_term(ONE);
442  for (size_t i = 0; i < arr.size(); ++i) {
443  b.add_term(alpha[i]);
444  }
445  c.add_term(success_flag);
446  this->pb.add_r1cs_constraint(
447  r1cs_constraint<FieldT>(a, b, c),
448  FMT(this->annotation_prefix, " main_constraint"));
449 
450  /* now success_flag is constrained to either 0 (if index is out of
451  range) or \alpha_i. constrain it and \alpha_i to zero */
452  generate_boolean_r1cs_constraint<FieldT>(
453  this->pb, success_flag, FMT(this->annotation_prefix, " success_flag"));
454 
455  /* compute result */
456  compute_result->generate_r1cs_constraints();
457 }
458 
459 template<typename FieldT>
460 void loose_multiplexing_gadget<FieldT>::generate_r1cs_witness()
461 {
462  /* assumes that idx can be fit in ulong; true for our purposes for now */
463  const libff::bigint<FieldT::num_limbs> valint =
464  this->pb.val(index).as_bigint();
465  unsigned long idx = valint.as_ulong();
466  const libff::bigint<FieldT::num_limbs> arrsize(arr.size());
467 
468  if (idx >= arr.size() ||
469  mpn_cmp(valint.data, arrsize.data, FieldT::num_limbs) >= 0) {
470  for (size_t i = 0; i < arr.size(); ++i) {
471  this->pb.val(alpha[i]) = FieldT::zero();
472  }
473 
474  this->pb.val(success_flag) = FieldT::zero();
475  } else {
476  for (size_t i = 0; i < arr.size(); ++i) {
477  this->pb.val(alpha[i]) =
478  (i == idx ? FieldT::one() : FieldT::zero());
479  }
480 
481  this->pb.val(success_flag) = FieldT::one();
482  }
483 
484  compute_result->generate_r1cs_witness();
485 }
486 
487 template<typename FieldT, typename VarT>
488 void create_linear_combination_constraints(
489  protoboard<FieldT> &pb,
490  const std::vector<FieldT> &base,
491  const std::vector<std::pair<VarT, FieldT>> &v,
492  const VarT &target,
493  const std::string &annotation_prefix)
494 {
495  for (size_t i = 0; i < base.size(); ++i) {
496  linear_combination<FieldT> a, b, c;
497 
498  a.add_term(ONE);
499  b.add_term(ONE, base[i]);
500 
501  for (auto &p : v) {
502  b.add_term(p.first.all_vars[i], p.second);
503  }
504 
505  c.add_term(target.all_vars[i]);
506 
507  pb.add_r1cs_constraint(
508  r1cs_constraint<FieldT>(a, b, c),
509  FMT(annotation_prefix, " linear_combination_%zu", i));
510  }
511 }
512 
513 template<typename FieldT, typename VarT>
514 void create_linear_combination_witness(
515  protoboard<FieldT> &pb,
516  const std::vector<FieldT> &base,
517  const std::vector<std::pair<VarT, FieldT>> &v,
518  const VarT &target)
519 {
520  for (size_t i = 0; i < base.size(); ++i) {
521  pb.val(target.all_vars[i]) = base[i];
522 
523  for (auto &p : v) {
524  pb.val(target.all_vars[i]) +=
525  p.second * pb.val(p.first.all_vars[i]);
526  }
527  }
528 }
529 
530 } // namespace libsnark
531 #endif // BASIC_GADGETS_TCC_