diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..6dd0f7cd Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml index 22b8f037..aa14b092 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,12 +22,12 @@ rand_distr = "0.4" rand_core = "0.6.4" rand_chacha = "0.3.1" rayon = "1" -bytemuck = { version = "1.17", features = ["derive"] } +bytemuck = { version = "1.13", features = ["derive"] } merlin = { version = "3.0.0", default-features = false } serde = { version = "1.0", features = ["derive"] } bincode = "1.3" -itertools = "0.13" -sha2 = { version = "0.10" } +itertools = "0.13.0" +sha2 = { version = "0.10.7", features = ["asm"] } criterion = "0.5" diff --git a/algebra/.DS_Store b/algebra/.DS_Store new file mode 100644 index 00000000..51e62409 Binary files /dev/null and b/algebra/.DS_Store differ diff --git a/algebra/src/extension/binomial_extension.rs b/algebra/src/extension/binomial_extension.rs index f03665a8..6ebf8c95 100644 --- a/algebra/src/extension/binomial_extension.rs +++ b/algebra/src/extension/binomial_extension.rs @@ -168,6 +168,10 @@ impl + Packable, const D: usize> Field } const MODULUS_VALUE: Self::Value = F::MODULUS_VALUE; + + fn random(rng: &mut R) -> Self { + Self::from_base_fn(|_| FieldUniformSampler::new().sample(rng)) + } } impl + Packable, const D: usize> Display diff --git a/algebra/src/polynomial/multivariate/data_structures.rs b/algebra/src/polynomial/multivariate/data_structures.rs index 2c1579e2..e0f83729 100644 --- a/algebra/src/polynomial/multivariate/data_structures.rs +++ b/algebra/src/polynomial/multivariate/data_structures.rs @@ -2,6 +2,8 @@ use std::{collections::HashMap, rc::Rc}; +use serde::Serialize; + use crate::Field; use super::{DenseMultilinearExtension, MultilinearExtension}; @@ -48,7 +50,7 @@ impl ListOfProductsOfPolynomials { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Serialize)] /// Stores the number of variables and max number of multiplicands of the added polynomial used by the prover. /// This data structures will be used as the verifier key. pub struct PolynomialInfo { diff --git a/algebra/src/polynomial/multivariate/multilinear/dense.rs b/algebra/src/polynomial/multivariate/multilinear/dense.rs index 865492a5..a44eab33 100644 --- a/algebra/src/polynomial/multivariate/multilinear/dense.rs +++ b/algebra/src/polynomial/multivariate/multilinear/dense.rs @@ -109,6 +109,15 @@ impl DenseMultilinearExtension { poly.truncate(1 << (nv - dim)); poly[0] } + + /// Convert to EF version + #[inline] + pub fn to_ef>(&self) -> DenseMultilinearExtension { + DenseMultilinearExtension:: { + num_vars: self.num_vars, + evaluations: self.evaluations.iter().map(|x| EF::from_base(*x)).collect(), + } + } } impl DenseMultilinearExtension { @@ -121,19 +130,19 @@ impl DenseMultilinearExtension { #[inline] pub fn get_decomposed_mles( &self, - base_len: u32, - bits_len: u32, + base_len: usize, + bits_len: usize, ) -> Vec>> { let mut val = self.evaluations.clone(); - let mask = F::mask(base_len); + let mask = F::mask(base_len as u32); - let mut bits = Vec::with_capacity(bits_len as usize); + let mut bits = Vec::with_capacity(bits_len); // extract `base_len` bits as one "bit" at a time for _ in 0..bits_len { let mut bit = vec![F::zero(); self.evaluations.len()]; bit.iter_mut().zip(val.iter_mut()).for_each(|(b_i, v_i)| { - v_i.decompose_lsb_bits_at(b_i, mask, base_len); + v_i.decompose_lsb_bits_at(b_i, mask, base_len as u32); }); bits.push(Rc::new(DenseMultilinearExtension::from_evaluations_vec( self.num_vars, @@ -310,17 +319,6 @@ impl<'a, F: Field> AddAssign<(F, &'a DenseMultilinearExtension)> } } -impl<'a, F: Field> AddAssign<(F, &'a Rc>)> - for DenseMultilinearExtension -{ - #[inline] - fn add_assign(&mut self, (f, rhs): (F, &'a Rc>)) { - self.iter_mut() - .zip(rhs.iter()) - .for_each(|(x, y)| *x += f.mul(y)); - } -} - impl Neg for DenseMultilinearExtension { type Output = DenseMultilinearExtension; diff --git a/algebra/src/utils/transcript.rs b/algebra/src/utils/transcript.rs index e1c7157e..ae6e20df 100644 --- a/algebra/src/utils/transcript.rs +++ b/algebra/src/utils/transcript.rs @@ -1,10 +1,9 @@ use std::marker::PhantomData; use rand::SeedableRng; -use rand_distr::Distribution; use serde::Serialize; -use crate::{AbstractExtensionField, Field, FieldUniformSampler}; +use crate::Field; use super::{Block, Prg}; @@ -12,7 +11,7 @@ use super::{Block, Prg}; /// to sample uniform field elements. pub struct Transcript { transcript: merlin::Transcript, - sampler: FieldUniformSampler, + // sampler: FieldUniformSampler, _marker: PhantomData, } @@ -22,89 +21,39 @@ impl Transcript { pub fn new() -> Self { Self { transcript: merlin::Transcript::new(b""), - sampler: FieldUniformSampler::new(), + // sampler: FieldUniformSampler::new(), _marker: PhantomData, } } } -impl Transcript { - /// Append the message to the transcript. - #[inline] - pub fn append_message(&mut self, msg: &[u8]) { - self.transcript.append_message(b"", msg); - } - /// Append elements to the transcript. - #[inline] - pub fn append_elements(&mut self, elems: &[F]) { - self.append_message(&bincode::serialize(elems).unwrap()); - } - - /// Append extension field elements to the transcript. - #[inline] - pub fn append_ext_field_elements>(&mut self, elems: &[EF]) { - let elems: Vec = elems - .iter() - .flat_map(|x| x.as_base_slice()) - .cloned() - .collect(); - self.append_message(&bincode::serialize(&elems).unwrap()); +impl Transcript { + /// Append the message to the transcript. + pub fn append_message(&mut self, label: &'static [u8], msg: &M) { + self.transcript + .append_message(label, &bincode::serialize(msg).unwrap()); } /// Generate the challenge bytes from the current transcript #[inline] - pub fn get_challenge_bytes(&mut self, bytes: &mut [u8]) { - self.transcript.challenge_bytes(b"", bytes); + pub fn get_challenge_bytes(&mut self, label: &'static [u8], bytes: &mut [u8]) { + self.transcript.challenge_bytes(label, bytes); } /// Generate the challenge from the current transcript - /// and append it to the transcript. - pub fn get_and_append_challenge(&mut self) -> F { + pub fn get_challenge(&mut self, label: &'static [u8]) -> F { let mut seed = [0u8; 16]; - self.transcript.challenge_bytes(b"", &mut seed); + self.transcript.challenge_bytes(label, &mut seed); let mut prg = Prg::from_seed(Block::from(seed)); - let challenge: F = self.sampler.sample(&mut prg); - self.append_message(&bincode::serialize(&challenge).unwrap()); - - challenge + F::random(&mut prg) } /// Generate the challenge vector from the current transcript - /// and append it to the transcript. - pub fn get_vec_and_append_challenge(&mut self, num: usize) -> Vec { + pub fn get_vec_challenge(&mut self, label: &'static [u8], num: usize) -> Vec { let mut seed = [0u8; 16]; - self.transcript.challenge_bytes(b"", &mut seed); + self.transcript.challenge_bytes(label, &mut seed); let mut prg = Prg::from_seed(Block::from(seed)); - - let challenge = self.sampler.sample_iter(&mut prg).take(num).collect(); - self.append_message(&bincode::serialize(&challenge).unwrap()); - - challenge - } - - /// Generate the challenge for extension field from the current transcript - /// and append it to the transcript. - #[inline] - pub fn get_ext_field_and_append_challenge(&mut self) -> EF - where - EF: AbstractExtensionField, - { - let value = self.get_vec_and_append_challenge(EF::D); - EF::from_base_slice(&value) - } - - /// Generate the challenge vector for extension field from the current transcript - /// and append it to the transcript. - #[inline] - pub fn get_vec_ext_field_and_append_challenge(&mut self, num: usize) -> Vec - where - EF: AbstractExtensionField, - { - let challenges = self.get_vec_and_append_challenge(num * EF::D); - challenges - .chunks_exact(EF::D) - .map(|ext| EF::from_base_slice(ext)) - .collect() + (0..num).map(|_| F::random(&mut prg)).collect() } } diff --git a/algebra/tests/multivariate_test.rs b/algebra/tests/multivariate_test.rs index a3f514f1..50f5b727 100644 --- a/algebra/tests/multivariate_test.rs +++ b/algebra/tests/multivariate_test.rs @@ -108,11 +108,11 @@ fn mle_arithmetic() { // test decomposition of mle { - let base_len = 3; + let base_len: usize = 3; let base = FF::new(1 << base_len); - let basis = >::new(base_len); + let basis = >::new(base_len as u32); let bits_len = basis.decompose_len(); - let decomposed_polys = poly1.get_decomposed_mles(base_len, bits_len as u32); + let decomposed_polys = poly1.get_decomposed_mles(base_len, bits_len); let point: Vec<_> = (0..NV).map(|_| uniform.sample(&mut rng)).collect(); let evaluation = decomposed_polys .iter() diff --git a/lattice/.DS_Store b/lattice/.DS_Store new file mode 100644 index 00000000..e3045a46 Binary files /dev/null and b/lattice/.DS_Store differ diff --git a/pcs/.DS_Store b/pcs/.DS_Store new file mode 100644 index 00000000..ec43d4e8 Binary files /dev/null and b/pcs/.DS_Store differ diff --git a/pcs/benches/brakedown_pcs.rs b/pcs/benches/brakedown_pcs.rs index e49f2813..d4f49d06 100644 --- a/pcs/benches/brakedown_pcs.rs +++ b/pcs/benches/brakedown_pcs.rs @@ -42,7 +42,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { Some(code_spec), ); - let mut trans = Transcript::::new(); + let mut trans = Transcript::::new(); let mut comm = BrakedownPolyCommitment::default(); let mut state = BrakedownCommitmentState::default(); let mut proof = BrakedownOpenProof::default(); @@ -84,7 +84,7 @@ fn configure() -> Criterion { criterion_group! { name = benches; config = configure(); - targets =criterion_benchmark + targets = criterion_benchmark } criterion_main!(benches); diff --git a/pcs/examples/brakedown_pcs.rs b/pcs/examples/brakedown_pcs.rs index c3db6bda..e61a7d25 100644 --- a/pcs/examples/brakedown_pcs.rs +++ b/pcs/examples/brakedown_pcs.rs @@ -34,7 +34,7 @@ fn main() { ); println!("setup time: {:?} ms", start.elapsed().as_millis()); - let mut trans = Transcript::::new(); + let mut trans = Transcript::::new(); let start = Instant::now(); let (comm, state) = @@ -54,7 +54,7 @@ fn main() { let eval = poly.evaluate_ext(&point); - let mut trans = Transcript::::new(); + let mut trans = Transcript::::new(); let start = Instant::now(); let check = BrakedownPCS::, ExpanderCodeSpec, EF>::verify( diff --git a/pcs/src/lib.rs b/pcs/src/lib.rs index c05c307f..73f8f8bc 100644 --- a/pcs/src/lib.rs +++ b/pcs/src/lib.rs @@ -8,12 +8,12 @@ pub mod multilinear; /// utils, mainly used to implement linear time encodable code now pub mod utils; -use algebra::{utils::Transcript, Field, MultilinearExtension}; +use algebra::{utils::Transcript, AbstractExtensionField, Field, MultilinearExtension}; // type Point =

>::Point; /// Polymomial Commitment Scheme -pub trait PolynomialCommitmentScheme { +pub trait PolynomialCommitmentScheme, S> { /// System parameters type Parameters; /// polynomial to commit @@ -42,7 +42,7 @@ pub trait PolynomialCommitmentScheme { commitment: &Self::Commitment, state: &Self::CommitmentState, points: &[Self::Point], - trans: &mut Transcript, + trans: &mut Transcript, ) -> Self::Proof; /// The Verification phase. @@ -52,6 +52,6 @@ pub trait PolynomialCommitmentScheme { points: &[Self::Point], eval: Self::Point, proof: &Self::Proof, - trans: &mut Transcript, + trans: &mut Transcript, ) -> bool; } diff --git a/pcs/src/multilinear/brakedown/mod.rs b/pcs/src/multilinear/brakedown/mod.rs index c099f05f..3519f88b 100644 --- a/pcs/src/multilinear/brakedown/mod.rs +++ b/pcs/src/multilinear/brakedown/mod.rs @@ -48,7 +48,7 @@ where EF: AbstractExtensionField, { /// Prover answers the challenge by computing the product of the challenge vector - /// and the committed matrix. + /// and the commited matirx. /// The computation of the product can be viewed as a linear combination of rows /// of the matrix with challenge vector as the coefficients. fn answer_challenge( @@ -227,12 +227,12 @@ where EF: AbstractExtensionField, { /// Generate random queries. - fn random_queries(pp: &BrakedownParams, trans: &mut Transcript) -> Vec { + fn random_queries(pp: &BrakedownParams, trans: &mut Transcript) -> Vec { let num_queries = pp.num_query(); let codeword_len = pp.code().codeword_len(); let mut seed = [0u8; 16]; - trans.get_challenge_bytes(&mut seed); + trans.get_challenge_bytes(b"Generate random queries", &mut seed); let mut prg = Prg::from_seed(Block::from(seed)); // Generate a random set of queries. @@ -244,7 +244,7 @@ where } } -impl PolynomialCommitmentScheme for BrakedownPCS +impl PolynomialCommitmentScheme for BrakedownPCS where F: Field + Serialize, H: Hash + Sync + Send, @@ -322,11 +322,12 @@ where commitment: &Self::Commitment, state: &Self::CommitmentState, points: &[Self::Point], - trans: &mut Transcript, + trans: &mut Transcript, ) -> Self::Proof { assert_eq!(points.len(), pp.num_vars()); // Hash the commitment to transcript. - trans.append_message(&commitment.to_bytes().unwrap()); + trans.append_message(b"commitment", &commitment); + // trans.append_message(&commitment.to_bytes().unwrap()); // Compute the tensor from the random point, see [DP23](https://eprint.iacr.org/2023/630.pdf). let tensor = Self::tensor_from_points(pp, points); @@ -334,7 +335,7 @@ where let rlc_msgs = Self::answer_challenge(pp, &tensor, state); // Hash rlc to transcript. - trans.append_ext_field_elements(&rlc_msgs); + trans.append_message(b"rlc", &rlc_msgs); // Sample random queries. let queries = Self::random_queries(pp, trans); @@ -355,12 +356,12 @@ where points: &[Self::Point], eval: Self::Point, proof: &Self::Proof, - trans: &mut Transcript, + trans: &mut Transcript, ) -> bool { assert_eq!(points.len(), pp.num_vars()); // Hash the commitment to transcript. - trans.append_message(&commitment.to_bytes().unwrap()); + trans.append_message(b"commitment", &commitment); let (tensor, residual) = Self::tensor_decompose(pp, points); @@ -371,7 +372,7 @@ where pp.code().encode_ext(&mut encoded_msg); // Hash rlc to transcript. - trans.append_ext_field_elements(&proof.rlc_msgs); + trans.append_message(b"rlc", &proof.rlc_msgs); // Sample random queries. let queries = Self::random_queries(pp, trans); diff --git a/pcs/tests/test_code.rs b/pcs/tests/test_code.rs index f525e03c..4b4e4417 100644 --- a/pcs/tests/test_code.rs +++ b/pcs/tests/test_code.rs @@ -69,13 +69,14 @@ fn code_distance_test() { let mut rng = rand::thread_rng(); let field_distr = FieldUniformSampler::new(); - let spec = ExpanderCodeSpec::new(0.1195, 0.0284, 1.420, 31, 30); + let spec = ExpanderCodeSpec::new(0.1195, 0.0284, 1.420, 31, 10); let brakedown_code: ExpanderCode = ExpanderCode::new(spec, 5000, &mut rng); let message_len = brakedown_code.message_len; let codeword_len = brakedown_code.codeword_len; let mut target_0 = vec![FF32::zero(); codeword_len]; + target_0[..message_len] .iter_mut() .for_each(|x| *x = rng.sample(field_distr)); @@ -93,6 +94,9 @@ fn code_distance_test() { .map(|(x_0, x_1)| (x_0 != x_1) as usize) .sum(); let num_expected = (brakedown_code.distance() * codeword_len as f64) as usize; + if num_real < num_expected { + println!("num real {:?} num expected {:?}", num_real, num_expected); + }; assert!(num_real >= num_expected); } } diff --git a/pcs/tests/test_pcs.rs b/pcs/tests/test_pcs.rs index 1f26605b..03224d22 100644 --- a/pcs/tests/test_pcs.rs +++ b/pcs/tests/test_pcs.rs @@ -32,7 +32,7 @@ fn pcs_test() { Some(code_spec), ); - let mut trans = Transcript::::new(); + let mut trans = Transcript::::new(); let (comm, state) = BrakedownPCS::, ExpanderCodeSpec, EF>::commit(&pp, &poly); @@ -50,7 +50,7 @@ fn pcs_test() { let eval = poly.evaluate_ext(&point); - let mut trans = Transcript::::new(); + let mut trans = Transcript::::new(); let proof = BrakedownOpenProof::::from_bytes(&buffer).unwrap(); diff --git a/zkfhe/.DS_Store b/zkfhe/.DS_Store new file mode 100644 index 00000000..58989bd8 Binary files /dev/null and b/zkfhe/.DS_Store differ diff --git a/zkp/.DS_Store b/zkp/.DS_Store new file mode 100644 index 00000000..c8560c8c Binary files /dev/null and b/zkp/.DS_Store differ diff --git a/zkp/Cargo.toml b/zkp/Cargo.toml index 1d5733a5..efeb9ab1 100644 --- a/zkp/Cargo.toml +++ b/zkp/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] algebra = { path = "../algebra" } +pcs = { path = "../pcs" } thiserror = { workspace = true } rand = { workspace = true } @@ -16,3 +17,16 @@ num-traits = { workspace = true } once_cell = { workspace = true } serde = { workspace = true } itertools = { workspace = true } +bincode = { workspace = true } + +[target.'cfg(all(unix, any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))'.dependencies] +sha2 = { workspace = true, features = ["asm"] } + +[target.'cfg(all(target_os = "windows", target_arch = "x86_64", target_env = "gnu"))'.dependencies] +sha2 = { workspace = true, features = ["asm"] } + +[target.'cfg(all(target_os = "windows", target_env = "msvc"))'.dependencies] +sha2 = { workspace = true } + +[dev-dependencies] +criterion = "0.3" \ No newline at end of file diff --git a/zkp/benches/range_check_bit_decomposition.rs b/zkp/benches/range_check_bit_decomposition.rs new file mode 100644 index 00000000..aeb588ec --- /dev/null +++ b/zkp/benches/range_check_bit_decomposition.rs @@ -0,0 +1,112 @@ +// use algebra::Basis; +// use algebra::{ +// derive::{DecomposableField, Field, Prime}, +// DecomposableField, DenseMultilinearExtension, Field, FieldUniformSampler, +// }; +// // use protocol::bit_decomposition::{BitDecomposition, DecomposedBits}; +// use criterion::{criterion_group, criterion_main, Criterion}; +// use rand::prelude::*; +// use rand_distr::Distribution; +// use std::rc::Rc; +// use std::time::Duration; +// use std::vec; +// use zkp::piop::{BitDecomposition, DecomposedBits}; + +// #[derive(Field, DecomposableField, Prime)] +// #[modulus = 132120577] +// pub struct Fp32(u32); + +// #[derive(Field, DecomposableField, Prime)] +// #[modulus = 59] +// pub struct Fq(u32); + +// // field type +// type FF = Fp32; + +// pub fn criterion_benchmark(c: &mut Criterion) { +// let lookup_num = 20; + +// let num_vars = 8; + +// let base_len: u32 = 3; +// let base: FF = FF::new(1 << base_len); +// let bits_len: u32 = >::new(base_len).decompose_len() as u32; + +// let mut rng = thread_rng(); +// let uniform_fq = >::new(); +// let uniform_ff = >::new(); + +// let d: Vec>> = (0..lookup_num) +// .map(|_| { +// Rc::new(DenseMultilinearExtension::from_evaluations_vec( +// num_vars, +// (0..(1 << num_vars)) +// .map(|_| FF::new(uniform_fq.sample(&mut rng).value())) +// .collect(), +// )) +// }) +// .collect(); + +// c.bench_function( +// &format!("prove lookup_num {} lookup size {}", lookup_num, num_vars), +// |b| { +// b.iter(|| { +// let d_bits: Vec<_> = d +// .iter() +// .map(|x| x.get_decomposed_mles(base_len, bits_len)) +// .collect(); +// let _: Vec<_> = d_bits.iter().collect(); + +// let mut decomposed_bits = DecomposedBits::new(base, base_len, bits_len, num_vars); +// for d_instance in d_bits.iter() { +// decomposed_bits.add_decomposed_bits_instance(d_instance); +// } + +// let _ = decomposed_bits.info(); + +// let u: Vec<_> = (0..num_vars).map(|_| uniform_ff.sample(&mut rng)).collect(); +// BitDecomposition::prove(&decomposed_bits, &u); +// }) +// }, +// ); + +// c.bench_function( +// &format!("verify lookup_num {} lookup size {}", lookup_num, num_vars), +// |b| { +// let d_bits: Vec<_> = d +// .iter() +// .map(|x| x.get_decomposed_mles(base_len, bits_len)) +// .collect(); +// let d_bits_ref: Vec<_> = d_bits.iter().collect(); + +// let mut decomposed_bits = DecomposedBits::new(base, base_len, bits_len, num_vars); +// for d_instance in d_bits.iter() { +// decomposed_bits.add_decomposed_bits_instance(d_instance); +// } + +// let decomposed_bits_info = decomposed_bits.info(); + +// let u: Vec<_> = (0..num_vars).map(|_| uniform_ff.sample(&mut rng)).collect(); +// let proof = BitDecomposition::prove(&decomposed_bits, &u); +// b.iter(|| { +// let subclaim = BitDecomposition::verifier(&proof, &decomposed_bits_info); +// assert!(subclaim.verify_subclaim(&d, &d_bits_ref, &u, &decomposed_bits_info)); +// }) +// }, +// ); +// } + +// fn configure() -> Criterion { +// Criterion::default() +// .warm_up_time(Duration::new(5, 0)) +// .measurement_time(Duration::new(10, 0)) +// .sample_size(10) +// } + +// criterion_group! { +// name = benches; +// config = configure(); +// targets = criterion_benchmark +// } + +// criterion_main!(benches); diff --git a/zkp/benches/range_check_lookup.rs b/zkp/benches/range_check_lookup.rs new file mode 100644 index 00000000..e2eadb8f --- /dev/null +++ b/zkp/benches/range_check_lookup.rs @@ -0,0 +1,103 @@ +// use algebra::{ +// derive::{DecomposableField, Field, Prime}, +// DenseMultilinearExtension, Field, +// }; +// use criterion::{criterion_group, criterion_main, Criterion}; +// use num_traits::Zero; +// use rand::prelude::*; +// use rand::SeedableRng; +// use rand_chacha::ChaCha12Rng; +// use std::rc::Rc; +// use std::time::Duration; +// use std::vec; +// use zkp::piop::{Lookup, LookupInstance}; + +// #[derive(Field, DecomposableField, Prime)] +// #[modulus = 132120577] +// pub struct Fp32(u32); + +// #[derive(Field, DecomposableField, Prime)] +// #[modulus = 59] +// pub struct Fq(u32); + +// // field type +// type FF = Fp32; + +// pub fn criterion_benchmark(c: &mut Criterion) { +// let num_vars = 8; +// let block_size = 2; +// let block_num = 20; +// let lookup_num: usize = block_num * block_size; +// let range = 59; + +// let mut rng = thread_rng(); +// let f_vec: Vec>> = (0..lookup_num) +// .map(|_| { +// let f_evaluations: Vec = (0..(1 << num_vars)) +// .map(|_| FF::new(rng.gen_range(0..range))) +// .collect(); +// Rc::new(DenseMultilinearExtension::from_evaluations_vec( +// num_vars, +// f_evaluations, +// )) +// }) +// .collect(); + +// let mut t_evaluations: Vec<_> = (0..range).map(FF::new).collect(); +// t_evaluations.resize(1 << num_vars, FF::zero()); +// let t = Rc::new(DenseMultilinearExtension::from_evaluations_vec( +// num_vars, +// t_evaluations, +// )); + +// let instance = LookupInstance::from_slice(&f_vec, t.clone(), block_size); +// let info = instance.info(); + +// c.bench_function( +// &format!( +// "rang check proving time of lookup num {}, lookup size {}, range size {}", +// lookup_num, +// 1 << num_vars, +// range +// ), +// |b| { +// let seed: ::Seed = Default::default(); +// let mut fs_rng_prover = ChaCha12Rng::from_seed(seed); +// b.iter(|| Lookup::prove(&mut fs_rng_prover, &instance)) +// }, +// ); + +// c.bench_function( +// &format!( +// "rang check verifying time of lookup num {}, lookup size {}, range size {}", +// lookup_num, +// 1 << num_vars, +// range +// ), +// |b| { +// let seed: ::Seed = Default::default(); +// let mut fs_rng_prover = ChaCha12Rng::from_seed(seed); +// let (proof, oracle) = Lookup::prove(&mut fs_rng_prover, &instance); +// b.iter(|| { +// let mut fs_rng_verifier = ChaCha12Rng::from_seed(seed); +// let subclaim = Lookup::verify(&mut fs_rng_verifier, &proof, &info); +// subclaim.verify_subclaim(f_vec.clone(), t.clone(), oracle.clone(), &info); +// }) +// }, +// ); +// } + +// fn configure() -> Criterion { +// Criterion::default() +// .warm_up_time(Duration::new(5, 0)) +// .measurement_time(Duration::new(10, 0)) +// .sample_size(10) +// } + +// criterion_group! { +// name = benches; +// config = configure(); +// targets = criterion_benchmark +// } + +// criterion_main!(benches); diff --git a/zkp/examples/accumulator.rs b/zkp/examples/accumulator.rs new file mode 100644 index 00000000..61f57611 --- /dev/null +++ b/zkp/examples/accumulator.rs @@ -0,0 +1,381 @@ +use algebra::{transformation::AbstractNTT, NTTField, NTTPolynomial, Polynomial}; +use algebra::{ + BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension, Field, MultilinearExtension, +}; +use itertools::izip; +use num_traits::One; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use sha2::Sha256; +use std::rc::Rc; +use std::vec; +use zkp::piop::{ + accumulator::AccumulatorSnarks, AccumulatorInstance, AccumulatorWitness, DecomposedBitsInfo, + NTTInstanceInfo, RlweCiphertext, RlweCiphertexts, RlweMultRgswInstance, +}; + +// # Parameters +// n = 1024: denotes the dimension of LWE +// N = 1024: denotes the dimension of ring in RLWE +// B = 2^3: denotes the basis used in the bit decomposition +// q = 1024: denotes the modulus in LWE +// Q = DefaultFieldU32: denotes the ciphertext modulus in RLWE +const DIM_LWE: usize = 1024; +const LOG_DIM_RLWE: usize = 10; +const LOG_B: usize = 3; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +fn random_rlwe_ciphertext(rng: &mut R, num_vars: usize) -> RlweCiphertext +where + R: rand::Rng + rand::CryptoRng, +{ + RlweCiphertext { + a: Rc::new(>::random(num_vars, rng)), + b: Rc::new(>::random(num_vars, rng)), + } +} + +fn random_rlwe_ciphertexts( + bits_len: usize, + rng: &mut R, + num_vars: usize, +) -> RlweCiphertexts +where + R: rand::Rng + rand::CryptoRng, +{ + RlweCiphertexts { + a_bits: (0..bits_len) + .map(|_| Rc::new(>::random(num_vars, rng))) + .collect(), + b_bits: (0..bits_len) + .map(|_| Rc::new(>::random(num_vars, rng))) + .collect(), + } +} + +/// Given an `index` of `len` bits, output a new index where the bits are reversed. +fn reverse_bits(index: usize, len: u32) -> usize { + let mut tmp = index; + let mut reverse_index = 0; + let mut pow = 1 << (len - 1); + for _ in 0..len { + reverse_index += pow * (1 & tmp); + pow >>= 1; + tmp >>= 1; + } + reverse_index +} + +/// Sort the array converting the index with reversed bits +/// array using little endian: 0 4 2 6 1 5 3 7 +/// array using big endian : 0 1 2 3 4 5 6 7 +/// For the same elements, the bits of the index are reversed, e.g. 100(4) <-> 001(1) and (110)6 <-> (011)3 +fn sort_array_with_reversed_bits(input: &[F], log_n: u32) -> Vec { + assert_eq!(input.len(), (1 << log_n) as usize); + let mut output = Vec::with_capacity(input.len()); + for i in 0..input.len() { + let reverse_i = reverse_bits(i, log_n); + output.push(input[reverse_i]); + } + output +} + +/// Invoke the existing api to perform ntt transform and convert the bit-reversed order to normal order +/// ```plain +/// normal order: 0 1 2 3 4 5 6 7 +/// +/// bit-reversed order: 0 4 2 6 1 5 3 7 +/// - ---- ---------- +fn ntt_transform_normal_order(log_n: u32, coeff: &[F]) -> Vec { + assert_eq!(coeff.len(), (1 << log_n) as usize); + let poly = >::from_slice(coeff); + let ntt_form: Vec<_> = F::get_ntt_table(log_n).unwrap().transform(&poly).data(); + sort_array_with_reversed_bits(&ntt_form, log_n) +} + +fn ntt_inverse_transform_normal_order(log_n: u32, points: &[F]) -> Vec { + assert_eq!(points.len(), (1 << log_n) as usize); + let reversed_points = sort_array_with_reversed_bits(points, log_n); + let ntt_poly = >::from_slice(&reversed_points); + F::get_ntt_table(log_n) + .unwrap() + .inverse_transform(&ntt_poly) + .data() +} + +fn generate_rlwe_mult_rgsw_instance( + num_vars: usize, + input_rlwe: RlweCiphertext, + bits_rgsw_c_ntt: RlweCiphertexts, + bits_rgsw_f_ntt: RlweCiphertexts, + bits_info: &DecomposedBitsInfo, + ntt_info: &NTTInstanceInfo, +) -> RlweMultRgswInstance { + // 1. Decompose the input of RLWE ciphertex + let bits_rlwe = RlweCiphertexts { + a_bits: input_rlwe + .a + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len), + b_bits: input_rlwe + .b + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len), + }; + + // 2. Compute the ntt form of the decomposed bits + let bits_rlwe_ntt = RlweCiphertexts { + a_bits: bits_rlwe + .a_bits + .iter() + .map(|bit| { + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + )) + }) + .collect(), + b_bits: bits_rlwe + .b_bits + .iter() + .map(|bit| { + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + )) + }) + .collect(), + }; + + assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rlwe_ntt.b_bits.len()); + assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rgsw_c_ntt.a_bits.len()); + assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rgsw_f_ntt.a_bits.len()); + + // 3. Compute the output of ntt form with the RGSW ciphertext and the decomposed bits of ntt form + let mut output_g_ntt = vec![F::zero(); 1 << num_vars]; + for (a, b, c, f) in izip!( + &bits_rlwe_ntt.a_bits, + &bits_rlwe_ntt.b_bits, + &bits_rgsw_c_ntt.a_bits, + &bits_rgsw_f_ntt.a_bits + ) { + output_g_ntt.iter_mut().enumerate().for_each(|(i, g_i)| { + *g_i += (a.evaluations[i] * c.evaluations[i]) + (b.evaluations[i] * f.evaluations[i]); + }); + } + + let mut output_h_ntt = vec![F::zero(); 1 << num_vars]; + for (a, b, c, f) in izip!( + &bits_rlwe_ntt.a_bits, + &bits_rlwe_ntt.b_bits, + &bits_rgsw_c_ntt.b_bits, + &bits_rgsw_f_ntt.b_bits + ) { + output_h_ntt.iter_mut().enumerate().for_each(|(i, h_i)| { + *h_i += (a.evaluations[i] * c.evaluations[i]) + (b.evaluations[i] * f.evaluations[i]); + }); + } + + let output_rlwe_ntt = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + output_g_ntt, + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + output_h_ntt, + )), + }; + + RlweMultRgswInstance::new( + num_vars, + bits_info, + ntt_info, + input_rlwe, + bits_rlwe, + bits_rlwe_ntt, + bits_rgsw_c_ntt, + bits_rgsw_f_ntt, + output_rlwe_ntt, + // &output_rlwe, + ) +} +// Perform d * ACC * RGSW(Zu) +// # Argument +// * input_d: scalar d = X^{-a_u} - 1 of the coefficient form +// * input_accumulator_ntt: ACC of the ntt form +// * input_rgsw_ntt: RGSW(Zu) of the ntt form +fn update_accumulator( + num_vars: usize, + acc_ntt: RlweCiphertext, + d: Rc>, + bits_rgsw_c_ntt: RlweCiphertexts, + bits_rgsw_f_ntt: RlweCiphertexts, + bits_info: &DecomposedBitsInfo, + ntt_info: &NTTInstanceInfo, +) -> AccumulatorWitness { + // 1. Perform ntt transform on (x^{-a_u} - 1) + let d_ntt = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + ntt_transform_normal_order(ntt_info.num_vars as u32, &d.evaluations), + )); + + // 2. Perform point-multiplication to compute (x^{-a_u} - 1) * ACC + let input_rlwe_ntt = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + izip!(&d_ntt.evaluations, &acc_ntt.a.evaluations) + .map(|(d_i, a_i)| *d_i * *a_i) + .collect(), + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + izip!(&d_ntt.evaluations, &acc_ntt.b.evaluations) + .map(|(d_i, b_i)| *d_i * *b_i) + .collect(), + )), + }; + + // 3. Compute the RLWE of coefficient form as the input of the multiplication between RLWE and RGSW + let input_rlwe = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + ntt_inverse_transform_normal_order( + ntt_info.num_vars as u32, + &input_rlwe_ntt.a.evaluations, + ), + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + ntt_inverse_transform_normal_order( + ntt_info.num_vars as u32, + &input_rlwe_ntt.b.evaluations, + ), + )), + }; + + let rlwe_mult_rgsw = generate_rlwe_mult_rgsw_instance( + num_vars, + input_rlwe, + bits_rgsw_c_ntt, + bits_rgsw_f_ntt, + bits_info, + ntt_info, + ); + + AccumulatorWitness { + acc_ntt: acc_ntt.clone(), + d, + d_ntt, + input_rlwe_ntt, + rlwe_mult_rgsw, + } +} + +fn generate_instance( + num_vars: usize, + input: RlweCiphertext, + num_updations: usize, + bits_info: &DecomposedBitsInfo, + ntt_info: &NTTInstanceInfo, +) -> AccumulatorInstance { + let mut rng = rand::thread_rng(); + let mut updations = Vec::with_capacity(num_updations); + + let mut acc_ntt = RlweCiphertext:: { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &input.a.evaluations), + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &input.b.evaluations), + )), + }; + for _ in 0..num_updations { + let d = Rc::new(DenseMultilinearExtension::random(num_vars, &mut rng)); + let bits_rgsw_c_ntt = random_rlwe_ciphertexts(bits_info.bits_len, &mut rng, num_vars); + let bits_rgsw_f_ntt = random_rlwe_ciphertexts(bits_info.bits_len, &mut rng, num_vars); + // perform ACC * d * RGSW + let updation = update_accumulator( + num_vars, + acc_ntt.clone(), + d, + bits_rgsw_c_ntt, + bits_rgsw_f_ntt, + bits_info, + ntt_info, + ); + // perform ACC + ACC * d * RGSW + acc_ntt = RlweCiphertext { + a: Rc::new(acc_ntt.a.as_ref() + updation.rlwe_mult_rgsw.output_rlwe_ntt.a.as_ref()), + b: Rc::new(acc_ntt.b.as_ref() + updation.rlwe_mult_rgsw.output_rlwe_ntt.b.as_ref()), + }; + updations.push(updation); + } + + let output = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_inverse_transform_normal_order(num_vars as u32, &acc_ntt.a.evaluations), + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_inverse_transform_normal_order(num_vars as u32, &acc_ntt.b.evaluations), + )), + }; + let output_ntt = acc_ntt; + AccumulatorInstance::new( + num_vars, + num_updations, + input, + updations, + output_ntt, + output, + bits_info, + ntt_info, + ) +} + +fn main() { + // information used to decompose bits + let base_len: usize = LOG_B; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = LOG_DIM_RLWE; + let bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len, + num_vars, + num_instances: 0, + }; + + // information used to perform NTT + let log_n = num_vars; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Rc::new(ntt_table); + let ntt_info = NTTInstanceInfo { + num_vars, + ntt_table, + num_ntt: 0, + }; + + let num_updations = DIM_LWE; + let input = random_rlwe_ciphertext(&mut thread_rng(), num_vars); + let instance = generate_instance(num_vars, input, num_updations, &bits_info, &ntt_info); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); +} diff --git a/zkp/examples/bit_decomposition.rs b/zkp/examples/bit_decomposition.rs new file mode 100644 index 00000000..4c0ed881 --- /dev/null +++ b/zkp/examples/bit_decomposition.rs @@ -0,0 +1,72 @@ +use algebra::{BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension}; +use algebra::{DecomposableField, Field, FieldUniformSampler}; +use itertools::izip; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use rand_distr::Distribution; +use sha2::Sha256; +use std::rc::Rc; +use zkp::piop::{BitDecompositionSnarks, DecomposedBits}; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +// # Parameters +// n = 1024: denotes the dimension of LWE +// N = 1024: denotes the dimension of ring in RLWE s.t. N = 2^num_vars +// B = 2^3: denotes the basis used in the bit decomposition +// q = 1024: denotes the modulus in LWE +// Q = BabyBear: denotes the ciphertext modulus in RLWE +const DIM_LWE: usize = 1024; +const LOG_DIM_RLWE: usize = 10; +const LOG_B: u32 = 2; + +fn generate_instance( + num_instances: usize, + num_vars: usize, + base_len: usize, + base: F, + bits_len: usize, +) -> DecomposedBits { + let mut rng = thread_rng(); + let uniform = >::new(); + let d = (0..num_instances) + .map(|_| { + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..(1 << num_vars)) + .map(|_| uniform.sample(&mut rng)) + .collect(), + )) + }) + .collect::>(); + + let d_bits: Vec<_> = d + .iter() + .map(|x| x.get_decomposed_mles(base_len, bits_len)) + .collect(); + + let mut decomposed_bits = DecomposedBits::new(base, base_len, bits_len, num_vars); + for (val, bits) in izip!(&d, &d_bits) { + decomposed_bits.add_decomposed_bits_instance(val, bits); + } + decomposed_bits +} +fn main() { + let base_len = LOG_B as usize; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = LOG_DIM_RLWE; + + // Generate 2 * n = 2048 instances to be proved, each instance consisting of N = 2^num_vars values to be decomposed. + let decomposed_bits = generate_instance::(2 * DIM_LWE, num_vars, base_len, base, bits_len); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + >::snarks::, ExpanderCodeSpec>( + &decomposed_bits, + &code_spec, + ); +} diff --git a/zkp/examples/ntt.rs b/zkp/examples/ntt.rs new file mode 100644 index 00000000..d1bdd0d5 --- /dev/null +++ b/zkp/examples/ntt.rs @@ -0,0 +1,119 @@ +use algebra::{transformation::AbstractNTT, NTTField, Polynomial}; +use algebra::{BabyBear, BabyBearExetension}; +use algebra::{DecomposableField, DenseMultilinearExtension, Field}; +use num_traits::One; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use sha2::Sha256; +use std::rc::Rc; +use zkp::piop::ntt::NTTInstances; +use zkp::piop::ntt::NTTSnarks; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +type PolyFF = Polynomial; + +// # Parameters +// n = 1024: denotes the dimension of LWE +// N = 1024: denotes the dimension of ring in RLWE +// B = 2^3: denotes the basis used in the bit decomposition +// q = 1024: denotes the modulus in LWE +// Q = DefaultFieldU32: denotes the ciphertext modulus in RLWE +const DIM_LWE: usize = 1024; +const LOG_DIM_RLWE: usize = 10; +const BITS_LEN: usize = 10; + +/// Given an `index` of `len` bits, output a new index where the bits are reversed. +fn reverse_bits(index: usize, len: u32) -> usize { + let mut tmp = index; + let mut reverse_index = 0; + let mut pow = 1 << (len - 1); + for _ in 0..len { + reverse_index += pow * (1 & tmp); + pow >>= 1; + tmp >>= 1; + } + reverse_index +} + +/// Sort the array converting the index with reversed bits +/// array using little endian: 0 4 2 6 1 5 3 7 +/// array using big endian : 0 1 2 3 4 5 6 7 +/// For the same elements, the bits of the index are reversed, e.g. 100(4) <-> 001(1) and (110)6 <-> (011)3 +fn sort_array_with_reversed_bits(input: &[F], log_n: u32) -> Vec { + assert_eq!(input.len(), (1 << log_n) as usize); + let mut output = Vec::with_capacity(input.len()); + for i in 0..input.len() { + let reverse_i = reverse_bits(i, log_n); + output.push(input[reverse_i]); + } + output +} + +/// Invoke the existing api to perform ntt transform and convert the bit-reversed order to normal oder +/// In other words, the orders of input and output are both normal order. +/// ```plain +/// normal order: 0 1 2 3 4 5 6 7 +/// +/// bit-reversed order: 0 4 2 6 1 5 3 7 +/// - ---- ---------- +fn ntt_transform_normal_order(log_n: u32, coeff: &[F]) -> Vec { + assert_eq!(coeff.len(), (1 << log_n) as usize); + let poly = >::from_slice(coeff); + let ntt_form: Vec<_> = F::get_ntt_table(log_n).unwrap().transform(&poly).data(); + sort_array_with_reversed_bits(&ntt_form, log_n) +} + +fn generate_single_instance( + instances: &mut NTTInstances, + log_n: usize, + rng: &mut R, +) { + let coeff = PolyFF::random(1 << log_n, rng).data(); + let point = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + log_n, + ntt_transform_normal_order(log_n as u32, &coeff) + .iter() + .map(|x| FF::new(x.value())) + .collect(), + )); + let coeff = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + log_n, + coeff.iter().map(|x| FF::new(x.value())).collect(), + )); + instances.add_ntt(&coeff, &point); +} + +fn main() { + let num_vars = LOG_DIM_RLWE; + // let num_ntt = 5 as usize; + let num_ntt = 2 * DIM_LWE * (1 + BITS_LEN); + let log_n: usize = num_vars; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); + + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Rc::new(ntt_table); + + let mut rng = thread_rng(); + + let mut ntt_instances = >::new(num_vars, &ntt_table); + for _ in 0..num_ntt { + generate_single_instance(&mut ntt_instances, log_n, &mut rng); + } + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + >::snarks::, ExpanderCodeSpec>( + &ntt_instances, + &code_spec, + ); +} diff --git a/zkp/examples/rlwe_mult_rgsw.rs b/zkp/examples/rlwe_mult_rgsw.rs new file mode 100644 index 00000000..83eb7dc9 --- /dev/null +++ b/zkp/examples/rlwe_mult_rgsw.rs @@ -0,0 +1,246 @@ +use algebra::{transformation::AbstractNTT, NTTField, Polynomial}; +use algebra::{BabyBear, BabyBearExetension, Basis, FieldUniformSampler}; +use algebra::{DenseMultilinearExtension, Field}; +use itertools::izip; +use num_traits::One; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use sha2::Sha256; +use std::rc::Rc; +use std::vec; +use zkp::piop::{ + rlwe_mul_rgsw::RlweMultRgswSnarks, DecomposedBitsInfo, NTTInstanceInfo, RlweCiphertext, + RlweCiphertexts, RlweMultRgswInstance, +}; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +// # Parameters +// n = 1024: denotes the dimension of LWE +// N = 1024: denotes the dimension of ring in RLWE +// B = 2^3: denotes the basis used in the bit decomposition +// q = 1024: denotes the modulus in LWE +// Q = DefaultFieldU32: denotes the ciphertext modulus in RLWE +const LOG_DIM_RLWE: usize = 10; +const LOG_B: usize = 2; + +/// Given an `index` of `len` bits, output a new index where the bits are reversed. +fn reverse_bits(index: usize, len: u32) -> usize { + let mut tmp = index; + let mut reverse_index = 0; + let mut pow = 1 << (len - 1); + for _ in 0..len { + reverse_index += pow * (1 & tmp); + pow >>= 1; + tmp >>= 1; + } + reverse_index +} + +/// Sort the array converting the index with reversed bits +/// array using little endian: 0 4 2 6 1 5 3 7 +/// array using big endian : 0 1 2 3 4 5 6 7 +/// For the same elements, the bits of the index are reversed, e.g. 100(4) <-> 001(1) and (110)6 <-> (011)3 +fn sort_array_with_reversed_bits(input: &[F], log_n: u32) -> Vec { + assert_eq!(input.len(), (1 << log_n) as usize); + let mut output = Vec::with_capacity(input.len()); + for i in 0..input.len() { + let reverse_i = reverse_bits(i, log_n); + output.push(input[reverse_i]); + } + output +} + +/// Invoke the existing api to perform ntt transform and convert the bit-reversed order to normal oder +/// In other words, the orders of input and output are both normal order. +/// ```plain +/// normal order: 0 1 2 3 4 5 6 7 +/// +/// bit-reversed order: 0 4 2 6 1 5 3 7 +/// - ---- ---------- +fn ntt_transform_normal_order(log_n: u32, coeff: &[F]) -> Vec { + assert_eq!(coeff.len(), (1 << log_n) as usize); + let poly = >::from_slice(coeff); + let ntt_form: Vec<_> = F::get_ntt_table(log_n).unwrap().transform(&poly).data(); + sort_array_with_reversed_bits(&ntt_form, log_n) +} + +fn generate_instance( + num_vars: usize, + input_rlwe: RlweCiphertext, + input_rgsw: (RlweCiphertexts, RlweCiphertexts), + bits_info: &DecomposedBitsInfo, + ntt_info: &NTTInstanceInfo, +) -> RlweMultRgswInstance { + // 1. Decompose the input of RLWE ciphertex + let bits_rlwe = RlweCiphertexts { + a_bits: input_rlwe + .a + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len), + b_bits: input_rlwe + .b + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len), + }; + let (bits_rgsw_c_ntt, bits_rgsw_f_ntt) = input_rgsw; + + // 2. Compute the ntt form of the decomposed bits + let bits_rlwe_ntt = RlweCiphertexts { + a_bits: bits_rlwe + .a_bits + .iter() + .map(|bit| { + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + )) + }) + .collect(), + b_bits: bits_rlwe + .b_bits + .iter() + .map(|bit| { + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + )) + }) + .collect(), + }; + + assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rlwe_ntt.b_bits.len()); + assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rgsw_c_ntt.a_bits.len()); + assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rgsw_f_ntt.a_bits.len()); + + // 3. Compute the output of ntt form with the RGSW ciphertext and the decomposed bits of ntt form + let mut output_g_ntt = vec![F::zero(); 1 << num_vars]; + for (a, b, c, f) in izip!( + &bits_rlwe_ntt.a_bits, + &bits_rlwe_ntt.b_bits, + &bits_rgsw_c_ntt.a_bits, + &bits_rgsw_f_ntt.a_bits + ) { + output_g_ntt.iter_mut().enumerate().for_each(|(i, g_i)| { + *g_i += (a.evaluations[i] * c.evaluations[i]) + (b.evaluations[i] * f.evaluations[i]); + }); + } + + let mut output_h_ntt = vec![F::zero(); 1 << num_vars]; + for (a, b, c, f) in izip!( + &bits_rlwe_ntt.a_bits, + &bits_rlwe_ntt.b_bits, + &bits_rgsw_c_ntt.b_bits, + &bits_rgsw_f_ntt.b_bits + ) { + output_h_ntt.iter_mut().enumerate().for_each(|(i, h_i)| { + *h_i += (a.evaluations[i] * c.evaluations[i]) + (b.evaluations[i] * f.evaluations[i]); + }); + } + + let output_rlwe_ntt = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + output_g_ntt, + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + output_h_ntt, + )), + }; + + RlweMultRgswInstance::new( + num_vars, + bits_info, + ntt_info, + input_rlwe, + bits_rlwe, + bits_rlwe_ntt, + bits_rgsw_c_ntt, + bits_rgsw_f_ntt, + output_rlwe_ntt, + ) +} + +fn main() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + // information used to decompose bits + let base_len = LOG_B; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = LOG_DIM_RLWE; + let bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len, + num_vars, + num_instances: 0, + }; + + // information used to perform NTT + let log_n = num_vars; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Rc::new(ntt_table); + let ntt_info = NTTInstanceInfo { + num_vars, + ntt_table, + num_ntt: 0, + }; + + // generate random RGSW ciphertext = (bits_rgsw_c_ntt, bits_rgsw_f_ntt) \in RLWE' \times \RLWE' + let mut bits_rgsw_c_ntt = >::new(bits_len); + let points: Vec<_> = (0..1 << num_vars) + .map(|_| uniform.sample(&mut rng)) + .collect(); + let coeffs: Vec<_> = (0..1 << num_vars) + .map(|_| uniform.sample(&mut rng)) + .collect(); + for _ in 0..bits_len { + bits_rgsw_c_ntt.add_rlwe( + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + ); + } + + let mut bits_rgsw_f_ntt = >::new(bits_len); + for _ in 0..bits_len { + bits_rgsw_f_ntt.add_rlwe( + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + ); + } + + // generate the random RLWE ciphertext + let input_rlwe = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_slice( + log_n, &coeffs, + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_slice( + log_n, &coeffs, + )), + }; + + // generate all the witness required + let instance = generate_instance( + num_vars, + input_rlwe, + (bits_rgsw_c_ntt, bits_rgsw_f_ntt), + &bits_info, + &ntt_info, + ); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); +} diff --git a/zkp/examples/round.rs b/zkp/examples/round.rs new file mode 100644 index 00000000..72e83d9f --- /dev/null +++ b/zkp/examples/round.rs @@ -0,0 +1,90 @@ +use algebra::{BabyBear, BabyBearExetension, DenseMultilinearExtension}; +use algebra::{DecomposableField, Field, FieldUniformSampler}; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use rand_distr::Distribution; +use sha2::Sha256; +use std::rc::Rc; +use zkp::piop::round::RoundSnarks; +use zkp::piop::{DecomposedBitsInfo, RoundInstance}; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +// # Parameters +// n = 1024: denotes the dimension of LWE +// N = 1024: denotes the dimension of ring in RLWE +// B = 2^3: denotes the basis used in the bit decomposition +// q = 1024: denotes the modulus in LWE +// Q = DefaultFieldU32: denotes the ciphertext modulus in RLWE +const LOG_DIM_RLWE: usize = 10; + +const FP: u32 = FF::MODULUS_VALUE; // ciphertext space +const FT: u32 = 4; // message space +const BASE_LEN: u32 = 1; +const FK: u32 = (FP - 1) / FT; +const LOG_FK: u32 = FK.next_power_of_two().ilog2(); +const DELTA: u32 = (1 << LOG_FK) - FK; + +#[inline] +fn decode(c: FF) -> u32 { + (c.value() as f64 * FT as f64 / FP as f64).floor() as u32 % FT +} + +fn generate_instance(num_vars: usize) -> RoundInstance { + let k = FF::new(FK); + let k_bits_len = LOG_FK as usize; + let delta: FF = FF::new(DELTA); + + let base_len = BASE_LEN as usize; + let base: FF = FF::new(1 << base_len); + + let mut rng = thread_rng(); + let uniform = >::new(); + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..1 << num_vars) + .map(|_| uniform.sample(&mut rng)) + .collect(), + )); + let output = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + input.iter().map(|x| FF::new(decode(*x))).collect(), + )); + let output_bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len: 2, + num_vars, + num_instances: 1, + }; + + let offset_bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + >::new( + k, + delta, + input, + output, + &output_bits_info, + &offset_bits_info, + ) +} +fn main() { + // Generate 1 instance to be proved, containing N = 2^num_vars round relation to be proved + let num_vars = LOG_DIM_RLWE; + let instance = generate_instance(num_vars); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); +} diff --git a/zkp/src/piop/accumulator.rs b/zkp/src/piop/accumulator.rs index 043c6d51..e7cae6de 100644 --- a/zkp/src/piop/accumulator.rs +++ b/zkp/src/piop/accumulator.rs @@ -3,53 +3,55 @@ //! Each updation contains two single ntt operations and one multiplication between RLWE and RGSW use crate::sumcheck::verifier::SubClaim; use crate::sumcheck::MLSumcheck; -use crate::sumcheck::Proof; -use crate::utils::eval_identity_function; +use crate::sumcheck::ProofWrapper; +use crate::sumcheck::SumcheckKit; +use core::fmt; use std::marker::PhantomData; use std::rc::Rc; +use std::time::Instant; +use super::ntt::NTTRecursiveProof; +use super::rlwe_mul_rgsw::RlweEval; +use super::rlwe_mul_rgsw::RlweMultRgswEval; +use super::rlwe_mul_rgsw::RlweMultRgswInfo; +use super::NTTBareIOP; +use super::RlweCiphertext; +use super::RlweMultRgswIOP; +use super::RlweMultRgswInstance; +use super::{DecomposedBitsInfo, NTTInstance, NTTInstanceInfo, NTTIOP}; +use crate::utils::{ + add_assign_ef, eval_identity_function, gen_identity_evaluations, print_statistic, + verify_oracle_relation, +}; +use algebra::utils::Transcript; +use algebra::AbstractExtensionField; use algebra::{ DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, MultilinearExtension, - PolynomialInfo, }; use itertools::izip; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; - -use super::bit_decomposition::{BitDecomposition, BitDecompositionProof, BitDecompositionSubClaim}; -use super::ntt::{NTTProof, NTTSubclaim}; -use super::{DecomposedBits, DecomposedBitsInfo, NTTInstance, NTTInstanceInfo, NTTIOP}; -use super::{RlweCiphertext, RlweCiphertexts}; +use itertools::Itertools; +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{LinearCode, LinearCodeSpec}, + utils::hash::Hash, + PolynomialCommitmentScheme, +}; +use serde::{Deserialize, Serialize}; -/// SNARKs for Multiplication between RLWE ciphertext and RGSW ciphertext +/// IOP for Accumulator pub struct AccumulatorIOP(PhantomData); -/// proof generated by prover -pub struct AccumulatorProof { - /// proof for bit decomposition - pub bit_decomposition_proof: BitDecompositionProof, - /// proof for ntt - pub ntt_proof: NTTProof, - /// proof for sumcheck - pub sumcheck_msg: Proof, -} - -/// subclaim reutrned to verifier -pub struct AccumulatorSubclaim { - /// subclaim returned from the Bit Decomposition IOP - pub bit_decomposition_subclaim: BitDecompositionSubClaim, - /// subclaim returned from the NTT IOP - pub ntt_subclaim: NTTSubclaim, - /// subclaim returned from the sumcheck protocol - pub sumcheck_subclaim: SubClaim, -} - +/// SNARKs for Accumulator compiled with PCS +pub struct AccumulatorSnarks>( + PhantomData, + PhantomData, +); /// accumulator witness when performing ACC = ACC + (X^{-a_u} + 1) * ACC * RGSW(Z_u) pub struct AccumulatorWitness { /// * Witness when performing input_rlwe_ntt := (X^{-a_u} + 1) * ACC /// - /// accumulator of ntt form - pub accumulator_ntt: RlweCiphertext, + /// ACC of ntt form + pub acc_ntt: RlweCiphertext, /// scalar d = (X^{-a_u} + 1) of coefficient form pub d: Rc>, /// scalar d = (X^{-a_u} + 1) of ntt form @@ -58,62 +60,272 @@ pub struct AccumulatorWitness { pub input_rlwe_ntt: RlweCiphertext, /// * Witness when performing output_rlwe_ntt := input_rlwe * RGSW(Z_u) where input_rlwe = (X^{-a_u} + 1) * ACC /// - /// result d * ACC of coefficient form - /// - /// rlwe = (a, b): store the input ciphertext (a, b) where a and b are two polynomials represented by N coefficients. - pub input_rlwe: RlweCiphertext, - /// bits_rlwe = (a_bits, b_bits): a_bits (b_bits) corresponds to the bit decomposition result of a (b) in the input rlwe ciphertext - pub bits_rlwe: RlweCiphertexts, - /// bits_rlwe_ntt: ntt form of the above bit decomposition result - pub bits_rlwe_ntt: RlweCiphertexts, - /// bits_rgsw_c_ntt: the ntt form of the first part (c) in the RGSW ciphertext - pub bits_rgsw_c_ntt: RlweCiphertexts, - /// bits_rgsw_c_ntt: the ntt form of the second part (f) in the RGSW ciphertext - pub bits_rgsw_f_ntt: RlweCiphertexts, - /// output_rlwe_ntt: store the output ciphertext (g', h') in the NTT-form - pub output_rlwe_ntt: RlweCiphertext, + /// result of RLWE * RGSW + pub rlwe_mult_rgsw: RlweMultRgswInstance, + // /// + // /// rlwe = (a, b): store the input ciphertext (a, b) where a and b are two polynomials represented by N coefficients. + // pub input_rlwe: RlweCiphertext, + // /// bits_rlwe = (a_bits, b_bits): a_bits (b_bits) corresponds to the bit decomposition result of a (b) in the input rlwe ciphertext + // pub bits_rlwe: RlweCiphertexts, + // /// bits_rlwe_ntt: ntt form of the above bit decomposition result + // pub bits_rlwe_ntt: RlweCiphertexts, + // /// bits_rgsw_c_ntt: the ntt form of the first part (c) in the RGSW ciphertext + // pub bits_rgsw_c_ntt: RlweCiphertexts, + // /// bits_rgsw_c_ntt: the ntt form of the second part (f) in the RGSW ciphertext + // pub bits_rgsw_f_ntt: RlweCiphertexts, + // /// output_rlwe_ntt: store the output ciphertext (g', h') in the NTT-form + // pub output_rlwe_ntt: RlweCiphertext, +} + +/// Evaluation of AccumulatorWitnessEval at the same random point +pub struct AccumulatorWitnessEval { + /// ACC of ntt form + pub acc_ntt: RlweEval, + /// scalar d = (X^{-a_u} + 1) of coefficient form + pub d: F, + /// scalar d = (X^{-a_u} + 1) of ntt form + pub d_ntt: F, + /// result d * ACC = RLWE of ntt form + pub input_rlwe_ntt: RlweEval, + /// result of RLWE * RGSW + pub rlwe_mult_rgsw: RlweMultRgswEval, } /// Store the ntt instance, bit decomposition instance, and the sumcheck instance for an Accumulator updating `t` times pub struct AccumulatorInstance { + /// number of variables + pub num_vars: usize, /// number of updations in Accumulator denoted by t pub num_updations: usize, - /// number of ntt transformation in Accumulator - pub num_ntt: usize, - /// the (virtually) randomized ntt instance to be proved - pub ntt_instance: NTTInstance, - /// all decomposed bits - pub decomposed_bits: DecomposedBits, - /// poly in the sumcheck instance - pub poly: ListOfProductsOfPolynomials, + /// input of the Accumulator, represented in coefficient form + pub input: RlweCiphertext, + /// input of the Accumulator, represented in NTT form + // pub input_ntt: RlweCiphertext, + /// witnesses stored in updations + pub updations: Vec>, + /// output of the Accumulator, represented in NTT form + pub output_ntt: RlweCiphertext, + /// output of the Accumulator, represented in coefficient form + pub output: RlweCiphertext, + /// info for RLWE * RGSW + pub mult_info: RlweMultRgswInfo, + /// info for decomposed bits + pub bits_info: DecomposedBitsInfo, + /// info for NTT + pub ntt_info: NTTInstanceInfo, +} + +/// Evaluation of AccumulatorInstance at the same random point +pub struct AccumulatorEval { + /// input of the Accumulator, represented in coefficient form + pub input: RlweEval, + /// witnesses stored in updations + pub updations: Vec>, + /// output of the Accumulator, represented in NTT form + pub output_ntt: RlweEval, + /// output of the Accumulator, represented in coefficient form + pub output: RlweEval, } /// Store the Accumulator info used to verify pub struct AccumulatorInstanceInfo { + /// number of variables + pub num_vars: usize, /// number of updations in Accumulator denoted by t pub num_updations: usize, - /// info to verify ntt + /// info for RLWE * RGSW + pub mult_info: RlweMultRgswInfo, + /// info for decomposed bits + pub bits_info: DecomposedBitsInfo, + /// info for NTT pub ntt_info: NTTInstanceInfo, - /// info to verify bit decomposition - pub decomposed_bits_info: DecomposedBitsInfo, - /// info to verify sumcheck - pub poly_info: PolynomialInfo, +} + +impl fmt::Display for AccumulatorInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "An instance of Accumulator: #vars = {}", self.num_vars)?; + write!(f, "- containing ")?; + self.bits_info.fmt(f)?; + write!(f, "\n- containing")?; + self.ntt_info.fmt(f) + } +} + +impl AccumulatorWitness { + /// Return the output_ntt + #[inline] + pub fn get_output(&self) -> RlweCiphertext { + self.rlwe_mult_rgsw.output_rlwe_ntt.clone() + } + + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 6 + self.rlwe_mult_rgsw.num_oracles() + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Return the number of ntt contained in this instance + #[inline] + pub fn num_ntt_contained(&self) -> usize { + self.rlwe_mult_rgsw.num_ntt_contained() + 3 + } + + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros + #[inline] + pub fn pack_all_mles(&self) -> Vec { + let mut res = self + .d + .iter() + .chain(self.d_ntt.iter()) + .copied() + .collect::>(); + res.append(&mut self.acc_ntt.pack_all_mles()); + res.append(&mut self.input_rlwe_ntt.pack_all_mles()); + res.append(&mut self.rlwe_mult_rgsw.pack_all_mles()); + res + } + + /// Convert to EF version + #[inline] + pub fn to_ef>(&self) -> AccumulatorWitness { + AccumulatorWitness:: { + acc_ntt: self.acc_ntt.to_ef::(), + d: Rc::new(self.d.to_ef::()), + d_ntt: Rc::new(self.d_ntt.to_ef::()), + input_rlwe_ntt: self.input_rlwe_ntt.to_ef::(), + rlwe_mult_rgsw: self.rlwe_mult_rgsw.to_ef::(), + } + } + + /// Evaluate at the same random point defined over F + #[inline] + pub fn evaluate(&self, point: &[F]) -> AccumulatorWitnessEval { + AccumulatorWitnessEval { + acc_ntt: self.acc_ntt.evaluate(point), + d: self.d.evaluate(point), + d_ntt: self.d_ntt.evaluate(point), + input_rlwe_ntt: self.input_rlwe_ntt.evaluate(point), + rlwe_mult_rgsw: self.rlwe_mult_rgsw.evaluate(point), + } + } + /// Evaluate at the same random point defined over EF + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> AccumulatorWitnessEval { + AccumulatorWitnessEval { + acc_ntt: self.acc_ntt.evaluate_ext(point), + d: self.d.evaluate_ext(point), + d_ntt: self.d_ntt.evaluate_ext(point), + input_rlwe_ntt: self.input_rlwe_ntt.evaluate_ext(point), + rlwe_mult_rgsw: self.rlwe_mult_rgsw.evaluate_ext(point), + } + } + + /// update the ntt instance to be proved + #[inline] + pub fn update_ntt_instance( + &self, + r_coeffs: &mut DenseMultilinearExtension, + r_points: &mut DenseMultilinearExtension, + randomness: &[F], + ) { + assert_eq!(randomness.len(), self.num_ntt_contained()); + // d ==NTT== d_ntt + let (r_used, r) = randomness.split_at(3); + *r_coeffs += (r_used[0], self.d.as_ref()); + *r_points += (r_used[0], self.d_ntt.as_ref()); + // input_rlwe ==NTT== input_rlwe_ntt + *r_coeffs += (r_used[1], self.rlwe_mult_rgsw.input_rlwe.a.as_ref()); + *r_points += (r_used[1], self.input_rlwe_ntt.a.as_ref()); + *r_coeffs += (r_used[2], self.rlwe_mult_rgsw.input_rlwe.b.as_ref()); + *r_points += (r_used[2], self.input_rlwe_ntt.b.as_ref()); + + self.rlwe_mult_rgsw + .update_ntt_instance(r_coeffs, r_points, r); + } + + /// update the ntt instance to be proved + #[inline] + pub fn update_ntt_instance_to_ef>( + &self, + r_coeffs: &mut DenseMultilinearExtension, + r_points: &mut DenseMultilinearExtension, + randomness: &[EF], + ) { + assert_eq!(randomness.len(), self.num_ntt_contained()); + // d ==NTT== d_ntt + let (r_used, r) = randomness.split_at(3); + add_assign_ef(r_coeffs, &r_used[0], self.d.as_ref()); + add_assign_ef(r_points, &r_used[0], self.d_ntt.as_ref()); + + // input_rlwe ==NTT== input_rlwe_ntt + add_assign_ef( + r_coeffs, + &r_used[1], + self.rlwe_mult_rgsw.input_rlwe.a.as_ref(), + ); + add_assign_ef(r_points, &r_used[1], self.input_rlwe_ntt.a.as_ref()); + add_assign_ef( + r_coeffs, + &r_used[2], + self.rlwe_mult_rgsw.input_rlwe.b.as_ref(), + ); + add_assign_ef(r_points, &r_used[2], self.input_rlwe_ntt.b.as_ref()); + + self.rlwe_mult_rgsw + .update_ntt_instance_to_ef::(r_coeffs, r_points, r); + } } impl AccumulatorInstance { /// construct an accumulator instance based on ntt info and bit-decomposition info + #[allow(clippy::too_many_arguments)] #[inline] pub fn new( num_vars: usize, + num_updations: usize, + input: RlweCiphertext, + updations: Vec>, + output_ntt: RlweCiphertext, + output: RlweCiphertext, + bits_info: &DecomposedBitsInfo, ntt_info: &NTTInstanceInfo, - decom_info: &DecomposedBitsInfo, ) -> Self { + let ntt_info = NTTInstanceInfo:: { + num_ntt: 4 + num_updations * updations[0].num_ntt_contained(), + num_vars, + ntt_table: Rc::clone(&ntt_info.ntt_table), + }; + + let bits_info = DecomposedBitsInfo:: { + num_vars, + base: bits_info.base, + base_len: bits_info.base_len, + bits_len: bits_info.bits_len, + num_instances: 2 * num_updations, + }; + + assert!(num_updations > 0); + let mult_info = updations[0].rlwe_mult_rgsw.info(); + assert_eq!(num_updations, updations.len()); Self { - num_updations: 0, - num_ntt: 0, - ntt_instance: >::from_info(ntt_info), - decomposed_bits: >::from_info(decom_info), - poly: >::new(num_vars), + num_vars, + num_updations, + input, + updations, + output, + output_ntt, + mult_info, + ntt_info, + bits_info, } } @@ -121,333 +333,782 @@ impl AccumulatorInstance { #[inline] pub fn info(&self) -> AccumulatorInstanceInfo { AccumulatorInstanceInfo { + num_vars: self.num_vars, num_updations: self.num_updations, - ntt_info: self.ntt_instance.info(), - decomposed_bits_info: self.decomposed_bits.info(), - poly_info: self.poly.info(), + mult_info: self.mult_info.clone(), + bits_info: self.bits_info.clone(), + ntt_info: self.ntt_info.clone(), } } - /// add witness - /// - /// # Arguments: - /// * randomness_ntt: randomness used for integrating (2k+3) ntt instances into the target ntt instance - /// * randomness_sumcheck: randomness used to integrating 2 sumcheck protocols - /// * identity_func_at_u: identity function at the random point u where u is chosen for sumcheck protocol - /// * witness: all intermediate witness when updating the accumulator once - pub fn add_witness( - &mut self, - randomness_ntt: &[F], - randomness_sumcheck: &[F], - identity_func_at_u: &Rc>, - witness: &AccumulatorWitness, - ) { - self.num_updations += 1; - assert_eq!( - randomness_ntt.len(), - ((self.decomposed_bits.bits_len << 1) + 3) as usize - ); - self.num_ntt += randomness_ntt.len(); - assert_eq!(randomness_sumcheck.len(), 2); + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 6 + self.num_updations * self.updations[0].num_oracles() + } - // Integrate the Bit-Decomposition Part - assert_eq!( - witness.bits_rlwe.a_bits.len(), - self.decomposed_bits.bits_len as usize - ); - assert_eq!( - witness.bits_rlwe.b_bits.len(), - self.decomposed_bits.bits_len as usize - ); - self.decomposed_bits - .add_decomposed_bits_instance(&witness.bits_rlwe.a_bits); - self.decomposed_bits - .add_decomposed_bits_instance(&witness.bits_rlwe.b_bits); - - // Integrate the NTT Part - let mut r = randomness_ntt.iter(); - self.ntt_instance - .add_ntt(*r.next().unwrap(), &witness.d, &witness.d_ntt); - self.ntt_instance.add_ntt( - *r.next().unwrap(), - &witness.input_rlwe.a, - &witness.input_rlwe_ntt.a, - ); - self.ntt_instance.add_ntt( - *r.next().unwrap(), - &witness.input_rlwe.b, - &witness.input_rlwe_ntt.b, - ); + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Return the number of NTT instances contained + #[inline] + pub fn num_ntt_contained(&self) -> usize { + 4 + self.num_updations * self.updations[0].num_ntt_contained() + } - // k ntt instances for a_i =NTT equal= a_i' - for (coeffs, points) in izip!(&witness.bits_rlwe.a_bits, &witness.bits_rlwe_ntt.a_bits) { - self.ntt_instance - .add_ntt(*r.next().unwrap(), coeffs, points); + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros. + pub fn pack_all_mles(&self) -> Vec { + let mut res = Vec::new(); + res.append(&mut self.input.pack_all_mles()); + res.append(&mut self.output_ntt.pack_all_mles()); + res.append(&mut self.output.pack_all_mles()); + for updation in &self.updations { + res.append(&mut updation.pack_all_mles()); } - // k ntt instances for b_i =NTT equal= b_i' - for (coeffs, points) in izip!(&witness.bits_rlwe.b_bits, &witness.bits_rlwe_ntt.b_bits) { - self.ntt_instance - .add_ntt(*r.next().unwrap(), coeffs, points); + res + } + + /// Generate the oracle to be committed that is composed of all the small oracles used in IOP. + /// The evaluations of this oracle is generated by the evaluations of all mles and the padded zeros. + /// The arrangement of this oracle should be consistent to its usage in verifying the subclaim. + #[inline] + pub fn generate_oracle(&self) -> DenseMultilinearExtension { + let num_vars_added = self.log_num_oracles(); + let num_vars = self.num_vars + num_vars_added; + let num_zeros_padded = ((1 << num_vars_added) - self.num_oracles()) * (1 << self.num_vars); + + let mut evals = self.pack_all_mles(); + evals.append(&mut vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// Construct a EF version + #[inline] + pub fn to_ef>(&self) -> AccumulatorInstance { + AccumulatorInstance:: { + num_vars: self.num_vars, + num_updations: self.num_updations, + input: self.input.to_ef::(), + updations: self + .updations + .iter() + .map(|updation| updation.to_ef::()) + .collect(), + output_ntt: self.output_ntt.to_ef::(), + output: self.output.to_ef::(), + mult_info: self.mult_info.to_ef::(), + bits_info: self.bits_info.to_ef::(), + ntt_info: self.ntt_info.to_ef::(), } + } - // Integrate the Sumcheck Part - let r_1 = randomness_sumcheck[0]; - let r_2 = randomness_sumcheck[1]; - // Sumcheck protocol for proving: g' = \sum_{i = 0}^{k-1} a_i' \cdot c_i + b_i' \cdot f_i - // When proving g'(x) = \sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) for x \in \{0, 1\}^\log n, - // prover claims the sum \sum_{x} eq(u, x) (\sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) - g'(x)) = 0 - // where u is randomly sampled by the verifier. - for (a, b, c, f) in izip!( - &witness.bits_rlwe_ntt.a_bits, - &witness.bits_rlwe_ntt.b_bits, - &witness.bits_rgsw_c_ntt.a_bits, - &witness.bits_rgsw_f_ntt.a_bits - ) { - let prod1 = [Rc::clone(a), Rc::clone(c), Rc::clone(identity_func_at_u)]; - let prod2 = [Rc::clone(b), Rc::clone(f), Rc::clone(identity_func_at_u)]; - self.poly.add_product(prod1, r_1); - self.poly.add_product(prod2, r_1); + /// Evaluate at the same random point + #[inline] + pub fn evaluate(&self, point: &[F]) -> AccumulatorEval { + AccumulatorEval:: { + input: self.input.evaluate(point), + output_ntt: self.output_ntt.evaluate(point), + output: self.output.evaluate(point), + updations: self + .updations + .iter() + .map(|updation| updation.evaluate(point)) + .collect(), } - // Sumcheck protocol for proving: h' = \sum_{i = 0}^{k-1} a_i' \cdot c_i' + b_i' \cdot f_i' - for (a, b, c, f) in izip!( - &witness.bits_rlwe_ntt.a_bits, - &witness.bits_rlwe_ntt.b_bits, - &witness.bits_rgsw_c_ntt.b_bits, - &witness.bits_rgsw_f_ntt.b_bits - ) { - let prod1 = [Rc::clone(a), Rc::clone(c), Rc::clone(identity_func_at_u)]; - let prod2 = [Rc::clone(b), Rc::clone(f), Rc::clone(identity_func_at_u)]; - self.poly.add_product(prod1, r_2); - self.poly.add_product(prod2, r_2); + } + + /// Evaluate at the same random point defiend over EF + #[inline] + pub fn evaluate_ext>(&self, point: &[EF]) -> AccumulatorEval { + AccumulatorEval:: { + input: self.input.evaluate_ext(point), + output_ntt: self.output_ntt.evaluate_ext(point), + output: self.output.evaluate_ext(point), + updations: self + .updations + .iter() + .map(|updation| updation.evaluate_ext(point)) + .collect(), } + } - self.poly.add_product( - [ - Rc::clone(&witness.output_rlwe_ntt.a), - Rc::clone(identity_func_at_u), - ], - -r_1, + /// Extract all ntt instances contained into a single random NTT instance + #[inline] + pub fn extract_ntt_instance(&self, randomness: &[F]) -> NTTInstance { + assert_eq!(randomness.len(), self.num_ntt_contained()); + let mut random_coeffs = >::from_evaluations_vec( + self.num_vars, + vec![F::zero(); 1 << self.num_vars], ); - self.poly.add_product( - [ - Rc::clone(&witness.output_rlwe_ntt.b), - Rc::clone(identity_func_at_u), - ], - -r_2, + let mut random_points = >::from_evaluations_vec( + self.num_vars, + vec![F::zero(); 1 << self.num_vars], ); + + let (r_used, r) = randomness.split_at(4); + // input ==NTT== input_ntt + let input_ntt = &self.updations[0].acc_ntt; + random_coeffs += (r_used[0], self.input.a.as_ref()); + random_points += (r_used[0], input_ntt.a.as_ref()); + random_coeffs += (r_used[1], self.input.b.as_ref()); + random_points += (r_used[1], input_ntt.b.as_ref()); + + // output_ntt ==NTT== output + random_coeffs += (r_used[2], self.output.a.as_ref()); + random_points += (r_used[2], self.output_ntt.a.as_ref()); + random_coeffs += (r_used[3], self.output.b.as_ref()); + random_points += (r_used[3], self.output_ntt.b.as_ref()); + + let r_each_num = self.updations[0].num_ntt_contained(); + // ntts in each accumulator + for (updation, r_each) in izip!(&self.updations, r.chunks_exact(r_each_num)) { + updation.update_ntt_instance(&mut random_coeffs, &mut random_points, r_each); + } + + NTTInstance:: { + num_vars: self.num_vars, + ntt_table: self.ntt_info.ntt_table.clone(), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), + } + } + + /// Extract all ntt instances contained into a single random NTT instance + #[inline] + pub fn extract_ntt_instance_to_ef>( + &self, + randomness: &[EF], + ) -> NTTInstance { + assert_eq!(randomness.len(), self.num_ntt_contained()); + let mut random_coeffs = >::from_evaluations_vec( + self.num_vars, + vec![EF::zero(); 1 << self.num_vars], + ); + let mut random_points = >::from_evaluations_vec( + self.num_vars, + vec![EF::zero(); 1 << self.num_vars], + ); + + let (r_used, r) = randomness.split_at(4); + // input ==NTT== input_ntt + let input_ntt = &self.updations[0].acc_ntt; + add_assign_ef(&mut random_coeffs, &r_used[0], self.input.a.as_ref()); + add_assign_ef(&mut random_points, &r_used[0], input_ntt.a.as_ref()); + add_assign_ef(&mut random_coeffs, &r_used[1], self.input.b.as_ref()); + add_assign_ef(&mut random_points, &r_used[1], input_ntt.b.as_ref()); + + // output_ntt ==NTT== output + add_assign_ef(&mut random_coeffs, &r_used[2], self.output.a.as_ref()); + add_assign_ef(&mut random_points, &r_used[2], self.output_ntt.a.as_ref()); + add_assign_ef(&mut random_coeffs, &r_used[3], self.output.b.as_ref()); + add_assign_ef(&mut random_points, &r_used[3], self.output_ntt.b.as_ref()); + + let r_each_num = self.updations[0].num_ntt_contained(); + // ntts in each accumulator + for (updation, r_each) in izip!(&self.updations, r.chunks_exact(r_each_num)) { + updation.update_ntt_instance_to_ef::( + &mut random_coeffs, + &mut random_points, + r_each, + ); + } + + NTTInstance:: { + num_vars: self.num_vars, + ntt_table: Rc::new( + self.ntt_info + .ntt_table + .iter() + .map(|x| EF::from_base(*x)) + .collect::>(), + ), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), + } } } -impl AccumulatorIOP { - /// prove the accumulator updation - pub fn prove(instance: &AccumulatorInstance, u: &[F]) -> AccumulatorProof { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, instance, u) +impl AccumulatorWitnessEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 6 + self.rlwe_mult_rgsw.num_oracles() } - /// prove the accumulator updation - pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, - instance: &AccumulatorInstance, - u: &[F], - ) -> AccumulatorProof { - AccumulatorProof { - bit_decomposition_proof: BitDecomposition::prove_as_subprotocol( - fs_rng, - &instance.decomposed_bits, - u, - ), - ntt_proof: NTTIOP::prove_as_subprotocol(fs_rng, &instance.ntt_instance, u), - sumcheck_msg: MLSumcheck::prove_as_subprotocol(fs_rng, &instance.poly) - .expect("sumcheck fail in accumulator updation") - .0, + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Flatten the evaluations into a Vector + #[inline] + pub fn flatten(&self) -> Vec { + let mut res = Vec::with_capacity(self.num_oracles()); + res.push(self.d); + res.push(self.d_ntt); + res.push(self.acc_ntt.0); + res.push(self.acc_ntt.1); + res.push(self.input_rlwe_ntt.0); + res.push(self.input_rlwe_ntt.1); + res.append(&mut self.rlwe_mult_rgsw.flatten()); + res + } + + /// Update the coefficients of the random NTT instance to be proved + #[inline] + pub fn update_ntt_instance_coeff(&self, r_coeff: &mut F, randomness: &[F]) { + let (r_used, r) = randomness.split_at(3); + *r_coeff += r_used[0] * self.d; + *r_coeff += r_used[1] * self.rlwe_mult_rgsw.input_rlwe.0; + *r_coeff += r_used[2] * self.rlwe_mult_rgsw.input_rlwe.1; + + self.rlwe_mult_rgsw.update_ntt_instance_coeff(r_coeff, r); + } + + /// Update the point-values of the random NTT instance to be proved + #[inline] + pub fn update_ntt_instance_point(&self, r_point: &mut F, randomness: &[F]) { + let (r_used, r) = randomness.split_at(3); + *r_point += r_used[0] * self.d_ntt; + *r_point += r_used[1] * self.input_rlwe_ntt.0; + *r_point += r_used[2] * self.input_rlwe_ntt.1; + + self.rlwe_mult_rgsw.update_ntt_instance_point(r_point, r); + } +} + +impl AccumulatorEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 6 + self.updations.len() * self.updations[0].num_oracles() + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Faltten all evaluations into a vector + #[inline] + pub fn flatten(&self) -> Vec { + let mut res = Vec::with_capacity(self.num_oracles()); + res.push(self.input.0); + res.push(self.input.1); + res.push(self.output_ntt.0); + res.push(self.output_ntt.1); + res.push(self.output.0); + res.push(self.output.1); + for updation in &self.updations { + res.append(&mut updation.flatten()); } + + res } - /// verify the proof - pub fn verify( - proof: &AccumulatorProof, - u: &[F], - info: &AccumulatorInstanceInfo, - ) -> AccumulatorSubclaim { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verify_as_subprotocol(&mut fs_rng, proof, u, info) + /// Update the coefficeint evaluation of the random NTT instance + #[inline] + pub fn update_ntt_instance_coeff(&self, r_coeff: &mut F, randomness: &[F]) { + let (r_used, r) = randomness.split_at(4); + *r_coeff += r_used[0] * self.input.0; + *r_coeff += r_used[1] * self.input.1; + *r_coeff += r_used[2] * self.output.0; + *r_coeff += r_used[3] * self.output.1; + + let r_each_num = r.len() / self.updations.len(); + for (updation, r_each) in izip!(&self.updations, r.chunks_exact(r_each_num)) { + updation.update_ntt_instance_coeff(r_coeff, r_each); + } } - /// verify the proof with provided RNG - pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &AccumulatorProof, - u: &[F], - info: &AccumulatorInstanceInfo, - ) -> AccumulatorSubclaim { - AccumulatorSubclaim { - bit_decomposition_subclaim: BitDecomposition::verifier_as_subprotocol( - fs_rng, - &proof.bit_decomposition_proof, - &info.decomposed_bits_info, - ), - ntt_subclaim: NTTIOP::verify_as_subprotocol( - fs_rng, - &proof.ntt_proof, - &info.ntt_info, - u, - ), - sumcheck_subclaim: MLSumcheck::verify_as_subprotocol( - fs_rng, - &info.poly_info, - F::zero(), - &proof.sumcheck_msg, - ) - .expect("sumcheck protocol in rlwe mult rgsw failed"), + /// Update the point evaluation of the random NTT instance + #[inline] + pub fn update_ntt_instance_point(&self, r_point: &mut F, randomness: &[F]) { + let (r_used, r) = randomness.split_at(4); + let input_ntt = &self.updations[0].acc_ntt; + *r_point += r_used[0] * input_ntt.0; + *r_point += r_used[1] * input_ntt.1; + + *r_point += r_used[2] * self.output_ntt.0; + *r_point += r_used[3] * self.output_ntt.1; + + let r_each_num = r.len() / self.updations.len(); + for (updation, r_each) in izip!(&self.updations, r.chunks_exact(r_each_num)) { + updation.update_ntt_instance_point(r_point, r_each); } } } -impl AccumulatorSubclaim { - /// verify the subclaim - /// - /// # Arguments - /// * u: random point chosen by verifier - /// * randomness_ntt: randomness used to combine ntt instances - /// * randomness_sumecheck: randomness used to combine sumcheck protocols - /// * ntt_coeffs: the final random ntt instance to be proved - /// * ntt_points: the final random ntt instance to be proved - /// * witness: all the winess when updating the accumulator - /// * info: info used to verify - #[allow(clippy::too_many_arguments)] - pub fn verify_subclaim( - &self, - u: &[F], - randomness_ntt: &[F], - randomness_sumcheck: &[F], - ntt_coeffs: &DenseMultilinearExtension, - ntt_points: &DenseMultilinearExtension, - witnesses: &Vec>, +impl AccumulatorIOP { + /// sample coins before proving sumcheck protocol + pub fn sample_coins(trans: &mut Transcript, instance: &AccumulatorInstance) -> Vec { + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + instance.num_updations + * (>::num_coins(&instance.updations[0].rlwe_mult_rgsw.info()) + + 2), + ) + } + + /// Return the number of random coins used in this IOP + pub fn num_coins(info: &AccumulatorInstanceInfo) -> usize { + info.num_updations * (>::num_coins(&info.mult_info) + 2) + } + + /// Prove accumulator updating `num_updations`` times + #[inline] + pub fn prove(instance: &AccumulatorInstance) -> (SumcheckKit, NTTRecursiveProof) { + let mut trans = Transcript::new(); + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&u)); + let randomness = Self::sample_coins(&mut trans, instance); + let randomness_ntt = >::sample_coins(&mut trans, instance.num_ntt_contained()); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let mut claimed_sum = F::zero(); + // add sumcheck products (without NTT) into poly + Self::prove_as_subprotocol(&randomness, &mut poly, instance, &eq_at_u); + + // add sumcheck_products of NTT into poly + let ntt_instance = instance.extract_ntt_instance(&randomness_ntt); + >::prove_as_subprotocol( + F::one(), + &mut poly, + &mut claimed_sum, + &ntt_instance, + &u, + ); + + // prove all sumcheck protocol into a large random sumcheck + let (proof, state) = MLSumcheck::prove_as_subprotocol(&mut trans, &poly) + .expect("fail to prove the sumcheck protocol"); + + // prove F(u, v) in a recursive manner + let recursive_proof = + >::prove_recursive(&mut trans, &state.randomness, &ntt_instance.info(), &u); + + ( + SumcheckKit { + proof, + claimed_sum, + info: poly.info(), + u, + randomness: state.randomness, + }, + recursive_proof, + ) + } + + /// Verify the accumulator updating `num_updations` times + #[inline] + pub fn verify( + wrapper: &mut ProofWrapper, + evals_at_r: &AccumulatorEval, + evals_at_u: &AccumulatorEval, info: &AccumulatorInstanceInfo, + recursive_proof: &NTTRecursiveProof, ) -> bool { - let num_ntt_instance = - (info.num_updations as u32) * ((info.decomposed_bits_info.bits_len << 1) + 3); - assert_eq!(randomness_ntt.len(), num_ntt_instance as usize); - assert_eq!(u.len(), info.ntt_info.log_n); - assert_eq!(randomness_sumcheck.len(), 2 * info.num_updations); - - // check 1: check the consistency of the randomized ntt instance and the original ntt instances - let mut coeffs_eval = F::zero(); - let mut points_eval = F::zero(); - let mut r_iter = randomness_ntt.iter(); - - for witness in witnesses { - let r = r_iter.next().unwrap(); - coeffs_eval += *r * witness.d.evaluate(u); - points_eval += *r * witness.d_ntt.evaluate(u); - let r = r_iter.next().unwrap(); - coeffs_eval += *r * witness.input_rlwe.a.evaluate(u); - points_eval += *r * witness.input_rlwe_ntt.a.evaluate(u); - let r = r_iter.next().unwrap(); - coeffs_eval += *r * witness.input_rlwe.b.evaluate(u); - points_eval += *r * witness.input_rlwe_ntt.b.evaluate(u); - - for (coeffs, points) in izip!(&witness.bits_rlwe.a_bits, &witness.bits_rlwe_ntt.a_bits) - { - let r = r_iter.next().unwrap(); - coeffs_eval += *r * coeffs.evaluate(u); - points_eval += *r * points.evaluate(u); - } + let mut trans = Transcript::new(); - for (coeffs, points) in izip!(&witness.bits_rlwe.b_bits, &witness.bits_rlwe_ntt.b_bits) - { - let r = r_iter.next().unwrap(); - coeffs_eval += *r * coeffs.evaluate(u); - points_eval += *r * points.evaluate(u); - } - } - if coeffs_eval != ntt_coeffs.evaluate(u) || points_eval != ntt_points.evaluate(u) { + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + info.num_vars, + ); + + // randomness to combine sumcheck protocols + let randomness = trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(info), + ); + let randomness_ntt = trans.get_vec_challenge( + b"randomness used to obtain the virtual random ntt instance", + >::num_coins(&info.ntt_info), + ); + + let mut subclaim = MLSumcheck::verify_as_subprotocol( + &mut trans, + &wrapper.info, + wrapper.claimed_sum, + &wrapper.proof, + ) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&u, &subclaim.point); + + // check the sumcheck evaluation (without NTT) + if !Self::verify_as_subprotocol(&randomness, &mut subclaim, evals_at_r, info, eq_at_u_r) { return false; } - // TODO: For ease of implementation, we pass the resulting randomized ntt_instance but it can be omitted after combined with PCS. - // check 2: check the subclaim returned from the ntt iop - if !self - .ntt_subclaim - .verify_subcliam(ntt_points, ntt_coeffs, u, &info.ntt_info) - { + let f_delegation = recursive_proof.delegation_claimed_sums[0]; + // one is to evaluate the random linear combination of evaluations at point r returned from sumcheck protocol + let mut ntt_coeff_evals_at_r = F::zero(); + evals_at_r.update_ntt_instance_coeff(&mut ntt_coeff_evals_at_r, &randomness_ntt); + // the other is to evaluate the random linear combination of evaluations at point u sampled before the sumcheck protocol + let mut ntt_point_evals_at_u = F::zero(); + evals_at_u.update_ntt_instance_point(&mut ntt_point_evals_at_u, &randomness_ntt); + + if !>::verify_as_subprotocol( + F::one(), + &mut subclaim, + &mut wrapper.claimed_sum, + ntt_coeff_evals_at_r, + ntt_point_evals_at_u, + f_delegation, + ) { return false; } - // check 3: check the subclaim returned from the bit decomposition iop - let mut d_bits = Vec::with_capacity(2 * info.num_updations); - let mut d_val = Vec::with_capacity(2 * info.num_updations); - for witness in witnesses { - d_bits.push(&witness.bits_rlwe.a_bits); - d_bits.push(&witness.bits_rlwe.b_bits); - d_val.push(Rc::clone(&witness.input_rlwe.a)); - d_val.push(Rc::clone(&witness.input_rlwe.b)); - } - if !self.bit_decomposition_subclaim.verify_subclaim( - &d_val, - &d_bits, - u, - &info.decomposed_bits_info, - ) { + if !(subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero()) { return false; } + >::verify_recursive(&mut trans, recursive_proof, &info.ntt_info, &u, &subclaim) + } + /// Prover Accumulator + #[inline] + pub fn prove_as_subprotocol( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &AccumulatorInstance, + eq_at_u: &Rc>, + ) { + let r_each_num = RlweMultRgswIOP::num_coins(&instance.mult_info) + 2; + assert_eq!(randomness.len(), instance.num_updations * r_each_num); - let mut r = randomness_sumcheck.iter(); - - // 4. check 4: check the subclaim returned from the sumcheck protocol consisting of two sub-sumcheck protocols - let mut sum_eval = F::zero(); - for witness in witnesses { - let mut sum1_eval = F::zero(); - let mut sum2_eval = F::zero(); - // The first part is to evaluate at a random point g' = \sum_{i = 0}^{k-1} a_i' \cdot c_i + b_i' \cdot f_i - // It is the reduction claim of prover asserting the sum \sum_{x} eq(u, x) (\sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) - g'(x)) = 0 - // where u is randomly sampled by the verifier. - for (a, b, c, f) in izip!( - &witness.bits_rlwe_ntt.a_bits, - &witness.bits_rlwe_ntt.b_bits, - &witness.bits_rgsw_c_ntt.a_bits, - &witness.bits_rgsw_f_ntt.a_bits - ) { - sum1_eval += (a.evaluate(&self.sumcheck_subclaim.point) - * c.evaluate(&self.sumcheck_subclaim.point)) - + (b.evaluate(&self.sumcheck_subclaim.point) - * f.evaluate(&self.sumcheck_subclaim.point)); - } + // in other updations, acc_ntt = acc_ntt (in last updation) + ouput_ntt of RLWE * RGSW + for (updation, r) in izip!(&instance.updations, randomness.chunks_exact(r_each_num)) { + let (r, r_mult) = r.split_at(2); + // When proving ACC = ACC + (x^a_u - 1) * ACC * RGSW + // step 1. `ACC` * `d` = RLWE + // sum_x eq(u, x) * (ACC.a(x) * d(x) - a(x)) = 0 + poly.add_product( + [ + Rc::clone(&updation.d_ntt), + Rc::clone(&updation.acc_ntt.a), + Rc::clone(eq_at_u), + ], + r[0], + ); + poly.add_product( + [Rc::clone(&updation.input_rlwe_ntt.a), Rc::clone(eq_at_u)], + -r[0], + ); + // sum_x eq(u, x) * (ACC.b(x) * d(x) - RLWE.b(x)) = 0 + poly.add_product( + [ + Rc::clone(&updation.d_ntt), + Rc::clone(&updation.acc_ntt.b), + Rc::clone(eq_at_u), + ], + r[1], + ); + poly.add_product( + [Rc::clone(&updation.input_rlwe_ntt.b), Rc::clone(eq_at_u)], + -r[1], + ); + + // step2: RLWE * RGSW + >::prove_as_subprotocol( + r_mult, + poly, + &updation.rlwe_mult_rgsw, + eq_at_u, + ); + } + } - // The second part is to evaluate at a random point h' = \sum_{i = 0}^{k-1} a_i' \cdot c_i' + b_i' \cdot f_i' - // It is the reduction claim of prover asserting the sum \sum_{x} eq(u, x) (\sum_{i = 0}^{k-1} a_i'(x) \cdot c_i'(x) + b_i'(x) \cdot f_i'(x) - g'(x)) = 0 - // where u is randomly sampled by the verifier. - for (a, b, c, f) in izip!( - &witness.bits_rlwe_ntt.a_bits, - &witness.bits_rlwe_ntt.b_bits, - &witness.bits_rgsw_c_ntt.b_bits, - &witness.bits_rgsw_f_ntt.b_bits + /// Verify the sumcheck part of accumulator updations (not including NTT part) + #[inline] + pub fn verify_as_subprotocol( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &AccumulatorEval, + info: &AccumulatorInstanceInfo, + eq_at_u_r: F, + ) -> bool { + let r_each_num = RlweMultRgswIOP::num_coins(&info.mult_info) + 2; + assert_eq!(randomness.len(), info.num_updations * r_each_num); + + // check the sumcheck part + for (updation, r) in izip!(&evals.updations, randomness.chunks_exact(r_each_num)) { + let (r, r_mult) = r.split_at(2); + subclaim.expected_evaluations -= eq_at_u_r + * (r[0] * (updation.d_ntt * updation.acc_ntt.0 - updation.input_rlwe_ntt.0) + + r[1] * (updation.d_ntt * updation.acc_ntt.1 - updation.input_rlwe_ntt.1)); + if !RlweMultRgswIOP::verify_as_subprotocol( + r_mult, + subclaim, + &updation.rlwe_mult_rgsw, + &info.mult_info, + eq_at_u_r, ) { - sum2_eval += (a.evaluate(&self.sumcheck_subclaim.point) - * c.evaluate(&self.sumcheck_subclaim.point)) - + (b.evaluate(&self.sumcheck_subclaim.point) - * f.evaluate(&self.sumcheck_subclaim.point)); + return false; } + } - let r_1 = r.next().unwrap(); - let r_2 = r.next().unwrap(); - sum_eval += eval_identity_function(u, &self.sumcheck_subclaim.point) - * (*r_1 - * (sum1_eval - - witness - .output_rlwe_ntt - .a - .evaluate(&self.sumcheck_subclaim.point)) - + *r_2 - * (sum2_eval - - witness - .output_rlwe_ntt - .b - .evaluate(&self.sumcheck_subclaim.point))) + // check the equality relations among the accmulator updations + for (this, next) in evals.updations.iter().tuple_windows() { + let this_acc = &this.acc_ntt; + let this_mult = &this.rlwe_mult_rgsw.output_rlwe_ntt; + let next_acc = &next.acc_ntt; + if !(this_acc.0 + this_mult.0 == next_acc.0 && this_acc.1 + this_mult.1 == next_acc.1) { + return false; + } } - sum_eval == self.sumcheck_subclaim.expected_evaluations + true + } +} + +impl AccumulatorSnarks +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, +{ + /// Complied with PCS to get SNARKs + pub fn snarks(instance: &AccumulatorInstance, code_spec: &S) + where + H: Hash + Sync + Send, + C: LinearCode + Serialize + for<'de> Deserialize<'de>, + S: LinearCodeSpec + Clone, + { + let instance_info = instance.info(); + println!("Prove {instance_info}\n"); + // This is the actual polynomial to be committed for prover, which consists of all the required small polynomials in the IOP and padded zero polynomials. + let committed_poly = instance.generate_oracle(); + // 1. Use PCS to commit the above polynomial. + let start = Instant::now(); + let pp = + BrakedownPCS::::setup(committed_poly.num_vars, Some(code_spec.clone())); + let setup_time = start.elapsed().as_millis(); + + let start = Instant::now(); + let (comm, comm_state) = BrakedownPCS::::commit(&pp, &committed_poly); + let commit_time = start.elapsed().as_millis(); + + // 2. Prover generates the proof + let prover_start = Instant::now(); + let mut iop_proof_size = 0; + let mut prover_trans = Transcript::::new(); + // Convert the original instance into an instance defined over EF + let instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); + + // 2.1 Generate the random point to instantiate the sumcheck protocol + let prover_u = prover_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&prover_u)); + + // 2.2 Construct the polynomial and the claimed sum to be proved in the sumcheck protocol + let mut sumcheck_poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let mut claimed_sum = EF::zero(); + let randomness = AccumulatorIOP::sample_coins(&mut prover_trans, &instance_ef); + let randomness_ntt = + >::sample_coins(&mut prover_trans, instance_info.ntt_info.num_ntt); + AccumulatorIOP::::prove_as_subprotocol( + &randomness, + &mut sumcheck_poly, + &instance_ef, + &eq_at_u, + ); + + // 2.? Prover extract the random ntt instance from all ntt instances + let ntt_instance = instance.extract_ntt_instance_to_ef::(&randomness_ntt); + >::prove_as_subprotocol( + EF::one(), + &mut sumcheck_poly, + &mut claimed_sum, + &ntt_instance, + &prover_u, + ); + let poly_info = sumcheck_poly.info(); + let ntt_instance_info = ntt_instance.info(); + + // 2.3 Generate proof of sumcheck protocol + let (sumcheck_proof, sumcheck_state) = + >::prove_as_subprotocol(&mut prover_trans, &sumcheck_poly) + .expect("Proof generated in Accumulator"); + iop_proof_size += bincode::serialize(&sumcheck_proof).unwrap().len(); + + // 2.? [one more step] Prover recursive prove the evaluation of F(u, v) + let recursive_proof = >::prove_recursive( + &mut prover_trans, + &sumcheck_state.randomness, + &ntt_instance_info, + &prover_u, + ); + iop_proof_size += bincode::serialize(&recursive_proof).unwrap().len(); + let iop_prover_time = prover_start.elapsed().as_millis(); + + // 2.4 Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let start = Instant::now(); + let evals_at_r = instance.evaluate_ext(&sumcheck_state.randomness); + let evals_at_u = instance.evaluate_ext(&prover_u); + + // 2.5 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point_at_r = sumcheck_state.randomness.clone(); + let mut requested_point_at_u = prover_u.clone(); + let oracle_randomness = prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_oracles(), + ); + requested_point_at_r.extend(&oracle_randomness); + requested_point_at_u.extend(&oracle_randomness); + let oracle_eval_at_r = committed_poly.evaluate_ext(&requested_point_at_r); + let oracle_eval_at_u = committed_poly.evaluate_ext(&requested_point_at_u); + + // 2.6 Generate the evaluation proof of the requested point + let eval_proof_at_r = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &requested_point_at_r, + &mut prover_trans, + ); + let eval_proof_at_u = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &requested_point_at_u, + &mut prover_trans, + ); + let pcs_open_time = start.elapsed().as_millis(); + + // 3. Verifier checks the proof + let verifier_start = Instant::now(); + let mut verifier_trans = Transcript::::new(); + + // 3.1 Generate the random point to instantiate the sumcheck protocol + let verifier_u = verifier_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + // 3.2 Generate the randomness used to randomize all the sub-sumcheck protocols + let randomness = verifier_trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance_info), + ); + let randomness_ntt = verifier_trans.get_vec_challenge( + b"randomness used to obtain the virtual random ntt instance", + >::num_coins(&instance_info.ntt_info), + ); + + // 3.3 Check the proof of the sumcheck protocol + let mut subclaim = >::verify_as_subprotocol( + &mut verifier_trans, + &poly_info, + claimed_sum, + &sumcheck_proof, + ) + .expect("Verify the proof generated in ACC"); + let eq_at_u_r = eval_identity_function(&verifier_u, &subclaim.point); + + // 3.4 Check the evaluation over a random point of the polynomial proved in the sumcheck protocol using evaluations over these small oracles used in IOP + let check_subclaim = AccumulatorIOP::::verify_as_subprotocol( + &randomness, + &mut subclaim, + &evals_at_r, + &instance_info, + eq_at_u_r, + ); + assert!(check_subclaim); + + // 3.? Check the NTT part + let f_delegation = recursive_proof.delegation_claimed_sums[0]; + // one is to evaluate the random linear combination of evaluations at point r returned from sumcheck protocol + let mut ntt_coeff_evals_at_r = EF::zero(); + evals_at_r.update_ntt_instance_coeff(&mut ntt_coeff_evals_at_r, &randomness_ntt); + // the other is to evaluate the random linear combination of evaluations at point u sampled before the sumcheck protocol + let mut ntt_point_evals_at_u = EF::zero(); + evals_at_u.update_ntt_instance_point(&mut ntt_point_evals_at_u, &randomness_ntt); + + // check the sumcheck part of NTT + let check_ntt_bare = >::verify_as_subprotocol( + EF::one(), + &mut subclaim, + &mut claimed_sum, + ntt_coeff_evals_at_r, + ntt_point_evals_at_u, + f_delegation, + ); + assert!(check_ntt_bare); + assert_eq!(subclaim.expected_evaluations, EF::zero()); + assert_eq!(claimed_sum, EF::zero()); + // check the recursive part of NTT + let check_recursive = >::verify_recursive( + &mut verifier_trans, + &recursive_proof, + &ntt_instance_info, + &verifier_u, + &subclaim, + ); + assert!(check_recursive); + + // 3.5 and also check the relation between these small oracles and the committed oracle + let start = Instant::now(); + let mut pcs_proof_size = 0; + let flatten_evals_at_r = evals_at_r.flatten(); + let flatten_evals_at_u = evals_at_u.flatten(); + let oracle_randomness = verifier_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + evals_at_r.log_num_oracles(), + ); + let check_oracle_at_r = + verify_oracle_relation(&flatten_evals_at_r, oracle_eval_at_r, &oracle_randomness); + let check_oracle_at_u = + verify_oracle_relation(&flatten_evals_at_u, oracle_eval_at_u, &oracle_randomness); + assert!(check_oracle_at_r && check_oracle_at_u); + let iop_verifier_time = verifier_start.elapsed().as_millis(); + + // 3.5 Check the evaluation of a random point over the committed oracle + let check_pcs_at_r = BrakedownPCS::::verify( + &pp, + &comm, + &requested_point_at_r, + oracle_eval_at_r, + &eval_proof_at_r, + &mut verifier_trans, + ); + let check_pcs_at_u = BrakedownPCS::::verify( + &pp, + &comm, + &requested_point_at_u, + oracle_eval_at_u, + &eval_proof_at_u, + &mut verifier_trans, + ); + assert!(check_pcs_at_r && check_pcs_at_u); + let pcs_verifier_time = start.elapsed().as_millis(); + pcs_proof_size += bincode::serialize(&eval_proof_at_r).unwrap().len() + + bincode::serialize(&eval_proof_at_u).unwrap().len() + + bincode::serialize(&flatten_evals_at_r).unwrap().len() + + bincode::serialize(&flatten_evals_at_u).unwrap().len(); + + // 4. print statistic + print_statistic( + iop_prover_time + pcs_open_time, + iop_verifier_time + pcs_verifier_time, + iop_proof_size + pcs_proof_size, + iop_prover_time, + iop_verifier_time, + iop_proof_size, + committed_poly.num_vars, + instance.num_oracles(), + instance.num_vars, + setup_time, + commit_time, + pcs_open_time, + pcs_verifier_time, + pcs_proof_size, + ); } } diff --git a/zkp/src/piop/addition_in_zq.rs b/zkp/src/piop/addition_in_zq.rs index 80107796..e16b3a31 100644 --- a/zkp/src/piop/addition_in_zq.rs +++ b/zkp/src/piop/addition_in_zq.rs @@ -10,46 +10,37 @@ //! where u is the common random challenge from the verifier, used to instantiate the sum, //! and then, it can be proved with the sumcheck protocol where the maximum variable-degree is 3. //! 3. a(x) + b(x) = c(x) + k(x)\cdot q => can be reduced to the evaluation of a random point since the LHS and RHS are both MLE - -use std::marker::PhantomData; -use std::rc::Rc; - -use super::bit_decomposition::{ - BitDecompositionProof, BitDecompositionSubClaim, DecomposedBits, DecomposedBitsInfo, +use crate::sumcheck::{verifier::SubClaim, MLSumcheck}; +use crate::sumcheck::{ProofWrapper, SumcheckKit}; +use crate::utils::{ + eval_identity_function, gen_identity_evaluations, print_statistic, verify_oracle_relation, }; -use crate::piop::BitDecomposition; -use crate::sumcheck::prover::ProverMsg; -use crate::utils::eval_identity_function; - -use crate::sumcheck::MLSumcheck; -use crate::utils::gen_identity_evaluations; use algebra::{ - DecomposableField, DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, - MultilinearExtension, PolynomialInfo, + utils::Transcript, AbstractExtensionField, DecomposableField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, MultilinearExtension, }; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; - -/// SNARKs for addition in Zq, i.e. a + b = c (mod q) -pub struct AdditionInZq(PhantomData); +use core::fmt; +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{LinearCode, LinearCodeSpec}, + utils::hash::Hash, + PolynomialCommitmentScheme, +}; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use std::rc::Rc; +use std::time::Instant; -/// proof generated by prover -pub struct AdditionInZqProof { - /// batched rangecheck proof for a, b, c \in Zq - pub rangecheck_msg: BitDecompositionProof, - /// sumcheck proof for \sum_{x} eq(u, x) * k(x) * (1-k(x)) = 0, i.e. k(x)\in\{0,1\}^l - pub sumcheck_msg: Vec>, -} +use super::bit_decomposition::DecomposedBitsEval; +use super::{BitDecomposition, DecomposedBits, DecomposedBitsInfo}; -/// subclaim returned to verifier -pub struct AdditionInZqSubclaim { - /// rangecheck subclaim for a, b, c \in Zq - pub(crate) rangecheck_subclaim: BitDecompositionSubClaim, - /// subcliam for \sum_{x} eq(u, x) * k(x) * (1-k(x)) = 0 - pub sumcheck_point: Vec, - /// expected value returned in the last round of the sumcheck - pub sumcheck_expected_evaluations: F, -} +/// IOP for addition in Zq, i.e. a + b = c (mod q) +pub struct AdditionInZq(PhantomData); +/// SNARKs for addition in Zq compied with PCS +pub struct AdditionInZqSnarks>( + PhantomData, + PhantomData, +); /// Stores the parameters used for addition in Zq and the inputs and witness for prover. pub struct AdditionInZqInstance { @@ -62,15 +53,41 @@ pub struct AdditionInZqInstance { /// introduced witness k pub k: Rc>, /// introduced witness to check the range of a, b, c - pub abc_bits: DecomposedBits, + pub abc_bits: Vec>>, + /// info for decomposed bits + pub bits_info: DecomposedBitsInfo, +} + +/// Evaluations of all MLEs involed in the instance at a random point +pub struct AdditionInZqInstanceEval { + /// inputs a, b, and c + pub abc: Vec, + /// introduced witness k + pub k: F, + /// introduced witness to check the range of a, b, c + pub abc_bits: Vec, } /// Stores the parameters used for addition in Zq and the public info for verifier. pub struct AdditionInZqInstanceInfo { /// modulus in addition pub q: F, + /// number of variables + pub num_vars: usize, /// Decomposition info for range check (i.e. bit decomposition) - pub decomposed_bits_info: DecomposedBitsInfo, + pub bits_info: DecomposedBitsInfo, +} + +impl fmt::Display for AdditionInZqInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "An instance of Addition In Zq: #vars = {}, q = {}", + self.num_vars, self.q, + )?; + write!(f, "- containing ")?; + self.bits_info.fmt(f) + } } impl AdditionInZqInstance { @@ -79,206 +96,473 @@ impl AdditionInZqInstance { pub fn info(&self) -> AdditionInZqInstanceInfo { AdditionInZqInstanceInfo { q: self.q, - decomposed_bits_info: self.abc_bits.info(), + num_vars: self.num_vars, + bits_info: self.bits_info.clone(), } } -} -impl AdditionInZqInstance { - /// Construct a new instance from vector + /// Return the number of small polynomials used in IOP #[inline] - pub fn from_vec( - abc: Vec>>, - k: &Rc>, - q: F, - base: F, - base_len: u32, - bits_len: u32, - ) -> Self { - let num_vars = k.num_vars; - assert_eq!(abc.len(), 3); - for x in &abc { - assert_eq!(x.num_vars, num_vars); - } + pub fn num_oracles(&self) -> usize { + assert_eq!(self.abc.len(), 3); + assert_eq!(self.abc_bits.len(), 3 * self.bits_info.bits_len); + self.abc.len() + 1 + self.abc_bits.len() + } - let abc_bits = abc + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros. + pub fn pack_all_mles(&self) -> Vec { + // arrangement: abc || k || abc_bits + self.abc .iter() - .map(|x| x.get_decomposed_mles(base_len, bits_len)) - .collect(); - Self { - q, - num_vars, - abc, - k: Rc::clone(k), - abc_bits: DecomposedBits { - base, - base_len, - bits_len, - num_vars, - instances: abc_bits, - }, + .flat_map(|v| v.iter()) + .chain(self.k.iter()) + .chain(self.abc_bits.iter().flat_map(|bit| bit.iter())) + .copied() + .collect::>() + } + + /// Generate the oracle to be committed that is composed of all the small oracles used in IOP. + /// The evaluations of this oracle is generated by the evaluations of all mles and the padded zeros. + /// The arrangement of this oracle should be consistent to its usage in verifying the subclaim. + pub fn generate_oracle(&self) -> DenseMultilinearExtension { + let num_vars_added = self.log_num_oracles(); + let num_vars = self.num_vars + num_vars_added; + let num_zeros_padded = ((1 << num_vars_added) - self.num_oracles()) * (1 << self.num_vars); + + // arrangement: all values||all decomposed bits||padded zeros + let mut evals = self.pack_all_mles(); + evals.append(&mut vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// Construct a EF version + pub fn to_ef>(&self) -> AdditionInZqInstance { + AdditionInZqInstance:: { + q: EF::from_base(self.q), + num_vars: self.num_vars, + abc: self.abc.iter().map(|v| Rc::new(v.to_ef())).collect(), + k: Rc::new(self.k.to_ef()), + abc_bits: self + .abc_bits + .iter() + .map(|bit| Rc::new(bit.to_ef())) + .collect(), + bits_info: self.bits_info.to_ef::(), + } + } + + /// Evaluate at a random point defined over Field + #[inline] + pub fn evaluate(&self, point: &[F]) -> AdditionInZqInstanceEval { + AdditionInZqInstanceEval:: { + abc: self.abc.iter().map(|v| v.evaluate(point)).collect(), + k: self.k.evaluate(point), + abc_bits: self + .abc_bits + .iter() + .map(|bit| bit.evaluate(point)) + .collect(), + } + } + + /// Evaluate at a random point defined over Extension Field + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> AdditionInZqInstanceEval { + AdditionInZqInstanceEval:: { + abc: self.abc.iter().map(|v| v.evaluate_ext(point)).collect(), + k: self.k.evaluate_ext(point), + abc_bits: self + .abc_bits + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), + } + } + + /// Extract DecomposedBits instance + #[inline] + pub fn extract_decomposed_bits(&self) -> DecomposedBits { + DecomposedBits { + base: self.bits_info.base, + base_len: self.bits_info.base_len, + bits_len: self.bits_info.bits_len, + num_vars: self.num_vars, + d_val: self.abc.to_owned(), + d_bits: self.abc_bits.to_owned(), + } + } +} + +impl AdditionInZqInstanceEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + // number of value oracle + number of decomposed bits oracle + self.abc.len() + 1 + self.abc_bits.len() + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Flatten all evals into a vector with the same arrangement of the committed polynomial + #[inline] + pub fn flatten(&self) -> Vec { + let mut res = Vec::with_capacity(self.num_oracles()); + res.extend(self.abc.iter()); + res.push(self.k); + res.extend(self.abc_bits.iter()); + res + } + + /// Extract DecomposedBitsEval instance + #[inline] + pub fn extract_decomposed_bits(&self) -> DecomposedBitsEval { + DecomposedBitsEval { + d_val: self.abc.to_owned(), + d_bits: self.abc_bits.to_owned(), } } +} +impl AdditionInZqInstance { /// Construct a new instance from slice #[inline] pub fn from_slice( abc: &[Rc>], k: &Rc>, q: F, - base: F, - base_len: u32, - bits_len: u32, + bits_info: &DecomposedBitsInfo, ) -> Self { let num_vars = k.num_vars; assert_eq!(abc.len(), 3); + assert_eq!(bits_info.num_instances, 3); for x in abc { assert_eq!(x.num_vars, num_vars); } let abc_bits = abc .iter() - .map(|x| x.get_decomposed_mles(base_len, bits_len)) + .flat_map(|x| x.get_decomposed_mles(bits_info.base_len, bits_info.bits_len)) .collect(); + Self { q, num_vars, abc: abc.to_owned(), k: Rc::clone(k), - abc_bits: DecomposedBits { - base, - base_len, - bits_len, - num_vars, - instances: abc_bits, - }, + abc_bits, + bits_info: bits_info.clone(), } } } -impl AdditionInZqSubclaim { - /// verify the sumcliam - /// * abc stores the inputs and the output to be added in Zq - /// * k stores the introduced witness s.t. a + b = c + k\cdot q - /// * abc_bits stores the decomposed bits for a, b, and c - /// * u is the common random challenge from the verifier, used to instantiate the sumcheck. - #[inline] - pub fn verify_subclaim( - &self, - q: F, - abc: &[Rc>], - k: &DenseMultilinearExtension, - abc_bits: &[&Vec>>], - u: &[F], +impl AdditionInZq { + /// sample coins before proving sumcheck protocol + pub fn sample_coins(trans: &mut Transcript, instance: &AdditionInZqInstance) -> Vec { + let bits_instance = instance.extract_decomposed_bits(); + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&bits_instance.info()) + 1, + ) + } + + /// return the number of coins used in this IOP + pub fn num_coins(info: &AdditionInZqInstanceInfo) -> usize { + >::num_coins(&info.bits_info) + 1 + } + + /// Prove addition in Zq given a, b, c, k, and the decomposed bits for a, b, and c. + pub fn prove(instance: &AdditionInZqInstance) -> SumcheckKit { + let mut trans = Transcript::::new(); + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let randomness = Self::sample_coins(&mut trans, instance); + let eq_at_u = Rc::new(gen_identity_evaluations(&u)); + Self::prove_as_subprotocol(&randomness, &mut poly, instance, &eq_at_u); + + let (proof, state) = MLSumcheck::prove_as_subprotocol(&mut trans, &poly) + .expect("fail to prove the sumcheck protocol"); + // (proof, state, poly.info()) + SumcheckKit { + proof, + info: poly.info(), + claimed_sum: F::zero(), + randomness: state.randomness, + u, + } + } + + /// Prove addition in Zq given a, b, c, k, and the decomposed bits for a, b, and c. + pub fn prove_as_subprotocol( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &AdditionInZqInstance, + eq_at_u: &Rc>, + ) { + let bits_instance = instance.extract_decomposed_bits(); + let bits_info = bits_instance.info(); + let bits_r_num = >::num_coins(&bits_info); + // 1. add products of poly used to prove decomposition + BitDecomposition::prove_as_subprotocol( + &randomness[..bits_r_num], + poly, + &bits_instance, + eq_at_u, + ); + + // 2. sumcheck for \sum_{x} eq(u, x) * k(x) * (1-k(x)) = 0, i.e. k(x)\in\{0,1\}^l + let coin = randomness[randomness.len() - 1]; + poly.add_product_with_linear_op( + [ + Rc::clone(eq_at_u), + Rc::clone(&instance.k), + Rc::clone(&instance.k), + ], + &[ + (F::one(), F::zero()), + (F::one(), F::zero()), + (-F::one(), F::one()), + ], + coin, + ); + } + + /// Verify addition in Zq + pub fn verify( + wrapper: &ProofWrapper, + evals: &AdditionInZqInstanceEval, info: &AdditionInZqInstanceInfo, ) -> bool { - assert_eq!(abc.len(), 3); - assert_eq!(abc_bits.len(), 3); + let mut trans = Transcript::new(); + + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + info.num_vars, + ); - // check 1: subclaim for rangecheck, i.e. a, b, c \in [Zq] - if !self - .rangecheck_subclaim - .verify_subclaim(abc, abc_bits, u, &info.decomposed_bits_info) - { + // randomness to combine sumcheck protocols + let randomness = trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(info), + ); + + let mut subclaim = + MLSumcheck::verify_as_subprotocol(&mut trans, &wrapper.info, F::zero(), &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&u, &subclaim.point); + + if !Self::verify_as_subprotocol(&randomness, &mut subclaim, evals, info, eq_at_u_r) { return false; } - // check 2: subclaim for sumcheck, i.e. eq(u, point) * k(point) * (1 - k(point)) = 0 - let eval_k = k.evaluate(&self.sumcheck_point); - if eval_identity_function(u, &self.sumcheck_point) * eval_k * (F::one() - eval_k) - != self.sumcheck_expected_evaluations - { + subclaim.expected_evaluations == F::zero() + } + + /// Verify addition in Zq + pub fn verify_as_subprotocol( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &AdditionInZqInstanceEval, + info: &AdditionInZqInstanceInfo, + eq_at_u_r: F, + ) -> bool { + // check 1: Verify the range check part in the sumcheck polynomial + let bits_evals = evals.extract_decomposed_bits(); + let bits_randomness = &randomness[..>::num_coins(&info.bits_info)]; + let check_decomposed_bits = >::verify_as_subprotocol( + bits_randomness, + subclaim, + &bits_evals, + &info.bits_info, + eq_at_u_r, + ); + if !check_decomposed_bits { + return false; + } + // check 2: a(u) + b(u) = c(u) + k(u) * q + if evals.abc[0] + evals.abc[1] != evals.abc[2] + evals.k * info.q { return false; } - // check 3: a(u) + b(u) = c(u) + k(u) * q - abc[0].evaluate(u) + abc[1].evaluate(u) == abc[2].evaluate(u) + k.evaluate(u) * q + // check 3: Verify the newly added part in the sumcheck polynomial + let coin = randomness[randomness.len() - 1]; + subclaim.expected_evaluations -= coin * eq_at_u_r * evals.k * (F::one() - evals.k); + true } } -impl AdditionInZq { - /// Prove addition in Zq given a, b, c, k, and the decomposed bits for a, b, and c. - pub fn prove(addition_instance: &AdditionInZqInstance, u: &[F]) -> AdditionInZqProof { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, addition_instance, u) - } +impl AdditionInZqSnarks +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, +{ + /// Complied with PCS to get SNARKs + pub fn snarks(instance: &AdditionInZqInstance, code_spec: &S) + where + H: Hash + Sync + Send, + C: LinearCode + Serialize + for<'de> Deserialize<'de>, + S: LinearCodeSpec + Clone, + { + let instance_info = instance.info(); + println!("Prove {instance_info}\n"); + // This is the actual polynomial to be committed for prover, which consists of all the required small polynomials in the IOP and padded zero polynomials. + let committed_poly = instance.generate_oracle(); + // 1. Use PCS to commit the above polynomial. + let start = Instant::now(); + let pp = + BrakedownPCS::::setup(committed_poly.num_vars, Some(code_spec.clone())); + let setup_time = start.elapsed().as_millis(); - /// Prove addition in Zq given a, b, c, k, and the decomposed bits for a, b, and c. - /// This function does the same thing as `prove`, but it uses a `Fiat-Shamir RNG` as the transcript/to generate the - /// verifier challenges. - pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, - addition_instance: &AdditionInZqInstance, - u: &[F], - ) -> AdditionInZqProof { - // 1. rangecheck - let rangecheck_msg = - BitDecomposition::prove_as_subprotocol(fs_rng, &addition_instance.abc_bits, u); - - let dim = u.len(); - assert_eq!(dim, addition_instance.num_vars); - let mut poly = >::new(dim); - - // 2. execute sumcheck for \sum_{x} eq(u, x) * k(x) * (1-k(x)) = 0, i.e. k(x)\in\{0,1\}^l - let mut product = Vec::with_capacity(3); - let mut op_coefficient = Vec::with_capacity(3); - product.push(Rc::new(gen_identity_evaluations(u))); - op_coefficient.push((F::one(), F::zero())); - - product.push(Rc::clone(&addition_instance.k)); - op_coefficient.push((F::one(), F::zero())); - product.push(Rc::clone(&addition_instance.k)); - op_coefficient.push((-F::one(), F::one())); - - poly.add_product_with_linear_op(product, &op_coefficient, F::one()); - let sumcheck_proof = MLSumcheck::prove_as_subprotocol(fs_rng, &poly) - .expect("sumcheck for addition in Zq failed"); - - AdditionInZqProof { - rangecheck_msg, - sumcheck_msg: sumcheck_proof.0, - } - } + let start = Instant::now(); + let (comm, comm_state) = BrakedownPCS::::commit(&pp, &committed_poly); + let commit_time = start.elapsed().as_millis(); - /// Verify addition in Zq given the proof and the verification key for bit decomposistion - pub fn verify( - proof: &AdditionInZqProof, - decomposed_bits_info: &DecomposedBitsInfo, - ) -> AdditionInZqSubclaim { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verifier_as_subprotocol(&mut fs_rng, proof, decomposed_bits_info) - } + // 2. Prover generates the proof + let prover_start = Instant::now(); + let mut iop_proof_size = 0; + let mut prover_trans = Transcript::::new(); + // Convert the original instance into an instance defined over EF + let instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); - /// Verify addition in Zq given the proof and the verification key for bit decomposistion - /// This function does the same thing as `prove`, but it uses a `Fiat-Shamir RNG` as the transcript/to generate the - /// verifier challenges. - pub fn verifier_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &AdditionInZqProof, - decomposed_bits_info: &DecomposedBitsInfo, - ) -> AdditionInZqSubclaim { - // TODO sample randomness via Fiat-Shamir RNG - let rangecheck_subclaim = BitDecomposition::verifier_as_subprotocol( - fs_rng, - &proof.rangecheck_msg, - decomposed_bits_info, + // 2.1 Generate the random point to instantiate the sumcheck protocol + let prover_u = prover_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, ); + let eq_at_u = Rc::new(gen_identity_evaluations(&prover_u)); - // execute sumcheck for \sum_{x} eq(u, x) * k(x) * (1-k(x)) = 0, i.e. k(x)\in\{0,1\}^l - let poly_info = PolynomialInfo { - max_multiplicands: 3, - num_variables: decomposed_bits_info.num_vars, - }; - - let subclaim = - MLSumcheck::verify_as_subprotocol(fs_rng, &poly_info, F::zero(), &proof.sumcheck_msg) - .expect("sumcheck protocol in addition in Zq failed"); - AdditionInZqSubclaim { - rangecheck_subclaim, - sumcheck_point: subclaim.point, - sumcheck_expected_evaluations: subclaim.expected_evaluations, - } + // 2.2 Construct the polynomial and the claimed sum to be proved in the sumcheck protocol + let mut sumcheck_poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let claimed_sum = EF::zero(); + let randomness = AdditionInZq::sample_coins(&mut prover_trans, &instance_ef); + AdditionInZq::prove_as_subprotocol(&randomness, &mut sumcheck_poly, &instance_ef, &eq_at_u); + + let poly_info = sumcheck_poly.info(); + + // 2.3 Generate proof of sumcheck protocol + let (sumcheck_proof, sumcheck_state) = + >::prove_as_subprotocol(&mut prover_trans, &sumcheck_poly) + .expect("Proof generated in Addition In Zq"); + iop_proof_size += bincode::serialize(&sumcheck_proof).unwrap().len(); + let iop_prover_time = prover_start.elapsed().as_millis(); + + // 2.4 Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let start = Instant::now(); + let evals = instance.evaluate_ext(&sumcheck_state.randomness); + + // 2.5 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point = sumcheck_state.randomness.clone(); + requested_point.extend(&prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_oracles(), + )); + let oracle_eval = committed_poly.evaluate_ext(&requested_point); + + // 2.6 Generate the evaluation proof of the requested point + let eval_proof = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &requested_point, + &mut prover_trans, + ); + let pcs_open_time = start.elapsed().as_millis(); + + // 3. Verifier checks the proof + let verifier_start = Instant::now(); + let mut verifier_trans = Transcript::::new(); + + // 3.1 Generate the random point to instantiate the sumcheck protocol + let verifier_u = verifier_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + // 3.2 Generate the randomness used to randomize all the sub-sumcheck protocols + let randomness = verifier_trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance_info), + ); + + // 3.3 Check the proof of the sumcheck protocol + let mut subclaim = >::verify_as_subprotocol( + &mut verifier_trans, + &poly_info, + claimed_sum, + &sumcheck_proof, + ) + .expect("Verify the proof generated in Bit Decompositon"); + let eq_at_u_r = eval_identity_function(&verifier_u, &subclaim.point); + + // 3.4 Check the evaluation over a random point of the polynomial proved in the sumcheck protocol using evaluations over these small oracles used in IOP + let check_subcliam = AdditionInZq::::verify_as_subprotocol( + &randomness, + &mut subclaim, + &evals, + &instance_info, + eq_at_u_r, + ); + assert!(check_subcliam && subclaim.expected_evaluations == EF::zero()); + let iop_verifier_time = verifier_start.elapsed().as_millis(); + + // 3.5 and also check the relation between these small oracles and the committed oracle + let start = Instant::now(); + let mut pcs_proof_size = 0; + let flatten_evals = evals.flatten(); + let oracle_randomness = verifier_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + evals.log_num_oracles(), + ); + let check_oracle = verify_oracle_relation(&flatten_evals, oracle_eval, &oracle_randomness); + assert!(check_oracle); + + // 3.5 Check the evaluation of a random point over the committed oracle + let check_pcs = BrakedownPCS::::verify( + &pp, + &comm, + &requested_point, + oracle_eval, + &eval_proof, + &mut verifier_trans, + ); + assert!(check_pcs); + let pcs_verifier_time = start.elapsed().as_millis(); + pcs_proof_size += bincode::serialize(&eval_proof).unwrap().len() + + bincode::serialize(&flatten_evals).unwrap().len(); + + // 4. print statistic + print_statistic( + iop_prover_time + pcs_open_time, + iop_verifier_time + pcs_verifier_time, + iop_proof_size + pcs_proof_size, + iop_prover_time, + iop_verifier_time, + iop_proof_size, + committed_poly.num_vars, + instance.num_oracles(), + instance.num_vars, + setup_time, + commit_time, + pcs_open_time, + pcs_verifier_time, + pcs_proof_size, + ); } } diff --git a/zkp/src/piop/bit_decomposition.rs b/zkp/src/piop/bit_decomposition.rs index 439c3239..3dfd2dbe 100644 --- a/zkp/src/piop/bit_decomposition.rs +++ b/zkp/src/piop/bit_decomposition.rs @@ -19,36 +19,35 @@ //! then the resulting purported sum is: //! $\sum_{x \in \{0, 1\}^\log M} \sum_{i = 0}^{l-1} r_i \cdot eq(u, x) \cdot [\prod_{k=0}^B (d_i(x) - k)] = 0$ //! where r_i (for i = 0..l) are sampled from the verifier. -use algebra::{DecomposableField, DenseMultilinearExtension, Field, MultilinearExtension}; +use crate::sumcheck::{verifier::SubClaim, MLSumcheck}; +use crate::sumcheck::{ProofWrapper, SumcheckKit}; +use crate::utils::{ + eval_identity_function, gen_identity_evaluations, print_statistic, verify_oracle_relation, +}; +use algebra::{ + utils::Transcript, AbstractExtensionField, DecomposableField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, MultilinearExtension, +}; +use core::fmt; +use itertools::izip; +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{LinearCode, LinearCodeSpec}, + utils::hash::Hash, + PolynomialCommitmentScheme, +}; +use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use std::rc::Rc; +use std::time::Instant; -use crate::sumcheck::prover::ProverMsg; - -use crate::sumcheck::MLSumcheck; -use crate::utils::{eval_identity_function, gen_identity_evaluations}; -use algebra::{FieldUniformSampler, ListOfProductsOfPolynomials, PolynomialInfo}; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; -use rand_distr::Distribution; - -/// SNARKs for bit decomposition +/// IOP for bit decomposition pub struct BitDecomposition(PhantomData); - -/// proof generated by prover -pub struct BitDecompositionProof { - pub(crate) sumcheck_msg: Vec>, -} - -/// subclaim returned to verifier -pub struct BitDecompositionSubClaim { - /// r - pub randomness: Vec, - /// reduced point from the sumcheck protocol - pub point: Vec, - /// expected value returned in sumcheck - pub expected_evaluation: F, -} +/// SNARKs for bit decomposition compied with PCS +pub struct BitDecompositionSnarks>( + PhantomData, + PhantomData, +); /// Stores the parameters used for bit decomposation and every instance of decomposed bits, /// and the batched polynomial used for the sumcheck protocol. @@ -59,13 +58,23 @@ pub struct DecomposedBits { /// base pub base: F, /// the length of base, i.e. log_2(base) - pub base_len: u32, + pub base_len: usize, /// the length of decomposed bits - pub bits_len: u32, + pub bits_len: usize, /// number of variables of every polynomial pub num_vars: usize, + /// batched values to be decomposed into bits + pub d_val: Vec>>, + /// batched plain deomposed bits, each of which corresponds to one bit decomposisiton instance + pub d_bits: Vec>>, +} + +/// Evaluations at a random point +pub struct DecomposedBitsEval { + /// batched values to be decomposed into bits + pub d_val: Vec, /// batched plain deomposed bits, each of which corresponds to one bit decomposisiton instance - pub instances: Vec>>>, + pub d_bits: Vec, } /// Stores the parameters used for bit decomposation. @@ -73,20 +82,43 @@ pub struct DecomposedBits { /// * It is required to decompose over a power-of-2 base. /// /// These parameters are used as the verifier key. -#[derive(Clone)] +#[derive(Clone, Serialize)] pub struct DecomposedBitsInfo { /// base pub base: F, /// the length of base, i.e. log_2(base) - pub base_len: u32, + pub base_len: usize, /// the length of decomposed bits (denoted by l) - pub bits_len: u32, + pub bits_len: usize, /// number of variables of every polynomial pub num_vars: usize, /// number of instances pub num_instances: usize, } +impl fmt::Display for DecomposedBitsInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} instances of Decomposed Bits: #vars = {}, base = 2^{}, #bits = {}", + self.num_instances, self.num_vars, self.base_len, self.bits_len + ) + } +} + +impl DecomposedBitsInfo { + /// Construct a EF version + pub fn to_ef>(&self) -> DecomposedBitsInfo { + DecomposedBitsInfo:: { + base: EF::from_base(self.base), + base_len: self.base_len, + bits_len: self.bits_len, + num_vars: self.num_vars, + num_instances: self.num_instances, + } + } +} + impl DecomposedBits { #[inline] /// Extract the information of decomposed bits for verification @@ -96,32 +128,34 @@ impl DecomposedBits { base_len: self.base_len, bits_len: self.bits_len, num_vars: self.num_vars, - num_instances: self.instances.len(), + num_instances: self.d_val.len(), } } /// Initiate the polynomial used for sumcheck protocol #[inline] - pub fn new(base: F, base_len: u32, bits_len: u32, num_vars: usize) -> Self { + pub fn new(base: F, base_len: usize, bits_len: usize, num_vars: usize) -> Self { DecomposedBits { base, base_len, bits_len, num_vars, - instances: Vec::new(), + d_val: Vec::new(), + d_bits: Vec::new(), } } - /// Initiate the polynomial from the given info used for sumcheck protocol + /// Return the number of small polynomials used in IOP #[inline] - pub fn from_info(info: &DecomposedBitsInfo) -> Self { - DecomposedBits { - base: info.base, - base_len: info.base_len, - bits_len: info.bits_len, - num_vars: info.num_vars, - instances: Vec::with_capacity(info.num_instances), - } + pub fn num_oracles(&self) -> usize { + // number of value oracle + number of decomposed bits oracle + self.d_val.len() + self.d_bits.len() + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize } #[inline] @@ -129,50 +163,94 @@ impl DecomposedBits { /// * decomposed_bits: store each bit pub fn add_decomposed_bits_instance( &mut self, + d_val: &Rc>, decomposed_bits: &[Rc>], ) { - assert_eq!(decomposed_bits.len(), self.bits_len as usize); + assert_eq!(decomposed_bits.len(), self.bits_len); for bit in decomposed_bits { assert_eq!(bit.num_vars, self.num_vars); } - self.instances.push(decomposed_bits.to_vec()); + self.d_bits.extend(decomposed_bits.to_owned()); + self.d_val.push(Rc::clone(d_val)); } + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros. + pub fn pack_all_mles(&self) -> Vec { + assert_eq!(self.d_val.len() * self.bits_len, self.d_bits.len()); + + // arrangement: all values||all decomposed bits + self.d_val + .iter() + .flat_map(|d| d.iter()) + // concatenated with decomposed bits + .chain(self.d_bits.iter().flat_map(|bit| bit.iter())) + .copied() + .collect::>() + } + + /// Generate the oracle to be committed that is composed of all the small oracles used in IOP. + /// The evaluations of this oracle is generated by the evaluations of all mles and the padded zeros. + /// The arrangement of this oracle should be consistent to its usage in verifying the subclaim. #[inline] - /// Batch all the sumcheck protocol, each corresponding to range-check one single bit. - /// * randomness: randomness used to linearly combine bits_len * num_instances sumcheck protocols - /// * u is the common random challenge from the verifier, used to instantiate every sum. - pub fn randomized_sumcheck(&self, randomness: &[F], u: &[F]) -> ListOfProductsOfPolynomials { - assert_eq!( - randomness.len(), - self.instances.len() * self.bits_len as usize - ); - assert_eq!(u.len(), self.num_vars); - - let mut poly = >::new(self.num_vars); - let identity_func_at_u = Rc::new(gen_identity_evaluations(u)); - let base = 1 << self.base_len; - - let mut r_iter = randomness.iter(); - for instance in &self.instances { - // For every bit, the reduced sum is $\sum_{x \in \{0, 1\}^\log M} eq(u, x) \cdot [\prod_{k=0}^B (d_i(x) - k)] = 0$ - // and the added product is r_i \cdot eq(u, x) \cdot [\prod_{k=0}^B (d_i(x) - k)] with the corresponding randomness - for bit in instance { - let mut product: Vec<_> = Vec::with_capacity(base + 1); - let mut op_coefficient: Vec<_> = Vec::with_capacity(base + 1); - product.push(Rc::clone(&identity_func_at_u)); - op_coefficient.push((F::one(), F::zero())); - - let mut minus_k = F::zero(); - for _ in 0..base { - product.push(Rc::clone(bit)); - op_coefficient.push((F::one(), minus_k)); - minus_k -= F::one(); - } - poly.add_product_with_linear_op(product, &op_coefficient, *r_iter.next().unwrap()); - } + pub fn generate_oracle(&self) -> DenseMultilinearExtension { + let num_vars_added = self.log_num_oracles(); + let num_vars = self.num_vars + num_vars_added; + let num_zeros_padded = ((1 << num_vars_added) - self.num_oracles()) * (1 << self.num_vars); + + // arrangement: all values||all decomposed bits||padded zeros + let mut evals = self.pack_all_mles(); + evals.append(&mut vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// Construct a EF version + #[inline] + pub fn to_ef>(&self) -> DecomposedBits { + DecomposedBits:: { + num_vars: self.num_vars, + base: EF::from_base(self.base), + base_len: self.base_len, + bits_len: self.bits_len, + d_val: self + .d_val + .iter() + .map(|val| Rc::new(val.to_ef())) + .collect::>(), + d_bits: self + .d_bits + .iter() + .map(|bit| Rc::new(bit.to_ef())) + .collect::>(), + } + } + + /// Evaluate at a random point defined over Field + #[inline] + pub fn evaluate(&self, point: &[F]) -> DecomposedBitsEval { + DecomposedBitsEval:: { + d_val: self.d_val.iter().map(|val| val.evaluate(point)).collect(), + d_bits: self.d_bits.iter().map(|bit| bit.evaluate(point)).collect(), + } + } + + /// Evaluate at a random point defined over Extension Field + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> DecomposedBitsEval { + DecomposedBitsEval:: { + d_val: self + .d_val + .iter() + .map(|val| val.evaluate_ext(point)) + .collect(), + d_bits: self + .d_bits + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), } - poly } } @@ -183,52 +261,164 @@ impl DecomposedBits { #[inline] pub fn add_value_instance(&mut self, value: &DenseMultilinearExtension) { assert_eq!(self.num_vars, value.num_vars); - self.instances - .push(value.get_decomposed_mles(self.base_len, self.bits_len)); + let mut bits = value.get_decomposed_mles(self.base_len, self.bits_len); + self.d_bits.append(&mut bits); } } -impl BitDecompositionSubClaim { - /// verify the subclaim - /// - /// # Argument - /// - /// * `d_val` stores each value to be decomposed - /// * `d_bits` stores the decomposed bits for each value in d_val - /// * `u` is the common random challenge from the verifier, used to instantiate every sum. - pub fn verify_subclaim( - &self, - d_val: &[Rc>], - d_bits: &[&Vec>>], - u: &[F], - decomposed_bits_info: &DecomposedBitsInfo, - ) -> bool { - assert_eq!(d_val.len(), decomposed_bits_info.num_instances); - assert_eq!(d_bits.len(), decomposed_bits_info.num_instances); - assert_eq!(u.len(), decomposed_bits_info.num_vars); +impl DecomposedBitsEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + // number of value oracle + number of decomposed bits oracle + self.d_val.len() + self.d_bits.len() + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } - let d_val_at_point: Vec<_> = d_val.iter().map(|val| val.evaluate(&self.point)).collect(); - let d_bits_at_point: Vec> = d_bits + /// Flatten all evals into a vector with the same arrangement of the committed polynomial + #[inline] + pub fn flatten(&self) -> Vec { + self.d_val .iter() - .map(|bits| bits.iter().map(|bit| bit.evaluate(&self.point)).collect()) - .collect(); + .chain(self.d_bits.iter()) + .copied() + .collect() + } +} + +impl BitDecomposition { + /// sample coins before proving sumcheck protocol + pub fn sample_coins(trans: &mut Transcript, instance: &DecomposedBits) -> Vec { + // batch `len_bits` sumcheck protocols into one with random linear combination + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + instance.d_bits.len(), + ) + } + + /// return the number of coins used in this IOP + pub fn num_coins(info: &DecomposedBitsInfo) -> usize { + info.bits_len * info.num_instances + } + + /// Prove bit decomposition given the decomposed bits as prover key. + pub fn prove(instance: &DecomposedBits) -> SumcheckKit { + let mut trans = Transcript::::new(); + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + // randomness to combine sumcheck protocols + let randomness = Self::sample_coins(&mut trans, instance); + let eq_at_u = Rc::new(gen_identity_evaluations(&u)); + Self::prove_as_subprotocol(&randomness, &mut poly, instance, &eq_at_u); + + let (proof, state) = MLSumcheck::prove_as_subprotocol(&mut trans, &poly) + .expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + randomness: state.randomness, + claimed_sum: F::zero(), + info: poly.info(), + u, + } + } + /// Prove bit decomposition given the decomposed bits as prover key. + /// This function does the same thing as `prove`, but it uses a `Fiat-Shamir RNG` as the transcript/to generate the + /// verifier challenges. + pub fn prove_as_subprotocol( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &DecomposedBits, + eq_at_u: &Rc>, + ) { + let base = 1 << instance.base_len; + + // For every bit, the reduced sum is $\sum_{x \in \{0, 1\}^\log M} eq(u, x) \cdot [\prod_{k=0}^B (d_i(x) - k)] = 0$ + // and the added product is r_i \cdot eq(u, x) \cdot [\prod_{k=0}^B (d_i(x) - k)] with the corresponding randomness + for (r, bit) in izip!(randomness, instance.d_bits.iter()) { + let mut product: Vec<_> = Vec::with_capacity(base + 1); + let mut op_coefficient: Vec<_> = Vec::with_capacity(base + 1); + product.push(Rc::clone(eq_at_u)); + op_coefficient.push((F::one(), F::zero())); + + let mut minus_k = F::zero(); + for _ in 0..base { + product.push(Rc::clone(bit)); + op_coefficient.push((F::one(), minus_k)); + minus_k -= F::one(); + } + poly.add_product_with_linear_op(product, &op_coefficient, *r); + } + } + + /// Verify bit decomposition given the basic information of decomposed bits as verifier key. + pub fn verify( + wrapper: &ProofWrapper, + evals: &DecomposedBitsEval, + info: &DecomposedBitsInfo, + ) -> bool { + let mut trans = Transcript::new(); + + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + info.num_vars, + ); + + // randomness to combine sumcheck protocols + let randomness = trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(info), + ); + + let mut subclaim = + MLSumcheck::verify_as_subprotocol(&mut trans, &wrapper.info, F::zero(), &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + + let eq_at_u_r = eval_identity_function(&u, &subclaim.point); + if !Self::verify_as_subprotocol(&randomness, &mut subclaim, evals, info, eq_at_u_r) { + return false; + } + + subclaim.expected_evaluations == F::zero() + } + + /// Verify bit decomposition + pub fn verify_as_subprotocol( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &DecomposedBitsEval, + info: &DecomposedBitsInfo, + eq_at_u_r: F, + ) -> bool { + assert_eq!(evals.d_val.len(), info.num_instances); + assert_eq!(evals.d_bits.len(), info.num_instances * info.bits_len); // base_pow = [1, B, ..., B^{l-1}] - let mut base_pow = vec![F::one(); decomposed_bits_info.bits_len as usize]; + let mut base_pow = vec![F::one(); info.bits_len]; base_pow.iter_mut().fold(F::one(), |acc, pow| { *pow *= acc; - acc * decomposed_bits_info.base + acc * info.base }); // check 1: d[point] = \sum_{i=0}^len B^i \cdot d_i[point] for every instance - if !d_val_at_point + if !evals + .d_val .iter() - .zip(d_bits_at_point.iter()) + .zip(evals.d_bits.chunks_exact(info.bits_len)) .all(|(val, bits)| { *val == bits .iter() .zip(base_pow.iter()) - .fold(F::zero(), |acc, (bit, pow)| acc + *pow * *bit) + .fold(F::zero(), |acc, (bit, pow)| acc + *bit * *pow) }) { return false; @@ -236,86 +426,183 @@ impl BitDecompositionSubClaim { // check 2: expected value returned in sumcheck // each instance contributes value: eq(u, x) \cdot \sum_{i = 0}^{l-1} r_i \cdot [\prod_{k=0}^B (d_i(x) - k)] =? expected_evaluation - let mut evaluation = F::zero(); - let mut r = self.randomness.iter(); - d_bits_at_point.iter().for_each(|bits| { - bits.iter().for_each(|bit| { - let mut prod = *r.next().unwrap(); - let mut minus_k = F::zero(); - for _ in 0..(1 << decomposed_bits_info.base_len) { - prod *= *bit + minus_k; - minus_k -= F::one(); - } - evaluation += prod; - }) - }); - self.expected_evaluation == evaluation * eval_identity_function(u, &self.point) + let mut real_eval = F::zero(); + for (r, bit) in izip!(randomness, &evals.d_bits) { + let mut prod = *r; + let mut minus_k = F::zero(); + for _ in 0..(1 << info.base_len) { + prod *= *bit + minus_k; + minus_k -= F::one(); + } + real_eval += prod; + } + subclaim.expected_evaluations -= real_eval * eq_at_u_r; + + true } } -impl BitDecomposition { - /// Prove bit decomposition given the decomposed bits as prover key. - pub fn prove(decomposed_bits: &DecomposedBits, u: &[F]) -> BitDecompositionProof { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, decomposed_bits, u) - } +impl BitDecompositionSnarks +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, +{ + /// Complied with PCS to get SNARKs + pub fn snarks(instance: &DecomposedBits, code_spec: &S) + where + H: Hash + Sync + Send, + C: LinearCode + Serialize + for<'de> Deserialize<'de>, + S: LinearCodeSpec + Clone, + { + let instance_info = instance.info(); + println!("Prove {instance_info}\n"); + // This is the actual polynomial to be committed for prover, which consists of all the required small polynomials in the IOP and padded zero polynomials. + let committed_poly = instance.generate_oracle(); + // 1. Use PCS to commit the above polynomial. + let start = Instant::now(); + let pp = + BrakedownPCS::::setup(committed_poly.num_vars, Some(code_spec.clone())); + let setup_time = start.elapsed().as_millis(); - /// Prove bit decomposition given the decomposed bits as prover key. - /// This function does the same thing as `prove`, but it uses a `Fiat-Shamir RNG` as the transcript/to generate the - /// verifier challenges. - pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, - decomposed_bits: &DecomposedBits, - u: &[F], - ) -> BitDecompositionProof { - let num_bits = decomposed_bits.instances.len() * decomposed_bits.bits_len as usize; - // TODO sample randomness via Fiat-Shamir RNG - // batch `len_bits` sumcheck protocols into one with random linear combination - let sampler = >::new(); - let randomness: Vec<_> = (0..num_bits).map(|_| sampler.sample(fs_rng)).collect(); - let poly = decomposed_bits.randomized_sumcheck(&randomness, u); - BitDecompositionProof { - sumcheck_msg: MLSumcheck::prove_as_subprotocol(fs_rng, &poly) - .expect("bit decomposition failed") - .0, - } - } + let start = Instant::now(); + let (comm, comm_state) = BrakedownPCS::::commit(&pp, &committed_poly); + let commit_time = start.elapsed().as_millis(); - /// Verify bit decomposition given the basic information of decomposed bits as verifier key. - pub fn verifier( - proof: &BitDecompositionProof, - decomposed_bits_info: &DecomposedBitsInfo, - ) -> BitDecompositionSubClaim { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verifier_as_subprotocol(&mut fs_rng, proof, decomposed_bits_info) - } + // 2. Prover generates the proof + let prover_start = Instant::now(); + let mut iop_proof_size = 0; + let mut prover_trans = Transcript::::new(); + // Convert the original instance into an instance defined over EF + let instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); - /// Verify bit decomposition given the basic information of decomposed bits as verifier key. - /// This function does the same thing as `prove`, but it uses a `Fiat-Shamir RNG` as the transcript/to generate the - /// verifier challenges. - pub fn verifier_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &BitDecompositionProof, - decomposed_bits_info: &DecomposedBitsInfo, - ) -> BitDecompositionSubClaim { - let num_bits = decomposed_bits_info.num_instances * decomposed_bits_info.bits_len as usize; - // TODO sample randomness via Fiat-Shamir RNG - // batch `len_bits` sumcheck protocols into one with random linear combination - let sampler = >::new(); - let randomness: Vec<_> = (0..num_bits).map(|_| sampler.sample(fs_rng)).collect(); - let poly_info = PolynomialInfo { - max_multiplicands: 1 + (1 << decomposed_bits_info.base_len), - num_variables: decomposed_bits_info.num_vars, - }; - let subclaim = - MLSumcheck::verify_as_subprotocol(fs_rng, &poly_info, F::zero(), &proof.sumcheck_msg) - .expect("bit decomposition verification failed"); - BitDecompositionSubClaim { - randomness, - point: subclaim.point, - expected_evaluation: subclaim.expected_evaluations, - } + // 2.1 Generate the random point to instantiate the sumcheck protocol + let prover_u = prover_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&prover_u)); + + // 2.2 Construct the polynomial and the claimed sum to be proved in the sumcheck protocol + let mut sumcheck_poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let claimed_sum = EF::zero(); + let randomness = BitDecomposition::sample_coins(&mut prover_trans, &instance_ef); + BitDecomposition::prove_as_subprotocol( + &randomness, + &mut sumcheck_poly, + &instance_ef, + &eq_at_u, + ); + + let poly_info = sumcheck_poly.info(); + + // 2.3 Generate proof of sumcheck protocol + let (sumcheck_proof, sumcheck_state) = + >::prove_as_subprotocol(&mut prover_trans, &sumcheck_poly) + .expect("Proof generated in Addition In Zq"); + iop_proof_size += bincode::serialize(&sumcheck_proof).unwrap().len(); + let iop_prover_time = prover_start.elapsed().as_millis(); + + // 2.4 Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let start = Instant::now(); + let evals = instance.evaluate_ext(&sumcheck_state.randomness); + // 2.5 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point = sumcheck_state.randomness.clone(); + requested_point.extend(&prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_oracles(), + )); + let oracle_eval = committed_poly.evaluate_ext(&requested_point); + + // 2.6 Generate the evaluation proof of the requested point + let eval_proof = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &requested_point, + &mut prover_trans, + ); + let pcs_open_time = start.elapsed().as_millis(); + + // 3. Verifier checks the proof + let verifier_start = Instant::now(); + let mut verifier_trans = Transcript::::new(); + + // 3.1 Generate the random point to instantiate the sumcheck protocol + let verifier_u = verifier_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + // 3.2 Generate the randomness used to randomize all the sub-sumcheck protocols + let randomness = verifier_trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance_info), + ); + + // 3.3 Check the proof of the sumcheck protocol + let mut subclaim = >::verify_as_subprotocol( + &mut verifier_trans, + &poly_info, + claimed_sum, + &sumcheck_proof, + ) + .expect("Verify the proof generated in Bit Decompositon"); + let eq_at_u_r = eval_identity_function(&verifier_u, &subclaim.point); + + // 3.4 Check the evaluation over a random point of the polynomial proved in the sumcheck protocol using evaluations over these small oracles used in IOP + let check_subcliam = BitDecomposition::::verify_as_subprotocol( + &randomness, + &mut subclaim, + &evals, + &instance_info, + eq_at_u_r, + ); + assert!(check_subcliam && subclaim.expected_evaluations == EF::zero()); + let iop_verifier_time = verifier_start.elapsed().as_millis(); + + // 3.5 and also check the relation between these small oracles and the committed oracle + let start = Instant::now(); + let mut pcs_proof_size = 0; + let flatten_evals = evals.flatten(); + let oracle_randomness = verifier_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + evals.log_num_oracles(), + ); + let check_oracle = verify_oracle_relation(&flatten_evals, oracle_eval, &oracle_randomness); + assert!(check_oracle); + + // 3.5 Check the evaluation of a random point over the committed oracle + + let check_pcs = BrakedownPCS::::verify( + &pp, + &comm, + &requested_point, + oracle_eval, + &eval_proof, + &mut verifier_trans, + ); + assert!(check_pcs); + let pcs_verifier_time = start.elapsed().as_millis(); + pcs_proof_size += bincode::serialize(&eval_proof).unwrap().len() + + bincode::serialize(&flatten_evals).unwrap().len(); + + // 4. print statistic + print_statistic( + iop_prover_time + pcs_open_time, + iop_verifier_time + pcs_verifier_time, + iop_proof_size + pcs_proof_size, + iop_prover_time, + iop_verifier_time, + iop_proof_size, + committed_poly.num_vars, + instance.num_oracles(), + instance.num_vars, + setup_time, + commit_time, + pcs_open_time, + pcs_verifier_time, + pcs_proof_size, + ) } } diff --git a/zkp/src/piop/look_up.rs b/zkp/src/piop/look_up.rs new file mode 100644 index 00000000..8778cc4e --- /dev/null +++ b/zkp/src/piop/look_up.rs @@ -0,0 +1,833 @@ +//! PIOP for range check +//! The prover wants to convince that lookups f are all in range +//! +//! <==> \forall x \in H_f, \forall i \in [lookup_num], f_i(x) \in [range] +//! +//! <==> \forall x in H_f, \forall i \in [lookup_num], f_i(x) \in {t(x) | x \in H_t} := {0, 1, 2, ..., range - 1} +//! where |H_f| is the size of one lookup and |H_t| is the size of table / range +//! +//! <==> \exists m s.t. \forall y, \sum_{i} \sum_{x \in H_f} 1 / f_i(x) - y = \sum_{x \in H_t} m(x) / t(x) - y +//! +//! <==> \sum_{i} \sum_{x \in H_f} 1 / f_i(x) - r = \sum_{x \in H_t} m(x) / t(x) - r +//! where r is a random challenge from verifier (a single random element since y is a single variable) +//! +//! <==> \sum_{x \in H_f} \sum_{i \in [block_num]} h_i(x) = \sum_{x \in H_t} h_t(x) +//! \forall i \in [block_num] \forall x \in H_f, h(x) * \prod_{j \in [block_size]}(f_j(x) - r) = \sum_{i \in [block_size]} \prod_{j \in [block_size], j != i} (f_j(x) - r) +//! \forall x \in H_t, h_t(x) * (t(x) - r) = m(x) +//! +//! <==> \sum_{x \in H_f} \sum_{i \in [block_num]} h_i(x) = c_sum +//! \sum_{x \in H_t} h_t(x) = c_sum +//! \sum_{x \in H_f} \sum_{i \in [block_num]} eq(x, u) * (h(x) * \prod_{j \in [block_size]}(f_j(x) - r) - r * \sum_{i \in [block_size]} \prod_{j \in [block_size], j != i} (f_j(x) - r)) = 0 +//! \sum_{x \in H_t} eq(x, u) * (h_t(x) * (t(x) - r) - m(x)) = 0 +//! where u is a random challenge given from verifier (a vector of random element) and c_sum is some constant +//! +//! <==> \sum_{x \in H_f} \sum_{i \in [block_num]} h_i(x) +//! + \sum_{i \in [block_num]} eq(x, u) * (h(x) * \prod_{j \in [block_size]}(f_j(x) - r) - r * \sum_{i \in [block_size]} \prod_{j \in [block_size], j != i} (f_j(x) - r)) +//! = c_sum +//! \sum_{x \in H_t} h_t(x) +//! + eq(x, u) * (h_t(x) * (t(x) - r) - m(x)) +//! = c_sum +//! where u is a random challenge given from verifier (a vector of random element) and c_sum is some constant + +use crate::sumcheck::{verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}; +use crate::utils::{ + batch_inverse, eval_identity_function, gen_identity_evaluations, print_statistic, + verify_oracle_relation, +}; +use algebra::{ + utils::Transcript, AbstractExtensionField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, MultilinearExtension, +}; +use core::fmt; +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{LinearCode, LinearCodeSpec}, + utils::hash::Hash, + PolynomialCommitmentScheme, +}; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use std::rc::Rc; +use std::time::Instant; + +/// SNARKs for range check in [T] := [0, T-1] +pub struct Lookup(PhantomData); + +// /// subclaim returned to verifier +// pub struct LookupSubclaim { +// /// random value +// pub random_value: F, +// /// random point +// pub random_point: Vec, +// /// random combine +// pub random_combine: Vec, +// /// subcliams +// pub sumcheck_points: Vec>, +// /// expected value returned in the last round of the sumcheck +// pub sumcheck_expected_evaluations: Vec, +// } + +/// Stores the parameters used for range check in [T] and the public info for verifier. +#[derive(Clone, Serialize)] +pub struct LookupInstanceInfo { + /// number of variables for lookups + pub num_vars: usize, + /// block num + pub block_num: usize, + /// block size + pub block_size: usize, + /// residual size + pub residual_size: usize, +} + +impl fmt::Display for LookupInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "instances of Lookup: num_vars = {}, block_num = {}, block_size = {}, residual_size = {}", + self.num_vars, self.block_num, self.block_size, self.residual_size + ) + } +} + +/// Stores the parameters used for range check in [T] and the inputs and witness for prover. +pub struct LookupInstance { + /// number of variables for lookups i.e. the size of log(|F|) + pub num_vars: usize, + /// block num + pub block_num: usize, + /// block_size + pub block_size: usize, + /// residual size + pub residual_size: usize, + /// inputs f + pub f_vec: Vec>>, + /// inputs t + pub t: Rc>, + /// intermediate oracle h + pub h_vec: Vec>>, + /// intermediate oracle m + pub m: Rc>, +} + +impl LookupInstance { + /// Extract the information of range check for verification + #[inline] + pub fn info(&self) -> LookupInstanceInfo { + let column_num = self.f_vec.len() + 1; + LookupInstanceInfo { + num_vars: self.num_vars, + block_size: self.block_size, + block_num: column_num / self.block_size, + residual_size: column_num % self.block_size, + } + } + + /// Construct a new instance from slice + #[inline] + pub fn from_slice( + f_vec: &[Rc>], + t: Rc>, + m: Rc>, + block_size: usize, + ) -> Self { + let num_vars = f_vec[0].num_vars; + let column_num = f_vec.len() + 1; + + f_vec.iter().for_each(|x| assert_eq!(x.num_vars, num_vars)); + assert_eq!(t.num_vars, num_vars); + assert_eq!(m.num_vars, num_vars); + + Self { + num_vars, + block_num: column_num / block_size, + block_size, + residual_size: column_num % block_size, + f_vec: f_vec.to_vec(), + t, + h_vec: Default::default(), + m, + } + } + + /// Construct a EF version + pub fn to_ef>(&self) -> LookupInstance { + LookupInstance:: { + num_vars: self.num_vars, + block_num: self.block_num, + block_size: self.block_size, + residual_size: self.residual_size, + f_vec: self.f_vec.iter().map(|x| Rc::new(x.to_ef())).collect(), + t: Rc::new(self.t.to_ef()), + h_vec: Default::default(), + m: Rc::new(self.m.to_ef()), + } + } + + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_first_oracles(&self) -> usize { + self.f_vec.len() + 2 + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_first_oracles(&self) -> usize { + self.num_first_oracles().next_power_of_two().ilog2() as usize + } + + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_second_oracles(&self) -> usize { + self.block_num + if self.residual_size != 0 { 1 } else { 0 } + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_second_oracles(&self) -> usize { + self.num_second_oracles().next_power_of_two().ilog2() as usize + } + + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros. + pub fn pack_first_mles(&self) -> Vec { + // arrangement: f | t | m + self.f_vec + .iter() + .flat_map(|x| x.iter()) + .chain(self.t.iter()) + .chain(self.m.iter()) + .copied() + .collect::>() + } + + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros. + pub fn pack_second_mles(&self) -> Vec { + // arrangement: h + self.h_vec + .iter() + .flat_map(|x| x.iter()) + .copied() + .collect::>() + } + + /// Generate the oracle to be committed that is composed of all the small oracles used in IOP. + /// The evaluations of this oracle is generated by the evaluations of all mles and the padded zeros. + /// The arrangement of this oracle should be consistent to its usage in verifying the subclaim. + pub fn generate_first_oracle(&self) -> DenseMultilinearExtension { + let num_vars_added = self.log_num_first_oracles(); + let num_vars = self.num_vars + num_vars_added; + let num_zeros_padded = + ((1 << num_vars_added) - self.num_first_oracles()) * (1 << self.num_vars); + + // arrangement: all values||all decomposed bits||padded zeros + let mut evals = self.pack_first_mles(); + evals.append(&mut vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// generate second oracle + pub fn generate_second_oracle(&mut self) -> DenseMultilinearExtension { + let num_vars_added = self.log_num_second_oracles(); + let num_vars = self.num_vars + num_vars_added; + let num_zeros_padded = + ((1 << num_vars_added) - self.num_second_oracles()) * (1 << self.num_vars); + + // arrangement: all values||all decomposed bits||padded zeros + let mut evals = self.pack_second_mles(); + evals.append(&mut vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// generate_h_vec + pub fn generate_h_vec(&mut self, random_value: F) { + let num_vars = self.num_vars; + + // integrate t into columns + let mut ft_vec = self.f_vec.clone(); + ft_vec.push(self.t.clone()); + + // construct shifted columns: (f(x) - r) + let shifted_ft_vec: Vec>> = ft_vec + .iter() + .map(|f| { + let evaluations = f.evaluations.iter().map(|x| *x - random_value).collect(); + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + evaluations, + )) + }) + .collect(); + + // construct inversed shifted columns: 1 / (f(x) - r) + let mut inversed_shifted_ft_evaluation_vec = batch_inverse( + &shifted_ft_vec + .iter() + .flat_map(|f| f.iter()) + .cloned() + .collect::>(), + ); + + let total_size = inversed_shifted_ft_evaluation_vec.len(); + + inversed_shifted_ft_evaluation_vec[(total_size - (1 << num_vars))..] + .iter_mut() + .zip(self.m.evaluations.iter()) + .for_each(|(inverse_shifted_t, m)| { + *inverse_shifted_t *= -(*m); + }); + + let chunks = + inversed_shifted_ft_evaluation_vec.chunks_exact(self.block_size * (1 << num_vars)); + + let residual = chunks.remainder(); + + // construct blocked columns + let mut h_vec: Vec>> = chunks + .map(|block| { + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + block.chunks_exact(1 << num_vars).fold( + vec![F::zero(); 1 << num_vars], + |mut h_evaluations, inversed_shifted_f| { + inversed_shifted_f + .iter() + .enumerate() + .for_each(|(idx, &val)| { + h_evaluations[idx] += val; + }); + h_evaluations + }, + ), + )) + }) + .collect(); + + let h_residual = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + residual.chunks_exact(1 << num_vars).fold( + vec![F::zero(); 1 << num_vars], + |mut acc, f| { + f.iter().enumerate().for_each(|(i, &val)| { + acc[i] += val; + }); + acc + }, + ), + )); + + if self.residual_size != 0 { + h_vec.push(h_residual) + }; + + self.h_vec = h_vec; + } + + /// Evaluate at a random point defined over Field + #[inline] + pub fn evaluate(&self, point: &[F]) -> LookupInstanceEval { + LookupInstanceEval:: { + f_vec: self.f_vec.iter().map(|x| x.evaluate(point)).collect(), + t: self.t.evaluate(point), + h_vec: self.h_vec.iter().map(|x| x.evaluate(point)).collect(), + m: self.m.evaluate(point), + } + } + + /// Evaluate at a random point defined over Extension Field + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> LookupInstanceEval { + LookupInstanceEval:: { + f_vec: self.f_vec.iter().map(|x| x.evaluate_ext(point)).collect(), + t: self.t.evaluate_ext(point), + h_vec: self.h_vec.iter().map(|x| x.evaluate_ext(point)).collect(), + m: self.m.evaluate_ext(point), + } + } +} + +/// Evaluations at a random point +pub struct LookupInstanceEval { + /// f_vec + pub f_vec: Vec, + /// t + pub t: F, + /// h_vec + pub h_vec: Vec, + /// m + pub m: F, +} + +impl LookupInstanceEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_first_oracles(&self) -> usize { + self.f_vec.len() + 2 + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_first_oracles(&self) -> usize { + self.num_first_oracles().next_power_of_two().ilog2() as usize + } + + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_second_oracles(&self) -> usize { + self.h_vec.len() + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_second_oracles(&self) -> usize { + self.num_second_oracles().next_power_of_two().ilog2() as usize + } + + /// Flatten all evals into a vector with the same arrangement of the committed polynomial + #[inline] + pub fn first_flatten(&self) -> Vec { + let mut res: Vec = Vec::new(); + res.extend(self.f_vec.iter().copied()); + res.push(self.t); + res.push(self.m); + res + } +} + +// execute sumcheck for +// \sum_{x \in H_f} +// r * \sum_{i \in [block_num]} h_i(x) +// + \sum_{i \in [block_num]} eq(x, u) * (h(x) * \prod_{j \in [block_size]}(f_j(x) - r) - \sum_{i \in [block_size]} \prod_{j \in [block_size], j != i} (f_j(x) - r)) +// = c_sum +impl Lookup { + /// random combine + pub fn sample_coins(trans: &mut Transcript, instance: &LookupInstance) -> Vec { + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + instance.block_num + if instance.residual_size != 0 { 1 } else { 0 }, + ) + } + + /// return the number of coins used in this IOP + pub fn num_coins(info: &LookupInstanceInfo) -> usize { + info.block_num + if info.residual_size != 0 { 1 } else { 0 } + } + + /// verifier challenges. + pub fn prove(instance: &mut LookupInstance) -> SumcheckKit { + let mut trans = Transcript::::new(); + + let random_value = trans.get_challenge(b"random point used to generate the second oracle"); + + instance.generate_h_vec(random_value); + + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + let mut randomness = Self::sample_coins(&mut trans, instance); + randomness.push(random_value); + + let eq_at_u = Rc::new(gen_identity_evaluations(&u)); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + + Self::prove_as_subprotocol(&randomness, &mut poly, instance, &eq_at_u); + + let (proof, state) = MLSumcheck::prove_as_subprotocol(&mut trans, &poly) + .expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + info: poly.info(), + claimed_sum: F::zero(), + randomness: state.randomness, + u, + } + } + + /// Prove bit decomposition given the decomposed bits as prover key. + /// This function does the same thing as `prove`, but it uses a `Fiat-Shamir RNG` as the transcript/to generate the + /// verifier challenges. + pub fn prove_as_subprotocol( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &LookupInstance, + eq_at_u: &Rc>, + ) { + let num_vars = instance.num_vars; + let random_combine = &randomness[0..randomness.len() - 1]; + let random_value = randomness[randomness.len() - 1]; + + // integrate t into columns + let mut ft_vec = instance.f_vec.clone(); + ft_vec.push(instance.t.clone()); + + // construct shifted columns: (f(x) - r) + let shifted_ft_vec: Vec>> = ft_vec + .iter() + .map(|f| { + let evaluations = f.evaluations.iter().map(|x| *x - random_value).collect(); + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + evaluations, + )) + }) + .collect(); + + // construct poly + for ((i, h), u_coef) in instance.h_vec.iter().enumerate().zip(random_combine.iter()) { + let product = vec![h.clone()]; + let op_coef = vec![(F::one(), F::zero())]; + poly.add_product_with_linear_op(product, &op_coef, F::one()); + + let is_last_block = i == instance.h_vec.len() - 1; + + let this_block_size = if is_last_block && (instance.residual_size != 0) { + instance.residual_size + } else { + instance.block_size + }; + + let block = + &shifted_ft_vec[i * instance.block_size..i * instance.block_size + this_block_size]; + + let mut id_op_coef = vec![(F::one(), F::zero()); this_block_size + 2]; + + let mut product = block.to_vec(); + product.extend(vec![eq_at_u.clone(), h.clone()]); + poly.add_product_with_linear_op(product, &id_op_coef, *u_coef); + + id_op_coef.pop(); + id_op_coef.pop(); + + for j in 0..this_block_size { + let mut product = block.to_vec(); + product[j] = eq_at_u.clone(); + if is_last_block && (j == this_block_size - 1) { + id_op_coef.push((-F::one(), F::zero())); + product.push(instance.m.clone()); + } + + poly.add_product_with_linear_op(product, &id_op_coef, -*u_coef); + } + } + } + + /// verify + pub fn verify( + wrapper: &ProofWrapper, + evals: &LookupInstanceEval, + info: &LookupInstanceInfo, + ) -> bool { + let mut trans = Transcript::new(); + + let random_value = trans.get_challenge(b"random point used to generate the second oracle"); + + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + info.num_vars, + ); + + // randomness to combine sumcheck protocols + let mut randomness = trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(info), + ); + randomness.push(random_value); + + let mut subclaim = + MLSumcheck::verify_as_subprotocol(&mut trans, &wrapper.info, F::zero(), &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&u, &subclaim.point); + + if !Self::verify_as_subprotocol(&randomness, &mut subclaim, evals, info, eq_at_u_r) { + return false; + } + + subclaim.expected_evaluations == F::zero() + } + + /// Verify addition in Zq + pub fn verify_as_subprotocol( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &LookupInstanceEval, + info: &LookupInstanceInfo, + eq_at_u_r: F, + ) -> bool { + let random_combine = &randomness[0..randomness.len() - 1]; + let random_value = randomness[randomness.len() - 1]; + + let mut ft_vec = evals.f_vec.clone(); + ft_vec.push(evals.t); + let h_vec = &evals.h_vec; + let m_eval = evals.m; + + let chunks = ft_vec.chunks_exact(info.block_size); + let residual = Some(chunks.remainder()).into_iter(); + + let mut eval = F::zero(); + for (i, ((h_eval, f_block), r_k)) in h_vec + .iter() + .zip(chunks.chain(residual)) + .zip(random_combine.iter()) + .enumerate() + { + let is_last_block = i == (h_vec.len() - 1); + //let h_eval = h.evaluate(point); + + let shifted_f_eval_block: Vec = f_block.iter().map(|f| *f - random_value).collect(); + + let sum_of_products: F = (0..shifted_f_eval_block.len()) + .map(|idx: usize| { + shifted_f_eval_block + .iter() + .enumerate() + .fold(F::one(), |acc, (i, x)| { + let mut mult = F::one(); + if i != idx { + mult *= x; + } + if is_last_block + && (idx == shifted_f_eval_block.len() - 1) + && (i == shifted_f_eval_block.len() - 1) + { + mult *= -m_eval; + } + acc * mult + }) + }) + .fold(F::zero(), |acc, x| acc + x); + + let product = shifted_f_eval_block.iter().fold(F::one(), |acc, x| acc * x); + + eval += *h_eval + eq_at_u_r * r_k * (*h_eval * product - sum_of_products); + } + + subclaim.expected_evaluations -= eval; + + true + } +} + +/// SNARKs for lookup compied with PCS +pub struct LookupSnarks>(PhantomData, PhantomData); + +impl LookupSnarks +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, +{ + /// Complied with PCS to get SNARKs + pub fn snarks(instance: &LookupInstance, code_spec: &S) + where + H: Hash + Sync + Send, + C: LinearCode + Serialize + for<'de> Deserialize<'de>, + S: LinearCodeSpec + Clone, + { + let instance_info = instance.info(); + println!("Prove {instance_info}\n"); + + let mut prover_trans = Transcript::::new(); + + // 1. First PCS + + // Construct poly + let first_committed_poly = instance.generate_first_oracle(); + // setup + let start = Instant::now(); + let first_pp = BrakedownPCS::::setup( + first_committed_poly.num_vars, + Some(code_spec.clone()), + ); + let first_setup_time = start.elapsed().as_millis(); + // commit + let start = Instant::now(); + let (first_comm, first_comm_state) = + BrakedownPCS::::commit(&first_pp, &first_committed_poly); + let first_commit_time = start.elapsed().as_millis(); + + // 2. Second PCS + + // Convert the original instance into an instance defined over EF + let mut instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); + + // Construct poly + let random_value = + prover_trans.get_challenge(b"random point used to generate the second oracle"); + instance_ef.generate_h_vec(random_value); + let second_committed_poly = instance_ef.generate_second_oracle(); + // Setup + let start = Instant::now(); + let _second_pp = BrakedownPCS::::setup( + second_committed_poly.num_vars, + Some(code_spec.clone()), + ); + let second_setup_time = start.elapsed().as_millis(); + // Commit + let start = Instant::now(); + // let (second_comm, second_comm_state) = + // BrakedownPCS::::commit(&pp, &second_committed_poly); + let second_commit_time = start.elapsed().as_millis(); + + // 3. Prover generates the proof + let prover_start = Instant::now(); + let mut iop_proof_size = 0; + + // 3.1 Generate the random point to instantiate the sumcheck protocol + let prover_u = prover_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&prover_u)); + + // 3.2 Construct the polynomial and the claimed sum to be proved in the sumcheck protocol + let mut sumcheck_poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let claimed_sum = EF::zero(); + + let mut randomness = Lookup::sample_coins(&mut prover_trans, &instance_ef); + randomness.push(random_value); + + Lookup::prove_as_subprotocol(&randomness, &mut sumcheck_poly, &instance_ef, &eq_at_u); + let poly_info = sumcheck_poly.info(); + + // 3.3 Generate proof of sumcheck protocol + let (sumcheck_proof, sumcheck_state) = + >::prove_as_subprotocol(&mut prover_trans, &sumcheck_poly) + .expect("Proof generated in Lookup"); + + iop_proof_size += bincode::serialize(&sumcheck_proof).unwrap().len(); + let iop_prover_time = prover_start.elapsed().as_millis(); + + // 3.4 Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let start = Instant::now(); + let evals = instance_ef.evaluate(&sumcheck_state.randomness); + + // 3.5 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut first_requested_point = sumcheck_state.randomness.clone(); + first_requested_point.extend(&prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_first_oracles(), + )); + let first_oracle_eval = first_committed_poly.evaluate_ext(&first_requested_point); + + // 3.6 Generate the evaluation proof of the requested point + let first_eval_proof = BrakedownPCS::::open( + &first_pp, + &first_comm, + &first_comm_state, + &first_requested_point, + &mut prover_trans, + ); + let first_pcs_open_time = start.elapsed().as_millis(); + + // 3.7 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut second_requested_point = sumcheck_state.randomness.clone(); + second_requested_point.extend(&prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_second_oracles(), + )); + // let second_oracle_eval = second_committed_poly.evaluate(&second_requested_point); + + // 3.8 Generate the evaluation proof of the requested point + // let second_eval_proof = BrakedownPCS::::open( + // &pp, + // &second_comm, + // &second_comm_state, + // &second_requested_point, + // &mut prover_trans, + // ); + let second_pcs_open_time = start.elapsed().as_millis(); + + // 4. Verifier checks the proof + let verifier_start = Instant::now(); + let mut verifier_trans = Transcript::::new(); + + let random_value = + verifier_trans.get_challenge(b"random point used to generate the second oracle"); + + // 4.1 Generate the random point to instantiate the sumcheck protocol + let verifier_u = verifier_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + // 4.2 Generate the randomness used to randomize all the sub-sumcheck protocols + let mut randomness = verifier_trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance_info), + ); + randomness.push(random_value); + + // 4.3 Check the proof of the sumcheck protocol + let mut subclaim = >::verify_as_subprotocol( + &mut verifier_trans, + &poly_info, + claimed_sum, + &sumcheck_proof, + ) + .expect("Verify the proof generated in Lookup"); + + // 4.4 Check the evaluation over a random point of the polynomial proved in the sumcheck protocol using evaluations over these small oracles used in IOP + let eq_at_u_r = eval_identity_function(&verifier_u, &subclaim.point); + let check_subcliam = Lookup::::verify_as_subprotocol( + &randomness, + &mut subclaim, + &evals, + &instance_info, + eq_at_u_r, + ); + assert!(check_subcliam && subclaim.expected_evaluations == EF::zero()); + let iop_verifier_time = verifier_start.elapsed().as_millis(); + + // 4.5 Check the relation between these small oracles and the committed oracle + let start = Instant::now(); + let mut first_pcs_proof_size = 0; + let flatten_evals = evals.first_flatten(); + let oracle_randomness = verifier_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + evals.log_num_first_oracles(), + ); + let check_oracle = + verify_oracle_relation(&flatten_evals, first_oracle_eval, &oracle_randomness); + assert!(check_oracle); + + // 3.5 Check the evaluation of a random point over the committed oracle + + let check_pcs = BrakedownPCS::::verify( + &first_pp, + &first_comm, + &first_requested_point, + first_oracle_eval, + &first_eval_proof, + &mut verifier_trans, + ); + assert!(check_pcs); + let pcs_verifier_time = start.elapsed().as_millis(); + first_pcs_proof_size += bincode::serialize(&first_eval_proof).unwrap().len() + + bincode::serialize(&flatten_evals).unwrap().len(); + + // 4. print statistic + print_statistic( + iop_prover_time + first_pcs_open_time + second_pcs_open_time, + iop_verifier_time + pcs_verifier_time, + iop_proof_size + first_pcs_proof_size, + iop_prover_time, + iop_verifier_time, + iop_proof_size, + first_committed_poly.num_vars, + instance.num_first_oracles() + instance.num_second_oracles(), + instance.num_vars, + first_setup_time + second_setup_time, + first_commit_time + second_commit_time, + first_pcs_open_time + second_pcs_open_time, + pcs_verifier_time, + first_pcs_proof_size, + ) + } +} diff --git a/zkp/src/piop/mod.rs b/zkp/src/piop/mod.rs index 58bf2c65..0758d40a 100644 --- a/zkp/src/piop/mod.rs +++ b/zkp/src/piop/mod.rs @@ -2,14 +2,18 @@ pub mod accumulator; pub mod addition_in_zq; pub mod bit_decomposition; +pub mod look_up; pub mod ntt; pub mod rlwe_mul_rgsw; pub mod round; -pub mod zq_to_rq; +// pub mod zq_to_rq; pub use accumulator::{AccumulatorIOP, AccumulatorInstance, AccumulatorWitness}; pub use addition_in_zq::{AdditionInZq, AdditionInZqInstance}; -pub use bit_decomposition::{BitDecomposition, DecomposedBits, DecomposedBitsInfo}; +pub use bit_decomposition::{ + BitDecomposition, BitDecompositionSnarks, DecomposedBits, DecomposedBitsInfo, +}; +pub use look_up::{Lookup, LookupInstance, LookupSnarks}; pub use ntt::ntt_bare::NTTBareIOP; pub use ntt::{NTTInstance, NTTInstanceInfo, NTTIOP}; pub use rlwe_mul_rgsw::{RlweCiphertext, RlweCiphertexts, RlweMultRgswIOP, RlweMultRgswInstance}; diff --git a/zkp/src/piop/ntt/mod.rs b/zkp/src/piop/ntt/mod.rs index 33dbe9c4..04d496d1 100644 --- a/zkp/src/piop/ntt/mod.rs +++ b/zkp/src/piop/ntt/mod.rs @@ -30,31 +30,52 @@ //! \tilde{\beta}((x, b),(z,0)) * \tilde{A}_{F}^{(k-1)}(z) ( (1-u_{i})+u_{i} * \tilde{ω}^{(k)}_{i+1}(z, 0) //! + \tilde{\beta}((x, b),(z,1)) * \tilde{A}_{F}^{(k-1)}(z) ( (1-u_{i})+u_{i} * \tilde{ω}^{(k)}_{i+1}(z, 1) * ω^{2^k} -use crate::sumcheck::prover::ProverState; -use crate::sumcheck::verifier::SubClaim; -use crate::sumcheck::MLSumcheck; -use crate::sumcheck::Proof; -use crate::utils::{eval_identity_function, gen_identity_evaluations}; -use std::marker::PhantomData; -use std::rc::Rc; - +use crate::sumcheck::{prover::ProverState, verifier::SubClaim, MLSumcheck, Proof}; +use crate::sumcheck::{ProofWrapper, SumcheckKit}; +use crate::utils::{ + eval_identity_function, gen_identity_evaluations, print_statistic, verify_oracle_relation, +}; use algebra::{ - DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, MultilinearExtension, - PolynomialInfo, + utils::Transcript, AbstractExtensionField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, MultilinearExtension, PolynomialInfo, +}; +use core::fmt; +use itertools::izip; +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{LinearCode, LinearCodeSpec}, + utils::hash::Hash, + PolynomialCommitmentScheme, }; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use std::rc::Rc; +use std::time::Instant; -use ntt_bare::{NTTBareIOP, NTTBareProof, NTTBareSubclaim}; +use ntt_bare::NTTBareIOP; pub mod ntt_bare; -/// SNARKs for NTT, i.e. $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ +/// IOP for NTT, i.e. $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ pub struct NTTIOP(PhantomData); +/// SNARKs for NTT compiled with PCS +pub struct NTTSnarks>(PhantomData, PhantomData); -/// proof generated by prover -pub struct NTTProof { - /// bare ntt proof for proving $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ - pub ntt_bare_proof: NTTBareProof, +/// Stores the NTT instance with the corresponding NTT table +pub struct NTTInstance { + /// log_n is the number of the variables + /// the degree of the polynomial is N - 1 + pub num_vars: usize, + /// stores {ω^0, ω^1, ..., ω^{2N-1}} + pub ntt_table: Rc>, + /// coefficient representation of the polynomial + pub coeffs: Rc>, + /// point-evaluation representation of the polynomial + pub points: Rc>, +} + +/// All the proofs generated only in the recursive phase to prove F(u, v), which does not contain the ntt_bare_proof. +#[derive(Serialize)] +pub struct NTTRecursiveProof { /// sumcheck proof for $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ /// collective sumcheck proofs for delegation pub delegation_sumcheck_msgs: Vec>, @@ -64,41 +85,54 @@ pub struct NTTProof { pub final_claim: F, } -/// subclaim returned to verifier -pub struct NTTSubclaim { - /// subclaim returned in ntt bare for proving $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ - pub ntt_bare_subclaim: NTTBareSubclaim, - /// the first claim in the delegation process, i.e. F(u, v) - pub delegation_first_claim: F, - /// the final claim in the delegation process - pub delegation_final_claim: F, - /// the requested point in the final claim - pub final_point: Vec, -} - -/// Stores the NTT instance with the corresponding NTT table -pub struct NTTInstance { - /// log_n is the number of the variables +/// Store all the NTT instances over Field to be proved, which will be randomized into a single random NTT instance over Extension Field. +pub struct NTTInstances { + /// number of ntt instances + pub num_ntt: usize, + /// number of variables, which equals to logN. /// the degree of the polynomial is N - 1 - pub log_n: usize, + pub num_vars: usize, /// stores {ω^0, ω^1, ..., ω^{2N-1}} pub ntt_table: Rc>, - /// coefficient representation of the polynomial - pub coeffs: DenseMultilinearExtension, - /// point-evaluation representation of the polynomial - pub points: DenseMultilinearExtension, + /// store the coefficient representaions + pub coeffs: Vec>>, + /// store the point-evaluation representation + pub points: Vec>>, } /// Stores the corresponding NTT table for the verifier #[derive(Clone)] pub struct NTTInstanceInfo { + /// number of instances randomized into this NTT instance + pub num_ntt: usize, /// log_n is the number of the variables /// the degree of the polynomial is N - 1 - pub log_n: usize, + pub num_vars: usize, /// stores {ω^0, ω^1, ..., ω^{2N-1}} pub ntt_table: Rc>, } +impl fmt::Display for NTTInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "a NTT instance randomized from {} NTT instances", + self.num_ntt, + ) + } +} + +impl NTTInstanceInfo { + /// Convert to EF version + pub fn to_ef>(&self) -> NTTInstanceInfo { + NTTInstanceInfo { + num_ntt: self.num_ntt, + num_vars: self.num_vars, + ntt_table: Rc::new(self.ntt_table.iter().map(|x| EF::from_base(*x)).collect()), + } + } +} + /// store the intermediate mles generated in each iteration in the `init_fourier_table_overall` algorithm pub struct IntermediateMLEs { f_mles: Vec>>, @@ -273,27 +307,12 @@ impl NTTInstance { #[inline] pub fn info(&self) -> NTTInstanceInfo { NTTInstanceInfo { - log_n: self.log_n, + num_ntt: 1, + num_vars: self.num_vars, ntt_table: Rc::clone(&self.ntt_table), } } - /// Construct a new instance from vector - #[inline] - pub fn from_vec( - log_n: usize, - ntt_table: &Rc>, - coeffs: DenseMultilinearExtension, - points: DenseMultilinearExtension, - ) -> Self { - Self { - log_n, - ntt_table: ntt_table.clone(), - coeffs, - points, - } - } - /// Construct a new instance from slice #[inline] pub fn from_slice( @@ -303,85 +322,265 @@ impl NTTInstance { points: &Rc>, ) -> Self { Self { - log_n, + num_vars: log_n, ntt_table: ntt_table.clone(), - coeffs: coeffs.as_ref().clone(), - points: points.as_ref().clone(), + coeffs: Rc::clone(coeffs), + points: Rc::clone(points), + } + } + + /// Construct a ntt instance defined over Extension Field + #[inline] + pub fn to_ef>(&self) -> NTTInstance { + NTTInstance:: { + num_vars: self.num_vars, + ntt_table: Rc::new(self.ntt_table.iter().map(|x| EF::from_base(*x)).collect()), + coeffs: Rc::new(self.coeffs.to_ef::()), + points: Rc::new(self.points.to_ef::()), } } +} - /// Construct a new instance from given info +impl NTTInstances { + /// Construct an empty container #[inline] - pub fn from_info(info: &NTTInstanceInfo) -> Self { + pub fn new(num_vars: usize, ntt_table: &Rc>) -> Self { Self { - log_n: info.log_n, - ntt_table: info.ntt_table.to_owned(), - coeffs: >::from_evaluations_vec( - info.log_n, - vec![F::zero(); 1 << info.log_n], - ), - points: >::from_evaluations_vec( - info.log_n, - vec![F::zero(); 1 << info.log_n], - ), + num_ntt: 0, + num_vars, + ntt_table: Rc::clone(ntt_table), + coeffs: Vec::new(), + points: Vec::new(), } } - /// add ntt_instance + /// Extract the information of the NTT Instance for verification + #[inline] + pub fn info(&self) -> NTTInstanceInfo { + NTTInstanceInfo { + num_ntt: self.num_ntt, + num_vars: self.num_vars, + ntt_table: Rc::clone(&self.ntt_table), + } + } + + /// Return the number of coefficient / point oracles + #[inline] + pub fn num_oracles(&self) -> usize { + self.num_ntt + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Add a ntt instance into the container #[inline] pub fn add_ntt( &mut self, - r: F, - coeffs: &Rc>, - points: &Rc>, + coeff: &Rc>, + point: &Rc>, ) { - self.coeffs += (r, coeffs); - self.points += (r, points); + self.num_ntt += 1; + assert_eq!(self.num_vars, coeff.num_vars); + assert_eq!(self.num_vars, point.num_vars); + self.coeffs.push(Rc::clone(coeff)); + self.points.push(Rc::clone(point)); } -} -impl NTTSubclaim { - /// verify the subcliam + /// Pack all the involved small polynomials into a single vector of evaluations. + /// The arrangement of this packed MLE is not as compact as others. + /// We deliberately do like this for ease of requested evaluation on the committed polynomial. #[inline] - pub fn verify_subcliam( + pub fn pack_all_mles(&self) -> Vec { + let num_vars_added_half = self.log_num_oracles(); + let num_zeros_padded_half = + ((1 << num_vars_added_half) - self.num_oracles()) * (1 << self.num_vars); + + // arrangement: all coeffs || padded zeros || all points || padded zeros + // The advantage of this arrangement is that F(0, x) packs all evaluations of coeff-MLEs and F(1, x) packs all evaluations of point-MLEs + let padded_zeros = vec![F::zero(); num_zeros_padded_half]; + self.coeffs + .iter() + .flat_map(|coeff| coeff.iter()) + .chain(padded_zeros.iter()) + .chain(self.points.iter().flat_map(|point| point.iter())) + .chain(padded_zeros.iter()) + .copied() + .collect::>() + } + + /// Generate the oracle to be committed that is composed of all the small oracles used in IOP. + /// The evaluations of this oracle is generated by the evaluations of all mles and the padded zeros. + /// The arrangement of this oracle should be consistent to its usage in verifying the subclaim. + #[inline] + pub fn generate_oracle(&self) -> DenseMultilinearExtension { + let num_oracles_half = self.num_ntt; + let num_vars_added_half = num_oracles_half.next_power_of_two().ilog2() as usize; + let num_vars = self.num_vars + num_vars_added_half + 1; + + // arrangement: all coeffs || padded zeros || all points || padded zeros + // The advantage of this arrangement is that F(0, x) packs all evaluations of coeff-MLEs and F(1, x) packs all evaluations of point-MLEs + let evals = self.pack_all_mles(); + >::from_evaluations_vec(num_vars, evals) + } + + /// Construct a random ntt instances from all the ntt instances to be proved, with randomness defined over Field + #[inline] + pub fn extract_ntt_instance(&self, randomness: &[F]) -> NTTInstance { + assert_eq!(randomness.len(), self.num_ntt); + let mut random_coeffs = >::from_evaluations_vec( + self.num_vars, + vec![F::zero(); 1 << self.num_vars], + ); + let mut random_points = >::from_evaluations_vec( + self.num_vars, + vec![F::zero(); 1 << self.num_vars], + ); + for (r, coeff, point) in izip!(randomness, &self.coeffs, &self.points) { + random_coeffs += (*r, coeff.as_ref()); + random_points += (*r, point.as_ref()); + } + NTTInstance:: { + num_vars: self.num_vars, + ntt_table: Rc::clone(&self.ntt_table), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), + } + } + + /// Construct a random ntt instances from all the ntt instances to be proved, with randomness defined over Extension Field + #[inline] + pub fn extract_ntt_instance_to_ef>( &self, - points: &DenseMultilinearExtension, - coeffs: &DenseMultilinearExtension, - u: &[F], + randomness: &[EF], + ) -> NTTInstance { + assert_eq!(randomness.len(), self.num_ntt); + let mut random_coeffs = >::from_evaluations_vec( + self.num_vars, + vec![EF::zero(); 1 << self.num_vars], + ); + let mut random_points = >::from_evaluations_vec( + self.num_vars, + vec![EF::zero(); 1 << self.num_vars], + ); + for (r, coeff, point) in izip!(randomness, &self.coeffs, &self.points) { + // multiplication between EF (r) and F (y) + random_coeffs + .iter_mut() + .zip(coeff.iter()) + .for_each(|(x, y)| *x += *r * *y); + random_points + .iter_mut() + .zip(point.iter()) + .for_each(|(x, y)| *x += *r * *y); + } + NTTInstance:: { + num_vars: self.num_vars, + ntt_table: Rc::new(self.ntt_table.iter().map(|x| EF::from_base(*x)).collect()), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), + } + } +} + +impl NTTIOP { + /// sample the random coins before proving sumcheck protocol + pub fn sample_coins(trans: &mut Transcript, num_ntt: usize) -> Vec { + trans.get_vec_challenge( + b"randomness used to obtain the virtual random ntt instance", + num_ntt, + ) + } + + /// return the number of coins used in this IOP + pub fn num_coins(info: &NTTInstanceInfo) -> usize { + info.num_ntt + } + + /// Prove NTT instance with delegation + pub fn prove(instance: &NTTInstance) -> (SumcheckKit, NTTRecursiveProof) { + let mut trans = Transcript::new(); + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let randomness = F::one(); + let mut claimed_sum = F::zero(); + >::prove_as_subprotocol( + randomness, + &mut poly, + &mut claimed_sum, + instance, + &u, + ); + + let (proof, state) = MLSumcheck::prove_as_subprotocol(&mut trans, &poly) + .expect("fail to prove the sumcheck protocol"); + + // prove F(u, v) in a recursive manner + let recursive_proof = + >::prove_recursive(&mut trans, &state.randomness, &instance.info(), &u); + + ( + SumcheckKit { + proof, + claimed_sum, + info: poly.info(), + u, + randomness: state.randomness, + }, + recursive_proof, + ) + } + + /// Verify NTT instance with delegation + pub fn verify( + wrapper: &mut ProofWrapper, + evals_at_r: F, + evals_at_u: F, info: &NTTInstanceInfo, + recursive_proof: &NTTRecursiveProof, ) -> bool { - assert_eq!(u.len(), info.log_n); - - // check1: check the subclaim for ntt bare, i.e. $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ - // Note that the verifier delegates the computation F(u, v) to prover, so F(u, v) is included. - if !self.ntt_bare_subclaim.verify_subclaim_with_delegation( - self.delegation_first_claim, - points, - coeffs, - u, + let mut trans = Transcript::new(); + + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + info.num_vars, + ); + + let randomness = F::one(); + + let mut subclaim = MLSumcheck::verify_as_subprotocol( + &mut trans, + &wrapper.info, + wrapper.claimed_sum, + &wrapper.proof, + ) + .expect("fail to verify the sumcheck protocol"); + + let f_delegation = recursive_proof.delegation_claimed_sums[0]; + if !>::verify_as_subprotocol( + randomness, + &mut subclaim, + &mut wrapper.claimed_sum, + evals_at_r, + evals_at_u, + f_delegation, ) { return false; } - // check2: check the final claim returned from the last round of delegation - let idx = 1 << (info.log_n); - let eval = eval_identity_function(&self.final_point, &[F::zero()]) - + eval_identity_function(&self.final_point, &[F::one()]) - * (F::one() - u[info.log_n - 1] + u[info.log_n - 1] * info.ntt_table[idx]) - * info.ntt_table[1]; - - self.delegation_final_claim == eval - } -} + if !(subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero()) { + return false; + } -impl NTTIOP { - /// prove - pub fn prove(ntt_instance: &NTTInstance, u: &[F]) -> NTTProof { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, ntt_instance, u) + >::verify_recursive(&mut trans, recursive_proof, info, &u, &subclaim) } - /// The delegation of F(u, v) consists of logN - 1 rounds, each of which is a sumcheck protocol. /// /// We define $A_{F}^{(k)}:\{0,1\}^{k+1} -> \mathbb{F}$ and $ω^{(k)}_{i+1}:\{0,1\}^{k+1} -> \mathbb{F}$. @@ -402,7 +601,7 @@ impl NTTIOP { /// * f: MLE \tilde{A}_{F}^{(k-1)}(z) for z\in \{0,1\}^k /// * w: MLE \tilde{ω}^{(k)}_{i+1}(z, b) for z\in \{0,1\}^k and b\in \{0, 1\}, which will be divided into two smaller MLEs \tilde{ω}^{(k)}_{i+1}(z, 0) and \tilde{ω}^{(k)}_{i+1}(z, 1) pub fn delegation_prover_round( - fs_rng: &mut impl RngCore, + trans: &mut Transcript, round: usize, point: &[F], u_i: F, @@ -414,10 +613,6 @@ impl NTTIOP { assert_eq!(w.num_vars, round + 1); let mut poly = >::new(round); - let mut product_left = Vec::with_capacity(3); - let mut product_right = Vec::with_capacity(3); - let mut ops_left = Vec::with_capacity(3); - let mut ops_right = Vec::with_capacity(3); // the equality function defined by the random point $(x, b)\in \mathbb{F}^{k+1}$ // it is divided into two MLEs \tilde{\beta}((x, b),(z,0)) and \tilde{\beta}((x, b),(z,1)) @@ -430,44 +625,49 @@ impl NTTIOP { // construct the polynomial to be sumed // left product is \tilde{\beta}((x, b),(z,0)) * \tilde{A}_{F}^{(k-1)}(z) ( (1-u_{i})+u_{i} * \tilde{ω}^{(k)}_{i+1}(z, 0) // right product is \tilde{\beta}((x, b),(z,1)) * \tilde{A}_{F}^{(k-1)}(z) ( (1-u_{i})+u_{i} * \tilde{ω}^{(k)}_{i+1}(z, 1) * ω^{2^k} - product_left.push(Rc::new(eq_func_left)); - ops_left.push((F::one(), F::zero())); - product_left.push(Rc::clone(f)); - ops_left.push((F::one(), F::zero())); - product_left.push(Rc::new(w_left)); - ops_left.push((u_i, F::one() - u_i)); - poly.add_product_with_linear_op(product_left, &ops_left, F::one()); - - product_right.push(Rc::new(eq_func_right)); - ops_right.push((F::one(), F::zero())); - product_right.push(Rc::clone(f)); - ops_right.push((F::one(), F::zero())); - product_right.push(Rc::new(w_right)); - ops_right.push((u_i, F::one() - u_i)); - poly.add_product_with_linear_op(product_right, &ops_right, w_coeff); - - MLSumcheck::prove_as_subprotocol(fs_rng, &poly) + poly.add_product_with_linear_op( + [Rc::new(eq_func_left), Rc::clone(f), Rc::new(w_left)], + &[ + (F::one(), F::zero()), + (F::one(), F::zero()), + (u_i, F::one() - u_i), + ], + F::one(), + ); + + poly.add_product_with_linear_op( + [Rc::new(eq_func_right), Rc::clone(f), Rc::new(w_right)], + &[ + (F::one(), F::zero()), + (F::one(), F::zero()), + (u_i, F::one() - u_i), + ], + w_coeff, + ); + + MLSumcheck::prove_as_subprotocol(trans, &poly) .expect("ntt proof of delegation failed in round {round}") } - /// prove NTT with delegation - pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, - ntt_instance: &NTTInstance, + /// Compared to the `prove` functionality, we just remove the phase to prove NTT bare. + /// + /// * `ntt_bare_state`: stores the prover state after proving the NTT bare + pub fn prove_recursive( + trans: &mut Transcript, + ntt_bare_randomness: &[F], + info: &NTTInstanceInfo, u: &[F], - ) -> NTTProof { - let log_n = ntt_instance.log_n; + ) -> NTTRecursiveProof { + let log_n = info.num_vars; - let intermediate_mles = init_fourier_table_overall(u, &ntt_instance.ntt_table); + let intermediate_mles = init_fourier_table_overall(u, &info.ntt_table); let (f_mles, w_mles) = (intermediate_mles.f_mles, intermediate_mles.w_mles); - // 1. prove a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) } for a random point u - let f_u = &f_mles[log_n - 1]; - let (ntt_bare_proof, state) = - NTTBareIOP::prove_as_subprotocol(fs_rng, f_u, ntt_instance, u); + // 1. (detached) prove a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) } for a random point u // the above sumcheck is reduced to prove F(u, v) where v is the requested point - let mut requested_point = state.randomness; + // Note that the delegated value F(u, v) is stored in proof.delegation_claimed_sums[0]. + let mut requested_point = ntt_bare_randomness.to_owned(); let mut reduced_claim = f_mles[log_n - 1].evaluate(&requested_point); // 2. prove the computation of F(u, v) in log_n - 1 rounds @@ -481,10 +681,10 @@ impl NTTIOP { let i = log_n - 1 - k; delegation_claimed_sums.push(reduced_claim); - let w_coeff = ntt_instance.ntt_table[1 << k]; + let w_coeff = info.ntt_table[1 << k]; let f = &f_mles[k - 1]; let (proof_round, state_round) = Self::delegation_prover_round( - fs_rng, + trans, k, &requested_point, u[i], @@ -499,25 +699,13 @@ impl NTTIOP { reduced_claim = f.evaluate(&requested_point); } - NTTProof { - ntt_bare_proof, + NTTRecursiveProof { delegation_sumcheck_msgs, delegation_claimed_sums, final_claim: reduced_claim, } } - /// prove NTT with delegation - pub fn verify( - proof: &NTTProof, - ntt_instance_info: &NTTInstanceInfo, - u: &[F], - ) -> NTTSubclaim { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verify_as_subprotocol(&mut fs_rng, proof, ntt_instance_info, u) - } - /// The delegation of F(u, v) consists of logN - 1 rounds, each of which is a sumcheck protocol. /// /// We define $A_{F}^{(k)}:\{0,1\}^{k+1} -> \mathbb{F}$ and $ω^{(k)}_{i+1}:\{0,1\}^{k+1} -> \mathbb{F}$. @@ -545,7 +733,7 @@ impl NTTIOP { reduced_claim: F, ntt_instance_info: &NTTInstanceInfo, ) -> bool { - let log_n = ntt_instance_info.log_n; + let log_n = ntt_instance_info.num_vars; let ntt_table = &ntt_instance_info.ntt_table; // r_left = (r, 0) and r_right = (r, 0) @@ -574,24 +762,24 @@ impl NTTIOP { eval == subclaim.expected_evaluations } - /// verify NTT with delegation - pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &NTTProof, - ntt_instance_info: &NTTInstanceInfo, + /// Compared to the `prove` functionality, we remove the phase to prove NTT bare. + /// Also, after detaching the verfication of NTT bare, verifier can directly check the recursive proofs. + pub fn verify_recursive( + trans: &mut Transcript, + proof: &NTTRecursiveProof, + info: &NTTInstanceInfo, u: &[F], - ) -> NTTSubclaim { - let log_n = ntt_instance_info.log_n; + subclaim: &SubClaim, + ) -> bool { + let log_n = info.num_vars; assert_eq!(proof.delegation_sumcheck_msgs.len(), log_n - 1); assert_eq!(proof.delegation_claimed_sums.len(), log_n - 1); - // TODO sample randomness via Fiat-Shamir RNG - // 1. verify a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) } for a random point u - let ntt_bare_subclaim = - NTTBareIOP::verify_as_subprotocol(fs_rng, &proof.ntt_bare_proof, ntt_instance_info); + // 1. [detached] verify a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) } for a random point u + // Note that the delegated value F(u, v) is stored in proof.delegation_claimed_sums[0]. // 2. verify the computation of F(u, v) in log_n - 1 rounds - let mut requested_point = ntt_bare_subclaim.point.clone(); + let mut requested_point = subclaim.point.clone(); for (cnt, k) in (1..log_n).rev().enumerate() { let i = log_n - 1 - k; @@ -601,7 +789,7 @@ impl NTTIOP { num_variables: k, }; let subclaim = MLSumcheck::verify_as_subprotocol( - fs_rng, + trans, &poly_info, proof.delegation_claimed_sums[cnt], &proof.delegation_sumcheck_msgs[cnt], @@ -623,22 +811,262 @@ impl NTTIOP { u[i], &subclaim, reduced_claim, - ntt_instance_info, + info, ) { panic!("ntt verification failed in round {cnt}"); } requested_point = subclaim.point; } + let delegation_final_claim = proof.final_claim; + let final_point = requested_point; // TODO: handle the case that log = 1 - // TODO: handle the case that log = 1 - assert_eq!(requested_point.len(), 1); - NTTSubclaim { - ntt_bare_subclaim, - delegation_first_claim: proof.delegation_claimed_sums[0], - delegation_final_claim: proof.final_claim, - final_point: requested_point, - } + assert_eq!(final_point.len(), 1); + + // check the final claim returned from the last round of delegation + let idx = 1 << (info.num_vars); + let eval = eval_identity_function(&final_point, &[F::zero()]) + + eval_identity_function(&final_point, &[F::one()]) + * (F::one() - u[info.num_vars - 1] + u[info.num_vars - 1] * info.ntt_table[idx]) + * info.ntt_table[1]; + + delegation_final_claim == eval + } +} + +impl NTTSnarks +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, +{ + /// Generate and check snarks + pub fn snarks(instance: &NTTInstances, code_spec: &S) + where + H: Hash + Sync + Send, + C: LinearCode + Serialize + for<'de> Deserialize<'de>, + S: LinearCodeSpec + Clone, + { + let instance_info = instance.info(); + println!("Prove {instance_info}\n"); + // This is the actual polynomial to be committed for prover, which consists of all the required small polynomials in the IOP and padded zero polynomials. + let committed_poly = instance.generate_oracle(); + + // 1. Use PCS to commit the above polynomial. + let start = Instant::now(); + let pp = + BrakedownPCS::::setup(committed_poly.num_vars, Some(code_spec.clone())); + let setup_time = start.elapsed().as_millis(); + + let start = Instant::now(); + let (comm, comm_state) = BrakedownPCS::::commit(&pp, &committed_poly); + let commit_time = start.elapsed().as_millis(); + + // 2. Prover generates the proof + let prover_start = Instant::now(); + let mut iop_proof_size = 0; + let mut prover_trans = Transcript::::new(); + + // 2.1 Generate the random point to instantiate the sumcheck protocol + let prover_u = prover_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + // 2.? [one more step] Prover generate the random ntt instance from all instances to be proved + let prover_r = >::sample_coins(&mut prover_trans, instance_info.num_ntt); + let instance_ef = instance.extract_ntt_instance_to_ef::(&prover_r); + let instance_ef_info = instance_ef.info(); + + // 2.2 Construct the polynomial and the claimed sum to be proved in the sumcheck protocol + let mut sumcheck_poly = >::new(instance.num_vars); + let mut claimed_sum = EF::zero(); + >::prove_as_subprotocol( + EF::one(), + &mut sumcheck_poly, + &mut claimed_sum, + &instance_ef, + &prover_u, + ); + + let poly_info = sumcheck_poly.info(); + + // 2.3 Generate proof of sumcheck protocol + let (sumcheck_proof, sumcheck_state) = + >::prove_as_subprotocol(&mut prover_trans, &sumcheck_poly) + .expect("Proof generated in Addition In Zq"); + iop_proof_size += bincode::serialize(&sumcheck_proof).unwrap().len(); + + // 2.? [one more step] Prover recursive prove the evaluation of F(u, v) + let recursive_proof = >::prove_recursive( + &mut prover_trans, + &sumcheck_state.randomness, + &instance_ef_info, + &prover_u, + ); + iop_proof_size += bincode::serialize(&recursive_proof).unwrap().len(); + let iop_prover_time = prover_start.elapsed().as_millis(); + + // 2.4 Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let coeff_evals_at_r = instance + .coeffs + .iter() + .map(|x| x.evaluate_ext(&sumcheck_state.randomness)) + .collect::>(); + let point_evals_at_u = instance + .points + .iter() + .map(|x| x.evaluate_ext(&prover_u)) + .collect::>(); + + // 2.5 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut coeff_requested_point = sumcheck_state.randomness.clone(); + let mut point_requested_point = prover_u.clone(); + let oracle_randomness = prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_oracles(), + ); + coeff_requested_point.extend(&oracle_randomness); + point_requested_point.extend(&oracle_randomness); + coeff_requested_point.push(EF::zero()); + point_requested_point.push(EF::one()); + + let oracle_coeff_eval = committed_poly.evaluate_ext(&coeff_requested_point); + let oracle_point_eval = committed_poly.evaluate_ext(&point_requested_point); + + // 2.6 Generate the evaluation proof of the requested points + let start = Instant::now(); + // requested point [sumcheck_r, oracle_r, 0] + let coeff_eval_proof = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &coeff_requested_point, + &mut prover_trans, + ); + // requested point [prover_u, oracle_r, 1] + let point_eval_proof = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &point_requested_point, + &mut prover_trans, + ); + let pcs_open_time = start.elapsed().as_millis(); + + // 3. Verifier checks the proof + let verifier_start = Instant::now(); + let mut verifier_trans = Transcript::::new(); + + // 3.1 Generate the random point to instantiate the sumcheck protocol + let verifier_u = verifier_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance_info.num_vars, + ); + + // 3.2 Verifier sample random coins to combine all sumcheck protocols proving ntt instances + let verifier_r = verifier_trans.get_vec_challenge( + b"randomness used to obtain the virtual random ntt instance", + instance_info.num_ntt, + ); + + // 3.3 Check the proof of the sumcheck protocol + let mut subclaim = >::verify_as_subprotocol( + &mut verifier_trans, + &poly_info, + claimed_sum, + &sumcheck_proof, + ) + .expect("Verify the proof generated in NTT"); + + // 3.4 Check the subclaim returned from the sumcheck protocol + let f_delegation = recursive_proof.delegation_claimed_sums[0]; + let evals_at_r = verifier_r + .iter() + .zip(coeff_evals_at_r.iter()) + .fold(EF::zero(), |acc, (r, eval)| acc + *r * *eval); + let evals_at_u = verifier_r + .iter() + .zip(point_evals_at_u.iter()) + .fold(EF::zero(), |acc, (r, eval)| acc + *r * *eval); + + let check_subclaim = >::verify_as_subprotocol( + EF::one(), + &mut subclaim, + &mut claimed_sum, + evals_at_r, + evals_at_u, + f_delegation, + ); + assert!(check_subclaim); + assert_eq!(subclaim.expected_evaluations, EF::zero()); + assert_eq!(claimed_sum, EF::zero()); + // Check the delegation of F(u, v) used in the above check + let check_recursive = >::verify_recursive( + &mut verifier_trans, + &recursive_proof, + &instance_ef_info, + &verifier_u, + &subclaim, + ); + assert!(check_recursive); + + // 3.5 and also check the relation between these small oracles and the committed oracle + let oracle_randomness = verifier_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_oracles(), + ); + let check_oracle_coeff = + verify_oracle_relation(&coeff_evals_at_r, oracle_coeff_eval, &oracle_randomness); + let check_oracle_point = + verify_oracle_relation(&point_evals_at_u, oracle_point_eval, &oracle_randomness); + assert!(check_oracle_coeff); + assert!(check_oracle_point); + let iop_verifier_time = verifier_start.elapsed().as_millis(); + + // 3.5 Check the evaluation of a random point over the committed oracle + let start = Instant::now(); + let mut pcs_proof_size = 0; + let check_pcs_coeff = BrakedownPCS::::verify( + &pp, + &comm, + &coeff_requested_point, + oracle_coeff_eval, + &coeff_eval_proof, + &mut verifier_trans, + ); + let check_pcs_point = BrakedownPCS::::verify( + &pp, + &comm, + &point_requested_point, + oracle_point_eval, + &point_eval_proof, + &mut verifier_trans, + ); + assert!(check_pcs_coeff); + assert!(check_pcs_point); + let pcs_verifier_time = start.elapsed().as_millis(); + pcs_proof_size += bincode::serialize(&coeff_eval_proof).unwrap().len() + + bincode::serialize(&coeff_evals_at_r).unwrap().len() + + bincode::serialize(&point_eval_proof).unwrap().len() + + bincode::serialize(&point_evals_at_u).unwrap().len(); + + // 4. print statistic + print_statistic( + iop_prover_time + pcs_open_time, + iop_verifier_time + pcs_verifier_time, + iop_proof_size + pcs_proof_size, + iop_prover_time, + iop_verifier_time, + iop_proof_size, + committed_poly.num_vars, + instance.num_oracles(), + instance.num_vars, + setup_time, + commit_time, + pcs_open_time, + pcs_verifier_time, + pcs_proof_size, + ) } } diff --git a/zkp/src/piop/ntt/ntt_bare.rs b/zkp/src/piop/ntt/ntt_bare.rs index c9637f66..fec7bdff 100644 --- a/zkp/src/piop/ntt/ntt_bare.rs +++ b/zkp/src/piop/ntt/ntt_bare.rs @@ -16,49 +16,21 @@ //! The LHS and RHS of the above equation are both MLE for y, so it can be reduced to check at a random point due to Schwartz-Zippel Lemma. //! The remaining thing is to prove $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ with the sumcheck protocol //! where u is the random challenge from the verifier. - -use crate::sumcheck::prover::ProverMsg; -use crate::sumcheck::prover::ProverState; -use crate::sumcheck::MLSumcheck; -use std::marker::PhantomData; -use std::rc::Rc; - +use crate::sumcheck::verifier::SubClaim; +use crate::sumcheck::{MLSumcheck, ProofWrapper, SumcheckKit}; use algebra::{ - DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, MultilinearExtension, - PolynomialInfo, + utils::Transcript, DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, + MultilinearExtension, }; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; +use serde::Serialize; +use std::marker::PhantomData; +use std::rc::Rc; use super::{NTTInstance, NTTInstanceInfo}; /// IOP for NTT, i.e. $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ pub struct NTTBareIOP(PhantomData); -/// proof generated by prover in bare ntt, which only consists of the sumcheck without delegation for F(u, v) -/// Without delegation, prover only needs to prove this sum -/// $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ -/// where u is a random point, given by verifier -pub struct NTTBareProof { - /// sumcheck_msg when proving - pub sumcheck_msg: Vec>, - /// the claimed sum is a(u) - pub claimed_sum: F, -} - -/// subclaim returned in bare ntt, which only consists of the sumcheck without delegation for F(u, v) -/// Without delegation, prover only needs to prove this sum -/// $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ -/// where u is a random point, given by verifier -pub struct NTTBareSubclaim { - /// the claimed sum is a(u) - pub claimed_sum: F, - /// the proof is reduced to the evaluation of this point (denoted by v) - pub point: Vec, - /// the proof is reduced to the evaluation equals to c(v) \cdot F(u, v) - pub expected_evaluation: F, -} - /// Naive implementation for initializing F(u, x) in NTT, which helps readers to understand the following dynamic programming version (`init_fourier_table``). /// The formula is derived from zkCNN (https://eprint.iacr.org/2021/673) /// In NTT, the Fourier matrix is different since we choose these points: ω^1, ω^3, ..., ω^{2N-1} @@ -153,144 +125,100 @@ pub fn init_fourier_table(u: &[F], ntt_table: &[F]) -> DenseMultilinea DenseMultilinearExtension::from_evaluations_vec(log_n, evaluations) } -impl NTTBareSubclaim { - /// verify the subcliam for sumcheck - /// $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ - /// - /// 1. a(u) = claimed_sum - /// 2. c(v) * F(u, v) = expected_evaluation - /// # Arguments - /// - /// * fourier_matrix: F(x, y) oracle - /// * points: a(x) oracle - /// * coeffs: c(x) oracle - /// * u: the random point sampled by verifier before executing the sumcheck protocol - #[inline] - pub fn verify_subclaim( - &self, - fourier_matrix: &DenseMultilinearExtension, - points: &DenseMultilinearExtension, - coeffs: &DenseMultilinearExtension, - u: &[F], - info: &NTTInstanceInfo, - ) -> bool { - assert_eq!(u.len(), info.log_n); - - // check 1: a(u) = claimed_sum - if self.claimed_sum != points.evaluate(u) { - return false; +impl NTTBareIOP { + /// Prove NTT instance without delegation + pub fn prove(instance: &NTTInstance) -> SumcheckKit { + let mut trans = Transcript::::new(); + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let randomness = F::one(); + let mut claimed_sum = F::zero(); + Self::prove_as_subprotocol(randomness, &mut poly, &mut claimed_sum, instance, &u); + // let evals_u = instance.points.evaluate(&u); + + let (proof, state) = MLSumcheck::prove_as_subprotocol(&mut trans, &poly) + .expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + info: poly.info(), + claimed_sum, + randomness: state.randomness, + u, } - - // check 2: c(v) * F(u, v) = expected_evaluation - let mut u_v: Vec<_> = Vec::with_capacity(info.log_n << 1); - u_v.extend(u); - u_v.extend(&self.point); - self.expected_evaluation == coeffs.evaluate(&self.point) * fourier_matrix.evaluate(&u_v) } - /// verify the subcliam for sumcheck - /// Compared to the `verify_subcliam`, verify delegate the computation F(u, v) to the prover, - /// so in this case verify can receives the evaluation F(u, v). - /// - /// $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ - /// - /// 1. a(u) = claimed_sum - /// 2. c(v) * F(u, v) = expected_evaluation - /// # Arguments - /// - /// * f_delegation: F(u, v) computed by prover - /// * points: a(x) oracle - /// * coeffs: c(x) oracle - /// * u: the random point sampled by verifier before executing the sumcheck protocol - pub fn verify_subclaim_with_delegation( - &self, - f_delegation: F, - points: &DenseMultilinearExtension, - coeffs: &DenseMultilinearExtension, - u: &[F], - ) -> bool { - // check 1: a(u) = claimed_sum - if self.claimed_sum != points.evaluate(u) { - return false; - } - - // check 2: c(v) * F(u, v) = expected_evaluation - self.expected_evaluation == coeffs.evaluate(&self.point) * f_delegation - } -} - -impl NTTBareIOP { - /// prove - pub fn prove( - ntt_instance: &NTTInstance, - f_u: &Rc>, - u: &[F], - ) -> NTTBareProof { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, f_u, ntt_instance, u).0 - } - - /// prove + /// Add the sumcheck proving NTT into the polynomial pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, - f_u: &Rc>, - ntt_instance: &NTTInstance, + randomness: F, + poly: &mut ListOfProductsOfPolynomials, + claimed_sum: &mut F, + instance: &NTTInstance, u: &[F], - ) -> (NTTBareProof, ProverState) { - let log_n = ntt_instance.log_n; - - let mut poly = >::new(log_n); - - let product = vec![Rc::clone(f_u), Rc::new(ntt_instance.coeffs.clone())]; - poly.add_product(product, F::one()); - - let (prover_msg, prover_state) = - MLSumcheck::prove_as_subprotocol(fs_rng, &poly).expect("ntt bare proof failed"); - - ( - NTTBareProof { - sumcheck_msg: prover_msg, - claimed_sum: ntt_instance.points.evaluate(u), - }, - prover_state, - ) + ) { + let f_u = Rc::new(init_fourier_table(u, &instance.ntt_table)); + poly.add_product([Rc::clone(&f_u), Rc::clone(&instance.coeffs)], randomness); + *claimed_sum += randomness * instance.points.evaluate(u); } - /// verify + /// Verify NTT instance without delegation pub fn verify( - ntt_bare_proof: &NTTBareProof, - ntt_instance_info: &NTTInstanceInfo, - ) -> NTTBareSubclaim { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verify_as_subprotocol(&mut fs_rng, ntt_bare_proof, ntt_instance_info) + wrapper: &mut ProofWrapper, + evals_at_r: F, + evals_at_u: F, + info: &NTTInstanceInfo, + ) -> bool { + let mut trans = Transcript::new(); + + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + info.num_vars, + ); + + let f_u = init_fourier_table(&u, &info.ntt_table); + // randomness to combine sumcheck protocols + let randomness = F::one(); + + let mut subclaim = MLSumcheck::verify_as_subprotocol( + &mut trans, + &wrapper.info, + wrapper.claimed_sum, + &wrapper.proof, + ) + .expect("fail to verify the sumcheck protocol"); + + let f_delegation = f_u.evaluate(&subclaim.point); + + if !Self::verify_as_subprotocol( + randomness, + &mut subclaim, + &mut wrapper.claimed_sum, + evals_at_r, + evals_at_u, + f_delegation, + ) { + return false; + } + + subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero() } - /// verify + /// Verify the evaluation of the sumcheck proving NTT pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, - ntt_bare_proof: &NTTBareProof, - ntt_instance_info: &NTTInstanceInfo, - ) -> NTTBareSubclaim { - // TODO sample randomness via Fiat-Shamir RNG - let poly_info = PolynomialInfo { - max_multiplicands: 2, - num_variables: ntt_instance_info.log_n, - }; - let subclaim = MLSumcheck::verify_as_subprotocol( - fs_rng, - &poly_info, - ntt_bare_proof.claimed_sum, - &ntt_bare_proof.sumcheck_msg, - ) - .expect("ntt bare verification failed"); - - NTTBareSubclaim { - claimed_sum: ntt_bare_proof.claimed_sum, - point: subclaim.point, - expected_evaluation: subclaim.expected_evaluations, - } + randomness: F, + subclaim: &mut SubClaim, + claimed_sum: &mut F, + evals_at_r: F, + evals_at_u: F, + f_delegation: F, + ) -> bool { + subclaim.expected_evaluations -= evals_at_r * f_delegation * randomness; + *claimed_sum -= evals_at_u * randomness; + true } } diff --git a/zkp/src/piop/rlwe_mul_rgsw.rs b/zkp/src/piop/rlwe_mul_rgsw.rs index 6de90768..bf2ac3fa 100644 --- a/zkp/src/piop/rlwe_mul_rgsw.rs +++ b/zkp/src/piop/rlwe_mul_rgsw.rs @@ -24,53 +24,44 @@ //! //! Hence, there are 2k + 2 NTT instances in this single multiplication instance. We can randomize all these 2k+2 NTT instances to obtain a single NTT instance, //! and use our NTT IOP to prove this randomized NTT instance. +use super::bit_decomposition::BitDecomposition; +use super::bit_decomposition::DecomposedBitsEval; +use super::ntt::NTTRecursiveProof; +use super::NTTBareIOP; +use super::{DecomposedBits, DecomposedBitsInfo, NTTInstance, NTTInstanceInfo, NTTIOP}; use crate::sumcheck::verifier::SubClaim; use crate::sumcheck::MLSumcheck; -use crate::sumcheck::Proof; -use crate::utils::{eval_identity_function, gen_identity_evaluations}; -use std::marker::PhantomData; -use std::rc::Rc; -use std::vec; - +use crate::sumcheck::ProofWrapper; +use crate::sumcheck::SumcheckKit; +use crate::utils::{ + add_assign_ef, eval_identity_function, gen_identity_evaluations, print_statistic, + verify_oracle_relation, +}; use algebra::{ - DenseMultilinearExtension, Field, FieldUniformSampler, ListOfProductsOfPolynomials, - MultilinearExtension, PolynomialInfo, + utils::Transcript, AbstractExtensionField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, MultilinearExtension, }; +use core::fmt; use itertools::izip; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; -use rand_distr::Distribution; - -use super::bit_decomposition::{BitDecomposition, BitDecompositionProof, BitDecompositionSubClaim}; -use super::ntt::{NTTProof, NTTSubclaim}; -use super::{DecomposedBits, DecomposedBitsInfo, NTTInstance, NTTInstanceInfo, NTTIOP}; -/// SNARKs for Multiplication between RLWE ciphertext and RGSW ciphertext +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{LinearCode, LinearCodeSpec}, + utils::hash::Hash, + PolynomialCommitmentScheme, +}; +use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; +use std::rc::Rc; +use std::time::Instant; +use std::vec; +/// IOP for RLWE * RGSW pub struct RlweMultRgswIOP(PhantomData); -/// proof generated by prover -pub struct RlweMultRgswProof { - /// proof for bit decomposition - pub bit_decomposition_proof: BitDecompositionProof, - /// proof for ntt - pub ntt_proof: NTTProof, - /// proof for sumcheck - pub sumcheck_msg: Proof, -} - -/// subclaim reutrned to verifier -pub struct RlweMultRgswSubclaim { - /// subclaim returned from the Bit Decomposition IOP - pub bit_decomposition_subclaim: BitDecompositionSubClaim, - /// randomness used to randomize 2k + 2 ntt instances - pub randomness_ntt: Vec, - /// subclaim returned from the NTT IOP - pub ntt_subclaim: NTTSubclaim, - /// randomness used in combination of the two sumcheck protocol - pub randomness_sumcheck: Vec, - /// subclaim returned from the sumcheck protocol - pub sumcheck_subclaim: SubClaim, -} - +/// SNARKs for RLWE * RGSW +pub struct RlweMultRgswSnarks>( + PhantomData, + PhantomData, +); /// RLWE ciphertext (a, b) where a and b represents two polynomials in some defined polynomial ring. /// Note that it can represent either a coefficient-form or a NTT-form. #[derive(Clone)] @@ -90,30 +81,16 @@ pub struct RlweCiphertexts { pub b_bits: Vec>>, } -impl RlweCiphertexts { - /// Construct an empty rlweciphertexts - pub fn new(bits_len: usize) -> Self { - Self { - a_bits: Vec::with_capacity(bits_len), - b_bits: Vec::with_capacity(bits_len), - } - } - - /// Add a RLWE ciphertext - pub fn add_rlwe(&mut self, a: DenseMultilinearExtension, b: DenseMultilinearExtension) { - self.a_bits.push(Rc::new(a)); - self.b_bits.push(Rc::new(b)); - } -} - /// Stores the multiplication instance between RLWE ciphertext and RGSW ciphertext with the corresponding NTT table /// Given (a, b) \in RLWE where a and b are two polynomials represented by N coefficients, /// and (c, f) \in RGSW = RLWE' \times RLWE' = (RLWE, ..., RLWE) \times (RLWE, ..., RLWE) where c = ((c0, c0'), ..., (ck-1, ck-1')) and f = ((f0, f0'), ..., (fk-1, fk-1')) pub struct RlweMultRgswInstance { - /// randomized ntt instance to be proved - pub ntt_instance: NTTInstance, + /// number of variables + pub num_vars: usize, /// info of decomposed bits - pub decomposed_bits_info: DecomposedBitsInfo, + pub bits_info: DecomposedBitsInfo, + /// info of ntt instance + pub ntt_info: NTTInstanceInfo, /// rlwe = (a, b): store the input ciphertext (a, b) where a and b are two polynomials represented by N coefficients. pub input_rlwe: RlweCiphertext, /// bits_rlwe = (a_bits, b_bits): a_bits (b_bits) corresponds to the bit decomposition result of a (b) in the input rlwe ciphertext @@ -122,21 +99,184 @@ pub struct RlweMultRgswInstance { pub bits_rlwe_ntt: RlweCiphertexts, /// bits_rgsw_c_ntt: the ntt form of the first part (c) in the RGSW ciphertext pub bits_rgsw_c_ntt: RlweCiphertexts, - /// bits_rgsw_c_ntt: the ntt form of the second part (f) in the RGSW ciphertext + /// bits_rgsw_f_ntt: the ntt form of the second part (f) in the RGSW ciphertext pub bits_rgsw_f_ntt: RlweCiphertexts, /// output_rlwe_ntt: store the output ciphertext (g', h') in the NTT-form pub output_rlwe_ntt: RlweCiphertext, - /// output_rlwe: store the output ciphertext (g, h) in the coefficient-form - pub output_rlwe: RlweCiphertext, + // output_rlwe: store the output ciphertext (g, h) in the coefficient-form + // pub output_rlwe: RlweCiphertext, +} + +/// Evaluation of RlweMultRgsw at the same random point +pub struct RlweMultRgswEval { + /// length of bits when decomposing bits + pub bits_len: usize, + /// rlwe = (a, b): store the input ciphertext (a, b) where a and b are two polynomials represented by N coefficients. + pub input_rlwe: RlweEval, + /// bits_rlwe = (a_bits, b_bits): a_bits (b_bits) corresponds to the bit decomposition result of a (b) in the input rlwe ciphertext + pub bits_rlwe: RlwesEval, + /// bits_rlwe_ntt: ntt form of the above bit decomposition result + pub bits_rlwe_ntt: RlwesEval, + /// bits_rgsw_c_ntt: the ntt form of the first part (c) in the RGSW ciphertext + pub bits_rgsw_c_ntt: RlwesEval, + /// bits_rgsw_f_ntt: the ntt form of the second part (f) in the RGSW ciphertext + pub bits_rgsw_f_ntt: RlwesEval, + /// output_rlwe_ntt: store the output ciphertext (g', h') in the NTT-form + pub output_rlwe_ntt: RlweEval, } +/// Evaluation of RlweCiphertext at the same random point +pub type RlweEval = (F, F); +/// Evaluation of RlweCiphertexts at the same random point +pub type RlwesEval = (Vec, Vec); + /// store the information used to verify #[derive(Clone)] pub struct RlweMultRgswInfo { + /// number of variables + pub num_vars: usize, /// information of ntt instance pub ntt_info: NTTInstanceInfo, /// information of bit decomposition - pub decomposed_bits_info: DecomposedBitsInfo, + pub bits_info: DecomposedBitsInfo, +} + +impl fmt::Display for RlweMultRgswInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "An instance of RLWE * RGSW: #vars = {}", self.num_vars)?; + write!(f, "- containing ")?; + self.bits_info.fmt(f)?; + write!(f, "\n- containing")?; + self.ntt_info.fmt(f) + } +} + +impl RlweMultRgswInfo { + /// Convert to EF version + pub fn to_ef>(&self) -> RlweMultRgswInfo { + RlweMultRgswInfo:: { + num_vars: self.num_vars, + ntt_info: self.ntt_info.to_ef::(), + bits_info: self.bits_info.to_ef::(), + } + } +} + +impl RlweCiphertext { + /// Pack mles + #[inline] + pub fn pack_all_mles(&self) -> Vec { + self.a + .iter() + .chain(self.b.iter()) + .copied() + .collect::>() + } + + /// Convert to EF version + #[inline] + pub fn to_ef>(&self) -> RlweCiphertext { + RlweCiphertext:: { + a: Rc::new(self.a.to_ef::()), + b: Rc::new(self.b.to_ef::()), + } + } + + /// Evaluate at the same random point defined F + #[inline] + pub fn evaluate(&self, point: &[F]) -> RlweEval { + (self.a.evaluate(point), self.b.evaluate(point)) + } + + /// Evaluate at the same random point defined over EF + #[inline] + pub fn evaluate_ext>(&self, point: &[EF]) -> RlweEval { + (self.a.evaluate_ext(point), self.b.evaluate_ext(point)) + } +} + +impl RlweCiphertexts { + /// Construct an empty rlweciphertexts + pub fn new(bits_len: usize) -> Self { + Self { + a_bits: Vec::with_capacity(bits_len), + b_bits: Vec::with_capacity(bits_len), + } + } + + /// Add a RLWE ciphertext + pub fn add_rlwe(&mut self, a: DenseMultilinearExtension, b: DenseMultilinearExtension) { + self.a_bits.push(Rc::new(a)); + self.b_bits.push(Rc::new(b)); + } + + /// Is empty + pub fn is_empty(&self) -> bool { + if self.a_bits.is_empty() || self.b_bits.is_empty() { + return true; + } + false + } + /// return the len + pub fn len(&self) -> usize { + if self.is_empty() { + return 0; + } + assert_eq!(self.a_bits.len(), self.b_bits.len()); + self.a_bits.len() + } + + /// Returns an iterator that iterates over the evaluations over {0,1}^`num_vars` + #[inline] + pub fn pack_all_mles(&self) -> Vec { + self.a_bits + .iter() + .flat_map(|bit| bit.iter()) + .chain(self.b_bits.iter().flat_map(|bit| bit.iter())) + .copied() + .collect() + } + + /// Convert to EF version + #[inline] + pub fn to_ef>(&self) -> RlweCiphertexts { + RlweCiphertexts:: { + a_bits: self + .a_bits + .iter() + .map(|bit| Rc::new(bit.to_ef::())) + .collect(), + b_bits: self + .b_bits + .iter() + .map(|bit| Rc::new(bit.to_ef::())) + .collect(), + } + } + + /// Evaluate at the same random point defined over F + #[inline] + pub fn evaluate(&self, point: &[F]) -> RlwesEval { + ( + self.a_bits.iter().map(|bit| bit.evaluate(point)).collect(), + self.b_bits.iter().map(|bit| bit.evaluate(point)).collect(), + ) + } + + /// Evaluate at the same random point defined over EF + #[inline] + pub fn evaluate_ext>(&self, point: &[EF]) -> RlwesEval { + ( + self.a_bits + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), + self.b_bits + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), + ) + } } impl RlweMultRgswInstance { @@ -144,225 +284,442 @@ impl RlweMultRgswInstance { #[inline] pub fn info(&self) -> RlweMultRgswInfo { RlweMultRgswInfo { - ntt_info: self.ntt_instance.info(), - decomposed_bits_info: self.decomposed_bits_info.clone(), + num_vars: self.num_vars, + ntt_info: self.ntt_info.clone(), + bits_info: self.bits_info.clone(), } } /// Construct the instance from reference #[allow(clippy::too_many_arguments)] #[inline] - pub fn from( - decomposed_bits_info: &DecomposedBitsInfo, + pub fn new( + num_vars: usize, + bits_info: &DecomposedBitsInfo, ntt_info: &NTTInstanceInfo, - randomness_ntt: &[F], - input_rlwe: &RlweCiphertext, - bits_rlwe: &RlweCiphertexts, - bits_rlwe_ntt: &RlweCiphertexts, - bits_rgsw_c_ntt: &RlweCiphertexts, - bits_rgsw_f_ntt: &RlweCiphertexts, - output_rlwe_ntt: &RlweCiphertext, - output_rlwe: &RlweCiphertext, + input_rlwe: RlweCiphertext, + bits_rlwe: RlweCiphertexts, + bits_rlwe_ntt: RlweCiphertexts, + bits_rgsw_c_ntt: RlweCiphertexts, + bits_rgsw_f_ntt: RlweCiphertexts, + output_rlwe_ntt: RlweCiphertext, + // output_rlwe: &RlweCiphertext, ) -> Self { - let num_ntt_instance = (decomposed_bits_info.bits_len << 1) + 2; - assert_eq!(randomness_ntt.len(), num_ntt_instance as usize); - - // randomize 2k + 1 ntt instances into a single one ntt instance - let mut ntt_instance = NTTInstance::from_info(ntt_info); - let mut r_iter = randomness_ntt.iter(); - - // k ntt instances for a_i =NTT equal= a_i' - for (coeffs, points) in izip!(&bits_rlwe.a_bits, &bits_rlwe_ntt.a_bits) { - let r = *r_iter.next().unwrap(); - ntt_instance.add_ntt(r, coeffs, points); - } - // k ntt instances for b_i =NTT equal= b_i' - for (coeffs, points) in izip!(&bits_rlwe.b_bits, &bits_rlwe_ntt.b_bits) { - let r = *r_iter.next().unwrap(); - ntt_instance.add_ntt(r, coeffs, points); - } - // 1 ntt instances for g =NTT equal= g' - let r = *r_iter.next().unwrap(); - ntt_instance.add_ntt(r, &output_rlwe.a, &output_rlwe_ntt.a); + // update num_ntt of ntt_info + let ntt_info = NTTInstanceInfo { + num_ntt: bits_info.bits_len << 1, + num_vars, + ntt_table: ntt_info.ntt_table.clone(), + }; - // 1 ntt instances for h =NTT equal= h' - let r = *r_iter.next().unwrap(); - ntt_instance.add_ntt(r, &output_rlwe.b, &output_rlwe_ntt.b); + assert_eq!(bits_rlwe.len(), bits_info.bits_len); + assert_eq!(bits_rlwe_ntt.len(), bits_info.bits_len); + assert_eq!(bits_rgsw_c_ntt.len(), bits_info.bits_len); + assert_eq!(bits_rgsw_f_ntt.len(), bits_info.bits_len); + // update num_instance of bits_info + let bits_info = DecomposedBitsInfo { + num_vars, + base: bits_info.base, + base_len: bits_info.base_len, + bits_len: bits_info.bits_len, + num_instances: 2, + }; RlweMultRgswInstance { - ntt_instance, - decomposed_bits_info: decomposed_bits_info.clone(), + num_vars, + bits_info: bits_info.clone(), + ntt_info: ntt_info.clone(), input_rlwe: input_rlwe.clone(), bits_rlwe: bits_rlwe.clone(), bits_rlwe_ntt: bits_rlwe_ntt.clone(), bits_rgsw_c_ntt: bits_rgsw_c_ntt.clone(), bits_rgsw_f_ntt: bits_rgsw_f_ntt.clone(), output_rlwe_ntt: output_rlwe_ntt.clone(), - output_rlwe: output_rlwe.clone(), + // output_rlwe: output_rlwe.clone(), } } -} -impl RlweMultRgswSubclaim { - /// verify the subclaim - /// - /// # Arguments - /// * `u`: random point chosen by verifier - /// * `randomness_ntt`: randomness used for combining a batch of ntt instances into a single one - /// * `ntt_coeffs`: coefficient form of the randomized ntt instance - /// * `ntt_points`: point-value form of the randomized ntt instance - /// * `input_rlwe`: rlwe = (a, b) storing the input ciphertext (a, b) where a and b are two polynomials represented by N coefficients. - /// * `bits_rlwe`: bits_rlwe = (a_bits, b_bits) where a_bits (b_bits) corresponds to the bit decomposition result of a (b) in the input rlwe ciphertext - /// * `bits_rlwe_ntt`: ntt form of the above bit decomposition result - /// * `bits_rgsw_c_ntt`: the ntt form of the first part (c) in the RGSW ciphertext - /// * `bits_rgsw_f_ntt`: the ntt form of the second part (f) in the RGSW ciphertext - /// * `output_rlwe_ntt`: store the output ciphertext (g', h') in the NTT-form - /// * `output_rlwe`: store the output ciphertext (g, h) in the coefficient-form - /// * `info`: contains the info for verifying ntt and bit decomposition - #[allow(clippy::too_many_arguments)] - pub fn verify_subclaim( - &self, - u: &[F], - randomness_ntt: &[F], - ntt_coeffs: &DenseMultilinearExtension, - ntt_points: &DenseMultilinearExtension, - input_rlwe: &RlweCiphertext, - bits_rlwe: &RlweCiphertexts, - bits_rlwe_ntt: &RlweCiphertexts, - bits_rgsw_c_ntt: &RlweCiphertexts, - bits_rgsw_f_ntt: &RlweCiphertexts, - output_rlwe_ntt: &RlweCiphertext, - output_rlwe: &RlweCiphertext, - info: &RlweMultRgswInfo, - ) -> bool { - let num_ntt_instance = (info.decomposed_bits_info.bits_len << 1) + 2; - assert_eq!(randomness_ntt.len(), num_ntt_instance as usize); - assert_eq!(u.len(), info.ntt_info.log_n); - assert_eq!(self.randomness_sumcheck.len(), 2); - - // check 1: check the consistency of the randomized ntt oracle and the original oracles - let mut coeffs_eval = F::zero(); - let mut points_eval = F::zero(); - let mut r_iter = randomness_ntt.iter(); - - for (coeffs, points) in izip!(&bits_rlwe.a_bits, &bits_rlwe_ntt.a_bits) { - let r = r_iter.next().unwrap(); - coeffs_eval += *r * coeffs.evaluate(u); - points_eval += *r * points.evaluate(u); + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 4 + 8 * self.bits_info.bits_len + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros. + pub fn pack_all_mles(&self) -> Vec { + let mut res = Vec::new(); + res.append(&mut self.input_rlwe.pack_all_mles()); + res.append(&mut self.output_rlwe_ntt.pack_all_mles()); + res.append(&mut self.bits_rlwe.pack_all_mles()); + res.append(&mut self.bits_rlwe_ntt.pack_all_mles()); + res.append(&mut self.bits_rgsw_c_ntt.pack_all_mles()); + res.append(&mut self.bits_rgsw_f_ntt.pack_all_mles()); + res + } + /// Generate the oracle to be committed that is composed of all the small oracles used in IOP. + /// The evaluations of this oracle is generated by the evaluations of all mles and the padded zeros. + /// The arrangement of this oracle should be consistent to its usage in verifying the subclaim. + pub fn generate_oracle(&self) -> DenseMultilinearExtension { + let num_vars_added = self.log_num_oracles(); + let num_vars = self.num_vars + num_vars_added; + let num_zeros_padded = ((1 << num_vars_added) - self.num_oracles()) * (1 << self.num_vars); + + // arrangement: all values||all decomposed bits||padded zeros + let mut evals = self.pack_all_mles(); + evals.append(&mut vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// Construct a EF version + pub fn to_ef>(&self) -> RlweMultRgswInstance { + RlweMultRgswInstance:: { + num_vars: self.num_vars, + bits_info: self.bits_info.to_ef::(), + ntt_info: self.ntt_info.to_ef::(), + input_rlwe: self.input_rlwe.to_ef::(), + bits_rlwe: self.bits_rlwe.to_ef::(), + bits_rlwe_ntt: self.bits_rlwe_ntt.to_ef::(), + bits_rgsw_c_ntt: self.bits_rgsw_c_ntt.to_ef::(), + bits_rgsw_f_ntt: self.bits_rgsw_f_ntt.to_ef::(), + output_rlwe_ntt: self.output_rlwe_ntt.to_ef::(), } - for (coeffs, points) in izip!(&bits_rlwe.b_bits, &bits_rlwe_ntt.b_bits) { - let r = r_iter.next().unwrap(); - coeffs_eval += *r * coeffs.evaluate(u); - points_eval += *r * points.evaluate(u); + } + + /// Evaluate at the same random point defined over F + #[inline] + pub fn evaluate(&self, point: &[F]) -> RlweMultRgswEval { + RlweMultRgswEval:: { + bits_len: self.bits_info.bits_len, + input_rlwe: self.input_rlwe.evaluate(point), + bits_rlwe: self.bits_rlwe.evaluate(point), + bits_rlwe_ntt: self.bits_rlwe_ntt.evaluate(point), + bits_rgsw_c_ntt: self.bits_rgsw_c_ntt.evaluate(point), + bits_rgsw_f_ntt: self.bits_rgsw_f_ntt.evaluate(point), + output_rlwe_ntt: self.output_rlwe_ntt.evaluate(point), } - let r = r_iter.next().unwrap(); - coeffs_eval += *r * output_rlwe.a.evaluate(u); - points_eval += *r * output_rlwe_ntt.a.evaluate(u); - let r = r_iter.next().unwrap(); - coeffs_eval += *r * output_rlwe.b.evaluate(u); - points_eval += *r * output_rlwe_ntt.b.evaluate(u); - - if coeffs_eval != ntt_coeffs.evaluate(u) || points_eval != ntt_points.evaluate(u) { - return false; + } + + /// Evaluate at the same random point defined over EF + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> RlweMultRgswEval { + RlweMultRgswEval:: { + bits_len: self.bits_info.bits_len, + input_rlwe: self.input_rlwe.evaluate_ext(point), + bits_rlwe: self.bits_rlwe.evaluate_ext(point), + bits_rlwe_ntt: self.bits_rlwe_ntt.evaluate_ext(point), + bits_rgsw_c_ntt: self.bits_rgsw_c_ntt.evaluate_ext(point), + bits_rgsw_f_ntt: self.bits_rgsw_f_ntt.evaluate_ext(point), + output_rlwe_ntt: self.output_rlwe_ntt.evaluate_ext(point), } + } - // TODO: For ease of implementation, we pass the resulting randomized ntt_instance but it can be omitted after combined with PCS. - // check 2: check the subclaim returned from the ntt iop - if !self - .ntt_subclaim - .verify_subcliam(ntt_points, ntt_coeffs, u, &info.ntt_info) - { - return false; + /// return the number of ntt instances contained + #[inline] + pub fn num_ntt_contained(&self) -> usize { + self.ntt_info.num_ntt + } + + /// Extract all NTT instances into a single random NTT instance to be proved + #[inline] + pub fn extract_ntt_instance(&self, randomness: &[F]) -> NTTInstance { + assert_eq!(randomness.len(), self.num_ntt_contained()); + let mut random_coeffs = >::from_evaluations_vec( + self.num_vars, + vec![F::zero(); 1 << self.num_vars], + ); + let mut random_points = >::from_evaluations_vec( + self.num_vars, + vec![F::zero(); 1 << self.num_vars], + ); + + self.update_ntt_instance(&mut random_coeffs, &mut random_points, randomness); + + NTTInstance:: { + num_vars: self.num_vars, + ntt_table: self.ntt_info.ntt_table.clone(), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), } + } - // check 3: check the subclaim returned from the bit decomposition iop - let d_bits = vec![&bits_rlwe.a_bits, &bits_rlwe.b_bits]; - let d_val = vec![input_rlwe.a.clone(), input_rlwe.b.clone()]; - if !self.bit_decomposition_subclaim.verify_subclaim( - &d_val, - &d_bits, - u, - &info.decomposed_bits_info, + /// Update the NTT instance to be proved + #[inline] + pub fn update_ntt_instance( + &self, + r_coeffs: &mut DenseMultilinearExtension, + r_points: &mut DenseMultilinearExtension, + randomness: &[F], + ) { + for (r, coeff, point) in izip!( + randomness, + self.bits_rlwe + .a_bits + .iter() + .chain(self.bits_rlwe.b_bits.iter()), + self.bits_rlwe_ntt + .a_bits + .iter() + .chain(self.bits_rlwe_ntt.b_bits.iter()) ) { - return false; + *r_coeffs += (*r, coeff.as_ref()); + *r_points += (*r, point.as_ref()); } + } + + /// Extract all NTT instances into a single random NTT defined over EF instance to be proved + #[inline] + pub fn extract_ntt_instance_to_ef>( + &self, + randomness: &[EF], + ) -> NTTInstance { + assert_eq!(randomness.len(), self.num_ntt_contained()); + let mut random_coeffs = >::from_evaluations_vec( + self.num_vars, + vec![EF::zero(); 1 << self.num_vars], + ); + let mut random_points = >::from_evaluations_vec( + self.num_vars, + vec![EF::zero(); 1 << self.num_vars], + ); - // 4. check 4: check the subclaim returned from the sumcheck protocol consisting of two sub-sumcheck protocols - let mut sum1_eval = F::zero(); - let mut sum2_eval = F::zero(); + self.update_ntt_instance_to_ef::(&mut random_coeffs, &mut random_points, randomness); - // The first part is to evaluate at a random point g' = \sum_{i = 0}^{k-1} a_i' \cdot c_i + b_i' \cdot f_i - // It is the reduction claim of prover asserting the sum \sum_{x} eq(u, x) (\sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) - g'(x)) = 0 - // where u is randomly sampled by the verifier. - for (a, b, c, f) in izip!( - &bits_rlwe_ntt.a_bits, - &bits_rlwe_ntt.b_bits, - &bits_rgsw_c_ntt.a_bits, - &bits_rgsw_f_ntt.a_bits - ) { - sum1_eval += (a.evaluate(&self.sumcheck_subclaim.point) - * c.evaluate(&self.sumcheck_subclaim.point)) - + (b.evaluate(&self.sumcheck_subclaim.point) - * f.evaluate(&self.sumcheck_subclaim.point)); + NTTInstance:: { + num_vars: self.num_vars, + ntt_table: Rc::new( + self.ntt_info + .ntt_table + .iter() + .map(|x| EF::from_base(*x)) + .collect(), + ), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), } + } - // The second part is to evaluate at a random point h' = \sum_{i = 0}^{k-1} a_i' \cdot c_i' + b_i' \cdot f_i' - // It is the reduction claim of prover asserting the sum \sum_{x} eq(u, x) (\sum_{i = 0}^{k-1} a_i'(x) \cdot c_i'(x) + b_i'(x) \cdot f_i'(x) - g'(x)) = 0 - // where u is randomly sampled by the verifier. - for (a, b, c, f) in izip!( - &bits_rlwe_ntt.a_bits, - &bits_rlwe_ntt.b_bits, - &bits_rgsw_c_ntt.b_bits, - &bits_rgsw_f_ntt.b_bits + /// Update NTT instance to be proved + #[inline] + pub fn update_ntt_instance_to_ef>( + &self, + r_coeffs: &mut DenseMultilinearExtension, + r_points: &mut DenseMultilinearExtension, + randomness: &[EF], + ) { + for (r, coeff, point) in izip!( + randomness, + self.bits_rlwe + .a_bits + .iter() + .chain(self.bits_rlwe.b_bits.iter()), + self.bits_rlwe_ntt + .a_bits + .iter() + .chain(self.bits_rlwe_ntt.b_bits.iter()) ) { - sum2_eval += (a.evaluate(&self.sumcheck_subclaim.point) - * c.evaluate(&self.sumcheck_subclaim.point)) - + (b.evaluate(&self.sumcheck_subclaim.point) - * f.evaluate(&self.sumcheck_subclaim.point)); + // multiplication between EF (r) and F (y) + add_assign_ef::(r_coeffs, r, coeff); + add_assign_ef::(r_points, r, point); } + } + + /// Extract DecomposedBits instance + #[inline] + pub fn extract_decomposed_bits(&self) -> DecomposedBits { + let mut res = DecomposedBits { + base: self.bits_info.base, + base_len: self.bits_info.base_len, + bits_len: self.bits_info.bits_len, + num_vars: self.num_vars, + d_val: Vec::with_capacity(2), + d_bits: Vec::with_capacity(2 * self.bits_info.bits_len), + }; + self.update_decomposed_bits(&mut res); + res + } + + /// Update DecomposedBits Instance + #[inline] + pub fn update_decomposed_bits(&self, decomposed_bits: &mut DecomposedBits) { + decomposed_bits.add_decomposed_bits_instance(&self.input_rlwe.a, &self.bits_rlwe.a_bits); + decomposed_bits.add_decomposed_bits_instance(&self.input_rlwe.b, &self.bits_rlwe.b_bits); + } +} + +impl RlweMultRgswEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 4 + 8 * self.bits_len + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Flatten all evals into a vector with the same arrangement of the committed polynomial + #[inline] + pub fn flatten(&self) -> Vec { + let mut res = Vec::with_capacity(self.num_oracles()); + res.extend([ + self.input_rlwe.0, + self.input_rlwe.1, + self.output_rlwe_ntt.0, + self.output_rlwe_ntt.1, + ]); + res.extend(self.bits_rlwe.0.iter()); + res.extend(self.bits_rlwe.1.iter()); + res.extend(self.bits_rlwe_ntt.0.iter()); + res.extend(self.bits_rlwe_ntt.1.iter()); + res.extend(self.bits_rgsw_c_ntt.0.iter()); + res.extend(self.bits_rgsw_c_ntt.1.iter()); + res.extend(self.bits_rgsw_f_ntt.0.iter()); + res.extend(self.bits_rgsw_f_ntt.1.iter()); + res + } + + /// Extract DecomposedBits Instance + #[inline] + pub fn extract_decomposed_bits(&self) -> DecomposedBitsEval { + let mut res = DecomposedBitsEval { + d_val: Vec::with_capacity(2), + d_bits: Vec::new(), + }; + self.update_decomposed_bits(&mut res); + res + } + + /// Update DecomposedBits with added bits in this instance + #[inline] + pub fn update_decomposed_bits(&self, bits_evals: &mut DecomposedBitsEval) { + bits_evals.d_val.push(self.input_rlwe.0); + bits_evals.d_val.push(self.input_rlwe.1); + bits_evals.d_bits.extend(&self.bits_rlwe.0); + bits_evals.d_bits.extend(&self.bits_rlwe.1); + } - self.sumcheck_subclaim.expected_evaluations - == eval_identity_function(u, &self.sumcheck_subclaim.point) - * (self.randomness_sumcheck[0] - * (sum1_eval - output_rlwe_ntt.a.evaluate(&self.sumcheck_subclaim.point)) - + self.randomness_sumcheck[1] - * (sum2_eval - output_rlwe_ntt.b.evaluate(&self.sumcheck_subclaim.point))) + /// Extract the NTT-Coefficient evaluation + #[inline] + pub fn update_ntt_instance_coeff(&self, r_coeff: &mut F, randomness: &[F]) { + assert_eq!( + randomness.len(), + self.bits_rlwe.0.len() + self.bits_rlwe.1.len() + ); + *r_coeff += self + .bits_rlwe + .0 + .iter() + .chain(self.bits_rlwe.1.iter()) + .zip(randomness) + .fold(F::zero(), |acc, (coeff, r)| acc + *r * *coeff); + } + + /// Extract the NTT-Coefficient evaluation + #[inline] + pub fn update_ntt_instance_point(&self, r_point: &mut F, randomness: &[F]) { + assert_eq!( + randomness.len(), + self.bits_rlwe_ntt.0.len() + self.bits_rlwe_ntt.1.len() + ); + *r_point += self + .bits_rlwe_ntt + .0 + .iter() + .chain(self.bits_rlwe_ntt.1.iter()) + .zip(randomness) + .fold(F::zero(), |acc, (coeff, r)| acc + *r * *coeff); } } -impl RlweMultRgswIOP { - /// prove the multiplication between RLWE ciphertext and RGSW ciphertext - pub fn prove(instance: &RlweMultRgswInstance, u: &[F]) -> RlweMultRgswProof { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, instance, u) +impl RlweMultRgswIOP { + /// sample coins before proving sumcheck protocol + pub fn sample_coins(trans: &mut Transcript, instance: &RlweMultRgswInstance) -> Vec { + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance.bits_info) + 2, + ) + } + + /// return the number of coins used in sumcheck protocol + pub fn num_coins(info: &RlweMultRgswInfo) -> usize { + >::num_coins(&info.bits_info) + 2 + } + + /// Prove RLWE * RGSW + pub fn prove(instance: &RlweMultRgswInstance) -> (SumcheckKit, NTTRecursiveProof) { + let mut trans = Transcript::::new(); + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&u)); + let randomness = Self::sample_coins(&mut trans, instance); + let randomness_ntt = >::sample_coins(&mut trans, instance.num_ntt_contained()); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let mut claimed_sum = F::zero(); + // add sumcheck products (without NTT) into poly + Self::prove_as_subprotocol(&randomness, &mut poly, instance, &eq_at_u); + + // add sumcheck products of NTT into poly + let ntt_instance = instance.extract_ntt_instance(&randomness_ntt); + >::prove_as_subprotocol( + F::one(), + &mut poly, + &mut claimed_sum, + &ntt_instance, + &u, + ); + + // prove all sumcheck protocol into a large random sumcheck + let (proof, state) = MLSumcheck::prove_as_subprotocol(&mut trans, &poly) + .expect("fail to prove the sumcheck protocol"); + + // prove F(u, v) in a recursive manner + let recursive_proof = + >::prove_recursive(&mut trans, &state.randomness, &ntt_instance.info(), &u); + + ( + SumcheckKit { + proof, + claimed_sum, + info: poly.info(), + u, + randomness: state.randomness, + }, + recursive_proof, + ) } - /// prove the multiplication between RLWE ciphertext and RGSW ciphertext - /// This function does the same thing as `prove`, but it uses a `Fiat-Shamir RNG` as the transcript/to generate the - /// verifier challenges. + /// Prove RLWE * RGSW with leaving the NTT part outside this interface + #[inline] pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, instance: &RlweMultRgswInstance, - u: &[F], - ) -> RlweMultRgswProof { - // construct the instance of bit decomposition - let mut decomposed_bits = DecomposedBits::from_info(&instance.decomposed_bits_info); - decomposed_bits.add_decomposed_bits_instance(&instance.bits_rlwe.a_bits); - decomposed_bits.add_decomposed_bits_instance(&instance.bits_rlwe.b_bits); - - let uniform = >::new(); - - let mut poly = >::new(instance.ntt_instance.log_n); - let identity_func_at_u = Rc::new(gen_identity_evaluations(u)); - - // randomly combine two sumcheck protocols - // TODO sample randomness via Fiat-Shamir RNG - let r_1 = uniform.sample(fs_rng); - let r_2 = uniform.sample(fs_rng); - // Sumcheck protocol for proving: g' = \sum_{i = 0}^{k-1} a_i' \cdot c_i + b_i' \cdot f_i - // When proving g'(x) = \sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) for x \in \{0, 1\}^\log n, - // prover claims the sum \sum_{x} eq(u, x) (\sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) - g'(x)) = 0 + eq_at_u: &Rc>, + ) { + let bits_instance = instance.extract_decomposed_bits(); + let bits_r_num = >::num_coins(&instance.bits_info); + let (r_bits, r) = randomness.split_at(bits_r_num); + BitDecomposition::prove_as_subprotocol(r_bits, poly, &bits_instance, eq_at_u); + + assert_eq!(r.len(), 2); + + // Integrate the second part of Sumcheck + // Sumcheck for proving g'(x) = \sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) for x \in \{0, 1\}^\log n. + // Prover claims the sum \sum_{x} eq(u, x) (\sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) - g'(x)) = 0 // where u is randomly sampled by the verifier. for (a, b, c, f) in izip!( &instance.bits_rlwe_ntt.a_bits, @@ -370,11 +727,15 @@ impl RlweMultRgswIOP { &instance.bits_rgsw_c_ntt.a_bits, &instance.bits_rgsw_f_ntt.a_bits ) { - let prod1 = [Rc::clone(a), Rc::clone(c), Rc::clone(&identity_func_at_u)]; - let prod2 = [Rc::clone(b), Rc::clone(f), Rc::clone(&identity_func_at_u)]; - poly.add_product(prod1, r_1); - poly.add_product(prod2, r_1); + let prod1 = [Rc::clone(a), Rc::clone(c), Rc::clone(eq_at_u)]; + let prod2 = [Rc::clone(b), Rc::clone(f), Rc::clone(eq_at_u)]; + poly.add_product(prod1, r[0]); + poly.add_product(prod2, r[0]); } + poly.add_product( + [Rc::clone(&instance.output_rlwe_ntt.a), Rc::clone(eq_at_u)], + -r[0], + ); // Sumcheck protocol for proving: h' = \sum_{i = 0}^{k-1} a_i' \cdot c_i' + b_i' \cdot f_i' for (a, b, c, f) in izip!( @@ -383,90 +744,378 @@ impl RlweMultRgswIOP { &instance.bits_rgsw_c_ntt.b_bits, &instance.bits_rgsw_f_ntt.b_bits ) { - let prod1 = [Rc::clone(a), Rc::clone(c), Rc::clone(&identity_func_at_u)]; - let prod2 = [Rc::clone(b), Rc::clone(f), Rc::clone(&identity_func_at_u)]; - poly.add_product(prod1, r_2); - poly.add_product(prod2, r_2); + let prod1 = [Rc::clone(a), Rc::clone(c), Rc::clone(eq_at_u)]; + let prod2 = [Rc::clone(b), Rc::clone(f), Rc::clone(eq_at_u)]; + poly.add_product(prod1, r[1]); + poly.add_product(prod2, r[1]); } - - poly.add_product( - [ - Rc::clone(&instance.output_rlwe_ntt.a), - Rc::clone(&identity_func_at_u), - ], - -r_1, - ); poly.add_product( - [ - Rc::clone(&instance.output_rlwe_ntt.b), - Rc::clone(&identity_func_at_u), - ], - -r_2, + [Rc::clone(&instance.output_rlwe_ntt.b), Rc::clone(eq_at_u)], + -r[1], ); - - RlweMultRgswProof { - bit_decomposition_proof: BitDecomposition::prove_as_subprotocol( - fs_rng, - &decomposed_bits, - u, - ), - ntt_proof: NTTIOP::prove_as_subprotocol(fs_rng, &instance.ntt_instance, u), - sumcheck_msg: MLSumcheck::prove_as_subprotocol(fs_rng, &poly) - .expect("sumcheck fail in rlwe * rgsw") - .0, - } } - /// verify the proof + /// Verify RLWE * RGSW + #[inline] pub fn verify( - proof: &RlweMultRgswProof, - randomness_ntt: &[F], - u: &[F], + wrapper: &mut ProofWrapper, + evals_at_r: &RlweMultRgswEval, + evals_at_u: &RlweMultRgswEval, info: &RlweMultRgswInfo, - ) -> RlweMultRgswSubclaim { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verify_as_subprotocol(&mut fs_rng, proof, randomness_ntt, u, info) + recursive_proof: &NTTRecursiveProof, + ) -> bool { + let mut trans = Transcript::new(); + + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + info.num_vars, + ); + + // randomness to combine sumcheck protocols + let randomness = trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(info), + ); + let randomness_ntt = trans.get_vec_challenge( + b"randomness used to obtain the virtual random ntt instance", + >::num_coins(&info.ntt_info), + ); + + let mut subclaim = MLSumcheck::verify_as_subprotocol( + &mut trans, + &wrapper.info, + wrapper.claimed_sum, + &wrapper.proof, + ) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&u, &subclaim.point); + + // check the sumcheck evaluation (without NTT) + if !Self::verify_as_subprotocol(&randomness, &mut subclaim, evals_at_r, info, eq_at_u_r) { + return false; + } + + let f_delegation = recursive_proof.delegation_claimed_sums[0]; + // one is to evaluate the random linear combination of evaluations at point r returned from sumcheck protocol + let mut ntt_coeff_evals_at_r = F::zero(); + evals_at_r.update_ntt_instance_coeff(&mut ntt_coeff_evals_at_r, &randomness_ntt); + // the other is to evaluate the random linear combination of evaluations at point u sampled before the sumcheck protocol + let mut ntt_point_evals_at_u = F::zero(); + evals_at_u.update_ntt_instance_point(&mut ntt_point_evals_at_u, &randomness_ntt); + + if !>::verify_as_subprotocol( + F::one(), + &mut subclaim, + &mut wrapper.claimed_sum, + ntt_coeff_evals_at_r, + ntt_point_evals_at_u, + f_delegation, + ) { + return false; + } + + if !(subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero()) { + return false; + } + >::verify_recursive(&mut trans, recursive_proof, &info.ntt_info, &u, &subclaim) } - /// verify the proof with provided RNG + /// Verify RLWE * RGSW with leaving NTT part outside of the interface + #[inline] pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &RlweMultRgswProof, - randomness_ntt: &[F], - u: &[F], + randomness: &[F], + subclaim: &mut SubClaim, + evals: &RlweMultRgswEval, info: &RlweMultRgswInfo, - ) -> RlweMultRgswSubclaim { - let uniform = >::new(); - // TODO sample randomness via Fiat-Shamir RNG - let r_1 = uniform.sample(fs_rng); - let r_2 = uniform.sample(fs_rng); - let poly_info = PolynomialInfo { - max_multiplicands: 3, - num_variables: info.ntt_info.log_n, - }; + eq_at_u_r: F, + ) -> bool { + // 1. check the bit decomposition part + let bits_eval = evals.extract_decomposed_bits(); + let bits_r_num = >::num_coins(&info.bits_info); + let (r_ntt, r) = randomness.split_at(bits_r_num); + let check_decomposed_bits = >::verify_as_subprotocol( + r_ntt, + subclaim, + &bits_eval, + &info.bits_info, + eq_at_u_r, + ); + if !check_decomposed_bits { + return false; + } - RlweMultRgswSubclaim { - bit_decomposition_subclaim: BitDecomposition::verifier_as_subprotocol( - fs_rng, - &proof.bit_decomposition_proof, - &info.decomposed_bits_info, - ), - ntt_subclaim: NTTIOP::verify_as_subprotocol( - fs_rng, - &proof.ntt_proof, - &info.ntt_info, - u, - ), - randomness_ntt: randomness_ntt.to_owned(), - randomness_sumcheck: vec![r_1, r_2], - sumcheck_subclaim: MLSumcheck::verify_as_subprotocol( - fs_rng, - &poly_info, - F::zero(), - &proof.sumcheck_msg, - ) - .expect("sumcheck protocol in rlwe mult rgsw failed"), + // 2. check the rest sumcheck protocols + // The first part is to evaluate at a random point g' = \sum_{i = 0}^{k-1} a_i' \cdot c_i + b_i' \cdot f_i + // It is the reduction claim of prover asserting the sum \sum_{x} eq(u, x) (\sum_{i = 0}^{k-1} a_i'(x) \cdot c_i(x) + b_i'(x) \cdot f_i(x) - g'(x)) = 0 + let mut sum1 = F::zero(); + let mut sum2 = F::zero(); + for (a, b, c, f) in izip!( + &evals.bits_rlwe_ntt.0, + &evals.bits_rlwe_ntt.1, + &evals.bits_rgsw_c_ntt.0, + &evals.bits_rgsw_f_ntt.0 + ) { + sum1 += *a * *c + *b * *f; } + + for (a, b, c, f) in izip!( + &evals.bits_rlwe_ntt.0, + &evals.bits_rlwe_ntt.1, + &evals.bits_rgsw_c_ntt.1, + &evals.bits_rgsw_f_ntt.1 + ) { + sum2 += *a * *c + *b * *f; + } + + subclaim.expected_evaluations -= eq_at_u_r + * (r[0] * (sum1 - evals.output_rlwe_ntt.0) + r[1] * (sum2 - evals.output_rlwe_ntt.1)); + true + } +} + +impl RlweMultRgswSnarks +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, +{ + /// Complied with PCS to get SNARKs + pub fn snarks(instance: &RlweMultRgswInstance, code_spec: &S) + where + H: Hash + Sync + Send, + C: LinearCode + Serialize + for<'de> Deserialize<'de>, + S: LinearCodeSpec + Clone, + { + let instance_info = instance.info(); + println!("Prove {instance_info}\n"); + // This is the actual polynomial to be committed for prover, which consists of all the required small polynomials in the IOP and padded zero polynomials. + let committed_poly = instance.generate_oracle(); + // 1. Use PCS to commit the above polynomial. + let start = Instant::now(); + let pp = + BrakedownPCS::::setup(committed_poly.num_vars, Some(code_spec.clone())); + let setup_time = start.elapsed().as_millis(); + + let start = Instant::now(); + let (comm, comm_state) = BrakedownPCS::::commit(&pp, &committed_poly); + let commit_time = start.elapsed().as_millis(); + + // 2. Prover generates the proof + let prover_start = Instant::now(); + let mut iop_proof_size = 0; + let mut prover_trans = Transcript::::new(); + // Convert the original instance into an instance defined over EF + let instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); + + // 2.1 Generate the random point to instantiate the sumcheck protocol + let prover_u = prover_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&prover_u)); + + // 2.2 Construct the polynomial and the claimed sum to be proved in the sumcheck protocol + let mut sumcheck_poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let mut claimed_sum = EF::zero(); + let randomness = RlweMultRgswIOP::sample_coins(&mut prover_trans, &instance_ef); + let randomness_ntt = + >::sample_coins(&mut prover_trans, instance_info.ntt_info.num_ntt); + RlweMultRgswIOP::::prove_as_subprotocol( + &randomness, + &mut sumcheck_poly, + &instance_ef, + &eq_at_u, + ); + + // 2.? Prover extract the random ntt instance from all ntt instances + let ntt_instance = instance.extract_ntt_instance_to_ef::(&randomness_ntt); + >::prove_as_subprotocol( + EF::one(), + &mut sumcheck_poly, + &mut claimed_sum, + &ntt_instance, + &prover_u, + ); + let poly_info = sumcheck_poly.info(); + let ntt_instance_info = ntt_instance.info(); + + // 2.3 Generate proof of sumcheck protocol + let (sumcheck_proof, sumcheck_state) = + >::prove_as_subprotocol(&mut prover_trans, &sumcheck_poly) + .expect("Proof generated in Addition In Zq"); + iop_proof_size += bincode::serialize(&sumcheck_proof).unwrap().len(); + + // 2.? [one more step] Prover recursive prove the evaluation of F(u, v) + let recursive_proof = >::prove_recursive( + &mut prover_trans, + &sumcheck_state.randomness, + &ntt_instance_info, + &prover_u, + ); + iop_proof_size += bincode::serialize(&recursive_proof).unwrap().len(); + let iop_prover_time = prover_start.elapsed().as_millis(); + + // 2.4 Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let start = Instant::now(); + let evals_at_r = instance.evaluate_ext(&sumcheck_state.randomness); + let evals_at_u = instance.evaluate_ext(&prover_u); + + // 2.5 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point_at_r = sumcheck_state.randomness.clone(); + let mut requested_point_at_u = prover_u.clone(); + let oracle_randomness = prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_oracles(), + ); + requested_point_at_r.extend(&oracle_randomness); + requested_point_at_u.extend(&oracle_randomness); + let oracle_eval_at_r = committed_poly.evaluate_ext(&requested_point_at_r); + let oracle_eval_at_u = committed_poly.evaluate_ext(&requested_point_at_u); + + // 2.6 Generate the evaluation proof of the requested point + let eval_proof_at_r = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &requested_point_at_r, + &mut prover_trans, + ); + let eval_proof_at_u = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &requested_point_at_u, + &mut prover_trans, + ); + let pcs_open_time = start.elapsed().as_millis(); + + // 3. Verifier checks the proof + let verifier_start = Instant::now(); + let mut verifier_trans = Transcript::::new(); + + // 3.1 Generate the random point to instantiate the sumcheck protocol + let verifier_u = verifier_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + // 3.2 Generate the randomness used to randomize all the sub-sumcheck protocols + let randomness = verifier_trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance_info), + ); + let randomness_ntt = verifier_trans.get_vec_challenge( + b"randomness used to obtain the virtual random ntt instance", + >::num_coins(&instance_info.ntt_info), + ); + + // 3.3 Check the proof of the sumcheck protocol + let mut subclaim = >::verify_as_subprotocol( + &mut verifier_trans, + &poly_info, + claimed_sum, + &sumcheck_proof, + ) + .expect("Verify the proof generated in Bit Decompositon"); + let eq_at_u_r = eval_identity_function(&verifier_u, &subclaim.point); + + // 3.4 Check the evaluation over a random point of the polynomial proved in the sumcheck protocol using evaluations over these small oracles used in IOP + let check_subclaim = RlweMultRgswIOP::::verify_as_subprotocol( + &randomness, + &mut subclaim, + &evals_at_r, + &instance_info, + eq_at_u_r, + ); + assert!(check_subclaim); + + // 3.? Check the NTT part + let f_delegation = recursive_proof.delegation_claimed_sums[0]; + // one is to evaluate the random linear combination of evaluations at point r returned from sumcheck protocol + let mut ntt_coeff_evals_at_r = EF::zero(); + evals_at_r.update_ntt_instance_coeff(&mut ntt_coeff_evals_at_r, &randomness_ntt); + // the other is to evaluate the random linear combination of evaluations at point u sampled before the sumcheck protocol + let mut ntt_point_evals_at_u = EF::zero(); + evals_at_u.update_ntt_instance_point(&mut ntt_point_evals_at_u, &randomness_ntt); + + // check the sumcheck part of NTT + let check_ntt_bare = >::verify_as_subprotocol( + EF::one(), + &mut subclaim, + &mut claimed_sum, + ntt_coeff_evals_at_r, + ntt_point_evals_at_u, + f_delegation, + ); + assert!(check_ntt_bare); + assert_eq!(subclaim.expected_evaluations, EF::zero()); + assert_eq!(claimed_sum, EF::zero()); + // check the recursive part of NTT + let check_recursive = >::verify_recursive( + &mut verifier_trans, + &recursive_proof, + &ntt_instance_info, + &verifier_u, + &subclaim, + ); + assert!(check_recursive); + + // 3.5 and also check the relation between these small oracles and the committed oracle + let start = Instant::now(); + let mut pcs_proof_size = 0; + let flatten_evals_at_r = evals_at_r.flatten(); + let flatten_evals_at_u = evals_at_u.flatten(); + let oracle_randomness = verifier_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + evals_at_r.log_num_oracles(), + ); + let check_oracle_at_r = + verify_oracle_relation(&flatten_evals_at_r, oracle_eval_at_r, &oracle_randomness); + let check_oracle_at_u = + verify_oracle_relation(&flatten_evals_at_u, oracle_eval_at_u, &oracle_randomness); + assert!(check_oracle_at_r && check_oracle_at_u); + let iop_verifier_time = verifier_start.elapsed().as_millis(); + + // 3.5 Check the evaluation of a random point over the committed oracle + let check_pcs_at_r = BrakedownPCS::::verify( + &pp, + &comm, + &requested_point_at_r, + oracle_eval_at_r, + &eval_proof_at_r, + &mut verifier_trans, + ); + let check_pcs_at_u = BrakedownPCS::::verify( + &pp, + &comm, + &requested_point_at_u, + oracle_eval_at_u, + &eval_proof_at_u, + &mut verifier_trans, + ); + assert!(check_pcs_at_r && check_pcs_at_u); + let pcs_verifier_time = start.elapsed().as_millis(); + pcs_proof_size += bincode::serialize(&eval_proof_at_r).unwrap().len() + + bincode::serialize(&eval_proof_at_u).unwrap().len() + + bincode::serialize(&flatten_evals_at_r).unwrap().len() + + bincode::serialize(&flatten_evals_at_u).unwrap().len(); + + // 4. print statistic + print_statistic( + iop_prover_time + pcs_open_time, + iop_verifier_time + pcs_verifier_time, + iop_proof_size + pcs_proof_size, + iop_prover_time, + iop_verifier_time, + iop_proof_size, + committed_poly.num_vars, + instance.num_oracles(), + instance.num_vars, + setup_time, + commit_time, + pcs_open_time, + pcs_verifier_time, + pcs_proof_size, + ); } } diff --git a/zkp/src/piop/round.rs b/zkp/src/piop/round.rs index cbae741d..17ad8a4d 100644 --- a/zkp/src/piop/round.rs +++ b/zkp/src/piop/round.rs @@ -16,40 +16,35 @@ //! 3. w(x)(1 - w(x)) = 0 where w indicates the option in the following constraint //! 4. w(x)(a(x)\cdot \lambda_1+b(x)\cdot \lambda_2)+(1-w(x))(a(x)-b(x)\cdot k-c(x))=0 //! where \lambda_1 and \lambda_2 are chosen by the verifier +use super::bit_decomposition::{BitDecomposition, DecomposedBitsEval}; +use super::{DecomposedBits, DecomposedBitsInfo}; use crate::sumcheck::verifier::SubClaim; -use crate::sumcheck::MLSumcheck; -use crate::sumcheck::Proof; -use crate::utils::eval_identity_function; +use crate::sumcheck::{MLSumcheck, ProofWrapper, SumcheckKit}; use crate::utils::gen_identity_evaluations; -use algebra::DecomposableField; +use crate::utils::{eval_identity_function, print_statistic, verify_oracle_relation}; +use algebra::{ + utils::Transcript, AbstractExtensionField, DecomposableField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, MultilinearExtension, +}; +use core::fmt; use itertools::izip; +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{LinearCode, LinearCodeSpec}, + utils::hash::Hash, + PolynomialCommitmentScheme, +}; +use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use std::rc::Rc; +use std::time::Instant; use std::vec; -use algebra::{ - DenseMultilinearExtension, Field, FieldUniformSampler, ListOfProductsOfPolynomials, - MultilinearExtension, PolynomialInfo, -}; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; -use rand_distr::Distribution; - -use super::bit_decomposition::{BitDecomposition, BitDecompositionProof, BitDecompositionSubClaim}; -use super::{DecomposedBits, DecomposedBitsInfo}; - /// Round IOP pub struct RoundIOP(PhantomData); +/// SNARKs for Round compiled with PCS +pub struct RoundSnarks>(PhantomData, PhantomData); -/// proof generated by prover -pub struct RoundIOPProof { - /// range check proof for output b - pub bit_decomp_proof_output: BitDecompositionProof, - /// range check proof for offset c - 1 - pub bit_decomp_proof_offset: BitDecompositionProof, - /// sumcheck msg - pub sumcheck_msg: Proof, -} /// Round Instance used as prover keys pub struct RoundInstance { /// number of variables @@ -63,21 +58,46 @@ pub struct RoundInstance { /// output denoted by b \in F_q pub output: Rc>, /// decomposed bits of output used for range check - pub output_bits: DecomposedBits, + pub output_bits: Vec>>, + /// decomposition info for outputs + pub output_bits_info: DecomposedBitsInfo, /// offset denoted by c = a - b * k \in [1, k] such that c - 1 \in [0, k) pub offset: Rc>, /// offset_aux_bits contains two instances of bit decomposition /// decomposed bits of c - 1 \in [0, 2^k_bit_len) used for range check /// decomposed bits of c - 1 + delta \in [0, 2^k_bit_len) used for range check - pub offset_aux_bits: DecomposedBits, - + pub offset_aux_bits: Vec>>, + /// decomposition info for offset + pub offset_bits_info: DecomposedBitsInfo, /// option denoted by w \in {0, 1} pub option: Rc>, } +/// Evaluation at a random point +pub struct RoundInstanceEval { + /// input denoted by a \in F_Q + pub input: F, + /// output denoted by b \in F_q + pub output: F, + /// decomposed bits of output used for range check + pub output_bits: Vec, + /// offset denoted by c = a - b * k \in [1, k] such that c - 1 \in [0, k) + pub offset: F, + /// offset_aux = offset - 1 and offset - 1 - delta + pub offset_aux: Vec, + /// offset_aux_bits contains two instances of bit decomposition + /// decomposed bits of c - 1 \in [0, 2^k_bit_len) used for range check + /// decomposed bits of c - 1 + delta \in [0, 2^k_bit_len) used for range check + pub offset_aux_bits: Vec, + /// option denoted by w \in {0, 1} + pub option: F, +} + /// Information about Round Instance used as verifier keys pub struct RoundInstanceInfo { + /// number of variables + pub num_vars: usize, /// k = Q - 1 / q is the modulus of the output pub k: F, /// delta = 2^k_bits_len - k @@ -88,16 +108,18 @@ pub struct RoundInstanceInfo { pub offset_bits_info: DecomposedBitsInfo, } -/// subclaim returned to verifier -pub struct RoundIOPSubclaim { - /// subclaim returned from the range check for output b \in [q] - pub bit_decomp_output_subclaim: BitDecompositionSubClaim, - /// subclaim returned from the range check for offset c - 1 \in [k] - pub bit_decomp_offset_subclaim: BitDecompositionSubClaim, - /// subclaim returned from the sumcheck protocol - pub sumcheck_subclaim: SubClaim, - /// randomness used in the sumcheck - pub randomness: (F, F), +impl fmt::Display for RoundInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "An instance of Round from Q to q: #vars = {}", + self.num_vars, + )?; + write!(f, "- containing ")?; + self.output_bits_info.fmt(f)?; + write!(f, "\n- containing ")?; + self.offset_bits_info.fmt(f) + } } impl RoundInstance { @@ -105,12 +127,159 @@ impl RoundInstance { #[inline] pub fn info(&self) -> RoundInstanceInfo { RoundInstanceInfo { + num_vars: self.num_vars, k: self.k, delta: self.delta, - output_bits_info: self.output_bits.info(), - offset_bits_info: self.offset_aux_bits.info(), + output_bits_info: self.output_bits_info.clone(), + offset_bits_info: self.offset_bits_info.clone(), } } + + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 4 + self.output_bits.len() + self.offset_aux_bits.len() + } + + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Pack all the involved small polynomials into a single vector of evaluations without padding + pub fn pack_all_mles(&self) -> Vec { + self.input + .iter() + .chain(self.output.iter()) + .chain(self.offset.iter()) + .chain(self.option.iter()) + .chain(self.output_bits.iter().flat_map(|bit| bit.iter())) + .chain(self.offset_aux_bits.iter().flat_map(|bit| bit.iter())) + .copied() + .collect::>() + } + + /// Generate the oracle to be committed that is composed of all the small oracles used in IOP. + /// The evaluations of this oracle is generated by the evaluations of all mles and the padded zeros. + /// The arrangement of this oracle should be consistent to its usage in verifying the subclaim. + pub fn generate_oracle(&self) -> DenseMultilinearExtension { + let num_vars_added = self.log_num_oracles(); + let num_vars = self.num_vars + num_vars_added; + let num_zeros_padded = ((1 << num_vars_added) - self.num_oracles()) * (1 << self.num_vars); + + // arrangement: all values||all decomposed bits||padded zeros + let mut evals = self.pack_all_mles(); + evals.append(&mut vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// Construct a EF version + pub fn to_ef>(&self) -> RoundInstance { + RoundInstance:: { + num_vars: self.num_vars, + k: EF::from_base(self.k), + delta: EF::from_base(self.delta), + input: Rc::new(self.input.to_ef::()), + output: Rc::new(self.output.to_ef::()), + offset: Rc::new(self.offset.to_ef::()), + option: Rc::new(self.option.to_ef::()), + output_bits: self + .output_bits + .iter() + .map(|bit| Rc::new(bit.to_ef())) + .collect(), + offset_aux_bits: self + .offset_aux_bits + .iter() + .map(|bit| Rc::new(bit.to_ef())) + .collect(), + output_bits_info: self.output_bits_info.to_ef::(), + offset_bits_info: self.offset_bits_info.to_ef::(), + } + } + + /// Evaluate at a random point defined over Field + #[inline] + pub fn evaluate(&self, point: &[F]) -> RoundInstanceEval { + let offset = self.offset.evaluate(point); + RoundInstanceEval:: { + input: self.input.evaluate(point), + output: self.output.evaluate(point), + offset, + option: self.option.evaluate(point), + output_bits: self + .output_bits + .iter() + .map(|bit| bit.evaluate(point)) + .collect(), + offset_aux: vec![offset - F::one(), offset - F::one() + self.delta], + offset_aux_bits: self + .offset_aux_bits + .iter() + .map(|bit| bit.evaluate(point)) + .collect(), + } + } + + /// Evaluate at a random point defined over Extension Field + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> RoundInstanceEval { + let offset = self.offset.evaluate_ext(point); + RoundInstanceEval:: { + input: self.input.evaluate_ext(point), + output: self.output.evaluate_ext(point), + offset, + option: self.option.evaluate_ext(point), + output_bits: self + .output_bits + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), + offset_aux: vec![offset - F::one(), offset - F::one() + self.delta], + offset_aux_bits: self + .offset_aux_bits + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), + } + } + + /// Extract DecomposedBits instance + #[inline] + pub fn extract_decomposed_bits(&self) -> (DecomposedBits, DecomposedBits) { + // c - 1 + let c_minus_one = DenseMultilinearExtension::from_evaluations_vec( + self.num_vars, + self.offset.iter().map(|x| *x - F::one()).collect(), + ); + // c - 1 + delta + let c_minus_one_delta = DenseMultilinearExtension::from_evaluations_vec( + self.num_vars, + c_minus_one.iter().map(|x| *x + self.delta).collect(), + ); + ( + DecomposedBits { + base: self.output_bits_info.base, + base_len: self.output_bits_info.base_len, + bits_len: self.output_bits_info.bits_len, + num_vars: self.num_vars, + d_val: vec![Rc::clone(&self.output)], + d_bits: self.output_bits.to_owned(), + }, + DecomposedBits { + base: self.offset_bits_info.base, + base_len: self.offset_bits_info.base_len, + bits_len: self.offset_bits_info.bits_len, + num_vars: self.num_vars, + d_val: vec![Rc::new(c_minus_one), Rc::new(c_minus_one_delta)], + d_bits: self.offset_aux_bits.to_owned(), + }, + ) + } } impl RoundInstance { @@ -131,8 +300,8 @@ impl RoundInstance { assert_eq!(1, output_bits_info.num_instances); assert_eq!(2, offset_bits_info.num_instances); - let mut output_bits = DecomposedBits::from_info(output_bits_info); - output_bits.add_value_instance(&output); + let output_bits = + output.get_decomposed_mles(output_bits_info.base_len, output_bits_info.bits_len); // set w = 1 iff a = 0 & b = 0 let option = Rc::new(DenseMultilinearExtension::::from_evaluations_vec( @@ -160,21 +329,21 @@ impl RoundInstance { .collect(), )); - let mut offset_aux_bits = DecomposedBits::from_info(offset_bits_info); // c - 1 let c_minus_one = DenseMultilinearExtension::from_evaluations_vec( num_vars, offset.iter().map(|x| *x - F::one()).collect(), ); + let mut offset_aux_bits = + c_minus_one.get_decomposed_mles(offset_bits_info.base_len, offset_bits_info.bits_len); // c - 1 + delta let c_minus_one_delta = DenseMultilinearExtension::from_evaluations_vec( num_vars, c_minus_one.iter().map(|x| *x + delta).collect(), ); - // decompose c - 1 - offset_aux_bits.add_value_instance(&c_minus_one); - // decompose c - 1 + delta - offset_aux_bits.add_value_instance(&c_minus_one_delta); + let mut c_minus_one_delta_bits = c_minus_one_delta + .get_decomposed_mles(offset_bits_info.base_len, offset_bits_info.bits_len); + offset_aux_bits.append(&mut c_minus_one_delta_bits); Self { num_vars, @@ -186,109 +355,130 @@ impl RoundInstance { offset, offset_aux_bits, option, + offset_bits_info: offset_bits_info.clone(), + output_bits_info: output_bits_info.clone(), } } } -impl RoundIOPSubclaim { - /// verify the subclaim - #[allow(clippy::too_many_arguments)] - pub fn verify_subclaim( - &self, - u: &[F], - (lambda_1, lambda_2): (F, F), - input: &Rc>, - output: &Rc>, - output_bits: &Vec>>, - offset: &Rc>, - offset_aux_bits_1: &Vec>>, - offset_aux_bits_2: &Vec>>, - option: &Rc>, - info: &RoundInstanceInfo, - ) -> bool { - // check 1: check the subclaim returned from the range check for output b \in [q] - let d_bits = vec![output_bits]; - let d_val = vec![output.clone()]; - if !self.bit_decomp_output_subclaim.verify_subclaim( - &d_val, - &d_bits, - u, - &info.output_bits_info, - ) { - return false; - } +impl RoundInstanceEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 4 + self.output_bits.len() + self.offset_aux_bits.len() + } - // check 2: check the subclaim returned from the range check for offset c - 1 \in [k] - let d_bits = vec![offset_aux_bits_1, offset_aux_bits_2]; - let c_minus_one = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - offset.num_vars, - offset.iter().map(|x| *x - F::one()).collect(), - )); - let c_minus_one_delta = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - offset.num_vars, - c_minus_one.iter().map(|x| *x + info.delta).collect(), - )); - let d_val = vec![c_minus_one, c_minus_one_delta]; - if !self.bit_decomp_offset_subclaim.verify_subclaim( - &d_val, - &d_bits, - u, - &info.offset_bits_info, - ) { - return false; - } + /// Return the log of the number of small polynomials used in IOP + #[inline] + pub fn log_num_oracles(&self) -> usize { + self.num_oracles().next_power_of_two().ilog2() as usize + } + + /// Flatten all evals into a vector with the same arrangement of the committed polynomial + #[inline] + pub fn flatten(&self) -> Vec { + let mut res = Vec::with_capacity(self.num_oracles()); + res.push(self.input); + res.push(self.output); + res.push(self.offset); + res.push(self.option); + res.extend(self.output_bits.iter()); + res.extend(self.offset_aux_bits.iter()); + res + } - // check 3: check the subclaim returned from the sumcheck protocol - let (r_1, r_2) = self.randomness; - let eq_val = eval_identity_function(u, &self.sumcheck_subclaim.point); - let option_eval = option.evaluate(&self.sumcheck_subclaim.point); - let input_eval = input.evaluate(&self.sumcheck_subclaim.point); - let output_eval = output.evaluate(&self.sumcheck_subclaim.point); - let offset_eval = offset.evaluate(&self.sumcheck_subclaim.point); - - self.sumcheck_subclaim.expected_evaluations - == r_1 * eq_val * option_eval * (F::one() - option_eval) - + r_2 - * eq_val - * (option_eval * (input_eval * lambda_1 + output_eval * lambda_2) - + (F::one() - option_eval) - * (input_eval - output_eval * info.k - offset_eval)) + /// Extract DecomposedBitsEval instance + #[inline] + pub fn extract_decomposed_bits(&self) -> (DecomposedBitsEval, DecomposedBitsEval) { + ( + DecomposedBitsEval { + d_val: vec![self.output], + d_bits: self.output_bits.to_owned(), + }, + DecomposedBitsEval { + d_val: self.offset_aux.to_owned(), + d_bits: self.offset_aux_bits.to_owned(), + }, + ) } } -impl RoundIOP { - /// Prove round operation - pub fn prove( - instance: &RoundInstance, - u: &[F], - (lambda_1, lambda_2): (F, F), - ) -> RoundIOPProof { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, instance, u, (lambda_1, lambda_2)) +impl RoundIOP { + /// sample coins before proving sumcheck protocol + pub fn sample_coins(trans: &mut Transcript, instance: &RoundInstance) -> Vec { + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance.output_bits_info) + + >::num_coins(&instance.offset_bits_info) + + 4, + ) } - /// Prove round operation - pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, - instance: &RoundInstance, - u: &[F], - (lambda_1, lambda_2): (F, F), - ) -> RoundIOPProof { - let uniform = >::new(); + /// return the number of coins used in this IOP + pub fn num_coins(info: &RoundInstanceInfo) -> usize { + >::num_coins(&info.output_bits_info) + + >::num_coins(&info.offset_bits_info) + + 4 + } + + /// Prove round + pub fn prove(instance: &RoundInstance) -> SumcheckKit { + let mut trans = Transcript::::new(); + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&u)); - let mut poly = >::new(instance.num_vars); - let identity_func_at_u = Rc::new(gen_identity_evaluations(u)); + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let randomness = Self::sample_coins(&mut trans, instance); + Self::prove_as_subprotocol(&randomness, &mut poly, instance, &eq_at_u); - // randomly combine two sumcheck protocols - // TODO sample randomness via Fiat-Shamir RNG - let r_1 = uniform.sample(fs_rng); - let r_2 = uniform.sample(fs_rng); + let (proof, state) = MLSumcheck::prove_as_subprotocol(&mut trans, &poly) + .expect("fail to prove the sumcheck protocol"); + // (proof, state, poly.info()) + SumcheckKit { + proof, + info: poly.info(), + claimed_sum: F::zero(), + randomness: state.randomness, + u, + } + } - // sumcheck1 for \sum_{x} eq(u, x) * w(x) * (1-w(x)) = 0, i.e. w(x)\in\{0,1\}^l with random coefficient r_1 + /// Prove round + pub fn prove_as_subprotocol( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &RoundInstance, + eq_at_u: &Rc>, + ) { + let (output_bits_instance, offset_bits_instance) = instance.extract_decomposed_bits(); + let output_bits_r_num = >::num_coins(&instance.output_bits_info); + let offset_bits_r_num = >::num_coins(&instance.offset_bits_info); + assert_eq!(randomness.len(), output_bits_r_num + offset_bits_r_num + 4); + // 1. add products used to prove decomposition + BitDecomposition::prove_as_subprotocol( + &randomness[..output_bits_r_num], + poly, + &output_bits_instance, + eq_at_u, + ); + BitDecomposition::prove_as_subprotocol( + &randomness[output_bits_r_num..output_bits_r_num + offset_bits_r_num], + poly, + &offset_bits_instance, + eq_at_u, + ); + + let lambda_1 = randomness[randomness.len() - 4]; + let lambda_2 = randomness[randomness.len() - 3]; + let r_1 = randomness[randomness.len() - 2]; + let r_2 = randomness[randomness.len() - 1]; + // 2. add sumcheck1 for \sum_{x} eq(u, x) * w(x) * (1-w(x)) = 0, i.e. w(x)\in\{0,1\}^l with random coefficient r_1 poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), + Rc::clone(eq_at_u), Rc::clone(&instance.option), Rc::clone(&instance.option), ], @@ -300,14 +490,14 @@ impl RoundIOP { r_1, ); - // sumcheck2 for \sum_{x} eq(u, x) * [w(x) * (a(x) * \lambda_1 + b(x) * \lambda_2)+(1 - w(x)) * (a(x) - b(x) * k - c(x))]=0 + // 3. add sumcheck2 for \sum_{x} eq(u, x) * [w(x) * (a(x) * \lambda_1 + b(x) * \lambda_2)+(1 - w(x)) * (a(x) - b(x) * k - c(x))]=0 // with random coefficient r_2 where \lambda_1 and \lambda_2 are chosen by the verifier // The following steps add five products composing the function in the above sumcheck protocol // product: eq(u, x) * w(x) * (a(x) * \lambda_1) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), + Rc::clone(eq_at_u), Rc::clone(&instance.option), Rc::clone(&instance.input), ], @@ -321,7 +511,7 @@ impl RoundIOP { // product: eq(u, x) * w(x) * (b(x) * \lambda_2) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), + Rc::clone(eq_at_u), Rc::clone(&instance.option), Rc::clone(&instance.output), ], @@ -335,7 +525,7 @@ impl RoundIOP { // product: eq(u, x) * (1 - w(x)) * a(x) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), + Rc::clone(eq_at_u), Rc::clone(&instance.option), Rc::clone(&instance.input), ], @@ -349,7 +539,7 @@ impl RoundIOP { // product: eq(u, x) * (1 - w(x)) * (-k * b(x)) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), + Rc::clone(eq_at_u), Rc::clone(&instance.option), Rc::clone(&instance.output), ], @@ -363,7 +553,7 @@ impl RoundIOP { // product: eq(u, x) * (1 - w(x)) * (-c(x)) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), + Rc::clone(eq_at_u), Rc::clone(&instance.option), Rc::clone(&instance.offset), ], @@ -374,68 +564,239 @@ impl RoundIOP { ], r_2, ); + } + + /// Verify addition in Zq + pub fn verify( + wrapper: &ProofWrapper, + evals: &RoundInstanceEval, + info: &RoundInstanceInfo, + ) -> bool { + let mut trans = Transcript::new(); + + let u = trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + info.num_vars, + ); + + // randomness to combine sumcheck protocols + let randomness = trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(info), + ); - RoundIOPProof { - bit_decomp_proof_output: BitDecomposition::prove_as_subprotocol( - fs_rng, - &instance.output_bits, - u, - ), - bit_decomp_proof_offset: BitDecomposition::prove_as_subprotocol( - fs_rng, - &instance.offset_aux_bits, - u, - ), - sumcheck_msg: MLSumcheck::prove_as_subprotocol(fs_rng, &poly) - .expect("sumcheck for round operation failed") - .0, + let mut subclaim = + MLSumcheck::verify_as_subprotocol(&mut trans, &wrapper.info, F::zero(), &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&u, &subclaim.point); + + if !Self::verify_as_subprotocol(&randomness, &mut subclaim, evals, info, eq_at_u_r) { + return false; } - } - /// verify - pub fn verify(proof: &RoundIOPProof, info: &RoundInstanceInfo) -> RoundIOPSubclaim { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verify_as_subprotocol(&mut fs_rng, proof, info) + subclaim.expected_evaluations == F::zero() } - /// verify with given rng + /// Verify round pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &RoundIOPProof, + randomness: &[F], + subclaim: &mut SubClaim, + evals: &RoundInstanceEval, info: &RoundInstanceInfo, - ) -> RoundIOPSubclaim { - let num_vars = info.output_bits_info.num_vars; - assert_eq!(num_vars, info.offset_bits_info.num_vars); - let uniform = >::new(); - // randomly combine two sumcheck protocols - // TODO sample randomness via Fiat-Shamir RNG - let r_1 = uniform.sample(fs_rng); - let r_2 = uniform.sample(fs_rng); - - let poly_info = PolynomialInfo { - max_multiplicands: 3, - num_variables: num_vars, - }; - RoundIOPSubclaim { - bit_decomp_output_subclaim: BitDecomposition::verifier_as_subprotocol( - fs_rng, - &proof.bit_decomp_proof_output, - &info.output_bits_info, - ), - bit_decomp_offset_subclaim: BitDecomposition::verifier_as_subprotocol( - fs_rng, - &proof.bit_decomp_proof_offset, - &info.offset_bits_info, - ), - sumcheck_subclaim: MLSumcheck::verify_as_subprotocol( - fs_rng, - &poly_info, - F::zero(), - &proof.sumcheck_msg, - ) - .expect("sumcheck protocol for round operation failed"), - randomness: (r_1, r_2), + eq_at_u_r: F, + ) -> bool { + let (output_bits_evals, offset_bits_evals) = evals.extract_decomposed_bits(); + let output_bits_r_num = >::num_coins(&info.output_bits_info); + let offset_bits_r_num = >::num_coins(&info.offset_bits_info); + assert_eq!(randomness.len(), output_bits_r_num + offset_bits_r_num + 4); + let check_output_bits = >::verify_as_subprotocol( + &randomness[..output_bits_r_num], + subclaim, + &output_bits_evals, + &info.output_bits_info, + eq_at_u_r, + ); + let check_offset_bits = >::verify_as_subprotocol( + &randomness[output_bits_r_num..output_bits_r_num + offset_bits_r_num], + subclaim, + &offset_bits_evals, + &info.offset_bits_info, + eq_at_u_r, + ); + if !(check_output_bits && check_offset_bits) { + return false; } + let lambda_1 = randomness[randomness.len() - 4]; + let lambda_2 = randomness[randomness.len() - 3]; + let r_1 = randomness[randomness.len() - 2]; + let r_2 = randomness[randomness.len() - 1]; + + // check 2: check the subclaim returned from the sumcheck protocol + subclaim.expected_evaluations -= r_1 * eq_at_u_r * evals.option * (F::one() - evals.option); + subclaim.expected_evaluations -= r_2 + * eq_at_u_r + * (evals.option * (evals.input * lambda_1 + evals.output * lambda_2) + + (F::one() - evals.option) * (evals.input - evals.output * info.k - evals.offset)); + true + } +} + +impl RoundSnarks +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, +{ + /// Complied with PCS to get SNARKs + pub fn snarks(instance: &RoundInstance, code_spec: &S) + where + H: Hash + Sync + Send, + C: LinearCode + Serialize + for<'de> Deserialize<'de>, + S: LinearCodeSpec + Clone, + { + let instance_info = instance.info(); + println!("Prove {instance_info}\n"); + // This is the actual polynomial to be committed for prover, which consists of all the required small polynomials in the IOP and padded zero polynomials. + let committed_poly = instance.generate_oracle(); + // 1. Use PCS to commit the above polynomial. + let start = Instant::now(); + let pp = + BrakedownPCS::::setup(committed_poly.num_vars, Some(code_spec.clone())); + let setup_time = start.elapsed().as_millis(); + + let start = Instant::now(); + let (comm, comm_state) = BrakedownPCS::::commit(&pp, &committed_poly); + let commit_time = start.elapsed().as_millis(); + + // 2. Prover generates the proof + let prover_start = Instant::now(); + let mut iop_proof_size = 0; + let mut prover_trans = Transcript::::new(); + // Convert the original instance into an instance defined over EF + let instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); + + // 2.1 Generate the random point to instantiate the sumcheck protocol + let prover_u = prover_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&prover_u)); + + // 2.2 Construct the polynomial and the claimed sum to be proved in the sumcheck protocol + let mut sumcheck_poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let claimed_sum = EF::zero(); + let randomness = RoundIOP::sample_coins(&mut prover_trans, &instance_ef); + RoundIOP::prove_as_subprotocol(&randomness, &mut sumcheck_poly, &instance_ef, &eq_at_u); + + let poly_info = sumcheck_poly.info(); + + // 2.3 Generate proof of sumcheck protocol + let (sumcheck_proof, sumcheck_state) = + >::prove_as_subprotocol(&mut prover_trans, &sumcheck_poly) + .expect("Proof generated in Addition In Zq"); + iop_proof_size += bincode::serialize(&sumcheck_proof).unwrap().len(); + let iop_prover_time = prover_start.elapsed().as_millis(); + + // 2.4 Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let start = Instant::now(); + let evals = instance.evaluate_ext(&sumcheck_state.randomness); + + // 2.5 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point = sumcheck_state.randomness.clone(); + requested_point.extend(&prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_oracles(), + )); + let oracle_eval = committed_poly.evaluate_ext(&requested_point); + + // 2.6 Generate the evaluation proof of the requested point + let eval_proof = BrakedownPCS::::open( + &pp, + &comm, + &comm_state, + &requested_point, + &mut prover_trans, + ); + let pcs_open_time = start.elapsed().as_millis(); + + // 3. Verifier checks the proof + let verifier_start = Instant::now(); + let mut verifier_trans = Transcript::::new(); + + // 3.1 Generate the random point to instantiate the sumcheck protocol + let verifier_u = verifier_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + // 3.2 Generate the randomness used to randomize all the sub-sumcheck protocols + let randomness = verifier_trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance_info), + ); + + // 3.3 Check the proof of the sumcheck protocol + let mut subclaim = >::verify_as_subprotocol( + &mut verifier_trans, + &poly_info, + claimed_sum, + &sumcheck_proof, + ) + .expect("Verify the proof generated in Bit Decompositon"); + let eq_at_u_r = eval_identity_function(&verifier_u, &subclaim.point); + + // 3.4 Check the evaluation over a random point of the polynomial proved in the sumcheck protocol using evaluations over these small oracles used in IOP + let check_subcliam = RoundIOP::::verify_as_subprotocol( + &randomness, + &mut subclaim, + &evals, + &instance_info, + eq_at_u_r, + ); + assert!(check_subcliam && subclaim.expected_evaluations == EF::zero()); + let iop_verifier_time = verifier_start.elapsed().as_millis(); + + // 3.5 and also check the relation between these small oracles and the committed oracle + let start = Instant::now(); + let mut pcs_proof_size = 0; + let flatten_evals = evals.flatten(); + let oracle_randomness = verifier_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + evals.log_num_oracles(), + ); + let check_oracle = verify_oracle_relation(&flatten_evals, oracle_eval, &oracle_randomness); + assert!(check_oracle); + + // 3.5 Check the evaluation of a random point over the committed oracle + let check_pcs = BrakedownPCS::::verify( + &pp, + &comm, + &requested_point, + oracle_eval, + &eval_proof, + &mut verifier_trans, + ); + assert!(check_pcs); + let pcs_verifier_time = start.elapsed().as_millis(); + pcs_proof_size += bincode::serialize(&eval_proof).unwrap().len() + + bincode::serialize(&flatten_evals).unwrap().len(); + + // 4. print statistic + print_statistic( + iop_prover_time + pcs_open_time, + iop_verifier_time + pcs_verifier_time, + iop_proof_size + pcs_proof_size, + iop_prover_time, + iop_verifier_time, + iop_proof_size, + committed_poly.num_vars, + instance.num_oracles(), + instance.num_vars, + setup_time, + commit_time, + pcs_open_time, + pcs_verifier_time, + pcs_proof_size, + ); } } diff --git a/zkp/src/piop/zq_to_rq.rs b/zkp/src/piop/zq_to_rq.rs index 98818545..13bd32f0 100644 --- a/zkp/src/piop/zq_to_rq.rs +++ b/zkp/src/piop/zq_to_rq.rs @@ -150,7 +150,7 @@ impl TransformZqtoRQInstance { }); // Rangecheck is defaultly designed for batching version, so we should construct a vector of one element r_bits - let r_bits = vec![r.get_decomposed_mles(base_len, bits_len)]; + let r_bits = r.get_decomposed_mles(base_len, bits_len); Self { q, @@ -167,7 +167,8 @@ impl TransformZqtoRQInstance { base_len, bits_len, num_vars, - instances: r_bits, + d_val: vec![Rc::clone(r)], + d_bits: r_bits, }, } } @@ -317,7 +318,7 @@ impl TransformZqtoRQ { //TODO sample randomness via Fiat-Shamir RNG // 1. rangecheck - let rangecheck_subclaim = BitDecomposition::verifier_as_subprotocol( + let rangecheck_subclaim = BitDecomposition::verify_as_subprotocol( fs_rng, &proof.rangecheck_msg, decomposed_bits_info, diff --git a/zkp/src/sumcheck/mod.rs b/zkp/src/sumcheck/mod.rs index bc9da551..75c6c9e9 100644 --- a/zkp/src/sumcheck/mod.rs +++ b/zkp/src/sumcheck/mod.rs @@ -1,10 +1,9 @@ //! Interactive Proof Protocol used for Multilinear Sumcheck // It is derived from https://github.com/arkworks-rs/sumcheck/blob/master/src/ml_sumcheck/protocol/mod.rs. -use algebra::{Field, ListOfProductsOfPolynomials, PolynomialInfo}; +use algebra::{utils::Transcript, Field, ListOfProductsOfPolynomials, PolynomialInfo}; use prover::{ProverMsg, ProverState}; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; +use serde::Serialize; use std::marker::PhantomData; use verifier::SubClaim; pub mod prover; @@ -21,7 +20,42 @@ pub struct MLSumcheck(PhantomData); /// proof generated by prover pub type Proof = Vec>; -impl MLSumcheck { +/// This is a wrapper for prover claiming sumcheck protocol +pub struct SumcheckKit { + /// claimed sum + pub claimed_sum: F, + /// poly info of the polynomial proved in the sumcheck + pub info: PolynomialInfo, + /// random point used to instatiate the sumcheck protocol + pub u: Vec, + /// sumcheck proof + pub proof: Proof, + /// random point returned from sumcheck protocol + pub randomness: Vec, +} + +/// This is a wrapper for verifier checking the sumcheck protocol +pub struct ProofWrapper { + /// claimed sum + pub claimed_sum: F, + /// poly info of the polynomial proved in the sumcheck + pub info: PolynomialInfo, + /// sumcheck proof + pub proof: Proof, +} + +impl SumcheckKit { + /// Extract the proof wrapper used by verifier + pub fn extract(&self) -> ProofWrapper { + ProofWrapper:: { + claimed_sum: self.claimed_sum, + info: self.info, + proof: self.proof.clone(), + } + } +} + +impl MLSumcheck { /// Extract sum from the proof pub fn extract_sum(proof: &Proof) -> F { proof[0].evaluations[0] + proof[0].evaluations[1] @@ -42,29 +76,27 @@ impl MLSumcheck { pub fn prove( polynomial: &ListOfProductsOfPolynomials, ) -> Result, crate::error::Error> { - // TODO switch to the Fiat-Shamir RNG - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, polynomial).map(|r| r.0) + let mut trans = Transcript::::new(); + Self::prove_as_subprotocol(&mut trans, polynomial).map(|r| r.0) } /// This function does the same thing as `prove`, but it uses a `Fiat-Shamir RNG` as the transcript/to generate the /// verifier challenges. Additionally, it returns the prover's state in addition to the proof. /// Both of these allow this sumcheck to be better used as a part of a larger protocol. pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, + trans: &mut Transcript, polynomial: &ListOfProductsOfPolynomials, ) -> Result<(Proof, ProverState), crate::error::Error> { - // TODO update Fiat-Shamir RNG using polynomial.info() - + trans.append_message(b"polynomial info", &polynomial.info()); + println!("[sumcheck] The polynomial (degree = {}) to be proved consists of {} MLEs (#vars = {}) in the form of {} products.", polynomial.max_multiplicands, polynomial.flattened_ml_extensions.len(), polynomial.num_variables, polynomial.products.len()); let mut prover_state = IPForMLSumcheck::prover_init(polynomial); let mut verifier_msg = None; let mut prover_msgs = Vec::with_capacity(polynomial.num_variables); for _ in 0..polynomial.num_variables { let prover_msg = IPForMLSumcheck::prove_round(&mut prover_state, &verifier_msg); - // TODO update Fiat-Shamir RNG using prover's message + trans.append_message(b"sumcheck msg", &prover_msg); prover_msgs.push(prover_msg); - verifier_msg = Some(IPForMLSumcheck::sample_round(fs_rng)); + verifier_msg = Some(IPForMLSumcheck::sample_round(trans)); } prover_state .randomness @@ -78,27 +110,25 @@ impl MLSumcheck { claimed_sum: F, proof: &Proof, ) -> Result, crate::Error> { - // TODO switch to the Fiat-Shamir RNG - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verify_as_subprotocol(&mut fs_rng, polynomial_info, claimed_sum, proof) + let mut trans = Transcript::::new(); + Self::verify_as_subprotocol(&mut trans, polynomial_info, claimed_sum, proof) } /// This function does the same thing as `verify`, but it uses a `Fiat-Shamir RNG`` as the transcript to generate the /// verifier challenges. This allows this sumcheck to be used as a part of a larger protocol. pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, + trans: &mut Transcript, polynomial_info: &PolynomialInfo, claimed_sum: F, proof: &Proof, ) -> Result, crate::Error> { - // TODO update Fiat-Shamir RNG using polynomial.info() + trans.append_message(b"polynomial info", polynomial_info); let mut verifier_state = IPForMLSumcheck::verifier_init(polynomial_info); for i in 0..polynomial_info.num_variables { let prover_msg = proof.get(i).expect("proof is incomplete"); - // TODO update Fiat-Shamir RNG using prover's message + trans.append_message(b"sumcheck msg", prover_msg); - IPForMLSumcheck::verify_round((*prover_msg).clone(), &mut verifier_state, fs_rng); + IPForMLSumcheck::verify_round((*prover_msg).clone(), &mut verifier_state, trans); } IPForMLSumcheck::check_and_generate_subclaim(verifier_state, claimed_sum) diff --git a/zkp/src/sumcheck/prover.rs b/zkp/src/sumcheck/prover.rs index b0de941b..060db79e 100644 --- a/zkp/src/sumcheck/prover.rs +++ b/zkp/src/sumcheck/prover.rs @@ -6,6 +6,8 @@ use std::vec; use algebra::Field; use algebra::{DenseMultilinearExtension, ListOfProductsOfPolynomials, MultilinearExtension}; +use serde::ser::SerializeSeq; +use serde::Serialize; use super::verifier::VerifierMsg; use super::IPForMLSumcheck; @@ -18,6 +20,18 @@ pub struct ProverMsg { pub(crate) evaluations: Rc>, } +impl Serialize for ProverMsg { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.evaluations.len()))?; + for e in self.evaluations.iter() { + seq.serialize_element(e)?; + } + seq.end() + } +} /// Prover State pub struct ProverState { /// sampled randomness given by the verifier diff --git a/zkp/src/sumcheck/verifier.rs b/zkp/src/sumcheck/verifier.rs index c1a3e1b8..ef7288a5 100644 --- a/zkp/src/sumcheck/verifier.rs +++ b/zkp/src/sumcheck/verifier.rs @@ -3,8 +3,7 @@ use std::vec; -use algebra::{Field, FieldUniformSampler, PolynomialInfo}; -use rand::distributions::Distribution; +use algebra::{utils::Transcript, Field, PolynomialInfo}; use crate::error::Error; use std::rc::Rc; @@ -31,6 +30,7 @@ pub struct VerifierState { } /// Subclaim when verifier is convinced +#[derive(Default)] pub struct SubClaim { /// the multi-dimensional point that this multilinear extension is evaluated to pub point: Vec, @@ -56,10 +56,10 @@ impl IPForMLSumcheck { /// Normally, this function should perform actual verification. Instead, `verify_round` only samples /// and stores randomness and perform verifications altogether in `check_and_generate_subclaim` at /// the last step. - pub fn verify_round( + pub fn verify_round( prover_msg: ProverMsg, verifier_state: &mut VerifierState, - rng: &mut R, + trans: &mut Transcript, ) -> Option> { if verifier_state.finished { panic!("incorrect verifier state: Verifier is already finished.") @@ -67,7 +67,7 @@ impl IPForMLSumcheck { // Now, verifier should check if the received P(0) + P(1) = expected. The check is moved to // `check_and_generate_subclaim`, and will be done after the last round. - let msg = Self::sample_round(rng); + let msg = Self::sample_round(trans); verifier_state.randomness.push(msg.randomness); verifier_state .polynomials_received @@ -121,9 +121,9 @@ impl IPForMLSumcheck { /// Simulate a verifier message without doing verification #[inline] - pub fn sample_round(rng: &mut R) -> VerifierMsg { + pub fn sample_round(trans: &mut Transcript) -> VerifierMsg { VerifierMsg { - randomness: FieldUniformSampler::new().sample(rng), + randomness: trans.get_challenge(b"random point in each round"), } } } diff --git a/zkp/src/utils.rs b/zkp/src/utils.rs index 6c3950a5..173fee5e 100644 --- a/zkp/src/utils.rs +++ b/zkp/src/utils.rs @@ -1,5 +1,8 @@ //! This module defines some useful utils that may invoked by piop. -use algebra::{DenseMultilinearExtension, Field}; +use algebra::{ + AbstractExtensionField, AsFrom, DecomposableField, DenseMultilinearExtension, Field, +}; +use std::rc::Rc; /// Generate MLE of the ideneity function eq(u,x) for x \in \{0, 1\}^dim pub fn gen_identity_evaluations(u: &[F]) -> DenseMultilinearExtension { @@ -28,6 +31,247 @@ pub fn eval_identity_function(u: &[F], v: &[F]) -> F { evaluation } +/// AddAssign<(EF, &'a DenseMultilinearExtension)> for DenseMultilinearExtension +/// output += r * input +pub fn add_assign_ef>( + output: &mut DenseMultilinearExtension, + r: &EF, + input: &DenseMultilinearExtension, +) { + output + .iter_mut() + .zip(input.iter()) + .for_each(|(x, y)| *x += *r * *y); +} + +/// Verify the relationship between the evaluations of these small oracles and the requested evaluation of the committed oracle +/// +/// # Arguments: +/// * `evals`: vector consisting of all the evaluations of relevant MLEs. The arrangement is consistent to the vector returned in `compute_evals`. +/// * `oracle_eval`: the requested point reduced over the committed polynomial, which is the random linear combination of the smaller oracles used in IOP. +/// * `trans`: the transcript maintained by verifier +#[inline] +pub fn verify_oracle_relation(evals: &[F], oracle_eval: F, random_point: &[F]) -> bool { + let eq_at_r = gen_identity_evaluations(random_point); + let randomized_eval = evals + .iter() + .zip(eq_at_r.iter()) + .fold(F::zero(), |acc, (eval, coeff)| acc + *eval * *coeff); + randomized_eval == oracle_eval +} + +/// Print statistic +/// +/// # Arguments +/// `total_prover_time` - open time in PCS + prover time in IOP +/// `total_verifier_time` - verifier time in PCS + verifier time in IOP +/// `total_proof_size` - eval proof + IOP proof +/// `committed_poly_num_vars` - number of variables of the committed polynomical +/// `num_oracles` - number of small oracles composing the committed oracle +/// `setup_time` - setup time in PCS +/// `commit_time` - commit time in PCS +/// `open_time` - open time in PCS +/// `verifier_time` - verifier +#[allow(clippy::too_many_arguments)] +pub fn print_statistic( + // Total + total_prover_time: u128, + total_verifier_time: u128, + total_proof_size: usize, + // IOP + iop_prover_time: u128, + iop_verifier_time: u128, + iop_proof_size: usize, + // PCS statistic + committed_poly_num_vars: usize, + num_oracles: usize, + oracle_num_vars: usize, + setup_time: u128, + commit_time: u128, + pcs_open_time: u128, + pcs_verifier_time: u128, + pcs_proof_size: usize, +) { + println!("\n==Total Statistic=="); + println!("Prove Time: {:?} ms", total_prover_time); + println!("Verifier Time: {:?} ms", total_verifier_time); + println!("Proof Size: {:?} Bytes", total_proof_size); + + println!("\n==IOP Statistic=="); + println!("Prove Time: {:?} ms", iop_prover_time); + println!("Verifier Time: {:?} ms", iop_verifier_time); + println!("Proof Size: {:?} Bytes", iop_proof_size); + + println!("\n==PCS Statistic=="); + println!( + "The committed polynomial is of {} variables,", + committed_poly_num_vars + ); + println!( + "which consists of {} smaller oracles used in IOP, each of which is of {} variables.", + num_oracles, oracle_num_vars + ); + println!("Setup Time: {:?} ms", setup_time); + println!("Commit Time: {:?} ms", commit_time); + println!("Open Time: {:?} ms", pcs_open_time); + println!("Verify Time: {:?} ms", pcs_verifier_time); + println!("Proof Size: {:?} Bytes\n", pcs_proof_size); +} + +// credit@Plonky3 +/// Batch multiplicative inverses with Montgomery's trick +/// This is Montgomery's trick. At a high level, we invert the product of the given field +/// elements, then derive the individual inverses from that via multiplication. +/// +/// The usual Montgomery trick involves calculating an array of cumulative products, +/// resulting in a long dependency chain. To increase instruction-level parallelism, we +/// compute WIDTH separate cumulative product arrays that only meet at the end. +/// +/// # Panics +/// Might panic if asserts or unwraps uncover a bug. +pub fn batch_inverse(x: &[F]) -> Vec { + // Higher WIDTH increases instruction-level parallelism, but too high a value will cause us + // to run out of registers. + const WIDTH: usize = 4; + // JN note: WIDTH is 4. The code is specialized to this value and will need + // modification if it is changed. I tried to make it more generic, but Rust's const + // generics are not yet good enough. + + // Handle special cases. Paradoxically, below is repetitive but concise. + // The branches should be very predictable. + let n = x.len(); + if n == 0 { + return Vec::new(); + } else if n == 1 { + return vec![x[0].inv()]; + } else if n == 2 { + let x01 = x[0] * x[1]; + let x01inv = x01.inv(); + return vec![x01inv * x[1], x01inv * x[0]]; + } else if n == 3 { + let x01 = x[0] * x[1]; + let x012 = x01 * x[2]; + let x012inv = x012.inv(); + let x01inv = x012inv * x[2]; + return vec![x01inv * x[1], x01inv * x[0], x012inv * x01]; + } + debug_assert!(n >= WIDTH); + + // Buf is reused for a few things to save allocations. + // Fill buf with cumulative product of x, only taking every 4th value. Concretely, buf will + // be [ + // x[0], x[1], x[2], x[3], + // x[0] * x[4], x[1] * x[5], x[2] * x[6], x[3] * x[7], + // x[0] * x[4] * x[8], x[1] * x[5] * x[9], x[2] * x[6] * x[10], x[3] * x[7] * x[11], + // ... + // ]. + // If n is not a multiple of WIDTH, the result is truncated from the end. For example, + // for n == 5, we get [x[0], x[1], x[2], x[3], x[0] * x[4]]. + let mut buf: Vec = Vec::with_capacity(n); + // cumul_prod holds the last WIDTH elements of buf. This is redundant, but it's how we + // convince LLVM to keep the values in the registers. + let mut cumul_prod: [F; WIDTH] = x[..WIDTH].try_into().unwrap(); + buf.extend(cumul_prod); + for (i, &xi) in x[WIDTH..].iter().enumerate() { + cumul_prod[i % WIDTH] *= xi; + buf.push(cumul_prod[i % WIDTH]); + } + debug_assert_eq!(buf.len(), n); + + let mut a_inv = { + // This is where the four dependency chains meet. + // Take the last four elements of buf and invert them all. + let c01 = cumul_prod[0] * cumul_prod[1]; + let c23 = cumul_prod[2] * cumul_prod[3]; + let c0123 = c01 * c23; + let c0123inv = c0123.inv(); + let c01inv = c0123inv * c23; + let c23inv = c0123inv * c01; + [ + c01inv * cumul_prod[1], + c01inv * cumul_prod[0], + c23inv * cumul_prod[3], + c23inv * cumul_prod[2], + ] + }; + + for i in (WIDTH..n).rev() { + // buf[i - WIDTH] has not been written to by this loop, so it equals + // x[i % WIDTH] * x[i % WIDTH + WIDTH] * ... * x[i - WIDTH]. + buf[i] = buf[i - WIDTH] * a_inv[i % WIDTH]; + // buf[i] now holds the inverse of x[i]. + a_inv[i % WIDTH] *= x[i]; + } + for i in (0..WIDTH).rev() { + buf[i] = a_inv[i]; + } + + for (&bi, &xi) in buf.iter().zip(x) { + // Sanity check only. + debug_assert_eq!(bi * xi, F::one()); + } + + buf +} + +/// compute_rangecheck_m +pub fn compute_general_m( + f_vec: &[Rc>], + t: Rc>, +) -> Rc> { + let num_vars = f_vec[0].num_vars; + let m_evaluations: Vec = t + .evaluations + .iter() + .map(|t_item| { + let m_f_vec = f_vec.iter().fold(F::zero(), |acc, f| { + let m_f: usize = f + .evaluations + .iter() + .filter(|&f_item| f_item == t_item) + .count(); + let m_f: F = F::new(F::Value::as_from(m_f as f64)); + acc + m_f + }); + + let m_t = t + .evaluations + .iter() + .filter(|&t_item2| t_item2 == t_item) + .count(); + let m_t: F = F::new(F::Value::as_from(m_t as f64)); + + m_f_vec / m_t + }) + .collect(); + + Rc::new(DenseMultilinearExtension::from_evaluations_slice( + num_vars, + &m_evaluations, + )) +} + +/// computer rangecheck m +pub fn compute_rangecheck_m( + f_vec: &[Rc>], + range: usize, +) -> Rc> { + let num_vars = f_vec[0].num_vars; + let num_padding_zero = (1 << num_vars) - range; + let mut m_usize = vec![0; range]; + f_vec.iter().for_each(|f| { + f.iter() + .for_each(|x| m_usize[(*x).value().into() as usize] += 1) + }); + let mut m: Vec = m_usize + .iter() + .map(|x| F::new(F::Value::as_from(*x as f64))) + .collect(); + m[0] /= F::new(F::Value::as_from((1 + num_padding_zero) as f64)); + m.resize(1 << num_vars, m[0]); + Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, m)) +} + #[cfg(test)] mod test { use crate::utils::{eval_identity_function, gen_identity_evaluations}; diff --git a/zkp/tests/test_accumulator.rs b/zkp/tests/test_accumulator.rs index d1b586f5..e905dd2b 100644 --- a/zkp/tests/test_accumulator.rs +++ b/zkp/tests/test_accumulator.rs @@ -1,30 +1,24 @@ +use algebra::{transformation::AbstractNTT, NTTField, NTTPolynomial, Polynomial}; use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - Basis, DenseMultilinearExtension, Field, FieldUniformSampler, MultilinearExtension, + BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension, Field, MultilinearExtension, }; -use algebra::{transformation::AbstractNTT, NTTField, NTTPolynomial, Polynomial}; use itertools::izip; use num_traits::One; -use rand_distr::Distribution; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::thread_rng; +use sha2::Sha256; use std::rc::Rc; use std::vec; -use zkp::{ - piop::{ - AccumulatorIOP, AccumulatorInstance, AccumulatorWitness, DecomposedBitsInfo, - NTTInstanceInfo, RlweCiphertext, RlweCiphertexts, - }, - utils::gen_identity_evaluations, +use zkp::piop::{ + accumulator::AccumulatorSnarks, AccumulatorIOP, AccumulatorInstance, AccumulatorWitness, + DecomposedBitsInfo, NTTInstanceInfo, RlweCiphertext, RlweCiphertexts, RlweMultRgswInstance, }; -#[derive(Field, DecomposableField, Prime, FheField, NTT)] -#[modulus = 132120577] -pub struct Fp32(u32); // field type -type FF = Fp32; - -#[derive(Field, DecomposableField, Prime)] -#[modulus = 59] -pub struct Fq(u32); +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; fn random_rlwe_ciphertext(rng: &mut R, num_vars: usize) -> RlweCiphertext where @@ -104,84 +98,33 @@ fn ntt_inverse_transform_normal_order(log_n: u32, points: & .data() } -// Perform d * ACC * RGSW(Zu) -// # Argument -// * input_d: scalar d = X^{-a_u} - 1 of the coefficient form -// * input_accumulator_ntt: ACC of the ntt form -// * input_rgsw_ntt: RGSW(Zu) of the ntt form -fn update_accumulator( - input_accumulator_ntt: &RlweCiphertext, - input_d: Rc>, - input_rgsw_ntt: (RlweCiphertexts, RlweCiphertexts), - basis_info: &DecomposedBitsInfo, +fn generate_rlwe_mult_rgsw_instance( + num_vars: usize, + input_rlwe: RlweCiphertext, + bits_rgsw_c_ntt: RlweCiphertexts, + bits_rgsw_f_ntt: RlweCiphertexts, + bits_info: &DecomposedBitsInfo, ntt_info: &NTTInstanceInfo, -) -> AccumulatorWitness { - // 1. Perform ntt transform on (x^{-a_u} - 1) - let input_d_ntt = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_transform_normal_order(ntt_info.log_n as u32, &input_d.evaluations), - )); - - // 2. Perform point-multiplication to compute (x^{-a_u} - 1) * ACC - let input_rlwe_ntt = RlweCiphertext { - a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - izip!( - &input_d_ntt.evaluations, - &input_accumulator_ntt.a.evaluations - ) - .map(|(d_i, a_i)| *d_i * *a_i) - .collect(), - )), - b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - izip!( - &input_d_ntt.evaluations, - &input_accumulator_ntt.b.evaluations - ) - .map(|(d_i, b_i)| *d_i * *b_i) - .collect(), - )), - }; - - // 3. Compute the RLWE of coefficient form as the input of the multiplication between RLWE and RGSW - let input_rlwe = RlweCiphertext { - a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_inverse_transform_normal_order( - ntt_info.log_n as u32, - &input_rlwe_ntt.a.evaluations, - ), - )), - b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_inverse_transform_normal_order( - ntt_info.log_n as u32, - &input_rlwe_ntt.b.evaluations, - ), - )), - }; - - // 4. Decompose the input of RLWE ciphertex +) -> RlweMultRgswInstance { + // 1. Decompose the input of RLWE ciphertex let bits_rlwe = RlweCiphertexts { a_bits: input_rlwe .a - .get_decomposed_mles(basis_info.base_len, basis_info.bits_len), + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len), b_bits: input_rlwe .b - .get_decomposed_mles(basis_info.base_len, basis_info.bits_len), + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len), }; - let (bits_rgsw_c_ntt, bits_rgsw_f_ntt) = input_rgsw_ntt; - // 5. Compute the ntt form of the decomposed bits + // 2. Compute the ntt form of the decomposed bits let bits_rlwe_ntt = RlweCiphertexts { a_bits: bits_rlwe .a_bits .iter() .map(|bit| { Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_transform_normal_order(ntt_info.log_n as u32, &bit.evaluations), + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), )) }) .collect(), @@ -190,8 +133,8 @@ fn update_accumulator( .iter() .map(|bit| { Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_transform_normal_order(ntt_info.log_n as u32, &bit.evaluations), + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), )) }) .collect(), @@ -201,8 +144,8 @@ fn update_accumulator( assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rgsw_c_ntt.a_bits.len()); assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rgsw_f_ntt.a_bits.len()); - // 6. Compute the output of ntt form with the RGSW ciphertext and the decomposed bits of ntt form - let mut output_g_ntt = vec![F::zero(); 1 << ntt_info.log_n]; + // 3. Compute the output of ntt form with the RGSW ciphertext and the decomposed bits of ntt form + let mut output_g_ntt = vec![F::zero(); 1 << num_vars]; for (a, b, c, f) in izip!( &bits_rlwe_ntt.a_bits, &bits_rlwe_ntt.b_bits, @@ -214,7 +157,7 @@ fn update_accumulator( }); } - let mut output_h_ntt = vec![F::zero(); 1 << ntt_info.log_n]; + let mut output_h_ntt = vec![F::zero(); 1 << num_vars]; for (a, b, c, f) in izip!( &bits_rlwe_ntt.a_bits, &bits_rlwe_ntt.b_bits, @@ -228,40 +171,173 @@ fn update_accumulator( let output_rlwe_ntt = RlweCiphertext { a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, + num_vars, output_g_ntt, )), b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, + num_vars, output_h_ntt, )), }; - AccumulatorWitness { - accumulator_ntt: input_accumulator_ntt.clone(), - d: input_d.clone(), - d_ntt: input_d_ntt, - input_rlwe_ntt, + RlweMultRgswInstance::new( + num_vars, + bits_info, + ntt_info, input_rlwe, bits_rlwe, bits_rlwe_ntt, bits_rgsw_c_ntt, bits_rgsw_f_ntt, output_rlwe_ntt, + // &output_rlwe, + ) +} +// Perform d * ACC * RGSW(Zu) +// # Argument +// * input_d: scalar d = X^{-a_u} - 1 of the coefficient form +// * input_accumulator_ntt: ACC of the ntt form +// * input_rgsw_ntt: RGSW(Zu) of the ntt form +fn update_accumulator( + num_vars: usize, + acc_ntt: RlweCiphertext, + d: Rc>, + bits_rgsw_c_ntt: RlweCiphertexts, + bits_rgsw_f_ntt: RlweCiphertexts, + bits_info: &DecomposedBitsInfo, + ntt_info: &NTTInstanceInfo, +) -> AccumulatorWitness { + // 1. Perform ntt transform on (x^{-a_u} - 1) + let d_ntt = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + ntt_transform_normal_order(ntt_info.num_vars as u32, &d.evaluations), + )); + + // 2. Perform point-multiplication to compute (x^{-a_u} - 1) * ACC + let input_rlwe_ntt = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + izip!(&d_ntt.evaluations, &acc_ntt.a.evaluations) + .map(|(d_i, a_i)| *d_i * *a_i) + .collect(), + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + izip!(&d_ntt.evaluations, &acc_ntt.b.evaluations) + .map(|(d_i, b_i)| *d_i * *b_i) + .collect(), + )), + }; + + // 3. Compute the RLWE of coefficient form as the input of the multiplication between RLWE and RGSW + let input_rlwe = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + ntt_inverse_transform_normal_order( + ntt_info.num_vars as u32, + &input_rlwe_ntt.a.evaluations, + ), + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + ntt_info.num_vars, + ntt_inverse_transform_normal_order( + ntt_info.num_vars as u32, + &input_rlwe_ntt.b.evaluations, + ), + )), + }; + + let rlwe_mult_rgsw = generate_rlwe_mult_rgsw_instance( + num_vars, + input_rlwe, + bits_rgsw_c_ntt, + bits_rgsw_f_ntt, + bits_info, + ntt_info, + ); + + AccumulatorWitness { + acc_ntt: acc_ntt.clone(), + d, + d_ntt, + input_rlwe_ntt, + rlwe_mult_rgsw, } } -#[test] -fn test_trivial_accumulator() { +fn generate_instance( + num_vars: usize, + input: RlweCiphertext, + num_updations: usize, + bits_info: &DecomposedBitsInfo, + ntt_info: &NTTInstanceInfo, +) -> AccumulatorInstance { let mut rng = rand::thread_rng(); - let uniform = >::new(); + let mut updations = Vec::with_capacity(num_updations); + + let mut acc_ntt = RlweCiphertext:: { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &input.a.evaluations), + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &input.b.evaluations), + )), + }; + for _ in 0..num_updations { + let d = Rc::new(DenseMultilinearExtension::random(num_vars, &mut rng)); + let bits_rgsw_c_ntt = random_rlwe_ciphertexts(bits_info.bits_len, &mut rng, num_vars); + let bits_rgsw_f_ntt = random_rlwe_ciphertexts(bits_info.bits_len, &mut rng, num_vars); + // perform ACC * d * RGSW + let updation = update_accumulator( + num_vars, + acc_ntt.clone(), + d, + bits_rgsw_c_ntt, + bits_rgsw_f_ntt, + bits_info, + ntt_info, + ); + // perform ACC + ACC * d * RGSW + acc_ntt = RlweCiphertext { + a: Rc::new(acc_ntt.a.as_ref() + updation.rlwe_mult_rgsw.output_rlwe_ntt.a.as_ref()), + b: Rc::new(acc_ntt.b.as_ref() + updation.rlwe_mult_rgsw.output_rlwe_ntt.b.as_ref()), + }; + updations.push(updation); + } + + let output = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_inverse_transform_normal_order(num_vars as u32, &acc_ntt.a.evaluations), + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_inverse_transform_normal_order(num_vars as u32, &acc_ntt.b.evaluations), + )), + }; + let output_ntt = acc_ntt; + AccumulatorInstance::new( + num_vars, + num_updations, + input, + updations, + output_ntt, + output, + bits_info, + ntt_info, + ) +} +#[test] +fn test_random_accumulator() { // information used to decompose bits - let base_len: u32 = 2; + let base_len: usize = 2; let base: FF = FF::new(1 << base_len); - let bits_len: u32 = >::new(base_len).decompose_len() as u32; + let bits_len = >::new(base_len as u32).decompose_len(); let num_vars = 10; - let basis_info = DecomposedBitsInfo { + let bits_info = DecomposedBitsInfo { base, base_len, bits_len, @@ -280,79 +356,129 @@ fn test_trivial_accumulator() { power *= root; } let ntt_table = Rc::new(ntt_table); - let ntt_info = NTTInstanceInfo { log_n, ntt_table }; - - let mut accumulator = random_rlwe_ciphertext(&mut rng, num_vars); - let mut accumulator_instance = >::new(num_vars, &ntt_info, &basis_info); + let ntt_info = NTTInstanceInfo { + num_vars, + ntt_table, + num_ntt: 0, + }; - // number of updations in ACC let num_updations = 10; - let mut witnesses = Vec::with_capacity(num_updations); - let random_d: Vec<_> = (0..1 << num_vars) - .map(|_| uniform.sample(&mut rng)) - .collect(); - - // number of ntt in each updation - let num_ntt_iter = ((bits_len << 1) + 3) as usize; - let num_ntt = num_updations * num_ntt_iter; - // randomness used to combine all ntt instances - let randomness_ntt = (0..num_ntt) - .map(|_| uniform.sample(&mut rng)) - .collect::>(); - let num_sumcheck = num_updations * 2; - // randomness used to combine all sumcheck protocols - let randomness_sumcheck = (0..num_sumcheck) - .map(|_| uniform.sample(&mut rng)) - .collect::>(); - let u = (0..num_vars) - .map(|_| uniform.sample(&mut rng)) - .collect::>(); - let identity_func_at_u = Rc::new(gen_identity_evaluations(&u)); - - // update accumulator for `num_updations` times - for idx in 0..num_updations { - let input_d = Rc::new(DenseMultilinearExtension::from_evaluations_slice( - num_vars, &random_d, - )); - let rgsw_ntt = ( - random_rlwe_ciphertexts(basis_info.bits_len as usize, &mut rng, num_vars), - random_rlwe_ciphertexts(basis_info.bits_len as usize, &mut rng, num_vars), - ); - let witness = update_accumulator(&accumulator, input_d, rgsw_ntt, &basis_info, &ntt_info); + let input = random_rlwe_ciphertext(&mut thread_rng(), num_vars); + let instance = generate_instance(num_vars, input, num_updations, &bits_info, &ntt_info); - accumulator_instance.add_witness( - &randomness_ntt[idx * num_ntt_iter..(idx + 1) * num_ntt_iter], - &randomness_sumcheck[idx * 2..(idx + 1) * 2], - &identity_func_at_u, - &witness, - ); - accumulator = RlweCiphertext { - a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - izip!(accumulator.a.iter(), witness.output_rlwe_ntt.a.iter()) - .map(|(acc, x)| *acc + *x) - .collect(), - )), - b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - izip!(accumulator.b.iter(), witness.output_rlwe_ntt.b.iter()) - .map(|(acc, x)| *acc + *x) - .collect(), - )), - }; - witnesses.push(witness); + let info = instance.info(); + + let (kit, recursive_proof) = AccumulatorIOP::::prove(&instance); + + let evals_at_r = instance.evaluate(&kit.randomness); + let evals_at_u = instance.evaluate(&kit.u); + + let mut wrapper = kit.extract(); + let check = AccumulatorIOP::::verify( + &mut wrapper, + &evals_at_r, + &evals_at_u, + &info, + &recursive_proof, + ); + + assert!(check); +} + +#[test] +fn test_random_accumulator_extension_field() { + // information used to decompose bits + let base_len: usize = 2; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = 10; + let bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len, + num_vars, + num_instances: 0, + }; + + // information used to perform NTT + let log_n = num_vars; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; } + let ntt_table = Rc::new(ntt_table); + let ntt_info = NTTInstanceInfo { + num_vars, + ntt_table, + num_ntt: 0, + }; + + let num_updations = 10; + let input = random_rlwe_ciphertext(&mut thread_rng(), num_vars); + let instance = generate_instance(num_vars, input, num_updations, &bits_info, &ntt_info); + let instance_ef = instance.to_ef::(); + + let info = instance_ef.info(); - let info = accumulator_instance.info(); - let proof = >::prove(&accumulator_instance, &u); - let subclaim = >::verify(&proof, &u, &info); - assert!(subclaim.verify_subclaim( - &u, - &randomness_ntt, - &randomness_sumcheck, - &accumulator_instance.ntt_instance.coeffs, - &accumulator_instance.ntt_instance.points, - &witnesses, + let (kit, recursive_proof) = AccumulatorIOP::::prove(&instance_ef); + + let evals_at_r = instance.evaluate_ext(&kit.randomness); + let evals_at_u = instance.evaluate_ext(&kit.u); + + let mut wrapper = kit.extract(); + let check = AccumulatorIOP::::verify( + &mut wrapper, + &evals_at_r, + &evals_at_u, &info, - )); + &recursive_proof, + ); + + assert!(check); +} + +#[test] +fn test_snarks() { + // information used to decompose bits + let base_len: usize = 2; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = 10; + let bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len, + num_vars, + num_instances: 0, + }; + + // information used to perform NTT + let log_n = num_vars; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Rc::new(ntt_table); + let ntt_info = NTTInstanceInfo { + num_vars, + ntt_table, + num_ntt: 0, + }; + + let num_updations = 10; + let input = random_rlwe_ciphertext(&mut thread_rng(), num_vars); + let instance = generate_instance(num_vars, input, num_updations, &bits_info, &ntt_info); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); } diff --git a/zkp/tests/test_addition_in_zq.rs b/zkp/tests/test_addition_in_zq.rs index 137a20b0..eb89d8f8 100644 --- a/zkp/tests/test_addition_in_zq.rs +++ b/zkp/tests/test_addition_in_zq.rs @@ -1,24 +1,26 @@ use algebra::{ derive::{DecomposableField, Field, Prime}, - Basis, DecomposableField, DenseMultilinearExtension, Field, FieldUniformSampler, + BabyBear, BabyBearExetension, Basis, DecomposableField, DenseMultilinearExtension, Field, + FieldUniformSampler, }; use num_traits::{One, Zero}; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; use rand::prelude::*; use rand_distr::Distribution; +use sha2::Sha256; use std::rc::Rc; use std::vec; -use zkp::piop::{AdditionInZq, AdditionInZqInstance}; - -#[derive(Field, DecomposableField, Prime)] -#[modulus = 132120577] -pub struct Fp32(u32); - +use zkp::piop::{ + addition_in_zq::AdditionInZqSnarks, AdditionInZq, AdditionInZqInstance, DecomposedBitsInfo, +}; #[derive(Field, DecomposableField, Prime)] #[modulus = 59] pub struct Fq(u32); -// field type -type FF = Fp32; +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; macro_rules! field_vec { ($t:ty; $elem:expr; $n:expr)=>{ @@ -31,14 +33,11 @@ macro_rules! field_vec { #[test] fn test_trivial_addition_in_zq() { - let mut rng = thread_rng(); - let sampler = >::new(); - let q = FF::new(9); - let base_len: u32 = 1; + let base_len = 1; let base: FF = FF::new(2); let num_vars = 2; - let bits_len: u32 = 4; + let bits_len = 4; let abc = vec![ Rc::new(DenseMultilinearExtension::from_evaluations_vec( num_vars, @@ -58,33 +57,107 @@ fn test_trivial_addition_in_zq() { field_vec!(FF; 1, 1, 0, 0), )); - // decompose bits of every element in a, b, c - let abc_bits: Vec<_> = abc + let bits_info = DecomposedBitsInfo:: { + base, + base_len, + bits_len, + num_vars, + num_instances: 3, + }; + let instance = AdditionInZqInstance::::from_slice(&abc, &k, q, &bits_info); + + let info = instance.info(); + + let kit = AdditionInZq::::prove(&instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let check = AdditionInZq::::verify(&wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_random_addition_in_zq() { + let mut rng = thread_rng(); + let uniform_fq = >::new(); + let num_vars = 10; + let q = FF::new(Fq::MODULUS_VALUE); + let base_len = 3; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + + // Addition in Zq + let a: Vec<_> = (0..(1 << num_vars)) + .map(|_| uniform_fq.sample(&mut rng)) + .collect(); + let b: Vec<_> = (0..(1 << num_vars)) + .map(|_| uniform_fq.sample(&mut rng)) + .collect(); + let c_k: Vec<_> = a .iter() - .map(|x| x.get_decomposed_mles(base_len, bits_len)) + .zip(b.iter()) + .map(|(x, y)| { + if x.value() + y.value() >= Fq::MODULUS_VALUE { + (*x + *y, Fq::one()) + } else { + (*x + *y, Fq::zero()) + } + }) .collect(); - let abc_bits_ref: Vec<_> = abc_bits.iter().collect(); - let abc_instance = AdditionInZqInstance::from_slice(&abc, &k, q, base, base_len, bits_len); - let addition_info = abc_instance.info(); + let (c, k): (Vec<_>, Vec<_>) = c_k.iter().cloned().unzip(); + + let abc = vec![ + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + // Convert to Fp + a.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + b.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + c.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )), + ]; - let u: Vec<_> = (0..num_vars).map(|_| sampler.sample(&mut rng)).collect(); + let k = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + k.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )); - let proof = AdditionInZq::prove(&abc_instance, &u); - let subclaim = AdditionInZq::verify(&proof, &addition_info.decomposed_bits_info); - assert!(subclaim.verify_subclaim(q, &abc, k.as_ref(), &abc_bits_ref, &u, &addition_info)); + let bits_info = DecomposedBitsInfo:: { + base, + base_len, + bits_len, + num_vars, + num_instances: 3, + }; + let instance = AdditionInZqInstance::::from_slice(&abc, &k, q, &bits_info); + + let info = instance.info(); + + let kit = AdditionInZq::::prove(&instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let check = AdditionInZq::::verify(&wrapper, &evals, &info); + + assert!(check); } #[test] -fn test_random_addition_in_zq() { +fn test_random_addition_in_zq_extension_field() { let mut rng = thread_rng(); let uniform_fq = >::new(); - let uniform_fp = >::new(); let num_vars = 10; let q = FF::new(Fq::MODULUS_VALUE); - let base_len: u32 = 3; + let base_len = 3; let base: FF = FF::new(1 << base_len); - let bits_len: u32 = >::new(base_len).decompose_len() as u32; + let bits_len = >::new(base_len as u32).decompose_len(); // Addition in Zq let a: Vec<_> = (0..(1 << num_vars)) @@ -128,19 +201,90 @@ fn test_random_addition_in_zq() { k.iter().map(|x: &Fq| FF::new(x.value())).collect(), )); - // decompose bits of every element in a, b, c - let abc_bits: Vec<_> = abc + let bits_info = DecomposedBitsInfo:: { + base, + base_len, + bits_len, + num_vars, + num_instances: 3, + }; + let instance = AdditionInZqInstance::::from_slice(&abc, &k, q, &bits_info); + + let instance_ef = instance.to_ef::(); + let info = instance_ef.info(); + + let kit = AdditionInZq::::prove(&instance_ef); + let evals = instance.evaluate_ext(&kit.randomness); + + let wrapper = kit.extract(); + let check = AdditionInZq::::verify(&wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_snarks() { + let mut rng = thread_rng(); + let uniform_fq = >::new(); + let num_vars = 10; + let q = FF::new(Fq::MODULUS_VALUE); + let base_len = 3; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + + // Addition in Zq + let a: Vec<_> = (0..(1 << num_vars)) + .map(|_| uniform_fq.sample(&mut rng)) + .collect(); + let b: Vec<_> = (0..(1 << num_vars)) + .map(|_| uniform_fq.sample(&mut rng)) + .collect(); + let c_k: Vec<_> = a .iter() - .map(|x| x.get_decomposed_mles(base_len, bits_len)) + .zip(b.iter()) + .map(|(x, y)| { + if x.value() + y.value() >= Fq::MODULUS_VALUE { + (*x + *y, Fq::one()) + } else { + (*x + *y, Fq::zero()) + } + }) .collect(); - let abc_bits_ref: Vec<_> = abc_bits.iter().collect(); - let abc_instance = AdditionInZqInstance::from_slice(&abc, &k, q, base, base_len, bits_len); - let addition_info = abc_instance.info(); + let (c, k): (Vec<_>, Vec<_>) = c_k.iter().cloned().unzip(); + + let abc = vec![ + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + // Convert to Fp + a.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + b.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + c.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )), + ]; + + let k = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + k.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )); - let u: Vec<_> = (0..num_vars).map(|_| uniform_fp.sample(&mut rng)).collect(); + let bits_info = DecomposedBitsInfo:: { + base, + base_len, + bits_len, + num_vars, + num_instances: 3, + }; + let instance = AdditionInZqInstance::::from_slice(&abc, &k, q, &bits_info); - let proof = AdditionInZq::prove(&abc_instance, &u); - let subclaim = AdditionInZq::verify(&proof, &addition_info.decomposed_bits_info); - assert!(subclaim.verify_subclaim(q, &abc, k.as_ref(), &abc_bits_ref, &u, &addition_info)); + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); } diff --git a/zkp/tests/test_bit_decomposition.rs b/zkp/tests/test_bit_decomposition.rs index 3068a2e3..f53f996c 100644 --- a/zkp/tests/test_bit_decomposition.rs +++ b/zkp/tests/test_bit_decomposition.rs @@ -1,21 +1,30 @@ -use algebra::Basis; +use algebra::utils::Transcript; use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - DenseMultilinearExtension, Field, FieldUniformSampler, + AbstractExtensionField, BabyBear, BabyBearExetension, Basis, ListOfProductsOfPolynomials, +}; +use algebra::{DenseMultilinearExtension, Field, FieldUniformSampler}; +use itertools::izip; +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{ExpanderCode, ExpanderCodeSpec}, + PolynomialCommitmentScheme, }; -// use protocol::bit_decomposition::{BitDecomposition, DecomposedBits}; use rand::prelude::*; use rand_distr::Distribution; +use sha2::Sha256; use std::rc::Rc; +use std::time::Instant; use std::vec; -use zkp::piop::{BitDecomposition, DecomposedBits}; - -#[derive(Field, Prime, DecomposableField, FheField, NTT)] -#[modulus = 132120577] -pub struct Fp32(u32); +use zkp::piop::{BitDecomposition, BitDecompositionSnarks, DecomposedBits}; +use zkp::sumcheck::MLSumcheck; +use zkp::utils::{ + eval_identity_function, gen_identity_evaluations, print_statistic, verify_oracle_relation, +}; -// field type -type FF = Fp32; +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; macro_rules! field_vec { ($t:ty; $elem:expr; $n:expr)=>{ @@ -28,9 +37,9 @@ macro_rules! field_vec { #[test] fn test_single_trivial_bit_decomposition_base_2() { - let base_len: u32 = 1; + let base_len = 1; let base: FF = FF::new(1 << base_len); - let bits_len: u32 = 2; + let bits_len = 2; let num_vars = 2; let d = Rc::new(DenseMultilinearExtension::from_evaluations_vec( @@ -51,27 +60,24 @@ fn test_single_trivial_bit_decomposition_base_2() { ]; let mut prover_key = DecomposedBits::new(base, base_len, bits_len, num_vars); - prover_key.add_decomposed_bits_instance(&d_bits); + prover_key.add_decomposed_bits_instance(&d, &d_bits); + let info = prover_key.info(); - let d_verifier = vec![d]; - let d_bits_verifier = vec![&d_bits]; + let kit = BitDecomposition::prove(&prover_key); + let evals = prover_key.evaluate(&kit.randomness); - let decomposed_bits_info = prover_key.info(); - let u = field_vec!(FF; 0, 0); - let proof = BitDecomposition::prove(&prover_key, &u); - let subclaim = BitDecomposition::verifier(&proof, &decomposed_bits_info); - assert!(subclaim.verify_subclaim(&d_verifier, &d_bits_verifier, &u, &decomposed_bits_info)); + let wrapper = kit.extract(); + let check = BitDecomposition::verify(&wrapper, &evals, &info); + assert!(check); } #[test] fn test_batch_trivial_bit_decomposition_base_2() { - let base_len: u32 = 1; + let base_len = 1; let base: FF = FF::new(1 << base_len); - let bits_len: u32 = 2; + let bits_len = 2; let num_vars = 2; - let mut rng = thread_rng(); - let uniform = >::new(); let d = vec![ Rc::new(DenseMultilinearExtension::from_evaluations_vec( num_vars, @@ -108,26 +114,27 @@ fn test_batch_trivial_bit_decomposition_base_2() { )), ], ]; - let d_bits_ref: Vec<_> = d_bits.iter().collect(); - let mut decomposed_bits = DecomposedBits::new(base, base_len, bits_len, num_vars); - for d_instance in &d_bits { - decomposed_bits.add_decomposed_bits_instance(d_instance); + let mut instance = DecomposedBits::new(base, base_len, bits_len, num_vars); + for (d_val, d_bits) in izip!(d, d_bits) { + instance.add_decomposed_bits_instance(&d_val, &d_bits); } - let decomposed_bits_info = decomposed_bits.info(); + let info = instance.info(); + + let kit = BitDecomposition::prove(&instance); + let evals = instance.evaluate(&kit.randomness); - let u: Vec<_> = (0..num_vars).map(|_| uniform.sample(&mut rng)).collect(); - let proof = BitDecomposition::prove(&decomposed_bits, &u); - let subclaim = BitDecomposition::verifier(&proof, &decomposed_bits_info); - assert!(subclaim.verify_subclaim(&d, &d_bits_ref, &u, &decomposed_bits_info)); + let wrapper = kit.extract(); + let check = BitDecomposition::verify(&wrapper, &evals, &info); + assert!(check); } #[test] fn test_single_bit_decomposition() { - let base_len: u32 = 4; + let base_len = 4; let base: FF = FF::new(1 << base_len); - let bits_len: u32 = >::new(base_len).decompose_len() as u32; + let bits_len = >::new(base_len as u32).decompose_len(); let num_vars = 10; let mut rng = thread_rng(); @@ -140,25 +147,25 @@ fn test_single_bit_decomposition() { )); let d_bits_prover = d.get_decomposed_mles(base_len, bits_len); - let d_verifier = vec![d]; - let d_bits_verifier = vec![&d_bits_prover]; - let mut decomposed_bits = DecomposedBits::new(base, base_len, bits_len, num_vars); - decomposed_bits.add_decomposed_bits_instance(&d_bits_prover); + let mut instance = DecomposedBits::new(base, base_len, bits_len, num_vars); + instance.add_decomposed_bits_instance(&d, &d_bits_prover); - let decomposed_bits_info = decomposed_bits.info(); + let info = instance.info(); - let u: Vec<_> = (0..num_vars).map(|_| uniform.sample(&mut rng)).collect(); - let proof = BitDecomposition::prove(&decomposed_bits, &u); - let subclaim = BitDecomposition::verifier(&proof, &decomposed_bits_info); - assert!(subclaim.verify_subclaim(&d_verifier, &d_bits_verifier, &u, &decomposed_bits_info)); + let sumcheck_kit = BitDecomposition::prove(&instance); + let evals = instance.evaluate(&sumcheck_kit.randomness); + + let wrapper = sumcheck_kit.extract(); + let check = BitDecomposition::verify(&wrapper, &evals, &info); + assert!(check); } #[test] fn test_batch_bit_decomposition() { - let base_len: u32 = 4; + let base_len = 4; let base: FF = FF::new(1 << base_len); - let bits_len: u32 = >::new(base_len).decompose_len() as u32; + let bits_len = >::new(base_len as u32).decompose_len(); let num_vars = 10; let mut rng = thread_rng(); @@ -194,17 +201,254 @@ fn test_batch_bit_decomposition() { .iter() .map(|x| x.get_decomposed_mles(base_len, bits_len)) .collect(); - let d_bits_ref: Vec<_> = d_bits.iter().collect(); - let mut decomposed_bits = DecomposedBits::new(base, base_len, bits_len, num_vars); - for d_instance in d_bits.iter() { - decomposed_bits.add_decomposed_bits_instance(d_instance); + let mut instance = DecomposedBits::new(base, base_len, bits_len, num_vars); + for (val, bits) in izip!(d, d_bits) { + instance.add_decomposed_bits_instance(&val, &bits); } - let decomposed_bits_info = decomposed_bits.info(); + let info = instance.info(); + + let sumcheck_kit = BitDecomposition::prove(&instance); + let evals = instance.evaluate(&sumcheck_kit.randomness); + + let wrapper = sumcheck_kit.extract(); + let check = BitDecomposition::verify(&wrapper, &evals, &info); + assert!(check); +} + +#[test] +fn test_single_bit_decomposition_extension_field() { + let base_len = 4; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = 10; + + let mut rng = thread_rng(); + let uniform = >::new(); + let d = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..(1 << num_vars)) + .map(|_| uniform.sample(&mut rng)) + .collect(), + )); + + let d_bits_prover = d.get_decomposed_mles(base_len, bits_len); + + let mut instance = DecomposedBits::new(base, base_len, bits_len, num_vars); + instance.add_decomposed_bits_instance(&d, &d_bits_prover); + + let instance_ef = instance.to_ef::(); + let info = instance_ef.info(); + + let kit = BitDecomposition::::prove(&instance_ef); + let evals = instance.evaluate_ext(&kit.randomness); + + let wrapper = kit.extract(); + let check = BitDecomposition::::verify(&wrapper, &evals, &info); + assert!(check); +} + +#[test] +fn test_snarks() { + let base_len = 4; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = 10; + + let mut rng = thread_rng(); + let uniform = >::new(); + let d = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..(1 << num_vars)) + .map(|_| uniform.sample(&mut rng)) + .collect(), + )); + + let d_bits_prover = d.get_decomposed_mles(base_len, bits_len); + + let mut instance = DecomposedBits::new(base, base_len, bits_len, num_vars); + instance.add_decomposed_bits_instance(&d, &d_bits_prover); + let instance_info = instance.info(); + let ef_zero = EF::from_base(FF::new(0)); + + println!("Prove {instance_info}\n"); + // This is the actual polynomial to be committed for prover, which consists of all the required small polynomials in the IOP and padded zero polynomials. + let committed_poly = instance.generate_oracle(); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + // 1. Use PCS to commit the above polynomial. + let start = Instant::now(); + let pp = BrakedownPCS::, ExpanderCodeSpec, EF>::setup( + committed_poly.num_vars, + Some(code_spec), + ); + let setup_time = start.elapsed().as_millis(); + + let start = Instant::now(); + let (comm, comm_state) = + BrakedownPCS::, ExpanderCodeSpec, EF>::commit( + &pp, + &committed_poly, + ); + let commit_time = start.elapsed().as_millis(); + + // 2. Prover generates the proof + let prover_start = Instant::now(); + let mut iop_proof_size = 0; + let mut prover_trans = Transcript::::new(); + // Convert the original instance into an instance defined over EF + let instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); + + // 2.1 Generate the random point to instantiate the sumcheck protocol + let prover_u = prover_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + let eq_at_u = Rc::new(gen_identity_evaluations(&prover_u)); + + // 2.2 Construct the polynomial and the claimed sum to be proved in the sumcheck protocol + let mut sumcheck_poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let claimed_sum = ef_zero; + // randomness to combine sumcheck protocols + let randomness = >::sample_coins(&mut prover_trans, &instance_ef); + BitDecomposition::prove_as_subprotocol(&randomness, &mut sumcheck_poly, &instance_ef, &eq_at_u); + let poly_info = sumcheck_poly.info(); + + // 2.3 Generate proof of sumcheck protocol + let (sumcheck_proof, sumcheck_state) = + >::prove_as_subprotocol(&mut prover_trans, &sumcheck_poly) + .expect("Proof generated in Addition In Zq"); + iop_proof_size += bincode::serialize(&sumcheck_proof).unwrap().len(); + let iop_prover_time = prover_start.elapsed().as_millis(); + + // 2.4 Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let start = Instant::now(); + let evals = instance.evaluate_ext(&sumcheck_state.randomness); + + // 2.5 Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point = sumcheck_state.randomness.clone(); + requested_point.extend(&prover_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.log_num_oracles(), + )); + let oracle_eval = committed_poly.evaluate_ext(&requested_point); + + // 2.6 Generate the evaluation proof of the requested point + let eval_proof = BrakedownPCS::, ExpanderCodeSpec, EF>::open( + &pp, + &comm, + &comm_state, + &requested_point, + &mut prover_trans, + ); + let pcs_open_time = start.elapsed().as_millis(); + + // 3. Verifier checks the proof + let verifier_start = Instant::now(); + let mut verifier_trans = Transcript::::new(); + + // 3.1 Generate the random point to instantiate the sumcheck protocol + let verifier_u = verifier_trans.get_vec_challenge( + b"random point used to instantiate sumcheck protocol", + instance.num_vars, + ); + + // 3.2 Generate the randomness used to randomize all the sub-sumcheck protocols + let randomness = verifier_trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + >::num_coins(&instance_info), + ); + + // 3.3 Check the proof of the sumcheck protocol + let mut subclaim = >::verify_as_subprotocol( + &mut verifier_trans, + &poly_info, + claimed_sum, + &sumcheck_proof, + ) + .expect("Verify the proof generated in Bit Decompositon"); + let eq_at_u_r = eval_identity_function(&verifier_u, &subclaim.point); + + // 3.4 Check the evaluation over a random point of the polynomial proved in the sumcheck protocol using evaluations over these small oracles used in IOP + let check_subcliam = BitDecomposition::::verify_as_subprotocol( + &randomness, + &mut subclaim, + &evals, + &instance_info, + eq_at_u_r, + ); + assert!(check_subcliam && subclaim.expected_evaluations == ef_zero); + let iop_verifier_time = verifier_start.elapsed().as_millis(); + + // 3.5 and also check the relation between these small oracles and the committed oracle + let start = Instant::now(); + let mut pcs_proof_size = 0; + let flatten_evals = evals.flatten(); + let oracle_randomness = verifier_trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + evals.log_num_oracles(), + ); + let check_oracle = verify_oracle_relation(&flatten_evals, oracle_eval, &oracle_randomness); + assert!(check_oracle); + + // 3.5 Check the evaluation of a random point over the committed oracle + + let check_pcs = BrakedownPCS::, ExpanderCodeSpec, EF>::verify( + &pp, + &comm, + &requested_point, + oracle_eval, + &eval_proof, + &mut verifier_trans, + ); + assert!(check_pcs); + let pcs_verifier_time = start.elapsed().as_millis(); + pcs_proof_size += bincode::serialize(&eval_proof).unwrap().len() + + bincode::serialize(&flatten_evals).unwrap().len(); + + print_statistic( + iop_prover_time + pcs_open_time, + iop_verifier_time + pcs_verifier_time, + iop_proof_size + pcs_proof_size, + iop_prover_time, + iop_verifier_time, + iop_proof_size, + committed_poly.num_vars, + instance.num_oracles(), + instance.num_vars, + setup_time, + commit_time, + pcs_open_time, + pcs_verifier_time, + pcs_proof_size, + ) +} + +#[test] +fn test_snarks_interface() { + let base_len = 4; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = 10; + + let mut rng = thread_rng(); + let uniform = >::new(); + let d = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..(1 << num_vars)) + .map(|_| uniform.sample(&mut rng)) + .collect(), + )); + + let d_bits_prover = d.get_decomposed_mles(base_len, bits_len); + + let mut instance = DecomposedBits::new(base, base_len, bits_len, num_vars); + instance.add_decomposed_bits_instance(&d, &d_bits_prover); - let u: Vec<_> = (0..num_vars).map(|_| uniform.sample(&mut rng)).collect(); - let proof = BitDecomposition::prove(&decomposed_bits, &u); - let subclaim = BitDecomposition::verifier(&proof, &decomposed_bits_info); - assert!(subclaim.verify_subclaim(&d, &d_bits_ref, &u, &decomposed_bits_info)); + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); } diff --git a/zkp/tests/test_ntt.rs b/zkp/tests/test_ntt.rs index 528808d1..4c679735 100644 --- a/zkp/tests/test_ntt.rs +++ b/zkp/tests/test_ntt.rs @@ -1,26 +1,23 @@ +use algebra::{transformation::AbstractNTT, NTTField, Polynomial}; use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - DenseMultilinearExtension, Field, FieldUniformSampler, NTTPolynomial, + BabyBear, BabyBearExetension, DecomposableField, DenseMultilinearExtension, Field, + MultilinearExtension, NTTPolynomial, }; -use algebra::{transformation::AbstractNTT, NTTField, Polynomial}; use num_traits::{One, Zero}; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; use rand::prelude::*; -use rand_distr::Distribution; +use sha2::Sha256; use std::rc::Rc; use std::vec; use zkp::piop::ntt::ntt_bare::init_fourier_table; +use zkp::piop::ntt::{NTTInstances, NTTSnarks}; use zkp::piop::{NTTBareIOP, NTTInstance, NTTIOP}; -#[derive(Field, Prime, DecomposableField, FheField, NTT)] -#[modulus = 132120577] -pub struct Fp32(u32); - -#[derive(Field, Prime)] -#[modulus = 59] -pub struct Fq(u32); - // field type -type FF = Fp32; +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; type PolyFF = Polynomial; fn obtain_fourier_matrix_oracle(log_n: u32) -> DenseMultilinearExtension { @@ -130,6 +127,26 @@ fn naive_ntt_transform_normal_order(log_n: u32, coeff: &[FF]) -> Vec { ntt_form } +fn generate_single_instance( + instances: &mut NTTInstances, + log_n: usize, + rng: &mut R, +) { + let coeff = PolyFF::random(1 << log_n, rng).data(); + let point = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + log_n, + ntt_transform_normal_order(log_n as u32, &coeff) + .iter() + .map(|x| FF::new(x.value())) + .collect(), + )); + let coeff = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + log_n, + coeff.iter().map(|x| FF::new(x.value())).collect(), + )); + instances.add_ntt(&coeff, &point); +} + #[test] fn test_reverse_bits() { assert_eq!(2, reverse_bits(4, 4)); @@ -182,7 +199,6 @@ fn test_ntt_bare_without_delegation() { let ntt_table = Rc::new(ntt_table); let mut rng = thread_rng(); - let uniform = >::new(); let coeff = PolyFF::random(1 << log_n, &mut rng).data(); let points = Rc::new(DenseMultilinearExtension::from_evaluations_vec( log_n, @@ -195,18 +211,24 @@ fn test_ntt_bare_without_delegation() { let ntt_instance = NTTInstance::from_slice(log_n, &ntt_table, &coeff, &points); let ntt_instance_info = ntt_instance.info(); - let u: Vec<_> = (0..log_n).map(|_| uniform.sample(&mut rng)).collect(); - let f_u = Rc::new(init_fourier_table(&u, &ntt_instance.ntt_table)); - let proof = NTTBareIOP::prove(&ntt_instance, &f_u, &u); - let subclaim = NTTBareIOP::verify(&proof, &ntt_instance_info); + let kit = NTTBareIOP::prove(&ntt_instance); + let evals_at_u = ntt_instance.points.evaluate(&kit.u); + let evals_at_r = ntt_instance.coeffs.evaluate(&kit.randomness); + + let f_u = init_fourier_table(&kit.u, &ntt_instance.ntt_table); + let f_delegation = f_u.evaluate(&kit.randomness); + let f_oracle = obtain_fourier_matrix_oracle(log_n as u32); + let point = [kit.u.clone(), kit.randomness.clone()].concat(); + assert_eq!(f_oracle.evaluate(&point), f_delegation); + + let mut wrapper = kit.extract(); - // Without delegation, the verifier needs to compute F(u, v) on its own. - let fourier_matrix = Rc::new(obtain_fourier_matrix_oracle(log_n as u32)); - assert!(subclaim.verify_subclaim(&fourier_matrix, &points, &coeff, &u, &ntt_instance_info)); + let check = NTTBareIOP::verify(&mut wrapper, evals_at_r, evals_at_u, &ntt_instance_info); + assert!(check); } #[test] -fn test_ntt_with_delegation() { +fn test_ntt_bare_without_delegation_extension_field() { let log_n: usize = 10; let m = 1 << (log_n + 1); let mut ntt_table = Vec::with_capacity(m as usize); @@ -219,7 +241,6 @@ fn test_ntt_with_delegation() { let ntt_table = Rc::new(ntt_table); let mut rng = thread_rng(); - let uniform = >::new(); let coeff = PolyFF::random(1 << log_n, &mut rng).data(); let points = Rc::new(DenseMultilinearExtension::from_evaluations_vec( log_n, @@ -230,18 +251,23 @@ fn test_ntt_with_delegation() { )); let ntt_instance = NTTInstance::from_slice(log_n, &ntt_table, &coeff, &points); - let ntt_instance_info = ntt_instance.info(); - let u: Vec<_> = (0..log_n).map(|_| uniform.sample(&mut rng)).collect(); - let proof = NTTIOP::prove(&ntt_instance, &u); - let subclaim = NTTIOP::verify(&proof, &ntt_instance_info, &u); + let instance_ef = ntt_instance.to_ef::(); + let ntt_instance_info = instance_ef.info(); - assert!(subclaim.verify_subcliam(&points, &coeff, &u, &ntt_instance_info)); + let kit = NTTBareIOP::::prove(&instance_ef); + let evals_at_u = ntt_instance.points.evaluate_ext(&kit.u); + let evals_at_r = ntt_instance.coeffs.evaluate_ext(&kit.randomness); + + let mut wrapper = kit.extract(); + + let check = NTTBareIOP::::verify(&mut wrapper, evals_at_r, evals_at_u, &ntt_instance_info); + assert!(check); } #[test] -fn test_ntt_combined_with_delegation() { - let log_n: usize = 5; +fn test_ntt_with_delegation() { + let log_n: usize = 10; let m = 1 << (log_n + 1); let mut ntt_table = Vec::with_capacity(m as usize); let root = FF::get_ntt_table(log_n as u32).unwrap().root(); @@ -253,41 +279,104 @@ fn test_ntt_combined_with_delegation() { let ntt_table = Rc::new(ntt_table); let mut rng = thread_rng(); - let uniform = >::new(); - let mut coeff1 = PolyFF::random(1 << log_n, &mut rng); - let points1 = DenseMultilinearExtension::from_evaluations_vec( + let coeff = PolyFF::random(1 << log_n, &mut rng).data(); + let points = Rc::new(DenseMultilinearExtension::from_evaluations_vec( log_n, - ntt_transform_normal_order(log_n as u32, coeff1.as_ref()), - ); + ntt_transform_normal_order(log_n as u32, &coeff), + )); + let coeff = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + log_n, coeff, + )); - let mut coeff2 = PolyFF::random(1 << log_n, &mut rng); - let points2 = DenseMultilinearExtension::from_evaluations_vec( - log_n, - ntt_transform_normal_order(log_n as u32, coeff2.as_ref()), + let ntt_instance = NTTInstance::from_slice(log_n, &ntt_table, &coeff, &points); + let ntt_instance_info = ntt_instance.info(); + + let (kit, recursive_proof) = NTTIOP::prove(&ntt_instance); + let evals_at_r = ntt_instance.coeffs.evaluate(&kit.randomness); + let evals_at_u = ntt_instance.points.evaluate(&kit.u); + + let mut wrapper = kit.extract(); + + let check = NTTIOP::verify( + &mut wrapper, + evals_at_r, + evals_at_u, + &ntt_instance_info, + &recursive_proof, ); + assert!(check); +} - let r_1 = uniform.sample(&mut rng); - let r_2 = uniform.sample(&mut rng); - coeff1.mul_scalar_assign(r_1); - coeff2.mul_scalar_assign(r_2); - let coeff = coeff1 + coeff2; +#[test] +fn test_ntt_with_delegation_extension_field() { + let log_n: usize = 10; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Rc::new(ntt_table); - let coeff = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + let mut rng = thread_rng(); + let coeff = PolyFF::random(1 << log_n, &mut rng).data(); + let points = Rc::new(DenseMultilinearExtension::from_evaluations_vec( log_n, - coeff.data(), + ntt_transform_normal_order(log_n as u32, &coeff), + )); + let coeff = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + log_n, coeff, )); - let mut points = - >::from_evaluations_vec(log_n, vec![FF::zero(); 1 << log_n]); - points += (r_1, &points1); - points += (r_2, &points2); - let points = Rc::new(points); let ntt_instance = NTTInstance::from_slice(log_n, &ntt_table, &coeff, &points); - let ntt_instance_info = ntt_instance.info(); - let u: Vec<_> = (0..log_n).map(|_| uniform.sample(&mut rng)).collect(); - let proof = NTTIOP::prove(&ntt_instance, &u); - let subclaim = NTTIOP::verify(&proof, &ntt_instance_info, &u); + let instance_ef = ntt_instance.to_ef::(); + let ntt_instance_info = instance_ef.info(); + + let (kit, recursive_proof) = NTTIOP::prove(&instance_ef); + let evals_at_r = ntt_instance.coeffs.evaluate_ext(&kit.randomness); + let evals_at_u = ntt_instance.points.evaluate_ext(&kit.u); + + let mut wrapper = kit.extract(); + let check = NTTIOP::verify( + &mut wrapper, + evals_at_r, + evals_at_u, + &ntt_instance_info, + &recursive_proof, + ); + assert!(check); +} + +#[test] +fn test_snarks() { + let num_vars = 10; + let num_ntt = 5; + let log_n: usize = num_vars; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); - assert!(subclaim.verify_subcliam(&points, &coeff, &u, &ntt_instance_info)); + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Rc::new(ntt_table); + + let mut rng = thread_rng(); + + let mut ntt_instances = >::new(num_vars, &ntt_table); + for _ in 0..num_ntt { + generate_single_instance(&mut ntt_instances, log_n, &mut rng); + } + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + >::snarks::, ExpanderCodeSpec>( + &ntt_instances, + &code_spec, + ); } diff --git a/zkp/tests/test_range_check.rs b/zkp/tests/test_range_check.rs new file mode 100644 index 00000000..bc67f7d5 --- /dev/null +++ b/zkp/tests/test_range_check.rs @@ -0,0 +1,178 @@ +use algebra::{ + derive::{DecomposableField, Field, Prime}, + BabyBear, BabyBearExetension, DenseMultilinearExtension, Field, +}; +use num_traits::Zero; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use sha2::Sha256; +use std::rc::Rc; +use std::vec; +use zkp::{ + piop::{Lookup, LookupInstance, LookupSnarks}, + utils::compute_rangecheck_m, +}; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +// #[derive(Field, DecomposableField, Prime)] +// #[modulus = 132120577] +// pub struct Fp32(u32); +// // field type +// type FF = Fp32; + +#[derive(Field, DecomposableField, Prime)] +#[modulus = 59] +pub struct Fq(u32); + +macro_rules! field_vec { + ($t:ty; $elem:expr; $n:expr)=>{ + vec![<$t>::new($elem);$n] + }; + ($t:ty; $($x:expr),+ $(,)?) => { + vec![$(<$t>::new($x)),+] + } +} + +#[test] +fn test_trivial_range_check() { + // prepare parameters + + let num_vars = 4; + let block_size = 2; + let range: usize = 6; + + // construct a trivial example + + let f0 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 1, 4, 5, 2, 3, 0, 1, 1, 3, 2, 1, 0, 4, 1, 1, 0), + )); + let f1 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 4, 2, 5, 3, 4, 0, 1, 4, 3, 2, 1, 0, 4, 1, 1, 3), + )); + let f2 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 4, 5, 1, 2, 3, 0, 1, 1, 3, 2, 1, 0, 4, 1, 1, 1), + )); + let f3 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 4, 5, 5, 2, 4, 0, 1, 2, 3, 2, 1, 0, 3, 1, 1, 1), + )); + let f4 = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 4, 1, 5, 2, 4, 0, 1, 3, 3, 2, 1, 0, 5, 1, 1, 2), + )); + + let f_vec = vec![f0, f1, f2, f3, f4]; + + let mut t_evaluations: Vec<_> = (0..range).map(|i| FF::new(i as u32)).collect(); + t_evaluations.resize(1 << num_vars, FF::zero()); + let t = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + t_evaluations, + )); + + let m = compute_rangecheck_m(&f_vec, range); + + let mut instance = LookupInstance::::from_slice(&f_vec, t.clone(), m, block_size); + let info = instance.info(); + + let kit = Lookup::::prove(&mut instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let check = Lookup::::verify(&wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_random_range_check() { + // prepare parameters + + let num_vars = 8; + let block_size = 4; + let block_num = 5; + let residual_size = 1; + let lookup_num = block_num * block_size + residual_size; + let range = 59; + + let mut rng = thread_rng(); + let f_vec: Vec>> = (0..lookup_num) + .map(|_| { + let f_evaluations: Vec = (0..(1 << num_vars)) + .map(|_| FF::new(rng.gen_range(0..range))) + .collect(); + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + f_evaluations, + )) + }) + .collect(); + + let mut t_evaluations: Vec<_> = (0..range as usize).map(|i| FF::new(i as u32)).collect(); + t_evaluations.resize(1 << num_vars, FF::zero()); + let t = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + t_evaluations, + )); + + let m = compute_rangecheck_m(&f_vec, range as usize); + + let mut instance = LookupInstance::::from_slice(&f_vec, t.clone(), m, block_size); + let info = instance.info(); + + let kit = Lookup::::prove(&mut instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let check = Lookup::::verify(&wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_snark() { + // prepare parameters + + let num_vars = 8; + let block_size = 4; + let block_num = 5; + let residual_size = 1; + let lookup_num = block_num * block_size + residual_size; + let range = 59; + + let mut rng = thread_rng(); + let f_vec: Vec>> = (0..lookup_num) + .map(|_| { + let f_evaluations: Vec = (0..(1 << num_vars)) + .map(|_| FF::new(rng.gen_range(0..range))) + .collect(); + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + f_evaluations, + )) + }) + .collect(); + + let mut t_evaluations: Vec<_> = (0..range as usize).map(|i| FF::new(i as u32)).collect(); + t_evaluations.resize(1 << num_vars, FF::zero()); + let t = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + t_evaluations, + )); + + let m = compute_rangecheck_m(&f_vec, range as usize); + + let instance = LookupInstance::::from_slice(&f_vec, t.clone(), m, block_size); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); +} diff --git a/zkp/tests/test_rlwe_mult_rgsw.rs b/zkp/tests/test_rlwe_mult_rgsw.rs index 6e75f3ce..3dd439fc 100644 --- a/zkp/tests/test_rlwe_mult_rgsw.rs +++ b/zkp/tests/test_rlwe_mult_rgsw.rs @@ -1,24 +1,24 @@ +use algebra::{transformation::AbstractNTT, NTTField, Polynomial}; use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - Basis, DenseMultilinearExtension, Field, FieldUniformSampler, + BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension, Field, FieldUniformSampler, }; -use algebra::{transformation::AbstractNTT, NTTField, NTTPolynomial, Polynomial}; use itertools::izip; use num_traits::One; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; use rand_distr::Distribution; +use sha2::Sha256; use std::rc::Rc; use std::vec; use zkp::piop::{ - DecomposedBitsInfo, NTTInstanceInfo, RlweCiphertext, RlweCiphertexts, RlweMultRgswIOP, - RlweMultRgswInstance, + rlwe_mul_rgsw::RlweMultRgswSnarks, DecomposedBitsInfo, NTTInstanceInfo, RlweCiphertext, + RlweCiphertexts, RlweMultRgswIOP, RlweMultRgswInstance, }; -#[derive(Field, Prime, DecomposableField, FheField, NTT)] -#[modulus = 132120577] -pub struct Fp32(u32); - // field type -type FF = Fp32; +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; /// Given an `index` of `len` bits, output a new index where the bits are reversed. fn reverse_bits(index: usize, len: u32) -> usize { @@ -60,40 +60,29 @@ fn ntt_transform_normal_order(log_n: u32, coeff: &[F]) -> V sort_array_with_reversed_bits(&ntt_form, log_n) } -fn ntt_inverse_transform_normal_order(log_n: u32, points: &[F]) -> Vec { - assert_eq!(points.len(), (1 << log_n) as usize); - let reversed_points = sort_array_with_reversed_bits(points, log_n); - let ntt_poly = >::from_slice(&reversed_points); - F::get_ntt_table(log_n) - .unwrap() - .inverse_transform(&ntt_poly) - .data() -} - /// This function DOES NOT implement the real functionality of multiplication between RLWE ciphertext and RGSW ciphertext /// It is only used to generate the consistent instance for test. /// /// # Arguments: /// * input_rlwe: RLWE ciphertext of the coefficient form /// * input_rgsw: RGSW ciphertext of the ntt form -/// * basis_info: information used to decompose bits +/// * bits_info: information used to decompose bits /// * ntt_info: information used to perform NTT -/// * randomness_ntt: randomness used to generate a single randomized NTT instance -fn gen_rlwe_mult_rgsw_instance( +fn generate_rlwe_mult_rgsw_instance( + num_vars: usize, input_rlwe: RlweCiphertext, input_rgsw: (RlweCiphertexts, RlweCiphertexts), - basis_info: &DecomposedBitsInfo, + bits_info: &DecomposedBitsInfo, ntt_info: &NTTInstanceInfo, - randomness_ntt: &[F], ) -> RlweMultRgswInstance { // 1. Decompose the input of RLWE ciphertex let bits_rlwe = RlweCiphertexts { a_bits: input_rlwe .a - .get_decomposed_mles(basis_info.base_len, basis_info.bits_len), + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len), b_bits: input_rlwe .b - .get_decomposed_mles(basis_info.base_len, basis_info.bits_len), + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len), }; let (bits_rgsw_c_ntt, bits_rgsw_f_ntt) = input_rgsw; @@ -104,8 +93,8 @@ fn gen_rlwe_mult_rgsw_instance( .iter() .map(|bit| { Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_transform_normal_order(ntt_info.log_n as u32, &bit.evaluations), + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), )) }) .collect(), @@ -114,8 +103,8 @@ fn gen_rlwe_mult_rgsw_instance( .iter() .map(|bit| { Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_transform_normal_order(ntt_info.log_n as u32, &bit.evaluations), + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), )) }) .collect(), @@ -126,7 +115,7 @@ fn gen_rlwe_mult_rgsw_instance( assert_eq!(bits_rlwe_ntt.a_bits.len(), bits_rgsw_f_ntt.a_bits.len()); // 3. Compute the output of ntt form with the RGSW ciphertext and the decomposed bits of ntt form - let mut output_g_ntt = vec![F::zero(); 1 << ntt_info.log_n]; + let mut output_g_ntt = vec![F::zero(); 1 << num_vars]; for (a, b, c, f) in izip!( &bits_rlwe_ntt.a_bits, &bits_rlwe_ntt.b_bits, @@ -138,7 +127,7 @@ fn gen_rlwe_mult_rgsw_instance( }); } - let mut output_h_ntt = vec![F::zero(); 1 << ntt_info.log_n]; + let mut output_h_ntt = vec![F::zero(); 1 << num_vars]; for (a, b, c, f) in izip!( &bits_rlwe_ntt.a_bits, &bits_rlwe_ntt.b_bits, @@ -151,58 +140,153 @@ fn gen_rlwe_mult_rgsw_instance( } // 4. Compute the output of coefficient form - let output_rlwe = RlweCiphertext { - a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_inverse_transform_normal_order(ntt_info.log_n as u32, &output_g_ntt), - )), - b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - ntt_inverse_transform_normal_order(ntt_info.log_n as u32, &output_h_ntt), - )), - }; + // let output_rlwe = RlweCiphertext { + // a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + // ntt_info.log_n, + // ntt_inverse_transform_normal_order(ntt_info.log_n as u32, &output_g_ntt), + // )), + // b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( + // ntt_info.log_n, + // ntt_inverse_transform_normal_order(ntt_info.log_n as u32, &output_h_ntt), + // )), + // }; let output_rlwe_ntt = RlweCiphertext { a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, + num_vars, output_g_ntt, )), b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, + num_vars, output_h_ntt, )), }; - RlweMultRgswInstance::from( - basis_info, + RlweMultRgswInstance::new( + num_vars, + bits_info, ntt_info, - randomness_ntt, - &input_rlwe, - &bits_rlwe, - &bits_rlwe_ntt, - &bits_rgsw_c_ntt, - &bits_rgsw_f_ntt, - &output_rlwe_ntt, - &output_rlwe, + input_rlwe, + bits_rlwe, + bits_rlwe_ntt, + bits_rgsw_c_ntt, + bits_rgsw_f_ntt, + output_rlwe_ntt, + // &output_rlwe, ) } #[test] -fn test_trivial_rlwe_mult_rgsw() { +fn test_random_rlwe_mult_rgsw() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + // information used to decompose bits + let base_len: usize = 2; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = 10; + let bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len, + num_vars, + num_instances: 0, + }; + + // information used to perform NTT + let log_n = num_vars; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Rc::new(ntt_table); + let ntt_info = NTTInstanceInfo { + num_vars, + ntt_table, + num_ntt: 0, + }; + + // generate random RGSW ciphertext = (bits_rgsw_c_ntt, bits_rgsw_f_ntt) \in RLWE' \times \RLWE' + let mut bits_rgsw_c_ntt = >::new(bits_len); + let points: Vec<_> = (0..1 << num_vars) + .map(|_| uniform.sample(&mut rng)) + .collect(); + let coeffs: Vec<_> = (0..1 << num_vars) + .map(|_| uniform.sample(&mut rng)) + .collect(); + for _ in 0..bits_len { + bits_rgsw_c_ntt.add_rlwe( + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + ); + } + + let mut bits_rgsw_f_ntt = >::new(bits_len); + for _ in 0..bits_len { + bits_rgsw_f_ntt.add_rlwe( + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + ); + } + + // generate the random RLWE ciphertext + let input_rlwe = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_slice( + log_n, &coeffs, + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_slice( + log_n, &coeffs, + )), + }; + + // generate all the witness required + let instance = generate_rlwe_mult_rgsw_instance( + num_vars, + input_rlwe, + (bits_rgsw_c_ntt, bits_rgsw_f_ntt), + &bits_info, + &ntt_info, + ); + + let info = instance.info(); + + let (kit, recursive_proof) = RlweMultRgswIOP::::prove(&instance); + let evals_at_r = instance.evaluate(&kit.randomness); + let evals_at_u = instance.evaluate(&kit.u); + + let mut wrapper = kit.extract(); + let check = RlweMultRgswIOP::::verify( + &mut wrapper, + &evals_at_r, + &evals_at_u, + &info, + &recursive_proof, + ); + + assert!(check); +} + +#[test] +fn test_random_rlwe_mult_rgsw_extension_field() { let mut rng = rand::thread_rng(); let uniform = >::new(); // information used to decompose bits - let base_len: u32 = 2; + let base_len: usize = 2; let base: FF = FF::new(1 << base_len); - let bits_len: u32 = >::new(base_len).decompose_len() as u32; + let bits_len = >::new(base_len as u32).decompose_len(); let num_vars = 10; - let basis_info = DecomposedBitsInfo { + let bits_info = DecomposedBitsInfo { base, base_len, bits_len, num_vars, - num_instances: 2, + num_instances: 0, }; // information used to perform NTT @@ -216,10 +300,14 @@ fn test_trivial_rlwe_mult_rgsw() { power *= root; } let ntt_table = Rc::new(ntt_table); - let ntt_info = NTTInstanceInfo { log_n, ntt_table }; + let ntt_info = NTTInstanceInfo { + num_vars, + ntt_table, + num_ntt: 0, + }; // generate random RGSW ciphertext = (bits_rgsw_c_ntt, bits_rgsw_f_ntt) \in RLWE' \times \RLWE' - let mut bits_rgsw_c_ntt = >::new(bits_len as usize); + let mut bits_rgsw_c_ntt = >::new(bits_len); let points: Vec<_> = (0..1 << num_vars) .map(|_| uniform.sample(&mut rng)) .collect(); @@ -233,7 +321,7 @@ fn test_trivial_rlwe_mult_rgsw() { ); } - let mut bits_rgsw_f_ntt = >::new(bits_len as usize); + let mut bits_rgsw_f_ntt = >::new(bits_len); for _ in 0..bits_len { bits_rgsw_f_ntt.add_rlwe( DenseMultilinearExtension::from_evaluations_slice(log_n, &points), @@ -251,43 +339,113 @@ fn test_trivial_rlwe_mult_rgsw() { )), }; - let num_ntt_instance = (basis_info.bits_len << 1) + 2; - let randomness_ntt = (0..num_ntt_instance) + // generate all the witness required + let instance = generate_rlwe_mult_rgsw_instance( + num_vars, + input_rlwe, + (bits_rgsw_c_ntt, bits_rgsw_f_ntt), + &bits_info, + &ntt_info, + ); + + let instance_ef = instance.to_ef::(); + let info = instance_ef.info(); + + let (kit, recursive_proof) = RlweMultRgswIOP::::prove(&instance_ef); + let evals_at_r = instance.evaluate_ext(&kit.randomness); + let evals_at_u = instance.evaluate_ext(&kit.u); + + let mut wrapper = kit.extract(); + let check = RlweMultRgswIOP::::verify( + &mut wrapper, + &evals_at_r, + &evals_at_u, + &info, + &recursive_proof, + ); + + assert!(check); +} + +#[test] +fn test_snarks() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + // information used to decompose bits + let base_len: usize = 2; + let base: FF = FF::new(1 << base_len); + let bits_len = >::new(base_len as u32).decompose_len(); + let num_vars = 10; + let bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len, + num_vars, + num_instances: 0, + }; + + // information used to perform NTT + let log_n = num_vars; + let m = 1 << (log_n + 1); + let mut ntt_table = Vec::with_capacity(m as usize); + let root = FF::get_ntt_table(log_n as u32).unwrap().root(); + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Rc::new(ntt_table); + let ntt_info = NTTInstanceInfo { + num_vars, + ntt_table, + num_ntt: 0, + }; + + // generate random RGSW ciphertext = (bits_rgsw_c_ntt, bits_rgsw_f_ntt) \in RLWE' \times \RLWE' + let mut bits_rgsw_c_ntt = >::new(bits_len); + let points: Vec<_> = (0..1 << num_vars) .map(|_| uniform.sample(&mut rng)) - .collect::>(); + .collect(); + let coeffs: Vec<_> = (0..1 << num_vars) + .map(|_| uniform.sample(&mut rng)) + .collect(); + for _ in 0..bits_len { + bits_rgsw_c_ntt.add_rlwe( + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + ); + } + + let mut bits_rgsw_f_ntt = >::new(bits_len); + for _ in 0..bits_len { + bits_rgsw_f_ntt.add_rlwe( + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + ); + } + + // generate the random RLWE ciphertext + let input_rlwe = RlweCiphertext { + a: Rc::new(DenseMultilinearExtension::from_evaluations_slice( + log_n, &coeffs, + )), + b: Rc::new(DenseMultilinearExtension::from_evaluations_slice( + log_n, &coeffs, + )), + }; // generate all the witness required - let instance = gen_rlwe_mult_rgsw_instance( + let instance = generate_rlwe_mult_rgsw_instance( + num_vars, input_rlwe, (bits_rgsw_c_ntt, bits_rgsw_f_ntt), - &basis_info, + &bits_info, &ntt_info, - &randomness_ntt, ); - // check the consistency of the randomized NTT instance - let ntt_points = - ntt_transform_normal_order(log_n as u32, &instance.ntt_instance.coeffs.evaluations); - assert_eq!(ntt_points, instance.ntt_instance.points.evaluations); - - let instance_info = instance.info(); - - let u: Vec<_> = (0..num_vars).map(|_| uniform.sample(&mut rng)).collect(); - let proof = RlweMultRgswIOP::prove(&instance, &u); - - let subclaim = RlweMultRgswIOP::verify(&proof, &randomness_ntt, &u, &instance_info); - assert!(subclaim.verify_subclaim( - &u, - &randomness_ntt, - &instance.ntt_instance.coeffs, - &instance.ntt_instance.points, - &instance.input_rlwe, - &instance.bits_rlwe, - &instance.bits_rlwe_ntt, - &instance.bits_rgsw_c_ntt, - &instance.bits_rgsw_f_ntt, - &instance.output_rlwe_ntt, - &instance.output_rlwe, - &instance_info - )); + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); } diff --git a/zkp/tests/test_round.rs b/zkp/tests/test_round.rs index 4ec8258e..bdab74e2 100644 --- a/zkp/tests/test_round.rs +++ b/zkp/tests/test_round.rs @@ -1,20 +1,24 @@ use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - DecomposableField, DenseMultilinearExtension, Field, FieldUniformSampler, + BabyBear, BabyBearExetension, DecomposableField, DenseMultilinearExtension, Field, + FieldUniformSampler, }; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; use rand_distr::Distribution; +use sha2::Sha256; use std::rc::Rc; use std::vec; -use zkp::piop::{DecomposedBitsInfo, RoundIOP, RoundInstance}; +use zkp::piop::{round::RoundSnarks, DecomposedBitsInfo, RoundIOP, RoundInstance}; -#[derive(Field, Prime, DecomposableField, FheField, NTT)] -#[modulus = 132120577] -pub struct Fp32(u32); - -type FF = Fp32; // field type -const FP: u32 = 132120577; // ciphertext space +type FF = BabyBear; // field type +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; +const FP: u32 = FF::MODULUS_VALUE; // ciphertext space const FT: u32 = 4; // message space +const LOG_FT: u32 = FT.next_power_of_two().ilog2(); const FK: u32 = (FP - 1) / FT; +const LOG_FK: u32 = FK.next_power_of_two().ilog2(); +const DELTA: u32 = (1 << LOG_FK) - FK; macro_rules! field_vec { ($t:ty; $elem:expr; $n:expr)=>{ @@ -41,12 +45,11 @@ fn test_round() { #[test] fn test_round_naive_iop() { - // k = (132120577 - 1) / FT = 33030144 = 2^25 - 2^19 let k = FF::new(FK); - let k_bits_len: u32 = 25; - let delta: FF = FF::new(1 << 19); + let k_bits_len = LOG_FK as usize; + let delta: FF = FF::new(DELTA); - let base_len: u32 = 1; + let base_len = 1; let base: FF = FF::new(1 << base_len); let num_vars = 2; @@ -85,27 +88,13 @@ fn test_round_naive_iop() { let info = instance.info(); - let mut rng = rand::thread_rng(); - let uniform = >::new(); - let u: Vec<_> = (0..num_vars).map(|_| uniform.sample(&mut rng)).collect(); - let lambda_1 = uniform.sample(&mut rng); - let lambda_2 = uniform.sample(&mut rng); - - let proof = RoundIOP::prove(&instance, &u, (lambda_1, lambda_2)); - let subclaim = RoundIOP::verify(&proof, &instance.info()); - - assert!(subclaim.verify_subclaim( - &u, - (lambda_1, lambda_2), - &instance.input, - &instance.output, - &instance.output_bits.instances[0], - &instance.offset, - &instance.offset_aux_bits.instances[0], - &instance.offset_aux_bits.instances[1], - &instance.option, - &info - )) + let kit = RoundIOP::::prove(&instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let check = RoundIOP::::verify(&wrapper, &evals, &info); + + assert!(check); } #[test] @@ -113,12 +102,11 @@ fn test_round_random_iop() { let mut rng = rand::thread_rng(); let uniform = >::new(); - // k = (132120577 - 1) / FT = 33030144 = 2^25 - 2^19 let k = FF::new(FK); - let k_bits_len: u32 = 25; - let delta: FF = FF::new(1 << 19); + let k_bits_len = LOG_FK as usize; + let delta: FF = FF::new(DELTA); - let base_len: u32 = 1; + let base_len = 1; let base: FF = FF::new(1 << base_len); let num_vars = 10; @@ -159,23 +147,124 @@ fn test_round_random_iop() { let info = instance.info(); - let u: Vec<_> = (0..num_vars).map(|_| uniform.sample(&mut rng)).collect(); - let lambda_1 = uniform.sample(&mut rng); - let lambda_2 = uniform.sample(&mut rng); - - let proof = RoundIOP::prove(&instance, &u, (lambda_1, lambda_2)); - let subclaim = RoundIOP::verify(&proof, &instance.info()); - - assert!(subclaim.verify_subclaim( - &u, - (lambda_1, lambda_2), - &instance.input, - &instance.output, - &instance.output_bits.instances[0], - &instance.offset, - &instance.offset_aux_bits.instances[0], - &instance.offset_aux_bits.instances[1], - &instance.option, - &info - )) + let kit = RoundIOP::::prove(&instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let check = RoundIOP::::verify(&wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_round_random_iop_extension_field() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + let k = FF::new(FK); + let k_bits_len = LOG_FK as usize; + let delta: FF = FF::new(DELTA); + + let base_len = 1; + let base: FF = FF::new(1 << base_len); + let num_vars = 10; + + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..1 << num_vars) + .map(|_| uniform.sample(&mut rng)) + .collect(), + )); + let output = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + input.iter().map(|x| FF::new(decode(*x))).collect(), + )); + let output_bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len: 2, + num_vars, + num_instances: 1, + }; + + let offset_bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + let instance = >::new( + k, + delta, + input, + output, + &output_bits_info, + &offset_bits_info, + ); + + let instance_ef = instance.to_ef::(); + let info = instance_ef.info(); + + let kit = RoundIOP::::prove(&instance_ef); + let evals = instance.evaluate_ext(&kit.randomness); + + let wrapper = kit.extract(); + let check = RoundIOP::::verify(&wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_snarks() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + let k = FF::new(FK); + let delta: FF = FF::new(DELTA); + + let base_len = 1; + let base: FF = FF::new(1 << base_len); + let num_vars = 10; + + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..1 << num_vars) + .map(|_| uniform.sample(&mut rng)) + .collect(), + )); + let output = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + input.iter().map(|x| FF::new(decode(*x))).collect(), + )); + let output_bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len: LOG_FT as usize, + num_vars, + num_instances: 1, + }; + + let offset_bits_info = DecomposedBitsInfo { + base, + base_len, + bits_len: LOG_FK as usize, + num_vars, + num_instances: 2, + }; + + let instance = >::new( + k, + delta, + input, + output, + &output_bits_info, + &offset_bits_info, + ); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + >::snarks::, ExpanderCodeSpec>( + &instance, &code_spec, + ); } diff --git a/zkp/tests/test_sumcheck.rs b/zkp/tests/test_sumcheck.rs index ed50c1a8..2f63ddb3 100644 --- a/zkp/tests/test_sumcheck.rs +++ b/zkp/tests/test_sumcheck.rs @@ -1,21 +1,16 @@ use algebra::{ - derive::{Field, Prime}, - DenseMultilinearExtension, Field, FieldUniformSampler, ListOfProductsOfPolynomials, - MultilinearExtension, + utils::Transcript, BabyBear, DenseMultilinearExtension, Field, FieldUniformSampler, + ListOfProductsOfPolynomials, MultilinearExtension, }; use rand::prelude::*; -use rand_chacha::ChaCha12Rng; use rand_distr::Distribution; +use serde::Serialize; use std::rc::Rc; use zkp::sumcheck::IPForMLSumcheck; use zkp::sumcheck::MLSumcheck; -#[derive(Field, Prime)] -#[modulus = 132120577] -pub struct Fp32(u32); - // field type -type FF = Fp32; +type FF = BabyBear; fn random_product( nv: usize, @@ -69,18 +64,24 @@ fn random_list_of_products( (poly, sum) } -fn test_protocol(nv: usize, num_multiplicands_range: (usize, usize), num_products: usize) { +fn test_protocol( + nv: usize, + num_multiplicands_range: (usize, usize), + num_products: usize, +) { let mut rng = thread_rng(); let (poly, asserted_sum) = - random_list_of_products::(nv, num_multiplicands_range, num_products, &mut rng); + random_list_of_products::(nv, num_multiplicands_range, num_products, &mut rng); let poly_info = poly.info(); let mut prover_state = IPForMLSumcheck::prover_init(&poly); let mut verifier_state = IPForMLSumcheck::verifier_init(&poly_info); let mut verifier_msg = None; + let mut verifier_trans = Transcript::::new(); for _ in 0..poly.num_variables { let prover_message = IPForMLSumcheck::prove_round(&mut prover_state, &verifier_msg); - verifier_msg = IPForMLSumcheck::verify_round(prover_message, &mut verifier_state, &mut rng); + verifier_msg = + IPForMLSumcheck::verify_round(prover_message, &mut verifier_state, &mut verifier_trans); } let subclaim = IPForMLSumcheck::check_and_generate_subclaim(verifier_state, asserted_sum) .expect("fail to generate subclaim"); @@ -103,21 +104,21 @@ fn test_polynomial(nv: usize, num_multiplicands_range: (usize, usize), num_produ ); } -fn test_polynomial_as_subprotocol( +fn test_polynomial_as_subprotocol( nv: usize, num_multiplicands_range: (usize, usize), num_products: usize, - prover_rng: &mut impl RngCore, - verifier_rng: &mut impl RngCore, + prover_trans: &mut Transcript, + verifier_trans: &mut Transcript, ) { let mut rng = thread_rng(); let (poly, asserted_sum) = - random_list_of_products::(nv, num_multiplicands_range, num_products, &mut rng); + random_list_of_products::(nv, num_multiplicands_range, num_products, &mut rng); let poly_info = poly.info(); let (proof, prover_state) = - MLSumcheck::prove_as_subprotocol(prover_rng, &poly).expect("fail to prove"); + MLSumcheck::prove_as_subprotocol(prover_trans, &poly).expect("fail to prove"); let subclaim = - MLSumcheck::verify_as_subprotocol(verifier_rng, &poly_info, asserted_sum, &proof) + MLSumcheck::verify_as_subprotocol(verifier_trans, &poly_info, asserted_sum, &proof) .expect("fail to verify"); assert!( poly.evaluate(&subclaim.point) == subclaim.expected_evaluations, @@ -133,20 +134,18 @@ fn test_trivial_polynomial() { let num_products = 5; for _ in 0..10 { - test_protocol(nv, num_multiplicands_range, num_products); + test_protocol::(nv, num_multiplicands_range, num_products); test_polynomial(nv, num_multiplicands_range, num_products); - let mut seed: ::Seed = Default::default(); - thread_rng().fill(&mut seed); - let mut prover_rng = ChaCha12Rng::from_seed(seed); - let mut verifier_rng = ChaCha12Rng::from_seed(seed); + let mut prover_trans = Transcript::new(); + let mut verifier_trans = Transcript::new(); - test_polynomial_as_subprotocol( + test_polynomial_as_subprotocol::( nv, num_multiplicands_range, num_products, - &mut prover_rng, - &mut verifier_rng, + &mut prover_trans, + &mut verifier_trans, ) } } @@ -158,20 +157,18 @@ fn test_normal_polynomial() { let num_products = 5; for _ in 0..10 { - test_protocol(nv, num_multiplicands_range, num_products); + test_protocol::(nv, num_multiplicands_range, num_products); test_polynomial(nv, num_multiplicands_range, num_products); - let mut seed: ::Seed = Default::default(); - thread_rng().fill(&mut seed); - let mut prover_rng = ChaCha12Rng::from_seed(seed); - let mut verifier_rng = ChaCha12Rng::from_seed(seed); + let mut prover_trans = Transcript::new(); + let mut verifier_trans = Transcript::new(); - test_polynomial_as_subprotocol( + test_polynomial_as_subprotocol::( nv, num_multiplicands_range, num_products, - &mut prover_rng, - &mut verifier_rng, + &mut prover_trans, + &mut verifier_trans, ) } } @@ -183,18 +180,16 @@ fn test_normal_polynomial_different_transcript_fails() { let num_multiplicands_range = (4, 9); let num_products = 5; - let mut prover_seed: ::Seed = Default::default(); - let mut verifier_seed: ::Seed = Default::default(); - thread_rng().fill(&mut prover_seed); - thread_rng().fill(&mut verifier_seed); - let mut prover_rng = ChaCha12Rng::from_seed(prover_seed); - let mut verifier_rng = ChaCha12Rng::from_seed(verifier_seed); - test_polynomial_as_subprotocol( + let mut prover_trans = Transcript::new(); + let mut verifier_trans = Transcript::new(); + verifier_trans.append_message(b"label", b"different transcript"); + + test_polynomial_as_subprotocol::( nv, num_multiplicands_range, num_products, - &mut prover_rng, - &mut verifier_rng, + &mut prover_trans, + &mut verifier_trans, ) } diff --git a/zkp/tests/test_zq_to_rq.rs b/zkp/tests/test_zq_to_rq.rs deleted file mode 100644 index 5522baba..00000000 --- a/zkp/tests/test_zq_to_rq.rs +++ /dev/null @@ -1,401 +0,0 @@ -use algebra::{ - derive::*, Basis, DecomposableField, DenseMultilinearExtension, Field, FieldUniformSampler, - SparsePolynomial, -}; -use num_traits::{One, Zero}; -use rand::prelude::*; -use rand_distr::Distribution; -use std::rc::Rc; -use std::vec; -use zkp::piop::zq_to_rq::{TransformZqtoRQ, TransformZqtoRQInstance}; - -#[derive(Field, Prime, DecomposableField)] -#[modulus = 132120577] -pub struct Fp32(u32); - -#[derive(Field, DecomposableField)] -#[modulus = 512] -pub struct Fq(u32); - -// field type -type FF = Fp32; - -macro_rules! field_vec { - ($t:ty; $elem:expr; $n:expr)=>{ - vec![<$t>::new($elem);$n] - }; - ($t:ty; $($x:expr),+ $(,)?) => { - vec![$(<$t>::new($x)),+] - } -} - -// a small and trivial example -// q = 8, Q = , N = 8 -// (2N/q) * a(x) = N * k(x) + r(x) -// k(x) * (1-k(x)) = 0 -// (r(x) + 1)(1 - 2k(x)) = s(x) -// \sum y C(u, y) * t(y) = s(u) - -// a = (0, 3, 5, 7) -// 2a = (0, 6, 10, 14) -// C = (1, 0, 0, 0, 0, 0, 0, 0) -// (0, 0, 0, 0, 0, 0, 1, 0) -// (0, 0, -1, 0, 0, 0, 0, 0) -// (0, 0, 0, 0, 0, 0, -1, 0) -// k = (0, 0, 1, 1) -// r = (0, 6, 2, 6) -// s = (1, 7, -3, -7) = (1, 7, 56, 52) - -#[test] -fn test_trivial_zq_to_rq() { - let mut rng = thread_rng(); - let sampler = >::new(); - let p = 132120577; - let q = 8; - let c_num_vars = 3; - let base_len: u32 = 1; - let base: FF = FF::new(2); - let num_vars = 2; - let bits_len: u32 = 3; - - let a = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - field_vec!(FF; 0, 3, 5, 7), - )); - - let k = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - field_vec!(FF; 0, 0, 1, 1), - )); - - let r = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - field_vec!(FF; 0, 6, 2, 6), - )); - - let s = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - field_vec!(FF; 1, 7, p-3, p-7), - )); - - let c = vec![ - Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(0, FF::one())], - )), - Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(6, FF::one())], - )), - Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(2, -FF::one())], - )), - Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(6, -FF::one())], - )), - ]; - - let c_dense = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - c_num_vars + num_vars, - field_vec!(FF; - 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 1, 0, - 0, 0, p-1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, p-1, 0), - )); - - let tmp = r.get_decomposed_mles(base_len, bits_len); - let r_bits = vec![&tmp]; - let instance = TransformZqtoRQInstance::from_vec( - q, - c.clone(), - a.clone(), - &k, - &r, - &s, - base, - base_len, - bits_len, - ); - let info = instance.info(); - let u: Vec<_> = (0..num_vars).map(|_| sampler.sample(&mut rng)).collect(); - let proof = TransformZqtoRQ::prove(&instance, &u); - let subclaim = TransformZqtoRQ::verify(&proof, &info.decomposed_bits_info, 3); - - assert!(subclaim.verify_subclaim( - q, - a, - &c_dense, - k.as_ref(), - vec![r].as_ref(), - s.as_ref(), - &r_bits, - &u, - &info - )); -} - -#[test] -fn test_random_zq_to_rq() { - let mut rng = thread_rng(); - let uniform_fq = >::new(); - let uniform_fp = >::new(); - let num_vars = 10; - let q = FF::new(Fq::MODULUS_VALUE); - let c_num_vars = (q.value() as usize).ilog2() as usize; - let base_len: u32 = 3; - let base: FF = FF::new(1 << base_len); - let bits_len: u32 = >::new(base_len).decompose_len() as u32; - - // generate a random instance - let a_over_fq: Vec<_> = (0..(1 << num_vars)) - .map(|_| uniform_fq.sample(&mut rng)) - .collect(); - let mut a = Vec::new(); - let mut k = Vec::new(); - let mut r = Vec::new(); - let mut s = Vec::new(); - let mut c = Vec::new(); - let mut c_dense_matrix = Vec::new(); - - a_over_fq.iter().for_each(|x| { - let mut x = FF::new(x.value()); - a.push(x); - x = FF::new(2) * x; - if x >= q { - k.push(FF::one()); - r.push(x - q); - s.push(-(x - q + FF::one())); - c.push(Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![((x - q).value() as usize, -FF::one())], - ))); - let mut c_dense_row = vec![FF::zero(); q.value() as usize]; - c_dense_row[(x - q).value() as usize] = -FF::one(); - c_dense_matrix.extend(c_dense_row); - } else { - k.push(FF::zero()); - r.push(x); - s.push(x + FF::one()); - c.push(Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(x.value() as usize, FF::one())], - ))); - let mut c_dense_row = vec![FF::zero(); q.value() as usize]; - c_dense_row[x.value() as usize] = FF::one(); - c_dense_matrix.extend(c_dense_row); - } - }); - - let a: Rc> = - Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, a)); - let k: Rc> = - Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, k)); - let r: Rc> = - Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, r)); - let s: Rc> = - Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, s)); - let c_dense = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars + c_num_vars, - c_dense_matrix, - )); - - let tmp = r.get_decomposed_mles(base_len, bits_len); - let r_bits: Vec<_> = vec![&tmp]; - let instance = TransformZqtoRQInstance::::from_vec( - q.value() as usize, - c.clone(), - a.clone(), - &k, - &r, - &s, - base, - base_len, - bits_len, - ); - let info = instance.info(); - let u: Vec<_> = (0..num_vars).map(|_| uniform_fp.sample(&mut rng)).collect(); - let proof = TransformZqtoRQ::prove(&instance, &u); - let subclaim = TransformZqtoRQ::verify(&proof, &info.decomposed_bits_info, c_num_vars); - - assert!(subclaim.verify_subclaim( - q.value() as usize, - a, - &c_dense, - k.as_ref(), - vec![r].as_ref(), - s.as_ref(), - &r_bits, - &u, - &info - )); -} - -#[test] -fn test_trivial_zq_to_rq_without_oracle() { - let mut rng = thread_rng(); - let sampler = >::new(); - let p = 132120577; - let q = 8; - let c_num_vars = 3; - let base_len: u32 = 1; - let base: FF = FF::new(2); - let num_vars = 2; - let bits_len: u32 = 3; - - let a = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - field_vec!(FF; 0, 3, 5, 7), - )); - - let k = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - field_vec!(FF; 0, 0, 1, 1), - )); - - let r = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - field_vec!(FF; 0, 6, 2, 6), - )); - - let s = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - num_vars, - field_vec!(FF; 1, 7, p-3, p-7), - )); - - let c_sparse = vec![ - Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(0, FF::one())], - )), - Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(6, FF::one())], - )), - Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(2, -FF::one())], - )), - Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(6, -FF::one())], - )), - ]; - - let tmp = r.get_decomposed_mles(base_len, bits_len); - let r_bits = vec![&tmp]; - let instance = TransformZqtoRQInstance::from_vec( - q, - c_sparse.clone(), - a.clone(), - &k, - &r, - &s, - base, - base_len, - bits_len, - ); - let info = instance.info(); - let u: Vec<_> = (0..num_vars).map(|_| sampler.sample(&mut rng)).collect(); - let proof = TransformZqtoRQ::prove(&instance, &u); - let subclaim = TransformZqtoRQ::verify(&proof, &info.decomposed_bits_info, 3); - - assert!(subclaim.verify_subclaim_without_oracle( - q, - a, - &c_sparse, - k.as_ref(), - vec![r].as_ref(), - s.as_ref(), - &r_bits, - &u, - &info - )); -} - -#[test] -fn test_random_zq_to_rq_without_oracle() { - let mut rng = thread_rng(); - let uniform_fq = >::new(); - let uniform_fp = >::new(); - let num_vars = 10; - let q = FF::new(Fq::MODULUS_VALUE); - let c_num_vars = (q.value() as usize).ilog2() as usize; - let base_len: u32 = 3; - let base: FF = FF::new(1 << base_len); - let bits_len: u32 = >::new(base_len).decompose_len() as u32; - - // generate a random instance - let a_over_fq: Vec<_> = (0..(1 << num_vars)) - .map(|_| uniform_fq.sample(&mut rng)) - .collect(); - let mut a = Vec::new(); - let mut k = Vec::new(); - let mut r = Vec::new(); - let mut s = Vec::new(); - let mut c_sparse = Vec::new(); - - a_over_fq.iter().for_each(|x| { - let mut x = FF::new(x.value()); - a.push(x); - x = FF::new(2) * x; - if x >= q { - k.push(FF::one()); - r.push(x - q); - s.push(-(x - q + FF::one())); - c_sparse.push(Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![((x - q).value() as usize, -FF::one())], - ))); - } else { - k.push(FF::zero()); - r.push(x); - s.push(x + FF::one()); - c_sparse.push(Rc::new(SparsePolynomial::from_evaluations_vec( - c_num_vars, - vec![(x.value() as usize, FF::one())], - ))); - } - }); - - let a: Rc> = - Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, a)); - let k: Rc> = - Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, k)); - let r: Rc> = - Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, r)); - let s: Rc> = - Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, s)); - - let tmp = r.get_decomposed_mles(base_len, bits_len); - let r_bits: Vec<_> = vec![&tmp]; - let instance = TransformZqtoRQInstance::::from_vec( - q.value() as usize, - c_sparse.clone(), - a.clone(), - &k, - &r, - &s, - base, - base_len, - bits_len, - ); - let info = instance.info(); - let u: Vec<_> = (0..num_vars).map(|_| uniform_fp.sample(&mut rng)).collect(); - let proof = TransformZqtoRQ::prove(&instance, &u); - let subclaim = TransformZqtoRQ::verify(&proof, &info.decomposed_bits_info, c_num_vars); - - assert!(subclaim.verify_subclaim_without_oracle( - q.value() as usize, - a, - &c_sparse, - k.as_ref(), - vec![r].as_ref(), - s.as_ref(), - &r_bits, - &u, - &info - )); -}