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/Cargo.toml b/algebra/Cargo.toml index 92999afd..e0d11183 100644 --- a/algebra/Cargo.toml +++ b/algebra/Cargo.toml @@ -20,6 +20,7 @@ serde = { workspace = true } bincode = { workspace = true } concrete-ntt = { git = "https://github.com/pado-labs/concrete-ntt", branch = "main", optional = true } itertools = { workspace = true } +rayon = { workspace = true } [features] default = ["concrete-ntt"] diff --git a/algebra/examples/field.rs b/algebra/examples/field.rs index 92eac008..f077f9fd 100644 --- a/algebra/examples/field.rs +++ b/algebra/examples/field.rs @@ -1,6 +1,6 @@ use algebra::{ - derive::*, DecomposableField, Field, FieldBinarySampler, FieldDiscreteGaussianSampler, - FieldTernarySampler, FieldUniformSampler, Polynomial, PrimeField, + derive::*, Field, FieldBinarySampler, FieldDiscreteGaussianSampler, FieldTernarySampler, + FieldUniformSampler, Polynomial, PrimeField, }; use num_traits::{Inv, One, Pow, Zero}; use rand::prelude::*; diff --git a/algebra/src/baby_bear/mod.rs b/algebra/src/baby_bear/mod.rs index 2b24bfd4..65b079bc 100644 --- a/algebra/src/baby_bear/mod.rs +++ b/algebra/src/baby_bear/mod.rs @@ -24,7 +24,7 @@ use crate::{ }; /// Implementation of BabyBear field. -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)] pub struct BabyBear(u32); impl Field for BabyBear { @@ -37,14 +37,30 @@ impl Field for BabyBear { fn new(value: Self::Value) -> Self { Self(to_monty(value)) } -} -impl DecomposableField for BabyBear { #[inline] fn value(self) -> Self::Value { from_monty(self.0) } +} + +impl PartialOrd for BabyBear { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.value().cmp(&other.value())) + } + + fn lt(&self, other: &Self) -> bool { + self.value() < other.value() + } +} + +impl Ord for BabyBear { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.value().cmp(&other.value()) + } +} +impl DecomposableField for BabyBear { #[inline] fn decompose(self, basis: crate::Basis) -> Vec { let mut temp = self.value(); diff --git a/algebra/src/extension/binomial_extension.rs b/algebra/src/extension/binomial_extension.rs index 0dd6bfb1..e9b7cc0c 100644 --- a/algebra/src/extension/binomial_extension.rs +++ b/algebra/src/extension/binomial_extension.rs @@ -162,6 +162,16 @@ 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)) + } + + /// This part is inaccurate. + #[inline] + fn value(self) -> Self::Value { + self.value[0].value() + } } impl + Packable, const D: usize> Display diff --git a/algebra/src/field/mod.rs b/algebra/src/field/mod.rs index 39f74ace..dcd34436 100644 --- a/algebra/src/field/mod.rs +++ b/algebra/src/field/mod.rs @@ -1,6 +1,7 @@ //! This place defines some concrete implement of field of the algebra. use std::fmt::{Debug, Display}; +use std::hash::Hash; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use num_traits::{ConstOne, ConstZero, Inv, Pow}; @@ -47,6 +48,7 @@ pub trait Field: + PartialOrd + ConstZero + ConstOne + + Hash + ConstNegOne + Add + Sub @@ -94,13 +96,13 @@ pub trait Field: Self::new(hi.as_into()) } -} -/// A trait defined for decomposable field, this is mainly for base field in FHE. -pub trait DecomposableField: Field { /// Gets inner value. fn value(self) -> Self::Value; +} +/// A trait defined for decomposable field, this is mainly for base field in FHE. +pub trait DecomposableField: Field { /// Decompose `self` according to `basis`, /// return the decomposed vector. /// diff --git a/algebra/src/goldilocks/mod.rs b/algebra/src/goldilocks/mod.rs index 3bf4cd21..e7c2deea 100644 --- a/algebra/src/goldilocks/mod.rs +++ b/algebra/src/goldilocks/mod.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use std::{ fmt::Display, + hash::Hash, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; @@ -46,14 +47,14 @@ impl Field for Goldilocks { fn new(value: Self::Value) -> Self { Self(value) } -} -impl DecomposableField for Goldilocks { #[inline] fn value(self) -> Self::Value { to_canonical_u64(self.0) } +} +impl DecomposableField for Goldilocks { #[inline] fn decompose(self, basis: crate::Basis) -> Vec { let mut temp = self.value(); @@ -259,6 +260,12 @@ impl Ord for Goldilocks { } } +impl Hash for Goldilocks { + #[inline] + fn hash(&self, state: &mut H) { + self.as_canonical_u64().hash(state); + } +} impl Neg for Goldilocks { type Output = Self; #[inline] diff --git a/algebra/src/polynomial/multivariate/data_structures.rs b/algebra/src/polynomial/multivariate/data_structures.rs index 2c1579e2..19d1e1ea 100644 --- a/algebra/src/polynomial/multivariate/data_structures.rs +++ b/algebra/src/polynomial/multivariate/data_structures.rs @@ -2,9 +2,12 @@ use std::{collections::HashMap, rc::Rc}; +use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; +use serde::{Deserialize, Serialize}; + use crate::Field; -use super::{DenseMultilinearExtension, MultilinearExtension}; +use super::DenseMultilinearExtension; /// Stores a list of products of `DenseMultilinearExtension` that is meant to be added together. /// @@ -48,7 +51,7 @@ impl ListOfProductsOfPolynomials { } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Serialize, Deserialize)] /// 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 { @@ -131,14 +134,22 @@ impl ListOfProductsOfPolynomials { /// Evaluate the polynomial at point `point` pub fn evaluate(&self, point: &[F]) -> F { - self.products + let mle_buff: Vec<_> = self + .flattened_ml_extensions .iter() - .zip(self.linear_ops.iter()) - .fold(F::zero(), |result, ((c, p), ops)| { - result - + p.iter().zip(ops.iter()).fold(*c, |acc, (&i, &(a, b))| { - acc * (self.flattened_ml_extensions[i].evaluate(point) * a + b) + .map(|m| m.as_ref().clone()) + .collect(); + self.products + .par_iter() + .zip(self.linear_ops.par_iter()) + .fold( + || F::zero(), + |res, ((c, p), ops)| { + res + p.iter().zip(ops.iter()).fold(*c, |acc, (&i, &(a, b))| { + acc * (mle_buff[i].evaluate(point) * a + b) }) - }) + }, + ) + .reduce(|| F::zero(), |acc, v| acc + v) } } diff --git a/algebra/src/polynomial/multivariate/multilinear/dense.rs b/algebra/src/polynomial/multivariate/multilinear/dense.rs index 36018955..6306c63c 100644 --- a/algebra/src/polynomial/multivariate/multilinear/dense.rs +++ b/algebra/src/polynomial/multivariate/multilinear/dense.rs @@ -3,19 +3,23 @@ use std::fmt::Debug; use std::ops::{Add, AddAssign, Index, Neg, Sub, SubAssign}; use std::slice::{Iter, IterMut}; +use std::vec; use num_traits::Zero; use rand_distr::Distribution; +use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; +use serde::Serialize; use crate::{ AbstractExtensionField, Bits, ConstBounded, DecomposableField, Field, FieldUniformSampler, + NTTPolynomial, Polynomial, }; -use super::MultilinearExtension; +use super::{MultilinearExtension, PAR_NUM_VAR_THRESHOLD}; use std::rc::Rc; /// Stores a multilinear polynomial in dense evaluation form. -#[derive(Clone, Default, PartialEq, Eq)] +#[derive(Clone, Default, PartialEq, Eq, Serialize)] pub struct DenseMultilinearExtension { /// The evaluation over {0,1}^`num_vars` pub evaluations: Vec, @@ -24,6 +28,29 @@ pub struct DenseMultilinearExtension { } impl DenseMultilinearExtension { + /// Construct an empty instance + #[inline] + pub fn new(num_vars: usize) -> Self { + Self::from_evaluations_vec(num_vars, vec![F::zero(); 1 << num_vars]) + } + + /// Construct from Polynomial structure + /// Note that the data passed via this interface should be in normal order. + #[inline] + pub fn from_polynomial(log_n: usize, poly: Polynomial) -> DenseMultilinearExtension { + DenseMultilinearExtension::from_evaluations_vec(log_n, poly.data()) + } + + /// Construct from NTTPolynomial structure + /// Note that the data passed via this interface should be in bit-reversed order. + #[inline] + pub fn from_ntt_polynomial( + log_n: usize, + ntt_poly: NTTPolynomial, + ) -> DenseMultilinearExtension { + DenseMultilinearExtension::from_evaluations_vec(log_n, ntt_poly.data()) + } + /// Construct a new polynomial from a list of evaluations where the index /// represents a point in {0,1}^`num_vars` in little endian form. For /// example, `0b1011` represents `P(1,1,0,1)` @@ -80,6 +107,41 @@ impl DenseMultilinearExtension { ); (left, right) } + /// Evaluate a point in the field. + #[inline] + pub fn evaluate(&self, ext_point: &[F]) -> F { + assert_eq!(ext_point.len(), self.num_vars, "The point size is invalid."); + let mut poly: Vec<_> = self.evaluations.to_vec(); + let nv = self.num_vars; + let dim = ext_point.len(); + // evaluate nv variable of partial point from left to right + // with dim rounds and \sum_{i=1}^{dim} 2^(nv - i) + // (If dim = nv, then the complexity is 2^{nv}.) + if dim <= PAR_NUM_VAR_THRESHOLD { + for i in 1..dim + 1 { + // fix a single variable to evaluate (1 << (nv - i)) evaluations from the last round + // with complexity of 2^(1 << (nv - i)) field multiplications + let r = ext_point[i - 1]; + for b in 0..(1 << (nv - i)) { + let left = poly[b << 1]; + let right = poly[(b << 1) + 1]; + poly[b] = r * (right - left) + left; + } + } + } else { + for i in 1..dim + 1 { + let r = ext_point[i - 1]; + let mut tmp = vec![F::zero(); 1 << (nv - i)]; + tmp.par_iter_mut().enumerate().for_each(|(b, t)| { + let left = poly[b << 1]; + let right = poly[(b << 1) + 1]; + *t = left + r * (right - left); + }); + poly = tmp; + } + } + poly[0] + } /// Evaluate a point in the extension field. #[inline] @@ -98,19 +160,52 @@ impl DenseMultilinearExtension { // evaluate nv variable of partial point from left to right // with dim rounds and \sum_{i=1}^{dim} 2^(nv - i) // (If dim = nv, then the complexity is 2^{nv}.) - for i in 1..dim + 1 { - // fix a single variable to evaluate (1 << (nv - i)) evaluations from the last round - // with complexity of 2^(1 << (nv - i)) field multiplications - let r = ext_point[i - 1]; - for b in 0..(1 << (nv - i)) { - let left = poly[b << 1]; - let right = poly[(b << 1) + 1]; - poly[b] = r * (right - left) + left; + if dim <= PAR_NUM_VAR_THRESHOLD { + for i in 1..dim + 1 { + // fix a single variable to evaluate (1 << (nv - i)) evaluations from the last round + // with complexity of 2^(1 << (nv - i)) field multiplications + let r = ext_point[i - 1]; + for b in 0..(1 << (nv - i)) { + let left = poly[b << 1]; + let right = poly[(b << 1) + 1]; + poly[b] = r * (right - left) + left; + } + } + } else { + for i in 1..dim + 1 { + let r = ext_point[i - 1]; + let mut tmp = vec![EF::zero(); 1 << (nv - i)]; + tmp.par_iter_mut().enumerate().for_each(|(b, t)| { + let left = poly[b << 1]; + let right = poly[(b << 1) + 1]; + *t = left + r * (right - left); + }); + poly = tmp; } } - poly.truncate(1 << (nv - dim)); poly[0] } + + /// Evaluate a point in the extension field. + #[inline] + pub fn evaluate_ext_opt(&self, eq_at_r: &DenseMultilinearExtension) -> EF + where + EF: AbstractExtensionField, + { + eq_at_r + .iter() + .zip(self.iter()) + .fold(EF::zero(), |acc, (c, val)| acc + *c * *val) + } + + /// 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 { @@ -123,20 +218,20 @@ 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 = ::MAX >> (::BITS - base_len); + let mask = ::MAX >> (::BITS - 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, @@ -313,17 +408,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/polynomial/multivariate/multilinear/mod.rs b/algebra/src/polynomial/multivariate/multilinear/mod.rs index 4d4375d7..aad89766 100644 --- a/algebra/src/polynomial/multivariate/multilinear/mod.rs +++ b/algebra/src/polynomial/multivariate/multilinear/mod.rs @@ -55,3 +55,5 @@ pub trait MultilinearExtension: /// hypercube. The evaluations are in little-endian order. fn to_evaluations(&self) -> Vec; } + +const PAR_NUM_VAR_THRESHOLD: usize = 20; diff --git a/algebra/src/polynomial/multivariate/multilinear/sparse.rs b/algebra/src/polynomial/multivariate/multilinear/sparse.rs index fd4c5d96..ceb65d18 100644 --- a/algebra/src/polynomial/multivariate/multilinear/sparse.rs +++ b/algebra/src/polynomial/multivariate/multilinear/sparse.rs @@ -1,6 +1,6 @@ use std::slice::{Iter, IterMut}; -use crate::{DenseMultilinearExtension, Field}; +use crate::{AbstractExtensionField, DenseMultilinearExtension, Field}; /// Sparse polynomial #[derive(Clone, Default, PartialEq, Eq)] @@ -12,6 +12,20 @@ pub struct SparsePolynomial { } impl SparsePolynomial { + /// Construct an empty sparse polynomial + #[inline] + pub fn new(num_vars: usize) -> Self { + Self { + num_vars, + evaluations: Vec::new(), + } + } + + /// Add one evaluation + #[inline] + pub fn add_eval(&mut self, idx: usize, val: F) { + self.evaluations.push((idx, val)); + } /// Construct a new polynomial from a list of evaluations where the index /// represents a point in {0,1}^`num_vars` in little endian form. For /// example, `0b1011` represents `P(1,1,0,1)` @@ -60,4 +74,17 @@ impl SparsePolynomial { }); DenseMultilinearExtension::from_evaluations_vec(self.num_vars, evaluations) } + + /// Convert to EF version + #[inline] + pub fn to_ef>(&self) -> SparsePolynomial { + SparsePolynomial:: { + num_vars: self.num_vars, + evaluations: self + .evaluations + .iter() + .map(|(idx, val)| (*idx, EF::from_base(*val))) + .collect(), + } + } } diff --git a/algebra/src/utils/transcript.rs b/algebra/src/utils/transcript.rs index e1c7157e..855e7ae6 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,6 @@ use super::{Block, Prg}; /// to sample uniform field elements. pub struct Transcript { transcript: merlin::Transcript, - sampler: FieldUniformSampler, _marker: PhantomData, } @@ -22,89 +20,38 @@ impl Transcript { pub fn new() -> Self { Self { transcript: merlin::Transcript::new(b""), - 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/algebra_derive/src/basic.rs b/algebra_derive/src/basic.rs index 2a6fe003..25d4850d 100644 --- a/algebra_derive/src/basic.rs +++ b/algebra_derive/src/basic.rs @@ -64,6 +64,13 @@ pub(crate) fn basic(name: &Ident, modulus: &TokenStream) -> TokenStream { } impl ::std::cmp::Eq for #name {} + + impl ::std::hash::Hash for #name { + #[inline] + fn hash(&self, state: &mut H){ + self.0.hash(state) + } + } } } diff --git a/algebra_derive/src/decomposable_field.rs b/algebra_derive/src/decomposable_field.rs index 0b0f339e..e04e16a8 100644 --- a/algebra_derive/src/decomposable_field.rs +++ b/algebra_derive/src/decomposable_field.rs @@ -15,11 +15,6 @@ fn impl_decomposable_field(input: Input) -> TokenStream { quote! { impl ::algebra::DecomposableField for #name{ - #[inline] - fn value(self) -> Self::Value { - self.0 - } - fn decompose(self, basis: ::algebra::Basis) -> Vec { let mut temp = self.0; diff --git a/algebra_derive/src/field.rs b/algebra_derive/src/field.rs index 1b216bfb..841ae480 100644 --- a/algebra_derive/src/field.rs +++ b/algebra_derive/src/field.rs @@ -110,6 +110,11 @@ fn impl_field(name: &proc_macro2::Ident, field_ty: &Type, modulus: &TokenStream) Self(value.reduce(::MODULUS)) } } + + #[inline] + fn value(self) -> Self::Value { + self.0 + } } } } diff --git a/lattice/tests/lattice_test.rs b/lattice/tests/lattice_test.rs index 796beecc..01000a3b 100644 --- a/lattice/tests/lattice_test.rs +++ b/lattice/tests/lattice_test.rs @@ -2,8 +2,8 @@ use algebra::derive::{DecomposableField, FheField, Field, Prime, NTT}; use algebra::modulus::PowOf2Modulus; use algebra::reduce::{AddReduce, MulReduce, SubReduce}; use algebra::{ - Basis, DecomposableField, Field, FieldDiscreteGaussianSampler, FieldTernarySampler, - FieldUniformSampler, ModulusConfig, Polynomial, + Basis, Field, FieldDiscreteGaussianSampler, FieldTernarySampler, FieldUniformSampler, + ModulusConfig, Polynomial, }; use lattice::*; use num_traits::{Inv, One}; 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..20a26c9d 100644 --- a/pcs/src/lib.rs +++ b/pcs/src/lib.rs @@ -8,22 +8,29 @@ 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}; +use serde::{Deserialize, Serialize}; // type Point =

>::Point; /// Polymomial Commitment Scheme -pub trait PolynomialCommitmentScheme { +pub trait PolynomialCommitmentScheme, S> { /// System parameters - type Parameters; - /// polynomial to commit + type Parameters: Default; + /// Polynomial to commit type Polynomial: MultilinearExtension; - /// commitment - type Commitment; + /// Extension field polynomial to commit + type EFPolynomial: MultilinearExtension; + /// Commitment + type Commitment: Serialize + for<'de> Deserialize<'de>; /// Auxiliary state of the commitment, output by the `commit` phase. type CommitmentState; + /// Auxiliary state of the commitment, output by the `commit` phase. + type CommitmentStateEF; /// Opening Proof - type Proof; + type Proof: Serialize + for<'de> Deserialize<'de> + Default + Clone; + /// Opening Proof for EF + type ProofEF: Serialize + for<'de> Deserialize<'de> + Default + Clone; /// Point type Point; @@ -36,15 +43,48 @@ pub trait PolynomialCommitmentScheme { poly: &Self::Polynomial, ) -> (Self::Commitment, Self::CommitmentState); + /// The Commit phase for extension field. + fn commit_ef( + pp: &Self::Parameters, + poly: &Self::EFPolynomial, + ) -> (Self::Commitment, Self::CommitmentStateEF); + /// The Opening phase. fn open( pp: &Self::Parameters, commitment: &Self::Commitment, state: &Self::CommitmentState, points: &[Self::Point], - trans: &mut Transcript, + trans: &mut Transcript, ) -> Self::Proof; + /// The Opening phase for EF. + fn open_ef( + pp: &Self::Parameters, + commitment: &Self::Commitment, + state: &Self::CommitmentStateEF, + points: &[Self::Point], + trans: &mut Transcript, + ) -> Self::ProofEF; + + /// The batch opening phase. + fn batch_open( + pp: &Self::Parameters, + commitment: &Self::Commitment, + state: &Self::CommitmentState, + batch_points: &[Vec], + trans: &mut Transcript, + ) -> Vec; + + /// The batch opening phase for EF. + fn batch_open_ef( + pp: &Self::Parameters, + commitment: &Self::Commitment, + state: &Self::CommitmentStateEF, + batch_points: &[Vec], + trans: &mut Transcript, + ) -> Vec; + /// The Verification phase. fn verify( pp: &Self::Parameters, @@ -52,6 +92,36 @@ pub trait PolynomialCommitmentScheme { points: &[Self::Point], eval: Self::Point, proof: &Self::Proof, - trans: &mut Transcript, + trans: &mut Transcript, + ) -> bool; + + /// The Verification phase for EF. + fn verify_ef( + pp: &Self::Parameters, + commitment: &Self::Commitment, + points: &[Self::Point], + eval: Self::Point, + proof: &Self::ProofEF, + trans: &mut Transcript, + ) -> bool; + + /// The batch verification phase. + fn batch_verify( + pp: &Self::Parameters, + commitment: &Self::Commitment, + batch_points: &[Vec], + evals: &[Self::Point], + proofs: &[Self::Proof], + trans: &mut Transcript, + ) -> bool; + + /// The batch verification phase for EF. + fn batch_verify_ef( + pp: &Self::Parameters, + commitment: &Self::Commitment, + batch_points: &[Vec], + evals: &[Self::Point], + proofs: &[Self::ProofEF], + trans: &mut Transcript, ) -> bool; } diff --git a/pcs/src/multilinear/brakedown/data_structure.rs b/pcs/src/multilinear/brakedown/data_structure.rs index 10d7d816..9e6a6d59 100644 --- a/pcs/src/multilinear/brakedown/data_structure.rs +++ b/pcs/src/multilinear/brakedown/data_structure.rs @@ -14,7 +14,7 @@ use bincode::Result; use crate::multilinear::brakedown::BRAKEDOWN_SECURITY_BIT; /// Define the structure of Brakedown parameters. -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Default)] pub struct BrakedownParams, C: LinearCode> { security_bit: usize, num_vars: usize, @@ -92,6 +92,7 @@ impl, C: LinearCode> BrakedownParams< } /// Return reference of code. + #[inline] pub fn code(&self) -> &C { &self.code } @@ -131,7 +132,7 @@ impl< pub type BrakedownPolyCommitment = MerkleRoot; /// Opening proof of Brakedown. -#[derive(Default, Serialize, Deserialize)] +#[derive(Default, Serialize, Deserialize, Clone)] pub struct BrakedownOpenProof where F: Field, @@ -165,6 +166,39 @@ where } } +/// Opening proof of Brakedown for extension field. +#[derive(Default, Serialize, Deserialize, Clone)] +pub struct BrakedownOpenProofGeneral +where + F: Field, + H: Hash, +{ + /// Random linear combination of messages. + pub rlc_msgs: Vec, + + /// The opening columns according to the queres. + pub opening_columns: Vec, + + /// Merkle paths. + pub merkle_paths: Vec, +} + +impl BrakedownOpenProofGeneral +where + F: Field + Serialize + for<'de> Deserialize<'de>, + H: Hash, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) + } + + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + /// Commitment state of Brakedown #[derive(Debug, Default)] pub struct BrakedownCommitmentState { diff --git a/pcs/src/multilinear/brakedown/mod.rs b/pcs/src/multilinear/brakedown/mod.rs index c099f05f..21439fb2 100644 --- a/pcs/src/multilinear/brakedown/mod.rs +++ b/pcs/src/multilinear/brakedown/mod.rs @@ -3,18 +3,19 @@ mod data_structure; pub use data_structure::{ - BrakedownCommitmentState, BrakedownOpenProof, BrakedownParams, BrakedownPolyCommitment, + BrakedownCommitmentState, BrakedownOpenProof, BrakedownOpenProofGeneral, BrakedownParams, + BrakedownPolyCommitment, }; use algebra::{ utils::{Block, Prg, Transcript}, AbstractExtensionField, DenseMultilinearExtension, Field, }; -use itertools::Itertools; +use itertools::{izip, Itertools}; use rand::SeedableRng; use rayon::prelude::*; use serde::{Deserialize, Serialize}; -use std::marker::PhantomData; +use std::{marker::PhantomData, mem::transmute}; use crate::{ utils::{ @@ -77,6 +78,36 @@ where answer } + /// Prover answers the challenge by computing the product of the challenge vector + /// and the committed matrix. + /// 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_ext( + pp: &BrakedownParams, + challenge: &[EF], + state: &BrakedownCommitmentState, + ) -> Vec { + assert_eq!(challenge.len(), pp.num_rows()); + let num_cols = pp.code().message_len(); + let codeword_len = pp.code().codeword_len(); + + // Compute the answer as a linear combination. + let mut answer = vec![EF::zero(); num_cols]; + state + .matrix + .chunks_exact(codeword_len) + .zip(challenge) + .for_each(|(row, coeff)| { + row.iter() + .take(num_cols) + .enumerate() + .for_each(|(idx, item)| { + answer[idx] += *coeff * *item; + }) + }); + answer + } + /// Prover answers the query of columns of given indices /// and gives merkle paths as the proof of its consistency with the merkle root. fn answer_queries( @@ -105,6 +136,33 @@ where (merkle_proof, columns) } + /// Prover answers the query of columns of given indices + /// and gives merkle paths as the proof of its consistency with the merkle root. + fn answer_queries_ext( + pp: &BrakedownParams, + queries: &[usize], + state: &BrakedownCommitmentState, + ) -> (Vec, Vec) { + let codeword_len = pp.code().codeword_len(); + let num_rows = pp.num_rows(); + + // Compute the merkle proofs + let merkle_proof = queries + .iter() + .flat_map(|idx| state.merkle_tree.query(*idx)) + .collect(); + + // Collect the columns as part of the answers. + let columns = queries + .iter() + .flat_map(|idx| { + (0..num_rows) + .map(|row_idx| state.matrix[row_idx * codeword_len + idx]) + .collect_vec() + }) + .collect(); + (merkle_proof, columns) + } /// Decompose an evaluation of point x into two tensors q1, q2 such that /// f(x) = q1 * M * q2 where M is the committed matrix. fn tensor_decompose(pp: &BrakedownParams, point: &[EF]) -> (Vec, Vec) { @@ -152,6 +210,30 @@ where merkle_check & consistency_check } + /// Check the merkle paths and consistency + fn check_query_answers_ext( + pp: &BrakedownParams, + challenge: &[EF], + queries: &[usize], + encoded_rlc_msg: &[EF], + merkle_paths: &[H::Output], + columns: &[EF], + commitment: &BrakedownPolyCommitment, + ) -> bool { + // Check input length + assert_eq!(challenge.len(), pp.num_rows()); + assert_eq!(columns.len(), queries.len() * pp.num_rows()); + assert_eq!(merkle_paths.len(), queries.len() * (commitment.depth + 1)); + + // Check merkle paths and consistency. + let (merkle_check, consistency_check) = rayon::join( + || Self::check_merkle_ext(pp, queries, merkle_paths, columns, commitment), + || Self::check_consistency_ext(pp, queries, challenge, encoded_rlc_msg, columns), + ); + + merkle_check & consistency_check + } + /// Check the hash of column is the same as the merkle leave. /// Check the merkle paths are consistent with the merkle root. fn check_merkle( @@ -170,9 +252,47 @@ where .map(|((column, hashes), column_idx)| { let mut hasher = H::new(); // Check the hash of column is the same as the merkle leave. - column - .iter() - .for_each(|item| hasher.update_string(item.to_string())); + column.iter().for_each(|item| unsafe { + #[allow(clippy::transmute_num_to_bytes)] + let bytes = transmute::(item.value().into()); + hasher.update_hash_value(&bytes) + }); + let leaf = hasher.output_reset(); + + // Check the merkle path is consistent with the merkle root + (leaf == hashes[0]) & MerkleTree::::check(&commitment.root, *column_idx, hashes) + }) + .collect(); + + res.iter().for_each(|b| check &= *b); + check + } + + /// Check the hash of column is the same as the merkle leave. + /// Check the merkle paths are consistent with the merkle root. + fn check_merkle_ext( + pp: &BrakedownParams, + queries: &[usize], + merkle_paths: &[H::Output], + columns: &[EF], + commitment: &BrakedownPolyCommitment, + ) -> bool { + let mut check = true; + + let res: Vec = columns + .par_chunks_exact(pp.num_rows()) + .zip(merkle_paths.par_chunks_exact(commitment.depth + 1)) + .zip(queries) + .map(|((column, hashes), column_idx)| { + let mut hasher = H::new(); + // Check the hash of column is the same as the merkle leave. + column.iter().for_each(|item| unsafe { + item.as_base_slice().iter().for_each(|x| { + #[allow(clippy::transmute_num_to_bytes)] + let bytes = transmute::(x.value().into()); + hasher.update_hash_value(&bytes) + }); + }); let leaf = hasher.output_reset(); // Check the merkle path is consistent with the merkle root @@ -208,6 +328,30 @@ where check } + /// Check the consistency of entries + fn check_consistency_ext( + pp: &BrakedownParams, + queries: &[usize], + challenge: &[EF], + encoded_rlc_msg: &[EF], + columns: &[EF], + ) -> bool { + let mut check = true; + columns + .chunks_exact(pp.num_rows()) + .zip(queries) + .for_each(|(column, idx)| { + let product = column + .iter() + .zip(challenge) + .fold(EF::zero(), |acc, (x0, x1)| acc + (*x1) * (*x0)); + + check &= product == encoded_rlc_msg[*idx]; + }); + + check + } + /// Compute the residual product (i.e., the inner product) #[inline] fn residual_product(answer: &[EF], residual: &[EF]) -> EF { @@ -227,12 +371,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,9 +388,9 @@ where } } -impl PolynomialCommitmentScheme for BrakedownPCS +impl PolynomialCommitmentScheme for BrakedownPCS where - F: Field + Serialize, + F: Field + Serialize + for<'de> Deserialize<'de>, H: Hash + Sync + Send, C: LinearCode + Serialize + for<'de> Deserialize<'de>, S: LinearCodeSpec, @@ -254,8 +398,11 @@ where { type Parameters = BrakedownParams; type Polynomial = DenseMultilinearExtension; + type EFPolynomial = DenseMultilinearExtension; type Commitment = BrakedownPolyCommitment; type CommitmentState = BrakedownCommitmentState; + type CommitmentStateEF = BrakedownCommitmentState; + type ProofEF = BrakedownOpenProofGeneral; type Proof = BrakedownOpenProof; type Point = EF; @@ -275,9 +422,7 @@ where let num_cols = pp.code().message_len(); let num_rows = pp.num_rows(); let codeword_len = pp.code().codeword_len(); - let mut matrix = vec![F::zero(); num_rows * codeword_len]; - // Fill each row of the matrix with a message and // encode the message into a codeword. matrix @@ -298,7 +443,70 @@ where .iter() .skip(index) .step_by(codeword_len) - .for_each(|item| hasher.update_string(item.to_string())); + .for_each(|item| unsafe { + #[allow(clippy::transmute_num_to_bytes)] + let bytes = transmute::(item.value().into()); + hasher.update_hash_value(&bytes) + }); + *hash = hasher.output_reset(); + }); + + let mut merkle_tree = MerkleTree::new(); + merkle_tree.generate(&hashes); + let depth = merkle_tree.depth; + let root = merkle_tree.root; + + let state = BrakedownCommitmentState { + matrix, + merkle_tree, + }; + + let com = BrakedownPolyCommitment { depth, root }; + + (com, state) + } + + fn commit_ef( + pp: &Self::Parameters, + poly: &Self::EFPolynomial, + ) -> (Self::Commitment, Self::CommitmentStateEF) { + // Check consistency of num_vars. + assert!(poly.num_vars == pp.num_vars()); + + // Prepare the matrix to commit. + let num_cols = pp.code().message_len(); + let num_rows = pp.num_rows(); + let codeword_len = pp.code().codeword_len(); + + let mut matrix = vec![EF::zero(); num_rows * codeword_len]; + + // Fill each row of the matrix with a message and + // encode the message into a codeword. + matrix + .par_chunks_exact_mut(codeword_len) + .zip(poly.evaluations.par_chunks_exact(num_cols)) + .for_each(|(row, eval)| { + row[..num_cols].copy_from_slice(eval); + pp.code().encode_ext(row) + }); + // Hash each column of the matrix into a hash value. + // Prepare the container of the entire merkle tree, pushing the + // layers of merkle tree into this container from bottom to top. + let mut hashes = vec![H::Output::default(); codeword_len]; + + hashes.par_iter_mut().enumerate().for_each(|(index, hash)| { + let mut hasher = H::new(); + matrix + .iter() + .skip(index) + .step_by(codeword_len) + .for_each(|item| unsafe { + item.as_base_slice().iter().for_each(|x| { + #[allow(clippy::transmute_num_to_bytes)] + let bytes = transmute::(x.value().into()); + hasher.update_hash_value(&bytes) + }); + }); *hash = hasher.output_reset(); }); @@ -322,11 +530,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 +543,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); @@ -349,18 +558,142 @@ where } } + fn open_ef( + pp: &Self::Parameters, + commitment: &Self::Commitment, + state: &Self::CommitmentStateEF, + points: &[Self::Point], + trans: &mut Transcript, + ) -> Self::ProofEF { + assert_eq!(points.len(), pp.num_vars()); + // Hash the commitment to transcript. + 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); + + let rlc_msgs = Self::answer_challenge_ext(pp, &tensor, state); + + // Hash rlc to transcript. + trans.append_message(b"rlc", &rlc_msgs); + + // Sample random queries. + let queries = Self::random_queries(pp, trans); + + // Generate the proofs for random queries. + let (merkle_paths, opening_columns) = Self::answer_queries_ext(pp, &queries, state); + + BrakedownOpenProofGeneral { + rlc_msgs, + merkle_paths, + opening_columns, + } + } + fn batch_open( + pp: &Self::Parameters, + commitment: &Self::Commitment, + state: &Self::CommitmentState, + batch_points: &[Vec], + trans: &mut Transcript, + ) -> Vec { + // Hash the commitment to transcript. + trans.append_message(b"commitment", &commitment); + + // Compute the tensor from the random point, see [DP23](https://eprint.iacr.org/2023/630.pdf). + let tensors: Vec> = batch_points + .iter() + .map(|points| { + assert_eq!(points.len(), pp.num_vars()); + Self::tensor_from_points(pp, points) + }) + .collect(); + + let rlc_msgss: Vec> = tensors + .par_iter() + .map(|tensor| Self::answer_challenge(pp, tensor, state)) + .collect(); + + let queries_vec: Vec> = rlc_msgss + .iter() + .map(|rlc_msg| { + trans.append_message(b"rlc", rlc_msg); + Self::random_queries(pp, trans) + }) + .collect(); + + queries_vec + .par_iter() + .zip(rlc_msgss.par_iter()) + .map(|(queries, rlc_msgs)| { + let (merkle_paths, opening_columns) = Self::answer_queries(pp, queries, state); + BrakedownOpenProof { + rlc_msgs: rlc_msgs.clone(), + merkle_paths, + opening_columns, + } + }) + .collect() + } + + fn batch_open_ef( + pp: &Self::Parameters, + commitment: &Self::Commitment, + state: &Self::CommitmentStateEF, + batch_points: &[Vec], + trans: &mut Transcript, + ) -> Vec { + // Hash the commitment to transcript. + trans.append_message(b"commitment", &commitment); + + // Compute the tensor from the random point, see [DP23](https://eprint.iacr.org/2023/630.pdf). + let tensors: Vec> = batch_points + .iter() + .map(|points| { + assert_eq!(points.len(), pp.num_vars()); + Self::tensor_from_points(pp, points) + }) + .collect(); + + let rlc_msgss: Vec> = tensors + .par_iter() + .map(|tensor| Self::answer_challenge_ext(pp, tensor, state)) + .collect(); + + let queries_vec: Vec> = rlc_msgss + .iter() + .map(|rlc_msg| { + trans.append_message(b"rlc", rlc_msg); + Self::random_queries(pp, trans) + }) + .collect(); + + queries_vec + .par_iter() + .zip(rlc_msgss.par_iter()) + .map(|(queries, rlc_msgs)| { + let (merkle_paths, opening_columns) = Self::answer_queries_ext(pp, queries, state); + BrakedownOpenProofGeneral { + rlc_msgs: rlc_msgs.clone(), + merkle_paths, + opening_columns, + } + }) + .collect() + } + fn verify( pp: &Self::Parameters, commitment: &Self::Commitment, 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 +704,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); @@ -392,4 +725,144 @@ where check } + + fn verify_ef( + pp: &Self::Parameters, + commitment: &Self::Commitment, + points: &[Self::Point], + eval: Self::Point, + proof: &Self::ProofEF, + trans: &mut Transcript, + ) -> bool { + assert_eq!(points.len(), pp.num_vars()); + + // Hash the commitment to transcript. + trans.append_message(b"commitment", &commitment); + + let (tensor, residual) = Self::tensor_decompose(pp, points); + + // Encode the answered random linear combination. + assert_eq!(proof.rlc_msgs.len(), pp.code().message_len()); + let mut encoded_msg = vec![EF::zero(); pp.code().codeword_len()]; + encoded_msg[..proof.rlc_msgs.len()].copy_from_slice(&proof.rlc_msgs); + pp.code().encode_ext(&mut encoded_msg); + + // Hash rlc to transcript. + trans.append_message(b"rlc", &proof.rlc_msgs); + + // Sample random queries. + let queries = Self::random_queries(pp, trans); + + // Proximity check. + let mut check = Self::check_query_answers_ext( + pp, + &tensor, + &queries, + &encoded_msg, + &proof.merkle_paths, + &proof.opening_columns, + commitment, + ); + + // Consistency check. + check &= eval == Self::residual_product(&proof.rlc_msgs, &residual); + + check + } + + fn batch_verify( + pp: &Self::Parameters, + commitment: &Self::Commitment, + batch_points: &[Vec], + evals: &[Self::Point], + proofs: &[Self::Proof], + trans: &mut Transcript, + ) -> bool { + // Hash the commitment to transcript. + trans.append_message(b"commitment", &commitment); + + let tensors_and_residuals: Vec<(Vec, Vec)> = batch_points + .par_iter() + .map(|points| { + assert_eq!(points.len(), pp.num_vars()); + Self::tensor_decompose(pp, points) + }) + .collect(); + + let queries_vec: Vec> = proofs + .iter() + .map(|proof| { + assert_eq!(proof.rlc_msgs.len(), pp.code().message_len()); + trans.append_message(b"rlc", &proof.rlc_msgs); + Self::random_queries(pp, trans) + }) + .collect(); + + izip!(proofs, &queries_vec, &tensors_and_residuals, evals) + .par_bridge() + .all(|(proof, queries, (tensor, residual), eval)| { + assert_eq!(proof.rlc_msgs.len(), pp.code().message_len()); + let mut encoded_msg = vec![EF::zero(); pp.code().codeword_len()]; + encoded_msg[..proof.rlc_msgs.len()].copy_from_slice(&proof.rlc_msgs); + pp.code().encode_ext(&mut encoded_msg); + + Self::check_query_answers( + pp, + tensor, + queries, + &encoded_msg, + &proof.merkle_paths, + &proof.opening_columns, + commitment, + ) & (*eval == Self::residual_product(&proof.rlc_msgs, residual)) + }) + } + + fn batch_verify_ef( + pp: &Self::Parameters, + commitment: &Self::Commitment, + batch_points: &[Vec], + evals: &[Self::Point], + proofs: &[Self::ProofEF], + trans: &mut Transcript, + ) -> bool { + // Hash the commitment to transcript. + trans.append_message(b"commitment", &commitment); + + let tensors_and_residuals: Vec<(Vec, Vec)> = batch_points + .par_iter() + .map(|points| { + assert_eq!(points.len(), pp.num_vars()); + Self::tensor_decompose(pp, points) + }) + .collect(); + + let queries_vec: Vec> = proofs + .iter() + .map(|proof| { + assert_eq!(proof.rlc_msgs.len(), pp.code().message_len()); + trans.append_message(b"rlc", &proof.rlc_msgs); + Self::random_queries(pp, trans) + }) + .collect(); + + izip!(proofs, &queries_vec, &tensors_and_residuals, evals) + .par_bridge() + .all(|(proof, queries, (tensor, residual), eval)| { + assert_eq!(proof.rlc_msgs.len(), pp.code().message_len()); + let mut encoded_msg = vec![EF::zero(); pp.code().codeword_len()]; + encoded_msg[..proof.rlc_msgs.len()].copy_from_slice(&proof.rlc_msgs); + pp.code().encode_ext(&mut encoded_msg); + + Self::check_query_answers_ext( + pp, + tensor, + queries, + &encoded_msg, + &proof.merkle_paths, + &proof.opening_columns, + commitment, + ) & (*eval == Self::residual_product(&proof.rlc_msgs, residual)) + }) + } } diff --git a/pcs/src/utils/hash.rs b/pcs/src/utils/hash.rs index c13e769b..2d21a2da 100644 --- a/pcs/src/utils/hash.rs +++ b/pcs/src/utils/hash.rs @@ -26,10 +26,7 @@ pub trait Hash: Debug + Clone + Default + Sized { /// Update with input fn update_hash_value(&mut self, input: &[u8]); - /// Update with a string as input - fn update_string(&mut self, input: String); - - /// Uutput a hash value and reset + /// Output a hash value and reset fn output_reset(&mut self) -> Self::Output; } @@ -41,11 +38,6 @@ impl Hash for Sha256 { self.update(input); } - #[inline] - fn update_string(&mut self, input: String) { - self.update(input); - } - #[inline] fn output_reset(&mut self) -> Self::Output { self.finalize_reset().into() 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..8180a4ae 100644 --- a/pcs/tests/test_pcs.rs +++ b/pcs/tests/test_pcs.rs @@ -2,7 +2,7 @@ use algebra::{ utils::Transcript, BabyBear, BabyBearExetension, DenseMultilinearExtension, FieldUniformSampler, }; use pcs::{ - multilinear::{brakedown::BrakedownPCS, BrakedownOpenProof}, + multilinear::{brakedown::BrakedownPCS, BrakedownOpenProof, BrakedownOpenProofGeneral}, utils::code::{ExpanderCode, ExpanderCodeSpec}, PolynomialCommitmentScheme, }; @@ -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(); @@ -59,4 +59,40 @@ fn pcs_test() { ); assert!(check); + + // Commit extension field polynomial. + let evaluations: Vec = rand::thread_rng() + .sample_iter(FieldUniformSampler::new()) + .take(1 << num_vars) + .collect(); + + let ext_poly = DenseMultilinearExtension::from_evaluations_vec(num_vars, evaluations); + + let mut trans = Transcript::::new(); + + let (comm, state) = + BrakedownPCS::, ExpanderCodeSpec, EF>::commit_ef(&pp, &ext_poly); + + let point: Vec = rand::thread_rng() + .sample_iter(FieldUniformSampler::new()) + .take(num_vars) + .collect(); + + let proof = BrakedownPCS::, ExpanderCodeSpec, EF>::open_ef( + &pp, &comm, &state, &point, &mut trans, + ); + + let buffer = proof.to_bytes().unwrap(); + + let eval = ext_poly.evaluate(&point); + + let mut trans = Transcript::::new(); + + let proof = BrakedownOpenProofGeneral::::from_bytes(&buffer).unwrap(); + + let check = BrakedownPCS::, ExpanderCodeSpec, EF>::verify_ef( + &pp, &comm, &point, eval, &proof, &mut trans, + ); + + assert!(check); } diff --git a/zkp/Cargo.toml b/zkp/Cargo.toml index 1d5733a5..42e7d2cb 100644 --- a/zkp/Cargo.toml +++ b/zkp/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] algebra = { path = "../algebra" } +pcs = { path = "../pcs" } +fhe_core = { path = "../fhe_core" } thiserror = { workspace = true } rand = { workspace = true } @@ -16,3 +18,17 @@ num-traits = { workspace = true } once_cell = { workspace = true } serde = { workspace = true } itertools = { workspace = true } +bincode = { workspace = true } +rayon = { 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" 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..04c3c67f --- /dev/null +++ b/zkp/examples/accumulator.rs @@ -0,0 +1,456 @@ +use algebra::utils::Transcript; +use algebra::{transformation::AbstractNTT, NTTField, NTTPolynomial, Polynomial}; +use algebra::{ + BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension, Field, MultilinearExtension, +}; +use itertools::izip; +use num_traits::One; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use sha2::Sha256; +use std::sync::Arc; +use std::time::Instant; +use std::vec; +use zkp::piop::accumulator::{ + AccumulatorParams, AccumulatorProof, AccumulatorProver, AccumulatorVerifier, +}; +use zkp::piop::{ + ntt::BitsOrder, AccumulatorInstance, AccumulatorWitness, BatchNTTInstanceInfo, + BitDecompositionInstanceInfo, ExternalProductInstance, RlweCiphertext, RlweCiphertextVector, +}; + +// # 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 = 7; +const BLOCK_SIZE: 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: >::random(num_vars, rng), + b: >::random(num_vars, rng), + } +} + +fn random_rlwe_ciphertexts( + bits_len: usize, + rng: &mut R, + num_vars: usize, +) -> RlweCiphertextVector +where + R: rand::Rng + rand::CryptoRng, +{ + RlweCiphertextVector { + a_vector: (0..bits_len) + .map(|_| >::random(num_vars, rng)) + .collect(), + b_vector: (0..bits_len) + .map(|_| >::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: RlweCiphertextVector, + bits_rgsw_f_ntt: RlweCiphertextVector, + bits_info: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, +) -> ExternalProductInstance { + // 1. Decompose the input of RLWE ciphertex + let bits_rlwe = RlweCiphertextVector { + a_vector: input_rlwe + .a + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len) + .iter() + .map(|d| d.as_ref().clone()) + .collect::>(), + b_vector: input_rlwe + .b + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len) + .iter() + .map(|d| d.as_ref().clone()) + .collect::>(), + }; + + // 2. Compute the ntt form of the decomposed bits + let bits_rlwe_ntt = RlweCiphertextVector { + a_vector: bits_rlwe + .a_vector + .iter() + .map(|bit| { + DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + ) + }) + .collect(), + b_vector: bits_rlwe + .b_vector + .iter() + .map(|bit| { + DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + ) + }) + .collect(), + }; + + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rlwe_ntt.b_vector.len()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rgsw_c_ntt.a_vector.len()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rgsw_f_ntt.a_vector.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_vector, + &bits_rlwe_ntt.b_vector, + &bits_rgsw_c_ntt.a_vector, + &bits_rgsw_f_ntt.a_vector + ) { + 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_vector, + &bits_rlwe_ntt.b_vector, + &bits_rgsw_c_ntt.b_vector, + &bits_rgsw_f_ntt.b_vector + ) { + 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: DenseMultilinearExtension::from_evaluations_vec(num_vars, output_g_ntt), + b: DenseMultilinearExtension::from_evaluations_vec(num_vars, output_h_ntt), + }; + + ExternalProductInstance::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: DenseMultilinearExtension, + bits_rgsw_c_ntt: RlweCiphertextVector, + bits_rgsw_f_ntt: RlweCiphertextVector, + bits_info: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, +) -> AccumulatorWitness { + // 1. Perform ntt transform on (x^{-a_u} - 1) + let d_ntt = 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: 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: 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: 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: 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: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, +) -> AccumulatorInstance { + let mut rng = rand::thread_rng(); + let mut updations = Vec::with_capacity(num_updations); + + let mut acc_ntt = RlweCiphertext:: { + a: DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &input.a.evaluations), + ), + b: DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &input.b.evaluations), + ), + }; + for _ in 0..num_updations { + let d = DenseMultilinearExtension::random(num_vars, &mut rng); + let bits_rgsw_c_ntt = + random_rlwe_ciphertexts(bits_info.clone().bits_len, &mut rng, num_vars); + let bits_rgsw_f_ntt = + random_rlwe_ciphertexts(bits_info.clone().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.clone(), + ntt_info.clone(), + ); + // perform ACC + ACC * d * RGSW + acc_ntt = RlweCiphertext { + a: acc_ntt.a + updation.rlwe_mult_rgsw.output_rlwe_ntt.a.clone(), + b: acc_ntt.b + updation.rlwe_mult_rgsw.output_rlwe_ntt.b.clone(), + }; + updations.push(updation); + } + + let output = RlweCiphertext { + a: DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_inverse_transform_normal_order(num_vars as u32, &acc_ntt.a.evaluations), + ), + b: 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 complete_snark() { + // 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 = BitDecompositionInstanceInfo { + 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 = Arc::new(ntt_table); + let ntt_info = BatchNTTInstanceInfo { + 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); + + // Parameters. + let mut params = AccumulatorParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let start = Instant::now(); + params.setup(&instance.info(), BLOCK_SIZE, code_spec); + println!( + "Accumulator setup time: {:?} ms", + start.elapsed().as_millis() + ); + // Prover + let acc_prover = AccumulatorProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + + let mut prover_trans = Transcript::::new(); + + let start = Instant::now(); + let proof = acc_prover.prove( + &mut prover_trans, + ¶ms, + &instance, + BLOCK_SIZE, + BitsOrder::Normal, + ); + println!( + "Accumulator proving time: {:?} ms", + start.elapsed().as_millis() + ); + + let proof_bytes = proof.to_bytes().unwrap(); + println!("Accumulator proof size: {:?} bytes", proof_bytes.len()); + + // Verifier. + let acc_verifier = AccumulatorVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::new(); + + let proof = AccumulatorProof::from_bytes(&proof_bytes).unwrap(); + + let start = Instant::now(); + let res = acc_verifier.verify( + &mut verifier_trans, + ¶ms, + &instance.info(), + BLOCK_SIZE, + BitsOrder::Normal, + &proof, + ); + println!( + "Accumulator verifying time: {:?} ms", + start.elapsed().as_millis() + ); + + assert!(res); +} +fn main() { + complete_snark() +} diff --git a/zkp/examples/addition_in_zq.rs b/zkp/examples/addition_in_zq.rs new file mode 100644 index 00000000..9fcc4475 --- /dev/null +++ b/zkp/examples/addition_in_zq.rs @@ -0,0 +1,136 @@ +use std::{rc::Rc, time::Instant}; + +use algebra::{utils::Transcript, BabyBear, BabyBearExetension, DenseMultilinearExtension, Field}; +use pcs::{ + multilinear::BrakedownPCS, + utils::code::{ExpanderCode, ExpanderCodeSpec}, +}; +use rand::{thread_rng, Rng}; +use sha2::Sha256; +use zkp::piop::{ + AdditionInZqInstance, AdditionInZqParams, AdditionInZqProof, AdditionInZqProver, + AdditionInZqVerifier, BitDecompositionInstanceInfo, +}; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +fn main() { + let mut rng = thread_rng(); + let bits_len = 10; + let modulus = 1024; + let q = FF::new(modulus); + let base_len = 2; + let base = FF::new(1 << base_len); + let num_vars = 10; + + let a: Vec<_> = (0..(1 << num_vars)) + .map(|_| rng.gen_range(0..modulus)) + .collect(); + + let b: Vec<_> = (0..(1 << num_vars)) + .map(|_| rng.gen_range(0..modulus)) + .collect(); + + let c_k: Vec<_> = a + .iter() + .zip(b.iter()) + .map(|(x, y)| { + if x + y >= modulus { + ((x + y) % modulus, 1) + } else { + ((x + y) % modulus, 0) + } + }) + .collect(); + + let (c, k): (Vec<_>, Vec<_>) = c_k.iter().cloned().unzip(); + + let abc = vec![ + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + a.iter().map(|&x| FF::new(x)).collect(), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + b.iter().map(|&x| FF::new(x)).collect(), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + c.iter().map(|&x| FF::new(x)).collect(), + )), + ]; + + let k = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + k.iter().map(|&x| FF::new(x)).collect(), + )); + + let bits_info = BitDecompositionInstanceInfo:: { + base, + base_len, + bits_len, + num_vars, + num_instances: 3, + }; + + let instance = AdditionInZqInstance::::from_slice(&abc, &k, q, &bits_info); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + let info = instance.info(); + println!("Prove {info}\n"); + + // Parameters. + let mut params = AdditionInZqParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let start = Instant::now(); + params.setup(&instance.info(), code_spec); + println!( + "addition in zq setup time: {:?} ms", + start.elapsed().as_millis() + ); + + // Prover. + let bd_prover = AdditionInZqProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let start = Instant::now(); + let proof = bd_prover.prove(&mut prover_trans, ¶ms, &instance); + println!( + "addition in zq proving time: {:?} ms", + start.elapsed().as_millis() + ); + + let proof_bytes = proof.to_bytes().unwrap(); + println!("addition in zq proof size: {:?} byts", proof_bytes.len()); + + // Verifier. + let bd_verifier = AdditionInZqVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + let proof = AdditionInZqProof::from_bytes(&proof_bytes).unwrap(); + + let start = Instant::now(); + let res = bd_verifier.verify(&mut verifier_trans, ¶ms, &info, &proof); + println!( + "addition in zq verifying time: {:?} ms", + start.elapsed().as_millis() + ); + assert!(res); +} diff --git a/zkp/examples/bit_decomposition.rs b/zkp/examples/bit_decomposition.rs new file mode 100644 index 00000000..74f232fd --- /dev/null +++ b/zkp/examples/bit_decomposition.rs @@ -0,0 +1,143 @@ +use algebra::derive::{DecomposableField, Field}; +use algebra::utils::Transcript; +use algebra::AsFrom; +use algebra::{BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension}; +use algebra::{DecomposableField, Field, FieldUniformSampler}; +use itertools::izip; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use rand_distr::Distribution; +use sha2::Sha256; +use std::rc::Rc; +use std::time::Instant; +use zkp::piop::bit_decomposition::{ + BitDecompositionParams, BitDecompositionProof, BitDecompositionProver, BitDecompositionVerifier, +}; +use zkp::piop::BitDecompositionInstance; + +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 LOG_DIM_RLWE: usize = 10; +const LOG_B: u32 = 1; + +fn generate_instance( + num_instances: usize, + num_vars: usize, + base_len: usize, + base: F, + bits_len: usize, +) -> BitDecompositionInstance { + let mut rng = thread_rng(); + // sample d in the range of Fq + let uniform = >::new(); + let d = (0..num_instances) + .map(|_| { + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..(1 << num_vars)) + .map(|_| { + F::new(F::Value::as_from( + uniform.sample(&mut rng).value().into() as u64 as f64, + )) + }) + .collect(), + )) + }) + .collect::>(); + + let d_bits: Vec<_> = d + .iter() + .map(|x| x.get_decomposed_mles(base_len, bits_len)) + .collect(); + + let mut decomposed_bits = BitDecompositionInstance::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 +} + +#[derive(Field, DecomposableField)] +#[modulus = 1024] +pub struct Fq(u32); + +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 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; + + let instance = generate_instance::(2, num_vars, base_len, base, bits_len); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + let info = instance.info(); + println!("Prove {info}\n"); + + // Parameters. + let mut params = BitDecompositionParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + params.setup(&instance.info(), code_spec); + + // Prover. + let bd_prover = BitDecompositionProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let start = Instant::now(); + let proof = bd_prover.prove(&mut prover_trans, ¶ms, &instance); + println!( + "bit decomposition proving time: {:?} ms", + start.elapsed().as_millis() + ); + + let proof_bytes = proof.to_bytes().unwrap(); + println!( + "bit decomposition proof size: {:?} bytes", + proof_bytes.len() + ); + + // Verifier. + let bd_verifier = BitDecompositionVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = BitDecompositionProof::from_bytes(&proof_bytes).unwrap(); + let start = Instant::now(); + let res = bd_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); + println!( + "bit decomposition verifying time: {:?} ms", + start.elapsed().as_millis() + ); + assert!(res); +} diff --git a/zkp/examples/external_product.rs b/zkp/examples/external_product.rs new file mode 100644 index 00000000..6654c7f0 --- /dev/null +++ b/zkp/examples/external_product.rs @@ -0,0 +1,311 @@ +use algebra::utils::Transcript; +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::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use sha2::Sha256; +use std::sync::Arc; +use std::time::Instant; +use std::vec; +use zkp::piop::external_product::{ + ExternalProductParams, ExternalProductProof, ExternalProductProver, ExternalProductVerifier, +}; +use zkp::piop::ntt::BitsOrder; +use zkp::piop::{ + BatchNTTInstanceInfo, BitDecompositionInstanceInfo, ExternalProductInstance, RlweCiphertext, + RlweCiphertextVector, +}; + +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 = 6; + +/// 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 +/// 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: (RlweCiphertextVector, RlweCiphertextVector), + bits_info: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, +) -> ExternalProductInstance { + // 1. Decompose the input of RLWE ciphertex + let bits_rlwe = RlweCiphertextVector { + a_vector: input_rlwe + .a + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len) + .iter() + .map(|d| d.as_ref().clone()) + .collect::>(), + b_vector: input_rlwe + .b + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len) + .iter() + .map(|d| d.as_ref().clone()) + .collect::>(), + }; + let (bits_rgsw_c_ntt, bits_rgsw_f_ntt) = input_rgsw; + + // 2. Compute the ntt form of the decomposed bits + let bits_rlwe_ntt = RlweCiphertextVector { + a_vector: bits_rlwe + .a_vector + .iter() + .map(|bit| { + DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + ) + }) + .collect(), + b_vector: bits_rlwe + .b_vector + .iter() + .map(|bit| { + DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + ) + }) + .collect(), + }; + + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rlwe_ntt.b_vector.len()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rgsw_c_ntt.a_vector.len()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rgsw_f_ntt.a_vector.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_vector, + &bits_rlwe_ntt.b_vector, + &bits_rgsw_c_ntt.a_vector, + &bits_rgsw_f_ntt.a_vector + ) { + 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_vector, + &bits_rlwe_ntt.b_vector, + &bits_rgsw_c_ntt.b_vector, + &bits_rgsw_f_ntt.b_vector + ) { + 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: DenseMultilinearExtension::from_evaluations_vec(num_vars, output_g_ntt), + b: DenseMultilinearExtension::from_evaluations_vec(num_vars, output_h_ntt), + }; + + ExternalProductInstance::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 = BitDecompositionInstanceInfo { + 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 = Arc::new(ntt_table); + let ntt_info = BatchNTTInstanceInfo { + 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_instance( + 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_instance( + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + ); + } + + // generate the random RLWE ciphertext + let input_rlwe = RlweCiphertext { + a: DenseMultilinearExtension::from_evaluations_slice(log_n, &coeffs), + b: 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); + + // Parameters. + let mut params = ExternalProductParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let block_size = 2; + let start = Instant::now(); + params.setup(&instance.info(), block_size, code_spec); + println!( + "external product setup time: {:?} ms", + start.elapsed().as_millis() + ); + + // Prover + let ep_prover = ExternalProductProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::new(); + + let start = Instant::now(); + let proof = ep_prover.prove( + &mut prover_trans, + ¶ms, + &instance, + block_size, + BitsOrder::Normal, + ); + println!( + "external product proving time: {:?} ms", + start.elapsed().as_millis() + ); + + let proof_bytes = proof.to_bytes().unwrap(); + println!("extenral product proof size: {:?} bytes", proof_bytes.len()); + // Verifier. + let ep_verifier = ExternalProductVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::new(); + + let proof = ExternalProductProof::from_bytes(&proof_bytes).unwrap(); + + let start = Instant::now(); + let res = ep_verifier.verify( + &mut verifier_trans, + ¶ms, + &instance.info(), + block_size, + BitsOrder::Normal, + &proof, + ); + println!( + "external product verifying time: {:?} ms", + start.elapsed().as_millis() + ); + assert!(res); +} diff --git a/zkp/examples/floor.rs b/zkp/examples/floor.rs new file mode 100644 index 00000000..48b0f2bf --- /dev/null +++ b/zkp/examples/floor.rs @@ -0,0 +1,139 @@ +use algebra::utils::Transcript; +use algebra::{BabyBear, BabyBearExetension, DenseMultilinearExtension}; +use algebra::{Field, FieldUniformSampler}; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use rand_distr::Distribution; +use sha2::Sha256; +use std::rc::Rc; +use std::time::Instant; +use zkp::piop::floor::{FloorParams, FloorProof, FloorProver, FloorVerifier}; +use zkp::piop::{BitDecompositionInstanceInfo, FloorInstance}; + +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 = 1024; // message space +const LOG_FT: usize = FT.next_power_of_two().ilog2() as usize; +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) -> FloorInstance { + 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 = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FT, + num_vars, + num_instances: 1, + }; + + let offset_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + >::new( + num_vars, + 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); + + let info = instance.info(); + println!("Prove {info}\n"); + + // Parameters. + let mut params = FloorParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let start = Instant::now(); + params.setup(&instance.info(), code_spec); + println!("floor setup time: {:?} ms", start.elapsed().as_millis()); + + // Prover. + let floor_prover = FloorProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let start = Instant::now(); + let proof = floor_prover.prove(&mut prover_trans, ¶ms, &instance); + println!("floor proving time: {:?} ms", start.elapsed().as_millis()); + + let proof_bytes = proof.to_bytes().unwrap(); + println!("floor proof size: {:?} byts", proof_bytes.len()); + + // Verifier. + let floor_verifier = FloorVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = FloorProof::from_bytes(&proof_bytes).unwrap(); + + let start = Instant::now(); + let res = floor_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); + println!("floor verifying time: {:?} ms", start.elapsed().as_millis()); + + assert!(res); +} diff --git a/zkp/examples/lift.rs b/zkp/examples/lift.rs new file mode 100644 index 00000000..deaf10d6 --- /dev/null +++ b/zkp/examples/lift.rs @@ -0,0 +1,149 @@ +use algebra::derive::{DecomposableField, Field}; +use algebra::utils::Transcript; +use algebra::{BabyBear, BabyBearExetension, DenseMultilinearExtension, SparsePolynomial}; +use algebra::{Field, FieldUniformSampler}; +use num_traits::{One, Zero}; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand_distr::Distribution; +use sha2::Sha256; +use std::rc::Rc; +use std::time::Instant; +use zkp::piop::lift::{LiftParams, LiftProof, LiftProver, LiftVerifier}; +use zkp::piop::{BitDecompositionInstanceInfo, LiftInstance}; + +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; + +#[derive(Field, DecomposableField)] +#[modulus = 1024] +pub struct Fq(u32); + +fn transform( + num_vars: usize, + input: FF, + q: FF, + dim_rlwe: FF, +) -> (DenseMultilinearExtension, SparsePolynomial) { + let factor = (FF::one() + FF::one()) * dim_rlwe / q; + let mapped_input = factor * input; + let mut output = vec![FF::zero(); 1 << num_vars]; + let mut sparse_output = SparsePolynomial::new(num_vars); + if mapped_input < dim_rlwe { + let idx = mapped_input.value() as usize; + output[idx] = FF::one(); + sparse_output.add_eval(idx, FF::one()); + } else { + let idx = (mapped_input - dim_rlwe).value() as usize; + output[idx] = -FF::one(); + sparse_output.add_eval(idx, -FF::one()); + } + ( + DenseMultilinearExtension::from_evaluations_vec(num_vars, output), + sparse_output, + ) +} + +fn main() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + let base_len = LOG_B; + let base: FF = FF::new(1 << base_len); + let num_vars = LOG_DIM_RLWE; + let q = FF::new(Fq::MODULUS_VALUE); + let dim_rlwe = FF::new(1 << num_vars); + + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..1 << num_vars) + .map(|_| FF::new(uniform.sample(&mut rng).value())) + .collect(), + )); + let mut outputs = Vec::with_capacity(1 << num_vars); + let mut sparse_outputs = Vec::with_capacity(1 << num_vars); + for x in input.iter() { + let (output, sparse_output) = transform(num_vars, *x, q, dim_rlwe); + outputs.push(Rc::new(output)); + sparse_outputs.push(Rc::new(sparse_output)); + } + + let bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: num_vars, + num_vars, + num_instances: 0, + }; + + let instance = LiftInstance::new( + num_vars, + q, + dim_rlwe, + &input, + &outputs, + &sparse_outputs, + &bits_info, + ); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + let info = instance.info(); + println!("Prove {info}\n"); + + // Parameters. + let mut params = LiftParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + + let start = Instant::now(); + params.setup(&instance.info(), code_spec); + println!("lift setup time: {:?} ms", start.elapsed().as_millis()); + + // Prover. + let floor_prover = LiftProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let start = Instant::now(); + let proof = floor_prover.prove(&mut prover_trans, ¶ms, &instance); + println!("lift proving time: {:?} ms", start.elapsed().as_millis()); + + let proof_bytes = proof.to_bytes().unwrap(); + println!("lift proof size: {:?} bytes", proof_bytes.len()); + + // Verifier. + let floor_verifier = LiftVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = LiftProof::from_bytes(&proof_bytes).unwrap(); + + let start = Instant::now(); + let res = floor_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); + println!("lift verifying time: {:?} ms", start.elapsed().as_millis()); + + assert!(res); +} diff --git a/zkp/examples/lookup.rs b/zkp/examples/lookup.rs new file mode 100644 index 00000000..d2293778 --- /dev/null +++ b/zkp/examples/lookup.rs @@ -0,0 +1,92 @@ +use std::time::Instant; + +use algebra::{utils::Transcript, BabyBear, BabyBearExetension, DenseMultilinearExtension, Field}; +use num_traits::Zero; +use pcs::{ + multilinear::BrakedownPCS, + utils::code::{ExpanderCode, ExpanderCodeSpec}, +}; +use rand::prelude::*; +use sha2::Sha256; +use zkp::piop::{ + lookup::{LookupParams, LookupProof, LookupProver, LookupVerifier}, + LookupInstance, +}; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +fn main() { + let num_vars = 10; + let block_size = 2; + let lookup_num = 2; + let range = 1024; + + 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(); + 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 = DenseMultilinearExtension::from_evaluations_vec(num_vars, t_evaluations); + + let instance = LookupInstance::from_slice(&f_vec, t, block_size); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + let info = instance.info(); + println!("Prove {info}\n"); + + // Parameters. + let mut params = LookupParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + + let start = Instant::now(); + params.setup(&instance.info(), code_spec); + println!("lookup setup time: {:?} ms", start.elapsed().as_millis()); + + // Prover. + let lookup_prover = LookupProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let start = Instant::now(); + let proof = lookup_prover.prove(&mut prover_trans, ¶ms, &instance); + println!("lookup proving time: {:?} ms", start.elapsed().as_millis()); + + let proof_bytes = proof.to_bytes().unwrap(); + println!("lookup proof size: {:?} bytes", proof_bytes.len()); + // Verifier. + let lookup_verifier = LookupVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = LookupProof::from_bytes(&proof_bytes).unwrap(); + let start = Instant::now(); + let res = lookup_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); + println!( + "lookup verifying time: {:?} ms", + start.elapsed().as_millis() + ); + assert!(res); +} diff --git a/zkp/examples/ntt.rs b/zkp/examples/ntt.rs new file mode 100644 index 00000000..f5a379b2 --- /dev/null +++ b/zkp/examples/ntt.rs @@ -0,0 +1,178 @@ +use algebra::utils::Transcript; +use algebra::{transformation::AbstractNTT, NTTField, Polynomial}; +use algebra::{BabyBear, BabyBearExetension}; +use algebra::{DenseMultilinearExtension, Field}; +use num_traits::One; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use sha2::Sha256; +use std::rc::Rc; +use std::sync::Arc; +use std::time::Instant; +use zkp::piop::ntt::{BatchNTTInstance, BitsOrder, NTTParams}; +use zkp::piop::ntt::{NTTProof, NTTProver, NTTVerifier}; + +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 order +/// 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 BatchNTTInstance, + 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_instance(&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 = Arc::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); + + let info = ntt_instances.info(); + println!("Prove {info}\n"); + + // Parameters. + let mut params = NTTParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let start = Instant::now(); + params.setup(&ntt_instances.info(), code_spec); + println!("ntt setup time: {:?} ms", start.elapsed().as_millis()); + + // Prover. + let ntt_prover = NTTProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + + let mut prover_trans = Transcript::::default(); + + let start = Instant::now(); + let proof = ntt_prover.prove( + &mut prover_trans, + ¶ms, + &ntt_instances, + BitsOrder::Normal, + ); + println!("ntt proving time: {:?} ms", start.elapsed().as_millis()); + + let proof_bytes = proof.to_bytes().unwrap(); + println!("ntt proof size: {:?} byts", proof_bytes.len()); + + // Verifier. + let ntt_verifier = NTTVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + + let mut verifier_trans = Transcript::::default(); + + let proof = NTTProof::from_bytes(&proof_bytes).unwrap(); + + let start = Instant::now(); + let res = ntt_verifier.verify( + &mut verifier_trans, + ¶ms, + &ntt_instances.info(), + &proof, + BitsOrder::Normal, + ); + + println!("ntt verifying time: {:?} ms", start.elapsed().as_millis()); + assert!(res); +} diff --git a/zkp/examples/round.rs b/zkp/examples/round.rs new file mode 100644 index 00000000..4d9d0573 --- /dev/null +++ b/zkp/examples/round.rs @@ -0,0 +1,141 @@ +use algebra::utils::Transcript; +use algebra::{BabyBear, BabyBearExetension, DenseMultilinearExtension}; +use algebra::{Field, FieldUniformSampler}; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::prelude::*; +use rand_distr::Distribution; +use sha2::Sha256; +use std::rc::Rc; +use std::time::Instant; +use zkp::piop::round::{RoundParams, RoundProof, RoundProver, RoundVerifier}; +use zkp::piop::{BitDecompositionInstanceInfo, 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 = 1024; // message space +const LOG_FT: usize = FT.next_power_of_two().ilog2() as usize; +const BASE_LEN: u32 = 1; +const FK: u32 = (FP - 1) / (2 * FT); +const LOG_2FK: u32 = (2 * FK).next_power_of_two().ilog2(); +const DELTA: u32 = (1 << LOG_2FK) - (2 * FK); + +#[inline] +fn decode(c: FF) -> u32 { + (c.value() as f64 * FT as f64 / FP as f64).round() as u32 % FT +} + +fn generate_instance(num_vars: usize) -> RoundInstance { + let q = FF::new(FT); + let k = FF::new(FK); + let k_bits_len = LOG_2FK 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 = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FT, + num_vars, + num_instances: 2, + }; + + let offset_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + >::new( + num_vars, + q, + 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); + + let info = instance.info(); + println!("Prove {info}\n"); + + // Parameters. + let mut params = RoundParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let start = Instant::now(); + params.setup(&instance.info(), code_spec); + println!("round setup time: {:?} ms", start.elapsed().as_millis()); + + // Prover. + let floor_prover = RoundProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let start = Instant::now(); + let proof = floor_prover.prove(&mut prover_trans, ¶ms, &instance); + println!("round proving time: {:?} ms", start.elapsed().as_millis()); + + let proof_bytes = proof.to_bytes().unwrap(); + println!("round proof size: {:?} byts", proof_bytes.len()); + + // Verifier. + let floor_verifier = RoundVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = RoundProof::from_bytes(&proof_bytes).unwrap(); + + let start = Instant::now(); + let res = floor_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); + println!("round verifying time: {:?} ms", start.elapsed().as_millis()); + + assert!(res); +} diff --git a/zkp/src/piop/accumulator.rs b/zkp/src/piop/accumulator.rs index 043c6d51..9858681d 100644 --- a/zkp/src/piop/accumulator.rs +++ b/zkp/src/piop/accumulator.rs @@ -1,119 +1,372 @@ //! IOP for Accumulator updating t times //! ACC = ACC + (X^{-a_u} - 1) * ACC * RGSW(Z_u) //! 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 std::marker::PhantomData; -use std::rc::Rc; +use crate::{ + piop::LookupIOP, + sumcheck::{self, verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}, +}; + +use core::fmt; +use std::{marker::PhantomData, rc::Rc, sync::Arc}; +use super::{ + external_product::{ + ExternalProductInstanceEval, ExternalProductInstanceInfo, ExternalProductInstanceInfoClean, + RlweEval, + }, + ntt::{BatchNTTInstanceInfoClean, BitsOrder, NTTRecursiveProof}, + BatchNTTInstanceInfo, BitDecompositionInstance, BitDecompositionInstanceInfo, + ExternalProductIOP, ExternalProductInstance, LookupInstance, LookupInstanceEval, + LookupInstanceInfo, NTTBareIOP, NTTInstance, RlweCiphertext, NTTIOP, +}; +use crate::utils::{ + add_assign_ef, eval_identity_function, gen_identity_evaluations, verify_oracle_relation, +}; use algebra::{ - DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, MultilinearExtension, - PolynomialInfo, + utils::Transcript, AbstractExtensionField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, 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}; - -/// SNARKs for Multiplication between RLWE ciphertext and RGSW ciphertext -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, -} +use bincode::Result; +use itertools::{izip, Itertools}; +use pcs::PolynomialCommitmentScheme; +use rayon::{ + iter::{ + IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, + ParallelIterator, + }, + slice::ParallelSlice, +}; +use serde::{Deserialize, Serialize}; -/// accumulator witness when performing ACC = ACC + (X^{-a_u} + 1) * ACC * RGSW(Z_u) +/// The Accumulator witness when performing ACC = ACC + (X^{-a_u} - 1) * ACC * RGSW(Z_u) +#[derive(Debug, Clone)] pub struct AccumulatorWitness { - /// * Witness when performing input_rlwe_ntt := (X^{-a_u} + 1) * ACC - /// - /// accumulator of ntt form - pub accumulator_ntt: RlweCiphertext, - /// scalar d = (X^{-a_u} + 1) of coefficient form - pub d: Rc>, - /// scalar d = (X^{-a_u} + 1) of ntt form - pub d_ntt: Rc>, - /// result d * ACC of ntt form + /// The NTT form of ACC. + pub acc_ntt: RlweCiphertext, + /// The coefficient form of scalar d = (X^{-a_u} - 1). + pub d: DenseMultilinearExtension, + /// The NTT form of scalar d = (X^{-a_u} - 1). + pub d_ntt: DenseMultilinearExtension, + /// The NTT form of d * ACC. 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, + /// The output of input_rlwe * RGSW(Z_u) + pub rlwe_mult_rgsw: ExternalProductInstance, +} + +/// Evaluation of AccumulatorWitnessEval at the same random point +#[derive(Serialize, Deserialize)] +pub struct AccumulatorWitnessEval { + /// The evaluation of the NTT form of ACC. + pub acc_ntt: RlweEval, + /// The evaluation of the coefficient form of scalar d. + pub d: F, + /// The evaluation of the NTT form of scalar d. + pub d_ntt: F, + /// The evaluation of the NTT form of d * ACC. + pub input_rlwe_ntt: RlweEval, + /// The evaluation of the ExternalProduct. + pub rlwe_mult_rgsw: ExternalProductInstanceEval, } /// Store the ntt instance, bit decomposition instance, and the sumcheck instance for an Accumulator updating `t` times pub struct AccumulatorInstance { - /// number of updations in Accumulator denoted by t + /// The number of variables. + pub num_vars: usize, + /// The number of updations. 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, + /// The input of the Accumulator, represented in coefficient form + pub input: RlweCiphertext, + /// The witnesses stored in updations + pub updations: Vec>, + /// The output of the Accumulator, represented in NTT form + pub output_ntt: RlweCiphertext, + /// The output of the Accumulator, represented in coefficient form + pub output: RlweCiphertext, + /// The info for RLWE * RGSW + pub mult_info: ExternalProductInstanceInfo, + /// The info for decomposed bits + pub bits_info: BitDecompositionInstanceInfo, + /// The info for NTT + pub ntt_info: BatchNTTInstanceInfo, +} + +/// Evaluation of AccumulatorInstance at the same random point +#[derive(Default, Serialize, Deserialize)] +pub struct AccumulatorInstanceEval { + /// The evaluation of the input of the Accumulator in coefficient form. + pub input: RlweEval, + /// The evaluations of the witnesses stored in updations. + pub updations: Vec>, + /// The evaluation of the output of the Accumulator in NTT form. + pub output_ntt: RlweEval, + /// The evaluation of the output of the Accumulator in coefficient form. + pub output: RlweEval, } /// Store the Accumulator info used to verify pub struct AccumulatorInstanceInfo { - /// number of updations in Accumulator denoted by t + /// The number of variables. + pub num_vars: usize, + /// The number of updations. pub num_updations: usize, - /// info to verify ntt - pub ntt_info: NTTInstanceInfo, - /// info to verify bit decomposition - pub decomposed_bits_info: DecomposedBitsInfo, - /// info to verify sumcheck - pub poly_info: PolynomialInfo, + /// The info for RLWE * RGSW. + pub mult_info: ExternalProductInstanceInfo, + /// The info for decomposed bits. + pub bits_info: BitDecompositionInstanceInfo, + /// The info for NTT. + pub ntt_info: BatchNTTInstanceInfo, +} + +/// Stores the information to be hashed. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AccumulatorInstanceInfoClean { + /// The number of variables. + pub num_vars: usize, + /// The number of updations. + pub num_updations: usize, + /// The info for RLWE * RGSW. + pub mult_info: ExternalProductInstanceInfoClean, + /// The info for decomposed bits. + pub bits_info: BitDecompositionInstanceInfo, + /// The info for NTT. + pub ntt_info: BatchNTTInstanceInfoClean, +} + +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 AccumulatorInstanceInfo { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 6 + self.num_updations * (6 + self.mult_info.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 + } + + /// Generate the number of variables in the committed polynomial. + #[inline] + pub fn generate_num_var(&self) -> usize { + self.num_vars + self.log_num_oracles() + } + + /// Construct an EF version. + #[inline] + pub fn to_ef>(&self) -> AccumulatorInstanceInfo { + AccumulatorInstanceInfo { + num_vars: self.num_vars, + num_updations: self.num_updations, + mult_info: self.mult_info.to_ef(), + bits_info: self.bits_info.to_ef(), + ntt_info: self.ntt_info.to_ef(), + } + } + + /// Convert to clean info. + #[inline] + pub fn to_clean(&self) -> AccumulatorInstanceInfoClean { + AccumulatorInstanceInfoClean { + num_vars: self.num_vars, + num_updations: self.num_updations, + mult_info: self.mult_info.to_clean(), + bits_info: self.bits_info.clone(), + ntt_info: self.ntt_info.to_clean(), + } + } + + /// Extract lookup info. + #[inline] + pub fn extract_lookup_info(&self, block_size: usize) -> LookupInstanceInfo { + LookupInstanceInfo { + num_vars: self.num_vars, + num_batch: 2 * self.bits_info.bits_len * self.num_updations, + block_size, + block_num: (2 * self.bits_info.bits_len * self.num_updations + block_size) / block_size, + } + } +} + +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 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: self.d.to_ef::(), + d_ntt: 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), + } + } + + /// Evaluate at the same random point defined over EF + #[inline] + pub fn evaluate_ext_opt>( + &self, + point: &DenseMultilinearExtension, + ) -> AccumulatorWitnessEval { + AccumulatorWitnessEval { + acc_ntt: self.acc_ntt.evaluate_ext_opt(point), + d: self.d.evaluate_ext_opt(point), + d_ntt: self.d_ntt.evaluate_ext_opt(point), + input_rlwe_ntt: self.input_rlwe_ntt.evaluate_ext_opt(point), + rlwe_mult_rgsw: self.rlwe_mult_rgsw.evaluate_ext_opt(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); + *r_points += (r_used[0], &self.d_ntt); + // input_rlwe ==NTT== input_rlwe_ntt + *r_coeffs += (r_used[1], &self.rlwe_mult_rgsw.input_rlwe.a); + *r_points += (r_used[1], &self.input_rlwe_ntt.a); + *r_coeffs += (r_used[2], &self.rlwe_mult_rgsw.input_rlwe.b); + *r_points += (r_used[2], &self.input_rlwe_ntt.b); + + 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); + add_assign_ef(r_points, &r_used[0], &self.d_ntt); + + // input_rlwe ==NTT== input_rlwe_ntt + add_assign_ef(r_coeffs, &r_used[1], &self.rlwe_mult_rgsw.input_rlwe.a); + add_assign_ef(r_points, &r_used[1], &self.input_rlwe_ntt.a); + add_assign_ef(r_coeffs, &r_used[2], &self.rlwe_mult_rgsw.input_rlwe.b); + add_assign_ef(r_points, &r_used[2], &self.input_rlwe_ntt.b); + + 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, - ntt_info: &NTTInstanceInfo, - decom_info: &DecomposedBitsInfo, + num_updations: usize, + input: RlweCiphertext, + updations: Vec>, + output_ntt: RlweCiphertext, + output: RlweCiphertext, + bits_info: &BitDecompositionInstanceInfo, + ntt_info: &BatchNTTInstanceInfo, ) -> Self { + let ntt_info = BatchNTTInstanceInfo:: { + num_ntt: 4 + num_updations * updations[0].num_ntt_contained(), + num_vars, + ntt_table: Arc::clone(&ntt_info.ntt_table), + }; + + let bits_info = BitDecompositionInstanceInfo:: { + 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 +374,1074 @@ 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 NTT instances contained + #[inline] + pub fn num_ntt_contained(&self) -> usize { + 4 + self.num_updations * self.updations[0].num_ntt_contained() + } - // 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, + /// 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()); + res.extend::>( + self.updations + .par_iter() + .flat_map(|updation| updation.pack_all_mles()) + .collect(), ); + 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 info = self.info(); + let num_vars = info.generate_num_var(); + let num_zeros_padded = (1 << num_vars) - info.num_oracles() * (1 << self.num_vars); - // 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); + let mut evals = self.pack_all_mles(); + evals.extend(&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 + .par_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::(), } - // 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); + } + + /// Evaluate at the same random point + #[inline] + pub fn evaluate(&self, point: &[F]) -> AccumulatorInstanceEval { + AccumulatorInstanceEval:: { + 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(), } + } - // 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 defined over EF + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> AccumulatorInstanceEval { + AccumulatorInstanceEval:: { + input: self.input.evaluate_ext(point), + output_ntt: self.output_ntt.evaluate_ext(point), + output: self.output.evaluate_ext(point), + updations: self + .updations + .par_iter() + .map(|updation| updation.evaluate_ext(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 defined over EF + #[inline] + pub fn evaluate_ext_opt>( + &self, + point: &DenseMultilinearExtension, + ) -> AccumulatorInstanceEval { + AccumulatorInstanceEval:: { + input: self.input.evaluate_ext_opt(point), + output_ntt: self.output_ntt.evaluate_ext_opt(point), + output: self.output.evaluate_ext_opt(point), + updations: self + .updations + .par_iter() + .map(|updation| updation.evaluate_ext_opt(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 = DenseMultilinearExtension::::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 = DenseMultilinearExtension::::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); + random_points += (r_used[0], &input_ntt.a); + random_coeffs += (r_used[1], &self.input.b); + random_points += (r_used[1], &input_ntt.b); + + // output_ntt ==NTT== output + random_coeffs += (r_used[2], &self.output.a); + random_points += (r_used[2], &self.output_ntt.a); + random_coeffs += (r_used[3], &self.output.b); + random_points += (r_used[3], &self.output_ntt.b); + + let r_each_num = self.updations[0].num_ntt_contained(); + // ntts in each accumulator + + let zero = DenseMultilinearExtension { + num_vars: self.num_vars, + evaluations: vec![F::zero(); 1 << self.num_vars], + }; + + let mut par_random_coeffs: Vec> = + vec![zero.clone(); self.updations.len()]; + let mut par_random_points: Vec> = + vec![zero.clone(); self.updations.len()]; + + self.updations + .par_iter() + .zip(r.par_chunks_exact(r_each_num)) + .zip(par_random_coeffs.par_iter_mut()) + .zip(par_random_points.par_iter_mut()) + .for_each(|(((updation, r_each), coeffs), points)| { + updation.update_ntt_instance(coeffs, points, r_each); + }); + + let random_coeffs = par_random_coeffs + .iter() + .fold(random_coeffs, |acc, x| acc + x); + + let random_points = par_random_points + .iter() + .fold(random_points, |acc, x| acc + x); + + 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 decomposed bits + #[inline] + pub fn extract_decomposed_bits(&self) -> BitDecompositionInstance { + let mut res = BitDecompositionInstance { + 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 * self.num_updations), + d_bits: Vec::with_capacity(2 * self.bits_info.bits_len * self.num_updations), + }; + for updation in &self.updations { + updation.rlwe_mult_rgsw.update_decomposed_bits(&mut res); + } + res + } + + /// Extract lookup instance + #[inline] + pub fn extract_lookup_instance(&self, block_size: usize) -> LookupInstance { + self.extract_decomposed_bits() + .extract_lookup_instance(block_size) } } -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 AccumulatorInstanceEval { + /// 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 coefficient 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 +/// IOP for Accumulator +#[derive(Default)] +pub struct AccumulatorIOP { + /// The random vector for random linear combination. + pub randomness: Vec, + /// The random vector for ntt. + pub randomness_ntt: Vec, + /// The random value for identity function. + pub u: Vec, +} + +impl AccumulatorIOP { + /// Sample the random coins before proving sumcheck protocol /// - /// # 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 + /// # Arguments. + /// + /// * `trans` - The transcripts. + pub fn sample_coins(trans: &mut Transcript, info: &AccumulatorInstanceInfo) -> Vec { + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(info), + ) + } + + /// Return the number of random coins used in this IOP + pub fn num_coins(info: &AccumulatorInstanceInfo) -> usize { + info.num_updations * (ExternalProductIOP::::num_coins() + 2) + } + + /// Generate the randomness. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The accumulator instance info. + #[inline] + pub fn generate_randomness( + &mut self, + trans: &mut Transcript, + info: &AccumulatorInstanceInfo, + ) { + self.randomness = Self::sample_coins(trans, info); + self.randomness_ntt = NTTIOP::::sample_coins(trans, &info.ntt_info.to_clean()); + self.u = trans.get_vec_challenge( + b"ACC IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + + /// Prove accumulator updating `num_updations`` times. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `instance` - The external product instance. + /// * `lookup_instance` - The extracted lookup instance. + /// * `lookup_iop` - The lookup IOP. + /// * `bits_order` - The indicator of bits order. + #[inline] + pub fn prove( + &self, + trans: &mut Transcript, + instance: &AccumulatorInstance, + lookup_instance: &LookupInstance, + lookup_iop: &LookupIOP, + bits_order: BitsOrder, + ) -> (SumcheckKit, NTTRecursiveProof) { + let eq_at_u = Rc::new(gen_identity_evaluations(&self.u)); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let mut claimed_sum = F::zero(); + + // add sumcheck products (without NTT) into poly + Self::prepare_products_of_polynomial(&self.randomness, &mut poly, instance, &eq_at_u); + + // add sumcheck_products of NTT into poly + let ntt_instance = instance.extract_ntt_instance(&self.randomness_ntt); + + NTTBareIOP::::prepare_products_of_polynomial( + F::one(), + &mut poly, + &mut claimed_sum, + &ntt_instance, + &self.u, + bits_order, + ); + + LookupIOP::::prepare_products_of_polynomial( + &lookup_iop.randomness, + &mut poly, + lookup_instance, + &eq_at_u, + ); + + // prove all sumcheck protocol into a large random sumcheck + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + // prove F(u, v) in a recursive manner + let recursive_proof = NTTIOP::::prove_recursion( + trans, + &state.randomness, + &ntt_instance.info(), + &self.u, + bits_order, + ); + + ( + SumcheckKit { + proof, + claimed_sum, + info: poly.info(), + u: self.u.clone(), + randomness: state.randomness, + }, + recursive_proof, + ) + } + + /// Prover Accumulator + #[inline] + pub fn prepare_products_of_polynomial( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &AccumulatorInstance, + eq_at_u: &Rc>, + ) { + let r_each_num = ExternalProductIOP::::num_coins() + 2; + assert_eq!(randomness.len(), instance.num_updations * r_each_num); + + // in other updations, acc_ntt = acc_ntt (in last updation) + output_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::new(updation.d_ntt.clone()), + Rc::new(updation.acc_ntt.a.clone()), + eq_at_u.clone(), + ], + r[0], + ); + poly.add_product( + [ + Rc::new(updation.input_rlwe_ntt.a.clone()), + 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::new(updation.d_ntt.clone()), + Rc::new(updation.acc_ntt.b.clone()), + eq_at_u.clone(), + ], + r[1], + ); + poly.add_product( + [ + Rc::new(updation.input_rlwe_ntt.b.clone()), + Rc::clone(eq_at_u), + ], + -r[1], + ); + + // step2: RLWE * RGSW + ExternalProductIOP::::prepare_products_of_polynomial( + r_mult, + poly, + &updation.rlwe_mult_rgsw, + eq_at_u, + ); + } + } + + /// Verify the accumulator updating `num_updations` times + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `wrapper` - The proof wrapper. + /// * `evals_at_r` - The evaluation points at r. + /// * `evals_at_u` - The evaluation points at u. + /// * `info` - The external product info. + /// * `lookup_info` - The derived lookup info. + /// * `recursive_proof` - The recursive sumcheck proof. + /// * `lookup_evals` - The extracted lookup instance evaluations. + /// * `lookup_iop` - The lookup IOP. + /// * `bits_order` - The indicator of bits order. + #[inline] #[allow(clippy::too_many_arguments)] - pub fn verify_subclaim( + pub fn verify( &self, - u: &[F], - randomness_ntt: &[F], - randomness_sumcheck: &[F], - ntt_coeffs: &DenseMultilinearExtension, - ntt_points: &DenseMultilinearExtension, - witnesses: &Vec>, + trans: &mut Transcript, + wrapper: &mut ProofWrapper, + evals_at_r: &AccumulatorInstanceEval, + evals_at_u: &AccumulatorInstanceEval, info: &AccumulatorInstanceInfo, - ) -> 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); - } + lookup_info: &LookupInstanceInfo, + recursive_proof: &NTTRecursiveProof, + lookup_evals: &LookupInstanceEval, + lookup_iop: &LookupIOP, + bits_order: BitsOrder, + ) -> (bool, Vec) { + let mut subclaim = + MLSumcheck::verify(trans, &wrapper.info, wrapper.claimed_sum, &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&self.u, &subclaim.point); - 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) { - return false; + // check the sumcheck evaluation (without NTT) + if !Self::verify_subclaim(&self.randomness, &mut subclaim, evals_at_r, info, eq_at_u_r) { + return (false, vec![]); } - // 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; - } + 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, &self.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, &self.randomness_ntt); - // 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 !NTTBareIOP::::verify_subclaim( + F::one(), + &mut subclaim, + &mut wrapper.claimed_sum, + ntt_coeff_evals_at_r, + ntt_point_evals_at_u, + f_delegation, + ) { + return (false, vec![]); } - if !self.bit_decomposition_subclaim.verify_subclaim( - &d_val, - &d_bits, - u, - &info.decomposed_bits_info, + + if !LookupIOP::::verify_subclaim( + &lookup_iop.randomness, + &mut subclaim, + lookup_evals, + lookup_info, + eq_at_u_r, ) { - return false; + return (false, vec![]); } - 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 + if !(subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero()) { + return (false, vec![]); + } + let res = NTTIOP::::verify_recursion( + trans, + recursive_proof, + &info.ntt_info, + &self.u, + &subclaim.point, + bits_order, + ); + + (res, subclaim.point) + } + + /// Verify the sumcheck part of accumulator updations (not including NTT part) + #[inline] + pub fn verify_subclaim( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &AccumulatorInstanceEval, + info: &AccumulatorInstanceInfo, + eq_at_u_r: F, + ) -> bool { + let r_each_num = ExternalProductIOP::::num_coins() + 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 !ExternalProductIOP::verify_subclaim( + r_mult, + subclaim, + &updation.rlwe_mult_rgsw, + &info.mult_info, + eq_at_u_r, ) { - 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)); + return false; } + } - // 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 - ) { - 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)); + // 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; } + } + true + } +} + +/// Accumulator proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct AccumulatorProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info. + pub poly_info: PolynomialInfo, + /// The first polynomial commitment. + pub first_comm: Pcs::Commitment, + /// The evaluation of the first packed polynomial. + pub first_oracle_eval_at_r: EF, + /// The opening of the first polynomial. + pub first_eval_proof_at_r: Pcs::Proof, + /// The evaluation of the first packed polynomial. + pub first_oracle_eval_at_u: EF, + /// The opening of the first polynomial. + pub first_eval_proof_at_u: Pcs::Proof, + /// The second polynomial commitment. + pub second_comm: Pcs::Commitment, + /// The evaluation of the second packed polynomial. + pub second_oracle_eval: EF, + /// The opening proof of the second polynomial. + pub second_eval_proof: Pcs::ProofEF, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// NTT recursive proof. + pub recursive_proof: NTTRecursiveProof, + /// The accumulator evaluations. + pub acc_evals_at_r: AccumulatorInstanceEval, + /// The accumulator evaluations. + pub acc_evals_at_u: AccumulatorInstanceEval, + /// The lookup evaluations. + pub lookup_evals: LookupInstanceEval, + /// The claimed sum from sumcheck. + pub claimed_sum: EF, +} + +impl AccumulatorProof +where + F: Field, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) + } + + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// Accumulator parameters. +pub struct AccumulatorParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameters for the first polynomial. + pub pp_first: Pcs::Parameters, + /// The parameters for the second polynomial. + pub pp_second: Pcs::Parameters, +} + +impl Default for AccumulatorParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp_first: Pcs::Parameters::default(), + pp_second: Pcs::Parameters::default(), + } + } +} + +impl AccumulatorParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + pub fn setup(&mut self, info: &AccumulatorInstanceInfo, block_size: usize, code_spec: S) { + self.pp_first = Pcs::setup(info.generate_num_var(), Some(code_spec.clone())); + + let lookup_info = info.extract_lookup_info(block_size); + self.pp_second = Pcs::setup(lookup_info.generate_second_num_var(), Some(code_spec)); + } +} + +/// Prover for Accumulator with PCS. +pub struct AccumulatorProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} - 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))) +impl Default for AccumulatorProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + AccumulatorProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, } - sum_eval == self.sumcheck_subclaim.expected_evaluations + } +} + +impl AccumulatorProver +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: PolynomialCommitmentScheme< + F, + EF, + S, + Polynomial = DenseMultilinearExtension, + EFPolynomial = DenseMultilinearExtension, + Point = EF, + >, +{ + /// The prover. + pub fn prove( + &self, + trans: &mut Transcript, + params: &AccumulatorParams, + instance: &AccumulatorInstance, + block_size: usize, + bits_order: BitsOrder, + ) -> AccumulatorProof { + let instance_info = instance.info(); + println!("Prove {instance_info}\n"); + + // It is better to hash the shared public instance information, including the table. + trans.append_message(b"accumulator instance", &instance_info.to_clean()); + + // 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 first_committed_poly = instance.generate_oracle(); + + // Use PCS to commit the above polynomial. + let (first_comm, first_comm_state) = Pcs::commit(¶ms.pp_first, &first_committed_poly); + + trans.append_message(b"ACC IOP: first commitment", &first_comm); + + println!( + "first polynomial has {:?} variables", + first_committed_poly.num_vars + ); + + // Convert the original instance into an instance defined over EF + let instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); + + // IOPs + let mut acc_iop = AccumulatorIOP::::default(); + let mut lookup_iop = LookupIOP::::default(); + + // generate ranomness for ACC iop. + acc_iop.generate_randomness(trans, &instance_info); + + // --- Lookup instance and commitment --- + let mut lookup_instance = instance_ef.extract_lookup_instance(block_size); + + let lookup_info = lookup_instance.info(); + println!("- containing {lookup_info}\n"); + + // Generate the first randomness for lookup iop. + lookup_iop.prover_generate_first_randomness(trans, &mut lookup_instance); + + // Compute the packed second polynomials for lookup, i.e., h vector. + let second_committed_poly = lookup_instance.generate_second_oracle(); + + // Commit the second polynomial. + let (second_comm, second_comm_state) = + Pcs::commit_ef(¶ms.pp_second, &second_committed_poly); + + trans.append_message(b"ACC IOP: second commitment", &second_comm); + + println!( + "second polynomial has {:?} variables", + second_committed_poly.num_vars + ); + + lookup_iop.generate_second_randomness(trans, &lookup_info); + + let (kit, recursive_proof) = acc_iop.prove( + trans, + &instance_ef, + &lookup_instance, + &lookup_iop, + bits_order, + ); + + // Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + + // let evals_at_r = instance.evaluate_ext(&sumcheck_state.randomness); + // let evals_at_u = instance.evaluate_ext(&prover_u); + + // TODO: use evaluate_opt. + + // let acc_evals_at_r = instance.evaluate_ext(&kit.randomness); + // let acc_evals_at_u = instance.evaluate_ext(&acc_iop.u); + + let (acc_evals_at_r, acc_evals_at_u) = rayon::join( + || instance.evaluate_ext(&kit.randomness), + || instance.evaluate_ext(&acc_iop.u), + ); + + // --- Lookup Part --- + let lookup_evals = lookup_instance.evaluate(&kit.randomness); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point_at_r = kit.randomness.clone(); + let mut requested_point_at_u = acc_iop.u.clone(); + let oracle_randomness = trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.info().log_num_oracles(), + ); + + requested_point_at_r.extend(&oracle_randomness); + requested_point_at_u.extend(&oracle_randomness); + + let first_oracle_eval_at_r = first_committed_poly.evaluate_ext(&requested_point_at_r); + let first_oracle_eval_at_u = first_committed_poly.evaluate_ext(&requested_point_at_u); + + let mut second_requested_point = kit.randomness.clone(); + let second_oracle_randomness = trans.get_vec_challenge( + b"Lookup IOP: random linear combination of evaluations of second oracles", + lookup_info.log_num_second_oracles(), + ); + + second_requested_point.extend(&second_oracle_randomness); + + let second_oracle_eval = second_committed_poly.evaluate(&second_requested_point); + + // Generate the evaluation proof of the requested point + let mut opens = Pcs::batch_open( + ¶ms.pp_first, + &first_comm, + &first_comm_state, + &[requested_point_at_r.clone(), requested_point_at_u.clone()], + trans, + ); + + let first_eval_proof_at_r = std::mem::take(&mut opens[0]); + let first_eval_proof_at_u = std::mem::take(&mut opens[1]); + + let second_eval_proof = Pcs::open_ef( + ¶ms.pp_second, + &second_comm, + &second_comm_state, + &second_requested_point, + trans, + ); + + AccumulatorProof { + poly_info: kit.info, + first_comm, + first_oracle_eval_at_r, + first_eval_proof_at_r, + first_oracle_eval_at_u, + first_eval_proof_at_u, + second_comm, + second_oracle_eval, + second_eval_proof, + sumcheck_proof: kit.proof, + recursive_proof, + acc_evals_at_r, + acc_evals_at_u, + lookup_evals, + claimed_sum: kit.claimed_sum, + } + } +} + +/// Prover for accumulator with PCS. +pub struct AccumulatorVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for AccumulatorVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + AccumulatorVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl AccumulatorVerifier +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: PolynomialCommitmentScheme< + F, + EF, + S, + Polynomial = DenseMultilinearExtension, + EFPolynomial = DenseMultilinearExtension, + Point = EF, + >, +{ + /// The verifier. + pub fn verify( + &self, + trans: &mut Transcript, + params: &AccumulatorParams, + info: &AccumulatorInstanceInfo, + block_size: usize, + bits_order: BitsOrder, + proof: &AccumulatorProof, + ) -> bool { + let mut res = true; + + trans.append_message(b"accumulator instance", &info.to_clean()); + trans.append_message(b"ACC IOP: first commitment", &proof.first_comm); + + let mut acc_iop = AccumulatorIOP::::default(); + let mut lookup_iop = LookupIOP::::default(); + let info_ef = info.to_ef(); + + acc_iop.generate_randomness(trans, &info_ef); + lookup_iop.verifier_generate_first_randomness(trans); + + trans.append_message(b"ACC IOP: second commitment", &proof.second_comm); + + // Verify the proof of sumcheck protocol. + let lookup_info = info.extract_lookup_info(block_size); + lookup_iop.generate_second_randomness(trans, &lookup_info); + + let mut wrapper = ProofWrapper { + claimed_sum: proof.claimed_sum, + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), + }; + + let (b, randomness) = acc_iop.verify( + trans, + &mut wrapper, + &proof.acc_evals_at_r, + &proof.acc_evals_at_u, + &info_ef, + &lookup_info, + &proof.recursive_proof, + &proof.lookup_evals, + &lookup_iop, + bits_order, + ); + res &= b; + + // Check the relation between these small oracles and the committed oracle. + let mut requested_point_at_r = randomness.clone(); + let mut requested_point_at_u = acc_iop.u; + let flatten_evals_at_r = proof.acc_evals_at_r.flatten(); + let flatten_evals_at_u = proof.acc_evals_at_u.flatten(); + let oracle_randomness = trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + proof.acc_evals_at_r.log_num_oracles(), + ); + + requested_point_at_r.extend(&oracle_randomness); + requested_point_at_u.extend(&oracle_randomness); + + res &= verify_oracle_relation( + &flatten_evals_at_r, + proof.first_oracle_eval_at_r, + &oracle_randomness, + ); + + res &= verify_oracle_relation( + &flatten_evals_at_u, + proof.first_oracle_eval_at_u, + &oracle_randomness, + ); + + let mut second_requested_point = randomness; + let second_oracle_randomness = trans.get_vec_challenge( + b"Lookup IOP: random linear combination of evaluations of second oracles", + proof.lookup_evals.log_num_second_oracles(), + ); + + second_requested_point.extend(&second_oracle_randomness); + + res &= verify_oracle_relation( + &proof.lookup_evals.h_vec, + proof.second_oracle_eval, + &second_oracle_randomness, + ); + + res &= Pcs::batch_verify( + ¶ms.pp_first, + &proof.first_comm, + &[requested_point_at_r, requested_point_at_u], + &[proof.first_oracle_eval_at_r, proof.first_oracle_eval_at_u], + &[ + proof.first_eval_proof_at_r.clone(), + proof.first_eval_proof_at_u.clone(), + ], + trans, + ); + + res &= Pcs::verify_ef( + ¶ms.pp_second, + &proof.second_comm, + &second_requested_point, + proof.second_oracle_eval, + &proof.second_eval_proof, + trans, + ); + + res } } diff --git a/zkp/src/piop/addition_in_zq.rs b/zkp/src/piop/addition_in_zq.rs index 80107796..901e8856 100644 --- a/zkp/src/piop/addition_in_zq.rs +++ b/zkp/src/piop/addition_in_zq.rs @@ -10,67 +10,101 @@ //! 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::piop::BitDecomposition; -use crate::sumcheck::prover::ProverMsg; -use crate::utils::eval_identity_function; - -use crate::sumcheck::MLSumcheck; -use crate::utils::gen_identity_evaluations; +use crate::sumcheck::{self, verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}; +use crate::utils::{eval_identity_function, gen_identity_evaluations, verify_oracle_relation}; use algebra::{ - DecomposableField, DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, - MultilinearExtension, PolynomialInfo, + utils::Transcript, AbstractExtensionField, DecomposableField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, PolynomialInfo, }; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; - -/// SNARKs for addition in Zq, i.e. a + b = c (mod q) -pub struct AdditionInZq(PhantomData); - -/// 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 bincode::Result; +use core::fmt; +use pcs::PolynomialCommitmentScheme; +use serde::{Deserialize, Serialize}; +use std::{marker::PhantomData, rc::Rc}; -/// 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, -} +use super::{ + BitDecompositionEval, BitDecompositionIOP, BitDecompositionInstance, + BitDecompositionInstanceInfo, +}; /// Stores the parameters used for addition in Zq and the inputs and witness for prover. pub struct AdditionInZqInstance { - /// modulus in addition + /// The modulus in addition pub q: F, - /// number of variables + /// The number of variables pub num_vars: usize, - /// inputs a, b, and c + /// The inputs a, b, and c pub abc: Vec>>, - /// introduced witness k + /// The introduced witness k pub k: Rc>, + /// The introduced witness to check the range of a, b, c + pub abc_bits: Vec>>, + /// The info for decomposed bits + pub bits_info: BitDecompositionInstanceInfo, +} + +/// Evaluations of all MLEs involved in the instance at a random point +#[derive(Serialize, Deserialize)] +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: DecomposedBits, + pub abc_bits: Vec, } /// Stores the parameters used for addition in Zq and the public info for verifier. +#[derive(Serialize, Deserialize)] pub struct AdditionInZqInstanceInfo { - /// modulus in addition + /// The modulus in addition pub q: F, + /// The number of variables + pub num_vars: usize, /// Decomposition info for range check (i.e. bit decomposition) - pub decomposed_bits_info: DecomposedBitsInfo, + pub bits_info: BitDecompositionInstanceInfo, +} + +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 AdditionInZqInstanceInfo { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 4 + 3 * 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 + } + + /// Generate the number of variables in the commited polynomial. + #[inline] + pub fn generate_num_var(&self) -> usize { + self.num_vars + self.log_num_oracles() + } + + /// Construct an EF version. + #[inline] + pub fn to_ef>(&self) -> AdditionInZqInstanceInfo { + AdditionInZqInstanceInfo:: { + q: EF::from_base(self.q), + num_vars: self.num_vars, + bits_info: self.bits_info.to_ef(), + } + } } impl AdditionInZqInstance { @@ -79,206 +113,582 @@ 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 + /// 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() + .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. #[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 generate_oracle(&self) -> DenseMultilinearExtension { + let info = self.info(); + let num_vars = info.generate_num_var(); + let num_zeros_padded = (1 << num_vars) - info.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::(), } + } - let abc_bits = 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, - }, + /// 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) -> BitDecompositionInstance { + BitDecompositionInstance { + 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) -> BitDecompositionEval { + BitDecompositionEval { + 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: &BitDecompositionInstanceInfo, ) -> 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. +/// IOP for addition in Zq, i.e. a + b = c (mod q) +#[derive(Default)] +pub struct AdditionInZqIOP { + /// The randomness vector for random linear combination. + pub randomness: Vec, + /// The random value for identity function. + pub u: Vec, +} + +impl AdditionInZqIOP { + /// Sample coins before proving sumcheck protocol #[inline] - pub fn verify_subclaim( + pub fn sample_coins(trans: &mut Transcript, info: &AdditionInZqInstanceInfo) -> Vec { + trans.get_vec_challenge( + b"Add in Zq IOP: randomness to combine sumcheck protocols", + Self::num_coins(info), + ) + } + + /// Return the number of coins used in this IOP + #[inline] + pub fn num_coins(info: &AdditionInZqInstanceInfo) -> usize { + >::num_coins(&info.bits_info) + 1 + } + + /// Generate the randomness + #[inline] + pub fn generate_randomness( + &mut self, + trans: &mut Transcript, + info: &AdditionInZqInstanceInfo, + ) { + self.randomness = Self::sample_coins(trans, info); + } + + /// Generate the randomness for the eq function. + #[inline] + pub fn generate_randomness_for_eq_function( + &mut self, + trans: &mut Transcript, + info: &AdditionInZqInstanceInfo, + ) { + self.u = trans.get_vec_challenge( + b"Add in Zq IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + + /// AdditionInZqIOP prover. + pub fn prove( &self, - q: F, - abc: &[Rc>], - k: &DenseMultilinearExtension, - abc_bits: &[&Vec>>], - u: &[F], + trans: &mut Transcript, + instance: &AdditionInZqInstance, + ) -> SumcheckKit { + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let eq_at_u = Rc::new(gen_identity_evaluations(&self.u)); + Self::prepare_products_of_polynomial(&self.randomness, &mut poly, instance, &eq_at_u); + + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + info: poly.info(), + claimed_sum: F::zero(), + randomness: state.randomness, + u: self.u.clone(), + } + } + + /// Prove addition in Zq given a, b, c, k, and the decomposed bits for a, b, and c. + pub fn prepare_products_of_polynomial( + 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 + BitDecompositionIOP::prepare_products_of_polynomial( + &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( + &self, + trans: &mut Transcript, + wrapper: &ProofWrapper, + evals: &AdditionInZqInstanceEval, info: &AdditionInZqInstanceInfo, - ) -> bool { - assert_eq!(abc.len(), 3); - assert_eq!(abc_bits.len(), 3); + ) -> (bool, Vec) { + let mut subclaim = MLSumcheck::verify(trans, &wrapper.info, F::zero(), &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&self.u, &subclaim.point); - // 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) - { - return false; + if !Self::verify_subclaim(&self.randomness, &mut subclaim, evals, info, eq_at_u_r) { + return (false, vec![]); } - // 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(), subclaim.point) + } + + /// Verify the subclaim. + pub fn verify_subclaim( + 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_subclaim( + 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) +/// AdditionInZq proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct AdditionInZqProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info + pub poly_info: PolynomialInfo, + /// Polynomial commitment. + pub poly_comm: Pcs::Commitment, + /// The evaluation of the polynomial. + pub oracle_eval: EF, + /// The opening proof of the polynomial. + pub eval_proof: Pcs::Proof, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// The evaluations. + pub evals: AdditionInZqInstanceEval, +} + +impl AdditionInZqProof +where + F: Field + Serialize + for<'de> Deserialize<'de>, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) } - /// 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"); + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// Addition in Zq parameter. +pub struct AdditionInZqParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameter for the polynomial commitment. + pub pp: Pcs::Parameters, +} + +impl Default for AdditionInZqParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp: Pcs::Parameters::default(), + } + } +} + +impl AdditionInZqParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + pub fn setup(&mut self, info: &AdditionInZqInstanceInfo, code_spec: S) { + self.pp = Pcs::setup(info.generate_num_var(), Some(code_spec)); + } +} + +/// Prover for addition in Zq with PCS. +pub struct AdditionInZqProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for AdditionInZqProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + AdditionInZqProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl AdditionInZqProver +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The prover. + pub fn prove( + &self, + trans: &mut Transcript, + params: &AdditionInZqParams, + instance: &AdditionInZqInstance, + ) -> AdditionInZqProof { + let instance_info = instance.info(); + + trans.append_message(b"addition in Zq instance", &instance_info); + + // 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(); + + // Use PCS to commit the above polynomial. + let (poly_comm, poly_comm_state) = Pcs::commit(¶ms.pp, &committed_poly); + + trans.append_message(b"polynomial commitment", &poly_comm); + + // Prover generates the proof. + // Convert the orignal instance into an instance defined over EF. + let instance_ef = instance.to_ef::(); + let instance_ef_info = instance_ef.info(); + let mut add_iop = AdditionInZqIOP::::default(); + + add_iop.generate_randomness(trans, &instance_ef_info); + add_iop.generate_randomness_for_eq_function(trans, &instance_ef_info); + let kit = add_iop.prove(trans, &instance_ef); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point = kit.randomness.clone(); + let oracle_randomness = trans.get_vec_challenge( + b"random linear combinaiton for evaluations of the oracles", + instance_info.log_num_oracles(), + ); + requested_point.extend(&oracle_randomness); + + // Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let evals = instance_ef.evaluate(&kit.randomness); + + let oracle_eval = committed_poly.evaluate_ext(&requested_point); + + // Generate the evaluation proof of the requested point. + let eval_proof = Pcs::open( + ¶ms.pp, + &poly_comm, + &poly_comm_state, + &requested_point, + trans, + ); AdditionInZqProof { - rangecheck_msg, - sumcheck_msg: sumcheck_proof.0, + poly_info: kit.info, + poly_comm, + oracle_eval, + eval_proof, + sumcheck_proof: kit.proof, + evals, } } +} + +/// Verifier for addition in Zq with PCS. +pub struct AdditionInZqVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} - /// Verify addition in Zq given the proof and the verification key for bit decomposistion +impl Default for AdditionInZqVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + AdditionInZqVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl AdditionInZqVerifier +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The verifier. 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) - } - - /// 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, - ); + &self, + trans: &mut Transcript, + params: &AdditionInZqParams, + info: &AdditionInZqInstanceInfo, + proof: &AdditionInZqProof, + ) -> bool { + let mut res = true; + + trans.append_message(b"addition in Zq instance", info); + trans.append_message(b"polynomial commitment", &proof.poly_comm); + + let mut add_iop = AdditionInZqIOP::::default(); - // 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, + add_iop.generate_randomness(trans, &info.to_ef()); + add_iop.generate_randomness_for_eq_function(trans, &info.to_ef()); + + let proof_wrapper = ProofWrapper { + claimed_sum: EF::zero(), + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), }; - 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, - } + let (b, randomness) = add_iop.verify(trans, &proof_wrapper, &proof.evals, &info.to_ef()); + + res &= b; + + // Check the relation between these small oracles and the committed oracle. + let flatten_evals = proof.evals.flatten(); + let oracle_randomness = trans.get_vec_challenge( + b"random linear combinaiton for evaluations of the oracles", + proof.evals.log_num_oracles(), + ); + res &= verify_oracle_relation(&flatten_evals, proof.oracle_eval, &oracle_randomness); + + // Check the evaluation of a random point over the committed oracle. + let mut requested_point = randomness.clone(); + requested_point.extend(&oracle_randomness); + res &= Pcs::verify( + ¶ms.pp, + &proof.poly_comm, + &requested_point, + proof.oracle_eval, + &proof.eval_proof, + trans, + ); + + res } } diff --git a/zkp/src/piop/bit_decomposition.rs b/zkp/src/piop/bit_decomposition.rs index 439c3239..bcf577dd 100644 --- a/zkp/src/piop/bit_decomposition.rs +++ b/zkp/src/piop/bit_decomposition.rs @@ -19,53 +19,48 @@ //! 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 std::marker::PhantomData; -use std::rc::Rc; +use crate::sumcheck::{self, verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}; +use crate::utils::{eval_identity_function, gen_identity_evaluations, verify_oracle_relation}; +use algebra::{ + utils::Transcript, AbstractExtensionField, DecomposableField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, PolynomialInfo, +}; +use bincode::Result; +use core::fmt; +use itertools::izip; +use pcs::PolynomialCommitmentScheme; +use serde::{Deserialize, Serialize}; +use std::{marker::PhantomData, rc::Rc}; -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 -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, -} +use super::LookupInstance; /// Stores the parameters used for bit decomposation and every instance of decomposed bits, /// and the batched polynomial used for the sumcheck protocol. /// /// It is required to decompose over a power-of-2 base. /// The resulting decomposed bits are used as the prover key. -pub struct DecomposedBits { - /// base +pub struct BitDecompositionInstance { + /// The power-of-two base pub base: F, - /// the length of base, i.e. log_2(base) - pub base_len: u32, - /// the length of decomposed bits - pub bits_len: u32, - /// number of variables of every polynomial + /// The length of the base, i.e. log_2(base) + pub base_len: usize, + /// The length of decomposed bits + pub bits_len: usize, + /// The number of variables of every polynomial pub num_vars: usize, - /// batched plain deomposed bits, each of which corresponds to one bit decomposisiton instance - pub instances: Vec>>>, + /// The batched values to be decomposed into bits + pub d_val: Vec>>, + /// The batched plain deomposed bits, each of which corresponds to one bit decomposisiton instance + pub d_bits: Vec>>, +} + +/// Evaluations at a random point +#[derive(Serialize, Deserialize)] +pub struct BitDecompositionEval { + /// The batched values to be decomposed into bits + pub d_val: Vec, + /// The batched plain deomposed bits, each of which corresponds to one bit decomposisiton instance + pub d_bits: Vec, } /// Stores the parameters used for bit decomposation. @@ -73,54 +68,86 @@ pub struct DecomposedBits { /// * It is required to decompose over a power-of-2 base. /// /// These parameters are used as the verifier key. -#[derive(Clone)] -pub struct DecomposedBitsInfo { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BitDecompositionInstanceInfo { /// 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 DecomposedBits { +impl fmt::Display for BitDecompositionInstanceInfo { + 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 BitDecompositionInstanceInfo { + /// 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.num_instances + self.num_instances * 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 + } + + /// Generate the number of variables in the committed polynomial. + #[inline] + pub fn generate_num_var(&self) -> usize { + self.num_vars + self.log_num_oracles() + } + + /// Construct an EF version. + #[inline] + pub fn to_ef>(&self) -> BitDecompositionInstanceInfo { + BitDecompositionInstanceInfo:: { + 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 BitDecompositionInstance { #[inline] /// Extract the information of decomposed bits for verification - pub fn info(&self) -> DecomposedBitsInfo { - DecomposedBitsInfo { + pub fn info(&self) -> BitDecompositionInstanceInfo { + BitDecompositionInstanceInfo { base: self.base, 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 { - DecomposedBits { + pub fn new(base: F, base_len: usize, bits_len: usize, num_vars: usize) -> Self { + BitDecompositionInstance { base, base_len, bits_len, num_vars, - instances: Vec::new(), - } - } - - /// Initiate the polynomial from the given info used for sumcheck protocol - #[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), + d_val: Vec::new(), + d_bits: Vec::new(), } } @@ -129,193 +156,620 @@ 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 info = self.info(); + let num_vars = info.generate_num_var(); + let num_zeros_padded = (1 << num_vars) - info.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) -> BitDecompositionInstance { + BitDecompositionInstance:: { + 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::>(), } - poly + } + + /// Evaluate at a random point defined over Field + #[inline] + pub fn evaluate(&self, point: &[F]) -> BitDecompositionEval { + BitDecompositionEval:: { + 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], + ) -> BitDecompositionEval { + BitDecompositionEval:: { + 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(), + } + } + + /// Evaluate at a random point defined over Extension Field + #[inline] + pub fn evaluate_ext_opt>( + &self, + eq_at_r: &DenseMultilinearExtension, + ) -> BitDecompositionEval { + BitDecompositionEval:: { + d_val: self + .d_val + .iter() + .map(|val| val.evaluate_ext_opt(eq_at_r)) + .collect(), + d_bits: self + .d_bits + .iter() + .map(|bit| bit.evaluate_ext_opt(eq_at_r)) + .collect(), + } + } + + /// Extract the lookup instance + #[inline] + pub fn extract_lookup_instance(&self, block_size: usize) -> LookupInstance { + let mut table = vec![F::zero(); 1 << self.num_vars]; + let mut acc = F::zero(); + for t in table.iter_mut().take(1 << self.base_len) { + *t = acc; + acc += F::one(); + } + let table = DenseMultilinearExtension::from_evaluations_vec(self.num_vars, table); + + LookupInstance::from_slice( + &self + .d_bits + .iter() + .map(|d| d.as_ref().clone()) + .collect::>(), + table, + block_size, + ) } } -impl DecomposedBits { +impl BitDecompositionInstance { /// Use the base defined in this instance to perform decomposition over the input value. /// Then add the result into this instance, meaning to add l sumcheck protocols. /// * decomposed_bits: store each bit #[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 BitDecompositionEval { + /// 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() + } - let d_val_at_point: Vec<_> = d_val.iter().map(|val| val.evaluate(&self.point)).collect(); - let d_bits_at_point: Vec> = d_bits + /// 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 { + self.d_val .iter() - .map(|bits| bits.iter().map(|bit| bit.evaluate(&self.point)).collect()) - .collect(); + .chain(self.d_bits.iter()) + .copied() + .collect() + } +} + +/// IOP for bit decomposition +#[derive(Default)] +pub struct BitDecompositionIOP { + /// The random vector for random linear combination. + pub randomness: Vec, + /// The random value for identity function. + pub u: Vec, +} + +impl BitDecompositionIOP { + /// Sample coins before proving sumcheck protocol + #[inline] + pub fn sample_coins( + trans: &mut Transcript, + info: &BitDecompositionInstanceInfo, + ) -> Vec { + // Batch `len_bits` sumcheck protocols into one with random linear combination + trans.get_vec_challenge( + b"BD IOP: randomness to combine sumcheck protocols", + Self::num_coins(info), + ) + } + + /// Return the number of coins used in this IOP + #[inline] + pub fn num_coins(info: &BitDecompositionInstanceInfo) -> usize { + info.bits_len * info.num_instances + } + + /// Generate the randomenss. + #[inline] + pub fn generate_randomness( + &mut self, + trans: &mut Transcript, + info: &BitDecompositionInstanceInfo, + ) { + self.randomness = Self::sample_coins(trans, info); + } + + /// Generate the randomness for the eq function. + #[inline] + pub fn generate_randomness_for_eq_function( + &mut self, + trans: &mut Transcript, + info: &BitDecompositionInstanceInfo, + ) { + self.u = trans.get_vec_challenge( + b"BD IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + /// BitDecomposition IOP prover. + pub fn prove( + &self, + trans: &mut Transcript, + instance: &BitDecompositionInstance, + ) -> SumcheckKit { + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + + let eq_at_u = Rc::new(gen_identity_evaluations(&self.u)); + + Self::prepare_products_of_polynomial(&self.randomness, &mut poly, instance, &eq_at_u); + + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + randomness: state.randomness, + claimed_sum: F::zero(), + info: poly.info(), + u: self.u.clone(), + } + } + + /// 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 prepare_products_of_polynomial( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &BitDecompositionInstance, + 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( + &self, + trans: &mut Transcript, + wrapper: &ProofWrapper, + evals: &BitDecompositionEval, + info: &BitDecompositionInstanceInfo, + ) -> (bool, Vec) { + let mut subclaim = MLSumcheck::verify(trans, &wrapper.info, F::zero(), &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + + let eq_at_u_r = eval_identity_function(&self.u, &subclaim.point); + if !Self::verify_subclaim(&self.randomness, &mut subclaim, evals, info, eq_at_u_r) { + return (false, vec![]); + } + + (subclaim.expected_evaluations == F::zero(), subclaim.point) + } + + /// Verify bit decomposition relation without verifying each bit < base + pub fn verify_subclaim_without_range_check( + evals: &BitDecompositionEval, + info: &BitDecompositionInstanceInfo, + ) -> 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 + 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) }) - { + } + + /// Verify bit decomposition + pub fn verify_subclaim( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &BitDecompositionEval, + info: &BitDecompositionInstanceInfo, + eq_at_u_r: F, + ) -> bool { + // check 1: d[point] = \sum_{i=0}^len B^i \cdot d_i[point] for every instance + if !Self::verify_subclaim_without_range_check(evals, info) { return false; } // 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) +/// Bit decomposition proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct BitDecompositionProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info + pub poly_info: PolynomialInfo, + /// Polynomial commitment. + pub poly_comm: Pcs::Commitment, + /// The evaluation of the polynomial. + pub oracle_eval: EF, + /// The opening proof of the polynomial. + pub eval_proof: Pcs::Proof, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// The evaluations. + pub evals: BitDecompositionEval, +} + +impl BitDecompositionProof +where + F: Field + Serialize + for<'de> Deserialize<'de>, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) } - /// 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); + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// BitDecomposition parameter. +pub struct BitDecompositionParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameter for the polynomial commitment. + pub pp: Pcs::Parameters, +} + +impl Default for BitDecompositionParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp: Pcs::Parameters::default(), + } + } +} + +impl BitDecompositionParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + #[inline] + pub fn setup(&mut self, info: &BitDecompositionInstanceInfo, code_spec: S) { + self.pp = Pcs::setup(info.generate_num_var(), Some(code_spec)); + } +} + +/// Prover for bit decomposition with PCS. +pub struct BitDecompositionProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for BitDecompositionProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + BitDecompositionProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl BitDecompositionProver +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The prover. + pub fn prove( + &self, + trans: &mut Transcript, + params: &BitDecompositionParams, + instance: &BitDecompositionInstance, + ) -> BitDecompositionProof { + let instance_info = instance.info(); + + trans.append_message(b"bit decomposition instance", &instance_info); + + // 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(); + + // Use PCS to commit the above polynomial. + let (poly_comm, poly_comm_state) = Pcs::commit(¶ms.pp, &committed_poly); + + trans.append_message(b"BD IOP: polynomial commitment", &poly_comm); + + // Prover generates the proof. + // Convert the orignal instance into an instance defined over EF. + let instance_ef = instance.to_ef::(); + let instance_ef_info = instance_ef.info(); + let mut bd_iop = BitDecompositionIOP::::default(); + + bd_iop.generate_randomness(trans, &instance_ef_info); + bd_iop.generate_randomness_for_eq_function(trans, &instance_ef_info); + let kit = bd_iop.prove(trans, &instance_ef); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point = kit.randomness.clone(); + let oracle_randomness = trans.get_vec_challenge( + b"BD IOP: random linear combinaiton for evaluations of the oracles", + instance_info.log_num_oracles(), + ); + requested_point.extend(&oracle_randomness); + + // Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let evals = instance_ef.evaluate(&kit.randomness); + + let oracle_eval = committed_poly.evaluate_ext(&requested_point); + + // Generate the evaluation proof of the requested point. + let eval_proof = Pcs::open( + ¶ms.pp, + &poly_comm, + &poly_comm_state, + &requested_point, + trans, + ); + BitDecompositionProof { - sumcheck_msg: MLSumcheck::prove_as_subprotocol(fs_rng, &poly) - .expect("bit decomposition failed") - .0, + poly_info: kit.info, + poly_comm, + oracle_eval, + eval_proof, + sumcheck_proof: kit.proof, + evals, } } +} - /// 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) +/// Verifier for bit decomposition with PCS. +pub struct BitDecompositionVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for BitDecompositionVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + BitDecompositionVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } } +} - /// 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, +impl BitDecompositionVerifier +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The verifier. + pub fn verify( + &self, + trans: &mut Transcript, + params: &BitDecompositionParams, + info: &BitDecompositionInstanceInfo, + proof: &BitDecompositionProof, + ) -> bool { + let mut res = true; + + trans.append_message(b"bit decomposition instance", info); + trans.append_message(b"BD IOP: polynomial commitment", &proof.poly_comm); + + let mut bd_iop = BitDecompositionIOP::::default(); + let info_ef = info.to_ef(); + + bd_iop.generate_randomness(trans, &info_ef); + bd_iop.generate_randomness_for_eq_function(trans, &info_ef); + + let proof_wrapper = ProofWrapper { + claimed_sum: EF::zero(), + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), }; - 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, - } + + let (b, randomness) = bd_iop.verify(trans, &proof_wrapper, &proof.evals, &info.to_ef()); + + res &= b; + + // Check the relation between these small oracles and the committed oracle. + let flatten_evals = proof.evals.flatten(); + let oracle_randomness = trans.get_vec_challenge( + b"BD IOP: random linear combinaiton for evaluations of the oracles", + proof.evals.log_num_oracles(), + ); + res &= verify_oracle_relation(&flatten_evals, proof.oracle_eval, &oracle_randomness); + + // Check the evaluation of a random point over the committed oracle. + let mut requested_point = randomness.clone(); + requested_point.extend(&oracle_randomness); + res &= Pcs::verify( + ¶ms.pp, + &proof.poly_comm, + &requested_point, + proof.oracle_eval, + &proof.eval_proof, + trans, + ); + + res } } diff --git a/zkp/src/piop/external_product.rs b/zkp/src/piop/external_product.rs new file mode 100644 index 00000000..56f149ed --- /dev/null +++ b/zkp/src/piop/external_product.rs @@ -0,0 +1,1498 @@ +//! PIOP for multiplication between RLWE ciphertext and RGSW ciphertext +//! The prover wants to convince verifier the correctness of the multiplication between the RLWE ciphertext and the RGSW ciphetext +//! +//! Input: (a, b) is a RLWE ciphertext and (c, f) is a RGSW ciphertext where RLWE' = Vec and RGSW = RLWE' \times RLWE'. +//! Output: (g, h) is a RLWE ciphertext +//! +//! 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')) +//! Note that (c, f) is given in the NTT form. +//! +//! The multiplication between RLWE and RGSW is performed as follows: +//! 1. Decompose the coefficients of the input RLWE into k bits: a -> (a_0, ..., a_k-1) and b -> (b_0, ..., b_k-1). +//! Note that these are polynomials in the FHE context but oracles in the ZKP context. +//! This can be proven with our Bit Decomposition IOP. +//! 2. Perform NTT on these bits: +//! There are 2k NTT instance, including a_0 =NTT-equal= a_0', ..., a_k-1 =NTT-equal= a_k-1', ...,b_0 =NTT-equal= b_0', ..., b_k-1 =NTT-equal= b_k-1' +//! NTT instance is linear, allowing us to randomize these NTT instances to obtain a single NTT instance. +//! This can be proven with our NTT IOP. +//! 3. Compute: +//! g' = \sum_{i = 0}^{k-1} a_i' \cdot c_i + b_i' \cdot f_i +//! h' = \sum_{i = 0}^{k-1} a_i' \cdot c_i' + b_i' \cdot f_i' +//! Each can be proven with a sumcheck protocol. +//! 4. Perform NTT on g' and h' to obtain its coefficient form g and h. +//! +//! 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::{ + ntt::{BatchNTTInstanceInfoClean, BitsOrder, NTTRecursiveProof}, + BatchNTTInstanceInfo, BitDecompositionEval, BitDecompositionIOP, BitDecompositionInstance, + BitDecompositionInstanceInfo, LookupInstance, LookupInstanceEval, LookupInstanceInfo, + NTTBareIOP, NTTInstance, NTTIOP, +}; +use crate::piop::LookupIOP; +use crate::sumcheck::{self, verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}; +use crate::utils::{ + add_assign_ef, eval_identity_function, gen_identity_evaluations, verify_oracle_relation, +}; +use algebra::{ + utils::Transcript, AbstractExtensionField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, PolynomialInfo, +}; +use bincode::Result; +use core::fmt; +use itertools::izip; +use pcs::PolynomialCommitmentScheme; +use serde::{Deserialize, Serialize}; +use std::{marker::PhantomData, rc::Rc, sync::Arc}; + +/// 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')) +#[derive(Debug, Clone)] +pub struct ExternalProductInstance { + /// number of variables + pub num_vars: usize, + /// info of decomposed bits + pub bits_info: BitDecompositionInstanceInfo, + /// info of ntt instance + pub ntt_info: BatchNTTInstanceInfo, + /// Store the input ciphertext (a, b) where a and b are two polynomials represented by N coefficients. + pub input_rlwe: RlweCiphertext, + /// a_bits (b_bits) corresponds to the bit decomposition result of a (b) in the input rlwe ciphertext + pub bits_rlwe: RlweCiphertextVector, + /// The ntt form of the above bit decomposition result + pub bits_rlwe_ntt: RlweCiphertextVector, + /// The ntt form of the first part (c) in the RGSW ciphertext + pub bits_rgsw_c_ntt: RlweCiphertextVector, + /// The ntt form of the second part (f) in the RGSW ciphertext + pub bits_rgsw_f_ntt: RlweCiphertextVector, + /// Store the output ciphertext (g', h') in the NTT-form + pub output_rlwe_ntt: RlweCiphertext, +} + +/// Evaluation of RlweMultRgsw at the same random point +#[derive(Serialize, Deserialize)] +pub struct ExternalProductInstanceEval { + /// The Length of bits when decomposing bits + pub bits_len: usize, + /// Store the input ciphertext (a, b) where a and b are two polynomials represented by N coefficients. + pub input_rlwe: RlweEval, + /// a_bits (b_bits) corresponds to the bit decomposition result of a (b) in the input rlwe ciphertext + pub bits_rlwe: RlwesEval, + /// The ntt form of the above bit decomposition result + pub bits_rlwe_ntt: RlwesEval, + /// The ntt form of the first part (c) in the RGSW ciphertext + pub bits_rgsw_c_ntt: RlwesEval, + /// The ntt form of the second part (f) in the RGSW ciphertext + pub bits_rgsw_f_ntt: RlwesEval, + /// 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 ExternalProductInstanceInfo { + /// number of variables + pub num_vars: usize, + /// information of ntt instance + pub ntt_info: BatchNTTInstanceInfo, + /// information of bit decomposition + pub bits_info: BitDecompositionInstanceInfo, +} + +impl fmt::Display for ExternalProductInstanceInfo { + 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 ExternalProductInstanceInfo { + /// 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 + } + + /// Generate the number of variables in the committed polynomial. + #[inline] + pub fn generate_num_var(&self) -> usize { + self.num_vars + self.log_num_oracles() + } + + /// Convert to EF version + #[inline] + pub fn to_ef>(&self) -> ExternalProductInstanceInfo { + ExternalProductInstanceInfo:: { + num_vars: self.num_vars, + ntt_info: self.ntt_info.to_ef::(), + bits_info: self.bits_info.to_ef::(), + } + } + + /// Convert to clean info. + #[inline] + pub fn to_clean(&self) -> ExternalProductInstanceInfoClean { + ExternalProductInstanceInfoClean:: { + num_vars: self.num_vars, + ntt_info_clean: self.ntt_info.to_clean(), + bits_info: self.bits_info.clone(), + } + } + + /// Extract lookup info. + #[inline] + pub fn extract_lookup_info(&self, block_size: usize) -> LookupInstanceInfo { + LookupInstanceInfo { + num_vars: self.num_vars, + num_batch: 2 * self.bits_info.bits_len, + block_size, + block_num: (2 * self.bits_info.bits_len + block_size) / block_size, + } + } +} + +/// Stores the information to be hashed. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ExternalProductInstanceInfoClean { + /// number of variables + pub num_vars: usize, + /// information of ntt instance + pub ntt_info_clean: BatchNTTInstanceInfoClean, + /// information of bit decomposition + pub bits_info: BitDecompositionInstanceInfo, +} +/// RLWE ciphertext (a, b) where a and b represent two polynomials in some defined polynomial ring. +/// Note that it can represent either in coefficient or NTT form. +#[derive(Debug, Clone)] +pub struct RlweCiphertext { + /// The first part of the rlwe ciphertext. + pub a: DenseMultilinearExtension, + /// The second part of the rlwe ciphertext. + pub b: DenseMultilinearExtension, +} +impl RlweCiphertext { + /// Pack mles + #[inline] + pub fn pack_all_mles(&self) -> Vec { + self.a + .iter() + .chain(self.b.iter()) + .copied() + .collect::>() + } + + /// Convert to an EF version + #[inline] + pub fn to_ef>(&self) -> RlweCiphertext { + RlweCiphertext:: { + a: self.a.to_ef::(), + b: 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)) + } + + /// Evaluate at the same random point defined over EF + #[inline] + pub fn evaluate_ext_opt>( + &self, + point: &DenseMultilinearExtension, + ) -> RlweEval { + ( + self.a.evaluate_ext_opt(point), + self.b.evaluate_ext_opt(point), + ) + } +} + +/// RLWE' ciphertexts represented by two vectors, containing k RLWE ciphertext. +#[derive(Debug, Clone)] +pub struct RlweCiphertextVector { + /// Store the first part of each RLWE ciphertext vector. + pub a_vector: Vec>, + /// Store the second part of each RLWE ciphertext vector. + pub b_vector: Vec>, +} +impl RlweCiphertextVector { + /// Construct an empty rlweciphertexts + /// + /// # Arguments. + /// + /// * `bits_len` - the decomposition bits length. + pub fn new(bits_len: usize) -> Self { + Self { + a_vector: Vec::with_capacity(bits_len), + b_vector: Vec::with_capacity(bits_len), + } + } + + /// Add a RLWE ciphertext + /// + /// # Arguments. + /// + /// * `a` - MLE of rlwe a. + /// * `b` - MLE of rlwe b. + pub fn add_rlwe_instance( + &mut self, + a: DenseMultilinearExtension, + b: DenseMultilinearExtension, + ) { + self.a_vector.push(a); + self.b_vector.push(b); + } + + /// Is empty + pub fn is_empty(&self) -> bool { + if self.a_vector.is_empty() || self.b_vector.is_empty() { + return true; + } + false + } + + /// Return the len + pub fn len(&self) -> usize { + if self.is_empty() { + return 0; + } + assert_eq!(self.a_vector.len(), self.b_vector.len()); + self.a_vector.len() + } + + /// Returns a vector that iterates over the evaluations over {0,1}^`num_vars` + #[inline] + pub fn pack_all_mles(&self) -> Vec { + self.a_vector + .iter() + .flat_map(|bit| bit.iter()) + .chain(self.b_vector.iter().flat_map(|bit| bit.iter())) + .copied() + .collect() + } + + /// Convert to EF version + #[inline] + pub fn to_ef>(&self) -> RlweCiphertextVector { + RlweCiphertextVector:: { + a_vector: self.a_vector.iter().map(|bit| bit.to_ef::()).collect(), + b_vector: self.b_vector.iter().map(|bit| bit.to_ef::()).collect(), + } + } + + /// Evaluate at the same random point defined over F + #[inline] + pub fn evaluate(&self, point: &[F]) -> RlwesEval { + ( + self.a_vector + .iter() + .map(|bit| bit.evaluate(point)) + .collect(), + self.b_vector + .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_vector + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), + self.b_vector + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), + ) + } + + /// Evaluate at the same random point defined over EF + #[inline] + pub fn evaluate_ext_opt>( + &self, + point: &DenseMultilinearExtension, + ) -> RlwesEval { + ( + self.a_vector + .iter() + .map(|bit| bit.evaluate_ext_opt(point)) + .collect(), + self.b_vector + .iter() + .map(|bit| bit.evaluate_ext_opt(point)) + .collect(), + ) + } +} + +impl ExternalProductInstance { + /// Extract the information + #[inline] + pub fn info(&self) -> ExternalProductInstanceInfo { + ExternalProductInstanceInfo { + 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 new( + num_vars: usize, + bits_info: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, + input_rlwe: RlweCiphertext, + bits_rlwe: RlweCiphertextVector, + bits_rlwe_ntt: RlweCiphertextVector, + bits_rgsw_c_ntt: RlweCiphertextVector, + bits_rgsw_f_ntt: RlweCiphertextVector, + output_rlwe_ntt: RlweCiphertext, + // output_rlwe: &RlweCiphertext, + ) -> Self { + // update num_ntt of ntt_info + let ntt_info = BatchNTTInstanceInfo { + num_ntt: bits_info.bits_len << 1, + num_vars, + ntt_table: ntt_info.ntt_table, + }; + + 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 = BitDecompositionInstanceInfo { + num_vars, + base: bits_info.base, + base_len: bits_info.base_len, + bits_len: bits_info.bits_len, + num_instances: 2, + }; + + ExternalProductInstance { + 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: output_rlwe.clone(), + } + } + + /// 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 = 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.xw + pub fn generate_oracle(&self) -> DenseMultilinearExtension { + let info = self.info(); + let num_vars = info.generate_num_var(); + let num_zeros_padded = (1 << num_vars) - info.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 an EF version + #[inline] + pub fn to_ef>(&self) -> ExternalProductInstance { + ExternalProductInstance:: { + 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::(), + } + } + + /// Evaluate at the same random point defined over F + #[inline] + pub fn evaluate(&self, point: &[F]) -> ExternalProductInstanceEval { + ExternalProductInstanceEval:: { + 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), + } + } + + /// Evaluate at the same random point defined over EF + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> ExternalProductInstanceEval { + ExternalProductInstanceEval:: { + 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), + } + } + + /// Evaluate given the equality function + #[inline] + pub fn evaluate_ext_opt>( + &self, + point: &DenseMultilinearExtension, + ) -> ExternalProductInstanceEval { + ExternalProductInstanceEval:: { + bits_len: self.bits_info.bits_len, + input_rlwe: self.input_rlwe.evaluate_ext_opt(point), + bits_rlwe: self.bits_rlwe.evaluate_ext_opt(point), + bits_rlwe_ntt: self.bits_rlwe_ntt.evaluate_ext_opt(point), + bits_rgsw_c_ntt: self.bits_rgsw_c_ntt.evaluate_ext_opt(point), + bits_rgsw_f_ntt: self.bits_rgsw_f_ntt.evaluate_ext_opt(point), + output_rlwe_ntt: self.output_rlwe_ntt.evaluate_ext_opt(point), + } + } + + /// 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), + } + } + + /// 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_vector + .iter() + .chain(self.bits_rlwe.b_vector.iter()), + self.bits_rlwe_ntt + .a_vector + .iter() + .chain(self.bits_rlwe_ntt.b_vector.iter()) + ) { + *r_coeffs += (*r, coeff); + *r_points += (*r, point); + } + } + + /// 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], + ); + + self.update_ntt_instance_to_ef::(&mut random_coeffs, &mut random_points, randomness); + + NTTInstance:: { + num_vars: self.num_vars, + ntt_table: Arc::new( + self.ntt_info + .ntt_table + .iter() + .map(|x| EF::from_base(*x)) + .collect(), + ), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), + } + } + + /// 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_vector + .iter() + .chain(self.bits_rlwe.b_vector.iter()), + self.bits_rlwe_ntt + .a_vector + .iter() + .chain(self.bits_rlwe_ntt.b_vector.iter()) + ) { + // 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) -> BitDecompositionInstance { + let mut res = BitDecompositionInstance { + 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 BitDecompositionInstance) { + decomposed_bits.add_decomposed_bits_instance( + &Rc::new(self.input_rlwe.a.clone()), + &self + .bits_rlwe + .a_vector + .iter() + .map(|bits| Rc::new(bits.clone())) + .collect::>(), + ); + decomposed_bits.add_decomposed_bits_instance( + &Rc::new(self.input_rlwe.b.clone()), + &self + .bits_rlwe + .b_vector + .iter() + .map(|bits| Rc::new(bits.clone())) + .collect::>(), + ); + } + + /// Extract lookup instance + #[inline] + pub fn extract_lookup_instance(&self, block_size: usize) -> LookupInstance { + self.extract_decomposed_bits() + .extract_lookup_instance(block_size) + } +} + +impl ExternalProductInstanceEval { + /// 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) -> BitDecompositionEval { + let mut res = BitDecompositionEval { + 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 BitDecompositionEval) { + 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); + } + + /// 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); + } +} + +/// IOP for RLWE * RGSW +#[derive(Default)] +pub struct ExternalProductIOP { + /// The random vector for random linear combination. + pub randomness: Vec, + /// The random vector for ntt. + pub randomness_ntt: Vec, + /// The random value for identity function. + pub u: Vec, +} + +impl ExternalProductIOP { + /// Sample the random coins before proving sumcheck protocol + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + #[inline] + pub fn sample_coins(trans: &mut Transcript) -> Vec { + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(), + ) + } + + /// Return the number of coins used in sumcheck protocol + #[inline] + pub fn num_coins() -> usize { + 2 + } + + /// Generate the randomness. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The external product instance info. + #[inline] + pub fn generate_randomness( + &mut self, + trans: &mut Transcript, + info: &ExternalProductInstanceInfo, + ) { + self.randomness = Self::sample_coins(trans); + self.randomness_ntt = NTTIOP::::sample_coins(trans, &info.ntt_info.to_clean()); + self.u = trans.get_vec_challenge( + b"EP IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + + /// Prove external product instance + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `instance` - The external product instance. + /// * `lookup_instance` - The extracted lookup instance. + /// * `lookup_iop` - The lookup IOP. + /// * `bits_order` - The indicator of bits order. + pub fn prove( + &self, + trans: &mut Transcript, + instance: &ExternalProductInstance, + lookup_instance: &LookupInstance, + lookup_iop: &LookupIOP, + bits_order: BitsOrder, + ) -> (SumcheckKit, NTTRecursiveProof) { + let eq_at_u = Rc::new(gen_identity_evaluations(&self.u)); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let mut claimed_sum = F::zero(); + // add sumcheck products (without NTT) into poly + Self::prepare_products_of_polynomial(&self.randomness, &mut poly, instance, &eq_at_u); + + // add sumcheck products of NTT into poly + let ntt_instance = instance.extract_ntt_instance(&self.randomness_ntt); + NTTBareIOP::::prepare_products_of_polynomial( + F::one(), + &mut poly, + &mut claimed_sum, + &ntt_instance, + &self.u, + bits_order, + ); + + LookupIOP::::prepare_products_of_polynomial( + &lookup_iop.randomness, + &mut poly, + lookup_instance, + &eq_at_u, + ); + + // prove all sumcheck protocol into a large random sumcheck + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + // prove F(u, v) in a recursive manner + let recursive_proof = NTTIOP::::prove_recursion( + trans, + &state.randomness, + &ntt_instance.info(), + &self.u, + bits_order, + ); + + ( + SumcheckKit { + proof, + claimed_sum, + info: poly.info(), + u: self.u.clone(), + randomness: state.randomness, + }, + recursive_proof, + ) + } + + /// Prove RLWE * RGSW with leaving the NTT part outside this interface + #[inline] + pub fn prepare_products_of_polynomial( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &ExternalProductInstance, + eq_at_u: &Rc>, + ) { + let r = randomness; + 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_vector, + &instance.bits_rlwe_ntt.b_vector, + &instance.bits_rgsw_c_ntt.a_vector, + &instance.bits_rgsw_f_ntt.a_vector + ) { + let prod1 = [Rc::new(a.clone()), Rc::new(c.clone()), Rc::clone(eq_at_u)]; + let prod2 = [Rc::new(b.clone()), Rc::new(f.clone()), Rc::clone(eq_at_u)]; + poly.add_product(prod1, r[0]); + poly.add_product(prod2, r[0]); + } + poly.add_product( + [ + Rc::new(instance.output_rlwe_ntt.a.clone()), + 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!( + &instance.bits_rlwe_ntt.a_vector, + &instance.bits_rlwe_ntt.b_vector, + &instance.bits_rgsw_c_ntt.b_vector, + &instance.bits_rgsw_f_ntt.b_vector + ) { + let prod1 = [Rc::new(a.clone()), Rc::new(c.clone()), Rc::clone(eq_at_u)]; + let prod2 = [Rc::new(b.clone()), Rc::new(f.clone()), Rc::clone(eq_at_u)]; + poly.add_product(prod1, r[1]); + poly.add_product(prod2, r[1]); + } + poly.add_product( + [ + Rc::new(instance.output_rlwe_ntt.b.clone()), + Rc::clone(eq_at_u), + ], + -r[1], + ); + } + + /// Verify external product proof. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `wrapper` - The proof wrapper. + /// * `evals_at_r` - The evaluation points at r. + /// * `evals_at_u` - The evaluation points at u. + /// * `info` - The external product info. + /// * `lookup_info` - The derived lookup info. + /// * `recursive_proof` - The recursive sumcheck proof. + /// * `lookup_evals` - The extracted lookup instance evaluations. + /// * `lookup_iop` - The lookup IOP. + /// * `bits_order` - The indicator of bits order. + #[allow(clippy::too_many_arguments)] + #[inline] + pub fn verify( + &self, + trans: &mut Transcript, + wrapper: &mut ProofWrapper, + evals_at_r: &ExternalProductInstanceEval, + evals_at_u: &ExternalProductInstanceEval, + info: &ExternalProductInstanceInfo, + lookup_info: &LookupInstanceInfo, + recursive_proof: &NTTRecursiveProof, + lookup_evals: &LookupInstanceEval, + lookup_iop: &LookupIOP, + bits_order: BitsOrder, + ) -> (bool, Vec) { + let mut subclaim = + MLSumcheck::verify(trans, &wrapper.info, wrapper.claimed_sum, &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&self.u, &subclaim.point); + + // check the sumcheck evaluation (without NTT) + if !Self::verify_subclaim(&self.randomness, &mut subclaim, evals_at_r, info, eq_at_u_r) { + return (false, vec![]); + } + + 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, &self.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, &self.randomness_ntt); + + if !NTTBareIOP::::verify_subclaim( + F::one(), + &mut subclaim, + &mut wrapper.claimed_sum, + ntt_coeff_evals_at_r, + ntt_point_evals_at_u, + f_delegation, + ) { + return (false, vec![]); + } + + if !LookupIOP::::verify_subclaim( + &lookup_iop.randomness, + &mut subclaim, + lookup_evals, + lookup_info, + eq_at_u_r, + ) { + return (false, vec![]); + } + if !(subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero()) { + return (false, vec![]); + } + let res = NTTIOP::::verify_recursion( + trans, + recursive_proof, + &info.ntt_info, + &self.u, + &subclaim.point, + bits_order, + ); + + (res, subclaim.point) + } + + /// Verify RLWE * RGSW with leaving NTT part outside of the interface + #[inline] + pub fn verify_subclaim( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &ExternalProductInstanceEval, + info: &ExternalProductInstanceInfo, + 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_subclaim_without_range_check( + &bits_eval, + &info.bits_info, + ); + if !check_decomposed_bits { + return false; + } + + let r = randomness; + // 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 + } +} + +/// External product proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct ExternalProductProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info. + pub poly_info: PolynomialInfo, + /// The first polynomial commitment. + pub first_comm: Pcs::Commitment, + /// The evaluation of the first packed polynomial. + pub first_oracle_eval_at_r: EF, + /// The opening of the first polynomial. + pub first_eval_proof_at_r: Pcs::Proof, + /// The evaluation of the first packed polynomial. + pub first_oracle_eval_at_u: EF, + /// The opening of the first polynomial. + pub first_eval_proof_at_u: Pcs::Proof, + /// The second polynomial commitment. + pub second_comm: Pcs::Commitment, + /// The evaluation of the second packed polynomial. + pub second_oracle_eval: EF, + /// The opening proof of the second polynomial. + pub second_eval_proof: Pcs::ProofEF, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// NTT recursive proof. + pub recursive_proof: NTTRecursiveProof, + /// The external product evaluations. + pub ep_evals_at_r: ExternalProductInstanceEval, + /// The external product evaluations. + pub ep_evals_at_u: ExternalProductInstanceEval, + /// The lookup evaluations. + pub lookup_evals: LookupInstanceEval, + /// The claimed sum from sumcheck. + pub claimed_sum: EF, +} + +impl ExternalProductProof +where + F: Field, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) + } + + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// External product parameters. +pub struct ExternalProductParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameters for the first polynomial. + pub pp_first: Pcs::Parameters, + /// The parameters for the second polynomial. + pub pp_second: Pcs::Parameters, +} + +impl Default for ExternalProductParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp_first: Pcs::Parameters::default(), + pp_second: Pcs::Parameters::default(), + } + } +} + +impl ExternalProductParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + pub fn setup( + &mut self, + info: &ExternalProductInstanceInfo, + block_size: usize, + code_spec: S, + ) { + self.pp_first = Pcs::setup(info.generate_num_var(), Some(code_spec.clone())); + + let lookup_info = info.extract_lookup_info(block_size); + self.pp_second = Pcs::setup(lookup_info.generate_second_num_var(), Some(code_spec)); + } +} + +/// Prover for external product with PCS. +pub struct ExternalProductProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for ExternalProductProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + ExternalProductProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl ExternalProductProver +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: PolynomialCommitmentScheme< + F, + EF, + S, + Polynomial = DenseMultilinearExtension, + EFPolynomial = DenseMultilinearExtension, + Point = EF, + >, +{ + /// The prover. + pub fn prove( + &self, + trans: &mut Transcript, + params: &ExternalProductParams, + instance: &ExternalProductInstance, + block_size: usize, + bits_order: BitsOrder, + ) -> ExternalProductProof { + let instance_info = instance.info(); + // It is better to hash the shared public instance information, including the table. + trans.append_message(b"extenral product instance", &instance_info.to_clean()); + + // 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 first_committed_poly = instance.generate_oracle(); + + // Use PCS to commit the above polynomial. + let (first_comm, first_comm_state) = Pcs::commit(¶ms.pp_first, &first_committed_poly); + + trans.append_message(b"EP IOP: first commitment", &first_comm); + + // Convert the original instance into an instance defined over EF + let instance_ef = instance.to_ef::(); + let instance_info = instance_ef.info(); + + // IOPs + let mut ep_iop = ExternalProductIOP::::default(); + let mut lookup_iop = LookupIOP::::default(); + + // generate ranomness for EP iop. + ep_iop.generate_randomness(trans, &instance_info); + + // --- Lookup instance and commitment --- + let mut lookup_instance = instance_ef.extract_lookup_instance(block_size); + let lookup_info = lookup_instance.info(); + + // Generate the first randomness for lookup iop. + lookup_iop.prover_generate_first_randomness(trans, &mut lookup_instance); + + // Compute the packed second polynomials for lookup, i.e., h vector. + let second_committed_poly = lookup_instance.generate_second_oracle(); + + // Commit the second polynomial. + let (second_comm, second_comm_state) = + Pcs::commit_ef(¶ms.pp_second, &second_committed_poly); + + trans.append_message(b"EP IOP: second commitment", &second_comm); + + lookup_iop.generate_second_randomness(trans, &lookup_info); + + let (kit, recursive_proof) = ep_iop.prove( + trans, + &instance_ef, + &lookup_instance, + &lookup_iop, + bits_order, + ); + + let ep_evals_at_r = instance.evaluate_ext(&kit.randomness); + let ep_evals_at_u = instance.evaluate_ext(&ep_iop.u); + + // let eq_at_r = gen_identity_evaluations(&kit.randomness); + // let evals_at_r = instance.evaluate_ext_opt(&eq_at_r); + // let evals_at_u = instance.evaluate_ext_opt(eq_at_u.as_ref()); + + // Lookup evaluation + let lookup_evals = lookup_instance.evaluate(&kit.randomness); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point_at_r = kit.randomness.clone(); + let mut requested_point_at_u = ep_iop.u.clone(); + let oracle_randomness = trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + instance.info().log_num_oracles(), + ); + requested_point_at_r.extend(&oracle_randomness); + requested_point_at_u.extend(&oracle_randomness); + + let first_oracle_eval_at_r = first_committed_poly.evaluate_ext(&requested_point_at_r); + let first_oracle_eval_at_u = first_committed_poly.evaluate_ext(&requested_point_at_u); + + let mut second_requested_point = kit.randomness.clone(); + let second_oracle_randomness = trans.get_vec_challenge( + b"Lookup IOP: random linear combination of evaluations of second oracles", + lookup_info.log_num_second_oracles(), + ); + + second_requested_point.extend(&second_oracle_randomness); + + let second_oracle_eval = second_committed_poly.evaluate(&second_requested_point); + + // Generate the evaluation proof of the requested point + let first_eval_proof_at_r = Pcs::open( + ¶ms.pp_first, + &first_comm, + &first_comm_state, + &requested_point_at_r, + trans, + ); + let first_eval_proof_at_u = Pcs::open( + ¶ms.pp_first, + &first_comm, + &first_comm_state, + &requested_point_at_u, + trans, + ); + + let second_eval_proof = Pcs::open_ef( + ¶ms.pp_second, + &second_comm, + &second_comm_state, + &second_requested_point, + trans, + ); + + ExternalProductProof { + poly_info: kit.info, + first_comm, + first_oracle_eval_at_r, + first_eval_proof_at_r, + first_oracle_eval_at_u, + first_eval_proof_at_u, + second_comm, + second_oracle_eval, + second_eval_proof, + sumcheck_proof: kit.proof, + recursive_proof, + ep_evals_at_r, + ep_evals_at_u, + lookup_evals, + claimed_sum: kit.claimed_sum, + } + } +} + +/// Prover for external product with PCS. +pub struct ExternalProductVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for ExternalProductVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + ExternalProductVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl ExternalProductVerifier +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: PolynomialCommitmentScheme< + F, + EF, + S, + Polynomial = DenseMultilinearExtension, + EFPolynomial = DenseMultilinearExtension, + Point = EF, + >, +{ + /// The verifier. + pub fn verify( + &self, + trans: &mut Transcript, + params: &ExternalProductParams, + info: &ExternalProductInstanceInfo, + block_size: usize, + bits_order: BitsOrder, + proof: &ExternalProductProof, + ) -> bool { + let mut res = true; + + trans.append_message(b"extenral product instance", &info.to_clean()); + trans.append_message(b"EP IOP: first commitment", &proof.first_comm); + + let mut ep_iop = ExternalProductIOP::::default(); + let mut lookup_iop = LookupIOP::::default(); + let info_ef = info.to_ef(); + + ep_iop.generate_randomness(trans, &info_ef); + lookup_iop.verifier_generate_first_randomness(trans); + + trans.append_message(b"EP IOP: second commitment", &proof.second_comm); + + // Verify the proof of sumcheck protocol. + let lookup_info = info.extract_lookup_info(block_size); + lookup_iop.generate_second_randomness(trans, &lookup_info); + + let mut wrapper = ProofWrapper { + claimed_sum: proof.claimed_sum, + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), + }; + let (b, randomness) = ep_iop.verify( + trans, + &mut wrapper, + &proof.ep_evals_at_r, + &proof.ep_evals_at_u, + &info_ef, + &lookup_info, + &proof.recursive_proof, + &proof.lookup_evals, + &lookup_iop, + bits_order, + ); + res &= b; + + // Check the relation between these small oracles and the committed oracle. + let mut request_point_at_r = randomness.clone(); + let mut request_point_at_u = ep_iop.u; + let flatten_evals_at_r = proof.ep_evals_at_r.flatten(); + let flatten_evals_at_u = proof.ep_evals_at_u.flatten(); + let oracle_randomness = trans.get_vec_challenge( + b"random linear combination for evaluations of oracles", + proof.ep_evals_at_r.log_num_oracles(), + ); + + request_point_at_r.extend(&oracle_randomness); + request_point_at_u.extend(&oracle_randomness); + + res &= verify_oracle_relation( + &flatten_evals_at_r, + proof.first_oracle_eval_at_r, + &oracle_randomness, + ); + + res &= verify_oracle_relation( + &flatten_evals_at_u, + proof.first_oracle_eval_at_u, + &oracle_randomness, + ); + + let mut second_requested_point = randomness; + let second_oracle_randomness = trans.get_vec_challenge( + b"Lookup IOP: random linear combination of evaluations of second oracles", + proof.lookup_evals.log_num_second_oracles(), + ); + + second_requested_point.extend(&second_oracle_randomness); + + res &= verify_oracle_relation( + &proof.lookup_evals.h_vec, + proof.second_oracle_eval, + &second_oracle_randomness, + ); + + // Check the evaluation of a random point over the committed oracles. + res &= Pcs::verify( + ¶ms.pp_first, + &proof.first_comm, + &request_point_at_r, + proof.first_oracle_eval_at_r, + &proof.first_eval_proof_at_r, + trans, + ); + + res &= Pcs::verify( + ¶ms.pp_first, + &proof.first_comm, + &request_point_at_u, + proof.first_oracle_eval_at_u, + &proof.first_eval_proof_at_u, + trans, + ); + + res &= Pcs::verify_ef( + ¶ms.pp_second, + &proof.second_comm, + &second_requested_point, + proof.second_oracle_eval, + &proof.second_eval_proof, + trans, + ); + + res + } +} diff --git a/zkp/src/piop/floor.rs b/zkp/src/piop/floor.rs new file mode 100644 index 00000000..ad26aaed --- /dev/null +++ b/zkp/src/piop/floor.rs @@ -0,0 +1,1008 @@ +//! Floor IOP +//! The round operation is the scaling operation, followed by a floor operation. +//! +//! The round operation takes as input a \in F_Q and outputs b \in Zq such that b = \floor (a * q) / Q. +//! In some senses, this operation maps an interval of F_Q into an element of Zq. +//! +//! The prover is going to prove: for x \in {0, 1}^\logM +//! 1. b(x) \in [q] -> which can be proven with a range check since q is a power-of-two +//! 2. c(x) \in [1, ..., k] +//! meant to prove c(x) - 1 \in [k] +//! Let L denote the logarithm of the next power-of-two number that is bigger or equal to k. +//! Let delta denote 2^L - k +//! It is necessary to be proven with 2 range checks: +//! one is to prove c(x) - 1 \in [2^L] +//! the other is to prove c(x) - 1 + delta \in [2^L] +//! 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::{ + BitDecompositionEval, BitDecompositionIOP, BitDecompositionInstance, + BitDecompositionInstanceInfo, +}; +use crate::sumcheck::{self, verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}; +use crate::utils::{eval_identity_function, gen_identity_evaluations, verify_oracle_relation}; +use algebra::{ + utils::Transcript, AbstractExtensionField, DecomposableField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, PolynomialInfo, +}; +use bincode::Result; +use core::fmt; +use itertools::izip; +use pcs::PolynomialCommitmentScheme; +use serde::{Deserialize, Serialize}; +use std::{marker::PhantomData, rc::Rc}; + +/// Floor Instance. +pub struct FloorInstance { + /// number of variables + pub num_vars: usize, + /// k = Q - 1 / q where q is the modulus of the output + pub k: F, + /// delta = 2^{k_bit_len} - k + pub delta: F, + /// input denoted by a \in F_Q + pub input: Rc>, + /// output denoted by b \in F_q + pub output: Rc>, + /// decomposed bits of output used for range check + pub output_bits: Vec>>, + /// decomposition info for outputs + pub output_bits_info: BitDecompositionInstanceInfo, + /// 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: Vec>>, + /// decomposition info for offset + pub offset_bits_info: BitDecompositionInstanceInfo, + /// selector denoted by w \in {0, 1} + pub selector: Rc>, +} + +/// Information about Floor Instance used as verifier keys +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FloorInstanceInfo { + /// 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 + pub delta: F, + /// decomposition info for outputs + pub output_bits_info: BitDecompositionInstanceInfo, + /// decomposition info for offset + pub offset_bits_info: BitDecompositionInstanceInfo, +} + +impl fmt::Display for FloorInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "An instance of Floor 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 FloorInstanceInfo { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 4 + self.output_bits_info.bits_len + 2 * self.offset_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 + } + + /// Generate the number of variables in the committed polynomial. + #[inline] + pub fn generate_num_var(&self) -> usize { + self.num_vars + self.log_num_oracles() + } + + /// Construct an EF version. + #[inline] + pub fn to_ef>(&self) -> FloorInstanceInfo { + FloorInstanceInfo:: { + num_vars: self.num_vars, + k: EF::from_base(self.k), + delta: EF::from_base(self.delta), + output_bits_info: self.output_bits_info.to_ef(), + offset_bits_info: self.offset_bits_info.to_ef(), + } + } +} + +impl FloorInstance { + /// Extract the information + #[inline] + pub fn info(&self) -> FloorInstanceInfo { + FloorInstanceInfo { + num_vars: self.num_vars, + k: self.k, + delta: self.delta, + output_bits_info: self.output_bits_info.clone(), + offset_bits_info: self.offset_bits_info.clone(), + } + } + + /// 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.selector.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 info = self.info(); + let num_vars = info.generate_num_var(); + let num_zeros_padded = (1 << num_vars) - info.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) -> FloorInstance { + FloorInstance:: { + 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::()), + selector: Rc::new(self.selector.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]) -> FloorInstanceEval { + let offset = self.offset.evaluate(point); + FloorInstanceEval:: { + input: self.input.evaluate(point), + output: self.output.evaluate(point), + offset, + selector: self.selector.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], + ) -> FloorInstanceEval { + let offset = self.offset.evaluate_ext(point); + FloorInstanceEval:: { + input: self.input.evaluate_ext(point), + output: self.output.evaluate_ext(point), + offset, + selector: self.selector.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, + ) -> (BitDecompositionInstance, BitDecompositionInstance) { + // 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(), + ); + ( + BitDecompositionInstance { + 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(), + }, + BitDecompositionInstance { + 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 FloorInstance { + /// Compute the witness required in proof and construct the instance + /// + /// # Arguments. + /// + /// * `num_vars` - The number of variables. + /// * `k` - The value (Q-1)/q + /// * `delta` - The offset. + /// * `input` - The MLE of input. + /// * `output` - The MLE of output. + /// * `output_bits_info` - The bit decomposition info of output bits. + /// * `offset_bits_info` - The bit decomposition info of offset bits. + #[inline] + pub fn new( + num_vars: usize, + k: F, + delta: F, + input: Rc>, + output: Rc>, + output_bits_info: &BitDecompositionInstanceInfo, + offset_bits_info: &BitDecompositionInstanceInfo, + ) -> Self { + assert_eq!(num_vars, output.num_vars); + assert_eq!(num_vars, output_bits_info.num_vars); + assert_eq!(num_vars, offset_bits_info.num_vars); + assert_eq!(1, output_bits_info.num_instances); + assert_eq!(2, offset_bits_info.num_instances); + + 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 selector = Rc::new(DenseMultilinearExtension::::from_evaluations_vec( + num_vars, + input + .iter() + .zip(output.iter()) + .map(|(a, b)| match (a.is_zero(), b.is_zero()) { + (true, true) => F::one(), + _ => F::zero(), + }) + .collect(), + )); + + // Note that we must set c \in [1, k] when w = 1 to ensure that c(x) \in [1, k] for all x \in {0,1}^logn + // if w = 0: c = a - b * k + // if w = 1: c = 1 defaultly + let offset = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + izip!(selector.iter(), input.iter(), output.iter()) + .map(|(w, a, b)| match w.is_zero() { + true => *a - *b * k, + false => F::one(), + }) + .collect(), + )); + + // 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(), + ); + 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, + k, + delta, + input, + output, + output_bits, + offset, + offset_aux_bits, + selector, + offset_bits_info: offset_bits_info.clone(), + output_bits_info: output_bits_info.clone(), + } + } +} + +/// Evaluation at a random point +#[derive(Serialize, Deserialize)] +pub struct FloorInstanceEval { + /// 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, + /// selector denoted by w \in {0, 1} + pub selector: F, +} + +impl FloorInstanceEval { + /// 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 + } + + /// 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.selector); + res.extend(self.output_bits.iter()); + res.extend(self.offset_aux_bits.iter()); + res + } + + /// Extract DecomposedBitsEval instance + #[inline] + pub fn extract_decomposed_bits(&self) -> (BitDecompositionEval, BitDecompositionEval) { + ( + BitDecompositionEval { + d_val: vec![self.output], + d_bits: self.output_bits.to_owned(), + }, + BitDecompositionEval { + d_val: self.offset_aux.to_owned(), + d_bits: self.offset_aux_bits.to_owned(), + }, + ) + } +} + +/// Floor IOP +#[derive(Default)] +pub struct FloorIOP { + /// The random vector for random linear combination. + pub randomness: Vec, + /// The random value for identity function. + pub u: Vec, +} + +impl FloorIOP { + /// Sample coins before proving sumcheck protocol + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The floor instance info. + pub fn sample_coins(trans: &mut Transcript, info: &FloorInstanceInfo) -> Vec { + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + BitDecompositionIOP::::num_coins(&info.output_bits_info) + + BitDecompositionIOP::::num_coins(&info.offset_bits_info) + + 4, + ) + } + + /// Return the number of coins used in this IOP + /// + /// # Arguments. + /// + /// * `info` - The floor instance info. + pub fn num_coins(info: &FloorInstanceInfo) -> usize { + BitDecompositionIOP::::num_coins(&info.output_bits_info) + + BitDecompositionIOP::::num_coins(&info.offset_bits_info) + + 4 + } + + /// Generate the rlc randomenss. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The floor instance info. + #[inline] + pub fn generate_randomness(&mut self, trans: &mut Transcript, info: &FloorInstanceInfo) { + self.randomness = Self::sample_coins(trans, info); + } + + /// Generate the randomness for the eq function. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The floor instance info. + #[inline] + pub fn generate_randomness_for_eq_function( + &mut self, + trans: &mut Transcript, + info: &FloorInstanceInfo, + ) { + self.u = trans.get_vec_challenge( + b"FLOOR IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + + /// Floor IOP prover. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `instance` - The floor instance. + pub fn prove(&self, trans: &mut Transcript, instance: &FloorInstance) -> SumcheckKit { + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + + let eq_at_u = Rc::new(gen_identity_evaluations(&self.u)); + + Self::prepare_products_of_polynomial(&self.randomness, &mut poly, instance, &eq_at_u); + + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + info: poly.info(), + claimed_sum: F::zero(), + randomness: state.randomness, + u: self.u.clone(), + } + } + + /// Add the sumcheck proving floor into the polynomial + /// + /// # Arguments. + /// + /// * `randomness` - The randomness used to randomnize the ntt instance. + /// * `poly` - The list of product of polynomials. + /// * `instance` - The floor instance. + /// * `eq_at_u` - The evaluation of eq function on point u. + pub fn prepare_products_of_polynomial( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + instance: &FloorInstance, + eq_at_u: &Rc>, + ) { + let (output_bits_instance, offset_bits_instance) = instance.extract_decomposed_bits(); + let output_bits_r_num = BitDecompositionIOP::::num_coins(&instance.output_bits_info); + let offset_bits_r_num = BitDecompositionIOP::::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 + BitDecompositionIOP::prepare_products_of_polynomial( + &randomness[..output_bits_r_num], + poly, + &output_bits_instance, + eq_at_u, + ); + BitDecompositionIOP::prepare_products_of_polynomial( + &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(eq_at_u), + Rc::clone(&instance.selector), + Rc::clone(&instance.selector), + ], + &[ + (F::one(), F::zero()), + (F::one(), F::zero()), + (-F::one(), F::one()), + ], + r_1, + ); + + // 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(eq_at_u), + Rc::clone(&instance.selector), + Rc::clone(&instance.input), + ], + &[ + (F::one(), F::zero()), + (F::one(), F::zero()), + (lambda_1, F::zero()), + ], + r_2, + ); + // product: eq(u, x) * w(x) * (b(x) * \lambda_2) + poly.add_product_with_linear_op( + [ + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), + Rc::clone(&instance.output), + ], + &[ + (F::one(), F::zero()), + (F::one(), F::zero()), + (lambda_2, F::zero()), + ], + r_2, + ); + // product: eq(u, x) * (1 - w(x)) * a(x) + poly.add_product_with_linear_op( + [ + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), + Rc::clone(&instance.input), + ], + &[ + (F::one(), F::zero()), + (-F::one(), F::one()), + (F::one(), F::zero()), + ], + r_2, + ); + // product: eq(u, x) * (1 - w(x)) * (-k * b(x)) + poly.add_product_with_linear_op( + [ + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), + Rc::clone(&instance.output), + ], + &[ + (F::one(), F::zero()), + (-F::one(), F::one()), + (-instance.k, F::zero()), + ], + r_2, + ); + // product: eq(u, x) * (1 - w(x)) * (-c(x)) + poly.add_product_with_linear_op( + [ + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), + Rc::clone(&instance.offset), + ], + &[ + (F::one(), F::zero()), + (-F::one(), F::one()), + (-F::one(), F::zero()), + ], + r_2, + ); + } + + /// Verify floor + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `wrapper` - The proof wrapper. + /// * `evals` - The evaluations of floor instances. + /// * `info` - The floor instance info. + pub fn verify( + &self, + trans: &mut Transcript, + wrapper: &ProofWrapper, + evals: &FloorInstanceEval, + info: &FloorInstanceInfo, + ) -> (bool, Vec) { + let mut subclaim = + MLSumcheck::verify(trans, &wrapper.info, wrapper.claimed_sum, &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&self.u, &subclaim.point); + + if !Self::verify_subclaim(&self.randomness, &mut subclaim, evals, info, eq_at_u_r) { + return (false, vec![]); + } + + let res = subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero(); + + (res, subclaim.point) + } + + /// Verify subclaim. + /// + /// # Arguments. + /// + /// * `randomness` - The randomness for rlc. + /// * `subclaim` - The subclaim returned from the sumcheck protocol. + /// * `evals` - The evaluations of floor instances. + /// * `info` - The floor instance info. + /// * `eq_at_u_r` - The value eq(u,r). + pub fn verify_subclaim( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &FloorInstanceEval, + info: &FloorInstanceInfo, + eq_at_u_r: F, + ) -> bool { + let (output_bits_evals, offset_bits_evals) = evals.extract_decomposed_bits(); + let output_bits_r_num = BitDecompositionIOP::::num_coins(&info.output_bits_info); + let offset_bits_r_num = BitDecompositionIOP::::num_coins(&info.offset_bits_info); + assert_eq!(randomness.len(), output_bits_r_num + offset_bits_r_num + 4); + let check_output_bits = BitDecompositionIOP::::verify_subclaim( + &randomness[..output_bits_r_num], + subclaim, + &output_bits_evals, + &info.output_bits_info, + eq_at_u_r, + ); + let check_offset_bits = BitDecompositionIOP::::verify_subclaim( + &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.selector * (F::one() - evals.selector); + subclaim.expected_evaluations -= r_2 + * eq_at_u_r + * (evals.selector * (evals.input * lambda_1 + evals.output * lambda_2) + + (F::one() - evals.selector) + * (evals.input - evals.output * info.k - evals.offset)); + + // check 3: a - b * k = c + true + } +} + +/// Floor proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct FloorProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info. + pub poly_info: PolynomialInfo, + /// Polynomial commitment. + pub poly_comm: Pcs::Commitment, + /// The evaluation of the polynomial on a random point. + pub oracle_eval: EF, + /// The opening proof of the evaluation. + pub eval_proof: Pcs::Proof, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// The evaluations of small oracles. + pub evals: FloorInstanceEval, +} + +impl FloorProof +where + F: Field + Serialize + for<'de> Deserialize<'de>, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) + } + + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// Floor parameter. +pub struct FloorParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameter for the polynomial commitment. + pub pp: Pcs::Parameters, +} + +impl Default for FloorParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp: Pcs::Parameters::default(), + } + } +} + +impl FloorParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + #[inline] + pub fn setup(&mut self, info: &FloorInstanceInfo, code_spec: S) { + self.pp = Pcs::setup(info.generate_num_var(), Some(code_spec)); + } +} + +/// Prover for Floor with PCS. +pub struct FloorProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for FloorProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + FloorProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl FloorProver +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The prover. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `params` - The pcs params. + /// * `instance` - The floor instance. + pub fn prove( + &self, + trans: &mut Transcript, + params: &FloorParams, + instance: &FloorInstance, + ) -> FloorProof { + let instance_info = instance.info(); + trans.append_message(b"floor instance", &instance_info); + + // 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(); + + // Use PCS to commit the above polynomial. + let (poly_comm, poly_comm_state) = Pcs::commit(¶ms.pp, &committed_poly); + + trans.append_message(b"Floor IOP: polynomial commitment", &poly_comm); + + // Prover generates the proof. + // Convert the orignal instance into an instance defined over EF. + let instance_ef = instance.to_ef::(); + let instance_ef_info = instance_ef.info(); + let mut floor_iop = FloorIOP::::default(); + + floor_iop.generate_randomness(trans, &instance_ef_info); + floor_iop.generate_randomness_for_eq_function(trans, &instance_ef_info); + + let kit = floor_iop.prove(trans, &instance_ef); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point = kit.randomness.clone(); + let oracle_randomness = trans.get_vec_challenge( + b"Floor IOP: random linear combination for evaluations of oracles", + instance_info.log_num_oracles(), + ); + requested_point.extend(&oracle_randomness); + + // Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let oracle_eval = committed_poly.evaluate_ext(&requested_point); + + let evals = instance.evaluate_ext(&kit.randomness); + + // Generate the evaluation proof of the requested point + let eval_proof = Pcs::open( + ¶ms.pp, + &poly_comm, + &poly_comm_state, + &requested_point, + trans, + ); + + FloorProof { + poly_info: kit.info, + poly_comm, + oracle_eval, + eval_proof, + sumcheck_proof: kit.proof, + evals, + } + } +} + +/// Verifier for Floor with PCS. +pub struct FloorVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for FloorVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + FloorVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl FloorVerifier +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The verifier. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `params` - The pcs params. + /// * `info` - The Floor instance info. + /// * `proof` - The Floor proof. + pub fn verify( + &self, + trans: &mut Transcript, + params: &FloorParams, + info: &FloorInstanceInfo, + proof: &FloorProof, + ) -> bool { + let mut res = true; + + trans.append_message(b"floor instance", info); + trans.append_message(b"Floor IOP: polynomial commitment", &proof.poly_comm); + + let mut floor_iop = FloorIOP::::default(); + let info_ef = info.to_ef(); + floor_iop.generate_randomness(trans, &info_ef); + floor_iop.generate_randomness_for_eq_function(trans, &info_ef); + + let proof_wrapper = ProofWrapper { + claimed_sum: EF::zero(), + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), + }; + + let (b, randomness) = floor_iop.verify(trans, &proof_wrapper, &proof.evals, &info.to_ef()); + + res &= b; + + // Check the relation between these small oracles and the committed oracle. + let flatten_evals = proof.evals.flatten(); + let oracle_randomness = trans.get_vec_challenge( + b"Floor IOP: random linear combination for evaluations of oracles", + proof.evals.log_num_oracles(), + ); + + res &= verify_oracle_relation(&flatten_evals, proof.oracle_eval, &oracle_randomness); + + // Check the evaluation of a random point over the committed oracle. + let mut requested_point = randomness.clone(); + requested_point.extend(&oracle_randomness); + res &= Pcs::verify( + ¶ms.pp, + &proof.poly_comm, + &requested_point, + proof.oracle_eval, + &proof.eval_proof, + trans, + ); + + res + } +} diff --git a/zkp/src/piop/lift.rs b/zkp/src/piop/lift.rs new file mode 100644 index 00000000..daef98c6 --- /dev/null +++ b/zkp/src/piop/lift.rs @@ -0,0 +1,1004 @@ +//! PIOP for transformation from Zq to R/QR +//! The prover wants to convince that a \in Zq is correctly transformed into c \in R/QR := ZQ^n s.t. +//! if a' = 2n/q * a < n, c has only one nonzero element 1 at index a' +//! if a' = 2n/q * a >= n, c has only one nonzero element -1 at index a' - n +//! q: the modulus for a +//! Q: the modulus for elements of vector c +//! n: the length of vector c +//! +//! Given M instances of transformation from Zq to R/QR, the main idea of this IOP is to prove: +//! For x \in \{0, 1\}^l +//! +//! 1. (2n/q) * a(x) = k(x) * n + r(x) => reduced to the evaluation of a random point since the LHS and RHS are both MLE +//! +//! 2. r(x) \in [N] => the range check can be proved by the Bit Decomposition IOP +//! +//! 3. k(x) \cdot (1 - k(x)) = 0 => can be reduced to prove the sum +//! $\sum_{x \in \{0, 1\}^\log M} eq(u, x) \cdot [k(x) \cdot (1 - k(x))] = 0$ +//! where u is the common random challenge from the verifier, used to instantiate the sum +//! +//! 4. (r(x) + 1)(1 - 2k(x)) = s(x) => can be reduced to prove the sum +//! $\sum_{x\in \{0,1\}^{\log M}} eq(u,x) \cdot ((r(x) + 1)(1 - 2k(x)) - s(x)) = 0$ +//! where u is the common random challenge from the verifier, used to instantiate the sum +//! +//! 5. \sum_{y \in {0,1}^logN} c(u,y)t(y) = s(u) => can be reduced to prove the sum +//! \sum_{y \in {0,1}^logN} c_u(y)t(y) = s(u) +//! where u is the common random challenge from the verifier, used to instantiate the sum +//! and c'(y) is computed from c_u(y) = c(u,y) +use super::{ + BitDecompositionEval, BitDecompositionIOP, BitDecompositionInstance, + BitDecompositionInstanceInfo, +}; +use crate::sumcheck::{self, verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}; +use crate::utils::{ + eval_identity_function, gen_identity_evaluations, gen_sparse_at_u, verify_oracle_relation, +}; +use algebra::{ + utils::Transcript, AbstractExtensionField, DecomposableField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, PolynomialInfo, SparsePolynomial, +}; +use bincode::Result; +use core::fmt; +use itertools::izip; +use pcs::PolynomialCommitmentScheme; +use serde::{Deserialize, Serialize}; +use std::{marker::PhantomData, rc::Rc}; + +/// Instance of lifting Zq to RQ. +/// In this instance, we require the outputs.len() == 1 << num_vars +pub struct LiftInstance { + /// number of variables + pub num_vars: usize, + /// modulus of Zq + pub q: F, + /// dimension of RWLE denoted by N + pub dim_rlwe: F, + /// input a in Zq + pub input: Rc>, + /// output C = (c_0, ..., c_{N-1})^T \in F^{N * N} + pub outputs: Vec>>, + /// sparse representation of outputs + pub sparse_outputs: Vec>>, + /// the relation (2N/q) * a = k * N + r + /// introduced witness k + pub k: Rc>, + /// introduced witness reminder r + pub reminder: Rc>, + /// decomposed bits of introduced reminder + pub reminder_bits: Vec>>, + /// introduced witness prod denoted by s(x) = (r(x) + 1) * (1 - 2k(x)) + pub prod: Rc>, + /// info for decomposed bits + pub bits_info: BitDecompositionInstanceInfo, +} + +/// Information of LiftInstance +#[derive(Serialize, Deserialize)] +pub struct LiftInstanceInfo { + /// number of variables + pub num_vars: usize, + /// number of instances. + pub num_instances: usize, + /// modulus of Zq + pub q: F, + /// dimension of RWLE denoted by N + pub dim_rlwe: F, + /// info for decomposed bits + pub bits_info: BitDecompositionInstanceInfo, +} + +impl fmt::Display for LiftInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "An instance of Transformation from Zq to RQ: #vars = {}", + self.num_vars, + )?; + write!(f, "- containing ")?; + self.bits_info.fmt(f) + } +} + +impl LiftInstanceInfo { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 4 + self.num_instances + 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 + } + + /// Generate the number of variables in the committed polynomial. + #[inline] + pub fn generate_num_var(&self) -> usize { + self.num_vars + self.log_num_oracles() + } + + /// Construct an EF version. + pub fn to_ef>(&self) -> LiftInstanceInfo { + LiftInstanceInfo:: { + num_vars: self.num_vars, + num_instances: self.num_instances, + q: EF::from_base(self.q), + dim_rlwe: EF::from_base(self.dim_rlwe), + bits_info: self.bits_info.to_ef(), + } + } + + /// Generate table [1,...N]. + pub fn generate_table(&self) -> Rc> { + let mut acc = F::zero(); + let mut table = vec![F::zero(); 1 << self.num_vars]; + for t in table.iter_mut() { + acc += F::one(); + *t = acc; + } + + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + self.num_vars, + table, + )) + } +} + +impl LiftInstance { + /// Extract the information + #[inline] + pub fn info(&self) -> LiftInstanceInfo { + LiftInstanceInfo { + num_vars: self.num_vars, + num_instances: self.outputs.len(), + q: self.q, + dim_rlwe: self.dim_rlwe, + bits_info: self.bits_info.clone(), + } + } + + /// Pack all the involved small polynomials into a single vector + pub fn pack_all_mles(&self) -> Vec { + self.input + .iter() + .chain(self.outputs.iter().flat_map(|output| output.iter())) + .chain(self.k.iter()) + .chain(self.reminder.iter()) + .chain(self.reminder_bits.iter().flat_map(|bit| bit.iter())) + .chain(self.prod.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 info = self.info(); + let num_vars = info.generate_num_var(); + let num_zeros_padded = (1 << num_vars) - info.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) -> LiftInstance { + LiftInstance:: { + num_vars: self.num_vars, + q: EF::from_base(self.q), + dim_rlwe: EF::from_base(self.dim_rlwe), + input: Rc::new(self.input.to_ef::()), + outputs: self + .outputs + .iter() + .map(|output| Rc::new(output.to_ef::())) + .collect(), + sparse_outputs: self + .sparse_outputs + .iter() + .map(|output| Rc::new(output.to_ef::())) + .collect(), + k: Rc::new(self.k.to_ef::()), + reminder: Rc::new(self.reminder.to_ef::()), + reminder_bits: self + .reminder_bits + .iter() + .map(|bit| Rc::new(bit.to_ef::())) + .collect(), + prod: Rc::new(self.prod.to_ef::()), + bits_info: self.bits_info.to_ef::(), + } + } + + /// Evaluate at the same random point + #[inline] + pub fn evaluate(&self, point: &[F]) -> LiftInstanceEval { + LiftInstanceEval { + input: self.input.evaluate(point), + outputs: self + .outputs + .iter() + .map(|output| output.evaluate(point)) + .collect(), + k: self.k.evaluate(point), + reminder: self.reminder.evaluate(point), + reminder_bits: self + .reminder_bits + .iter() + .map(|bit| bit.evaluate(point)) + .collect(), + prod: self.prod.evaluate(point), + } + } + + /// Evaluate at the same random point + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> LiftInstanceEval { + LiftInstanceEval { + input: self.input.evaluate_ext(point), + outputs: self + .outputs + .iter() + .map(|output| output.evaluate_ext(point)) + .collect(), + k: self.k.evaluate_ext(point), + reminder: self.reminder.evaluate_ext(point), + reminder_bits: self + .reminder_bits + .iter() + .map(|bit| bit.evaluate_ext(point)) + .collect(), + prod: self.prod.evaluate_ext(point), + } + } + + /// Extract DecomposedBits instance + #[inline] + pub fn extract_decomposed_bits(&self) -> BitDecompositionInstance { + BitDecompositionInstance { + 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![Rc::clone(&self.reminder)], + d_bits: self.reminder_bits.to_owned(), + } + } +} + +impl LiftInstance { + /// Construct an instance + #[inline] + pub fn new( + num_vars: usize, + q: F, + dim_rlwe: F, + input: &Rc>, + outputs: &[Rc>], + sparse_outputs: &[Rc>], + bits_info: &BitDecompositionInstanceInfo, + ) -> Self { + assert_eq!(outputs.len(), 1 << num_vars); + // factor = 2N/q + let f_two = F::one() + F::one(); + let factor = f_two * dim_rlwe / q; + let mapped_input = input.iter().map(|x| *x * factor).collect::>(); + let mut k = vec![F::zero(); 1 << num_vars]; + let mut reminder = vec![F::zero(); 1 << num_vars]; + // (2N/q) * input = k * N + r + for (m_in, k_, r) in izip!(mapped_input.iter(), k.iter_mut(), reminder.iter_mut()) { + (*k_, *r) = match m_in < &dim_rlwe { + true => (F::zero(), *m_in), + false => (F::one(), *m_in - dim_rlwe), + }; + } + + let k = Rc::new(DenseMultilinearExtension::from_evaluations_vec(num_vars, k)); + let reminder = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, reminder, + )); + let reminder_bits = reminder.get_decomposed_mles(bits_info.base_len, bits_info.bits_len); + let bits_info = BitDecompositionInstanceInfo { + base: bits_info.base, + base_len: bits_info.base_len, + bits_len: bits_info.bits_len, + num_vars, + num_instances: 1, + }; + + // s = (r + 1) * (1 - 2k) + let prod = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + reminder + .iter() + .zip(k.iter()) + .map(|(r, _k)| (*r + F::one()) * (F::one() - f_two * *_k)) + .collect::>(), + )); + + let mut acc = F::zero(); + let mut table = vec![F::zero(); 1 << num_vars]; + for t in table.iter_mut() { + acc += F::one(); + *t = acc; + } + LiftInstance { + num_vars, + q, + dim_rlwe, + input: input.to_owned(), + outputs: outputs.to_owned(), + sparse_outputs: sparse_outputs.to_owned(), + k, + reminder, + reminder_bits, + prod, + bits_info, + } + } +} + +/// Evaluations at the same random point +#[derive(Serialize, Deserialize)] +pub struct LiftInstanceEval { + /// input a in Zq + pub input: F, + /// output C = (c_0, ..., c_{N-1})^T \in F^{N * N} + pub outputs: Vec, + /// We introduce witness k and r such that (2N/q) * a = k * N + r + /// introduced witness k + pub k: F, + /// introduced witness reminder r + pub reminder: F, + /// decomposed bits of introduced reminder + pub reminder_bits: Vec, + /// introduced witness prod denoted by s(x) = (r(x) + 1) * (1 - 2k(x)) + pub prod: F, +} + +impl LiftInstanceEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 4 + self.outputs.len() + self.reminder_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.push(self.input); + res.extend(self.outputs.iter()); + res.push(self.k); + res.push(self.reminder); + res.extend(self.reminder_bits.iter()); + res.push(self.prod); + res + } + + /// Extract DecomposedBitsEval + #[inline] + pub fn extract_decomposed_bits(&self) -> BitDecompositionEval { + BitDecompositionEval { + d_val: vec![self.reminder], + d_bits: self.reminder_bits.to_owned(), + } + } +} + +/// IOP for transformation from Zq to RQ i.e. R/QR +#[derive(Default)] +pub struct LiftIOP { + /// The random vector for random linear combination. + pub randomness: Vec, + /// The random value for identity function. + pub u: Vec, +} + +impl LiftIOP { + /// Sample coins before proving sumcheck protocol + /// + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The lift instance info. + pub fn sample_coins(trans: &mut Transcript, info: &LiftInstanceInfo) -> Vec { + trans.get_vec_challenge( + b"randomness to combine sumcheck protocols", + Self::num_coins(info), + ) + } + + /// Return the number of coins used in this IOP + /// + /// # Arguments. + /// + /// * `info` - The lift instance info. + pub fn num_coins(info: &LiftInstanceInfo) -> usize { + BitDecompositionIOP::::num_coins(&info.bits_info) + 3 + } + + /// Generate the rlc randomenss. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The lift instance info. + #[inline] + pub fn generate_randomness(&mut self, trans: &mut Transcript, info: &LiftInstanceInfo) { + self.randomness = Self::sample_coins(trans, info); + } + + /// Generate the randomness for the eq function. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The lift instance info. + #[inline] + pub fn generate_randomness_for_eq_function( + &mut self, + trans: &mut Transcript, + info: &LiftInstanceInfo, + ) { + self.u = trans.get_vec_challenge( + b"LIFT IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + + /// Lift IOP prover. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `instance` - The lift instance. + pub fn prove(&self, trans: &mut Transcript, instance: &LiftInstance) -> SumcheckKit { + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + + let eq_at_u = Rc::new(gen_identity_evaluations(&self.u)); + let matrix_at_u = Rc::new(gen_sparse_at_u(&instance.sparse_outputs, &self.u)); + + let mut claimed_sum = F::zero(); + Self::prepare_products_of_polynomial( + &self.randomness, + &mut poly, + &mut claimed_sum, + instance, + &matrix_at_u, + &eq_at_u, + &self.u, + ); + + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + SumcheckKit { + proof, + info: poly.info(), + claimed_sum, + randomness: state.randomness, + u: self.u.clone(), + } + } + + /// Add the sumcheck proving lift into the polynomial + /// + /// # Arguments. + /// + /// * `randomness` - The randomness used to randomnize the ntt instance. + /// * `poly` - The list of product of polynomials. + /// * `claimed_sum` - The claimed sum. + /// * `instance` - The round instance. + /// * `matrix_at_u` - The evaluation of matrix on point u. + /// * `eq_at_u` - The evaluation of eq function on point u. + /// * `u` - The randomness for eq. + pub fn prepare_products_of_polynomial( + randomness: &[F], + poly: &mut ListOfProductsOfPolynomials, + claimed_sum: &mut F, + instance: &LiftInstance, + matrix_at_u: &Rc>, + eq_at_u: &Rc>, + u: &[F], + ) { + let bits_instance = instance.extract_decomposed_bits(); + let bits_r_num = >::num_coins(&instance.bits_info); + assert_eq!(randomness.len(), bits_r_num + 3); + let (r_bits, r) = randomness.split_at(bits_r_num); + // 1. add products used to prove decomposition + BitDecompositionIOP::prepare_products_of_polynomial(r_bits, poly, &bits_instance, eq_at_u); + + // 2. add sumcheck \sum_{x} eq(u, x) * k(x) * (1-k(x)) = 0, i.e. k(x)\in\{0,1\}^l with random coefficient r[0] + 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()), + ], + r[0], + ); + + // 3. add sumcheck \sum_{x} eq(u, x) * [ (r(x) + 1) * (1 - 2k(x)) - s(x)] + poly.add_product_with_linear_op( + [ + Rc::clone(eq_at_u), + Rc::clone(&instance.reminder), + Rc::clone(&instance.k), + ], + &[ + (F::one(), F::zero()), + (F::one(), F::one()), + (-F::one() - F::one(), F::one()), + ], + r[1], + ); + poly.add_product([Rc::clone(eq_at_u), Rc::clone(&instance.prod)], -r[1]); + + // 4. add sumcheck \sum_y C(u, y)t(y) = s(u) + poly.add_product( + [ + Rc::clone(matrix_at_u), + Rc::clone(&instance.info().generate_table()), + ], + r[2], + ); + *claimed_sum += instance.prod.evaluate(u) * r[2]; + } + + /// Verify lift + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `wrapper` - The proof wrapper. + /// * `evals_at_r` - The evaluations at random point r. + /// * `evals_at_u` - The evaluations at random point u. + /// * `info` - The list instance info. + pub fn verify( + &self, + trans: &mut Transcript, + wrapper: &ProofWrapper, + evals_at_r: &LiftInstanceEval, + evals_at_u: &LiftInstanceEval, + info: &LiftInstanceInfo, + ) -> (bool, Vec) { + let mut subclaim = + MLSumcheck::verify(trans, &wrapper.info, wrapper.claimed_sum, &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&self.u, &subclaim.point); + + if !Self::verify_subclaim( + &self.randomness, + &mut subclaim, + wrapper.claimed_sum, + evals_at_r, + evals_at_u, + info, + eq_at_u_r, + &self.u, + ) { + return (false, vec![]); + } + + let res = subclaim.expected_evaluations == F::zero(); + + (res, subclaim.point) + } + + /// Verify subclaim. + /// + /// # Arguments. + /// + /// * `randomness` - The randomness for rlc. + /// * `subclaim` - The subclaim returned from the sumcheck protocol. + /// * `claimed_sum` - The claimed sum. + /// * `evals_at_r` - The evaluations at random point r. + /// * `evals_at_u` - The evaluations at random point u. + /// * `info` - The round instance info. + /// * `eq_at_u_r` - The value eq(u,r). + /// * `u` - The randomness for eq function. + #[allow(clippy::too_many_arguments)] + #[inline] + pub fn verify_subclaim( + randomness: &[F], + subclaim: &mut SubClaim, + claimed_sum: F, + evals_at_r: &LiftInstanceEval, + evals_at_u: &LiftInstanceEval, + info: &LiftInstanceInfo, + eq_at_u_r: F, + u: &[F], + ) -> bool { + let bits_eval = evals_at_r.extract_decomposed_bits(); + let bits_r_num = >::num_coins(&info.bits_info); + assert_eq!(randomness.len(), bits_r_num + 3); + let (bits_r, r) = randomness.split_at(bits_r_num); + // check 1: check the decomposed bits + let check_bits = >::verify_subclaim( + bits_r, + subclaim, + &bits_eval, + &info.bits_info, + eq_at_u_r, + ); + if !check_bits { + return false; + } + // check 2: check \sum_{x} eq(u, x) * k(x) * (1-k(x)) = 0, i.e. w(x)\in\{0,1\}^l + subclaim.expected_evaluations -= + r[0] * eq_at_u_r * evals_at_r.k * (F::one() - evals_at_r.k); + // check 3: check sumcheck \sum_{x} eq(u, x) * [ (r(x) + 1) * (1 - 2k(x)) - s(x)] + let f_two = F::one() + F::one(); + subclaim.expected_evaluations -= r[1] + * eq_at_u_r + * ((evals_at_r.reminder + F::one()) * (F::one() - f_two * evals_at_r.k) + - evals_at_r.prod); + + // check 4: check \sum_y C(u, y)t(y) = s(u) + let num_vars = u.len(); + assert_eq!(evals_at_r.outputs.len(), 1 << num_vars); + // c_r = C(x, r) + let c_r = DenseMultilinearExtension::from_evaluations_slice(num_vars, &evals_at_r.outputs); + subclaim.expected_evaluations -= + c_r.evaluate(u) * info.generate_table().evaluate(&subclaim.point) * r[2]; + // TODO optimize evals_at_u to a single F, s(u) + let mut res = claimed_sum == evals_at_u.prod * r[2]; + + // check 5: (2N/q) * a = k * N + r + res &= f_two * info.dim_rlwe * evals_at_r.input + == (evals_at_r.k * info.dim_rlwe + evals_at_r.reminder) * info.q; + + res + } +} + +/// Lift proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct LiftProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info. + pub poly_info: PolynomialInfo, + /// Polynomial commitment. + pub poly_comm: Pcs::Commitment, + /// The evaluation of the polynomial on r. + pub oracle_eval_at_r: EF, + /// The opening proof of the above evaluation. + pub eval_proof_at_r: Pcs::Proof, + /// The evaluation of the polynomial on u. + pub oracle_eval_at_u: EF, + /// The opening proof of the above evaluation. + pub eval_proof_at_u: Pcs::Proof, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// The evaluation of small oracles. + pub evals_at_r: LiftInstanceEval, + /// The evaluation of small oracles. + pub evals_at_u: LiftInstanceEval, + /// Claimed sum in sumcheck. + pub claimed_sum: EF, +} + +impl LiftProof +where + F: Field + Serialize + for<'de> Deserialize<'de>, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) + } + + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// Lift parameter. +pub struct LiftParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameter for the polynomial commitment. + pub pp: Pcs::Parameters, +} + +impl Default for LiftParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp: Pcs::Parameters::default(), + } + } +} + +impl LiftParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + #[inline] + pub fn setup(&mut self, info: &LiftInstanceInfo, code_spec: S) { + self.pp = Pcs::setup(info.generate_num_var(), Some(code_spec)); + } +} + +/// Prover for Lift with PCS. +pub struct LiftProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for LiftProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + LiftProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl LiftProver +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The prover. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `params` - The pcs params. + /// * `instance` - The lift instance. + pub fn prove( + &self, + trans: &mut Transcript, + params: &LiftParams, + instance: &LiftInstance, + ) -> LiftProof { + let instance_info = instance.info(); + trans.append_message(b"lift instance", &instance_info); + + // 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(); + + // Use PCS to commit the above polynomial. + let (poly_comm, poly_comm_state) = Pcs::commit(¶ms.pp, &committed_poly); + + trans.append_message(b"Lift IOP: polynomial commitment", &poly_comm); + + // Prover generates the proof. + // Convert the orignal instance into an instance defined over EF. + let instance_ef = instance.to_ef::(); + let instance_ef_info = instance_ef.info(); + let mut lift_iop = LiftIOP::::default(); + + lift_iop.generate_randomness(trans, &instance_ef_info); + lift_iop.generate_randomness_for_eq_function(trans, &instance_ef_info); + + let kit = lift_iop.prove(trans, &instance_ef); + + // Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let evals_at_r = instance.evaluate_ext(&kit.randomness); + let evals_at_u = instance.evaluate_ext(&lift_iop.u); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point_at_r = kit.randomness.clone(); + let mut requested_point_at_u = lift_iop.u.clone(); + let oracle_randomness = trans.get_vec_challenge( + b"Lift IOP: random linear combination for evaluations of oracles", + instance_info.log_num_oracles(), + ); + + requested_point_at_r.extend(oracle_randomness.iter()); + requested_point_at_u.extend(oracle_randomness.iter()); + 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); + + // Generate the evaluation proof of the requested point + let eval_proof_at_r = Pcs::open( + ¶ms.pp, + &poly_comm, + &poly_comm_state, + &requested_point_at_r, + trans, + ); + let eval_proof_at_u = Pcs::open( + ¶ms.pp, + &poly_comm, + &poly_comm_state, + &requested_point_at_u, + trans, + ); + + LiftProof { + poly_info: kit.info, + poly_comm, + oracle_eval_at_r, + eval_proof_at_r, + oracle_eval_at_u, + eval_proof_at_u, + sumcheck_proof: kit.proof, + evals_at_r, + evals_at_u, + claimed_sum: kit.claimed_sum, + } + } +} + +/// Verifier for Lift with PCS. +pub struct LiftVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for LiftVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + LiftVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl LiftVerifier +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The verifier. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `params` - The pcs params. + /// * `info` - The lift instance info. + /// * `proof` - The lift proof. + pub fn verify( + &self, + trans: &mut Transcript, + params: &LiftParams, + info: &LiftInstanceInfo, + proof: &LiftProof, + ) -> bool { + let mut res = true; + trans.append_message(b"lift instance", info); + trans.append_message(b"Lift IOP: polynomial commitment", &proof.poly_comm); + + let mut lift_iop = LiftIOP::::default(); + let info_ef = info.to_ef(); + + lift_iop.generate_randomness(trans, &info_ef); + lift_iop.generate_randomness_for_eq_function(trans, &info_ef); + + let proof_wrapper = ProofWrapper { + claimed_sum: proof.claimed_sum, + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), + }; + + let (b, randomness) = lift_iop.verify( + trans, + &proof_wrapper, + &proof.evals_at_r, + &proof.evals_at_u, + &info_ef, + ); + + res &= b; + + // Check the relation between these small oracles and the committed oracle + let flatten_evals_at_u = proof.evals_at_u.flatten(); + let flatten_evals_at_r = proof.evals_at_r.flatten(); + let oracle_randomness = trans.get_vec_challenge( + b"Lift IOP: random linear combination for evaluations of oracles", + proof.evals_at_u.log_num_oracles(), + ); + + let mut requested_point_at_r = randomness; + let mut requested_point_at_u = lift_iop.u; + + requested_point_at_r.extend(&oracle_randomness); + requested_point_at_u.extend(&oracle_randomness); + + res &= verify_oracle_relation( + &flatten_evals_at_u, + proof.oracle_eval_at_u, + &oracle_randomness, + ); + + res &= verify_oracle_relation( + &flatten_evals_at_r, + proof.oracle_eval_at_r, + &oracle_randomness, + ); + + // Check the evaluation of a random point over the committed oracle. + res &= Pcs::verify( + ¶ms.pp, + &proof.poly_comm, + &requested_point_at_r, + proof.oracle_eval_at_r, + &proof.eval_proof_at_r, + trans, + ); + + res &= Pcs::verify( + ¶ms.pp, + &proof.poly_comm, + &requested_point_at_u, + proof.oracle_eval_at_u, + &proof.eval_proof_at_u, + trans, + ); + + res + } +} diff --git a/zkp/src/piop/lookup.rs b/zkp/src/piop/lookup.rs new file mode 100644 index 00000000..033c6f65 --- /dev/null +++ b/zkp/src/piop/lookup.rs @@ -0,0 +1,1025 @@ +//! PIOP for lookups +//! 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::{self, verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}; +use crate::utils::{ + batch_inverse, eval_identity_function, gen_identity_evaluations, verify_oracle_relation, +}; +use algebra::{ + utils::Transcript, AbstractExtensionField, AsFrom, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, PolynomialInfo, +}; +use bincode::Result; +use core::fmt; +use pcs::PolynomialCommitmentScheme; +use rayon::{iter::ParallelIterator, slice::ParallelSlice}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, marker::PhantomData, rc::Rc}; + +/// Stores the parameters used for lookup and the public info for verifier. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LookupInstanceInfo { + /// number of variables for lookups + pub num_vars: usize, + /// number of batches + pub num_batch: usize, + /// block size + pub block_size: usize, + /// number of blocks + pub block_num: usize, +} + +impl fmt::Display for LookupInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "instances of Lookup: num_vars = {}, num_batch = {}, block_size = {}", + self.num_vars, self.num_batch, self.block_size, + ) + } +} + +impl LookupInstanceInfo { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_first_oracles(&self) -> usize { + self.num_batch + 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 + } + + /// 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 + } + + /// Generate the number of variables in the first committed polynomial. + #[inline] + pub fn generate_first_num_var(&self) -> usize { + self.num_vars + self.log_num_first_oracles() + } + + /// Generate the number of variables in the second committed polynomial. + #[inline] + pub fn generate_second_num_var(&self) -> usize { + self.num_vars + self.log_num_second_oracles() + } +} + +/// Stores the parameters used for lookup and the inputs and witness for prover. +#[derive(Serialize)] +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, + /// inputs f + pub batch_f: Vec>, + /// inputs table + pub table: DenseMultilinearExtension, + /// intermediate oracle h + pub blocks: Vec>, + /// intermediate oracle m + pub freq: DenseMultilinearExtension, +} + +impl LookupInstance { + /// Extract the information of lookup for verification + #[inline] + pub fn info(&self) -> LookupInstanceInfo { + LookupInstanceInfo { + num_vars: self.num_vars, + num_batch: self.batch_f.len(), + block_size: self.block_size, + block_num: self.block_num, + } + } + + /// Construct an empty instance + #[inline] + pub fn new(num_vars: usize, table: DenseMultilinearExtension, block_size: usize) -> Self { + assert_eq!(table.num_vars, num_vars); + Self { + num_vars, + block_num: 0, + block_size, + batch_f: Vec::new(), + table, + blocks: Vec::new(), + freq: Default::default(), + } + } + + /// Construct a new instance from slice with prepared m. + #[inline] + pub fn from_slice_pure( + num_vars: usize, + batch_f: &[DenseMultilinearExtension], + table: DenseMultilinearExtension, + freq: DenseMultilinearExtension, + block_size: usize, + ) -> Self { + assert!(block_size > 0); + let column_num = batch_f.len() + 1; + Self { + num_vars, + block_num: (column_num + block_size - 1) / block_size, + block_size, + batch_f: batch_f.to_vec(), + table, + blocks: Default::default(), + freq, + } + } + + /// Construct a new instance from slice + #[inline] + pub fn from_slice( + batch_f: &[DenseMultilinearExtension], + table: DenseMultilinearExtension, + block_size: usize, + ) -> Self { + assert!(block_size > 0); + let num_vars = batch_f[0].num_vars; + let column_num = batch_f.len() + 1; + + let mut m_table = HashMap::new(); + let mut hash_table = HashMap::new(); + + table.evaluations.iter().for_each(|&t| { + m_table + .entry(t) + .and_modify(|counter| *counter += 1usize) + .or_insert(1); + hash_table.insert(t, 0usize); + }); + + batch_f.iter().for_each(|f| { + assert_eq!(f.num_vars, num_vars); + f.evaluations.iter().for_each(|&x| { + hash_table.entry(x).and_modify(|counter| *counter += 1); + }) + }); + + let m_evaluations: Vec = table + .evaluations + .iter() + .map(|t| { + let m_f = hash_table[t]; + let m_t = m_table[t]; + + let m_f = F::new(F::Value::as_from(m_f as f64)); + let m_t = F::new(F::Value::as_from(m_t as f64)); + + m_f / m_t + }) + .collect(); + + let m: DenseMultilinearExtension = + DenseMultilinearExtension::from_evaluations_slice(num_vars, &m_evaluations); + Self { + num_vars, + block_num: (column_num + block_size - 1) / block_size, + block_size, + batch_f: batch_f.to_vec(), + table, + blocks: Default::default(), + freq: m, + } + } + + /// Construct an EF version + pub fn to_ef>(&self) -> LookupInstance { + LookupInstance:: { + num_vars: self.num_vars, + block_num: self.block_num, + block_size: self.block_size, + batch_f: self.batch_f.iter().map(|x| x.to_ef()).collect(), + table: self.table.to_ef(), + blocks: Default::default(), + freq: self.freq.to_ef(), + } + } + + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros. + #[inline] + pub fn pack_first_mles(&self) -> Vec { + // arrangement: f | t | m + self.batch_f + .iter() + .flat_map(|x| x.iter()) + .chain(self.table.iter()) + .chain(self.freq.iter()) + .copied() + .collect::>() + } + + /// Pack all the involved small polynomials into a single vector of evaluations without padding zeros. + #[inline] + pub fn pack_second_mles(&self) -> Vec { + // arrangement: h + self.blocks + .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 info = self.info(); + let num_vars = info.generate_first_num_var(); + let num_zeros_padded = (1 << num_vars) - info.num_first_oracles() * (1 << self.num_vars); + + let mut evals = self.pack_first_mles(); + evals.extend_from_slice(&vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// Generate second oracle + pub fn generate_second_oracle(&mut self) -> DenseMultilinearExtension { + let info = self.info(); + let num_vars = info.generate_second_num_var(); + let num_zeros_padded = (1 << num_vars) - info.num_second_oracles() * (1 << self.num_vars); + + let mut evals = self.pack_second_mles(); + evals.extend_from_slice(&vec![F::zero(); num_zeros_padded]); + >::from_evaluations_vec(num_vars, evals) + } + + /// Generate the h vector. + 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.batch_f.clone(); + ft_vec.push(self.table.clone()); + + // Construct shifted columns: (f(x) - r) + let shifted_ft_vec: Vec = ft_vec + .iter() + .flat_map(|f| f.iter()) + .map(|&x| x - random_value) + .collect(); + + let num_threads = rayon::current_num_threads(); + // let chunk_size = (shifted_ft_vec.len() + num_threads - 1) / num_threads; + let chunk_size = shifted_ft_vec.len() / num_threads; + + // Construct inversed shifted columns: 1 / (f(x) - r) + let mut inversed_shifted_ft_evaluation_vec: Vec = shifted_ft_vec + .par_chunks(chunk_size) + .map(|x| batch_inverse(x)) + .flatten() + .collect(); + + let total_size = inversed_shifted_ft_evaluation_vec.len(); + inversed_shifted_ft_evaluation_vec[(total_size - (1 << num_vars))..] + .iter_mut() + .zip(self.freq.evaluations.iter()) + .for_each(|(inverse_shifted_t, m)| { + *inverse_shifted_t *= -(*m); + }); + + let chunks = inversed_shifted_ft_evaluation_vec.chunks(self.block_size * (1 << num_vars)); + + // Construct blocked columns + let h_vec: Vec> = chunks + .map(|block| { + 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(); + + self.blocks = h_vec; + } + + /// Evaluate at a random point defined over base field + #[inline] + pub fn evaluate(&self, point: &[F]) -> LookupInstanceEval { + let mut batch_f = vec![]; + let mut h_vec = vec![]; + let mut table = F::zero(); + let mut freq = F::zero(); + rayon::scope(|s| { + s.spawn(|_| batch_f = self.batch_f.iter().map(|x| x.evaluate(point)).collect()); + s.spawn(|_| h_vec = self.blocks.iter().map(|x| x.evaluate(point)).collect()); + s.spawn(|_| table = self.table.evaluate(point)); + s.spawn(|_| freq = self.freq.evaluate(point)); + }); + + LookupInstanceEval:: { + batch_f, + table, + h_vec, + freq, + } + } + + /// Evaluate at a random point defined over extension field + #[inline] + pub fn evaluate_ext>( + &self, + point: &[EF], + ) -> LookupInstanceEval { + // TODO: Parallel + LookupInstanceEval:: { + batch_f: self.batch_f.iter().map(|x| x.evaluate_ext(point)).collect(), + table: self.table.evaluate_ext(point), + h_vec: self.blocks.iter().map(|x| x.evaluate_ext(point)).collect(), + freq: self.freq.evaluate_ext(point), + } + } +} + +/// Evaluations at a random point +#[derive(Serialize, Deserialize)] +pub struct LookupInstanceEval { + /// The batched vector f + pub batch_f: Vec, + /// The target table + pub table: F, + /// The block vector h + pub h_vec: Vec, + /// The frequency vector m + pub freq: F, +} + +impl LookupInstanceEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_first_oracles(&self) -> usize { + self.batch_f.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 the evals of the first polynomial 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.batch_f.iter().copied()); + res.push(self.table); + res.push(self.freq); + res + } +} + +/// Lookup IOP. +#[derive(Default)] +pub struct LookupIOP { + /// The random value to generate r. + pub random_value: F, + /// The random vector for random linear combination. + pub randomness: Vec, + /// The random value for identity function. + pub u: Vec, +} + +impl LookupIOP { + /// Sample coins for random combination. + #[inline] + pub fn sample_coins(trans: &mut Transcript, instance_info: &LookupInstanceInfo) -> Vec { + trans.get_vec_challenge( + b"Lookup IOP: randomness to combine sumcheck protocols", + instance_info.block_num, + ) + } + + /// Return the number of coins used in this IOP + #[inline] + pub fn num_coins(info: &LookupInstanceInfo) -> usize { + info.block_num + } + + /// The prover initiates the h vector and random value. + #[inline] + pub fn prover_generate_first_randomness( + &mut self, + trans: &mut Transcript, + instance: &mut LookupInstance, + ) { + self.random_value = + trans.get_challenge(b"Lookup IOP: random point used to generate the second oracle"); + + instance.generate_h_vec(self.random_value); + } + + /// The verifier generate the first randomness. + #[inline] + pub fn verifier_generate_first_randomness(&mut self, trans: &mut Transcript) { + self.random_value = + trans.get_challenge(b"Lookup IOP: random point used to generate the second oracle"); + } + + /// Generate randomness for linear combination and identity function. + #[inline] + pub fn generate_second_randomness( + &mut self, + trans: &mut Transcript, + instance_info: &LookupInstanceInfo, + ) { + self.randomness = Self::sample_coins(trans, instance_info); + self.randomness.push(self.random_value); + } + + /// Generate the randomness for eq function. + #[inline] + pub fn generate_randomness_for_eq_function( + &mut self, + trans: &mut Transcript, + instance_info: &LookupInstanceInfo, + ) { + self.u = trans.get_vec_challenge( + b"Lookup IOP: random point used to instantiate sumcheck protocol", + instance_info.num_vars, + ); + } + + /// Lookup IOP prover. + pub fn prove(&self, trans: &mut Transcript, instance: &LookupInstance) -> SumcheckKit { + let eq_at_u = Rc::new(gen_identity_evaluations(&self.u)); + + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + + Self::prepare_products_of_polynomial(&self.randomness, &mut poly, instance, &eq_at_u); + + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + info: poly.info(), + claimed_sum: F::zero(), + randomness: state.randomness, + u: self.u.clone(), + } + } + + /// Handle the list of products of polynomial. + pub fn prepare_products_of_polynomial( + 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.batch_f.clone(); + ft_vec.push(instance.table.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 the product of polynomials. + for ((i, h), u_coef) in instance + .blocks + .iter() + .enumerate() + .zip(random_combine.iter()) + { + let product = vec![Rc::new(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.blocks.len() - 1; + + let residual_size = (instance.batch_f.len() + 1) % instance.block_size; + + let this_block_size = if is_last_block && (residual_size != 0) { + 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(), Rc::new(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(Rc::new(instance.freq.clone())); + } + + poly.add_product_with_linear_op(product, &id_op_coef, -*u_coef); + } + } + } + + /// Verify the lookup statement + pub fn verify( + &self, + trans: &mut Transcript, + wrapper: &ProofWrapper, + evals: &LookupInstanceEval, + info: &LookupInstanceInfo, + ) -> (bool, Vec) { + let mut subclaim = MLSumcheck::verify(trans, &wrapper.info, F::zero(), &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&self.u, &subclaim.point); + + if !Self::verify_subclaim(&self.randomness, &mut subclaim, evals, info, eq_at_u_r) { + return (false, vec![]); + } + + (subclaim.expected_evaluations == F::zero(), subclaim.point) + } + + /// Verify the subclaim of the sumcheck. + pub fn verify_subclaim( + 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.batch_f.clone(); + ft_vec.push(evals.table); + let h_vec = &evals.h_vec; + let m_eval = evals.freq; + + 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 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 + } +} + +/// Lookup proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct LookupProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info + pub poly_info: PolynomialInfo, + /// The first polynomial commitment. + pub first_comm: Pcs::Commitment, + /// The evaluation of the first packed polynomial. + pub first_oracle_eval: EF, + /// The opening proof of the first polynomial. + pub first_eval_proof: Pcs::Proof, + /// The second polynomial commitment. + pub second_comm: Pcs::Commitment, + /// The evaluation of the second packed polynomial. + pub second_oracle_eval: EF, + /// The opening proof of the second polynomial. + pub second_eval_proof: Pcs::ProofEF, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// The evaluations. + pub evals: LookupInstanceEval, +} +impl LookupProof +where + F: Field, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) + } + + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// Lookup parameters. +pub struct LookupParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameters for the first polynomial. + pub pp_first: Pcs::Parameters, + /// The parameters for the second polynomial. + pub pp_second: Pcs::Parameters, +} + +impl Default for LookupParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp_first: Pcs::Parameters::default(), + pp_second: Pcs::Parameters::default(), + } + } +} + +impl LookupParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + pub fn setup(&mut self, info: &LookupInstanceInfo, code_spec: S) { + self.pp_first = Pcs::setup(info.generate_first_num_var(), Some(code_spec.clone())); + + self.pp_second = Pcs::setup(info.generate_second_num_var(), Some(code_spec)); + } +} + +/// Prover for lookup with PCS. +pub struct LookupProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for LookupProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + LookupProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl LookupProver +where + F: Field, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: PolynomialCommitmentScheme< + F, + EF, + S, + Polynomial = DenseMultilinearExtension, + EFPolynomial = DenseMultilinearExtension, + Point = EF, + >, +{ + /// The prover. + pub fn prove( + &self, + trans: &mut Transcript, + params: &LookupParams, + instance: &LookupInstance, + ) -> LookupProof { + let instance_info = instance.info(); + // It is better to hash the shared public instance information, including the table. + trans.append_message(b"lookup instance", &instance_info); + + // 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 first_committed_poly = instance.generate_first_oracle(); + + // Use PCS to commit the above polynomial. + let (first_comm, first_comm_state) = Pcs::commit(¶ms.pp_first, &first_committed_poly); + + trans.append_message(b"Lookup IOP: first commitment", &first_comm); + // Prover generates the proof + // Convert the original instance into an instance defined over EF + let mut instance_ef = instance.to_ef::(); + + let mut lookup_iop = LookupIOP::::default(); + + lookup_iop.prover_generate_first_randomness(trans, &mut instance_ef); + + // Compute the packed second polynomials, i.e., h vector. + let second_committed_poly = instance_ef.generate_second_oracle(); + + // Commit the second polynomial. + let (second_comm, second_comm_state) = + Pcs::commit_ef(¶ms.pp_second, &second_committed_poly); + + trans.append_message(b"Lookup IOP: second commitment", &second_comm); + + // Generate proof of sumcheck protocol + lookup_iop.generate_second_randomness(trans, &instance_info); + lookup_iop.generate_randomness_for_eq_function(trans, &instance_info); + let kit = lookup_iop.prove(trans, &instance_ef); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut first_requested_point = kit.randomness.clone(); + let mut second_requested_point = kit.randomness.clone(); + + let first_oracle_randomness = trans.get_vec_challenge( + b"Lookup IOP: random linear combinaiton for evaluations of first oracles", + instance_info.log_num_first_oracles(), + ); + first_requested_point.extend(&first_oracle_randomness); + + let second_oracle_randomness = trans.get_vec_challenge( + b"Lookup IOP: random linear combination of evaluations of second oracles", + instance_info.log_num_second_oracles(), + ); + second_requested_point.extend(&second_oracle_randomness); + + // Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let evals = instance_ef.evaluate(&kit.randomness); + + let first_oracle_eval = first_committed_poly.evaluate_ext(&first_requested_point); + + let second_oracle_eval = second_committed_poly.evaluate(&second_requested_point); + + // Generate the evaluation proof of the requested point. + let first_eval_proof = Pcs::open( + ¶ms.pp_first, + &first_comm, + &first_comm_state, + &first_requested_point, + trans, + ); + + let second_eval_proof = Pcs::open_ef( + ¶ms.pp_second, + &second_comm, + &second_comm_state, + &second_requested_point, + trans, + ); + + LookupProof { + poly_info: kit.info, + first_comm, + first_oracle_eval, + first_eval_proof, + second_comm, + second_oracle_eval, + second_eval_proof, + sumcheck_proof: kit.proof, + evals, + } + } +} + +/// Verifier for lookup with PCS. +pub struct LookupVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for LookupVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + LookupVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl LookupVerifier +where + F: Field, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// Verify lookup with PCS. + pub fn verify( + &self, + trans: &mut Transcript, + params: &LookupParams, + info: &LookupInstanceInfo, + proof: &LookupProof, + ) -> bool { + let mut res = true; + + trans.append_message(b"lookup instance", info); + trans.append_message(b"Lookup IOP: first commitment", &proof.first_comm); + + let mut lookup_iop = LookupIOP::::default(); + + lookup_iop.verifier_generate_first_randomness(trans); + + trans.append_message(b"Lookup IOP: second commitment", &proof.second_comm); + + // Verify the proof of sumcheck protocol. + lookup_iop.generate_second_randomness(trans, info); + lookup_iop.generate_randomness_for_eq_function(trans, info); + let proof_wrapper = ProofWrapper { + claimed_sum: EF::zero(), + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), + }; + let (b, randomness) = lookup_iop.verify(trans, &proof_wrapper, &proof.evals, info); + + res &= b; + + // Check the relation between these small oracles and the committed oracle. + let flatten_evals = proof.evals.first_flatten(); + let first_oracle_randomness = trans.get_vec_challenge( + b"Lookup IOP: random linear combinaiton for evaluations of first oracles", + proof.evals.log_num_first_oracles(), + ); + + res &= verify_oracle_relation( + &flatten_evals, + proof.first_oracle_eval, + &first_oracle_randomness, + ); + + let second_oracle_randomnes = trans.get_vec_challenge( + b"Lookup IOP: random linear combination of evaluations of second oracles", + proof.evals.log_num_second_oracles(), + ); + + res &= verify_oracle_relation( + &proof.evals.h_vec, + proof.second_oracle_eval, + &second_oracle_randomnes, + ); + + let mut first_requested_point = randomness.clone(); + first_requested_point.extend(&first_oracle_randomness); + // Check the evaluation of a random point over the first committed oracle. + res &= Pcs::verify( + ¶ms.pp_first, + &proof.first_comm, + &first_requested_point, + proof.first_oracle_eval, + &proof.first_eval_proof, + trans, + ); + + let mut second_requested_point = randomness; + second_requested_point.extend(&second_oracle_randomnes); + // Check the evaluation of a random point over the second committed oracle. + res &= Pcs::verify_ef( + ¶ms.pp_second, + &proof.second_comm, + &second_requested_point, + proof.second_oracle_eval, + &proof.second_eval_proof, + trans, + ); + res + } +} diff --git a/zkp/src/piop/mod.rs b/zkp/src/piop/mod.rs index 58bf2c65..cd1b5f10 100644 --- a/zkp/src/piop/mod.rs +++ b/zkp/src/piop/mod.rs @@ -2,15 +2,50 @@ pub mod accumulator; pub mod addition_in_zq; pub mod bit_decomposition; +pub mod external_product; +pub mod floor; +pub mod lift; +pub mod lookup; pub mod ntt; -pub mod rlwe_mul_rgsw; pub mod round; -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 ntt::ntt_bare::NTTBareIOP; -pub use ntt::{NTTInstance, NTTInstanceInfo, NTTIOP}; -pub use rlwe_mul_rgsw::{RlweCiphertext, RlweCiphertexts, RlweMultRgswIOP, RlweMultRgswInstance}; -pub use round::{RoundIOP, RoundInstance}; +pub use accumulator::{ + AccumulatorIOP, AccumulatorInstance, AccumulatorInstanceEval, AccumulatorInstanceInfo, + AccumulatorInstanceInfoClean, AccumulatorParams, AccumulatorProof, AccumulatorProver, + AccumulatorVerifier, AccumulatorWitness, AccumulatorWitnessEval, +}; +pub use addition_in_zq::{ + AdditionInZqIOP, AdditionInZqInstance, AdditionInZqInstanceEval, AdditionInZqInstanceInfo, + AdditionInZqParams, AdditionInZqProof, AdditionInZqProver, AdditionInZqVerifier, +}; +pub use bit_decomposition::{ + BitDecompositionEval, BitDecompositionIOP, BitDecompositionInstance, + BitDecompositionInstanceInfo, BitDecompositionParams, BitDecompositionProof, + BitDecompositionProver, BitDecompositionVerifier, +}; +pub use external_product::{ + ExternalProductIOP, ExternalProductInstance, ExternalProductInstanceEval, + ExternalProductInstanceInfo, ExternalProductInstanceInfoClean, ExternalProductParams, + ExternalProductProof, ExternalProductProver, ExternalProductVerifier, RlweCiphertext, + RlweCiphertextVector, +}; +pub use floor::{ + FloorIOP, FloorInstance, FloorInstanceEval, FloorInstanceInfo, FloorParams, FloorProof, + FloorProver, FloorVerifier, +}; +pub use lift::{ + LiftIOP, LiftInstance, LiftInstanceEval, LiftInstanceInfo, LiftParams, LiftProof, LiftProver, + LiftVerifier, +}; +pub use lookup::{ + LookupIOP, LookupInstance, LookupInstanceEval, LookupInstanceInfo, LookupParams, LookupProof, + LookupProver, LookupVerifier, +}; +pub use ntt::{ + ntt_bare::NTTBareIOP, BatchNTTInstanceInfo, NTTInstance, NTTParams, NTTProof, NTTProver, + NTTRecursiveProof, NTTVerifier, NTTIOP, +}; +pub use round::{ + RoundIOP, RoundInstance, RoundInstanceEval, RoundInstanceInfo, RoundParams, RoundProof, + RoundProver, RoundVerifier, +}; diff --git a/zkp/src/piop/ntt/mod.rs b/zkp/src/piop/ntt/mod.rs index 33dbe9c4..573ae93e 100644 --- a/zkp/src/piop/ntt/mod.rs +++ b/zkp/src/piop/ntt/mod.rs @@ -30,73 +30,179 @@ //! \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::{self, ProofWrapper, SumcheckKit}; +use crate::sumcheck::{prover::ProverState, verifier::SubClaim, MLSumcheck, Proof}; +use crate::utils::{eval_identity_function, gen_identity_evaluations, verify_oracle_relation}; use algebra::{ - DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, MultilinearExtension, - PolynomialInfo, + utils::Transcript, AbstractExtensionField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, PolynomialInfo, }; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; +use core::fmt; +use itertools::izip; +use pcs::PolynomialCommitmentScheme; +use serde::{Deserialize, Serialize}; +use std::{marker::PhantomData, rc::Rc, sync::Arc}; -use ntt_bare::{NTTBareIOP, NTTBareProof, NTTBareSubclaim}; +use bincode::Result; +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) }$$ -pub struct NTTIOP(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, - /// 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>, - /// collective claimed sums for delegation - pub delegation_claimed_sums: Vec, - /// final claim - 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, +/// Types of bit order. +#[derive(Debug, Clone, Copy)] +pub enum BitsOrder { + /// The normal order. + Normal, + /// The reverse order. + Reverse, } /// 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 log_n: usize, + pub num_vars: usize, /// stores {ω^0, ω^1, ..., ω^{2N-1}} - pub ntt_table: Rc>, + pub ntt_table: Arc>, /// coefficient representation of the polynomial - pub coeffs: DenseMultilinearExtension, + pub coeffs: Rc>, /// point-evaluation representation of the polynomial - pub points: DenseMultilinearExtension, + pub points: Rc>, +} + +impl NTTInstance { + /// Extract the information of the NTT Instance for verification + #[inline] + pub fn info(&self) -> BatchNTTInstanceInfo { + BatchNTTInstanceInfo { + num_ntt: 1, + num_vars: self.num_vars, + ntt_table: Arc::clone(&self.ntt_table), + } + } + + /// Construct a new instance from slice + #[inline] + pub fn from_slice( + log_n: usize, + ntt_table: &Arc>, + coeffs: &Rc>, + points: &Rc>, + ) -> Self { + Self { + num_vars: log_n, + ntt_table: ntt_table.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: Arc::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::()), + } + } +} + +/// 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 BatchNTTInstance { + /// number of ntt instances + pub num_ntt: usize, + /// number of variables, which equals to logN. + /// the degree of the polynomial is N - 1 + pub num_vars: usize, + /// stores {ω^0, ω^1, ..., ω^{2N-1}} + pub ntt_table: Arc>, + /// store the coefficient representations + pub coeffs: Vec>>, + /// store the point-evaluation representation + pub points: Vec>>, } /// Stores the corresponding NTT table for the verifier -#[derive(Clone)] -pub struct NTTInstanceInfo { +#[derive(Clone, Debug)] +pub struct BatchNTTInstanceInfo { + /// 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>, + pub ntt_table: Arc>, +} + +/// Stores the information to be hashed. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BatchNTTInstanceInfoClean { + /// 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 num_vars: usize, +} + +impl fmt::Display for BatchNTTInstanceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "a NTT instance randomized from {} NTT instances", + self.num_ntt, + ) + } +} + +impl BatchNTTInstanceInfo { + /// Return the clean info. + #[inline] + pub fn to_clean(&self) -> BatchNTTInstanceInfoClean { + BatchNTTInstanceInfoClean { + num_ntt: self.num_ntt, + num_vars: self.num_vars, + } + } + /// 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 + } + + /// Generate the number of variables in the committed polynomial. + #[inline] + pub fn generate_num_var(&self) -> usize { + self.num_vars + self.log_num_oracles() + 1 + } + + /// Convert to EF version + pub fn to_ef>(&self) -> BatchNTTInstanceInfo { + BatchNTTInstanceInfo { + num_ntt: self.num_ntt, + num_vars: self.num_vars, + ntt_table: Arc::new(self.ntt_table.iter().map(|x| EF::from_base(*x)).collect()), + } + } +} + +/// All the proofs generated only in the recursive phase to prove F(u, v), which does not contain the ntt_bare_proof. +#[derive(Serialize, Deserialize)] +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>, + /// collective claimed sums for delegation + pub delegation_claimed_sums: Vec, + /// final claim + pub final_claim: F, } /// store the intermediate mles generated in each iteration in the `init_fourier_table_overall` algorithm @@ -107,10 +213,10 @@ pub struct IntermediateMLEs { impl IntermediateMLEs { /// Initiate the vector - pub fn new(n_rounds: u32) -> Self { + pub fn new(n_rounds: usize) -> Self { IntermediateMLEs { - f_mles: Vec::with_capacity(n_rounds as usize), - w_mles: Vec::with_capacity(n_rounds as usize), + f_mles: Vec::with_capacity(n_rounds), + w_mles: Vec::with_capacity(n_rounds), } } @@ -148,9 +254,15 @@ impl IntermediateMLEs { /// `init_fourier_table_overall` (this function) stores many intermediate evaluations for the ease of the delegation of F(u, v) /// /// # Arguments -/// * u: the random point -/// * ntt_table: It stores the NTT table: ω^0, ω^1, ..., ω^{2N - 1} -pub fn init_fourier_table_overall(u: &[F], ntt_table: &[F]) -> IntermediateMLEs { +/// +/// * `u` - The random point +/// * `ntt_table` - The NTT table: ω^0, ω^1, ..., ω^{2N - 1} +/// * `bits_order` - The indicator of bits order. +pub fn init_fourier_table_overall( + u: &[F], + ntt_table: &[F], + bits_order: BitsOrder, +) -> IntermediateMLEs { let log_n = u.len(); // N = 1 << dim let m = ntt_table.len(); // M = 2N = 2 * (1 << dim) @@ -161,7 +273,7 @@ pub fn init_fourier_table_overall(u: &[F], ntt_table: &[F]) -> Interme evaluations[0] = F::one(); // stores all the intermediate evaluations of the table (i.e. F(u, x)) and the term ω^{2^{i + 1} * X} in each iteration - let mut intermediate_mles = >::new(log_n as u32); + let mut intermediate_mles = >::new(log_n); // * Compute \prod_{i=0}^{\log{N-1}} ((1 - u_i) + u_i * ω^{2^{i + 1} * X}) * ω^{2^i * x_i} // The reason why we update the table with u_i in reverse order is that @@ -170,39 +282,77 @@ pub fn init_fourier_table_overall(u: &[F], ntt_table: &[F]) -> Interme // // Note that the last term ω^{2^i * x_i} is indeed multiplied in the normal order, from x_0 to x_{log{n-1}} // since we actually iterate from the LSB to MSB when updating the table from size 1, 2, 4, 8, ..., n in dynamic programming. - for i in (0..log_n).rev() { - // i starts from log_n - 1 and ends to 0 - let this_round_dim = log_n - i; - let last_round_dim = this_round_dim - 1; - let this_round_table_size = 1 << this_round_dim; - let last_round_table_size = 1 << last_round_dim; - - let mut evaluations_w_term = vec![F::zero(); this_round_table_size]; - for x in (0..this_round_table_size).rev() { - // idx is to indicate the power ω^{2^{i + 1} * X} in ntt_table - let idx = (1 << (i + 1)) * x % m; - // the bit index in this iteration is last_round_dim = this_round_dim - 1 - // If x >= last_round_table_size, meaning the bit = 1, we need to multiply by ω^{2^last_round_dim * 1} - if x >= last_round_table_size { - evaluations[x] = evaluations[x % last_round_table_size] - * (F::one() - u[i] + u[i] * ntt_table[idx]) - * ntt_table[1 << last_round_dim]; + + match bits_order { + BitsOrder::Normal => { + for i in (0..log_n).rev() { + // i starts from log_n - 1 and ends to 0 + let this_round_dim = log_n - i; + let last_round_dim = this_round_dim - 1; + let this_round_table_size = 1 << this_round_dim; + let last_round_table_size = 1 << last_round_dim; + + let mut evaluations_w_term = vec![F::zero(); this_round_table_size]; + for x in (0..this_round_table_size).rev() { + // idx is to indicate the power ω^{2^{i + 1} * X} in ntt_table + let idx = (1 << (i + 1)) * x % m; + // the bit index in this iteration is last_round_dim = this_round_dim - 1 + // If x >= last_round_table_size, meaning the bit = 1, we need to multiply by ω^{2^last_round_dim * 1} + if x >= last_round_table_size { + evaluations[x] = evaluations[x % last_round_table_size] + * (F::one() - u[i] + u[i] * ntt_table[idx]) + * ntt_table[1 << last_round_dim]; + } + // the bit index in this iteration is last_round_dim = this_round_dim - 1 + // If x < last_round_table_size, meaning the bit = 0, we do not need to multiply because ω^{2^last_round_dim * 0} = 1 + else { + evaluations[x] = evaluations[x % last_round_table_size] + * (F::one() - u[i] + u[i] * ntt_table[idx]); + } + evaluations_w_term[x] = ntt_table[idx]; + } + intermediate_mles.add_round_mles( + this_round_dim, + &evaluations[..this_round_table_size], + evaluations_w_term, + ); } - // the bit index in this iteration is last_round_dim = this_round_dim - 1 - // If x < last_round_table_size, meaning the bit = 0, we do not need to multiply because ω^{2^last_round_dim * 0} = 1 - else { - evaluations[x] = evaluations[x % last_round_table_size] - * (F::one() - u[i] + u[i] * ntt_table[idx]); + } + + BitsOrder::Reverse => { + for (i, u_i) in u.iter().enumerate() { + // i starts from log_n - 1 and ends to 0 + let this_round_dim = i + 1; + let last_round_dim = this_round_dim - 1; + let this_round_table_size = 1 << this_round_dim; + let last_round_table_size = 1 << last_round_dim; + + let mut evaluations_w_term = vec![F::zero(); this_round_table_size]; + for x in (0..this_round_table_size).rev() { + let idx = (1 << (log_n - i)) * x % m; + // the bit index in this iteration is last_round_dim = this_round_dim - 1 + // If x >= last_round_table_size, meaning the bit = 1, we need to multiply by ω^{2^last_round_dim * 1} + if x >= last_round_table_size { + evaluations[x] = evaluations[x % last_round_table_size] + * (F::one() - *u_i + *u_i * ntt_table[idx]) + * ntt_table[1 << last_round_dim]; + } + // the bit index in this iteration is last_round_dim = this_round_dim - 1 + // If x < last_round_table_size, meaning the bit = 0, we do not need to multiply because ω^{2^last_round_dim * 0} = 1 + else { + evaluations[x] = evaluations[x % last_round_table_size] + * (F::one() - *u_i + *u_i * ntt_table[idx]); + } + evaluations_w_term[x] = ntt_table[idx]; + } + intermediate_mles.add_round_mles( + this_round_dim, + &evaluations[..this_round_table_size], + evaluations_w_term, + ); } - evaluations_w_term[x] = ntt_table[idx]; } - intermediate_mles.add_round_mles( - this_round_dim, - &evaluations[..this_round_table_size], - evaluations_w_term, - ); } - intermediate_mles } @@ -210,10 +360,10 @@ pub fn init_fourier_table_overall(u: &[F], ntt_table: &[F]) -> Interme /// /// # Arguments: /// -/// * ntt_table: NTT table for w (M-th root of unity) containing {1, w, w^1, ..., w^{M-1}} -/// * log_m: log of M -/// * x_dim: dimension of x or the num of variables of the outputted mle -/// * exp: the exponent of the function defined above +/// * `ntt_table` - The NTT table for w (M-th root of unity) containing {1, w, w^1, ..., w^{M-1}} +/// * `log_m` - The log of M +/// * `x_dim` - The dimension of x or the num of variables of the outputted mle +/// * `exp` - The exponent of the function defined above pub fn naive_w_power_times_x_table( ntt_table: &[F], log_m: usize, @@ -230,6 +380,24 @@ pub fn naive_w_power_times_x_table( DenseMultilinearExtension::from_evaluations_vec(x_dim, evaluations) } +/// This is for the reverse order. +pub fn naive_w_power_times_x_table_reverse_order( + ntt_table: &[F], + log_m: usize, + x_dim: usize, + sub: usize, +) -> DenseMultilinearExtension { + let m = 1 << log_m; // M = 2N = 2 * (1 << dim) + assert_eq!(ntt_table.len(), m); + assert_eq!(sub, x_dim); + + let mut evaluations: Vec<_> = (0..(1 << x_dim)).map(|_| F::one()).collect(); + for x in 0..(1 << x_dim) { + evaluations[x] = ntt_table[(1 << (log_m - sub)) * x % m]; + } + DenseMultilinearExtension::from_evaluations_vec(x_dim, evaluations) +} + /// Evaluate the mle w^{2^exp * x} for a random point r \in F^{x_dim} /// This formula is also derived from the techniques in [zkCNN](https://eprint.iacr.org/2021/673). /// @@ -239,15 +407,13 @@ pub fn naive_w_power_times_x_table( /// * Note that the above equation only holds for exp <= logM - x_dim; /// * otherwise, the exponent 2^exp * x involves a modular addition, disabling the decomposition. /// -/// (Although I am not clearly making it out, the experiment result shows the above argument.) -/// /// # Arguments: /// -/// * ntt_table: NTT table for w (M-th root of unity) containing {1, w, w^1, ..., w^{M-1}} -/// * log_m: log of M -/// * x_dim: dimension of x or the num of variables of the outputted mle -/// * exp: the exponent of the function defined above -/// * r: random point in F^{x_dim} +/// * `ntt_table` - The NTT table for w (M-th root of unity) containing {1, w, w^1, ..., w^{M-1}} +/// * `log_m` - The log of M +/// * `x_dim` - The dimension of x or the num of variables of the outputted mle +/// * `exp` - The exponent of the function defined above +/// * `r` - The random point in F^{x_dim} pub fn eval_w_power_times_x( ntt_table: &[F], log_m: usize, @@ -268,120 +434,347 @@ pub fn eval_w_power_times_x( prod } -impl NTTInstance { - /// Extract the information of the NTT Instance for verification +/// This is for the reverse order. +pub fn eval_w_power_times_x_reverse_order( + ntt_table: &[F], + log_m: usize, + x_dim: usize, + sub: usize, + r: &[F], +) -> F { + assert_eq!(ntt_table.len(), 1 << log_m); + assert_eq!(x_dim, r.len()); + assert_eq!(sub, x_dim); + let mut prod = F::one(); + + for (i, &r_i) in r.iter().enumerate() { + let log_exp = (log_m - sub + i) % log_m; + prod *= F::one() - r_i + r_i * ntt_table[1 << log_exp]; + } + + prod +} + +impl BatchNTTInstance { + /// Construct an empty container #[inline] - pub fn info(&self) -> NTTInstanceInfo { - NTTInstanceInfo { - log_n: self.log_n, - ntt_table: Rc::clone(&self.ntt_table), + pub fn new(num_vars: usize, ntt_table: &Arc>) -> Self { + Self { + num_ntt: 0, + num_vars, + ntt_table: Arc::clone(ntt_table), + coeffs: Vec::new(), + points: Vec::new(), } } - /// Construct a new instance from vector + /// Extract the information of the NTT Instance for verification #[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, + pub fn info(&self) -> BatchNTTInstanceInfo { + BatchNTTInstanceInfo { + num_ntt: self.num_ntt, + num_vars: self.num_vars, + ntt_table: Arc::clone(&self.ntt_table), } } - /// Construct a new instance from slice + /// Add an ntt instance into the container #[inline] - pub fn from_slice( - log_n: usize, - ntt_table: &Rc>, - coeffs: &Rc>, - points: &Rc>, - ) -> Self { - Self { - log_n, - ntt_table: ntt_table.clone(), - coeffs: coeffs.as_ref().clone(), - points: points.as_ref().clone(), + pub fn add_ntt_instance( + &mut self, + coeff: &Rc>, + point: &Rc>, + ) { + 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)); + } + + /// 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 pack_all_mles(&self) -> Vec { + let info = self.info(); + let num_vars_added_half = info.log_num_oracles(); + let num_zeros_padded_half = + ((1 << num_vars_added_half) - info.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_vars = self.info().generate_num_var(); + // 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 + /// + /// # Arguments. + /// + /// * `randomnss` - The randomness used for random linear combination. + #[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: Arc::clone(&self.ntt_table), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), } } - /// Construct a new instance from given info + /// Construct a random ntt instances from all the ntt instances to be proved, with randomness defined over Extension Field + /// # Arguments. + /// + /// * `randomnss` - The randomness used for random linear combination. #[inline] - pub fn from_info(info: &NTTInstanceInfo) -> 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], - ), + pub fn extract_ntt_instance_to_ef>( + &self, + 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: Arc::new(self.ntt_table.iter().map(|x| EF::from_base(*x)).collect()), + coeffs: Rc::new(random_coeffs), + points: Rc::new(random_points), + } + } +} + +/// IOP for NTT +#[derive(Default)] +pub struct NTTIOP { + /// The randomness to combine all the NTT instances. + pub rlc_randomness: Vec, + /// The random value for initiating sumcheck. + pub u: Vec, +} + +impl NTTIOP { + /// Sample the random coins before proving sumcheck protocol + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The batched ntt instance info without ntt table. + pub fn sample_coins(trans: &mut Transcript, info: &BatchNTTInstanceInfoClean) -> Vec { + trans.get_vec_challenge( + b"randomness used to obtain the virtual random ntt instance", + Self::num_coins(info), + ) } - /// add ntt_instance + /// Return the number of coins used in this IOP + /// + /// # Arguments. + /// + /// * `info` - The batched ntt instance info without ntt table. + pub fn num_coins(info: &BatchNTTInstanceInfoClean) -> usize { + info.num_ntt + } + + /// Generate the randomness. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The batched ntt instance info without ntt table. #[inline] - pub fn add_ntt( + pub fn generate_randomness( &mut self, - r: F, - coeffs: &Rc>, - points: &Rc>, + trans: &mut Transcript, + info: &BatchNTTInstanceInfoClean, ) { - self.coeffs += (r, coeffs); - self.points += (r, points); + self.rlc_randomness = Self::sample_coins(trans, info); } -} -impl NTTSubclaim { - /// verify the subcliam + /// Generate the randomness for the eq function. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The batched ntt instance info without ntt table. #[inline] - pub fn verify_subcliam( + pub fn generate_randomness_for_eq_function( + &mut self, + trans: &mut Transcript, + info: &BatchNTTInstanceInfoClean, + ) { + self.u = trans.get_vec_challenge( + b"NTT IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + + /// Set the randomness for the eq function. + /// + /// # Arguments. + /// + /// * `u` - The vector to be set. + #[inline] + pub fn set_randomness_for_eq_function(&mut self, u: &[F]) { + self.u = u.to_vec(); + } + + /// Prove NTT instance with delegation + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `instance` - The (randomized) ntt instance. + /// * `bits_order` - The indicator of bits order. + pub fn prove( &self, - points: &DenseMultilinearExtension, - coeffs: &DenseMultilinearExtension, - u: &[F], - info: &NTTInstanceInfo, - ) -> 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, + trans: &mut Transcript, + instance: &NTTInstance, + bits_order: BitsOrder, + ) -> (SumcheckKit, NTTRecursiveProof) { + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + + // Just prove one NTT instance. + let randomness = F::one(); + let mut claimed_sum = F::zero(); + NTTBareIOP::::prepare_products_of_polynomial( + randomness, + &mut poly, + &mut claimed_sum, + instance, + &self.u, + bits_order, + ); + + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + // Prove F(u, v) in a recursive manner + let recursive_proof = Self::prove_recursion( + trans, + &state.randomness, + &instance.info(), + &self.u, + bits_order, + ); + + ( + SumcheckKit { + proof, + claimed_sum, + info: poly.info(), + u: self.u.clone(), + randomness: state.randomness, + }, + recursive_proof, + ) + } + + /// Verify NTT instance with delegation + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `wrapper` - The proof wrapper. + /// * `coeff_evals_at_r` - The (randomized) coefficient polynomial evaluated at r. + /// * `point_evals_at_u` - The (randomized) point polynomial evaluated at u. + /// * `info` - The batched ntt instances info. + /// * `recursive_proof` - The recursive sumcheck proof. + /// * `bits_order` - The indicator of bits order. + #[allow(clippy::too_many_arguments)] + pub fn verify( + &self, + trans: &mut Transcript, + wrapper: &mut ProofWrapper, + coeff_evals_at_r: F, + point_evals_at_u: F, + info: &BatchNTTInstanceInfo, + recursive_proof: &NTTRecursiveProof, + bits_order: BitsOrder, + ) -> (bool, Vec) { + // Just verify one NTT instance. + let randomness = F::one(); + + let mut subclaim = + MLSumcheck::verify(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 !NTTBareIOP::::verify_subclaim( + randomness, + &mut subclaim, + &mut wrapper.claimed_sum, + coeff_evals_at_r, + point_evals_at_u, + f_delegation, ) { - return false; + return (false, vec![]); } - // 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]; + if !(subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero()) { + return (false, vec![]); + } - self.delegation_final_claim == eval - } -} + let b = Self::verify_recursion( + trans, + recursive_proof, + info, + &self.u, + &subclaim.point, + bits_order, + ); -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) + (b, subclaim.point) } - /// 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}$. @@ -395,14 +788,15 @@ impl NTTIOP { /// one has coefficient one, and the other has coefficient ω^{2^k}. /// /// # Arguments - /// * round: round number denoted by k, which is iterated in a reverse order as described in the algorithm - /// * point: the random point $(x, b)\in \mathbb{F}^{k+1}$ reduced from the last sumcheck, used to prove the sum in the round - /// * u_i: parameter in this round as described in the formula - /// * w_coeff: the coefficient ω^{2^k} of the second product - /// * 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, + /// + /// * `round` - The round number. + /// * `point` - The random point (x, b) reduced from the last sumcheck. + /// * `u_i` - The parameter in this round. + /// * `w_coeff` - The coefficient ω^{2^k} of the second product. + /// * `f` - The MLE \tilde{A}_{F}^{(k-1)}(z) for z\in \{0,1\}^k + /// * `w` - The MLE \tilde{ω}^{(k)}_{i+1}(z, b) for z\in \{0,1\}^k and b\in \{0, 1\}. + pub fn prove_recursion_round( + trans: &mut Transcript, round: usize, point: &[F], u_i: F, @@ -414,10 +808,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 +820,55 @@ 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) - .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, + 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(trans, &poly).expect("ntt proof of delegation failed in round {round}") + } + + /// Compared to the `prove` functionality, we just remove the phase to prove NTT bare. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `ntt_bare_randomness` - The randomness output by the NTT bare sumcheck protocol. + /// * `info` - The batched ntt instances info. + /// * `u` - The randomness to initiate the sumcheck protocol. + /// * `bits_order` - The indicator of bits order. + pub fn prove_recursion( + trans: &mut Transcript, + ntt_bare_randomness: &[F], + info: &BatchNTTInstanceInfo, u: &[F], - ) -> NTTProof { - let log_n = ntt_instance.log_n; + bits_order: BitsOrder, + ) -> 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, bits_order); 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 @@ -477,47 +878,61 @@ impl NTTIOP { // store the claimed sum of the sumcheck protocol in each round let mut delegation_claimed_sums = Vec::with_capacity(log_n - 1); for k in (1..log_n).rev() { - // start form log_n - 1; - let i = log_n - 1 - k; - delegation_claimed_sums.push(reduced_claim); - - let w_coeff = ntt_instance.ntt_table[1 << k]; - let f = &f_mles[k - 1]; - let (proof_round, state_round) = Self::delegation_prover_round( - fs_rng, - k, - &requested_point, - u[i], - w_coeff, - f, - &w_mles[k], - ); - delegation_sumcheck_msgs.push(proof_round); - - // the requested point returned from this round of sumcheck protocol, which initiates the claimed sum of the next round - requested_point = state_round.randomness; - reduced_claim = f.evaluate(&requested_point); + match bits_order { + BitsOrder::Normal => { + // start form log_n - 1; + let i = log_n - 1 - k; + delegation_claimed_sums.push(reduced_claim); + + let w_coeff = info.ntt_table[1 << k]; + let f = &f_mles[k - 1]; + let (proof_round, state_round) = Self::prove_recursion_round( + trans, + k, + &requested_point, + u[i], + w_coeff, + f, + &w_mles[k], + ); + delegation_sumcheck_msgs.push(proof_round); + + // the requested point returned from this round of sumcheck protocol, which initiates the claimed sum of the next round + requested_point = state_round.randomness; + reduced_claim = f.evaluate(&requested_point); + } + + BitsOrder::Reverse => { + delegation_claimed_sums.push(reduced_claim); + + let w_coeff = info.ntt_table[1 << k]; + let f = &f_mles[k - 1]; + let (proof_round, state_round) = Self::prove_recursion_round( + trans, + k, + &requested_point, + u[k], + w_coeff, + f, + &w_mles[k], + ); + + delegation_sumcheck_msgs.push(proof_round); + + // the requested point returned from this round of sumcheck protocol, which initiates the claimed sum of the next round + requested_point = state_round.randomness; + 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}$. @@ -532,23 +947,26 @@ impl NTTIOP { /// If the equality holds, it is reduced to check the evaluation of \tilde{A}_{F}^{(k-1)}(z = r). /// /// # Arguments - /// * round: round number denoted by k, which is iterated in a reverse order as described in the algorithm - /// * x_b_point: the random point $(x, b)\in \mathbb{F}^{k+1}$ reduced from the last sumcheck - /// * u_i: parameter in this round as described in the formula - /// * subclaim: the subclaim returned from this round of the sumcheck, containing the random point r used for equality check - /// * reduced_claim: the given evaluation of \tilde{A}_{F}^{(k-1)}(z = r) so verify does not need to compute on his own - pub fn delegation_verify_round( + /// + /// * `round` - The round number. + /// * `x_b_point` - The random point (x, b) reduced from the last sumcheck. + /// * `u_i` - The parameter in this round. + /// * `subclaim` - The subclaim returned from this round of the sumcheck. + /// * `reduced_claim` - The given evaluation. + /// * `bits_order` - The indicator of bits order. + pub fn verify_recursion_round( round: usize, x_b_point: &[F], u_i: F, subclaim: &SubClaim, reduced_claim: F, - ntt_instance_info: &NTTInstanceInfo, + ntt_instance_info: &BatchNTTInstanceInfo, + bits_order: BitsOrder, ) -> 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) + // r_left = (r, 0) and r_right = (r, 1) let mut r_left: Vec<_> = Vec::with_capacity(round + 1); let mut r_right: Vec<_> = Vec::with_capacity(round + 1); r_left.extend(&subclaim.point); @@ -556,59 +974,105 @@ impl NTTIOP { r_left.push(F::zero()); r_right.push(F::one()); - // compute $\ω^{(k)}_{i+1}(x,b ) = \ω^{2^{i+1}\cdot j}$ for $j = X+2^{i+1}\cdot b$ at point (r, 0) and (r, 1) - // exp: i + 1 = n - k - let exp = log_n - round; - // w_left = \tilde{ω}^{(k)}_{i+1}(r, 0) and w_right = \tilde{ω}^{(k)}_{i+1}(r, 0) - let w_left = eval_w_power_times_x(ntt_table, log_n + 1, round + 1, exp, &r_left); - let w_right = eval_w_power_times_x(ntt_table, log_n + 1, round + 1, exp, &r_right); - - let eval = eval_identity_function(x_b_point, &r_left) - * reduced_claim - * (F::one() - u_i + u_i * w_left) - + eval_identity_function(x_b_point, &r_right) - * reduced_claim - * (F::one() - u_i + u_i * w_right) - * ntt_table[1 << round]; - - eval == subclaim.expected_evaluations - } - - /// verify NTT with delegation - pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &NTTProof, - ntt_instance_info: &NTTInstanceInfo, + match bits_order { + BitsOrder::Normal => { + // compute $\ω^{(k)}_{i+1}(x,b ) = \ω^{2^{i+1}\cdot j}$ for $j = X+2^{i+1}\cdot b$ at point (r, 0) and (r, 1) + // exp: i + 1 = n - k + let exp = log_n - round; + // w_left = \tilde{ω}^{(k)}_{i+1}(r, 0) and w_right = \tilde{ω}^{(k)}_{i+1}(r, 0) + let w_left = eval_w_power_times_x(ntt_table, log_n + 1, round + 1, exp, &r_left); + let w_right = eval_w_power_times_x(ntt_table, log_n + 1, round + 1, exp, &r_right); + + let eval = eval_identity_function(x_b_point, &r_left) + * reduced_claim + * (F::one() - u_i + u_i * w_left) + + eval_identity_function(x_b_point, &r_right) + * reduced_claim + * (F::one() - u_i + u_i * w_right) + * ntt_table[1 << round]; + + eval == subclaim.expected_evaluations + } + BitsOrder::Reverse => { + // compute $\ω^{(k)}_{i+1}(x,b ) = \ω^{2^{i+1}\cdot j}$ for $j = X+2^{i+1}\cdot b$ at point (r, 0) and (r, 1) + let sub = round + 1; + // w_left = \tilde{ω}^{(k)}_{i+1}(r, 0) and w_right = \tilde{ω}^{(k)}_{i+1}(r, 0) + let w_left = eval_w_power_times_x_reverse_order( + ntt_table, + log_n + 1, + round + 1, + sub, + &r_left, + ); + let w_right = eval_w_power_times_x_reverse_order( + ntt_table, + log_n + 1, + round + 1, + sub, + &r_right, + ); + + let eval = eval_identity_function(x_b_point, &r_left) + * reduced_claim + * (F::one() - u_i + u_i * w_left) + + eval_identity_function(x_b_point, &r_right) + * reduced_claim + * (F::one() - u_i + u_i * w_right) + * ntt_table[1 << round]; + + eval == subclaim.expected_evaluations + } + } + } + + /// Compared to the `prove` functionality, we remove the phase to prove NTT bare. + /// Also, after detaching the verification of NTT bare, verifier can directly check the recursive proofs. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `proof` - The recursive sumcheck proofs. + /// * `info` - The batched ntt instances info. + /// * `u` - The randomness to initiate sumcheck protocol. + /// * `randomness` - The randomness output by the sumcheck protocol. + /// * `bits_order` - The indicator of bits order. + pub fn verify_recursion( + trans: &mut Transcript, + proof: &NTTRecursiveProof, + info: &BatchNTTInstanceInfo, u: &[F], - ) -> NTTSubclaim { - let log_n = ntt_instance_info.log_n; + randomness: &[F], + bits_order: BitsOrder, + ) -> 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 = randomness.to_vec(); for (cnt, k) in (1..log_n).rev().enumerate() { - let i = log_n - 1 - k; + let i = match bits_order { + BitsOrder::Normal => log_n - 1 - k, + _ => 0, + }; // verify the proof of the sumcheck protocol let poly_info = PolynomialInfo { max_multiplicands: 3, num_variables: k, }; - let subclaim = MLSumcheck::verify_as_subprotocol( - fs_rng, + let subclaim = MLSumcheck::verify( + trans, &poly_info, proof.delegation_claimed_sums[cnt], &proof.delegation_sumcheck_msgs[cnt], ) .expect("ntt verification failed in round {cnt}"); - // In the last round of the sumcheck protocol, the verify needs to check the equality of the evaluation of the polynomial to be summed at a random point z = r \in \{0,1\}}^k. + // In the last round of the sumcheck protocol, the verifier needs to check the equality of the evaluation of the polynomial to be summed at a random point z = r \in \{0,1\}}^k. // The verifier is given the evaluation of \tilde{A}_{F}^{(k-1)}(z = r) instead of computing on his own, so he can use it to check. // If the equality holds, it is reduced to check the evaluation of \tilde{A}_{F}^{(k-1)}(z = r). let reduced_claim = if cnt < log_n - 2 { @@ -617,37 +1081,418 @@ impl NTTIOP { proof.final_claim }; // check the equality - if !Self::delegation_verify_round( - k, - &requested_point, - u[i], - &subclaim, - reduced_claim, - ntt_instance_info, - ) { - panic!("ntt verification failed in round {cnt}"); + match bits_order { + BitsOrder::Normal => { + if !Self::verify_recursion_round( + k, + &requested_point, + u[i], + &subclaim, + reduced_claim, + info, + BitsOrder::Normal, + ) { + panic!("ntt verification failed in round {cnt}"); + } + requested_point = subclaim.point; + } + BitsOrder::Reverse => { + if !Self::verify_recursion_round( + k, + &requested_point, + u[k], + &subclaim, + reduced_claim, + info, + BitsOrder::Reverse, + ) { + panic!("ntt verification failed in round {cnt}"); + } + requested_point = subclaim.point; + } } - 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 = match bits_order { + BitsOrder::Normal => { + 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] + } + BitsOrder::Reverse => { + eval_identity_function(&final_point, &[F::zero()]) + + eval_identity_function(&final_point, &[F::one()]) + * (F::one() - u[0] + u[0] * info.ntt_table[idx]) + * info.ntt_table[1] + } + }; + + delegation_final_claim == eval + } +} + +/// NTT proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct NTTProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info. + pub poly_info: PolynomialInfo, + /// Polynomial commitment. + pub poly_comm: Pcs::Commitment, + /// The evaluation of the polynomial of coefficient on a random point. + pub coeff_oracle_eval: EF, + /// The opening proof of the above evaluation of the coefficient polynomial. + pub coeff_eval_proof: Pcs::Proof, + /// The coefficient evaluations of each NTT instance on the random point. + pub coeff_eval: Vec, + /// The evaluation of the polynomial of point. + pub point_oracle_eval: EF, + /// The opening proof of the above evaluation of the point polynomial. + pub point_eval_proof: Pcs::Proof, + /// The point evaluations of each NTT instance on the random point. + pub point_eval: Vec, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// The recursive proof. + pub recursive_proof: NTTRecursiveProof, +} + +impl NTTProof +where + F: Field + Serialize + for<'de> Deserialize<'de>, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) + } + + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// BitDecomposition parameter. +pub struct NTTParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameter for the polynomial commitment. + pub pp: Pcs::Parameters, +} + +impl Default for NTTParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp: Pcs::Parameters::default(), } } } +impl NTTParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + #[inline] + pub fn setup(&mut self, info: &BatchNTTInstanceInfo, code_spec: S) { + self.pp = Pcs::setup(info.generate_num_var(), Some(code_spec)) + } +} + +/// Prover for NTT IOT with PCS. +pub struct NTTProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for NTTProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + NTTProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl NTTProver +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The prover. + pub fn prove( + &self, + trans: &mut Transcript, + params: &NTTParams, + instance: &BatchNTTInstance, + bits_order: BitsOrder, + ) -> NTTProof { + let instance_info = instance.info(); + + trans.append_message(b"ntt instance", &instance_info.to_clean()); + // 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(); + + // Use PCS to commit the above polynomial. + let (poly_comm, comm_state) = Pcs::commit(¶ms.pp, &committed_poly); + + trans.append_message(b"NTT IOP: polynomial commitment", &poly_comm); + + // Generate the randomness for the sumcheck protocol. + let mut iop = NTTIOP::default(); + iop.generate_randomness(trans, &instance_info.to_clean()); + iop.generate_randomness_for_eq_function(trans, &instance_info.to_clean()); + + // Extract the target ntt instance, note that it is define over EF. + let target_ntt_instance = instance.extract_ntt_instance_to_ef::(&iop.rlc_randomness); + + // Prove sumcheck protocol + let (kit, recursive_proof) = iop.prove(trans, &target_ntt_instance, bits_order); + + // Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol. + let eq_at_r = gen_identity_evaluations(&kit.randomness); + let eq_at_u = gen_identity_evaluations(&iop.u); + + let coeff_evals_at_r = instance + .coeffs + .iter() + .map(|x| x.evaluate_ext_opt(&eq_at_r)) + .collect::>(); + let point_evals_at_u = instance + .points + .iter() + .map(|x| x.evaluate_ext_opt(&eq_at_u)) + .collect::>(); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut coeff_requested_point = kit.randomness.clone(); + let mut point_requested_point = iop.u.clone(); + let oracle_randomness = trans.get_vec_challenge( + b"NTT: random linear combination for evaluations of oracles", + instance_info.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 coeff_oracle_eval = committed_poly.evaluate_ext(&coeff_requested_point); + let point_oracle_eval = committed_poly.evaluate_ext(&point_requested_point); + + let coeff_eval_proof = Pcs::open( + ¶ms.pp, + &poly_comm, + &comm_state, + &coeff_requested_point, + trans, + ); + + let point_eval_proof = Pcs::open( + ¶ms.pp, + &poly_comm, + &comm_state, + &point_requested_point, + trans, + ); + + NTTProof { + poly_info: kit.info, + poly_comm, + coeff_oracle_eval, + coeff_eval_proof, + coeff_eval: coeff_evals_at_r, + point_oracle_eval, + point_eval_proof, + point_eval: point_evals_at_u, + sumcheck_proof: kit.proof, + recursive_proof, + } + } +} + +/// Verifier for NTT IOP with PCS. +pub struct NTTVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for NTTVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + NTTVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl NTTVerifier +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The verifier. + pub fn verify( + &self, + trans: &mut Transcript, + params: &NTTParams, + info: &BatchNTTInstanceInfo, + proof: &NTTProof, + bits_order: BitsOrder, + ) -> bool { + let mut res = true; + + trans.append_message(b"ntt instance", &info.to_clean()); + trans.append_message(b"NTT IOP: polynomial commitment", &proof.poly_comm); + + let mut iop = NTTIOP::default(); + + iop.generate_randomness(trans, &info.to_clean()); + iop.generate_randomness_for_eq_function(trans, &info.to_clean()); + + // Get evals_at_r and evals_at_u. + let evals_at_r = iop + .rlc_randomness + .iter() + .zip(proof.coeff_eval.iter()) + .fold(EF::zero(), |acc, (r, eval)| acc + *r * eval); + + let evals_at_u = iop + .rlc_randomness + .iter() + .zip(proof.point_eval.iter()) + .fold(EF::zero(), |acc, (r, eval)| acc + *r * eval); + + // Check the subclaim returned from the sumcheck protocol. + let mut wrapper = ProofWrapper { + claimed_sum: evals_at_u, + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), + }; + + let (b, randomness) = iop.verify( + trans, + &mut wrapper, + evals_at_r, + evals_at_u, + &info.to_ef(), + &proof.recursive_proof, + bits_order, + ); + + res &= b; + + // Check the relation between these small oracles and the committed oracle. + let oracle_randomness = trans.get_vec_challenge( + b"NTT: random linear combination for evaluations of oracles", + info.log_num_oracles(), + ); + res &= verify_oracle_relation( + &proof.coeff_eval, + proof.coeff_oracle_eval, + &oracle_randomness, + ); + + res &= verify_oracle_relation( + &proof.point_eval, + proof.point_oracle_eval, + &oracle_randomness, + ); + + let mut coeff_requested_points = randomness; + coeff_requested_points.extend(&oracle_randomness); + coeff_requested_points.push(EF::zero()); + let mut point_requested_points = iop.u.clone(); + point_requested_points.extend(&oracle_randomness); + point_requested_points.push(EF::one()); + + res &= Pcs::verify( + ¶ms.pp, + &proof.poly_comm, + &coeff_requested_points, + proof.coeff_oracle_eval, + &proof.coeff_eval_proof, + trans, + ); + + res &= Pcs::verify( + ¶ms.pp, + &proof.poly_comm, + &point_requested_points, + proof.point_oracle_eval, + &proof.point_eval_proof, + trans, + ); + + res + } +} + #[cfg(test)] mod test { - use crate::piop::ntt::{eval_w_power_times_x, naive_w_power_times_x_table}; + use crate::piop::ntt::{eval_w_power_times_x, naive_w_power_times_x_table, BitsOrder}; use algebra::{ derive::{DecomposableField, FheField, Field, Prime, NTT}, - DenseMultilinearExtension, FieldUniformSampler, MultilinearExtension, NTTField, + DenseMultilinearExtension, FieldUniformSampler, NTTField, }; use num_traits::{One, Zero}; use rand::thread_rng; @@ -697,7 +1542,8 @@ mod test { } let fourier_mle = DenseMultilinearExtension::from_evaluations_vec(dim << 1, fourier_matrix); - let partial_fourier_mle = &init_fourier_table_overall(&u, &ntt_table).f_mles[dim - 1]; + let partial_fourier_mle = + &init_fourier_table_overall(&u, &ntt_table, BitsOrder::Normal).f_mles[dim - 1]; assert_eq!(fourier_mle.evaluate(&u_v), partial_fourier_mle.evaluate(&v)); } diff --git a/zkp/src/piop/ntt/ntt_bare.rs b/zkp/src/piop/ntt/ntt_bare.rs index c9637f66..d0050d58 100644 --- a/zkp/src/piop/ntt/ntt_bare.rs +++ b/zkp/src/piop/ntt/ntt_bare.rs @@ -16,48 +16,13 @@ //! 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 crate::sumcheck::verifier::SubClaim; +use crate::sumcheck::{MLSumcheck, ProofWrapper, SumcheckKit}; +use algebra::{utils::Transcript, DenseMultilinearExtension, Field, ListOfProductsOfPolynomials}; +use serde::Serialize; use std::rc::Rc; -use algebra::{ - DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, MultilinearExtension, - PolynomialInfo, -}; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; - -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, -} +use super::{BatchNTTInstanceInfo, BitsOrder, NTTInstance}; /// 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) @@ -67,8 +32,9 @@ pub struct NTTBareSubclaim { /// As a result, the equation (8) in zkCNN is = ω^X * \prod_{i=0}^{\log{N-1}} ((1 - u_i) + u_i * ω^{2^{i + 1} * X}) in this case. /// /// # Arguments -/// * u: the random point -/// * ntt_table: It stores the NTT table: ω^0, ω^1, ..., ω^{2N - 1} +/// +/// * `u` - The random point +/// * `ntt_table` - It stores the NTT table: ω^0, ω^1, ..., ω^{2N - 1} /// /// In order to delegate the computation F(u, v) to prover, we decompose the ω^X term into the grand product. /// @@ -83,7 +49,7 @@ pub fn naive_init_fourier_table( let mut evaluations = vec![F::one(); 1 << log_n]; for (x, eval_at_x) in evaluations.iter_mut().enumerate() { - for (i, &u_i) in u.iter().enumerate().take(log_n) { + for (i, &u_i) in u.iter().enumerate() { let idx = (1 << (i + 1)) * x % m; let x_i = (x >> i) & 1; @@ -95,6 +61,34 @@ pub fn naive_init_fourier_table( DenseMultilinearExtension::from_evaluations_vec(log_n, evaluations) } +/// This is for the reverse order. +/// +/// # Arguments +/// +/// * `u` - The random point +/// * `ntt_table` - It stores the NTT table: ω^0, ω^1, ..., ω^{2N - 1} +pub fn naive_init_fourier_table_reverse_order( + u: &[F], + ntt_table: &[F], +) -> DenseMultilinearExtension { + let log_n = u.len(); + let m = ntt_table.len(); // m = 2n = 2 * (1 << dim) + + let mut evaluations = vec![F::one(); 1 << log_n]; + + for (x, eval_at_x) in evaluations.iter_mut().enumerate() { + for (i, &u_i) in u.iter().enumerate().take(log_n) { + let idx = (1 << (log_n - i)) * x % m; + + let x_i = (x >> i) & 1; + let x_i_idx = (1 << i) * x_i; + *eval_at_x *= ((F::one() - u_i) + u_i * ntt_table[idx]) * ntt_table[x_i_idx]; + } + } + + DenseMultilinearExtension::from_evaluations_vec(log_n, evaluations) +} + /// Generate MLE for the Fourier function F(u, x) for x \in \{0, 1\}^dim where u is the random point. /// Dynamic programming implementation for initializing F(u, x) in NTT (derived from zkCNN: https://eprint.iacr.org/2021/673) /// `N` is the dimension of the vector used to represent the polynomial in NTT. @@ -109,8 +103,9 @@ pub fn naive_init_fourier_table( /// * (This function is the dynamic programming version of the above function.) /// /// # Arguments -/// * u: the random point -/// * ω: It stores the NTT table: ω^0, ω^1, ..., ω^{2N - 1} +/// +/// * `u` - The random point +/// * `ntt_table` - It stores the NTT table: ω^0, ω^1, ..., ω^{2N - 1} pub fn init_fourier_table(u: &[F], ntt_table: &[F]) -> DenseMultilinearExtension { let log_n = u.len(); // n = 1 << dim let m = ntt_table.len(); // m = 2n = 2 * (1 << dim) @@ -153,144 +148,204 @@ 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); +/// This is for the reverse order. +/// +/// # Arguments +/// +/// * `u` - The random point +/// * `ntt_table` - It stores the NTT table: ω^0, ω^1, ..., ω^{2N - 1} +pub fn init_fourier_table_reverse_order( + u: &[F], + ntt_table: &[F], +) -> DenseMultilinearExtension { + let log_n = u.len(); // n = 1 << dim + let m = ntt_table.len(); // m = 2n = 2 * (1 << dim) - // check 1: a(u) = claimed_sum - if self.claimed_sum != points.evaluate(u) { - return false; - } + // It store the evaluations of all F(u, x) for x \in \{0, 1\}^dim. + // Note that in our implementation, we use little endian form, so the index `0b1011` + // represents the point `P(1,1,0,1)` in {0,1}^`dim` + let mut evaluations: Vec<_> = vec![F::zero(); 1 << log_n]; + evaluations[0] = F::one(); + + for (i, u_i) in u.iter().enumerate() { + let last_table_size = 1 << i; - // 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) + for j in (0..1 << (i + 1)).rev() { + let idx = (1 << (log_n - i)) * j % m; + // bit is the most significant bit of j. If bit = 1, we need to multiply by ω^{2^k * 1} + let bit = j >> i; + if bit == 1 { + evaluations[j] = evaluations[j % last_table_size] + * (F::one() - *u_i + *u_i * ntt_table[idx]) + * ntt_table[last_table_size]; + } + // If bit = 0, we do not need to multiply because ω^{2^k * 0} = 1 + else { + evaluations[j] = + evaluations[j % last_table_size] * (F::one() - u_i + *u_i * ntt_table[idx]); + } + } } + DenseMultilinearExtension::from_evaluations_vec(log_n, evaluations) +} - /// 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). +/// IOP for NTT, i.e. $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ +#[derive(Default)] +pub struct NTTBareIOP { + /// The random value for identity function. + pub u: Vec, +} + +impl NTTBareIOP { + /// Generate the randomness. /// - /// $$a(u) = \sum_{x\in \{0, 1\}^{\log N} c(x)\cdot F(u, x) }$$ + /// # Arguments. /// - /// 1. a(u) = claimed_sum - /// 2. c(v) * F(u, v) = expected_evaluation - /// # Arguments + /// * `trans` - The transcripts. + /// * `info` - The batched ntt instances info. + #[inline] + pub fn generate_randomness( + &mut self, + trans: &mut Transcript, + info: &BatchNTTInstanceInfo, + ) { + self.u = trans.get_vec_challenge( + b"NTTBare IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + + /// Prove NTT instance without delegation + /// + /// # 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( + /// * `trans` - The transcript. + /// * `instance` - The NTT instance. + /// * `bits_order` - The indicator of bits order. + pub fn prove( &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; + trans: &mut Transcript, + instance: &NTTInstance, + bits_order: BitsOrder, + ) -> SumcheckKit { + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + let randomness = F::one(); + let mut claimed_sum = F::zero(); + Self::prepare_products_of_polynomial( + randomness, + &mut poly, + &mut claimed_sum, + instance, + &self.u, + bits_order, + ); + + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + info: poly.info(), + claimed_sum, + randomness: state.randomness, + u: self.u.clone(), } - - // 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>, + /// Add the sumcheck proving NTT into the polynomial + /// + /// # Arguments. + /// + /// * `randomness` - The randomness used to randomnize the ntt instance. + /// * `poly` - The list of product of polynomials. + /// * `claimed_sum` - The claimed sum of the sumcheck. + /// * `instance` - The NTT instance. + /// * `u` - The randomness used to initiate the sumcheck protocol. + /// * `bits_order` - The indicator of bits order. + pub fn prepare_products_of_polynomial( + randomness: F, + poly: &mut ListOfProductsOfPolynomials, + claimed_sum: &mut F, + instance: &NTTInstance, 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 + bits_order: BitsOrder, + ) { + let f_u = match bits_order { + BitsOrder::Normal => Rc::new(init_fourier_table(u, &instance.ntt_table)), + BitsOrder::Reverse => Rc::new(init_fourier_table_reverse_order(u, &instance.ntt_table)), + }; + poly.add_product([Rc::clone(&f_u), Rc::clone(&instance.coeffs)], randomness); + *claimed_sum += randomness * instance.points.evaluate(u); } - /// prove - pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, - f_u: &Rc>, - ntt_instance: &NTTInstance, - u: &[F], - ) -> (NTTBareProof, ProverState) { - let log_n = ntt_instance.log_n; + /// Verify NTT instance without delegation + /// + /// # Arguments. + /// + /// * `trans` - The transcript. + /// * `wrapper` - The proof warpper. + /// * `coeff_evals_at_r` - The coefficient poly evaluated at random point r. + /// * `point_evals_at_u` - The point poly evaluated at random point u. + /// * `info` - The info of NTT instance. + /// * `bits_order` - The indicator of bits order. + pub fn verify( + &self, + trans: &mut Transcript, + wrapper: &mut ProofWrapper, + coeff_evals_at_r: F, + point_evals_at_u: F, + info: &BatchNTTInstanceInfo, + bits_order: BitsOrder, + ) -> bool { + let f_u = match bits_order { + BitsOrder::Normal => init_fourier_table(&self.u, &info.ntt_table), + BitsOrder::Reverse => init_fourier_table_reverse_order(&self.u, &info.ntt_table), + }; - let mut poly = >::new(log_n); + // randomness to combine sumcheck protocols + let randomness = F::one(); - let product = vec![Rc::clone(f_u), Rc::new(ntt_instance.coeffs.clone())]; - poly.add_product(product, F::one()); + let mut subclaim = + MLSumcheck::verify(trans, &wrapper.info, wrapper.claimed_sum, &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); - let (prover_msg, prover_state) = - MLSumcheck::prove_as_subprotocol(fs_rng, &poly).expect("ntt bare proof failed"); + let f_delegation = f_u.evaluate(&subclaim.point); - ( - NTTBareProof { - sumcheck_msg: prover_msg, - claimed_sum: ntt_instance.points.evaluate(u), - }, - prover_state, - ) - } + if !Self::verify_subclaim( + randomness, + &mut subclaim, + &mut wrapper.claimed_sum, + coeff_evals_at_r, + point_evals_at_u, + f_delegation, + ) { + return false; + } - /// verify - 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) + subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero() } - /// verify - 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, - } + /// Verify the subclaim of the sumcheck proving NTT + /// + /// # Arguments. + /// + /// * `randomness` - The randomness used to randomize the NTT instance. + /// * `subclaim` - The subclaim output by the sumcheck verifier. + /// * `claimed_sum` - The claimed value of the sumcheck. + /// * `coeff_evals_at_r` - The coefficient poly evaluated at random point r. + /// * `point_evals_at_u` - The point poly evaluated at random point u. + /// * `f_delegation` - The point F(u,r). + pub fn verify_subclaim( + randomness: F, + subclaim: &mut SubClaim, + claimed_sum: &mut F, + coeff_evals_at_r: F, + point_evals_at_u: F, + f_delegation: F, + ) -> bool { + subclaim.expected_evaluations -= coeff_evals_at_r * f_delegation * randomness; + *claimed_sum -= point_evals_at_u * randomness; + true } } @@ -298,7 +353,7 @@ impl NTTBareIOP { mod test { use algebra::{ derive::{DecomposableField, FheField, Field, Prime, NTT}, - DenseMultilinearExtension, Field, FieldUniformSampler, MultilinearExtension, NTTField, + DenseMultilinearExtension, Field, FieldUniformSampler, NTTField, }; use num_traits::{One, Zero}; use rand::thread_rng; diff --git a/zkp/src/piop/rlwe_mul_rgsw.rs b/zkp/src/piop/rlwe_mul_rgsw.rs deleted file mode 100644 index 6de90768..00000000 --- a/zkp/src/piop/rlwe_mul_rgsw.rs +++ /dev/null @@ -1,472 +0,0 @@ -//! PIOP for multiplication between RLWE ciphertext and RGSW ciphertext -//! The prover wants to convince verifier the correctness of the multiplication between the RLWE ciphertext and the RGSW ciphetext -//! -//! Input: (a, b) is a RLWE ciphertext and (c, f) is a RGSW ciphertext where RLWE' = Vec and RGSW = RLWE' \times RLWE'. -//! Output: (g, h) is a RLWE ciphertext -//! -//! 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')) -//! Note that (c, f) is given in the NTT form. -//! -//! The multiplication between RLWE and RGSW is performed as follows: -//! 1. Decompose the coefficients of the input RLWE into k bits: a -> (a_0, ..., a_k-1) and b -> (b_0, ..., b_k-1). -//! Note that these are polynomials in the FHE context but oracles in the ZKP context. -//! This can be proven with our Bit Decomposition IOP. -//! 2. Perform NTT on these bits: -//! There are 2k NTT instance, including a_0 =NTT-equal= a_0', ..., a_k-1 =NTT-equal= a_k-1', ...,b_0 =NTT-equal= b_0', ..., b_k-1 =NTT-equal= b_k-1' -//! NTT instance is linear, allowing us to randomize these NTT instances to obtain a single NTT instance. -//! This can be proven with our NTT IOP. -//! 3. Compute: -//! g' = \sum_{i = 0}^{k-1} a_i' \cdot c_i + b_i' \cdot f_i -//! h' = \sum_{i = 0}^{k-1} a_i' \cdot c_i' + b_i' \cdot f_i' -//! Each can be proven with a sumcheck protocol. -//! 4. Perform NTT on g' and h' to obtain its coefficient form g and h. -//! -//! 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 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 algebra::{ - DenseMultilinearExtension, Field, FieldUniformSampler, ListOfProductsOfPolynomials, - MultilinearExtension, PolynomialInfo, -}; -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 -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, -} - -/// 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)] -pub struct RlweCiphertext { - /// the first part of the ciphertext, chosen at random in the FHE scheme. - pub a: Rc>, - /// the second part of the ciphertext, computed with the plaintext in the FHE scheme. - pub b: Rc>, -} - -/// RLWE' ciphertexts represented by two vectors, containing k RLWE ciphertext. -#[derive(Clone)] -pub struct RlweCiphertexts { - /// store the first part of each RLWE ciphertext. - pub a_bits: Vec>>, - /// store the second part of each RLWE ciphertext. - 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, - /// info of decomposed bits - pub decomposed_bits_info: DecomposedBitsInfo, - /// 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, - /// output_rlwe: store the output ciphertext (g, h) in the coefficient-form - pub output_rlwe: RlweCiphertext, -} - -/// store the information used to verify -#[derive(Clone)] -pub struct RlweMultRgswInfo { - /// information of ntt instance - pub ntt_info: NTTInstanceInfo, - /// information of bit decomposition - pub decomposed_bits_info: DecomposedBitsInfo, -} - -impl RlweMultRgswInstance { - /// Extract the information - #[inline] - pub fn info(&self) -> RlweMultRgswInfo { - RlweMultRgswInfo { - ntt_info: self.ntt_instance.info(), - decomposed_bits_info: self.decomposed_bits_info.clone(), - } - } - - /// Construct the instance from reference - #[allow(clippy::too_many_arguments)] - #[inline] - pub fn from( - decomposed_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, - ) -> 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); - - // 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); - - RlweMultRgswInstance { - ntt_instance, - decomposed_bits_info: decomposed_bits_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(), - } - } -} - -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); - } - 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); - } - 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; - } - - // 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; - } - - // 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, - ) { - return false; - } - - // 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(); - - // 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)); - } - - // 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 - ) { - 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)); - } - - 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))) - } -} - -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) - } - - /// 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. - pub fn prove_as_subprotocol( - fs_rng: &mut impl RngCore, - 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 - // where u is randomly sampled by the verifier. - for (a, b, c, f) in izip!( - &instance.bits_rlwe_ntt.a_bits, - &instance.bits_rlwe_ntt.b_bits, - &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); - } - - // 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!( - &instance.bits_rlwe_ntt.a_bits, - &instance.bits_rlwe_ntt.b_bits, - &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); - } - - 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, - ); - - 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 - pub fn verify( - proof: &RlweMultRgswProof, - randomness_ntt: &[F], - u: &[F], - 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) - } - - /// verify the proof with provided RNG - pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &RlweMultRgswProof, - randomness_ntt: &[F], - u: &[F], - 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, - }; - - 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"), - } - } -} diff --git a/zkp/src/piop/round.rs b/zkp/src/piop/round.rs index cbae741d..256c7f02 100644 --- a/zkp/src/piop/round.rs +++ b/zkp/src/piop/round.rs @@ -1,146 +1,354 @@ //! Round IOP -//! The round operation is the scaling operation, followed by a floor operation. -//! -//! The round operation takes as input a \in F_Q and outputs b \in Zq such that b = \floor (a * q) / Q. -//! In some senses, this operation maps an interval of F_Q into an element of Zq. -//! -//! The prover is going to prove: for x \in {0, 1}^\logM -//! 1. b(x) \in [q] -> which can be proven with a range check since q is a power-of-two -//! 2. c(x) \in [1, ..., k] -//! meant to prove c(x) - 1 \in [k] -//! Let L denote the logarithm of the next power-of-two number that is bigger or equal to k. -//! Let delta denote 2^L - k -//! It is necessary to be proven with 2 range checks: -//! one is to prove c(x) - 1 \in [2^L] -//! the other is to prove c(x) - 1 + delta \in [2^L] -//! 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 crate::sumcheck::verifier::SubClaim; -use crate::sumcheck::MLSumcheck; -use crate::sumcheck::Proof; -use crate::utils::eval_identity_function; -use crate::utils::gen_identity_evaluations; -use algebra::DecomposableField; -use itertools::izip; -use std::marker::PhantomData; -use std::rc::Rc; -use std::vec; - +use super::{ + BitDecompositionEval, BitDecompositionIOP, BitDecompositionInstance, + BitDecompositionInstanceInfo, +}; +use crate::sumcheck::{self, verifier::SubClaim, MLSumcheck, ProofWrapper, SumcheckKit}; +use crate::utils::{eval_identity_function, gen_identity_evaluations, verify_oracle_relation}; use algebra::{ - DenseMultilinearExtension, Field, FieldUniformSampler, ListOfProductsOfPolynomials, - MultilinearExtension, PolynomialInfo, + utils::Transcript, AbstractExtensionField, DecomposableField, DenseMultilinearExtension, Field, + ListOfProductsOfPolynomials, PolynomialInfo, }; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; -use rand_distr::Distribution; - -use super::bit_decomposition::{BitDecomposition, BitDecompositionProof, BitDecompositionSubClaim}; -use super::{DecomposedBits, DecomposedBitsInfo}; +use bincode::Result; +use core::fmt; +use itertools::izip; +use pcs::PolynomialCommitmentScheme; +use serde::{Deserialize, Serialize}; +use std::{marker::PhantomData, rc::Rc}; -/// Round IOP -pub struct RoundIOP(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 pub num_vars: usize, - /// k = Q - 1 / q where q is the modulus of the output + /// modulus q + pub q: F, + /// k = (Q - 1) / 2q where q is the modulus of the output pub k: F, - /// delta = 2^{k_bit_len} - k + /// delta = 2^{2*k_len} - k pub delta: F, /// input denoted by a \in F_Q pub input: Rc>, + /// We introduce b' and e such that b' = b + e * q /// output denoted by b \in F_q pub output: Rc>, - /// decomposed bits of output used for range check - pub output_bits: DecomposedBits, - - /// offset denoted by c = a - b * k \in [1, k] such that c - 1 \in [0, k) + /// auxiliary output denoted by b' \in {1, ..., q} satisfying b = b' (mod q) + pub output_aux: Rc>, + /// witness for molular operation denoted by e \in {0, 1}. + pub output_mod: Rc>, + /// decomposed bits of output and auxiliary output used for range check + pub output_bits: Vec>>, + /// decomposition info for outputs in [q] + pub output_bits_info: BitDecompositionInstanceInfo, + /// offset denoted by c = a - b * k \in [1, 2k] 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, - - /// option denoted by w \in {0, 1} - pub option: Rc>, + /// decomposed bits of c - 1 \in [0, 2^2k_len) used for range check + /// decomposed bits of c - 1 + delta \in [0, 2^2k_len) used for range check + pub offset_aux_bits: Vec>>, + /// decomposition info for offset + pub offset_bits_info: BitDecompositionInstanceInfo, + /// selector denoted by w \in {0, 1} + pub selector: Rc>, } /// Information about Round Instance used as verifier keys +#[derive(Serialize, Deserialize)] pub struct RoundInstanceInfo { - /// k = Q - 1 / q is the modulus of the output + /// number of variables + pub num_vars: usize, + /// modulus q + pub q: F, + /// k = Q - 1 / 2q is the modulus of the output pub k: F, - /// delta = 2^k_bits_len - k + /// Let 2k_len denotes the bit length of 2k + /// delta = 2^{2k_len} - k pub delta: F, /// decomposition info for outputs - pub output_bits_info: DecomposedBitsInfo, + pub output_bits_info: BitDecompositionInstanceInfo, /// decomposition info for offset - pub offset_bits_info: DecomposedBitsInfo, + pub offset_bits_info: BitDecompositionInstanceInfo, } -/// 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 RoundInstanceInfo { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 6 + 2 * self.output_bits_info.bits_len + 2 * self.offset_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 + } + + /// Generate the number of variables in the committed polynomial. + #[inline] + pub fn generate_num_var(&self) -> usize { + self.num_vars + self.log_num_oracles() + } + + /// Construct an EF version. + #[inline] + pub fn to_ef>(&self) -> RoundInstanceInfo { + RoundInstanceInfo:: { + num_vars: self.num_vars, + q: EF::from_base(self.q), + k: EF::from_base(self.k), + delta: EF::from_base(self.delta), + output_bits_info: self.output_bits_info.to_ef(), + offset_bits_info: self.offset_bits_info.to_ef(), + } + } +} impl RoundInstance { /// Extract the information #[inline] pub fn info(&self) -> RoundInstanceInfo { RoundInstanceInfo { + num_vars: self.num_vars, + q: self.q, 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(), + } + } + + /// 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.output_aux.iter()) + .chain(self.output_mod.iter()) + .chain(self.offset.iter()) + .chain(self.selector.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 info = self.info(); + let num_vars = info.generate_num_var(); + let num_zeros_padded = (1 << num_vars) - info.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, + q: EF::from_base(self.q), + 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::()), + output_aux: Rc::new(self.output_aux.to_ef::()), + output_mod: Rc::new(self.output_mod.to_ef::()), + offset: Rc::new(self.offset.to_ef::()), + selector: Rc::new(self.selector.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); + let output = self.output.evaluate(point); + let output_aux = self.output_aux.evaluate(point); + RoundInstanceEval:: { + input: self.input.evaluate(point), + output, + output_aux, + output_mod: self.output_mod.evaluate(point), + output_all: vec![output, output_aux - F::one()], + offset, + selector: self.selector.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); + let output = self.output.evaluate_ext(point); + let output_aux = self.output_aux.evaluate_ext(point); + RoundInstanceEval:: { + input: self.input.evaluate_ext(point), + output, + output_aux, + output_mod: self.output_mod.evaluate_ext(point), + output_all: vec![output, output_aux - F::one()], + offset, + selector: self.selector.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, + ) -> (BitDecompositionInstance, BitDecompositionInstance) { + // 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(), + ); + + // b' - 1 + let b_plus_minus_one = DenseMultilinearExtension::from_evaluations_vec( + self.num_vars, + self.output_aux.iter().map(|x| *x - F::one()).collect(), + ); + ( + BitDecompositionInstance { + 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), Rc::new(b_plus_minus_one)], + d_bits: self.output_bits.to_owned(), + }, + BitDecompositionInstance { + 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 { /// Compute the witness required in proof and construct the instance + /// + /// # Arguments. + /// + /// * `num_vars` - The number of variables. + /// * `q` - The modulus of the output. + /// * `k` - The value (Q-1)/2q. + /// * `delta` - The offset. + /// * `input` - The MLE of input. + /// * `output` - The MLS of output. + /// * `output_bits_info` - + #[allow(clippy::too_many_arguments)] #[inline] pub fn new( + num_vars: usize, + q: F, k: F, delta: F, input: Rc>, output: Rc>, - output_bits_info: &DecomposedBitsInfo, - offset_bits_info: &DecomposedBitsInfo, + output_bits_info: &BitDecompositionInstanceInfo, + offset_bits_info: &BitDecompositionInstanceInfo, ) -> Self { - let num_vars = input.num_vars; assert_eq!(num_vars, output.num_vars); assert_eq!(num_vars, output_bits_info.num_vars); assert_eq!(num_vars, offset_bits_info.num_vars); - assert_eq!(1, output_bits_info.num_instances); + assert_eq!(2, 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 mut output_bits = + output.get_decomposed_mles(output_bits_info.base_len, output_bits_info.bits_len); + + // zero MLE + let mut output_aux = DenseMultilinearExtension::::new(num_vars); + let mut output_mod = DenseMultilinearExtension::::new(num_vars); + for (b, b_prime, mod_q) in + izip!(output.iter(), output_aux.iter_mut(), output_mod.iter_mut()) + { + if *b == F::zero() { + *b_prime = q; + *mod_q = F::one(); + } else { + *b_prime = *b; + } + } + let output_aux_minus_one = DenseMultilinearExtension::from_evaluations_vec( + num_vars, + output_aux.iter().map(|x| *x - F::one()).collect::>(), + ); + output_bits.append( + &mut output_aux_minus_one + .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( + // set w = 1 iff a = k & b = 0 + let selector = Rc::new(DenseMultilinearExtension::::from_evaluations_vec( num_vars, input .iter() .zip(output.iter()) - .map(|(a, b)| match (a.is_zero(), b.is_zero()) { + .map(|(a, b)| match ((*a - k).is_zero(), b.is_zero()) { (true, true) => F::one(), _ => F::zero(), }) @@ -150,147 +358,270 @@ impl RoundInstance { // Note that we must set c \in [1, k] when w = 1 to ensure that c(x) \in [1, k] for all x \in {0,1}^logn // if w = 0: c = a - b * k // if w = 1: c = 1 defaultly + let f_two = F::one() + F::one(); let offset = Rc::new(DenseMultilinearExtension::from_evaluations_vec( num_vars, - izip!(option.iter(), input.iter(), output.iter()) - .map(|(w, a, b)| match w.is_zero() { - true => *a - *b * k, + izip!(selector.iter(), input.iter(), output_aux.iter()) + .map(|(w, a, b_prime)| match w.is_zero() { + true => *a - (f_two * b_prime - F::one()) * k, false => F::one(), }) .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, + q, k, delta, input, output, + output_aux: Rc::new(output_aux), + output_mod: Rc::new(output_mod), output_bits, offset, offset_aux_bits, - option, + selector, + 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; - } +/// Evaluation at a random point +#[derive(Serialize, Deserialize)] +pub struct RoundInstanceEval { + /// input denoted by a \in F_Q + pub input: F, + /// output denoted by b \in F_q + pub output: F, + /// auxiliary output denoted by b' \in {1, ..., q} satisfying b = b' (mod q) + pub output_aux: F, + /// witness for molular operation denoted by e \in {0, 1} + pub output_mod: F, + /// output and output_aux - 1 + pub output_all: Vec, + /// decomposed bits of output and auxiliary 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, + /// selector denoted by w \in {0, 1} + pub selector: F, +} - // 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; - } +impl RoundInstanceEval { + /// Return the number of small polynomials used in IOP + #[inline] + pub fn num_oracles(&self) -> usize { + 6 + 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 + } + + /// 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.output_aux); + res.push(self.output_mod); + res.push(self.offset); + res.push(self.selector); + 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) -> (BitDecompositionEval, BitDecompositionEval) { + ( + BitDecompositionEval { + d_val: self.output_all.to_owned(), + d_bits: self.output_bits.to_owned(), + }, + BitDecompositionEval { + 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)) +/// Round IOP +#[derive(Default)] +pub struct RoundIOP { + /// The random vector for random linear combination. + pub randomness: Vec, + /// The random value for identity function. + pub u: Vec, +} + +impl RoundIOP { + /// Sample coins before proving sumcheck protocol + /// + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The round instance info. + pub fn sample_coins(trans: &mut Transcript, info: &RoundInstanceInfo) -> Vec { + trans.get_vec_challenge( + b"Round IOP: randomness to combine sumcheck protocols", + Self::num_coins(info), + ) } - /// 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 + /// + /// # Arguments. + /// + /// * `info` - The round instance info. + pub fn num_coins(info: &RoundInstanceInfo) -> usize { + BitDecompositionIOP::::num_coins(&info.output_bits_info) + + BitDecompositionIOP::::num_coins(&info.offset_bits_info) + + 5 + } - let mut poly = >::new(instance.num_vars); - let identity_func_at_u = Rc::new(gen_identity_evaluations(u)); + /// Generate the rlc randomenss. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The round instance info. + #[inline] + pub fn generate_randomness(&mut self, trans: &mut Transcript, info: &RoundInstanceInfo) { + self.randomness = Self::sample_coins(trans, info); + } - // 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); + /// Generate the randomness for the eq function. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `info` - The round instance info. + #[inline] + pub fn generate_randomness_for_eq_function( + &mut self, + trans: &mut Transcript, + info: &RoundInstanceInfo, + ) { + self.u = trans.get_vec_challenge( + b"ROUND IOP: random point used to instantiate sumcheck protocol", + info.num_vars, + ); + } + + /// Round IOP prover. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `instance` - The round instance. + pub fn prove(&self, trans: &mut Transcript, instance: &RoundInstance) -> SumcheckKit { + let mut poly = ListOfProductsOfPolynomials::::new(instance.num_vars); + + let eq_at_u = Rc::new(gen_identity_evaluations(&self.u)); + + Self::prepare_products_of_polynomial(&self.randomness, &mut poly, instance, &eq_at_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 + let (proof, state) = + MLSumcheck::prove(trans, &poly).expect("fail to prove the sumcheck protocol"); + + SumcheckKit { + proof, + info: poly.info(), + claimed_sum: F::zero(), + randomness: state.randomness, + u: self.u.clone(), + } + } + + /// Add the sumcheck proving floor into the polynomial + /// + /// # Arguments. + /// + /// * `randomness` - The randomness used to randomnize the ntt instance. + /// * `poly` - The list of product of polynomials. + /// * `instance` - The round instance. + /// * `eq_at_u` - The evaluation of eq function on point u. + pub fn prepare_products_of_polynomial( + 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 + 5); + // 1. add products used to prove decomposition + BitDecompositionIOP::prepare_products_of_polynomial( + &randomness[..output_bits_r_num], + poly, + &output_bits_instance, + eq_at_u, + ); + BitDecompositionIOP::prepare_products_of_polynomial( + &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() - 5]; + let lambda_2 = randomness[randomness.len() - 4]; + let r_0 = randomness[randomness.len() - 3]; + let r_1 = randomness[randomness.len() - 2]; + let r_2 = randomness[randomness.len() - 1]; + // add sumcheck for \sum_{x} eq(u, x) * e(x) * (1-e(x))=0 poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), - Rc::clone(&instance.option), - Rc::clone(&instance.option), + Rc::clone(eq_at_u), + Rc::clone(&instance.output_mod), + Rc::clone(&instance.output_mod), + ], + &[ + (F::one(), F::zero()), + (F::one(), F::zero()), + (-F::one(), F::one()), + ], + r_0, + ); + + // 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(eq_at_u), + Rc::clone(&instance.selector), + Rc::clone(&instance.selector), ], &[ (F::one(), F::zero()), @@ -300,29 +631,29 @@ 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)-k) * \lambda_1 + b(x) * \lambda_2) + (1 - w(x)) * (a(x) - (2b'(x)-1) * 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) + // product: eq(u, x) * w(x) * ((a(x)-k) * \lambda_1) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), - Rc::clone(&instance.option), + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), Rc::clone(&instance.input), ], &[ (F::one(), F::zero()), (F::one(), F::zero()), - (lambda_1, F::zero()), + (lambda_1, -lambda_1 * instance.k), ], r_2, ); // product: eq(u, x) * w(x) * (b(x) * \lambda_2) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), - Rc::clone(&instance.option), + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), Rc::clone(&instance.output), ], &[ @@ -335,8 +666,8 @@ 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(&instance.option), + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), Rc::clone(&instance.input), ], &[ @@ -346,25 +677,26 @@ impl RoundIOP { ], r_2, ); - // product: eq(u, x) * (1 - w(x)) * (-k * b(x)) + // product: eq(u, x) * (1 - w(x)) * (- (2b'(x)-1) * k) + // product: eq(u, x) * (1 - w(x)) * (2k * b'(x) - k) * (-r_2) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), - Rc::clone(&instance.option), - Rc::clone(&instance.output), + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), + Rc::clone(&instance.output_aux), ], &[ (F::one(), F::zero()), (-F::one(), F::one()), - (-instance.k, F::zero()), + ((F::one() + F::one()) * instance.k, -instance.k), ], - r_2, + -r_2, ); // product: eq(u, x) * (1 - w(x)) * (-c(x)) poly.add_product_with_linear_op( [ - Rc::clone(&identity_func_at_u), - Rc::clone(&instance.option), + Rc::clone(eq_at_u), + Rc::clone(&instance.selector), Rc::clone(&instance.offset), ], &[ @@ -374,68 +706,374 @@ impl RoundIOP { ], r_2, ); + } + + /// Verify round + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `wrapper` - The proof wrapper. + /// * `evals` - The evaluations of round instances. + /// * `info` - The round instance info. + pub fn verify( + &self, + trans: &mut Transcript, + wrapper: &ProofWrapper, + evals: &RoundInstanceEval, + info: &RoundInstanceInfo, + ) -> (bool, Vec) { + let mut subclaim = + MLSumcheck::verify(trans, &wrapper.info, wrapper.claimed_sum, &wrapper.proof) + .expect("fail to verify the sumcheck protocol"); + let eq_at_u_r = eval_identity_function(&self.u, &subclaim.point); + + if !Self::verify_subclaim(&self.randomness, &mut subclaim, evals, info, eq_at_u_r) { + return (false, vec![]); + } + + let res = subclaim.expected_evaluations == F::zero() && wrapper.claimed_sum == F::zero(); + + (res, subclaim.point) + } + + /// Verify subclaim. + /// + /// # Arguments. + /// + /// * `randomness` - The randomness for rlc. + /// * `subclaim` - The subclaim returned from the sumcheck protocol. + /// * `evals` - The evaluations of round instances. + /// * `info` - The round instance info. + /// * `eq_at_u_r` - The value eq(u,r). + pub fn verify_subclaim( + randomness: &[F], + subclaim: &mut SubClaim, + evals: &RoundInstanceEval, + info: &RoundInstanceInfo, + 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 + 5); + let check_output_bits = >::verify_subclaim( + &randomness[..output_bits_r_num], + subclaim, + &output_bits_evals, + &info.output_bits_info, + eq_at_u_r, + ); + let check_offset_bits = >::verify_subclaim( + &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() - 5]; + let lambda_2 = randomness[randomness.len() - 4]; + let r_0 = randomness[randomness.len() - 3]; + let r_1 = randomness[randomness.len() - 2]; + let r_2 = randomness[randomness.len() - 1]; + + let f_two = F::one() + F::one(); + // check 2: check the subclaim returned from the sumcheck protocol + subclaim.expected_evaluations -= + r_0 * eq_at_u_r * evals.output_mod * (F::one() - evals.output_mod); + subclaim.expected_evaluations -= + r_1 * eq_at_u_r * evals.selector * (F::one() - evals.selector); + subclaim.expected_evaluations -= r_2 + * eq_at_u_r + * (evals.selector * ((evals.input - info.k) * lambda_1 + evals.output * lambda_2) + + (F::one() - evals.selector) + * (evals.input + - (f_two * evals.output_aux - F::one()) * info.k + - evals.offset)); + + // check 3: b' = b + e * q + evals.output_aux == evals.output + evals.output_mod * info.q + } +} + +/// Round proof with PCS. +#[derive(Serialize, Deserialize)] +pub struct RoundProof< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// Polynomial info. + pub poly_info: PolynomialInfo, + /// Polynomial commitment. + pub poly_comm: Pcs::Commitment, + /// The evaluation of the polynomial on a random point. + pub oracle_eval: EF, + /// The opening proof of the evaluation. + pub eval_proof: Pcs::Proof, + /// The sumcheck proof. + pub sumcheck_proof: sumcheck::Proof, + /// The evaluation of small oracles. + pub evals: RoundInstanceEval, +} + +impl RoundProof +where + F: Field + Serialize + for<'de> Deserialize<'de>, + EF: AbstractExtensionField + Serialize + for<'de> Deserialize<'de>, + Pcs: PolynomialCommitmentScheme, +{ + /// Convert into bytes. + pub fn to_bytes(&self) -> Result> { + bincode::serialize(&self) + } + + /// Recover from bytes. + pub fn from_bytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes) + } +} + +/// Round parameter. +pub struct RoundParams< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + /// The parameter for the polynomial commitment. + pub pp: Pcs::Parameters, +} + +impl Default for RoundParams +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + Self { + pp: Pcs::Parameters::default(), + } + } +} + +impl RoundParams +where + F: Field, + EF: AbstractExtensionField, + S: Clone, + Pcs: PolynomialCommitmentScheme, +{ + /// Setup for the PCS. + #[inline] + pub fn setup(&mut self, info: &RoundInstanceInfo, code_spec: S) { + self.pp = Pcs::setup(info.generate_num_var(), Some(code_spec)); + } +} + +/// Prover for Round with PCS. +pub struct RoundProver< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for RoundProver +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + RoundProver { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } + } +} + +impl RoundProver +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The prover. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `params` - The pcs params. + /// * `instance` - The round instance. + pub fn prove( + &self, + trans: &mut Transcript, + params: &RoundParams, + instance: &RoundInstance, + ) -> RoundProof { + let instance_info = instance.info(); + trans.append_message(b"round instance", &instance_info); + + // 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(); + + // Use PCS to commit the above polynomial. + let (poly_comm, poly_comm_state) = Pcs::commit(¶ms.pp, &committed_poly); + + trans.append_message(b"Round IOP: polynomial commitment", &poly_comm); + + // Prover generates the proof. + // Convert the orignal instance into an instance defined over EF. + let instance_ef = instance.to_ef::(); + let instance_ef_info = instance_ef.info(); + let mut round_iop = RoundIOP::::default(); + + round_iop.generate_randomness(trans, &instance_ef_info); + round_iop.generate_randomness_for_eq_function(trans, &instance_ef_info); + + let kit = round_iop.prove(trans, &instance_ef); + + // Reduce the proof of the above evaluations to a single random point over the committed polynomial + let mut requested_point = kit.randomness.clone(); + let oracle_randomness = trans.get_vec_challenge( + b"Round IOP: random linear combination for evaluations of oracles", + instance_info.log_num_oracles(), + ); + requested_point.extend(&oracle_randomness); + + // Compute all the evaluations of these small polynomials used in IOP over the random point returned from the sumcheck protocol + let oracle_eval = committed_poly.evaluate_ext(&requested_point); + + let evals = instance.evaluate_ext(&kit.randomness); + + // Generate the evaluation proof of the requested point + let eval_proof = Pcs::open( + ¶ms.pp, + &poly_comm, + &poly_comm_state, + &requested_point, + trans, + ); - 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, + RoundProof { + poly_info: kit.info, + poly_comm, + oracle_eval, + eval_proof, + sumcheck_proof: kit.proof, + evals, } } +} - /// 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) +/// Verifier for Round with PCS. +pub struct RoundVerifier< + F: Field, + EF: AbstractExtensionField, + S, + Pcs: PolynomialCommitmentScheme, +> { + _marker_f: PhantomData, + _marker_ef: PhantomData, + _marker_s: PhantomData, + _marker_pcs: PhantomData, +} + +impl Default for RoundVerifier +where + F: Field, + EF: AbstractExtensionField, + Pcs: PolynomialCommitmentScheme, +{ + fn default() -> Self { + RoundVerifier { + _marker_f: PhantomData::, + _marker_ef: PhantomData::, + _marker_s: PhantomData::, + _marker_pcs: PhantomData::, + } } +} - /// verify with given rng - pub fn verify_as_subprotocol( - fs_rng: &mut impl RngCore, - proof: &RoundIOPProof, +impl RoundVerifier +where + F: Field + Serialize, + EF: AbstractExtensionField + Serialize, + S: Clone, + Pcs: + PolynomialCommitmentScheme, Point = EF>, +{ + /// The verifier. + /// + /// # Arguments. + /// + /// * `trans` - The transcripts. + /// * `params` - The pcs params. + /// * `info` - The Round instance info. + /// * `proof` - The Round proof. + pub fn verify( + &self, + trans: &mut Transcript, + params: &RoundParams, 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, + proof: &RoundProof, + ) -> bool { + let mut res = true; + + trans.append_message(b"round instance", info); + trans.append_message(b"Round IOP: polynomial commitment", &proof.poly_comm); + + let mut round_iop = RoundIOP::::default(); + let info_ef = info.to_ef(); + round_iop.generate_randomness(trans, &info_ef); + round_iop.generate_randomness_for_eq_function(trans, &info_ef); + + let proof_wrapper = ProofWrapper { + claimed_sum: EF::zero(), + info: proof.poly_info, + proof: proof.sumcheck_proof.clone(), }; - 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), - } + + let (b, randomness) = round_iop.verify(trans, &proof_wrapper, &proof.evals, &info_ef); + + res &= b; + + // Check the relation between these small oracles and the committed oracle. + let flatten_evals = proof.evals.flatten(); + let oracle_randomness = trans.get_vec_challenge( + b"Round IOP: random linear combination for evaluations of oracles", + proof.evals.log_num_oracles(), + ); + + res &= verify_oracle_relation(&flatten_evals, proof.oracle_eval, &oracle_randomness); + + // Check the evaluation of a random point over the committed oracle. + let mut requested_point = randomness.clone(); + requested_point.extend(&oracle_randomness); + res &= Pcs::verify( + ¶ms.pp, + &proof.poly_comm, + &requested_point, + proof.oracle_eval, + &proof.eval_proof, + trans, + ); + + res } } diff --git a/zkp/src/piop/zq_to_rq.rs b/zkp/src/piop/zq_to_rq.rs deleted file mode 100644 index 98818545..00000000 --- a/zkp/src/piop/zq_to_rq.rs +++ /dev/null @@ -1,524 +0,0 @@ -//! PIOP for transformation from Zq to R/QR -//! The prover wants to convince that a \in Zq is correctly transformed into c \in R/QR := ZQ^n s.t. -//! if a' = 2n/q * a < n, c has only one nonzero element 1 at index a' -//! if a' = 2n/q * a >= n, c has only one nonzero element -1 at index a' - n -//! q: the modulus for a -//! Q: the modulus for elements of vector c -//! n: the length of vector c -//! -//! Given M instances of transformation from Zq to R/QR, the main idea of this IOP is to prove: -//! For x \in \{0, 1\}^l -//! -//! 1. (2n/q) * a(x) = k(x) * n + r(x) => reduced to the evaluation of a random point since the LHS and RHS are both MLE -//! -//! 2. r(x) \in [q] => the range check can be proved by the Bit Decomposition IOP -//! -//! 3. k(x) \cdot (1 - k(x)) = 0 => can be reduced to prove the sum -//! $\sum_{x \in \{0, 1\}^\log M} eq(u, x) \cdot [k(x) \cdot (1 - k(x))] = 0$ -//! where u is the common random challenge from the verifier, used to instantiate the sum -//! -//! 4. (r(x) + 1)(1 - 2k(x)) = s(x) => can be reduced to prove the sum -//! $\sum_{x\in \{0,1\}^{\log M}} eq(u,x) \cdot ((r(x) + 1)(1 - 2k(x)) - s(x)) = 0$ -//! where u is the common random challenge from the verifier, used to instantiate the sum -//! -//! 5. \sum_{y \in {0,1}^logN} c(u,y)t(y) = s(u) => can be reduced to prove the sum -//! \sum_{y \in {0,1}^logN} c_u(y)t(y) = s(u) -//! where u is the common random challenge from the verifier, used to instantiate the sum -//! and c'(y) is computed from c_u(y) = c(u,y) - -use std::marker::PhantomData; -use std::rc::Rc; - -use super::bit_decomposition::{ - BitDecompositionProof, BitDecompositionSubClaim, DecomposedBits, DecomposedBitsInfo, -}; -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::{ - AsFrom, DecomposableField, DenseMultilinearExtension, Field, ListOfProductsOfPolynomials, - MultilinearExtension, PolynomialInfo, SparsePolynomial, -}; -use rand::{RngCore, SeedableRng}; -use rand_chacha::ChaCha12Rng; - -/// proof generated by prover -pub struct TransformZqtoRQProof { - /// singe rangecheck proof for r - pub rangecheck_msg: BitDecompositionProof, - /// sumcheck proofs for - /// \sum_{x} eq(u,x) * k(x) * (1 - k(x)) = 0; - /// \sum_{x} eq(u,x) * ((r(x) + 1) * (1 - 2k(x)) - s(x)) = 0; - /// \sum_{y} c_u(y) * t(y) = s(u) - pub sumcheck_msgs: Vec>>, - /// the claimed sum of the third sumcheck i.e. s(u) - pub s_u: F, -} - -/// subclaim returned to verifier -pub struct TransformZqtoRQSubclaim { - /// rangecheck subclaim for a, b, c \in Zq - pub(crate) rangecheck_subclaim: BitDecompositionSubClaim, - /// subcliam - pub sumcheck_points: Vec>, - /// expected value returned in the last round of the sumcheck - pub sumcheck_expected_evaluations: Vec, -} - -/// Stores the parameters used for transformation from Zq to RQ and the inputs and witness for prover. -/// example parameters: LWE: n=512, q=512, RLWE: N=1024, Q=132120577 -pub struct TransformZqtoRQInstance { - /// number of variables - pub num_vars: usize, - /// modulus of Zq - pub q: usize, - /// row_num of c - pub m: usize, - /// column_num of c - pub n: usize, - /// inputs c - pub c: Vec>>, - /// inputs a - pub a: Rc>, - /// introduced witness k - pub k: Rc>, - /// introduced witness r - pub r: Rc>, - /// introduced witness s - pub s: Rc>, - /// introduced witness to check the range of a, b, c - pub r_bits: DecomposedBits, -} - -/// Stores the parameters used for addition in Zq and the public info for verifier. -pub struct TransformZqtoRQInstanceInfo { - /// number of variables - pub num_vars: usize, - /// modulus of Zq - pub q: usize, - /// row_num of c - pub m: usize, - /// column_num of c - pub n: usize, - /// decomposition info for range check (i.e. bit decomposition) - pub decomposed_bits_info: DecomposedBitsInfo, -} - -impl TransformZqtoRQInstance { - /// Extract the information of addition in Zq for verification - #[inline] - pub fn info(&self) -> TransformZqtoRQInstanceInfo { - TransformZqtoRQInstanceInfo { - num_vars: self.num_vars, - q: self.q, - m: self.m, - n: self.n, - decomposed_bits_info: self.r_bits.info(), - } - } - - /// Construct a new instance from vector - #[inline] - #[allow(clippy::too_many_arguments)] - pub fn from_vec( - q: usize, - c: Vec>>, - a: Rc>, - k: &Rc>, - r: &Rc>, - s: &Rc>, - base: F, - base_len: u32, - bits_len: u32, - ) -> Self { - let num_vars = a.num_vars; - let c_num_vars = c[0].num_vars; - let n = 2_usize.pow(c_num_vars as u32); - let m = c.len(); - - assert_eq!(k.num_vars, num_vars); - assert_eq!(r.num_vars, num_vars); - assert_eq!(s.num_vars, num_vars); - assert_eq!(2_usize.pow(num_vars as u32), m); - assert_eq!(2 * n % q, 0); - c.iter().for_each(|x| { - assert_eq!(x.num_vars, c_num_vars); - assert_eq!(x.evaluations.len(), 1); - }); - - // 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)]; - - Self { - q, - m, - n, - num_vars, - c, - a, - k: Rc::clone(k), - r: Rc::clone(r), - s: Rc::clone(s), - r_bits: DecomposedBits { - base, - base_len, - bits_len, - num_vars, - instances: r_bits, - }, - } - } -} - -/// SNARKs for transformation from Zq to RQ i.e. R/QR -pub struct TransformZqtoRQ(PhantomData); - -impl TransformZqtoRQ { - /// Prove transformation from a \in Zq to c \in R/QR - pub fn prove( - transform_instance: &TransformZqtoRQInstance, - u: &[F], - ) -> TransformZqtoRQProof { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::prove_as_subprotocol(&mut fs_rng, transform_instance, u) - } - - /// Prove transformation from Zq to R/QR given input a, c, witness k, r, s and the decomposed bits for r. - /// 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, - transform_instance: &TransformZqtoRQInstance, - u: &[F], - ) -> TransformZqtoRQProof { - let dim = u.len(); - assert_eq!(dim, transform_instance.num_vars); - - // 1. rangecheck for r - let rangecheck_msg = - BitDecomposition::prove_as_subprotocol(fs_rng, &transform_instance.r_bits, u); - - // 2. execute sumcheck for \sum_{x \in {0,1}^logM} eq(u, x) * k(x) * (1-k(x)) = 0 i.e. k(x) \in \{0,1\}^dim - let mut poly = >::new(dim); - - 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(&transform_instance.k)); - op_coefficient.push((F::one(), F::zero())); - product.push(Rc::clone(&transform_instance.k)); - op_coefficient.push((-F::one(), F::one())); - poly.add_product_with_linear_op(product, &op_coefficient, F::one()); - - let first_sumcheck_proof = MLSumcheck::prove_as_subprotocol(fs_rng, &poly) - .expect("sumcheck for transformation from Zq to RQ failed"); - - // 3. execute sumcheck for \sum_{x \in {0,1}^logM} eq(u,x)((r(x) + 1) * (1 - 2k(x)) - s(x)) = 0 i.e. (r(x) + 1)(1 - 2k(x)) = s(x) for x in \{0,1\}^dim - let mut poly = >::new(dim); - - 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(&transform_instance.r)); - op_coefficient.push((F::one(), F::one())); - product.push(Rc::clone(&transform_instance.k)); - op_coefficient.push((-(F::one() + F::one()), F::one())); - poly.add_product_with_linear_op(product, &op_coefficient, F::one()); - - let mut product = Vec::with_capacity(2); - let mut op_coefficient = Vec::with_capacity(2); - product.push(Rc::new(gen_identity_evaluations(u))); - op_coefficient.push((F::one(), F::zero())); - product.push(Rc::clone(&transform_instance.s)); - op_coefficient.push((-F::one(), F::zero())); - poly.add_product_with_linear_op(product, &op_coefficient, F::one()); - - let second_sumcheck_proof = MLSumcheck::prove_as_subprotocol(fs_rng, &poly) - .expect("sumcheck for transformation from Zq to RQ failed"); - - // 4. sumcheck for \sum_{y \in {0,1}^logN} c(u,y)t(y) = s(u) - let c_num_vars = transform_instance.n.ilog(2) as usize; - - // construct c_u - let eq_u = gen_identity_evaluations(u).evaluations; - let mut c_u_evaluations = vec![F::zero(); transform_instance.n]; - transform_instance - .c - .iter() - .enumerate() - .for_each(|(x_idx, sparse_p)| { - sparse_p.iter().for_each(|(y_idx, value)| { - c_u_evaluations[*y_idx] += eq_u[x_idx] * value; - }); - }); - let c_u = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - c_num_vars, - c_u_evaluations, - )); - - // construct t - let t_evaluations = (1..=transform_instance.n) - .map(|i| F::new(F::Value::as_from(i as f64))) - .collect(); - let t = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - c_num_vars, - t_evaluations, - )); - - let mut poly = >::new(c_num_vars); - let mut product = Vec::with_capacity(2); - let mut op_coefficient = Vec::with_capacity(2); - product.push(Rc::clone(&c_u)); - op_coefficient.push((F::one(), F::zero())); - product.push(Rc::clone(&t)); - op_coefficient.push((F::one(), F::zero())); - poly.add_product_with_linear_op(product, &op_coefficient, F::one()); - - let third_sumcheck_proof = MLSumcheck::prove_as_subprotocol(fs_rng, &poly) - .expect("sumcheck for transformation from Zq to RQ failed"); - - TransformZqtoRQProof { - rangecheck_msg, - sumcheck_msgs: vec![ - first_sumcheck_proof.0, - second_sumcheck_proof.0, - third_sumcheck_proof.0, - ], - s_u: transform_instance.s.evaluate(u), - } - } - - /// Verify transformation from Zq to RQ given the proof and the verification key for bit decomposistion - pub fn verify( - proof: &TransformZqtoRQProof, - decomposed_bits_info: &DecomposedBitsInfo, - c_num_vars: usize, - ) -> TransformZqtoRQSubclaim { - let seed: ::Seed = Default::default(); - let mut fs_rng = ChaCha12Rng::from_seed(seed); - Self::verifier_as_subprotocol(&mut fs_rng, proof, decomposed_bits_info, c_num_vars) - } - - /// Verify transformation from Zq to RQ 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: &TransformZqtoRQProof, - decomposed_bits_info: &DecomposedBitsInfo, - c_num_vars: usize, - ) -> TransformZqtoRQSubclaim { - //TODO sample randomness via Fiat-Shamir RNG - - // 1. rangecheck - let rangecheck_subclaim = BitDecomposition::verifier_as_subprotocol( - fs_rng, - &proof.rangecheck_msg, - decomposed_bits_info, - ); - - // 2. sumcheck - let poly_info = PolynomialInfo { - max_multiplicands: 3, - num_variables: decomposed_bits_info.num_vars, - }; - - let first_subclaim = MLSumcheck::verify_as_subprotocol( - fs_rng, - &poly_info, - F::zero(), - &proof.sumcheck_msgs[0], - ) - .expect("sumcheck protocol for transformation from Zq to RQ failed"); - - let second_subclaim = MLSumcheck::verify_as_subprotocol( - fs_rng, - &poly_info, - F::zero(), - &proof.sumcheck_msgs[1], - ) - .expect("sumcheck protocol for transformation from Zq to RQ failed"); - - let poly_info = PolynomialInfo { - max_multiplicands: 2, - num_variables: c_num_vars, - }; - let third_subclaim = MLSumcheck::verify_as_subprotocol( - fs_rng, - &poly_info, - proof.s_u, - &proof.sumcheck_msgs[2], - ) - .expect("sumcheck protocol for transformation from Zq to RQ failed"); - - TransformZqtoRQSubclaim { - rangecheck_subclaim, - sumcheck_points: vec![ - first_subclaim.point, - second_subclaim.point, - third_subclaim.point, - ], - sumcheck_expected_evaluations: vec![ - first_subclaim.expected_evaluations, - second_subclaim.expected_evaluations, - third_subclaim.expected_evaluations, - ], - } - } -} - -impl TransformZqtoRQSubclaim { - /// verify the sumcliam - /// * a stores the input and c stores the output of transformation from Zq to RQ - /// * k, r, s stores the introduced witness - /// * r_bits stores the decomposed bits for r - /// * u is the common random challenge from the verifier, used to instantiate the sumcheck. - #[inline] - #[allow(clippy::too_many_arguments)] - pub fn verify_subclaim( - &self, - q: usize, - a: Rc>, - c_dense: &DenseMultilinearExtension, - k: &DenseMultilinearExtension, - r: &[Rc>], - s: &DenseMultilinearExtension, - r_bits: &[&Vec>>], - u: &[F], - info: &TransformZqtoRQInstanceInfo, - ) -> bool { - assert_eq!(r_bits.len(), 1); - assert_eq!(r.len(), 1); - - // check 1: subclaim for rangecheck, r \in [Zq] - if !self - .rangecheck_subclaim - .verify_subclaim(r, r_bits, u, &info.decomposed_bits_info) - { - 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_points[0]); - if eval_identity_function(u, &self.sumcheck_points[0]) * eval_k * (F::one() - eval_k) - != self.sumcheck_expected_evaluations[0] - { - return false; - } - - // check 3: subclaim for sumcheck, i.e. eq(u, point) * ((r(point) + 1) * (1 - 2 * k(point)) - s(point)) = 0 - if eval_identity_function(u, &self.sumcheck_points[1]) - * ((r[0].evaluate(&self.sumcheck_points[1]) + F::one()) - * (F::one() - (F::one() + F::one()) * k.evaluate(&self.sumcheck_points[1])) - - s.evaluate(&self.sumcheck_points[1])) - != self.sumcheck_expected_evaluations[1] - { - return false; - } - - // check 4: subclaim for sumcheck, i.e. c(u, point) * t(point) = s(u) - let eval_c_u = c_dense.evaluate(&[&self.sumcheck_points[2], u].concat()); - let t_evaluations = (1..=info.n) - .map(|i| F::new(F::Value::as_from(i as f64))) - .collect(); - let t = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - info.n.ilog(2) as usize, - t_evaluations, - )); - if eval_c_u * t.evaluate(&self.sumcheck_points[2]) != self.sumcheck_expected_evaluations[2] - { - return false; - } - - // check 5: (2n/q) * a(u) = k(u) * n + r(u) - let n = F::new(F::Value::as_from(info.n as f64)); - let n_divied_by_q = F::new(F::Value::as_from((info.n / q) as f64)); - - (F::one() + F::one()) * n_divied_by_q * a.evaluate(u) - == n * k.evaluate(u) + r[0].evaluate(u) - } - - /// verify the sumcliam - /// * a stores the input and c stores the output of transformation from Zq to RQ - /// * k, r, s stores the introduced witness - /// * r_bits stores the decomposed bits for r - /// * u is the common random challenge from the verifier, used to instantiate the sumcheck. - #[inline] - #[allow(clippy::too_many_arguments)] - pub fn verify_subclaim_without_oracle( - &self, - q: usize, - a: Rc>, - c_sparse: &[Rc>], - k: &DenseMultilinearExtension, - r: &[Rc>], - s: &DenseMultilinearExtension, - r_bits: &[&Vec>>], - u: &[F], - info: &TransformZqtoRQInstanceInfo, - ) -> bool { - assert_eq!(r_bits.len(), 1); - assert_eq!(r.len(), 1); - - // check 1: subclaim for rangecheck, r \in [Zq] - if !self - .rangecheck_subclaim - .verify_subclaim(r, r_bits, u, &info.decomposed_bits_info) - { - 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_points[0]); - if eval_identity_function(u, &self.sumcheck_points[0]) * eval_k * (F::one() - eval_k) - != self.sumcheck_expected_evaluations[0] - { - return false; - } - - // check 3: subclaim for sumcheck, i.e. eq(u, point) * ((r(point) + 1) * (1 - 2 * k(point)) - s(point)) = 0 - if eval_identity_function(u, &self.sumcheck_points[1]) - * ((r[0].evaluate(&self.sumcheck_points[1]) + F::one()) - * (F::one() - (F::one() + F::one()) * k.evaluate(&self.sumcheck_points[1])) - - s.evaluate(&self.sumcheck_points[1])) - != self.sumcheck_expected_evaluations[1] - { - return false; - } - - // check 4: subclaim for sumcheck, i.e. c(u, point) * t(point) = s(u) - let eq_u = gen_identity_evaluations(u); - let eq_v = gen_identity_evaluations(&self.sumcheck_points[2]); - let mut eval_c_u = F::zero(); - c_sparse.iter().enumerate().for_each(|(x_idx, c)| { - assert_eq!(c.evaluations.len(), 1); - let (y_idx, c_val) = c.evaluations[0]; - eval_c_u += eq_u[x_idx] * eq_v[y_idx] * c_val; - }); - - let t_evaluations = (1..=info.n) - .map(|i| F::new(F::Value::as_from(i as f64))) - .collect(); - let t = Rc::new(DenseMultilinearExtension::from_evaluations_vec( - info.n.ilog(2) as usize, - t_evaluations, - )); - if eval_c_u * t.evaluate(&self.sumcheck_points[2]) != self.sumcheck_expected_evaluations[2] - { - return false; - } - - // check 5: (2n/q) * a(u) = k(u) * n + r(u) - let n = F::new(F::Value::as_from(info.n as f64)); - let n_divied_by_q = F::new(F::Value::as_from((info.n / q) as f64)); - - (F::one() + F::one()) * n_divied_by_q * a.evaluate(u) - == n * k.evaluate(u) + r[0].evaluate(u) - } -} diff --git a/zkp/src/sumcheck/mod.rs b/zkp/src/sumcheck/mod.rs index bc9da551..57049d6f 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 instantiate 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] @@ -40,31 +74,19 @@ impl MLSumcheck { /// /// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$ 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) - } - - /// 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 @@ -74,31 +96,18 @@ impl MLSumcheck { /// verify the proof using `polynomial_info` as the verifier key pub fn verify( + trans: &mut Transcript, polynomial_info: &PolynomialInfo, 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) - } - - /// 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, - 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, &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..f63b593e 100644 --- a/zkp/src/sumcheck/prover.rs +++ b/zkp/src/sumcheck/prover.rs @@ -6,16 +6,19 @@ use std::vec; use algebra::Field; use algebra::{DenseMultilinearExtension, ListOfProductsOfPolynomials, MultilinearExtension}; +use rayon::iter::{ + IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, +}; +use serde::{Deserialize, Serialize}; use super::verifier::VerifierMsg; use super::IPForMLSumcheck; -use std::rc::Rc; /// Prover Message -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct ProverMsg { /// evaluations on P(0), P(1), P(2), ... - pub(crate) evaluations: Rc>, + pub(crate) evaluations: Vec, } /// Prover State @@ -92,7 +95,7 @@ impl IPForMLSumcheck { let r = prover_state.randomness[i - 1]; prover_state .flattened_ml_extensions - .iter_mut() + .par_iter_mut() .for_each(|mulplicand| { *mulplicand = mulplicand.fix_variables(&[r]); }); @@ -111,42 +114,42 @@ impl IPForMLSumcheck { // the degree of univariate polynomial sent by prover at this round let degree = prover_state.max_multiplicands; - let zeros = (vec![F::zero(); degree + 1], vec![F::zero(); degree + 1]); - // In effect, this fold is essentially doing simply: + // In effect, this loop is essentially doing simply: // for b in 0..1 << (nv - i) // The goal is to evaluate degree + 1 points for each b, all of which has been fixed with the same (i-1) variables. // Note that the function proved in the sumcheck is a sum of products. - let fold_result = (0..1 << (nv - i)).fold(zeros, |(mut products_sum, mut product), b| { - // This loop is evaluating each product over the specific degree + 1 points. - for ((coefficient, products), linear_ops) in prover_state - .list_of_products - .iter() - .zip(prover_state.linear_ops.iter()) - { - product.fill(*coefficient); - // This loop is evaluating each MLE to update the product via performing the accumulated multiplication. - for (&jth_product, &(op_a, op_b)) in products.iter().zip(linear_ops.iter()) { - // (a, b) is a wrapped linear operation over original MLE - let table = &prover_state.flattened_ml_extensions[jth_product]; - let mut start = (table[b << 1] * op_a) + op_b; - let step = (table[(b << 1) + 1] * op_a) + op_b - start; - // Evaluate each point P(t) for t = 0..degree + 1 via the accumulated addition instead of the multiplication by t. - // [t|b] = [0|b] + t * ([1|b] - [0|b]) represented by little-endian - for p in product.iter_mut() { - *p *= start; - start += step; + let res = prover_state + .list_of_products + .par_iter() + .zip(prover_state.linear_ops.par_iter()) + .map(|((coefficient, products), linear_ops)| { + let mut products_sum = vec![F::zero(); degree + 1]; + for b in 0..1 << (nv - i) { + let mut product = vec![*coefficient; degree + 1]; + // This loop is evaluating each MLE to update the product via performing the accumulated multiplication. + for (&jth_product, &(op_a, op_b)) in products.iter().zip(linear_ops.iter()) { + // (a, b) is a wrapped linear operation over original MLE + let table = &prover_state.flattened_ml_extensions[jth_product]; + let op = table[b << 1] * op_a; + let mut start = op + op_b; + let step = (table[(b << 1) + 1] * op_a) - op; + // Evaluate each point P(t) for t = 0..degree + 1 via the accumulated addition instead of the multiplication by t. + // [t|b] = [0|b] + t * ([1|b] - [0|b]) represented by little-endian + for p in product.iter_mut() { + *p *= start; + start += step; + } + } + for t in 0..degree + 1 { + products_sum[t] += product[t]; } } - for t in 0..degree + 1 { - products_sum[t] += product[t]; - } - } - (products_sum, product) - }); - let products_sum = fold_result.0; - - ProverMsg { - evaluations: Rc::new(products_sum), - } + products_sum + }) + .reduce( + || vec![F::zero(); degree + 1], + |acc, a| acc.iter().zip(a.iter()).map(|(&acc, &a)| acc + a).collect(), + ); + ProverMsg { evaluations: res } } } diff --git a/zkp/src/sumcheck/verifier.rs b/zkp/src/sumcheck/verifier.rs index c1a3e1b8..1c08f15d 100644 --- a/zkp/src/sumcheck/verifier.rs +++ b/zkp/src/sumcheck/verifier.rs @@ -3,11 +3,9 @@ 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; use super::{prover::ProverMsg, IPForMLSumcheck}; @@ -25,12 +23,13 @@ pub struct VerifierState { max_multiplicands: usize, finished: bool, /// a list storing the univariate polynomial in evaluations sent by the prover at each round - polynomials_received: Vec>>, + polynomials_received: Vec>, /// a list storing the randomness sampled by the verifier at each round randomness: Vec, } /// 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 +55,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( - prover_msg: ProverMsg, + 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,11 +66,11 @@ 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 - .push(prover_msg.evaluations); + .push(prover_msg.evaluations.clone()); // Now, verifier should set `expected` to P(r). // This operation is also moved to `check_and_generate_subclaim`, @@ -121,9 +120,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..895ab118 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 std::rc::Rc; + +use algebra::{AbstractExtensionField, DenseMultilinearExtension, Field, SparsePolynomial}; +use itertools::izip; /// Generate MLE of the ideneity function eq(u,x) for x \in \{0, 1\}^dim pub fn gen_identity_evaluations(u: &[F]) -> DenseMultilinearExtension { @@ -28,12 +31,237 @@ pub fn eval_identity_function(u: &[F], v: &[F]) -> F { evaluation } +/// Evaluate M(u, y) for y \in \{0, 1\}^dim where M is a sparse matrix. +/// To be more specific, each row of the matrix consists of only constant entries. +/// M(u, y) = \sum_{x,y}\in {0,1} eq(u, x) * M(x, y) = \sum_{x, y}\in Non-zero Set eq(u, x) * M(x, y) +pub fn gen_sparse_at_u( + sparse_matrix: &[Rc>], + u: &[F], +) -> DenseMultilinearExtension { + assert_eq!(sparse_matrix.len(), 1 << u.len()); + let eq_at_u = gen_identity_evaluations(u); + + assert!(!sparse_matrix.is_empty()); + let num_vars = sparse_matrix[0].num_vars; + let mut evals = vec![F::zero(); 1 << num_vars]; + for (eq_u, row) in izip!(eq_at_u.iter(), sparse_matrix.iter()) { + for (idx, val) in row.iter() { + evals[*idx] += *eq_u * *val; + } + } + DenseMultilinearExtension::from_evaluations_vec(num_vars, evals) +} + +/// Evaluate M(u, y) for y \in \{0, 1\}^dim where M is a sparse matrix. +/// To be more specific, each row of the matrix consists of only constant entries. + +pub fn gen_sparse_at_u_to_ef>( + sparse_matrix: &[Rc>], + u: &[EF], +) -> DenseMultilinearExtension { + assert_eq!(sparse_matrix.len(), 1 << u.len()); + let eq_at_u = gen_identity_evaluations(u); + + assert!(!sparse_matrix.is_empty()); + let num_vars = sparse_matrix[0].num_vars; + let mut evals = vec![EF::zero(); 1 << num_vars]; + for (eq_u, row) in izip!(eq_at_u.iter(), sparse_matrix.iter()) { + for (idx, val) in row.iter() { + evals[*idx] += *eq_u * *val; + } + } + DenseMultilinearExtension::from_evaluations_vec(num_vars, evals) +} + +/// 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 +} + #[cfg(test)] mod test { use crate::utils::{eval_identity_function, gen_identity_evaluations}; use algebra::{ derive::{Field, Prime}, - FieldUniformSampler, MultilinearExtension, + FieldUniformSampler, }; use rand::thread_rng; use rand_distr::Distribution; diff --git a/zkp/tests/test_accumulator.rs b/zkp/tests/test_accumulator.rs index d1b586f5..67129f12 100644 --- a/zkp/tests/test_accumulator.rs +++ b/zkp/tests/test_accumulator.rs @@ -1,38 +1,38 @@ +use algebra::utils::Transcript; +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 std::rc::Rc; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand::thread_rng; +use sha2::Sha256; +use std::sync::Arc; use std::vec; -use zkp::{ - piop::{ - AccumulatorIOP, AccumulatorInstance, AccumulatorWitness, DecomposedBitsInfo, - NTTInstanceInfo, RlweCiphertext, RlweCiphertexts, - }, - utils::gen_identity_evaluations, +use zkp::piop::accumulator::{ + AccumulatorParams, AccumulatorProof, AccumulatorProver, AccumulatorVerifier, +}; +use zkp::piop::ntt::BitsOrder; +use zkp::piop::{ + AccumulatorInstance, AccumulatorWitness, BatchNTTInstanceInfo, BitDecompositionInstanceInfo, + ExternalProductInstance, RlweCiphertext, RlweCiphertextVector, }; -#[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 R: rand::Rng + rand::CryptoRng, { RlweCiphertext { - a: Rc::new(>::random(num_vars, rng)), - b: Rc::new(>::random(num_vars, rng)), + a: >::random(num_vars, rng), + b: >::random(num_vars, rng), } } @@ -40,16 +40,16 @@ fn random_rlwe_ciphertexts( bits_len: usize, rng: &mut R, num_vars: usize, -) -> RlweCiphertexts +) -> RlweCiphertextVector where R: rand::Rng + rand::CryptoRng, { - RlweCiphertexts { - a_bits: (0..bits_len) - .map(|_| Rc::new(>::random(num_vars, rng))) + RlweCiphertextVector { + a_vector: (0..bits_len) + .map(|_| >::random(num_vars, rng)) .collect(), - b_bits: (0..bits_len) - .map(|_| Rc::new(>::random(num_vars, rng))) + b_vector: (0..bits_len) + .map(|_| >::random(num_vars, rng)) .collect(), } } @@ -104,122 +104,77 @@ 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, - 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 - let bits_rlwe = RlweCiphertexts { - a_bits: input_rlwe +fn generate_rlwe_mult_rgsw_instance( + num_vars: usize, + input_rlwe: RlweCiphertext, + bits_rgsw_c_ntt: RlweCiphertextVector, + bits_rgsw_f_ntt: RlweCiphertextVector, + bits_info: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, +) -> ExternalProductInstance { + // 1. Decompose the input of RLWE ciphertex + let bits_rlwe = RlweCiphertextVector { + a_vector: input_rlwe .a - .get_decomposed_mles(basis_info.base_len, basis_info.bits_len), - b_bits: input_rlwe + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len) + .iter() + .map(|d| d.as_ref().clone()) + .collect(), + b_vector: 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) + .iter() + .map(|d| d.as_ref().clone()) + .collect(), }; - let (bits_rgsw_c_ntt, bits_rgsw_f_ntt) = input_rgsw_ntt; - // 5. Compute the ntt form of the decomposed bits - let bits_rlwe_ntt = RlweCiphertexts { - a_bits: bits_rlwe - .a_bits + // 2. Compute the ntt form of the decomposed bits + let bits_rlwe_ntt = RlweCiphertextVector { + a_vector: bits_rlwe + .a_vector .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), - )) + DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + ) }) .collect(), - b_bits: bits_rlwe - .b_bits + b_vector: bits_rlwe + .b_vector .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), - )) + 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()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rlwe_ntt.b_vector.len()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rgsw_c_ntt.a_vector.len()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rgsw_f_ntt.a_vector.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, - &bits_rgsw_c_ntt.a_bits, - &bits_rgsw_f_ntt.a_bits + &bits_rlwe_ntt.a_vector, + &bits_rlwe_ntt.b_vector, + &bits_rgsw_c_ntt.a_vector, + &bits_rgsw_f_ntt.a_vector ) { 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 << 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, - &bits_rgsw_c_ntt.b_bits, - &bits_rgsw_f_ntt.b_bits + &bits_rlwe_ntt.a_vector, + &bits_rlwe_ntt.b_vector, + &bits_rgsw_c_ntt.b_vector, + &bits_rgsw_f_ntt.b_vector ) { 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]); @@ -227,41 +182,170 @@ fn update_accumulator( } let output_rlwe_ntt = RlweCiphertext { - a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - output_g_ntt, - )), - b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - output_h_ntt, - )), + a: DenseMultilinearExtension::from_evaluations_vec(num_vars, output_g_ntt), + b: DenseMultilinearExtension::from_evaluations_vec(num_vars, output_h_ntt), }; - AccumulatorWitness { - accumulator_ntt: input_accumulator_ntt.clone(), - d: input_d.clone(), - d_ntt: input_d_ntt, - input_rlwe_ntt, + ExternalProductInstance::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: DenseMultilinearExtension, + bits_rgsw_c_ntt: RlweCiphertextVector, + bits_rgsw_f_ntt: RlweCiphertextVector, + bits_info: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, +) -> AccumulatorWitness { + // 1. Perform ntt transform on (x^{-a_u} - 1) + let d_ntt = 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: 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: 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: 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: 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: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, +) -> AccumulatorInstance { let mut rng = rand::thread_rng(); - let uniform = >::new(); + let mut updations = Vec::with_capacity(num_updations); + + let mut acc_ntt = RlweCiphertext:: { + a: DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &input.a.evaluations), + ), + b: DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &input.b.evaluations), + ), + }; + for _ in 0..num_updations { + let d = DenseMultilinearExtension::random(num_vars, &mut rng); + let bits_rgsw_c_ntt = + random_rlwe_ciphertexts(bits_info.clone().bits_len, &mut rng, num_vars); + let bits_rgsw_f_ntt = + random_rlwe_ciphertexts(bits_info.clone().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.clone(), + ntt_info.clone(), + ); + // perform ACC + ACC * d * RGSW + acc_ntt = RlweCiphertext { + a: acc_ntt.a + updation.clone().rlwe_mult_rgsw.output_rlwe_ntt.a, + b: acc_ntt.b + updation.clone().rlwe_mult_rgsw.output_rlwe_ntt.b, + }; + updations.push(updation); + } + + let output = RlweCiphertext { + a: DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_inverse_transform_normal_order(num_vars as u32, &acc_ntt.a.evaluations), + ), + b: 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_accumulator_snark() { // information used to decompose bits - let base_len: u32 = 2; + let base_len: usize = 5; 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 = BitDecompositionInstanceInfo { base, base_len, bits_len, @@ -279,80 +363,68 @@ fn test_trivial_accumulator() { ntt_table.push(power); 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_table = Arc::new(ntt_table); + let ntt_info = BatchNTTInstanceInfo { + 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 code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + // Parameters. + let mut params = AccumulatorParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let block_size = 2; + params.setup(&instance.info(), block_size, code_spec); + + // Prover + let acc_prover = AccumulatorProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + + let mut prover_trans = Transcript::::new(); + + let proof = acc_prover.prove( + &mut prover_trans, + ¶ms, + &instance, + block_size, + BitsOrder::Normal, + ); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let acc_verifier = AccumulatorVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::new(); + + let proof = AccumulatorProof::from_bytes(&proof_bytes).unwrap(); + + let res = acc_verifier.verify( + &mut verifier_trans, + ¶ms, + &instance.info(), + block_size, + BitsOrder::Normal, + &proof, + ); - 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, - &info, - )); + assert!(res); } diff --git a/zkp/tests/test_addition_in_zq.rs b/zkp/tests/test_addition_in_zq.rs index 137a20b0..af24bf3b 100644 --- a/zkp/tests/test_addition_in_zq.rs +++ b/zkp/tests/test_addition_in_zq.rs @@ -1,24 +1,32 @@ use algebra::{ derive::{DecomposableField, Field, Prime}, - Basis, DecomposableField, DenseMultilinearExtension, Field, FieldUniformSampler, + utils::Transcript, + BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension, Field, FieldUniformSampler, }; use num_traits::{One, Zero}; +use pcs::{ + multilinear::BrakedownPCS, + 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::{ + AdditionInZqParams, AdditionInZqProof, AdditionInZqProver, AdditionInZqVerifier, + }, + AdditionInZqIOP, AdditionInZqInstance, BitDecompositionInstanceInfo, +}; #[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 +39,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 +63,127 @@ 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 = BitDecompositionInstanceInfo:: { + 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 mut add_iop = AdditionInZqIOP::default(); + let mut prover_trans = Transcript::::new(); + add_iop.generate_randomness(&mut prover_trans, &info); + add_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = add_iop.prove(&mut prover_trans, &instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let mut add_iop = AdditionInZqIOP::default(); + let mut verifier_trans = Transcript::::new(); + add_iop.generate_randomness(&mut verifier_trans, &info); + add_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = add_iop.verify(&mut verifier_trans, &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 k = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + k.iter().map(|x: &Fq| FF::new(x.value())).collect(), + )); + + let bits_info = BitDecompositionInstanceInfo:: { + 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 mut add_iop = AdditionInZqIOP::default(); + let mut prover_trans = Transcript::::new(); + add_iop.generate_randomness(&mut prover_trans, &info); + add_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); - let u: Vec<_> = (0..num_vars).map(|_| sampler.sample(&mut rng)).collect(); + let kit = add_iop.prove(&mut prover_trans, &instance); + let evals = instance.evaluate(&kit.randomness); - 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 wrapper = kit.extract(); + let mut add_iop = AdditionInZqIOP::default(); + let mut verifier_trans = Transcript::::new(); + add_iop.generate_randomness(&mut verifier_trans, &info); + add_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = add_iop.verify(&mut verifier_trans, &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 +227,134 @@ 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 = BitDecompositionInstanceInfo:: { + 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 mut add_iop = AdditionInZqIOP::::default(); + + let mut prover_trans = Transcript::::new(); + add_iop.generate_randomness(&mut prover_trans, &info); + add_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = add_iop.prove(&mut prover_trans, &instance.to_ef()); + let evals = instance.to_ef().evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let mut add_iop = AdditionInZqIOP::default(); + let mut verifier_trans = Transcript::::new(); + add_iop.generate_randomness(&mut verifier_trans, &info); + add_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = add_iop.verify(&mut verifier_trans, &wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_addition_in_zq_snark() { + 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 bits_info = BitDecompositionInstanceInfo:: { + base, + base_len, + bits_len, + num_vars, + num_instances: 3, + }; + let instance = AdditionInZqInstance::::from_slice(&abc, &k, q, &bits_info); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + // Parameters. + let mut params = AdditionInZqParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + params.setup(&instance.info(), code_spec); + + // Prover. + let bd_prover = AdditionInZqProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let proof = bd_prover.prove(&mut prover_trans, ¶ms, &instance); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let bd_verifier = AdditionInZqVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + let proof = AdditionInZqProof::from_bytes(&proof_bytes).unwrap(); - let u: Vec<_> = (0..num_vars).map(|_| uniform_fp.sample(&mut rng)).collect(); + let res = bd_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); - 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)); + assert!(res); } diff --git a/zkp/tests/test_bit_decomposition.rs b/zkp/tests/test_bit_decomposition.rs index 3068a2e3..e4ad1184 100644 --- a/zkp/tests/test_bit_decomposition.rs +++ b/zkp/tests/test_bit_decomposition.rs @@ -1,21 +1,25 @@ -use algebra::Basis; use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - DenseMultilinearExtension, Field, FieldUniformSampler, + utils::Transcript, BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension, Field, + FieldUniformSampler, +}; +use itertools::izip; +use pcs::{ + multilinear::brakedown::BrakedownPCS, + utils::code::{ExpanderCode, ExpanderCodeSpec}, }; -// use protocol::bit_decomposition::{BitDecomposition, DecomposedBits}; use rand::prelude::*; use rand_distr::Distribution; +use sha2::Sha256; use std::rc::Rc; -use std::vec; -use zkp::piop::{BitDecomposition, DecomposedBits}; - -#[derive(Field, Prime, DecomposableField, FheField, NTT)] -#[modulus = 132120577] -pub struct Fp32(u32); +use zkp::piop::bit_decomposition::{ + BitDecompositionIOP, BitDecompositionInstance, BitDecompositionParams, BitDecompositionProof, + BitDecompositionProver, BitDecompositionVerifier, +}; -// 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 +32,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( @@ -50,28 +54,40 @@ 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); + let mut prover_key = BitDecompositionInstance::new(base, base_len, bits_len, num_vars); + prover_key.add_decomposed_bits_instance(&d, &d_bits); + let info = prover_key.info(); + + let mut prover_trans = Transcript::::new(); + let mut bd_iop = BitDecompositionIOP::default(); + + bd_iop.generate_randomness(&mut prover_trans, &info); + bd_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = bd_iop.prove(&mut prover_trans, &prover_key); + + let evals = prover_key.evaluate(&kit.randomness); + + let wrapper: zkp::sumcheck::ProofWrapper = kit.extract(); + let mut verifier_trans = Transcript::::new(); - let d_verifier = vec![d]; - let d_bits_verifier = vec![&d_bits]; + let mut bd_iop = BitDecompositionIOP::default(); - 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)); + bd_iop.generate_randomness(&mut verifier_trans, &info); + bd_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = bd_iop.verify(&mut verifier_trans, &wrapper, &evals, &prover_key.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 +124,42 @@ 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 = BitDecompositionInstance::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 mut prover_trans = Transcript::::new(); + let mut bd_iop = BitDecompositionIOP::default(); + + bd_iop.generate_randomness(&mut prover_trans, &info); + bd_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = bd_iop.prove(&mut prover_trans, &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 mut verifier_trans = Transcript::::new(); + + let mut bd_iop = BitDecompositionIOP::default(); + + bd_iop.generate_randomness(&mut verifier_trans, &info); + bd_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = bd_iop.verify(&mut verifier_trans, &wrapper, &evals, &instance.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 +172,39 @@ 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 = BitDecompositionInstance::new(base, base_len, bits_len, num_vars); + instance.add_decomposed_bits_instance(&d, &d_bits_prover); + + let info = instance.info(); + let mut prover_trans = Transcript::::new(); + let mut bd_iop = BitDecompositionIOP::default(); + + bd_iop.generate_randomness(&mut prover_trans, &info); + bd_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = bd_iop.prove(&mut prover_trans, &instance); + + let evals = instance.evaluate(&kit.randomness); - let decomposed_bits_info = decomposed_bits.info(); + let wrapper = kit.extract(); + let mut verifier_trans = Transcript::::new(); - 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 mut bd_iop = BitDecompositionIOP::default(); + + bd_iop.generate_randomness(&mut verifier_trans, &info); + bd_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = bd_iop.verify(&mut verifier_trans, &wrapper, &evals, &instance.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 +240,94 @@ 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 = BitDecompositionInstance::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 mut prover_trans = Transcript::::new(); + let mut bd_iop = BitDecompositionIOP::default(); + + bd_iop.generate_randomness(&mut prover_trans, &info); + bd_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = bd_iop.prove(&mut prover_trans, &instance); + + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let mut verifier_trans = Transcript::::new(); + + let mut bd_iop = BitDecompositionIOP::default(); + + bd_iop.generate_randomness(&mut verifier_trans, &info); + bd_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = bd_iop.verify(&mut verifier_trans, &wrapper, &evals, &instance.info()); + + assert!(check); +} + +#[test] +fn test_bit_decomposition_snark() { + 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 = BitDecompositionInstance::new(base, base_len, bits_len, num_vars); + instance.add_decomposed_bits_instance(&d, &d_bits_prover); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + // Parameters. + let mut params = BitDecompositionParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + params.setup(&instance.info(), code_spec); + + // Prover. + let bd_prover = BitDecompositionProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let proof = bd_prover.prove(&mut prover_trans, ¶ms, &instance); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let bd_verifier = BitDecompositionVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = BitDecompositionProof::from_bytes(&proof_bytes).unwrap(); + + let res = bd_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); - 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)); + assert!(res); } diff --git a/zkp/tests/test_external_product.rs b/zkp/tests/test_external_product.rs new file mode 100644 index 00000000..6ec14e4d --- /dev/null +++ b/zkp/tests/test_external_product.rs @@ -0,0 +1,297 @@ +use algebra::utils::Transcript; +use algebra::{transformation::AbstractNTT, NTTField, Polynomial}; +use algebra::{ + BabyBear, BabyBearExetension, Basis, DenseMultilinearExtension, Field, FieldUniformSampler, +}; +use itertools::izip; +use num_traits::One; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; +use rand_distr::Distribution; +use sha2::Sha256; +use std::sync::Arc; +use std::vec; +use zkp::piop::external_product::{ + ExternalProductParams, ExternalProductProof, ExternalProductProver, ExternalProductVerifier, +}; +use zkp::piop::ntt::BitsOrder; +use zkp::piop::{ + BatchNTTInstanceInfo, BitDecompositionInstanceInfo, ExternalProductInstance, RlweCiphertext, + RlweCiphertextVector, +}; + +// field type +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 { + 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) +} + +/// 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 +/// * bits_info: information used to decompose bits +/// * ntt_info: information used to perform NTT +fn generate_rlwe_mult_rgsw_instance( + num_vars: usize, + input_rlwe: RlweCiphertext, + input_rgsw: (RlweCiphertextVector, RlweCiphertextVector), + bits_info: BitDecompositionInstanceInfo, + ntt_info: BatchNTTInstanceInfo, +) -> ExternalProductInstance { + // 1. Decompose the input of RLWE ciphertex + let bits_rlwe = RlweCiphertextVector { + a_vector: input_rlwe + .a + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len) + .iter() + .map(|d| d.as_ref().clone()) + .collect::>(), + b_vector: input_rlwe + .b + .get_decomposed_mles(bits_info.base_len, bits_info.bits_len) + .iter() + .map(|d| d.as_ref().clone()) + .collect::>(), + }; + let (bits_rgsw_c_ntt, bits_rgsw_f_ntt) = input_rgsw; + + // 2. Compute the ntt form of the decomposed bits + let bits_rlwe_ntt = RlweCiphertextVector { + a_vector: bits_rlwe + .a_vector + .iter() + .map(|bit| { + DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + ) + }) + .collect(), + b_vector: bits_rlwe + .b_vector + .iter() + .map(|bit| { + DenseMultilinearExtension::from_evaluations_vec( + num_vars, + ntt_transform_normal_order(num_vars as u32, &bit.evaluations), + ) + }) + .collect(), + }; + + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rlwe_ntt.b_vector.len()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rgsw_c_ntt.a_vector.len()); + assert_eq!(bits_rlwe_ntt.a_vector.len(), bits_rgsw_f_ntt.a_vector.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_vector, + &bits_rlwe_ntt.b_vector, + &bits_rgsw_c_ntt.a_vector, + &bits_rgsw_f_ntt.a_vector + ) { + 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_vector, + &bits_rlwe_ntt.b_vector, + &bits_rgsw_c_ntt.b_vector, + &bits_rgsw_f_ntt.b_vector + ) { + 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: DenseMultilinearExtension::from_evaluations_vec(num_vars, output_g_ntt), + b: DenseMultilinearExtension::from_evaluations_vec(num_vars, output_h_ntt), + }; + + ExternalProductInstance::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, + ) +} + +#[test] +fn test_external_product_snark() { + 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 = BitDecompositionInstanceInfo { + 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 = Arc::new(ntt_table); + let ntt_info = BatchNTTInstanceInfo { + 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_instance( + 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_instance( + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + DenseMultilinearExtension::from_evaluations_slice(log_n, &points), + ); + } + + // generate the random RLWE ciphertext + let input_rlwe = RlweCiphertext { + a: DenseMultilinearExtension::from_evaluations_slice(log_n, &coeffs), + b: 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 code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + // Parameters. + let mut params = ExternalProductParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let block_size = 2; + params.setup(&instance.info(), block_size, code_spec); + + // Prover + let ep_prover = ExternalProductProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::new(); + + let proof = ep_prover.prove( + &mut prover_trans, + ¶ms, + &instance, + block_size, + BitsOrder::Normal, + ); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let ep_verifier = ExternalProductVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::new(); + + let proof = ExternalProductProof::from_bytes(&proof_bytes).unwrap(); + + let res = ep_verifier.verify( + &mut verifier_trans, + ¶ms, + &instance.info(), + block_size, + BitsOrder::Normal, + &proof, + ); + + assert!(res); +} diff --git a/zkp/tests/test_floor.rs b/zkp/tests/test_floor.rs new file mode 100644 index 00000000..c1bb5ea0 --- /dev/null +++ b/zkp/tests/test_floor.rs @@ -0,0 +1,353 @@ +use algebra::{ + utils::Transcript, BabyBear, BabyBearExetension, DenseMultilinearExtension, Field, + FieldUniformSampler, +}; +use pcs::{ + multilinear::BrakedownPCS, + utils::code::{ExpanderCode, ExpanderCodeSpec}, +}; +use rand_distr::Distribution; +use sha2::Sha256; +use std::rc::Rc; +use std::vec; +use zkp::piop::{ + floor::{FloorParams, FloorProof, FloorProver, FloorVerifier}, + BitDecompositionInstanceInfo, FloorIOP, FloorInstance, +}; + +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 = 1024; // message space +const LOG_FT: usize = FT.next_power_of_two().ilog2() as usize; +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)=>{ + vec![<$t>::new($elem);$n] + }; + ($t:ty; $($x:expr),+ $(,)?) => { + vec![$(<$t>::new($x)),+] + } +} + +#[inline] +fn decode(c: FF) -> u32 { + (c.value() as f64 * FT as f64 / FP as f64).floor() as u32 % FT +} + +#[test] +fn test_floor() { + let decode_4 = |c: FF| (c.value() as f64 * 4_f64 / FP as f64).floor() as u32 % FT; + assert_eq!(decode_4(FF::new(0)), 0); + assert_eq!(decode_4(FF::new(FP / 4)), 0); + assert_eq!(decode_4(FF::new(FP / 4 + 1)), 1); + assert_eq!(decode_4(FF::new(FP / 2)), 1); + assert_eq!(decode_4(FF::new(FP / 2 + 1)), 2); +} + +#[test] +fn test_floor_naive_iop() { + const FP: u32 = FF::MODULUS_VALUE; // ciphertext space + const FT: u32 = 4; // message space + const LOG_FT: usize = FT.next_power_of_two().ilog2() as usize; + const FK: u32 = (FP - 1) / FT; + const LOG_FK: u32 = FK.next_power_of_two().ilog2(); + const DELTA: u32 = (1 << LOG_FK) - FK; + + 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 = 2; + + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, FP/4, FP/4 + 1, FP/2 + 1), + )); + let output = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, 0, 1, 2), + )); + let output_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FT, + num_vars, + num_instances: 1, + }; + + let offset_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + let instance = >::new( + num_vars, + k, + delta, + input, + output, + &output_bits_info, + &offset_bits_info, + ); + + let info = instance.info(); + let mut floor_iop = FloorIOP::default(); + let mut prover_trans = Transcript::::new(); + floor_iop.generate_randomness(&mut prover_trans, &info); + floor_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = floor_iop.prove(&mut prover_trans, &instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + + let mut floor_iop = FloorIOP::default(); + let mut verifier_trans = Transcript::::new(); + floor_iop.generate_randomness(&mut verifier_trans, &info); + floor_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = floor_iop.verify(&mut verifier_trans, &wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_floor_random_iop() { + 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 = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FT, + num_vars, + num_instances: 1, + }; + + let offset_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + let instance = >::new( + num_vars, + k, + delta, + input, + output, + &output_bits_info, + &offset_bits_info, + ); + + let info = instance.info(); + let mut floor_iop = FloorIOP::default(); + let mut prover_trans = Transcript::::new(); + floor_iop.generate_randomness(&mut prover_trans, &info); + floor_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = floor_iop.prove(&mut prover_trans, &instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let mut floor_iop = FloorIOP::default(); + let mut verifier_trans = Transcript::::new(); + floor_iop.generate_randomness(&mut verifier_trans, &info); + floor_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = floor_iop.verify(&mut verifier_trans, &wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_floor_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 = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FT, + num_vars, + num_instances: 1, + }; + + let offset_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + let instance = >::new( + num_vars, + k, + delta, + input, + output, + &output_bits_info, + &offset_bits_info, + ); + + let instance_ef = instance.to_ef::(); + let info = instance_ef.info(); + let mut floor_iop = FloorIOP::default(); + let mut prover_trans = Transcript::::new(); + + floor_iop.generate_randomness(&mut prover_trans, &info); + floor_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = floor_iop.prove(&mut prover_trans, &instance_ef); + let evals = instance.evaluate_ext(&kit.randomness); + + let wrapper = kit.extract(); + + let mut floor_iop = FloorIOP::default(); + let mut verifier_trans = Transcript::::new(); + + floor_iop.generate_randomness(&mut verifier_trans, &info); + floor_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = floor_iop.verify(&mut verifier_trans, &wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_floor_snark() { + 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 = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FT, + num_vars, + num_instances: 1, + }; + + let offset_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FK as usize, + num_vars, + num_instances: 2, + }; + + let instance = >::new( + num_vars, + 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); + + // Parameters. + let mut params = FloorParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + params.setup(&instance.info(), code_spec); + + // Prover. + let floor_prover = FloorProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let proof = floor_prover.prove(&mut prover_trans, ¶ms, &instance); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let floor_verifier = FloorVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = FloorProof::from_bytes(&proof_bytes).unwrap(); + + let res = floor_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); + + assert!(res); +} diff --git a/zkp/tests/test_lift.rs b/zkp/tests/test_lift.rs new file mode 100644 index 00000000..e59ec429 --- /dev/null +++ b/zkp/tests/test_lift.rs @@ -0,0 +1,448 @@ +use algebra::{ + derive::{DecomposableField, Field, Prime}, + utils::Transcript, + BabyBear, BabyBearExetension, DenseMultilinearExtension, Field, FieldUniformSampler, + SparsePolynomial, +}; +use num_traits::{One, Zero}; +use pcs::{ + multilinear::BrakedownPCS, + utils::code::{ExpanderCode, ExpanderCodeSpec}, +}; +use rand_distr::Distribution; +use sha2::Sha256; +use std::rc::Rc; +use std::vec; +use zkp::piop::{ + lift::{LiftParams, LiftProof, LiftProver, LiftVerifier}, + BitDecompositionInstanceInfo, LiftIOP, LiftInstance, +}; + +#[derive(Field, DecomposableField)] +#[modulus = 8] +pub struct Fq(u32); + +// type FF = BabyBear; // field type +// type EF = BabyBearExetension; +#[derive(Field, Prime, DecomposableField)] +#[modulus = 132120577] +pub struct Fp32(u32); + +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)=>{ + 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 q = FF::new(8); + let dim_rlwe = FF::new(8); + let base_len: usize = 1; + let base: FF = FF::new(2); + let num_vars = 3; + let bits_len: usize = 3; + + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, 3, 5, 7, 0, 3, 5, 7), + )); + + let sparse_outputs = vec![ + Rc::new(SparsePolynomial::from_evaluations_vec( + num_vars, + vec![(0, FF::one())], + )), + Rc::new(SparsePolynomial::from_evaluations_vec( + num_vars, + vec![(6, FF::one())], + )), + Rc::new(SparsePolynomial::from_evaluations_vec( + num_vars, + vec![(2, -FF::one())], + )), + Rc::new(SparsePolynomial::from_evaluations_vec( + num_vars, + vec![(6, -FF::one())], + )), + Rc::new(SparsePolynomial::from_evaluations_vec( + num_vars, + vec![(0, FF::one())], + )), + Rc::new(SparsePolynomial::from_evaluations_vec( + num_vars, + vec![(6, FF::one())], + )), + Rc::new(SparsePolynomial::from_evaluations_vec( + num_vars, + vec![(2, -FF::one())], + )), + Rc::new(SparsePolynomial::from_evaluations_vec( + num_vars, + vec![(6, -FF::one())], + )), + ]; + + let outputs = vec![ + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 1, 0, 0, 0, 0, 0, 0, 0), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, 0, 0, 0, 0, 0, 1, 0), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, 0, FF::MODULUS_VALUE-1, 0, 0, 0, 0, 0), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, 0, 0, 0, 0, 0, FF::MODULUS_VALUE-1, 0), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 1, 0, 0, 0, 0, 0, 0, 0), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, 0, 0, 0, 0, 0, 1, 0), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, 0, FF::MODULUS_VALUE-1, 0, 0, 0, 0, 0), + )), + Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + field_vec!(FF; 0, 0, 0, 0, 0, 0, FF::MODULUS_VALUE-1, 0), + )), + ]; + + let bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len, + num_vars, + num_instances: 0, + }; + let instance = LiftInstance::new( + num_vars, + q, + dim_rlwe, + &input, + &outputs, + &sparse_outputs, + &bits_info, + ); + + let info = instance.info(); + let mut lift_iop = LiftIOP::default(); + let mut prover_trans = Transcript::::new(); + + lift_iop.generate_randomness(&mut prover_trans, &info); + lift_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = lift_iop.prove(&mut prover_trans, &instance); + let evals_at_r = instance.evaluate(&kit.randomness); + let evals_at_u = instance.evaluate(&kit.u); + + let wrapper = kit.extract(); + + let mut lift_iop = LiftIOP::default(); + let mut verifier_trans = Transcript::::new(); + + lift_iop.generate_randomness(&mut verifier_trans, &info); + lift_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = lift_iop.verify( + &mut verifier_trans, + &wrapper, + &evals_at_r, + &evals_at_u, + &info, + ); + + assert!(check); +} + +fn transform( + num_vars: usize, + input: FF, + q: FF, + dim_rlwe: FF, +) -> (DenseMultilinearExtension, SparsePolynomial) { + let factor = (FF::one() + FF::one()) * dim_rlwe / q; + let mapped_input = factor * input; + let mut output = vec![FF::zero(); 1 << num_vars]; + let mut sparse_output = SparsePolynomial::new(num_vars); + if mapped_input < dim_rlwe { + let idx = mapped_input.value() as usize; + output[idx] = FF::one(); + sparse_output.add_eval(idx, FF::one()); + } else { + let idx = (mapped_input - dim_rlwe).value() as usize; + output[idx] = -FF::one(); + sparse_output.add_eval(idx, -FF::one()); + } + ( + DenseMultilinearExtension::from_evaluations_vec(num_vars, output), + sparse_output, + ) +} + +#[test] +fn test_random_zq_to_rq() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + let base_len = 1; + let base: FF = FF::new(1 << base_len); + let num_vars = 4; + let q = FF::new(Fq::MODULUS_VALUE); + let dim_rlwe = FF::new(1 << num_vars); + + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..1 << num_vars) + .map(|_| FF::new(uniform.sample(&mut rng).value())) + .collect(), + )); + let mut outputs = Vec::with_capacity(1 << num_vars); + let mut sparse_outputs = Vec::with_capacity(1 << num_vars); + for x in input.iter() { + let (output, sparse_output) = transform(num_vars, *x, q, dim_rlwe); + outputs.push(Rc::new(output)); + sparse_outputs.push(Rc::new(sparse_output)); + } + + let bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: num_vars, + num_vars, + num_instances: 0, + }; + + let instance = LiftInstance::new( + num_vars, + q, + dim_rlwe, + &input, + &outputs, + &sparse_outputs, + &bits_info, + ); + + let info = instance.info(); + + let mut lift_iop = LiftIOP::default(); + let mut prover_trans = Transcript::::new(); + + lift_iop.generate_randomness(&mut prover_trans, &info); + lift_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = lift_iop.prove(&mut prover_trans, &instance); + let evals_at_r = instance.evaluate(&kit.randomness); + let evals_at_u = instance.evaluate(&kit.u); + + let wrapper = kit.extract(); + + let mut lift_iop = LiftIOP::default(); + let mut verifier_trans = Transcript::::new(); + + lift_iop.generate_randomness(&mut verifier_trans, &info); + lift_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = lift_iop.verify( + &mut verifier_trans, + &wrapper, + &evals_at_r, + &evals_at_u, + &info, + ); + + assert!(check); +} + +#[test] +fn test_random_zq_to_rq_extension_field() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + let base_len = 1; + let base: FF = FF::new(1 << base_len); + let num_vars = 4; + let q = FF::new(Fq::MODULUS_VALUE); + let dim_rlwe = FF::new(1 << num_vars); + + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..1 << num_vars) + .map(|_| FF::new(uniform.sample(&mut rng).value())) + .collect(), + )); + let mut outputs = Vec::with_capacity(1 << num_vars); + let mut sparse_outputs = Vec::with_capacity(1 << num_vars); + for x in input.iter() { + let (output, sparse_output) = transform(num_vars, *x, q, dim_rlwe); + outputs.push(Rc::new(output)); + sparse_outputs.push(Rc::new(sparse_output)); + } + + let bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: num_vars, + num_vars, + num_instances: 0, + }; + + let instance = LiftInstance::new( + num_vars, + q, + dim_rlwe, + &input, + &outputs, + &sparse_outputs, + &bits_info, + ); + + let instance_ef = instance.to_ef::(); + + let info = instance_ef.info(); + + let mut lift_iop = LiftIOP::default(); + let mut prover_trans = Transcript::::new(); + + lift_iop.generate_randomness(&mut prover_trans, &info); + lift_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = lift_iop.prove(&mut prover_trans, &instance_ef); + + let evals_at_r = instance.evaluate_ext(&kit.randomness); + let evals_at_u = instance.evaluate_ext(&kit.u); + + let mut lift_iop = LiftIOP::default(); + let mut verifier_trans = Transcript::::new(); + + lift_iop.generate_randomness(&mut verifier_trans, &info); + lift_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let wrapper = kit.extract(); + let (check, _) = lift_iop.verify( + &mut verifier_trans, + &wrapper, + &evals_at_r, + &evals_at_u, + &info, + ); + + assert!(check); +} + +#[test] +fn test_lift_snark() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + let base_len = 1; + let base: FF = FF::new(1 << base_len); + let num_vars = 4; + let q = FF::new(Fq::MODULUS_VALUE); + let dim_rlwe = FF::new(1 << num_vars); + + let input = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_vars, + (0..1 << num_vars) + .map(|_| FF::new(uniform.sample(&mut rng).value())) + .collect(), + )); + let mut outputs = Vec::with_capacity(1 << num_vars); + let mut sparse_outputs = Vec::with_capacity(1 << num_vars); + for x in input.iter() { + let (output, sparse_output) = transform(num_vars, *x, q, dim_rlwe); + outputs.push(Rc::new(output)); + sparse_outputs.push(Rc::new(sparse_output)); + } + + let bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: num_vars, + num_vars, + num_instances: 0, + }; + + let instance = LiftInstance::new( + num_vars, + q, + dim_rlwe, + &input, + &outputs, + &sparse_outputs, + &bits_info, + ); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + // Parameters. + let mut params = LiftParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + params.setup(&instance.info(), code_spec); + + // Prover. + let floor_prover = LiftProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let proof = floor_prover.prove(&mut prover_trans, ¶ms, &instance); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let floor_verifier = LiftVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = LiftProof::from_bytes(&proof_bytes).unwrap(); + + let res = floor_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); + + assert!(res); +} diff --git a/zkp/tests/test_lookup.rs b/zkp/tests/test_lookup.rs new file mode 100644 index 00000000..7aca983e --- /dev/null +++ b/zkp/tests/test_lookup.rs @@ -0,0 +1,232 @@ +use algebra::{ + derive::{DecomposableField, Field, Prime}, + utils::Transcript, + BabyBear, BabyBearExetension, DenseMultilinearExtension, Field, +}; +use num_traits::Zero; +use pcs::{ + multilinear::BrakedownPCS, + utils::code::{ExpanderCode, ExpanderCodeSpec}, +}; +use rand::prelude::*; +use sha2::Sha256; +use std::rc::Rc; +use std::vec; +use zkp::piop::{ + lookup::{LookupParams, LookupProof, LookupProver, LookupVerifier}, + LookupIOP, LookupInstance, +}; + +type FF = BabyBear; +type EF = BabyBearExetension; +type Hash = Sha256; +const BASE_FIELD_BITS: usize = 31; + +#[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 = [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, + )); + + // construct instance + + let mut instance = LookupInstance::::from_slice( + &f_vec.iter().map(|d| d.as_ref().clone()).collect::>(), + t.as_ref().clone(), + block_size, + ); + let info = instance.info(); + + let mut prover_trans = Transcript::::new(); + let mut lookup = LookupIOP::default(); + + lookup.prover_generate_first_randomness(&mut prover_trans, &mut instance); + lookup.generate_second_randomness(&mut prover_trans, &info); + lookup.generate_randomness_for_eq_function(&mut prover_trans, &info); + let kit = lookup.prove(&mut prover_trans, &instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let mut verifier_trans = Transcript::::new(); + + let mut lookup = LookupIOP::default(); + + lookup.verifier_generate_first_randomness(&mut verifier_trans); + lookup.generate_second_randomness(&mut verifier_trans, &info); + lookup.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = lookup.verify(&mut verifier_trans, &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 mut instance = LookupInstance::from_slice( + &f_vec.iter().map(|d| d.as_ref().clone()).collect::>(), + t.as_ref().clone(), + block_size, + ); + let info = instance.info(); + let mut lookup = LookupIOP::default(); + + let mut prover_trans = Transcript::::new(); + lookup.prover_generate_first_randomness(&mut prover_trans, &mut instance); + lookup.generate_second_randomness(&mut prover_trans, &info); + lookup.generate_randomness_for_eq_function(&mut prover_trans, &info); + let kit = lookup.prove(&mut prover_trans, &instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let mut verifier_trans = Transcript::::new(); + + let mut lookup = LookupIOP::default(); + lookup.verifier_generate_first_randomness(&mut verifier_trans); + lookup.generate_second_randomness(&mut verifier_trans, &info); + lookup.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = lookup.verify(&mut verifier_trans, &wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_lookup_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(); + 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 = DenseMultilinearExtension::from_evaluations_vec(num_vars, t_evaluations); + + let instance = LookupInstance::from_slice(&f_vec, t, block_size); + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + // Parameters. + let mut params = LookupParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + params.setup(&instance.info(), code_spec); + + // Prover. + let lookup_prover = LookupProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let proof = lookup_prover.prove(&mut prover_trans, ¶ms, &instance); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let lookup_verifier = LookupVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = LookupProof::from_bytes(&proof_bytes).unwrap(); + let res = lookup_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); + + assert!(res); +} diff --git a/zkp/tests/test_ntt.rs b/zkp/tests/test_ntt.rs index 528808d1..7ac202f2 100644 --- a/zkp/tests/test_ntt.rs +++ b/zkp/tests/test_ntt.rs @@ -1,26 +1,23 @@ -use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - DenseMultilinearExtension, Field, FieldUniformSampler, NTTPolynomial, -}; +use algebra::utils::Transcript; use algebra::{transformation::AbstractNTT, NTTField, Polynomial}; +use algebra::{BabyBear, BabyBearExetension, DenseMultilinearExtension, Field, NTTPolynomial}; use num_traits::{One, Zero}; +use pcs::multilinear::BrakedownPCS; +use pcs::utils::code::{ExpanderCode, ExpanderCodeSpec}; use rand::prelude::*; -use rand_distr::Distribution; +use sha2::Sha256; use std::rc::Rc; +use std::sync::Arc; use std::vec; use zkp::piop::ntt::ntt_bare::init_fourier_table; +use zkp::piop::ntt::{BatchNTTInstance, BitsOrder, NTTParams, NTTProof, NTTProver, NTTVerifier}; 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 { @@ -86,6 +83,14 @@ fn ntt_transform_normal_order(log_n: u32, coeff: &[F]) -> V sort_array_with_reversed_bits(&ntt_form, log_n) } +/// Invoke the existing api to perform ntt transform. +/// The input is in normal order and the output is in the bit-reversed order +fn ntt_transform_reverse_order(log_n: u32, coeff: &[F]) -> Vec { + assert_eq!(coeff.len(), (1 << log_n) as usize); + let poly = >::from_slice(coeff); + F::get_ntt_table(log_n).unwrap().transform(&poly).data() +} + /// Invoke the existing api to perform ntt inverse transform and convert the bit-reversed order to normal order /// In other words, the orders of input and output are both normal order. fn ntt_inverse_transform_normal_order(log_n: u32, points: &[F]) -> Vec { @@ -130,6 +135,36 @@ fn naive_ntt_transform_normal_order(log_n: u32, coeff: &[FF]) -> Vec { ntt_form } +fn generate_single_instance( + instances: &mut BatchNTTInstance, + log_n: usize, + rng: &mut R, + bits_order: BitsOrder, +) { + let coeff = PolyFF::random(1 << log_n, rng).data(); + let point = match bits_order { + BitsOrder::Normal => 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(), + )), + BitsOrder::Reverse => Rc::new(DenseMultilinearExtension::from_evaluations_vec( + log_n, + ntt_transform_reverse_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_instance(&coeff, &point); +} + #[test] fn test_reverse_bits() { assert_eq!(2, reverse_bits(4, 4)); @@ -179,10 +214,9 @@ fn test_ntt_bare_without_delegation() { ntt_table.push(power); power *= root; } - let ntt_table = Rc::new(ntt_table); + let ntt_table = Arc::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, @@ -194,19 +228,37 @@ 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); - - // 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 mut ntt_iop = NTTBareIOP::default(); + let mut prover_trans = Transcript::::new(); + ntt_iop.generate_randomness(&mut prover_trans, &ntt_instance_info); + let kit = ntt_iop.prove(&mut prover_trans, &ntt_instance, BitsOrder::Normal); + 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(); + let mut ntt_iop = NTTBareIOP::default(); + let mut verifier_trans = Transcript::::new(); + ntt_iop.generate_randomness(&mut verifier_trans, &ntt_instance_info); + + let check = ntt_iop.verify( + &mut verifier_trans, + &mut wrapper, + evals_at_r, + evals_at_u, + &ntt_instance_info, + BitsOrder::Normal, + ); + 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); @@ -216,10 +268,9 @@ fn test_ntt_with_delegation() { ntt_table.push(power); power *= root; } - let ntt_table = Rc::new(ntt_table); + let ntt_table = Arc::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 +281,37 @@ 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); - - assert!(subclaim.verify_subcliam(&points, &coeff, &u, &ntt_instance_info)); + let instance_ef = ntt_instance.to_ef::(); + let ntt_instance_info = instance_ef.info(); + + let mut ntt_iop = NTTBareIOP::::default(); + let mut prover_trans = Transcript::::new(); + ntt_iop.generate_randomness(&mut prover_trans, &ntt_instance_info); + + let kit = ntt_iop.prove(&mut prover_trans, &instance_ef, BitsOrder::Normal); + 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 mut ntt_iop = NTTBareIOP::::default(); + let mut verifier_trans = Transcript::::default(); + ntt_iop.generate_randomness(&mut verifier_trans, &ntt_instance_info); + let check = ntt_iop.verify( + &mut verifier_trans, + &mut wrapper, + evals_at_r, + evals_at_u, + &ntt_instance_info, + BitsOrder::Normal, + ); + 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(); @@ -250,44 +320,173 @@ fn test_ntt_combined_with_delegation() { ntt_table.push(power); power *= root; } - let ntt_table = Rc::new(ntt_table); + let ntt_table = Arc::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 mut ntt_iop = NTTIOP::default(); + let mut prover_trans = Transcript::::new(); + ntt_iop.generate_randomness(&mut prover_trans, &ntt_instance_info.to_clean()); + ntt_iop.generate_randomness_for_eq_function(&mut prover_trans, &ntt_instance_info.to_clean()); + + let (kit, recursive_proof) = ntt_iop.prove(&mut prover_trans, &ntt_instance, BitsOrder::Normal); + 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 mut ntt_iop = NTTIOP::default(); + let mut verifier_trans = Transcript::::new(); + ntt_iop.generate_randomness(&mut verifier_trans, &ntt_instance_info.to_clean()); + ntt_iop.generate_randomness_for_eq_function(&mut verifier_trans, &ntt_instance_info.to_clean()); + let (check, _) = ntt_iop.verify( + &mut verifier_trans, + &mut wrapper, + evals_at_r, + evals_at_u, + &ntt_instance_info, + &recursive_proof, + BitsOrder::Normal, ); + 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 = Arc::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 mut ntt_iop = NTTIOP::default(); + let mut prover_trans = Transcript::::new(); + ntt_iop.generate_randomness(&mut prover_trans, &ntt_instance_info.to_clean()); + ntt_iop.generate_randomness_for_eq_function(&mut prover_trans, &ntt_instance_info.to_clean()); + + let (kit, recursive_proof) = ntt_iop.prove(&mut prover_trans, &instance_ef, BitsOrder::Normal); + + 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 mut ntt_iop = NTTIOP::default(); + let mut verifier_trans = Transcript::::new(); + ntt_iop.generate_randomness(&mut verifier_trans, &ntt_instance_info.to_clean()); + ntt_iop.generate_randomness_for_eq_function(&mut verifier_trans, &ntt_instance_info.to_clean()); + let (check, _) = ntt_iop.verify( + &mut verifier_trans, + &mut wrapper, + evals_at_r, + evals_at_u, + &ntt_instance_info, + &recursive_proof, + BitsOrder::Normal, + ); + assert!(check); +} + +fn ntt_snark(bits_order: BitsOrder) { + 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(); + + let mut power = FF::one(); + for _ in 0..m { + ntt_table.push(power); + power *= root; + } + let ntt_table = Arc::new(ntt_table); - assert!(subclaim.verify_subcliam(&points, &coeff, &u, &ntt_instance_info)); + 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, bits_order); + } + + let code_spec = ExpanderCodeSpec::new(0.1195, 0.0248, 1.9, BASE_FIELD_BITS, 10); + + // Parameters. + let mut params = NTTParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + params.setup(&ntt_instances.info(), code_spec); + + // Prover. + let ntt_prover = NTTProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + + let mut prover_trans = Transcript::::default(); + + let proof = ntt_prover.prove(&mut prover_trans, ¶ms, &ntt_instances, bits_order); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let ntt_verifier = NTTVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + + let mut verifier_trans = Transcript::::default(); + + let proof = NTTProof::from_bytes(&proof_bytes).unwrap(); + + let res = ntt_verifier.verify( + &mut verifier_trans, + ¶ms, + &ntt_instances.info(), + &proof, + bits_order, + ); + + assert!(res); +} + +#[test] +fn test_ntt_snarks() { + ntt_snark(BitsOrder::Normal); + ntt_snark(BitsOrder::Reverse); } diff --git a/zkp/tests/test_rlwe_mult_rgsw.rs b/zkp/tests/test_rlwe_mult_rgsw.rs deleted file mode 100644 index 6e75f3ce..00000000 --- a/zkp/tests/test_rlwe_mult_rgsw.rs +++ /dev/null @@ -1,293 +0,0 @@ -use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - Basis, DenseMultilinearExtension, Field, FieldUniformSampler, -}; -use algebra::{transformation::AbstractNTT, NTTField, NTTPolynomial, Polynomial}; -use itertools::izip; -use num_traits::One; -use rand_distr::Distribution; -use std::rc::Rc; -use std::vec; -use zkp::piop::{ - DecomposedBitsInfo, NTTInstanceInfo, RlweCiphertext, RlweCiphertexts, RlweMultRgswIOP, - RlweMultRgswInstance, -}; - -#[derive(Field, Prime, DecomposableField, FheField, NTT)] -#[modulus = 132120577] -pub struct Fp32(u32); - -// field type -type FF = Fp32; - -/// 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() -} - -/// 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 -/// * ntt_info: information used to perform NTT -/// * randomness_ntt: randomness used to generate a single randomized NTT instance -fn gen_rlwe_mult_rgsw_instance( - input_rlwe: RlweCiphertext, - input_rgsw: (RlweCiphertexts, RlweCiphertexts), - basis_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), - b_bits: input_rlwe - .b - .get_decomposed_mles(basis_info.base_len, basis_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( - ntt_info.log_n, - ntt_transform_normal_order(ntt_info.log_n as u32, &bit.evaluations), - )) - }) - .collect(), - b_bits: bits_rlwe - .b_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), - )) - }) - .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 << ntt_info.log_n]; - 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 << ntt_info.log_n]; - 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]); - }); - } - - // 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_ntt = RlweCiphertext { - a: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - output_g_ntt, - )), - b: Rc::new(DenseMultilinearExtension::from_evaluations_vec( - ntt_info.log_n, - output_h_ntt, - )), - }; - - RlweMultRgswInstance::from( - basis_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, - ) -} - -#[test] -fn test_trivial_rlwe_mult_rgsw() { - let mut rng = rand::thread_rng(); - let uniform = >::new(); - - // information used to decompose bits - let base_len: u32 = 2; - let base: FF = FF::new(1 << base_len); - let bits_len: u32 = >::new(base_len).decompose_len() as u32; - let num_vars = 10; - let basis_info = DecomposedBitsInfo { - base, - base_len, - bits_len, - num_vars, - num_instances: 2, - }; - - // 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 { log_n, ntt_table }; - - // 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 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 as usize); - 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, - )), - }; - - let num_ntt_instance = (basis_info.bits_len << 1) + 2; - let randomness_ntt = (0..num_ntt_instance) - .map(|_| uniform.sample(&mut rng)) - .collect::>(); - - // generate all the witness required - let instance = gen_rlwe_mult_rgsw_instance( - input_rlwe, - (bits_rgsw_c_ntt, bits_rgsw_f_ntt), - &basis_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 - )); -} diff --git a/zkp/tests/test_round.rs b/zkp/tests/test_round.rs index 4ec8258e..49d8d99a 100644 --- a/zkp/tests/test_round.rs +++ b/zkp/tests/test_round.rs @@ -1,20 +1,30 @@ use algebra::{ - derive::{DecomposableField, FheField, Field, Prime, NTT}, - DecomposableField, DenseMultilinearExtension, Field, FieldUniformSampler, + utils::Transcript, BabyBear, BabyBearExetension, DenseMultilinearExtension, Field, + FieldUniformSampler, +}; +use pcs::{ + multilinear::BrakedownPCS, + utils::code::{ExpanderCode, ExpanderCodeSpec}, }; use rand_distr::Distribution; +use sha2::Sha256; use std::rc::Rc; use std::vec; -use zkp::piop::{DecomposedBitsInfo, RoundIOP, RoundInstance}; - -#[derive(Field, Prime, DecomposableField, FheField, NTT)] -#[modulus = 132120577] -pub struct Fp32(u32); +use zkp::piop::{ + round::{RoundParams, RoundProof, RoundProver, RoundVerifier}, + BitDecompositionInstanceInfo, RoundIOP, RoundInstance, +}; -type FF = Fp32; // field type -const FP: u32 = 132120577; // ciphertext space -const FT: u32 = 4; // message space -const FK: u32 = (FP - 1) / FT; +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 = 1024; // message space +const LOG_FT: usize = FT.next_power_of_two().ilog2() as usize; +const FK: u32 = (FP - 1) / (2 * FT); +const LOG_2FK: u32 = (2 * FK).next_power_of_two().ilog2(); +const DELTA: u32 = (1 << LOG_2FK) - (2 * FK); macro_rules! field_vec { ($t:ty; $elem:expr; $n:expr)=>{ @@ -27,26 +37,34 @@ macro_rules! field_vec { #[inline] fn decode(c: FF) -> u32 { - (c.value() as f64 * FT as f64 / FP as f64).floor() as u32 % FT + (c.value() as f64 * FT as f64 / FP as f64).round() as u32 % FT } #[test] fn test_round() { - assert_eq!(decode(FF::new(0)), 0); - assert_eq!(decode(FF::new(FP / 4)), 0); - assert_eq!(decode(FF::new(FP / 4 + 1)), 1); - assert_eq!(decode(FF::new(FP / 2)), 1); - assert_eq!(decode(FF::new(FP / 2 + 1)), 2); + let decode_4 = |c: FF| (c.value() as f64 * 4_f64 / FP as f64).round() as u32 % FT; + assert_eq!(decode_4(FF::new(0)), 0); + assert_eq!(decode_4(FF::new(FP / 4)), 1); + assert_eq!(decode_4(FF::new(FP / 4 + 1)), 1); + assert_eq!(decode_4(FF::new(FP / 2)), 2); + assert_eq!(decode_4(FF::new(FP / 2 + 1)), 2); } #[test] fn test_round_naive_iop() { - // k = (132120577 - 1) / FT = 33030144 = 2^25 - 2^19 + const FP: u32 = FF::MODULUS_VALUE; // ciphertext space + const FT: u32 = 4; // message space + const LOG_FT: usize = FT.next_power_of_two().ilog2() as usize; + const FK: u32 = (FP - 1) / (2 * FT); + const LOG_2FK: u32 = (2 * FK).next_power_of_two().ilog2(); + const DELTA: u32 = (1 << LOG_2FK) - (2 * FK); + + let q = FF::new(FT); let k = FF::new(FK); - let k_bits_len: u32 = 25; - let delta: FF = FF::new(1 << 19); + let k_bits_len = LOG_2FK 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; @@ -56,17 +74,17 @@ fn test_round_naive_iop() { )); let output = Rc::new(DenseMultilinearExtension::from_evaluations_vec( num_vars, - field_vec!(FF; 0, 0, 1, 2), + field_vec!(FF; 0, 1, 1, 2), )); - let output_bits_info = DecomposedBitsInfo { + let output_bits_info = BitDecompositionInstanceInfo { base, base_len, - bits_len: 2, + bits_len: LOG_FT, num_vars, - num_instances: 1, + num_instances: 2, }; - let offset_bits_info = DecomposedBitsInfo { + let offset_bits_info = BitDecompositionInstanceInfo { base, base_len, bits_len: k_bits_len, @@ -75,6 +93,8 @@ fn test_round_naive_iop() { }; let instance = >::new( + num_vars, + q, k, delta, input, @@ -84,28 +104,25 @@ fn test_round_naive_iop() { ); let info = instance.info(); + let mut round_iop = RoundIOP::default(); + let mut prover_trans = Transcript::::new(); - 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 - )) + round_iop.generate_randomness(&mut prover_trans, &info); + round_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = round_iop.prove(&mut prover_trans, &instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let mut round_iop = RoundIOP::default(); + let mut verifier_trans = Transcript::::new(); + + round_iop.generate_randomness(&mut verifier_trans, &info); + round_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = round_iop.verify(&mut verifier_trans, &wrapper, &evals, &info); + + assert!(check); } #[test] @@ -113,12 +130,12 @@ fn test_round_random_iop() { let mut rng = rand::thread_rng(); let uniform = >::new(); - // k = (132120577 - 1) / FT = 33030144 = 2^25 - 2^19 + let q = FF::new(FT); let k = FF::new(FK); - let k_bits_len: u32 = 25; - let delta: FF = FF::new(1 << 19); + let k_bits_len = LOG_2FK 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; @@ -132,15 +149,15 @@ fn test_round_random_iop() { num_vars, input.iter().map(|x| FF::new(decode(*x))).collect(), )); - let output_bits_info = DecomposedBitsInfo { + let output_bits_info = BitDecompositionInstanceInfo { base, base_len, - bits_len: 2, + bits_len: LOG_FT, num_vars, - num_instances: 1, + num_instances: 2, }; - let offset_bits_info = DecomposedBitsInfo { + let offset_bits_info = BitDecompositionInstanceInfo { base, base_len, bits_len: k_bits_len, @@ -149,6 +166,8 @@ fn test_round_random_iop() { }; let instance = >::new( + num_vars, + q, k, delta, input, @@ -158,24 +177,189 @@ fn test_round_random_iop() { ); let info = instance.info(); + let mut round_iop = RoundIOP::default(); + let mut prover_trans = Transcript::::new(); + + round_iop.generate_randomness(&mut prover_trans, &info); + round_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = round_iop.prove(&mut prover_trans, &instance); + let evals = instance.evaluate(&kit.randomness); + + let wrapper = kit.extract(); + let mut round_iop = RoundIOP::default(); + let mut verifier_trans = Transcript::::new(); + + round_iop.generate_randomness(&mut verifier_trans, &info); + round_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + + let (check, _) = round_iop.verify(&mut verifier_trans, &wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_round_random_iop_extension_field() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + let q = FF::new(FT); + let k = FF::new(FK); + let k_bits_len = LOG_2FK 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 = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FT, + num_vars, + num_instances: 2, + }; + + let offset_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + let instance = >::new( + num_vars, + q, + k, + delta, + input, + output, + &output_bits_info, + &offset_bits_info, + ); + + let instance_ef = instance.to_ef::(); + let info = instance_ef.info(); + + let mut round_iop = RoundIOP::default(); + let mut prover_trans = Transcript::::new(); + + round_iop.generate_randomness(&mut prover_trans, &info); + round_iop.generate_randomness_for_eq_function(&mut prover_trans, &info); + + let kit = round_iop.prove(&mut prover_trans, &instance_ef); + let evals = instance.evaluate_ext(&kit.randomness); + + let wrapper = kit.extract(); + + let mut round_iop = RoundIOP::default(); + let mut verifier_trans = Transcript::::new(); + + round_iop.generate_randomness(&mut verifier_trans, &info); + round_iop.generate_randomness_for_eq_function(&mut verifier_trans, &info); + let (check, _) = round_iop.verify(&mut verifier_trans, &wrapper, &evals, &info); + + assert!(check); +} + +#[test] +fn test_round_snark() { + let mut rng = rand::thread_rng(); + let uniform = >::new(); + + let q = FF::new(FT); + let k = FF::new(FK); + let k_bits_len = LOG_2FK 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 = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: LOG_FT, + num_vars, + num_instances: 2, + }; + + let offset_bits_info = BitDecompositionInstanceInfo { + base, + base_len, + bits_len: k_bits_len, + num_vars, + num_instances: 2, + }; + + let instance = >::new( + num_vars, + q, + 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); + + // Parameters. + let mut params = RoundParams::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + params.setup(&instance.info(), code_spec); + + // Prover. + let floor_prover = RoundProver::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut prover_trans = Transcript::::default(); + + let proof = floor_prover.prove(&mut prover_trans, ¶ms, &instance); + + let proof_bytes = proof.to_bytes().unwrap(); + + // Verifier. + let floor_verifier = RoundVerifier::< + FF, + EF, + ExpanderCodeSpec, + BrakedownPCS, ExpanderCodeSpec, EF>, + >::default(); + let mut verifier_trans = Transcript::::default(); + + let proof = RoundProof::from_bytes(&proof_bytes).unwrap(); + + let res = floor_verifier.verify(&mut verifier_trans, ¶ms, &instance.info(), &proof); - 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 - )) + assert!(res); } diff --git a/zkp/tests/test_sumcheck.rs b/zkp/tests/test_sumcheck.rs index ed50c1a8..a352ca3b 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,27 @@ 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"); @@ -95,30 +99,32 @@ fn test_polynomial(nv: usize, num_multiplicands_range: (usize, usize), num_produ let (poly, asserted_sum) = random_list_of_products::(nv, num_multiplicands_range, num_products, &mut rng); let poly_info = poly.info(); - let proof = MLSumcheck::prove(&poly).expect("fail to prove"); - let subclaim = MLSumcheck::verify(&poly_info, asserted_sum, &proof).expect("fail to verify"); + let mut trans = Transcript::::new(); + let (proof, _) = MLSumcheck::prove(&mut trans, &poly).expect("fail to prove"); + + let mut trans = Transcript::::new(); + let subclaim = + MLSumcheck::verify(&mut trans, &poly_info, asserted_sum, &proof).expect("fail to verify"); assert!( poly.evaluate(&subclaim.point) == subclaim.expected_evaluations, "wrong subclaim" ); } -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"); - let subclaim = - MLSumcheck::verify_as_subprotocol(verifier_rng, &poly_info, asserted_sum, &proof) - .expect("fail to verify"); + let (proof, prover_state) = MLSumcheck::prove(prover_trans, &poly).expect("fail to prove"); + let subclaim = MLSumcheck::verify(verifier_trans, &poly_info, asserted_sum, &proof) + .expect("fail to verify"); assert!( poly.evaluate(&subclaim.point) == subclaim.expected_evaluations, "wrong subclaim" @@ -133,20 +139,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 +162,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 +185,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, ) } @@ -213,7 +213,8 @@ fn test_extract_sum() { let mut rng = thread_rng(); let (poly, asserted_sum) = random_list_of_products::(8, (3, 4), 3, &mut rng); - let proof = MLSumcheck::prove(&poly).expect("fail to prove"); + let mut trans = Transcript::::new(); + let (proof, _) = MLSumcheck::prove(&mut trans, &poly).expect("fail to prove"); assert_eq!(MLSumcheck::extract_sum(&proof), asserted_sum); } @@ -269,9 +270,13 @@ fn test_shared_reference() { drop(prover); let poly_info = poly.info(); - let proof = MLSumcheck::prove(&poly).expect("fail to prove"); + let mut trans = Transcript::::new(); + let (proof, _) = MLSumcheck::prove(&mut trans, &poly).expect("fail to prove"); let asserted_sum = MLSumcheck::extract_sum(&proof); - let subclaim = MLSumcheck::verify(&poly_info, asserted_sum, &proof).expect("fail to verify"); + + let mut trans = Transcript::::new(); + let subclaim = + MLSumcheck::verify(&mut trans, &poly_info, asserted_sum, &proof).expect("fail to verify"); assert!( poly.evaluate(&subclaim.point) == subclaim.expected_evaluations, "wrong subclaim" 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 - )); -}