Skip to content
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...
madars committed May 9, 2015
1 parent 650486e commit af725eeb82bd380a02f8be0d8931c8edf55afd03
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);

This comment has been minimized.

Copy link
@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?

This comment has been minimized.

Copy link
@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);

This comment has been minimized.

Copy link
@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.
You can’t perform that action at this time.