Permalink
Browse files

Fixes SNARK input consistency soundness bug identified by Bryan Parno.

A soundness bug in the input consistency check, found by Bryan Parno,
has been fixed. We thank Bryan for identifying the bug and for helping
us fix it.

Technical details: The bug affected the R1CS-to-QAP reduction, in that
some rank-1 constraint systems produced distinct, but not linearly
independent, QAP polynomials. The fix ensures independence. The cost
is an increase of QAP degree from cs.num_constraints()+1 to
cs.num_constraints()+cs.num_inputs()+1. Typically, cs.num_inputs() is
dominated by cs.num_constraints() so the degree increase is
negligible. Concretely, our experiments show that the increase is
typically less than 0.007% for all applications reported by us thus
far, including Zerocash [BCGGMTV14, S&P], SNARKs for TinyRAM [BCTV14,
USENIX Security], and Scalable Zero-Knowledge [BCTV14, CRYPTO].

See Remark 2.5 in the (revised) extended version of "Succinct
Non-Interactive Zero Knowledge for a von Neumann Architecture", at
http://eprint.iacr.org/2013/879 .
  • Loading branch information...
1 parent 650486e commit af725eeb82bd380a02f8be0d8931c8edf55afd03 @madars madars committed May 9, 2015
Showing with 36 additions and 29 deletions.
  1. +22 −25 src/reductions/r1cs_to_qap/r1cs_to_qap.tcc
  2. +14 −4 src/relations/arithmetic_programs/qap/tests/test_qap.cpp
@@ -18,8 +18,6 @@
#include "common/utils.hpp"
#include "algebra/evaluation_domain/evaluation_domain.hpp"
-#define R1CS_TO_QAP_ADDITIONAL_CONSTRAINTS 1 // +1 is for an additional constraint needed for the soundness of input consistency
-
namespace libsnark {
/**
@@ -39,41 +37,41 @@ qap_instance<FieldT> r1cs_to_qap_instance_map(const r1cs_constraint_system<Field
{
enter_block("Call to r1cs_to_qap_instance_map");
- const std::shared_ptr<evaluation_domain<FieldT> > domain = get_evaluation_domain<FieldT>(cs.num_constraints() + R1CS_TO_QAP_ADDITIONAL_CONSTRAINTS);
+ const std::shared_ptr<evaluation_domain<FieldT> > domain = get_evaluation_domain<FieldT>(cs.num_constraints() + cs.num_inputs() + 1);
std::vector<std::map<size_t, FieldT> > A_in_Lagrange_basis(cs.num_variables()+1);
std::vector<std::map<size_t, FieldT> > B_in_Lagrange_basis(cs.num_variables()+1);
std::vector<std::map<size_t, FieldT> > C_in_Lagrange_basis(cs.num_variables()+1);
enter_block("Compute polynomials A, B, C in Lagrange basis");
/**
- * add and process the constraint
- * (1 + \sum_{i=1}^{num_inputs} (i+1) * input_i) * 0 = 0
+ * add and process the constraints
+ * input_i * 0 = 0
* to ensure soundness of input consistency
*/
for (size_t i = 0; i <= cs.num_inputs(); ++i)
{
- A_in_Lagrange_basis[i][0] += FieldT(i+1);
+ A_in_Lagrange_basis[i][cs.num_constraints() + i] = FieldT::one();
}
/* process all other constraints */
for (size_t i = 0; i < cs.num_constraints(); ++i)
{
for (size_t j = 0; j < cs.constraints[i].a.terms.size(); ++j)
{
- A_in_Lagrange_basis[cs.constraints[i].a.terms[j].index][i+1] +=
+ A_in_Lagrange_basis[cs.constraints[i].a.terms[j].index][i] +=
cs.constraints[i].a.terms[j].coeff;
}
for (size_t j = 0; j < cs.constraints[i].b.terms.size(); ++j)
{
- B_in_Lagrange_basis[cs.constraints[i].b.terms[j].index][i+1] +=
+ B_in_Lagrange_basis[cs.constraints[i].b.terms[j].index][i] +=
cs.constraints[i].b.terms[j].coeff;
}
for (size_t j = 0; j < cs.constraints[i].c.terms.size(); ++j)
{
- C_in_Lagrange_basis[cs.constraints[i].c.terms[j].index][i+1] +=
- cs.constraints[i].c.terms[j].coeff;
+ C_in_Lagrange_basis[cs.constraints[i].c.terms[j].index][i] +=
+ cs.constraints[i].c.terms[j].coeff;
}
}
leave_block("Compute polynomials A, B, C in Lagrange basis");
@@ -109,7 +107,7 @@ qap_instance_evaluation<FieldT> r1cs_to_qap_instance_map_with_evaluation(const r
{
enter_block("Call to r1cs_to_qap_instance_map_with_evaluation");
- const std::shared_ptr<evaluation_domain<FieldT> > domain = get_evaluation_domain<FieldT>(cs.num_constraints() + R1CS_TO_QAP_ADDITIONAL_CONSTRAINTS);
+ const std::shared_ptr<evaluation_domain<FieldT> > domain = get_evaluation_domain<FieldT>(cs.num_constraints() + cs.num_inputs() + 1);
@daira
daira May 11, 2015

(Not related directly to the security bug...)
Can the code duplication between this function and r1cs_to_qap_instance_map above be reduced?

@arielgabizon
arielgabizon Dec 2, 2016

I think I understand the code change, but I can't parse the explanation, either here or in the paper - what does input_i*0 =0 mean?
Btw, you need that A0,..,An are not just linearly independent, but also have intersection {0} with the span of A_{n+1},..,A_m. I think your implementation achieves this.
About that see proof of 3.6 here:
https://github.com/zcash/mpc/blob/master/whitepaper.pdf

std::vector<FieldT> At, Bt, Ct, Ht;
@@ -123,33 +121,33 @@ qap_instance_evaluation<FieldT> r1cs_to_qap_instance_map_with_evaluation(const r
enter_block("Compute evaluations of A, B, C, H at t");
const std::vector<FieldT> u = domain->lagrange_coeffs(t);
/**
- * add and process the constraint
- * (1 + \sum_{i=1}^{num_inputs} (i+1) * input_i) * 0 = 0
+ * add and process the constraints
+ * input_i * 0 = 0
* to ensure soundness of input consistency
*/
for (size_t i = 0; i <= cs.num_inputs(); ++i)
{
- At[i] += u[0] * FieldT(i+1);
+ At[i] = u[cs.num_constraints() + i];
}
/* process all other constraints */
for (size_t i = 0; i < cs.num_constraints(); ++i)
{
for (size_t j = 0; j < cs.constraints[i].a.terms.size(); ++j)
{
At[cs.constraints[i].a.terms[j].index] +=
- u[i+1]*cs.constraints[i].a.terms[j].coeff;
+ u[i]*cs.constraints[i].a.terms[j].coeff;
}
for (size_t j = 0; j < cs.constraints[i].b.terms.size(); ++j)
{
Bt[cs.constraints[i].b.terms[j].index] +=
- u[i+1]*cs.constraints[i].b.terms[j].coeff;
+ u[i]*cs.constraints[i].b.terms[j].coeff;
}
for (size_t j = 0; j < cs.constraints[i].c.terms.size(); ++j)
{
Ct[cs.constraints[i].c.terms[j].index] +=
- u[i+1]*cs.constraints[i].c.terms[j].coeff;
+ u[i]*cs.constraints[i].c.terms[j].coeff;
}
}
@@ -217,25 +215,24 @@ qap_witness<FieldT> r1cs_to_qap_witness_map(const r1cs_constraint_system<FieldT>
/* sanity check */
assert(cs.is_satisfied(primary_input, auxiliary_input));
- const std::shared_ptr<evaluation_domain<FieldT> > domain = get_evaluation_domain<FieldT>(cs.num_constraints() + R1CS_TO_QAP_ADDITIONAL_CONSTRAINTS);
+ const std::shared_ptr<evaluation_domain<FieldT> > domain = get_evaluation_domain<FieldT>(cs.num_constraints() + cs.num_inputs() + 1);
@daira
daira May 11, 2015

more potential to reduce code duplication here

r1cs_variable_assignment<FieldT> full_variable_assignment = primary_input;
full_variable_assignment.insert(full_variable_assignment.end(), auxiliary_input.begin(), auxiliary_input.end());
enter_block("Compute evaluation of polynomials A, B on set S");
std::vector<FieldT> aA(domain->m, FieldT::zero()), aB(domain->m, FieldT::zero());
- /* account for the additional constraint (1 + \sum_{i=1}^{num_inputs} (i+1) * input_i) * 0 = 0 */
- aA[0] = FieldT::one();
- for (size_t i = 0; i < cs.num_inputs(); ++i)
+ /* account for the additional constraints input_i * 0 = 0 */
+ for (size_t i = 0; i <= cs.num_inputs(); ++i)
{
- aA[0] += full_variable_assignment[i] * FieldT(i+2);
+ aA[i+cs.num_constraints()] = (i > 0 ? full_variable_assignment[i-1] : FieldT::one());
}
/* account for all other constraints */
for (size_t i = 0; i < cs.num_constraints(); ++i)
{
- aA[i+1] += cs.constraints[i].a.evaluate(full_variable_assignment);
- aB[i+1] += cs.constraints[i].b.evaluate(full_variable_assignment);
+ aA[i] += cs.constraints[i].a.evaluate(full_variable_assignment);
+ aB[i] += cs.constraints[i].b.evaluate(full_variable_assignment);
}
leave_block("Compute evaluation of polynomials A, B on set S");
@@ -284,7 +281,7 @@ qap_witness<FieldT> r1cs_to_qap_witness_map(const r1cs_constraint_system<FieldT>
std::vector<FieldT> aC(domain->m, FieldT::zero());
for (size_t i = 0; i < cs.num_constraints(); ++i)
{
- aC[i+1] += cs.constraints[i].c.evaluate(full_variable_assignment);
+ aC[i] += cs.constraints[i].c.evaluate(full_variable_assignment);
}
leave_block("Compute evaluation of polynomial C on set S");
@@ -20,23 +20,33 @@
using namespace libsnark;
template<typename FieldT>
-void test_qap(const size_t num_constraints, const size_t num_inputs, const bool binary_input)
+void test_qap(const size_t qap_degree, const size_t num_inputs, const bool binary_input)
{
+ /*
+ We construct an instance where the QAP degree is qap_degree.
+ So we generate an instance of R1CS where the number of constraints qap_degree - num_inputs - 1.
+ See the transformation from R1CS to QAP for why this is the case.
+ So we need that qap_degree >= num_inputs + 1.
+ */
+ assert(num_inputs + 1 <= qap_degree);
enter_block("Call to test_qap");
- print_indent(); printf("* Number of constraints: %zu\n", num_constraints);
+ const size_t num_constraints = qap_degree - num_inputs - 1;
+
+ print_indent(); printf("* QAP degree: %zu\n", qap_degree);
print_indent(); printf("* Number of inputs: %zu\n", num_inputs);
+ print_indent(); printf("* Number of R1CS constraints: %zu\n", num_constraints);
print_indent(); printf("* Input type: %s\n", binary_input ? "binary" : "field");
enter_block("Generate constraint system and assignment");
r1cs_example<FieldT> example;
if (binary_input)
{
- example = generate_r1cs_example_with_binary_input<FieldT>(num_constraints - R1CS_TO_QAP_ADDITIONAL_CONSTRAINTS, num_inputs);
+ example = generate_r1cs_example_with_binary_input<FieldT>(num_constraints, num_inputs);
}
else
{
- example = generate_r1cs_example_with_field_input<FieldT>(num_constraints - R1CS_TO_QAP_ADDITIONAL_CONSTRAINTS, num_inputs);
+ example = generate_r1cs_example_with_field_input<FieldT>(num_constraints, num_inputs);
}
leave_block("Generate constraint system and assignment");

0 comments on commit af725ee

Please sign in to comment.