MNT4/6 curves and recursive SNARKs (#150)

* Add mnt6_753 curve
Generalize mnt6 curve model

* Add mnt4 curves

* Use resampled generators

* Calculate correct G2 cofactors

* Add fields to r1cs-std

* Add pairings

* Improve reusing of Fq/Fr among MNT curves

* Add instantiations of curves
Fix Fp6_2over3
Rebase code to current master

* Add test for recursive NIZK proof verification

* Address comments in PR

* Improve test case and port to GM17
Also fix a minor bug in to_field_vec
This commit is contained in:
Pascal Berrang
2020-04-04 19:50:46 +02:00
committed by GitHub
parent 81f3105a91
commit 8631f883c4
37 changed files with 5522 additions and 8 deletions

View File

@@ -46,6 +46,6 @@ std = ["r1cs", "algebra-core/std", "r1cs-core/std", "r1cs-std/std"]
parallel = ["std", "rayon", "gm17/parallel", "groth16/parallel", "ff-fft/parallel"]
[dev-dependencies]
algebra = { path = "../algebra", default-features = false, features = [ "jubjub", "bls12_377" ] }
r1cs-std = { path = "../r1cs-std", default-features = false, features = [ "jubjub", "bls12_377" ] }
algebra = { path = "../algebra", default-features = false, features = [ "jubjub", "bls12_377", "mnt4_298", "mnt6_298" ] }
r1cs-std = { path = "../r1cs-std", default-features = false, features = [ "jubjub", "bls12_377", "mnt4_298", "mnt6_298" ] }
rand_xorshift = { version = "0.2" }

View File

@@ -532,3 +532,266 @@ mod test {
}
}
}
#[cfg(test)]
mod test_recursive {
use gm17::*;
use r1cs_core::{ConstraintSynthesizer, ConstraintSystem, SynthesisError};
use super::*;
use algebra::{
fields::FpParameters,
mnt4_298::{Fq as MNT4Fq, FqParameters as MNT4FqParameters, Fr as MNT4Fr, MNT4_298},
mnt6_298::{Fq as MNT6Fq, FqParameters as MNT6FqParameters, Fr as MNT6Fr, MNT6_298},
test_rng, BigInteger, PrimeField,
};
use r1cs_std::{
fields::fp::FpGadget, mnt4_298::PairingGadget as MNT4_298PairingGadget,
mnt6_298::PairingGadget as MNT6_298PairingGadget,
test_constraint_system::TestConstraintSystem, uint8::UInt8,
};
use rand::Rng;
type TestProofSystem1 = Gm17<MNT6_298, Bench<MNT4Fq>, MNT6Fr>;
type TestVerifierGadget1 = Gm17VerifierGadget<MNT6_298, MNT6Fq, MNT6_298PairingGadget>;
type TestProofGadget1 = ProofGadget<MNT6_298, MNT6Fq, MNT6_298PairingGadget>;
type TestVkGadget1 = VerifyingKeyGadget<MNT6_298, MNT6Fq, MNT6_298PairingGadget>;
type TestProofSystem2 = Gm17<MNT4_298, Wrapper, MNT4Fr>;
type TestVerifierGadget2 = Gm17VerifierGadget<MNT4_298, MNT4Fq, MNT4_298PairingGadget>;
type TestProofGadget2 = ProofGadget<MNT4_298, MNT4Fq, MNT4_298PairingGadget>;
type TestVkGadget2 = VerifyingKeyGadget<MNT4_298, MNT4Fq, MNT4_298PairingGadget>;
#[derive(Clone)]
struct Bench<F: Field> {
inputs: Vec<Option<F>>,
num_constraints: usize,
}
impl<F: Field> ConstraintSynthesizer<F> for Bench<F> {
fn generate_constraints<CS: ConstraintSystem<F>>(
self,
cs: &mut CS,
) -> Result<(), SynthesisError> {
assert!(self.inputs.len() >= 2);
assert!(self.num_constraints >= self.inputs.len());
let mut variables: Vec<_> = Vec::with_capacity(self.inputs.len());
for (i, input) in self.inputs.into_iter().enumerate() {
let input_var = cs.alloc_input(
|| format!("Input {}", i),
|| input.ok_or(SynthesisError::AssignmentMissing),
)?;
variables.push((input, input_var));
}
for i in 0..self.num_constraints {
let new_entry = {
let (input_1_val, input_1_var) = variables[i];
let (input_2_val, input_2_var) = variables[i + 1];
let result_val = input_1_val
.and_then(|input_1| input_2_val.map(|input_2| input_1 * &input_2));
let result_var = cs.alloc(
|| format!("Result {}", i),
|| result_val.ok_or(SynthesisError::AssignmentMissing),
)?;
cs.enforce(
|| format!("Enforce constraint {}", i),
|lc| lc + input_1_var,
|lc| lc + input_2_var,
|lc| lc + result_var,
);
(result_val, result_var)
};
variables.push(new_entry);
}
Ok(())
}
}
struct Wrapper {
inputs: Vec<Option<MNT4Fq>>,
params: Parameters<MNT6_298>,
proof: Proof<MNT6_298>,
}
impl ConstraintSynthesizer<MNT6Fq> for Wrapper {
fn generate_constraints<CS: ConstraintSystem<MNT6Fq>>(
self,
cs: &mut CS,
) -> Result<(), SynthesisError> {
let params = self.params;
let proof = self.proof;
let inputs: Vec<_> = self
.inputs
.into_iter()
.map(|input| input.unwrap())
.collect();
let input_gadgets;
{
let mut cs = cs.ns(|| "Allocate Input");
// Chain all input values in one large byte array.
let input_bytes = inputs
.clone()
.into_iter()
.flat_map(|input| {
input
.into_repr()
.as_ref()
.iter()
.flat_map(|l| l.to_le_bytes().to_vec())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
// Allocate this byte array as input packed into field elements.
let input_bytes = UInt8::alloc_input_vec(cs.ns(|| "Input"), &input_bytes[..])?;
// 40 byte
let element_size = <MNT4FqParameters as FpParameters>::BigInt::NUM_LIMBS * 8;
input_gadgets = input_bytes
.chunks(element_size)
.map(|chunk| {
chunk
.iter()
.flat_map(|byte| byte.into_bits_le())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
}
let vk_gadget = TestVkGadget1::alloc(cs.ns(|| "Vk"), || Ok(&params.vk))?;
let proof_gadget =
TestProofGadget1::alloc(cs.ns(|| "Proof"), || Ok(proof.clone())).unwrap();
<TestVerifierGadget1 as NIZKVerifierGadget<TestProofSystem1, MNT6Fq>>::check_verify(
cs.ns(|| "Verify"),
&vk_gadget,
input_gadgets.iter(),
&proof_gadget,
)?;
Ok(())
}
}
#[test]
fn gm17_recursive_verifier_test() {
let num_inputs = 100;
let num_constraints = num_inputs;
let rng = &mut test_rng();
let mut inputs: Vec<Option<MNT4Fq>> = Vec::with_capacity(num_inputs);
for _ in 0..num_inputs {
inputs.push(Some(rng.gen()));
}
// Generate inner params and proof.
let inner_params = {
let c = Bench::<MNT4Fq> {
inputs: vec![None; num_inputs],
num_constraints,
};
generate_random_parameters(c, rng).unwrap()
};
let inner_proof = {
// Create an instance of our circuit (with the
// witness)
let c = Bench {
inputs: inputs.clone(),
num_constraints,
};
// Create a groth16 proof with our parameters.
create_random_proof(c, &inner_params, rng).unwrap()
};
// Generate outer params and proof.
let params = {
let c = Wrapper {
inputs: inputs.clone(),
params: inner_params.clone(),
proof: inner_proof.clone(),
};
generate_random_parameters(c, rng).unwrap()
};
{
let proof = {
// Create an instance of our circuit (with the
// witness)
let c = Wrapper {
inputs: inputs.clone(),
params: inner_params.clone(),
proof: inner_proof.clone(),
};
// Create a groth16 proof with our parameters.
create_random_proof(c, &params, rng).unwrap()
};
let mut cs = TestConstraintSystem::<MNT4Fq>::new();
let inputs: Vec<_> = inputs.into_iter().map(|input| input.unwrap()).collect();
let mut input_gadgets = Vec::new();
{
let bigint_size = <MNT4FqParameters as FpParameters>::BigInt::NUM_LIMBS * 64;
let mut input_bits = Vec::new();
let mut cs = cs.ns(|| "Allocate Input");
for (i, input) in inputs.into_iter().enumerate() {
let input_gadget =
FpGadget::alloc_input(cs.ns(|| format!("Input {}", i)), || Ok(input))
.unwrap();
let mut fp_bits = input_gadget
.to_bits(cs.ns(|| format!("To bits {}", i)))
.unwrap();
// FpGadget::to_bits outputs a big-endian binary representation of
// fe_gadget's value, so we have to reverse it to get the little-endian
// form.
fp_bits.reverse();
// Use 320 bits per element.
for _ in fp_bits.len()..bigint_size {
fp_bits.push(Boolean::constant(false));
}
input_bits.extend_from_slice(&fp_bits);
}
// Pack input bits into field elements of the underlying circuit.
let max_size = 8 * (<MNT6FqParameters as FpParameters>::CAPACITY / 8) as usize;
let max_size = max_size as usize;
let bigint_size = <MNT6FqParameters as FpParameters>::BigInt::NUM_LIMBS * 64;
for chunk in input_bits.chunks(max_size) {
let mut chunk = chunk.to_vec();
let len = chunk.len();
for _ in len..bigint_size {
chunk.push(Boolean::constant(false));
}
input_gadgets.push(chunk);
}
// assert!(!verify_proof(&pvk, &proof, &[a]).unwrap());
}
let vk_gadget = TestVkGadget2::alloc_input(cs.ns(|| "Vk"), || Ok(&params.vk)).unwrap();
let proof_gadget =
TestProofGadget2::alloc(cs.ns(|| "Proof"), || Ok(proof.clone())).unwrap();
println!("Time to verify!\n\n\n\n");
<TestVerifierGadget2 as NIZKVerifierGadget<TestProofSystem2, MNT4Fq>>::check_verify(
cs.ns(|| "Verify"),
&vk_gadget,
input_gadgets.iter(),
&proof_gadget,
)
.unwrap();
if !cs.is_satisfied() {
println!("=========================================================");
println!("Unsatisfied constraints:");
println!("{:?}", cs.which_is_unsatisfied().unwrap());
println!("=========================================================");
}
// cs.print_named_objects();
assert!(cs.is_satisfied());
}
}
}

View File

@@ -479,3 +479,266 @@ mod test {
}
}
}
#[cfg(test)]
mod test_recursive {
use groth16::*;
use r1cs_core::{ConstraintSynthesizer, ConstraintSystem, SynthesisError};
use super::*;
use algebra::{
fields::FpParameters,
mnt4_298::{Fq as MNT4Fq, FqParameters as MNT4FqParameters, Fr as MNT4Fr, MNT4_298},
mnt6_298::{Fq as MNT6Fq, FqParameters as MNT6FqParameters, Fr as MNT6Fr, MNT6_298},
test_rng, BigInteger, PrimeField,
};
use r1cs_std::{
fields::fp::FpGadget, mnt4_298::PairingGadget as MNT4_298PairingGadget,
mnt6_298::PairingGadget as MNT6_298PairingGadget,
test_constraint_system::TestConstraintSystem, uint8::UInt8,
};
use rand::Rng;
type TestProofSystem1 = Groth16<MNT6_298, Bench<MNT4Fq>, MNT6Fr>;
type TestVerifierGadget1 = Groth16VerifierGadget<MNT6_298, MNT6Fq, MNT6_298PairingGadget>;
type TestProofGadget1 = ProofGadget<MNT6_298, MNT6Fq, MNT6_298PairingGadget>;
type TestVkGadget1 = VerifyingKeyGadget<MNT6_298, MNT6Fq, MNT6_298PairingGadget>;
type TestProofSystem2 = Groth16<MNT4_298, Wrapper, MNT4Fr>;
type TestVerifierGadget2 = Groth16VerifierGadget<MNT4_298, MNT4Fq, MNT4_298PairingGadget>;
type TestProofGadget2 = ProofGadget<MNT4_298, MNT4Fq, MNT4_298PairingGadget>;
type TestVkGadget2 = VerifyingKeyGadget<MNT4_298, MNT4Fq, MNT4_298PairingGadget>;
#[derive(Clone)]
struct Bench<F: Field> {
inputs: Vec<Option<F>>,
num_constraints: usize,
}
impl<F: Field> ConstraintSynthesizer<F> for Bench<F> {
fn generate_constraints<CS: ConstraintSystem<F>>(
self,
cs: &mut CS,
) -> Result<(), SynthesisError> {
assert!(self.inputs.len() >= 2);
assert!(self.num_constraints >= self.inputs.len());
let mut variables: Vec<_> = Vec::with_capacity(self.inputs.len());
for (i, input) in self.inputs.into_iter().enumerate() {
let input_var = cs.alloc_input(
|| format!("Input {}", i),
|| input.ok_or(SynthesisError::AssignmentMissing),
)?;
variables.push((input, input_var));
}
for i in 0..self.num_constraints {
let new_entry = {
let (input_1_val, input_1_var) = variables[i];
let (input_2_val, input_2_var) = variables[i + 1];
let result_val = input_1_val
.and_then(|input_1| input_2_val.map(|input_2| input_1 * &input_2));
let result_var = cs.alloc(
|| format!("Result {}", i),
|| result_val.ok_or(SynthesisError::AssignmentMissing),
)?;
cs.enforce(
|| format!("Enforce constraint {}", i),
|lc| lc + input_1_var,
|lc| lc + input_2_var,
|lc| lc + result_var,
);
(result_val, result_var)
};
variables.push(new_entry);
}
Ok(())
}
}
struct Wrapper {
inputs: Vec<Option<MNT4Fq>>,
params: Parameters<MNT6_298>,
proof: Proof<MNT6_298>,
}
impl ConstraintSynthesizer<MNT6Fq> for Wrapper {
fn generate_constraints<CS: ConstraintSystem<MNT6Fq>>(
self,
cs: &mut CS,
) -> Result<(), SynthesisError> {
let params = self.params;
let proof = self.proof;
let inputs: Vec<_> = self
.inputs
.into_iter()
.map(|input| input.unwrap())
.collect();
let input_gadgets;
{
let mut cs = cs.ns(|| "Allocate Input");
// Chain all input values in one large byte array.
let input_bytes = inputs
.clone()
.into_iter()
.flat_map(|input| {
input
.into_repr()
.as_ref()
.iter()
.flat_map(|l| l.to_le_bytes().to_vec())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
// Allocate this byte array as input packed into field elements.
let input_bytes = UInt8::alloc_input_vec(cs.ns(|| "Input"), &input_bytes[..])?;
// 40 byte
let element_size = <MNT4FqParameters as FpParameters>::BigInt::NUM_LIMBS * 8;
input_gadgets = input_bytes
.chunks(element_size)
.map(|chunk| {
chunk
.iter()
.flat_map(|byte| byte.into_bits_le())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
}
let vk_gadget = TestVkGadget1::alloc(cs.ns(|| "Vk"), || Ok(&params.vk))?;
let proof_gadget =
TestProofGadget1::alloc(cs.ns(|| "Proof"), || Ok(proof.clone())).unwrap();
<TestVerifierGadget1 as NIZKVerifierGadget<TestProofSystem1, MNT6Fq>>::check_verify(
cs.ns(|| "Verify"),
&vk_gadget,
input_gadgets.iter(),
&proof_gadget,
)?;
Ok(())
}
}
#[test]
fn groth16_recursive_verifier_test() {
let num_inputs = 100;
let num_constraints = num_inputs;
let rng = &mut test_rng();
let mut inputs: Vec<Option<MNT4Fq>> = Vec::with_capacity(num_inputs);
for _ in 0..num_inputs {
inputs.push(Some(rng.gen()));
}
// Generate inner params and proof.
let inner_params = {
let c = Bench::<MNT4Fq> {
inputs: vec![None; num_inputs],
num_constraints,
};
generate_random_parameters(c, rng).unwrap()
};
let inner_proof = {
// Create an instance of our circuit (with the
// witness)
let c = Bench {
inputs: inputs.clone(),
num_constraints,
};
// Create a groth16 proof with our parameters.
create_random_proof(c, &inner_params, rng).unwrap()
};
// Generate outer params and proof.
let params = {
let c = Wrapper {
inputs: inputs.clone(),
params: inner_params.clone(),
proof: inner_proof.clone(),
};
generate_random_parameters(c, rng).unwrap()
};
{
let proof = {
// Create an instance of our circuit (with the
// witness)
let c = Wrapper {
inputs: inputs.clone(),
params: inner_params.clone(),
proof: inner_proof.clone(),
};
// Create a groth16 proof with our parameters.
create_random_proof(c, &params, rng).unwrap()
};
let mut cs = TestConstraintSystem::<MNT4Fq>::new();
let inputs: Vec<_> = inputs.into_iter().map(|input| input.unwrap()).collect();
let mut input_gadgets = Vec::new();
{
let bigint_size = <MNT4FqParameters as FpParameters>::BigInt::NUM_LIMBS * 64;
let mut input_bits = Vec::new();
let mut cs = cs.ns(|| "Allocate Input");
for (i, input) in inputs.into_iter().enumerate() {
let input_gadget =
FpGadget::alloc_input(cs.ns(|| format!("Input {}", i)), || Ok(input))
.unwrap();
let mut fp_bits = input_gadget
.to_bits(cs.ns(|| format!("To bits {}", i)))
.unwrap();
// FpGadget::to_bits outputs a big-endian binary representation of
// fe_gadget's value, so we have to reverse it to get the little-endian
// form.
fp_bits.reverse();
// Use 320 bits per element.
for _ in fp_bits.len()..bigint_size {
fp_bits.push(Boolean::constant(false));
}
input_bits.extend_from_slice(&fp_bits);
}
// Pack input bits into field elements of the underlying circuit.
let max_size = 8 * (<MNT6FqParameters as FpParameters>::CAPACITY / 8) as usize;
let max_size = max_size as usize;
let bigint_size = <MNT6FqParameters as FpParameters>::BigInt::NUM_LIMBS * 64;
for chunk in input_bits.chunks(max_size) {
let mut chunk = chunk.to_vec();
let len = chunk.len();
for _ in len..bigint_size {
chunk.push(Boolean::constant(false));
}
input_gadgets.push(chunk);
}
// assert!(!verify_proof(&pvk, &proof, &[a]).unwrap());
}
let vk_gadget = TestVkGadget2::alloc_input(cs.ns(|| "Vk"), || Ok(&params.vk)).unwrap();
let proof_gadget =
TestProofGadget2::alloc(cs.ns(|| "Proof"), || Ok(proof.clone())).unwrap();
println!("Time to verify!\n\n\n\n");
<TestVerifierGadget2 as NIZKVerifierGadget<TestProofSystem2, MNT4Fq>>::check_verify(
cs.ns(|| "Verify"),
&vk_gadget,
input_gadgets.iter(),
&proof_gadget,
)
.unwrap();
if !cs.is_satisfied() {
println!("=========================================================");
println!("Unsatisfied constraints:");
println!("{:?}", cs.which_is_unsatisfied().unwrap());
println!("=========================================================");
}
// cs.print_named_objects();
assert!(cs.is_satisfied());
}
}
}