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>
This commit is contained in:
Родитель
096c07910a
Коммит
7bbc366e5d
|
@ -14,7 +14,7 @@ jobs:
|
|||
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 @@ Here is another example to use the NIZK variant of the Spartan proof system:
|
|||
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 @@ Here is another example to use the NIZK variant of the Spartan proof system:
|
|||
|
||||
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 @@ Finally, we provide an example that specifies a custom R1CS instance instead of
|
|||
|
||||
// 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 @@ fn nizk_prove_benchmark(c: &mut Criterion) {
|
|||
|
||||
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 @@ fn nizk_verify_benchmark(c: &mut Criterion) {
|
|||
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 @@ pub fn main() {
|
|||
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 @@ impl DensePolynomial {
|
|||
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()
|
||||
|
|
256
src/lib.rs
256
src/lib.rs
|
@ -10,10 +10,12 @@ 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;
|
||||
mod errors;
|
||||
|
@ -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 @@ impl 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 @@ impl Instance {
|
|||
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 @@ impl Instance {
|
|||
|
||||
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 @@ impl Instance {
|
|||
}
|
||||
|
||||
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 @@ impl Instance {
|
|||
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 @@ pub struct SNARKGens {
|
|||
|
||||
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 @@ impl SNARK {
|
|||
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 @@ pub struct NIZKGens {
|
|||
|
||||
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 @@ impl NIZK {
|
|||
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,
|
||||
|
@ -544,4 +655,85 @@ mod tests {
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче