2 *****************************************************************************
4 Implementation of interfaces for a R1CS-to-SAP reduction.
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 *****************************************************************************/
14 #ifndef R1CS_TO_SAP_TCC_
15 #define R1CS_TO_SAP_TCC_
17 #include <libff/common/profiling.hpp>
18 #include <libff/common/utils.hpp>
19 #include <libfqfft/evaluation_domain/get_evaluation_domain.hpp>
25 * Helper function to multiply a field element by 4 efficiently
27 template<typename FieldT> FieldT times_four(FieldT x)
29 FieldT times_two = x + x;
30 return times_two + times_two;
34 * Helper function to find evaluation domain that will be used by the reduction
35 * for a given R1CS instance.
37 template<typename FieldT>
38 std::shared_ptr<libfqfft::evaluation_domain<FieldT>> r1cs_to_sap_get_domain(
39 const r1cs_constraint_system<FieldT> &cs)
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.
49 return libfqfft::get_evaluation_domain<FieldT>(
50 2 * cs.num_constraints() + 2 * cs.num_inputs() + 1);
54 * Instance map for the R1CS-to-SAP reduction.
56 template<typename FieldT>
57 sap_instance<FieldT> r1cs_to_sap_instance_map(
58 const r1cs_constraint_system<FieldT> &cs)
60 libff::enter_block("Call to r1cs_to_sap_instance_map");
62 const std::shared_ptr<libfqfft::evaluation_domain<FieldT>> domain =
63 r1cs_to_sap_get_domain(cs);
65 size_t sap_num_variables =
66 cs.num_variables() + cs.num_constraints() + cs.num_inputs();
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);
73 libff::enter_block("Compute polynomials A, C in Lagrange basis");
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
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())
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]
96 cs.constraints[i].a.terms[j].coeff;
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]
104 cs.constraints[i].b.terms[j].coeff;
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);
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();
117 * add and convert the extra constraints
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])
123 * note that i = 0 is a special case, where this constraint is expressible
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
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())
137 size_t extra_constr_offset = 2 * cs.num_constraints();
138 size_t extra_var_offset2 = cs.num_variables() + cs.num_constraints();
140 * NB: extra variables start at (extra_var_offset2 + 1), because i starts at
144 A_in_Lagrange_basis[0][extra_constr_offset] = FieldT::one();
145 C_in_Lagrange_basis[0][extra_constr_offset] = FieldT::one();
147 for (size_t i = 1; i <= cs.num_inputs(); ++i) {
148 A_in_Lagrange_basis[i][extra_constr_offset + 2 * i - 1] +=
150 A_in_Lagrange_basis[0][extra_constr_offset + 2 * i - 1] +=
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();
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();
163 libff::leave_block("Compute polynomials A, C in Lagrange basis");
165 libff::leave_block("Call to r1cs_to_sap_instance_map");
167 return sap_instance<FieldT>(
172 std::move(A_in_Lagrange_basis),
173 std::move(C_in_Lagrange_basis));
177 * Instance map for the R1CS-to-SAP reduction followed by evaluation
178 * of the resulting QAP instance.
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)
184 libff::enter_block("Call to r1cs_to_sap_instance_map_with_evaluation");
186 const std::shared_ptr<libfqfft::evaluation_domain<FieldT>> domain =
187 r1cs_to_sap_get_domain(cs);
189 size_t sap_num_variables =
190 cs.num_variables() + cs.num_constraints() + cs.num_inputs();
192 std::vector<FieldT> At, Ct, Ht;
194 At.resize(sap_num_variables + 1, FieldT::zero());
195 Ct.resize(sap_num_variables + 1, FieldT::zero());
196 Ht.reserve(domain->m + 1);
198 const FieldT Zt = domain->compute_vanishing_polynomial(t);
200 libff::enter_block("Compute evaluations of A, C, H at t");
201 const std::vector<FieldT> u = domain->evaluate_all_lagrange_polynomials(t);
203 * add and process all constraints as in r1cs_to_sap_instance_map
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;
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;
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);
226 Ct[extra_var_offset + i] += u[2 * i];
227 Ct[extra_var_offset + i] += u[2 * i + 1];
230 size_t extra_constr_offset = 2 * cs.num_constraints();
231 size_t extra_var_offset2 = cs.num_variables() + cs.num_constraints();
233 At[0] += u[extra_constr_offset];
234 Ct[0] += u[extra_constr_offset];
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];
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];
247 FieldT ti = FieldT::one();
248 for (size_t i = 0; i < domain->m + 1; ++i) {
252 libff::leave_block("Compute evaluations of A, C, H at t");
254 libff::leave_block("Call to r1cs_to_sap_instance_map_with_evaluation");
256 return sap_instance_evaluation<FieldT>(
269 * Witness map for the R1CS-to-SAP reduction.
271 * The witness map takes zero knowledge into account when d1, d2 are random.
273 * More precisely, compute the coefficients
276 * H(z) := (A(z)*A(z)-C(z))/Z(z)
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"
282 * m = number of variables of the SAP
283 * n = degree of the SAP
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))
294 * The code below is not as simple as the above high-level description due to
295 * some reshuffling to save space.
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,
305 libff::enter_block("Call to r1cs_to_sap_witness_map");
308 assert(cs.is_satisfied(primary_input, auxiliary_input));
310 const std::shared_ptr<libfqfft::evaluation_domain<FieldT>> domain =
311 r1cs_to_sap_get_domain(cs);
313 size_t sap_num_variables =
314 cs.num_variables() + cs.num_constraints() + cs.num_inputs();
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());
322 * we need to generate values of all the extra variables that we added
323 * during the reduction
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.
330 for (size_t i = 0; i < cs.num_constraints(); ++i) {
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
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);
342 for (size_t i = 1; i <= cs.num_inputs(); ++i) {
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
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);
353 libff::enter_block("Compute evaluation of polynomial A on set S");
354 std::vector<FieldT> aA(domain->m, FieldT::zero());
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);
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);
365 size_t extra_constr_offset = 2 * cs.num_constraints();
367 aA[extra_constr_offset] += FieldT::one();
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();
373 aA[extra_constr_offset + 2 * i] += full_variable_assignment[i - 1];
374 aA[extra_constr_offset + 2 * i] -= FieldT::one();
377 libff::leave_block("Compute evaluation of polynomial A on set S");
379 libff::enter_block("Compute coefficients of polynomial A");
381 libff::leave_block("Compute coefficients of polynomial A");
383 libff::enter_block("Compute ZK-patch");
384 std::vector<FieldT> coefficients_for_H(domain->m + 1, FieldT::zero());
386 #pragma omp parallel for
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]);
392 coefficients_for_H[0] -= d2;
393 domain->add_poly_Z(d1 * d1, coefficients_for_H);
394 libff::leave_block("Compute ZK-patch");
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");
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
404 #pragma omp parallel for
406 for (size_t i = 0; i < domain->m; ++i) {
407 H_tmp[i] = aA[i] * aA[i];
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) {
416 times_four(cs.constraints[i].c.evaluate(full_variable_assignment));
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];
422 size_t extra_var_offset2 = cs.num_variables() + cs.num_constraints();
423 aC[extra_constr_offset] += FieldT::one();
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]);
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];
435 libff::leave_block("Compute evaluation of polynomial C on set S");
437 libff::enter_block("Compute coefficients of polynomial C");
439 libff::leave_block("Compute coefficients of polynomial C");
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");
446 #pragma omp parallel for
448 for (size_t i = 0; i < domain->m; ++i) {
449 H_tmp[i] = (H_tmp[i] - aC[i]);
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");
456 libff::leave_block("Compute evaluation of polynomial H on set T");
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");
462 libff::enter_block("Compute sum of H and ZK-patch");
464 #pragma omp parallel for
466 for (size_t i = 0; i < domain->m; ++i) {
467 coefficients_for_H[i] += H_tmp[i];
469 libff::leave_block("Compute sum of H and ZK-patch");
471 libff::leave_block("Call to r1cs_to_sap_witness_map");
473 return sap_witness<FieldT>(
479 full_variable_assignment,
480 std::move(coefficients_for_H));
483 } // namespace libsnark
485 #endif // R1CS_TO_SAP_TCC_