Arbitrary number of variables and contraints (#34)
* This commit makes adding an arbitrary number of variables and inputs possible and removes the
implementation leaking to the interface for
num_inps + 1 <= num_vars, num_vars: a power of 2, num_cons: a power of 2, but not 1.
1. When creating a new R1CS Instance throught the public interface,
it is required # constraints and # of vars be a power of 2. I remove
that requirement by padding with dummy constraints and vars until the nearest
power of 2.
2. The sumcheck protocol in src/sumcheck.rs does not work for 1 constraint, even
though 1 is a power of 2. I have to pad to a minimum of two constraints.
3. Added a test in src/r1csproof.rs called test_padded_constraints.
* Move test to src/lib.rs
* Remove padding metadata
* remove unused use
* Simplify padding to power of 2
* run cargo fmt
* Fix indexing bug
* Rayon is optional, depending on 'multicore' feature
* Update rust toolchain
* cargo fmt
* cleaner to track num_vars_padded and num_cons_padded
* cleanup
* further cleanup
* Cleanup & comments
* small fixes
* adjust code for padding constraints
* fix a bug with pad call
* add comment about num_nz_entries
* extend padding to NIZK methods
extend padding to NIZK methods
Co-authored-by: Lef Ioannidis <elefthei@seas.upenn.edu>
Co-authored-by: Srinath Setty <srinath@microsoft.com>
@@ -14,7 +14,7 @@
steps: - uses: actions/checkout@v2 - name: Install - run: rustup default nightly-2021-01-03 + run: rustup default nightly-2021-01-31 - name: Build run: cargo build --verbose - name: Run tests
@@ -17,7 +17,7 @@ rand = "0.7.3"
digest = "0.8.1" sha3 = "0.8.2" byteorder = "1.3.4" -rayon = "1.3.0" +rayon = { version = "1.3.0", optional = true } serde = { version = "1.0.106", features = ["derive"] } bincode = "1.2.1" subtle = { version = "^2.2.3", default-features = false }@@ -52,5 +52,5 @@ name = "nizk"
harness = false [features] -multicore = [] +multicore = ["rayon"] profile = []
@@ -82,7 +82,7 @@ let num_cons = 1024;
let num_inputs = 10; // produce public parameters - let gens = NIZKGens::new(num_cons, num_vars); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); // ask the library to produce a synthentic R1CS instance let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);@@ -102,6 +102,7 @@ ```
Finally, we provide an example that specifies a custom R1CS instance instead of using a synthetic instance ```rust +#![allow(non_snake_case)] # extern crate curve25519_dalek; # extern crate libspartan; # extern crate merlin;@@ -163,9 +164,9 @@ // Z4 * 1 - 0 = 0
// parameters of the R1CS instance rounded to the nearest power of two let num_cons = 4; - let num_vars = 8; + let num_vars = 5; let num_inputs = 2; - let num_non_zero_entries = 8; + let num_non_zero_entries = 5; // We will encode the above constraints into three matrices, where // the coefficients in the matrix are in the little-endian byte order
@@ -24,7 +24,7 @@ let num_inputs = 10;
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs); - let gens = NIZKGens::new(num_cons, num_vars); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); let name = format!("NIZK_prove_{}", num_vars); group.bench_function(&name, move |b| {@@ -54,7 +54,7 @@ let num_cons = num_vars;
let num_inputs = 10; let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs); - let gens = NIZKGens::new(num_cons, num_vars); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); // produce a proof of satisfiability let mut prover_transcript = Transcript::new(b"example");
@@ -27,7 +27,7 @@ // produce a synthetic R1CSInstance
let (inst, vars, inputs) = Instance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs); // produce public generators - let gens = NIZKGens::new(num_cons, num_vars); + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); // produce a proof of satisfiability let mut prover_transcript = Transcript::new(b"nizk_example");
@@ -149,7 +149,7 @@ let R_size = self.Z.len() / L_size;
assert_eq!(L_size * R_size, self.Z.len()); let C = (0..L_size) .into_par_iter() - .map(|&i| { + .map(|i| { self.Z[R_size * i..R_size * (i + 1)] .commit(&blinds[i], gens) .compress()
@@ -10,9 +10,11 @@ extern crate curve25519_dalek;
extern crate digest; extern crate merlin; extern crate rand; -extern crate rayon; extern crate sha3; extern crate test; + +#[cfg(feature = "multicore")] +extern crate rayon; mod commitments; mod dense_mlpoly;@@ -31,6 +33,7 @@ mod timer;
mod transcript; mod unipoly; +use core::cmp::max; use errors::{ProofVerifyError, R1CSError}; use merlin::Transcript; use r1csinstance::{@@ -86,6 +89,22 @@ Ok(Assignment {
assignment: assignment_scalar.unwrap(), }) } + + /// pads Assignment to the specified length + fn pad(&self, len: usize) -> VarsAssignment { + // check that the new length is higher than current length + assert!(len > self.assignment.len()); + + let padded_assignment = { + let mut padded_assignment = self.assignment.clone(); + padded_assignment.extend(vec![Scalar::zero(); len - self.assignment.len()]); + padded_assignment + }; + + VarsAssignment { + assignment: padded_assignment, + } + } } /// `VarsAssignment` holds an assignment of values to variables in an `Instance`@@ -109,20 +128,37 @@ A: &Vec<(usize, usize, [u8; 32])>,
B: &Vec<(usize, usize, [u8; 32])>, C: &Vec<(usize, usize, [u8; 32])>, ) -> Result<Instance, R1CSError> { - // check that num_cons is power of 2 - if num_cons.next_power_of_two() != num_cons { - return Err(R1CSError::NonPowerOfTwoCons); - } + let (num_vars_padded, num_cons_padded) = { + let num_vars_padded = { + let mut num_vars_padded = num_vars; - // check that the number of variables is a power of 2 - if num_vars.next_power_of_two() != num_vars { - return Err(R1CSError::NonPowerOfTwoVars); - } + // ensure that num_inputs + 1 <= num_vars + num_vars_padded = max(num_vars_padded, num_inputs + 1); - // check that num_inputs + 1 <= num_vars - if num_inputs >= num_vars { - return Err(R1CSError::InvalidNumberOfInputs); - } + // ensure that num_vars_padded a power of two + if num_vars_padded.next_power_of_two() != num_vars_padded { + num_vars_padded = num_vars_padded.next_power_of_two(); + } + num_vars_padded + }; + + let num_cons_padded = { + let mut num_cons_padded = num_cons; + + // ensure that num_cons_padded is at least 2 + if num_cons_padded == 0 || num_cons_padded == 1 { + num_cons_padded = 2; + } + + // ensure that num_cons_padded is power of 2 + if num_cons.next_power_of_two() != num_cons { + num_cons_padded = num_cons.next_power_of_two(); + } + num_cons_padded + }; + + (num_vars_padded, num_cons_padded) + }; let bytes_to_scalar = |tups: &Vec<(usize, usize, [u8; 32])>| -> Result<Vec<(usize, usize, Scalar)>, R1CSError> {@@ -142,11 +178,26 @@ }
let val = Scalar::from_bytes(&val_bytes); if val.is_some().unwrap_u8() == 1 { - mat.push((row, col, val.unwrap())); + // if col >= num_vars, it means that it is referencing a 1 or input in the satisfying + // assignment + if col >= num_vars { + mat.push((row, col + num_vars_padded - num_vars, val.unwrap())); + } else { + mat.push((row, col, val.unwrap())); + } } else { return Err(R1CSError::InvalidScalar); } } + + // pad with additional constraints up until num_cons_padded if the original constraints were 0 or 1 + // we do not need to pad otherwise because the dummy constraints are implicit in the sum-check protocol + if num_cons == 0 || num_cons == 1 { + for i in tups.len()..num_cons_padded { + mat.push((i, num_vars, Scalar::zero())); + } + } + Ok(mat) };@@ -166,8 +217,8 @@ return Err(C_scalar.err().unwrap());
} let inst = R1CSInstance::new( - num_cons, - num_vars, + num_cons_padded, + num_vars_padded, num_inputs, &A_scalar.unwrap(), &B_scalar.unwrap(),@@ -183,15 +234,31 @@ &self,
vars: &VarsAssignment, inputs: &InputsAssignment, ) -> Result<bool, R1CSError> { - if vars.assignment.len() != self.inst.get_num_vars() { - return Err(R1CSError::InvalidNumberOfVars); + if vars.assignment.len() > self.inst.get_num_vars() { + return Err(R1CSError::InvalidNumberOfInputs); } if inputs.assignment.len() != self.inst.get_num_inputs() { return Err(R1CSError::InvalidNumberOfInputs); } - Ok(self.inst.is_sat(&vars.assignment, &inputs.assignment)) + // we might need to pad variables + let padded_vars = { + let num_padded_vars = self.inst.get_num_vars(); + let num_vars = vars.assignment.len(); + let padded_vars = if num_padded_vars > num_vars { + vars.pad(num_padded_vars) + } else { + vars.clone() + }; + padded_vars + }; + + Ok( + self + .inst + .is_sat(&padded_vars.assignment, &inputs.assignment), + ) } /// Constructs a new synthetic R1CS `Instance` and an associated satisfying assignment@@ -217,12 +284,21 @@ }
impl SNARKGens { /// Constructs a new `SNARKGens` given the size of the R1CS statement + /// `num_nz_entries` specifies the maximum number of non-zero entries in any of the three R1CS matrices pub fn new(num_cons: usize, num_vars: usize, num_inputs: usize, num_nz_entries: usize) -> Self { - let gens_r1cs_sat = R1CSGens::new(b"gens_r1cs_sat", num_cons, num_vars); + let num_vars_padded = { + let mut num_vars_padded = max(num_vars, num_inputs + 1); + if num_vars_padded != num_vars_padded.next_power_of_two() { + num_vars_padded = num_vars_padded.next_power_of_two(); + } + num_vars_padded + }; + + let gens_r1cs_sat = R1CSGens::new(b"gens_r1cs_sat", num_cons, num_vars_padded); let gens_r1cs_eval = R1CSCommitmentGens::new( b"gens_r1cs_eval", num_cons, - num_vars, + num_vars_padded, num_inputs, num_nz_entries, );@@ -276,14 +352,29 @@ // to aid the prover produce its randomness
let mut random_tape = RandomTape::new(b"proof"); transcript.append_protocol_name(SNARK::protocol_name()); let (r1cs_sat_proof, rx, ry) = { - let (proof, rx, ry) = R1CSProof::prove( - &inst.inst, - vars.assignment, - &inputs.assignment, - &gens.gens_r1cs_sat, - transcript, - &mut random_tape, - ); + let (proof, rx, ry) = { + // we might need to pad variables + let padded_vars = { + let num_padded_vars = inst.inst.get_num_vars(); + let num_vars = vars.assignment.len(); + let padded_vars = if num_padded_vars > num_vars { + vars.pad(num_padded_vars) + } else { + vars + }; + padded_vars + }; + + R1CSProof::prove( + &inst.inst, + padded_vars.assignment, + &inputs.assignment, + &gens.gens_r1cs_sat, + transcript, + &mut random_tape, + ) + }; + let proof_encoded: Vec<u8> = bincode::serialize(&proof).unwrap(); Timer::print(&format!("len_r1cs_sat_proof {:?}", proof_encoded.len()));@@ -378,8 +469,16 @@ }
impl NIZKGens { /// Constructs a new `NIZKGens` given the size of the R1CS statement - pub fn new(num_cons: usize, num_vars: usize) -> Self { - let gens_r1cs_sat = R1CSGens::new(b"gens_r1cs_sat", num_cons, num_vars); + pub fn new(num_cons: usize, num_vars: usize, num_inputs: usize) -> Self { + let num_vars_padded = { + let mut num_vars_padded = max(num_vars, num_inputs + 1); + if num_vars_padded != num_vars_padded.next_power_of_two() { + num_vars_padded = num_vars_padded.next_power_of_two(); + } + num_vars_padded + }; + + let gens_r1cs_sat = R1CSGens::new(b"gens_r1cs_sat", num_cons, num_vars_padded); NIZKGens { gens_r1cs_sat } } }@@ -410,9 +509,21 @@ // to aid the prover produce its randomness
let mut random_tape = RandomTape::new(b"proof"); transcript.append_protocol_name(NIZK::protocol_name()); let (r1cs_sat_proof, rx, ry) = { + // we might need to pad variables + let padded_vars = { + let num_padded_vars = inst.inst.get_num_vars(); + let num_vars = vars.assignment.len(); + let padded_vars = if num_padded_vars > num_vars { + vars.pad(num_padded_vars) + } else { + vars + }; + padded_vars + }; + let (proof, rx, ry) = R1CSProof::prove( &inst.inst, - vars.assignment, + padded_vars.assignment, &input.assignment, &gens.gens_r1cs_sat, transcript,@@ -543,5 +654,86 @@
let inst = Instance::new(num_cons, num_vars, num_inputs, &A, &B, &C); assert_eq!(inst.is_err(), true); assert_eq!(inst.err(), Some(R1CSError::InvalidScalar)); + } + + #[test] + fn test_padded_constraints() { + // parameters of the R1CS instance + let num_cons = 1; + let num_vars = 0; + let num_inputs = 3; + let num_non_zero_entries = 3; + + // We will encode the above constraints into three matrices, where + // the coefficients in the matrix are in the little-endian byte order + let mut A: Vec<(usize, usize, [u8; 32])> = Vec::new(); + let mut B: Vec<(usize, usize, [u8; 32])> = Vec::new(); + let mut C: Vec<(usize, usize, [u8; 32])> = Vec::new(); + + // Create a^2 + b + 13 + A.push((0, num_vars + 2, Scalar::one().to_bytes())); // 1*a + B.push((0, num_vars + 2, Scalar::one().to_bytes())); // 1*a + C.push((0, num_vars + 1, Scalar::one().to_bytes())); // 1*z + C.push((0, num_vars, (-Scalar::from(13u64)).to_bytes())); // -13*1 + C.push((0, num_vars + 3, (-Scalar::one()).to_bytes())); // -1*b + + // Var Assignments (Z_0 = 16 is the only output) + let vars = vec![Scalar::zero().to_bytes(); num_vars]; + + // create an InputsAssignment (a = 1, b = 2) + let mut inputs = vec![Scalar::zero().to_bytes(); num_inputs]; + inputs[0] = Scalar::from(16u64).to_bytes(); + inputs[1] = Scalar::from(1u64).to_bytes(); + inputs[2] = Scalar::from(2u64).to_bytes(); + + let assignment_inputs = InputsAssignment::new(&inputs).unwrap(); + let assignment_vars = VarsAssignment::new(&vars).unwrap(); + + // Check if instance is satisfiable + let inst = Instance::new(num_cons, num_vars, num_inputs, &A, &B, &C).unwrap(); + let res = inst.is_sat(&assignment_vars, &assignment_inputs); + assert_eq!(res.unwrap(), true, "should be satisfied"); + + // SNARK public params + let gens = SNARKGens::new(num_cons, num_vars, num_inputs, num_non_zero_entries); + + // create a commitment to the R1CS instance + let (comm, decomm) = SNARK::encode(&inst, &gens); + + // produce a SNARK + let mut prover_transcript = Transcript::new(b"snark_example"); + let proof = SNARK::prove( + &inst, + &decomm, + assignment_vars.clone(), + &assignment_inputs, + &gens, + &mut prover_transcript, + ); + + // verify the SNARK + let mut verifier_transcript = Transcript::new(b"snark_example"); + assert!(proof + .verify(&comm, &assignment_inputs, &mut verifier_transcript, &gens) + .is_ok()); + + // NIZK public params + let gens = NIZKGens::new(num_cons, num_vars, num_inputs); + + // produce a NIZK + let mut prover_transcript = Transcript::new(b"nizk_example"); + let proof = NIZK::prove( + &inst, + assignment_vars, + &assignment_inputs, + &gens, + &mut prover_transcript, + ); + + // verify the NIZK + let mut verifier_transcript = Transcript::new(b"nizk_example"); + assert!(proof + .verify(&inst, &assignment_inputs, &mut verifier_transcript, &gens) + .is_ok()); } }