Clearmatics Libsnark  0.1
C++ library for zkSNARK proofs
r1cs_to_sap.tcc
Go to the documentation of this file.
1 /** @file
2  *****************************************************************************
3 
4  Implementation of interfaces for a R1CS-to-SAP reduction.
5 
6  See r1cs_to_qap.hpp .
7 
8  *****************************************************************************
9  * @author This file is part of libsnark, developed by SCIPR Lab
10  * and contributors (see AUTHORS).
11  * @copyright MIT license (see LICENSE file)
12  *****************************************************************************/
13 
14 #ifndef R1CS_TO_SAP_TCC_
15 #define R1CS_TO_SAP_TCC_
16 
17 #include <libff/common/profiling.hpp>
18 #include <libff/common/utils.hpp>
19 #include <libfqfft/evaluation_domain/get_evaluation_domain.hpp>
20 
21 namespace libsnark
22 {
23 
24 /**
25  * Helper function to multiply a field element by 4 efficiently
26  */
27 template<typename FieldT> FieldT times_four(FieldT x)
28 {
29  FieldT times_two = x + x;
30  return times_two + times_two;
31 }
32 
33 /**
34  * Helper function to find evaluation domain that will be used by the reduction
35  * for a given R1CS instance.
36  */
37 template<typename FieldT>
38 std::shared_ptr<libfqfft::evaluation_domain<FieldT>> r1cs_to_sap_get_domain(
39  const r1cs_constraint_system<FieldT> &cs)
40 {
41  /*
42  * the SAP instance will have:
43  * - two constraints for every constraint in the original constraint system
44  * - two constraints for every public input, except the 0th, which
45  * contributes just one extra constraint
46  * see comments in r1cs_to_sap_instance_map for details on where these
47  * constraints come from.
48  */
49  return libfqfft::get_evaluation_domain<FieldT>(
50  2 * cs.num_constraints() + 2 * cs.num_inputs() + 1);
51 }
52 
53 /**
54  * Instance map for the R1CS-to-SAP reduction.
55  */
56 template<typename FieldT>
57 sap_instance<FieldT> r1cs_to_sap_instance_map(
58  const r1cs_constraint_system<FieldT> &cs)
59 {
60  libff::enter_block("Call to r1cs_to_sap_instance_map");
61 
62  const std::shared_ptr<libfqfft::evaluation_domain<FieldT>> domain =
63  r1cs_to_sap_get_domain(cs);
64 
65  size_t sap_num_variables =
66  cs.num_variables() + cs.num_constraints() + cs.num_inputs();
67 
68  std::vector<std::map<size_t, FieldT>> A_in_Lagrange_basis(
69  sap_num_variables + 1);
70  std::vector<std::map<size_t, FieldT>> C_in_Lagrange_basis(
71  sap_num_variables + 1);
72 
73  libff::enter_block("Compute polynomials A, C in Lagrange basis");
74  /**
75  * process R1CS constraints, converting a constraint of the form
76  * \sum a_i x_i * \sum b_i x_i = \sum c_i x_i
77  * into two constraints
78  * (\sum (a_i + b_i) x_i)^2 = 4 \sum c_i x_i + x'_i
79  * (\sum (a_i - b_i) x_i)^2 = x'_i
80  * where x'_i is an extra variable (a separate one for each original
81  * constraint)
82  *
83  * this adds 2 * cs.num_constraints() constraints
84  * (numbered 0 .. 2 * cs.num_constraints() - 1)
85  * and cs.num_constraints() extra variables
86  * (numbered cs.num_variables() + 1 .. cs.num_variables() +
87  * cs.num_constraints())
88  */
89  size_t extra_var_offset = cs.num_variables() + 1;
90  for (size_t i = 0; i < cs.num_constraints(); ++i) {
91  for (size_t j = 0; j < cs.constraints[i].a.terms.size(); ++j) {
92  A_in_Lagrange_basis[cs.constraints[i].a.terms[j].index][2 * i] +=
93  cs.constraints[i].a.terms[j].coeff;
94  A_in_Lagrange_basis[cs.constraints[i].a.terms[j].index]
95  [2 * i + 1] +=
96  cs.constraints[i].a.terms[j].coeff;
97  }
98 
99  for (size_t j = 0; j < cs.constraints[i].b.terms.size(); ++j) {
100  A_in_Lagrange_basis[cs.constraints[i].b.terms[j].index][2 * i] +=
101  cs.constraints[i].b.terms[j].coeff;
102  A_in_Lagrange_basis[cs.constraints[i].b.terms[j].index]
103  [2 * i + 1] -=
104  cs.constraints[i].b.terms[j].coeff;
105  }
106 
107  for (size_t j = 0; j < cs.constraints[i].c.terms.size(); ++j) {
108  C_in_Lagrange_basis[cs.constraints[i].c.terms[j].index][2 * i] +=
109  times_four(cs.constraints[i].c.terms[j].coeff);
110  }
111 
112  C_in_Lagrange_basis[extra_var_offset + i][2 * i] += FieldT::one();
113  C_in_Lagrange_basis[extra_var_offset + i][2 * i + 1] += FieldT::one();
114  }
115 
116  /**
117  * add and convert the extra constraints
118  * x_i * 1 = x_i
119  * to ensure that the polynomials 0 .. cs.num_inputs() are linearly
120  * independent from each other and the rest, which is required for security
121  * proofs (see [GM17, p. 29])
122  *
123  * note that i = 0 is a special case, where this constraint is expressible
124  * as x_0^2 = x_0,
125  * whereas for every other i we introduce an extra variable x''_i and do
126  * (x_i + x_0)^2 = 4 x_i + x''_i
127  * (x_i - x_0)^2 = x''_i
128  *
129  * this adds 2 * cs.num_inputs() + 1 extra constraints
130  * (numbered 2 * cs.num_constraints() ..
131  * 2 * cs.num_constraints() + 2 * cs.num_inputs())
132  * and cs.num_inputs() extra variables
133  * (numbered cs.num_variables() + cs.num_constraints() + 1 ..
134  * cs.num_variables() + cs.num_constraints() + cs.num_inputs())
135  */
136 
137  size_t extra_constr_offset = 2 * cs.num_constraints();
138  size_t extra_var_offset2 = cs.num_variables() + cs.num_constraints();
139  /**
140  * NB: extra variables start at (extra_var_offset2 + 1), because i starts at
141  * 1 below
142  */
143 
144  A_in_Lagrange_basis[0][extra_constr_offset] = FieldT::one();
145  C_in_Lagrange_basis[0][extra_constr_offset] = FieldT::one();
146 
147  for (size_t i = 1; i <= cs.num_inputs(); ++i) {
148  A_in_Lagrange_basis[i][extra_constr_offset + 2 * i - 1] +=
149  FieldT::one();
150  A_in_Lagrange_basis[0][extra_constr_offset + 2 * i - 1] +=
151  FieldT::one();
152  C_in_Lagrange_basis[i][extra_constr_offset + 2 * i - 1] +=
153  times_four(FieldT::one());
154  C_in_Lagrange_basis[extra_var_offset2 + i]
155  [extra_constr_offset + 2 * i - 1] += FieldT::one();
156 
157  A_in_Lagrange_basis[i][extra_constr_offset + 2 * i] += FieldT::one();
158  A_in_Lagrange_basis[0][extra_constr_offset + 2 * i] -= FieldT::one();
159  C_in_Lagrange_basis[extra_var_offset2 + i]
160  [2 * cs.num_constraints() + 2 * i] += FieldT::one();
161  }
162 
163  libff::leave_block("Compute polynomials A, C in Lagrange basis");
164 
165  libff::leave_block("Call to r1cs_to_sap_instance_map");
166 
167  return sap_instance<FieldT>(
168  domain,
169  sap_num_variables,
170  domain->m,
171  cs.num_inputs(),
172  std::move(A_in_Lagrange_basis),
173  std::move(C_in_Lagrange_basis));
174 }
175 
176 /**
177  * Instance map for the R1CS-to-SAP reduction followed by evaluation
178  * of the resulting QAP instance.
179  */
180 template<typename FieldT>
181 sap_instance_evaluation<FieldT> r1cs_to_sap_instance_map_with_evaluation(
182  const r1cs_constraint_system<FieldT> &cs, const FieldT &t)
183 {
184  libff::enter_block("Call to r1cs_to_sap_instance_map_with_evaluation");
185 
186  const std::shared_ptr<libfqfft::evaluation_domain<FieldT>> domain =
187  r1cs_to_sap_get_domain(cs);
188 
189  size_t sap_num_variables =
190  cs.num_variables() + cs.num_constraints() + cs.num_inputs();
191 
192  std::vector<FieldT> At, Ct, Ht;
193 
194  At.resize(sap_num_variables + 1, FieldT::zero());
195  Ct.resize(sap_num_variables + 1, FieldT::zero());
196  Ht.reserve(domain->m + 1);
197 
198  const FieldT Zt = domain->compute_vanishing_polynomial(t);
199 
200  libff::enter_block("Compute evaluations of A, C, H at t");
201  const std::vector<FieldT> u = domain->evaluate_all_lagrange_polynomials(t);
202  /**
203  * add and process all constraints as in r1cs_to_sap_instance_map
204  */
205  size_t extra_var_offset = cs.num_variables() + 1;
206  for (size_t i = 0; i < cs.num_constraints(); ++i) {
207  for (size_t j = 0; j < cs.constraints[i].a.terms.size(); ++j) {
208  At[cs.constraints[i].a.terms[j].index] +=
209  u[2 * i] * cs.constraints[i].a.terms[j].coeff;
210  At[cs.constraints[i].a.terms[j].index] +=
211  u[2 * i + 1] * cs.constraints[i].a.terms[j].coeff;
212  }
213 
214  for (size_t j = 0; j < cs.constraints[i].b.terms.size(); ++j) {
215  At[cs.constraints[i].b.terms[j].index] +=
216  u[2 * i] * cs.constraints[i].b.terms[j].coeff;
217  At[cs.constraints[i].b.terms[j].index] -=
218  u[2 * i + 1] * cs.constraints[i].b.terms[j].coeff;
219  }
220 
221  for (size_t j = 0; j < cs.constraints[i].c.terms.size(); ++j) {
222  Ct[cs.constraints[i].c.terms[j].index] +=
223  times_four(u[2 * i] * cs.constraints[i].c.terms[j].coeff);
224  }
225 
226  Ct[extra_var_offset + i] += u[2 * i];
227  Ct[extra_var_offset + i] += u[2 * i + 1];
228  }
229 
230  size_t extra_constr_offset = 2 * cs.num_constraints();
231  size_t extra_var_offset2 = cs.num_variables() + cs.num_constraints();
232 
233  At[0] += u[extra_constr_offset];
234  Ct[0] += u[extra_constr_offset];
235 
236  for (size_t i = 1; i <= cs.num_inputs(); ++i) {
237  At[i] += u[extra_constr_offset + 2 * i - 1];
238  At[0] += u[extra_constr_offset + 2 * i - 1];
239  Ct[i] += times_four(u[extra_constr_offset + 2 * i - 1]);
240  Ct[extra_var_offset2 + i] += u[extra_constr_offset + 2 * i - 1];
241 
242  At[i] += u[extra_constr_offset + 2 * i];
243  At[0] -= u[extra_constr_offset + 2 * i];
244  Ct[extra_var_offset2 + i] += u[extra_constr_offset + 2 * i];
245  }
246 
247  FieldT ti = FieldT::one();
248  for (size_t i = 0; i < domain->m + 1; ++i) {
249  Ht.emplace_back(ti);
250  ti *= t;
251  }
252  libff::leave_block("Compute evaluations of A, C, H at t");
253 
254  libff::leave_block("Call to r1cs_to_sap_instance_map_with_evaluation");
255 
256  return sap_instance_evaluation<FieldT>(
257  domain,
258  sap_num_variables,
259  domain->m,
260  cs.num_inputs(),
261  t,
262  std::move(At),
263  std::move(Ct),
264  std::move(Ht),
265  Zt);
266 }
267 
268 /**
269  * Witness map for the R1CS-to-SAP reduction.
270  *
271  * The witness map takes zero knowledge into account when d1, d2 are random.
272  *
273  * More precisely, compute the coefficients
274  * h_0,h_1,...,h_n
275  * of the polynomial
276  * H(z) := (A(z)*A(z)-C(z))/Z(z)
277  * where
278  * A(z) := A_0(z) + \sum_{k=1}^{m} w_k A_k(z) + d1 * Z(z)
279  * C(z) := C_0(z) + \sum_{k=1}^{m} w_k C_k(z) + d2 * Z(z)
280  * Z(z) := "vanishing polynomial of set S"
281  * and
282  * m = number of variables of the SAP
283  * n = degree of the SAP
284  *
285  * This is done as follows:
286  * (1) compute evaluations of A,C on S = {sigma_1,...,sigma_n}
287  * (2) compute coefficients of A,C
288  * (3) compute evaluations of A,C on T = "coset of S"
289  * (4) compute evaluation of H on T
290  * (5) compute coefficients of H
291  * (6) patch H to account for d1,d2
292  (i.e., add coefficients of the polynomial (2*d1*A - d2 + d1^2 * Z))
293  *
294  * The code below is not as simple as the above high-level description due to
295  * some reshuffling to save space.
296  */
297 template<typename FieldT>
298 sap_witness<FieldT> r1cs_to_sap_witness_map(
299  const r1cs_constraint_system<FieldT> &cs,
300  const r1cs_primary_input<FieldT> &primary_input,
301  const r1cs_auxiliary_input<FieldT> &auxiliary_input,
302  const FieldT &d1,
303  const FieldT &d2)
304 {
305  libff::enter_block("Call to r1cs_to_sap_witness_map");
306 
307  /* sanity check */
308  assert(cs.is_satisfied(primary_input, auxiliary_input));
309 
310  const std::shared_ptr<libfqfft::evaluation_domain<FieldT>> domain =
311  r1cs_to_sap_get_domain(cs);
312 
313  size_t sap_num_variables =
314  cs.num_variables() + cs.num_constraints() + cs.num_inputs();
315 
316  r1cs_variable_assignment<FieldT> full_variable_assignment = primary_input;
317  full_variable_assignment.insert(
318  full_variable_assignment.end(),
319  auxiliary_input.begin(),
320  auxiliary_input.end());
321  /**
322  * we need to generate values of all the extra variables that we added
323  * during the reduction
324  *
325  * note: below, we pass full_variable_assignment into the .evaluate()
326  * method of the R1CS constraints. however, these extra variables shouldn't
327  * be a problem, because .evaluate() only accesses the variables that are
328  * actually used in the constraint.
329  */
330  for (size_t i = 0; i < cs.num_constraints(); ++i) {
331  /**
332  * this is variable (extra_var_offset + i), an extra variable
333  * we introduced that is not present in the input.
334  * its value is (a - b)^2
335  */
336  FieldT extra_var =
337  cs.constraints[i].a.evaluate(full_variable_assignment) -
338  cs.constraints[i].b.evaluate(full_variable_assignment);
339  extra_var = extra_var * extra_var;
340  full_variable_assignment.push_back(extra_var);
341  }
342  for (size_t i = 1; i <= cs.num_inputs(); ++i) {
343  /**
344  * this is variable (extra_var_offset2 + i), an extra variable
345  * we introduced that is not present in the input.
346  * its value is (x_i - 1)^2
347  */
348  FieldT extra_var = full_variable_assignment[i - 1] - FieldT::one();
349  extra_var = extra_var * extra_var;
350  full_variable_assignment.push_back(extra_var);
351  }
352 
353  libff::enter_block("Compute evaluation of polynomial A on set S");
354  std::vector<FieldT> aA(domain->m, FieldT::zero());
355 
356  /* account for all constraints, as in r1cs_to_sap_instance_map */
357  for (size_t i = 0; i < cs.num_constraints(); ++i) {
358  aA[2 * i] += cs.constraints[i].a.evaluate(full_variable_assignment);
359  aA[2 * i] += cs.constraints[i].b.evaluate(full_variable_assignment);
360 
361  aA[2 * i + 1] += cs.constraints[i].a.evaluate(full_variable_assignment);
362  aA[2 * i + 1] -= cs.constraints[i].b.evaluate(full_variable_assignment);
363  }
364 
365  size_t extra_constr_offset = 2 * cs.num_constraints();
366 
367  aA[extra_constr_offset] += FieldT::one();
368 
369  for (size_t i = 1; i <= cs.num_inputs(); ++i) {
370  aA[extra_constr_offset + 2 * i - 1] += full_variable_assignment[i - 1];
371  aA[extra_constr_offset + 2 * i - 1] += FieldT::one();
372 
373  aA[extra_constr_offset + 2 * i] += full_variable_assignment[i - 1];
374  aA[extra_constr_offset + 2 * i] -= FieldT::one();
375  }
376 
377  libff::leave_block("Compute evaluation of polynomial A on set S");
378 
379  libff::enter_block("Compute coefficients of polynomial A");
380  domain->iFFT(aA);
381  libff::leave_block("Compute coefficients of polynomial A");
382 
383  libff::enter_block("Compute ZK-patch");
384  std::vector<FieldT> coefficients_for_H(domain->m + 1, FieldT::zero());
385 #ifdef MULTICORE
386 #pragma omp parallel for
387 #endif
388  /* add coefficients of the polynomial (2*d1*A - d2) + d1*d1*Z */
389  for (size_t i = 0; i < domain->m; ++i) {
390  coefficients_for_H[i] = (d1 * aA[i]) + (d1 * aA[i]);
391  }
392  coefficients_for_H[0] -= d2;
393  domain->add_poly_Z(d1 * d1, coefficients_for_H);
394  libff::leave_block("Compute ZK-patch");
395 
396  libff::enter_block("Compute evaluation of polynomial A on set T");
397  domain->cosetFFT(aA, FieldT::multiplicative_generator);
398  libff::leave_block("Compute evaluation of polynomial A on set T");
399 
400  libff::enter_block("Compute evaluation of polynomial H on set T");
401  std::vector<FieldT> &H_tmp =
402  aA; // can overwrite aA because it is not used later
403 #ifdef MULTICORE
404 #pragma omp parallel for
405 #endif
406  for (size_t i = 0; i < domain->m; ++i) {
407  H_tmp[i] = aA[i] * aA[i];
408  }
409 
410  libff::enter_block("Compute evaluation of polynomial C on set S");
411  std::vector<FieldT> aC(domain->m, FieldT::zero());
412  /* again, accounting for all constraints */
413  size_t extra_var_offset = cs.num_variables() + 1;
414  for (size_t i = 0; i < cs.num_constraints(); ++i) {
415  aC[2 * i] +=
416  times_four(cs.constraints[i].c.evaluate(full_variable_assignment));
417 
418  aC[2 * i] += full_variable_assignment[extra_var_offset + i - 1];
419  aC[2 * i + 1] += full_variable_assignment[extra_var_offset + i - 1];
420  }
421 
422  size_t extra_var_offset2 = cs.num_variables() + cs.num_constraints();
423  aC[extra_constr_offset] += FieldT::one();
424 
425  for (size_t i = 1; i <= cs.num_inputs(); ++i) {
426  aC[extra_constr_offset + 2 * i - 1] +=
427  times_four(full_variable_assignment[i - 1]);
428 
429  aC[extra_constr_offset + 2 * i - 1] +=
430  full_variable_assignment[extra_var_offset2 + i - 1];
431  aC[extra_constr_offset + 2 * i] +=
432  full_variable_assignment[extra_var_offset2 + i - 1];
433  }
434 
435  libff::leave_block("Compute evaluation of polynomial C on set S");
436 
437  libff::enter_block("Compute coefficients of polynomial C");
438  domain->iFFT(aC);
439  libff::leave_block("Compute coefficients of polynomial C");
440 
441  libff::enter_block("Compute evaluation of polynomial C on set T");
442  domain->cosetFFT(aC, FieldT::multiplicative_generator);
443  libff::leave_block("Compute evaluation of polynomial C on set T");
444 
445 #ifdef MULTICORE
446 #pragma omp parallel for
447 #endif
448  for (size_t i = 0; i < domain->m; ++i) {
449  H_tmp[i] = (H_tmp[i] - aC[i]);
450  }
451 
452  libff::enter_block("Divide by Z on set T");
453  domain->divide_by_Z_on_coset(H_tmp);
454  libff::leave_block("Divide by Z on set T");
455 
456  libff::leave_block("Compute evaluation of polynomial H on set T");
457 
458  libff::enter_block("Compute coefficients of polynomial H");
459  domain->icosetFFT(H_tmp, FieldT::multiplicative_generator);
460  libff::leave_block("Compute coefficients of polynomial H");
461 
462  libff::enter_block("Compute sum of H and ZK-patch");
463 #ifdef MULTICORE
464 #pragma omp parallel for
465 #endif
466  for (size_t i = 0; i < domain->m; ++i) {
467  coefficients_for_H[i] += H_tmp[i];
468  }
469  libff::leave_block("Compute sum of H and ZK-patch");
470 
471  libff::leave_block("Call to r1cs_to_sap_witness_map");
472 
473  return sap_witness<FieldT>(
474  sap_num_variables,
475  domain->m,
476  cs.num_inputs(),
477  d1,
478  d2,
479  full_variable_assignment,
480  std::move(coefficients_for_H));
481 }
482 
483 } // namespace libsnark
484 
485 #endif // R1CS_TO_SAP_TCC_