From fbd400941eb522a92c855041f5231292a47dd0bc Mon Sep 17 00:00:00 2001 From: zhenfei Date: Thu, 12 May 2022 17:29:36 -0400 Subject: [PATCH] initial implemetation of Sumcheck protocol (#7) --- Cargo.toml | 4 +- pcs/Cargo.toml | 8 + pcs/readme.md | 3 + pcs/src/lib.rs | 8 + poly-iop/Cargo.toml | 32 +++ poly-iop/readme.md | 7 + poly-iop/src/errors.rs | 27 ++ poly-iop/src/lib.rs | 23 ++ poly-iop/src/structs.rs | 40 +++ poly-iop/src/sum_check/mod.rs | 404 ++++++++++++++++++++++++++++ poly-iop/src/sum_check/prover.rs | 175 ++++++++++++ poly-iop/src/sum_check/verifier.rs | 196 ++++++++++++++ poly-iop/src/transcript.rs | 106 ++++++++ poly-iop/src/utils.rs | 24 ++ poly-iop/src/virtual_poly.rs | 211 +++++++++++++++ poly-iop/src/zero_check/mod.rs | 316 ++++++++++++++++++++++ poly-iop/src/zero_check/prover.rs | 17 ++ poly-iop/src/zero_check/verifier.rs | 14 + 18 files changed, 1614 insertions(+), 1 deletion(-) create mode 100644 pcs/Cargo.toml create mode 100644 pcs/readme.md create mode 100644 pcs/src/lib.rs create mode 100644 poly-iop/Cargo.toml create mode 100644 poly-iop/readme.md create mode 100644 poly-iop/src/errors.rs create mode 100644 poly-iop/src/lib.rs create mode 100644 poly-iop/src/structs.rs create mode 100644 poly-iop/src/sum_check/mod.rs create mode 100644 poly-iop/src/sum_check/prover.rs create mode 100644 poly-iop/src/sum_check/verifier.rs create mode 100644 poly-iop/src/transcript.rs create mode 100644 poly-iop/src/utils.rs create mode 100644 poly-iop/src/virtual_poly.rs create mode 100644 poly-iop/src/zero_check/mod.rs create mode 100644 poly-iop/src/zero_check/prover.rs create mode 100644 poly-iop/src/zero_check/verifier.rs diff --git a/Cargo.toml b/Cargo.toml index 6c26082..d841a62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,6 @@ [workspace] members = [ - "hyperplonk" + "hyperplonk", + "pcs", + "poly-iop" ] diff --git a/pcs/Cargo.toml b/pcs/Cargo.toml new file mode 100644 index 0000000..3880fee --- /dev/null +++ b/pcs/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "pcs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/pcs/readme.md b/pcs/readme.md new file mode 100644 index 0000000..d72e8f7 --- /dev/null +++ b/pcs/readme.md @@ -0,0 +1,3 @@ +KZG based multilinear polynomial commitment +----- + diff --git a/pcs/src/lib.rs b/pcs/src/lib.rs new file mode 100644 index 0000000..1b4a90c --- /dev/null +++ b/pcs/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/poly-iop/Cargo.toml b/poly-iop/Cargo.toml new file mode 100644 index 0000000..26f294d --- /dev/null +++ b/poly-iop/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "poly-iop" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +ark-ff = { version = "^0.3.0", default-features = false } +ark-std = { version = "^0.3.0", default-features = false } +ark-poly = { version = "^0.3.0", default-features = false } +ark-serialize = { version = "^0.3.0", default-features = false } +ark-bls12-381 = { version = "0.3.0", default-features = false, features = [ "curve" ] } + +rand_chacha = { version = "0.3.0", default-features = false } +merlin = { version = "3.0.0", default-features = false } +displaydoc = { version = "0.2.3", default-features = false } + +rayon = { version = "1.5.2", default-features = false, optional = true } + +[features] +default = [ "parallel" ] +parallel = [ + "rayon", + "ark-std/parallel", + "ark-ff/parallel", + "ark-poly/parallel" + ] +print-trace = [ + "ark-std/print-trace" + ] \ No newline at end of file diff --git a/poly-iop/readme.md b/poly-iop/readme.md new file mode 100644 index 0000000..0881499 --- /dev/null +++ b/poly-iop/readme.md @@ -0,0 +1,7 @@ +Poly IOP +----- + +Implements the following protocols + +- [ ] sum checks +- [ ] zero checks \ No newline at end of file diff --git a/poly-iop/src/errors.rs b/poly-iop/src/errors.rs new file mode 100644 index 0000000..580345a --- /dev/null +++ b/poly-iop/src/errors.rs @@ -0,0 +1,27 @@ +//! Error module. + +use ark_std::string::String; +use displaydoc::Display; + +/// A `enum` specifying the possible failure modes of the PolyIOP. +#[derive(Display, Debug)] +pub enum PolyIOPErrors { + /// Invalid Prover + InvalidProver(String), + /// Invalid Verifier + InvalidVerifier(String), + /// Invalid Proof + InvalidProof(String), + /// Invalid parameters + InvalidParameters(String), + /// Invalid Transcript + InvalidTranscript(String), + /// An error during (de)serialization + SerializationError(ark_serialize::SerializationError), +} + +impl From for PolyIOPErrors { + fn from(e: ark_serialize::SerializationError) -> Self { + Self::SerializationError(e) + } +} diff --git a/poly-iop/src/lib.rs b/poly-iop/src/lib.rs new file mode 100644 index 0000000..df1efe3 --- /dev/null +++ b/poly-iop/src/lib.rs @@ -0,0 +1,23 @@ +#![allow(dead_code)] + +use std::marker::PhantomData; + +use ark_ff::PrimeField; + +mod errors; +mod structs; +mod sum_check; +mod transcript; +mod utils; +mod virtual_poly; +// mod zero_check; + +pub use virtual_poly::VirtualPolynomial; + +/// Struct for PolyIOP protocol. +/// It is instantiated with +/// - SumCheck protocol. +/// - ZeroCheck protocol. (WIP) +pub struct PolyIOP { + phantom: PhantomData, +} diff --git a/poly-iop/src/structs.rs b/poly-iop/src/structs.rs new file mode 100644 index 0000000..49d12b5 --- /dev/null +++ b/poly-iop/src/structs.rs @@ -0,0 +1,40 @@ +//! Structs for polynomials and extensions. + +use ark_ff::PrimeField; +use std::marker::PhantomData; + +#[derive(Clone, Debug, Default, PartialEq)] +/// Auxiliary information about the multilinear polynomial +pub struct DomainInfo { + /// max number of multiplicands in each product + pub max_degree: usize, + /// number of variables of the polynomial + pub num_variables: usize, + /// Associated field + #[doc(hidden)] + pub(crate) phantom: PhantomData, +} + +/// Subclaim when verifier is convinced +pub struct SubClaim { + /// the multi-dimensional point that this multilinear extension is evaluated + /// to + pub point: Vec, + /// the expected evaluation + pub expected_evaluation: F, +} + +/// An IOP proof is a list of messages from prover to verifier +/// through the interactive protocol. +/// It is a shared struct for both sumcheck and zerocheck protocols. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct IOPProof { + pub proofs: Vec>, +} + +/// A message from the prover to the verifier at a given round +/// is a list of evaluations. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct IOPProverMessage { + pub(crate) evaluations: Vec, +} diff --git a/poly-iop/src/sum_check/mod.rs b/poly-iop/src/sum_check/mod.rs new file mode 100644 index 0000000..14aa635 --- /dev/null +++ b/poly-iop/src/sum_check/mod.rs @@ -0,0 +1,404 @@ +//! This module implements the sum check protocol. +//! Currently this is a simple wrapper of the sumcheck protocol +//! from Arkworks. + +use crate::{ + errors::PolyIOPErrors, + structs::{DomainInfo, IOPProof, SubClaim}, + transcript::IOPTranscript, + virtual_poly::VirtualPolynomial, + PolyIOP, +}; +use ark_ff::PrimeField; +use ark_std::{end_timer, start_timer}; + +mod prover; +mod verifier; + +pub use prover::ProverState; +pub use verifier::VerifierState; + +pub trait SumCheck { + type Proof; + type PolyList; + type DomainInfo; + type SubClaim; + type Transcript; + + /// extract sum from the proof + fn extract_sum(proof: &Self::Proof) -> F; + + /// Initialize the system with a transcript + /// + /// This function is optional -- in the case where a SumCheck is + /// an building block for a more complex protocol, the transcript + /// may be initialized by this complex protocol, and passed to the + /// SumCheck prover/verifier. + fn init_transcript() -> Self::Transcript; + + /// generate proof of the sum of polynomial over {0,1}^`num_vars` + /// + /// The polynomial is represented by a list of products of polynomials along + /// with its coefficient that is meant to be added together. + /// + /// This data structure of the polynomial is a list of list of + /// `(coefficient, DenseMultilinearExtension)`. + /// * Number of products n = `polynomial.products.len()`, + /// * Number of multiplicands of ith product m_i = + /// `polynomial.products[i].1.len()`, + /// * Coefficient of ith product c_i = `polynomial.products[i].0` + /// + /// The resulting polynomial is + /// + /// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$ + fn prove( + poly: &Self::PolyList, + transcript: &mut Self::Transcript, + ) -> Result; + + /// verify the claimed sum using the proof + fn verify( + sum: F, + proof: &Self::Proof, + domain_info: &Self::DomainInfo, + transcript: &mut Self::Transcript, + ) -> Result; +} + +pub trait SumCheckProver +where + Self: Sized, +{ + type PolyList; + type ProverMessage; + + /// initialize the prover to argue for the sum of polynomial over + /// {0,1}^`num_vars` + /// + /// The polynomial is represented by a list of products of polynomials along + /// with its coefficient that is meant to be added together. + /// + /// This data structure of the polynomial is a list of list of + /// `(coefficient, DenseMultilinearExtension)`. + /// * Number of products n = `polynomial.products.len()`, + /// * Number of multiplicands of ith product m_i = + /// `polynomial.products[i].1.len()`, + /// * Coefficient of ith product c_i = `polynomial.products[i].0` + /// + /// The resulting polynomial is + /// + /// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$ + fn prover_init(polynomial: &Self::PolyList) -> Result; + + /// receive message from verifier, generate prover message, and proceed to + /// next round + /// + /// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2). + fn prove_round_and_update_state( + &mut self, + challenge: &Option, + ) -> Result; +} + +pub trait SumCheckVerifier { + type DomainInfo; + type ProverMessage; + type Challenge; + type Transcript; + type SubClaim; + + /// initialize the verifier + fn verifier_init(index_info: &Self::DomainInfo) -> Self; + + /// Run verifier at current round, given prover message + /// + /// 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. + fn verify_round_and_update_state( + &mut self, + prover_msg: &Self::ProverMessage, + transcript: &mut Self::Transcript, + ) -> Result; + + /// verify the sumcheck phase, and generate the subclaim + /// + /// If the asserted sum is correct, then the multilinear polynomial + /// evaluated at `subclaim.point` is `subclaim.expected_evaluation`. + /// Otherwise, it is highly unlikely that those two will be equal. + /// Larger field size guarantees smaller soundness error. + fn check_and_generate_subclaim( + &self, + asserted_sum: &F, + ) -> Result; +} + +impl SumCheck for PolyIOP { + type Proof = IOPProof; + + type PolyList = VirtualPolynomial; + + type DomainInfo = DomainInfo; + + type SubClaim = SubClaim; + + type Transcript = IOPTranscript; + + fn extract_sum(proof: &Self::Proof) -> F { + let start = start_timer!(|| "extract sum"); + let res = proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1]; + end_timer!(start); + res + } + + /// Initialize the system with a transcript + /// + /// This function is optional -- in the case where a SumCheck is + /// an building block for a more complex protocol, the transcript + /// may be initialized by this complex protocol, and passed to the + /// SumCheck prover/verifier. + fn init_transcript() -> Self::Transcript { + let start = start_timer!(|| "init transcript"); + let res = IOPTranscript::::new(b"Initializing SumCheck transcript"); + end_timer!(start); + res + } + + /// generate proof of the sum of polynomial over {0,1}^`num_vars` + /// + /// The polynomial is represented by a list of products of polynomials along + /// with its coefficient that is meant to be added together. + /// + /// This data structure of the polynomial is a list of list of + /// `(coefficient, DenseMultilinearExtension)`. + /// * Number of products n = `polynomial.products.len()`, + /// * Number of multiplicands of ith product m_i = + /// `polynomial.products[i].1.len()`, + /// * Coefficient of ith product c_i = `polynomial.products[i].0` + /// + /// The resulting polynomial is + /// + /// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$ + fn prove( + poly: &Self::PolyList, + transcript: &mut Self::Transcript, + ) -> Result { + let start = start_timer!(|| "sum check prove"); + + transcript.append_domain_info(&poly.domain_info)?; + + let mut prover_state = ProverState::prover_init(poly)?; + let mut challenge = None; + let mut prover_msgs = Vec::with_capacity(poly.domain_info.num_variables); + for _ in 0..poly.domain_info.num_variables { + let prover_msg = + ProverState::prove_round_and_update_state(&mut prover_state, &challenge)?; + transcript.append_prover_message(&prover_msg)?; + prover_msgs.push(prover_msg); + challenge = Some(transcript.get_and_append_challenge(b"Internal round")?); + } + + end_timer!(start); + Ok(IOPProof { + proofs: prover_msgs, + }) + } + + /// verify the claimed sum using the proof + fn verify( + claimed_sum: F, + proof: &Self::Proof, + domain_info: &Self::DomainInfo, + transcript: &mut Self::Transcript, + ) -> Result { + let start = start_timer!(|| "sum check prove"); + + transcript.append_domain_info(domain_info)?; + let mut verifier_state = VerifierState::verifier_init(domain_info); + for i in 0..domain_info.num_variables { + let prover_msg = proof.proofs.get(i).expect("proof is incomplete"); + transcript.append_prover_message(prover_msg)?; + VerifierState::verify_round_and_update_state( + &mut verifier_state, + prover_msg, + transcript, + )?; + } + + let res = VerifierState::check_and_generate_subclaim(&verifier_state, &claimed_sum); + + end_timer!(start); + res + } +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::virtual_poly::test::random_list_of_products; + use ark_bls12_381::Fr; + use ark_ff::UniformRand; + use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; + use ark_std::test_rng; + use std::rc::Rc; + + fn test_sumcheck(nv: usize, num_multiplicands_range: (usize, usize), num_products: usize) { + let mut rng = test_rng(); + let mut transcript = PolyIOP::init_transcript(); + + let (poly, asserted_sum) = + random_list_of_products::(nv, num_multiplicands_range, num_products, &mut rng); + let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove"); + let poly_info = poly.domain_info.clone(); + let mut transcript = PolyIOP::init_transcript(); + let subclaim = PolyIOP::verify(asserted_sum, &proof, &poly_info, &mut transcript) + .expect("fail to verify"); + assert!( + poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation, + "wrong subclaim" + ); + } + + fn test_sumcheck_internal( + nv: usize, + num_multiplicands_range: (usize, usize), + num_products: usize, + ) { + let mut rng = test_rng(); + let (poly, asserted_sum) = + random_list_of_products::(nv, num_multiplicands_range, num_products, &mut rng); + let poly_info = poly.domain_info.clone(); + let mut prover_state = ProverState::prover_init(&poly).unwrap(); + let mut verifier_state = VerifierState::verifier_init(&poly_info); + let mut challenge = None; + let mut transcript = IOPTranscript::new(b"a test transcript"); + transcript + .append_message(b"testing", b"initializing transcript for testing") + .unwrap(); + for _ in 0..poly.domain_info.num_variables { + let prover_message = + ProverState::prove_round_and_update_state(&mut prover_state, &challenge).unwrap(); + + challenge = Some( + VerifierState::verify_round_and_update_state( + &mut verifier_state, + &prover_message, + &mut transcript, + ) + .unwrap(), + ); + } + let subclaim = VerifierState::check_and_generate_subclaim(&verifier_state, &asserted_sum) + .expect("fail to generate subclaim"); + assert!( + poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation, + "wrong subclaim" + ); + } + + #[test] + fn test_trivial_polynomial() { + let nv = 1; + let num_multiplicands_range = (4, 13); + let num_products = 5; + + test_sumcheck(nv, num_multiplicands_range, num_products); + test_sumcheck_internal(nv, num_multiplicands_range, num_products); + } + #[test] + fn test_normal_polynomial() { + let nv = 12; + let num_multiplicands_range = (4, 9); + let num_products = 5; + + test_sumcheck(nv, num_multiplicands_range, num_products); + test_sumcheck_internal(nv, num_multiplicands_range, num_products); + } + #[test] + #[should_panic] + fn zero_polynomial_should_error() { + let nv = 0; + let num_multiplicands_range = (4, 13); + let num_products = 5; + + test_sumcheck(nv, num_multiplicands_range, num_products); + test_sumcheck_internal(nv, num_multiplicands_range, num_products); + } + + #[test] + fn test_extract_sum() { + let mut rng = test_rng(); + let mut transcript = PolyIOP::init_transcript(); + let (poly, asserted_sum) = random_list_of_products::(8, (3, 4), 3, &mut rng); + + let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove"); + assert_eq!(PolyIOP::extract_sum(&proof), asserted_sum); + } + + #[test] + /// Test that the memory usage of shared-reference is linear to number of + /// unique MLExtensions instead of total number of multiplicands. + fn test_shared_reference() { + let mut rng = test_rng(); + let ml_extensions: Vec<_> = (0..5) + .map(|_| Rc::new(DenseMultilinearExtension::::rand(8, &mut rng))) + .collect(); + let mut poly = VirtualPolynomial::new(8); + poly.add_product( + vec![ + ml_extensions[2].clone(), + ml_extensions[3].clone(), + ml_extensions[0].clone(), + ], + Fr::rand(&mut rng), + ) + .unwrap(); + poly.add_product( + vec![ + ml_extensions[1].clone(), + ml_extensions[4].clone(), + ml_extensions[4].clone(), + ], + Fr::rand(&mut rng), + ) + .unwrap(); + poly.add_product( + vec![ + ml_extensions[3].clone(), + ml_extensions[2].clone(), + ml_extensions[1].clone(), + ], + Fr::rand(&mut rng), + ) + .unwrap(); + poly.add_product( + vec![ml_extensions[0].clone(), ml_extensions[0].clone()], + Fr::rand(&mut rng), + ) + .unwrap(); + poly.add_product(vec![ml_extensions[4].clone()], Fr::rand(&mut rng)) + .unwrap(); + + assert_eq!(poly.flattened_ml_extensions.len(), 5); + + // test memory usage for prover + let prover = ProverState::prover_init(&poly).unwrap(); + assert_eq!(prover.poly.flattened_ml_extensions.len(), 5); + drop(prover); + + let mut transcript = PolyIOP::init_transcript(); + let poly_info = poly.domain_info.clone(); + let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove"); + let asserted_sum = PolyIOP::extract_sum(&proof); + + let mut transcript = PolyIOP::init_transcript(); + let subclaim = PolyIOP::verify(asserted_sum, &proof, &poly_info, &mut transcript) + .expect("fail to verify"); + assert!( + poly.evaluate(&subclaim.point).unwrap() == subclaim.expected_evaluation, + "wrong subclaim" + ); + } +} diff --git a/poly-iop/src/sum_check/prover.rs b/poly-iop/src/sum_check/prover.rs new file mode 100644 index 0000000..6dc02c6 --- /dev/null +++ b/poly-iop/src/sum_check/prover.rs @@ -0,0 +1,175 @@ +//! Prover +use std::rc::Rc; + +// TODO: some of the struct is generic for Sum Checks and Zero Checks. +// If so move them to src/structs.rs +use super::SumCheckProver; +use crate::{errors::PolyIOPErrors, structs::IOPProverMessage, virtual_poly::VirtualPolynomial}; +use ark_ff::PrimeField; +use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; +use ark_std::{end_timer, start_timer, vec::Vec}; + +#[cfg(feature = "parallel")] +use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; + +/// Prover State +pub struct ProverState { + /// sampled randomness given by the verifier + pub challenges: Vec, + /// the current round number + pub(crate) round: usize, + /// pointer to the virtual polynomial + pub(crate) poly: VirtualPolynomial, +} + +impl SumCheckProver for ProverState { + type PolyList = VirtualPolynomial; + type ProverMessage = IOPProverMessage; + + /// initialize the prover to argue for the sum of polynomial over + /// {0,1}^`num_vars` + /// + /// The polynomial is represented by a list of products of polynomials along + /// with its coefficient that is meant to be added together. + /// + /// This data structure of the polynomial is a list of list of + /// `(coefficient, DenseMultilinearExtension)`. + /// * Number of products n = `polynomial.products.len()`, + /// * Number of multiplicands of ith product m_i = + /// `polynomial.products[i].1.len()`, + /// * Coefficient of ith product c_i = `polynomial.products[i].0` + /// + /// The resulting polynomial is + /// + /// $$\sum_{i=0}^{n}C_i\cdot\prod_{j=0}^{m_i}P_{ij}$$ + fn prover_init(polynomial: &Self::PolyList) -> Result { + let start = start_timer!(|| "prover init"); + if polynomial.domain_info.num_variables == 0 { + return Err(PolyIOPErrors::InvalidParameters( + "Attempt to prove a constant.".to_string(), + )); + } + end_timer!(start); + + Ok(ProverState { + challenges: Vec::with_capacity(polynomial.domain_info.num_variables), + round: 0, + poly: polynomial.clone(), + }) + } + + /// receive message from verifier, generate prover message, and proceed to + /// next round + /// + /// Main algorithm used is from section 3.2 of [XZZPS19](https://eprint.iacr.org/2019/317.pdf#subsection.3.2). + fn prove_round_and_update_state( + &mut self, + challenge: &Option, + ) -> Result { + let start = start_timer!(|| format!("prove {}-th round and update state", self.round)); + + let fix_argument = start_timer!(|| "fix argument"); + + let mut flattened_ml_extensions: Vec> = self + .poly + .flattened_ml_extensions + .iter() + .map(|x| x.as_ref().clone()) + .collect(); + let products = self.poly.products.clone(); + + if let Some(chal) = challenge { + if self.round == 0 { + return Err(PolyIOPErrors::InvalidProver( + "first round should be prover first.".to_string(), + )); + } + self.challenges.push(*chal); + + // fix argument + let i = self.round; + let r = self.challenges[i - 1]; + #[cfg(feature = "parallel")] + flattened_ml_extensions + .par_iter_mut() + .for_each(|multiplicand| *multiplicand = multiplicand.fix_variables(&[r])); + + #[cfg(not(feature = "parallel"))] + flattened_ml_extensions + .iter_mut() + .for_each(|multiplicand| *multiplicand = multiplicand.fix_variables(&[r])); + } else if self.round > 0 { + return Err(PolyIOPErrors::InvalidProver( + "verifier message is empty".to_string(), + )); + } + end_timer!(fix_argument); + + self.round += 1; + + if self.round > self.poly.domain_info.num_variables { + return Err(PolyIOPErrors::InvalidProver( + "Prover is not active".to_string(), + )); + } + + let i = self.round; + let nv = self.poly.domain_info.num_variables; + let degree = self.poly.domain_info.max_degree; // the degree of univariate polynomial sent by prover at this round + + let mut products_sum = Vec::with_capacity(degree + 1); + products_sum.resize(degree + 1, F::zero()); + + let compute_sum = start_timer!(|| "compute sum"); + // generate sum + for b in 0..1 << (nv - i) { + #[cfg(feature = "parallel")] + products_sum + .par_iter_mut() + .take(degree + 1) + .enumerate() + .for_each(|(i, e)| { + // evaluate P_round(t) + for (coefficient, products) in products.iter() { + let num_multiplicands = products.len(); + let mut product = *coefficient; + for &f in products.iter().take(num_multiplicands) { + let table = &flattened_ml_extensions[f]; // f's range is checked in init + product *= table[b << 1] * (F::one() - F::from(i as u64)) + + table[(b << 1) + 1] * F::from(i as u64); + } + *e += product; + } + }); + #[cfg(not(feature = "parallel"))] + products_sum + .iter_mut() + .take(degree + 1) + .enumerate() + .for_each(|(i, e)| { + // evaluate P_round(t) + for (coefficient, products) in products.iter() { + let num_multiplicands = products.len(); + let mut product = *coefficient; + for &f in products.iter().take(num_multiplicands) { + let table = &flattened_ml_extensions[f]; // f's range is checked in init + product *= table[b << 1] * (F::one() - F::from(i as u64)) + + table[(b << 1) + 1] * F::from(i as u64); + } + *e += product; + } + }); + } + + self.poly.flattened_ml_extensions = flattened_ml_extensions + .iter() + .map(|x| Rc::new(x.clone())) + .collect(); + + end_timer!(compute_sum); + end_timer!(start); + Ok(IOPProverMessage { + evaluations: products_sum, + }) + } +} diff --git a/poly-iop/src/sum_check/verifier.rs b/poly-iop/src/sum_check/verifier.rs new file mode 100644 index 0000000..28e8f69 --- /dev/null +++ b/poly-iop/src/sum_check/verifier.rs @@ -0,0 +1,196 @@ +// TODO: some of the struct is generic for Sum Checks and Zero Checks. +// If so move them to src/structs.rs + +use super::SumCheckVerifier; +use crate::{ + errors::PolyIOPErrors, + structs::{DomainInfo, IOPProverMessage, SubClaim}, + transcript::IOPTranscript, +}; +use ark_ff::PrimeField; +use ark_std::{end_timer, start_timer}; + +#[cfg(feature = "parallel")] +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; + +/// Verifier State +pub struct VerifierState { + round: usize, + num_vars: usize, + max_degree: usize, + finished: bool, + /// a list storing the univariate polynomial in evaluation form sent by the + /// prover at each round + polynomials_received: Vec>, + /// a list storing the randomness sampled by the verifier at each round + challenges: Vec, +} + +impl SumCheckVerifier for VerifierState { + type DomainInfo = DomainInfo; + type ProverMessage = IOPProverMessage; + type Challenge = F; + type Transcript = IOPTranscript; + type SubClaim = SubClaim; + + /// initialize the verifier + fn verifier_init(index_info: &Self::DomainInfo) -> Self { + let start = start_timer!(|| "verifier init"); + let res = VerifierState { + round: 1, + num_vars: index_info.num_variables, + max_degree: index_info.max_degree, + finished: false, + polynomials_received: Vec::with_capacity(index_info.num_variables), + challenges: Vec::with_capacity(index_info.num_variables), + }; + end_timer!(start); + res + } + + /// Run verifier at current round, given prover message + /// + /// 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. + fn verify_round_and_update_state( + &mut self, + prover_msg: &Self::ProverMessage, + transcript: &mut Self::Transcript, + ) -> Result { + let start = start_timer!(|| format!("verify {}-th round and update state", self.round)); + + if self.finished { + return Err(PolyIOPErrors::InvalidVerifier( + "Incorrect verifier state: Verifier is already finished.".to_string(), + )); + } + + // 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 challenge = transcript.get_and_append_challenge(b"Internal round")?; + self.challenges.push(challenge); + self.polynomials_received + .push(prover_msg.evaluations.to_vec()); + + // Now, verifier should set `expected` to P(r). + // This operation is also moved to `check_and_generate_subclaim`, + // and will be done after the last round. + + if self.round == self.num_vars { + // accept and close + self.finished = true; + } else { + self.round += 1; + } + + end_timer!(start); + Ok(challenge) + } + + /// verify the sumcheck phase, and generate the subclaim + /// + /// If the asserted sum is correct, then the multilinear polynomial + /// evaluated at `subclaim.point` is `subclaim.expected_evaluation`. + /// Otherwise, it is highly unlikely that those two will be equal. + /// Larger field size guarantees smaller soundness error. + fn check_and_generate_subclaim( + &self, + asserted_sum: &F, + ) -> Result { + let start = start_timer!(|| "check_and_generate_subclaim"); + if !self.finished { + return Err(PolyIOPErrors::InvalidVerifier( + "Incorrect verifier state: Verifier has not finished.".to_string(), + )); + } + + if self.polynomials_received.len() != self.num_vars { + return Err(PolyIOPErrors::InvalidVerifier( + "insufficient rounds".to_string(), + )); + } + + #[cfg(feature = "parallel")] + let mut expected_vec = self + .polynomials_received + .clone() + .into_par_iter() + .zip(self.challenges.clone().into_par_iter()) + .map(|(evaluations, challenge)| { + if evaluations.len() != self.max_degree + 1 { + return Err(PolyIOPErrors::InvalidVerifier(format!( + "incorrect number of evaluations: {} vs {}", + evaluations.len(), + self.max_degree + 1 + ))); + } + Ok(interpolate_uni_poly::(&evaluations, challenge)) + }) + .collect::, PolyIOPErrors>>()?; + + #[cfg(not(feature = "parallel"))] + let mut expected_vec = self + .polynomials_received + .clone() + .into_iter() + .zip(self.challenges.clone().into_iter()) + .map(|(evaluations, challenge)| { + if evaluations.len() != self.max_degree + 1 { + return Err(PolyIOPErrors::InvalidVerifier(format!( + "incorrect number of evaluations: {} vs {}", + evaluations.len(), + self.max_degree + 1 + ))); + } + Ok(interpolate_uni_poly::(&evaluations, challenge)) + }) + .collect::, PolyIOPErrors>>()?; + // insert the asserted_sum to the first position of the expected vector + expected_vec.insert(0, *asserted_sum); + + for (evaluations, &expected) in self + .polynomials_received + .iter() + .zip(expected_vec.iter()) + .take(self.num_vars) + { + if evaluations[0] + evaluations[1] != expected { + return Err(PolyIOPErrors::InvalidProof( + "Prover message is not consistent with the claim.".to_string(), + )); + } + } + end_timer!(start); + Ok(SubClaim { + point: self.challenges.to_vec(), + // the last expected value (unchecked) will be included in the subclaim + expected_evaluation: expected_vec[self.num_vars], + }) + } +} + +/// interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this +/// polynomial at `eval_at`. +pub(crate) fn interpolate_uni_poly(p_i: &[F], eval_at: F) -> F { + let start = start_timer!(|| "interpolate_uni_poly"); + let mut result = F::zero(); + let mut i = F::zero(); + for term in p_i.iter() { + let mut term = *term; + let mut j = F::zero(); + for _ in 0..p_i.len() { + if j != i { + term = term * (eval_at - j) / (i - j) + } + j += F::one(); + } + i += F::one(); + result += term; + } + end_timer!(start); + result +} diff --git a/poly-iop/src/transcript.rs b/poly-iop/src/transcript.rs new file mode 100644 index 0000000..e9164c4 --- /dev/null +++ b/poly-iop/src/transcript.rs @@ -0,0 +1,106 @@ +use std::marker::PhantomData; + +use ark_ff::PrimeField; +use merlin::Transcript; + +use crate::{ + errors::PolyIOPErrors, + structs::{DomainInfo, IOPProverMessage}, + to_bytes, +}; + +pub struct IOPTranscript { + transcript: Transcript, + is_empty: bool, + #[doc(hidden)] + phantom: PhantomData, +} + +impl IOPTranscript { + /// create a new IOP transcript + pub(crate) fn new(label: &'static [u8]) -> Self { + Self { + transcript: Transcript::new(label), + is_empty: true, + phantom: PhantomData::default(), + } + } + + // append the message to the transcript + pub(crate) fn append_message( + &mut self, + label: &'static [u8], + msg: &[u8], + ) -> Result<(), PolyIOPErrors> { + self.transcript.append_message(label, msg); + self.is_empty = false; + Ok(()) + } + + pub(crate) fn append_domain_info( + &mut self, + domain_info: &DomainInfo, + ) -> Result<(), PolyIOPErrors> { + let message = format!( + "max_mul {} num_var {}", + domain_info.max_degree, domain_info.num_variables + ); + self.append_message(b"aux info", message.as_bytes())?; + + Ok(()) + } + + // append the message to the transcript + pub(crate) fn append_field_element( + &mut self, + label: &'static [u8], + field_elem: &F, + ) -> Result<(), PolyIOPErrors> { + self.append_message(label, &to_bytes!(field_elem)?) + } + + pub(crate) fn append_prover_message( + &mut self, + prover_message: &IOPProverMessage, + ) -> Result<(), PolyIOPErrors> { + for e in prover_message.evaluations.iter() { + self.append_field_element(b"prover_message", e)?; + } + Ok(()) + } + + // generate the challenge for the current transcript + // and append it to the transcript + pub(crate) fn get_and_append_challenge( + &mut self, + label: &'static [u8], + ) -> Result { + if self.is_empty { + return Err(PolyIOPErrors::InvalidTranscript( + "transcript is empty".to_string(), + )); + } + + let mut buf = [0u8; 64]; + self.transcript.challenge_bytes(label, &mut buf); + let challenge = F::from_le_bytes_mod_order(&buf); + self.transcript + .append_message(label, &to_bytes!(&challenge)?); + Ok(challenge) + } + + // generate a list of challenges for the current transcript + // and append it to the transcript + pub(crate) fn get_and_append_challenge_vectors( + &mut self, + label: &'static [u8], + len: usize, + ) -> Result, PolyIOPErrors> { + // we need to reject when transcript is empty + let mut res = vec![]; + for _ in 0..len { + res.push(self.get_and_append_challenge(label)?) + } + Ok(res) + } +} diff --git a/poly-iop/src/utils.rs b/poly-iop/src/utils.rs new file mode 100644 index 0000000..831c2fe --- /dev/null +++ b/poly-iop/src/utils.rs @@ -0,0 +1,24 @@ +//! useful macros. + +/// Takes as input a struct, and converts them to a series of bytes. All traits +/// that implement `CanonicalSerialize` can be automatically converted to bytes +/// in this manner. +#[macro_export] +macro_rules! to_bytes { + ($x:expr) => {{ + let mut buf = ark_std::vec![]; + ark_serialize::CanonicalSerialize::serialize($x, &mut buf).map(|_| buf) + }}; +} + +#[test] +fn test_to_bytes() { + use ark_bls12_381::Fr; + use ark_serialize::CanonicalSerialize; + use ark_std::One; + let f1 = Fr::one(); + + let mut bytes = ark_std::vec![]; + f1.serialize(&mut bytes).unwrap(); + assert_eq!(bytes, to_bytes!(&f1).unwrap()); +} diff --git a/poly-iop/src/virtual_poly.rs b/poly-iop/src/virtual_poly.rs new file mode 100644 index 0000000..040e101 --- /dev/null +++ b/poly-iop/src/virtual_poly.rs @@ -0,0 +1,211 @@ +use crate::{errors::PolyIOPErrors, structs::DomainInfo}; +use ark_ff::PrimeField; +use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; +use ark_std::{end_timer, start_timer}; +use std::{cmp::max, collections::HashMap, marker::PhantomData, rc::Rc}; + +/// A virtual polynomial is a list of multilinear polynomials +#[derive(Clone, Debug, Default, PartialEq)] +pub struct VirtualPolynomial { + /// Aux information about the multilinear polynomial + pub domain_info: DomainInfo, + /// list of reference to products (as usize) of multilinear extension + pub products: Vec<(F, Vec)>, + /// Stores multilinear extensions in which product multiplicand can refer + /// to. + pub flattened_ml_extensions: Vec>>, + /// Pointers to the above poly extensions + raw_pointers_lookup_table: HashMap<*const DenseMultilinearExtension, usize>, +} + +impl VirtualPolynomial { + /// Returns an empty polynomial + pub fn new(num_variables: usize) -> Self { + VirtualPolynomial { + domain_info: DomainInfo { + max_degree: 0, + num_variables, + phantom: PhantomData::default(), + }, + products: Vec::new(), + flattened_ml_extensions: Vec::new(), + raw_pointers_lookup_table: HashMap::new(), + } + } + + /// Add a list of multilinear extensions that is meant to be multiplied + /// together. The resulting polynomial will be multiplied by the scalar + /// `coefficient`. + pub fn add_product( + &mut self, + product: impl IntoIterator>>, + coefficient: F, + ) -> Result<(), PolyIOPErrors> { + let product: Vec>> = product.into_iter().collect(); + let mut indexed_product = Vec::with_capacity(product.len()); + assert!(!product.is_empty()); + self.domain_info.max_degree = max(self.domain_info.max_degree, product.len()); + for m in product { + if m.num_vars != self.domain_info.num_variables { + return Err(PolyIOPErrors::InvalidParameters(format!( + "product has a multiplicand with wrong number of variables {} vs {}", + m.num_vars, self.domain_info.num_variables + ))); + } + + let m_ptr: *const DenseMultilinearExtension = Rc::as_ptr(&m); + if let Some(index) = self.raw_pointers_lookup_table.get(&m_ptr) { + indexed_product.push(*index) + } else { + let curr_index = self.flattened_ml_extensions.len(); + self.flattened_ml_extensions.push(m.clone()); + self.raw_pointers_lookup_table.insert(m_ptr, curr_index); + indexed_product.push(curr_index); + } + } + self.products.push((coefficient, indexed_product)); + Ok(()) + } + + /// Evaluate the polynomial at point `point` + pub fn evaluate(&self, point: &[F]) -> Result { + let start = start_timer!(|| "begin evaluation"); + + if self.domain_info.num_variables != point.len() { + return Err(PolyIOPErrors::InvalidParameters(format!( + "wrong number of variables {} vs {}", + self.domain_info.num_variables, + point.len() + ))); + } + + let evals: Vec = self + .flattened_ml_extensions + .iter() + .map(|x| { + x.evaluate(point).unwrap() // safe unwrap here since we have + // already checked that num_var + // matches + }) + .collect(); + + let res = self + .products + .iter() + .map(|(c, p)| *c * p.iter().map(|&i| evals[i]).product::()) + .sum(); + + end_timer!(start); + Ok(res) + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use ark_std::rand::{Rng, RngCore}; + + pub fn random_product( + nv: usize, + num_multiplicands: usize, + rng: &mut R, + ) -> (Vec>>, F) { + let mut multiplicands = Vec::with_capacity(num_multiplicands); + for _ in 0..num_multiplicands { + multiplicands.push(Vec::with_capacity(1 << nv)) + } + let mut sum = F::zero(); + + for _ in 0..(1 << nv) { + let mut product = F::one(); + for i in 0..num_multiplicands { + let val = F::rand(rng); + multiplicands[i].push(val); + product *= val; + } + sum += product; + } + + ( + multiplicands + .into_iter() + .map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x))) + .collect(), + sum, + ) + } + + pub(crate) fn random_list_of_products( + nv: usize, + num_multiplicands_range: (usize, usize), + num_products: usize, + rng: &mut R, + ) -> (VirtualPolynomial, F) { + let mut sum = F::zero(); + let mut poly = VirtualPolynomial::new(nv); + for _ in 0..num_products { + let num_multiplicands = + rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1); + let (product, product_sum) = random_product(nv, num_multiplicands, rng); + let coefficient = F::rand(rng); + poly.add_product(product.into_iter(), coefficient).unwrap(); + sum += product_sum * coefficient; + } + + (poly, sum) + } + + // pub fn random_zero_product( + // nv: usize, + // num_multiplicands: usize, + // rng: &mut R, + // ) -> Vec>> { + // let degree = 2; + // let mut multiplicands = Vec::with_capacity(degree); + // for _ in 0..degree { + // multiplicands.push(Vec::with_capacity(1 << nv)) + // } + // let mut sum = F::zero(); + + // for _ in 0..(1 << nv) { + // let mut product = F::one(); + // for i in 0..degree { + // let val = F::zero(); // F::rand(rng); + // multiplicands[i].push(val); + // product *= val; + // } + // sum += product; + // } + + // // // last nv offsets the poly to 0 + // // for i in 0..num_multiplicands - 1 { + // // multiplicands[i].push(F::one()); + // // } + // // multiplicands[num_multiplicands - 1].push(-sum); + + // multiplicands + // .into_iter() + // .map(|x| + // Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x))) + // .collect() + // } + + // pub(crate) fn random_zero_list_of_products( + // nv: usize, + // num_multiplicands_range: (usize, usize), + // num_products: usize, + // rng: &mut R, + // ) -> VirtualPolynomial { + // let mut poly = VirtualPolynomial::new(nv); + // for _ in 0..num_products { + // let num_multiplicands = + // + // rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1); + // let product = random_zero_product(nv, num_multiplicands, rng); + // let coefficient = F::rand(rng); + // poly.add_product(product.into_iter(), coefficient); + // } + + // poly + // } +} diff --git a/poly-iop/src/zero_check/mod.rs b/poly-iop/src/zero_check/mod.rs new file mode 100644 index 0000000..3667de9 --- /dev/null +++ b/poly-iop/src/zero_check/mod.rs @@ -0,0 +1,316 @@ +mod prover; +mod verifier; + +use ark_ff::PrimeField; +use ark_poly::DenseMultilinearExtension; +pub use prover::ProverState; +use std::rc::Rc; +pub use verifier::VerifierState; + +use crate::{ + errors::PolyIOPErrors, + structs::{DomainInfo, IOPProof, SubClaim}, + sum_check::SumCheck, + transcript::IOPTranscript, + virtual_poly::VirtualPolynomial, + PolyIOP, +}; + +pub trait ZeroCheck { + type Proof; + type PolyList; + type DomainInfo; + type SubClaim; + type Transcript; + + /// Initialize the system with a transcript + /// + /// This function is optional -- in the case where a ZeroCheck is + /// an building block for a more complex protocol, the transcript + /// may be initialized by this complex protocol, and passed to the + /// ZeroCheck prover/verifier. + fn init_transcript() -> Self::Transcript; + + fn prove( + poly: &Self::PolyList, + transcript: &mut Self::Transcript, + ) -> Result; + + /// verify the claimed sum using the proof + fn verify( + proof: &Self::Proof, + domain_info: &Self::DomainInfo, + transcript: &mut Self::Transcript, + ) -> Result; +} + +impl ZeroCheck for PolyIOP { + type Proof = IOPProof; + type PolyList = VirtualPolynomial; + type DomainInfo = DomainInfo; + type SubClaim = SubClaim; + type Transcript = IOPTranscript; + + /// Initialize the system with a transcript + /// + /// This function is optional -- in the case where a ZeroCheck is + /// an building block for a more complex protocol, the transcript + /// may be initialized by this complex protocol, and passed to the + /// ZeroCheck prover/verifier. + fn init_transcript() -> Self::Transcript { + IOPTranscript::::new(b"Initializing ZeroCheck transcript") + } + + fn prove( + poly: &Self::PolyList, + transcript: &mut Self::Transcript, + ) -> Result { + let length = poly.domain_info.num_variables; + let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?; + let f_hat = build_f_hat(poly, r.as_ref()); + >::prove(&f_hat, transcript) + } + + /// Verify the claimed sum using the proof. + /// Caller needs to makes sure that `\hat f = f * eq(x, r)` + fn verify( + proof: &Self::Proof, + domain_info: &Self::DomainInfo, + transcript: &mut Self::Transcript, + ) -> Result { + println!( + "sum: {}", + proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1] + ); + + >::verify(F::zero(), proof, domain_info, transcript) + } +} + +// Input poly f(x) and a random vector r, output +// \hat f(x) = \sum_{x_i \in eval_x} f(x_i) eq(x, r) +// where +// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i)) +fn build_f_hat(poly: &VirtualPolynomial, r: &[F]) -> VirtualPolynomial { + assert_eq!(poly.domain_info.num_variables, r.len()); + let mut res = poly.clone(); + let eq_x_r = build_eq_x_r(r); + res.add_product([eq_x_r; 1], F::one()); + // // First, we build array for {1 - r_i} + // let one_minus_r: Vec = r.iter().map(|ri| F::one() - ri).collect(); + + // let mut eval = vec![]; + // // let eq_x_r = build_eq_x_r(r); + // let num_var = r.len(); + // let mut res = VirtualPolynomial::new(num_var); + // // res.add_product([eq_x_r; 1], F::one()); + + // for i in 0..1 << num_var { + // let bit_sequence = bit_decompose(i, num_var); + // let bit_points: Vec = bit_sequence.iter().map(|&x| F::from(x as + // u64)).collect(); let mut eq_eval = F::one(); + // for (&bit, (ri, one_minus_ri)) in + // bit_sequence.iter().zip(r.iter().zip(one_minus_r.iter())) { + // if bit { + // eq_eval *= ri; + // } else { + // eq_eval *= one_minus_ri; + // } + // } + // eval.push(eq_eval * poly.evaluate(&bit_points)) + // } + // let hat_f = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + // num_var, eval, + // )); + // res.add_product([hat_f; 1], F::one()); + res +} + +// Evaluate +// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i)) +// over r, which is +// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i)) +fn build_eq_x_r(r: &[F]) -> Rc> { + // we build eq(x,r) from its evaluations + // we want to evaluate eq(x,r) over x \in {0, 1}^num_vars + // for example, with num_vars = 4, x is a binary vector of 4, then + // 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3) + // 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3) + // 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3) + // 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3) + // .... + // 1 1 1 1 -> r0 * r1 * r2 * r3 + // we will need 2^num_var evaluations + + // First, we build array for {1 - r_i} + let one_minus_r: Vec = r.iter().map(|ri| F::one() - ri).collect(); + + let num_var = r.len(); + let mut eval = vec![]; + + // TODO: optimize the following code + // currently, a naive implementation requires num_var * 2^num_var + // field multiplications. + for i in 0..1 << num_var { + let mut current_eval = F::one(); + let bit_sequence = bit_decompose(i, num_var); + + for (&bit, (ri, one_minus_ri)) in bit_sequence.iter().zip(r.iter().zip(one_minus_r.iter())) + { + if bit { + current_eval *= *ri; + } else { + current_eval *= *one_minus_ri; + } + } + eval.push(current_eval); + } + let res = Rc::new(DenseMultilinearExtension::from_evaluations_vec( + num_var, eval, + )); + + res +} + +fn bit_decompose(input: u64, num_var: usize) -> Vec { + let mut res = Vec::with_capacity(num_var); + let mut i = input; + for _ in 0..num_var { + res.push(i & 1 == 1); + i >>= 1; + } + res +} + +#[cfg(test)] +mod test { + + use super::ZeroCheck; + use crate::{virtual_poly::test::random_zero_list_of_products, PolyIOP, VirtualPolynomial}; + use ark_bls12_381::Fr; + use ark_ff::UniformRand; + use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; + use ark_std::test_rng; + use std::rc::Rc; + + fn test_polynomial(nv: usize, num_multiplicands_range: (usize, usize), num_products: usize) { + let mut rng = test_rng(); + let mut transcript = PolyIOP::init_transcript(); + transcript + .append_message(b"testing", b"initializing transcript for testing") + .unwrap(); + let poly = random_zero_list_of_products::( + nv, + num_multiplicands_range, + num_products, + &mut rng, + ); + // println!("{:?}", poly); + + let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove"); + println!( + "{}", + proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1] + ); + + let poly_info = poly.domain_info.clone(); + let mut transcript = PolyIOP::init_transcript(); + transcript + .append_message(b"testing", b"initializing transcript for testing") + .unwrap(); + let subclaim = + PolyIOP::verify(&proof, &poly_info, &mut transcript).expect("fail to verify"); + assert!( + poly.evaluate(&subclaim.point) == subclaim.expected_evaluation, + "wrong subclaim" + ); + } + + #[test] + fn test_trivial_polynomial() { + let nv = 1; + let num_multiplicands_range = (4, 5); + let num_products = 1; + + test_polynomial(nv, num_multiplicands_range, num_products); + } + #[test] + fn test_normal_polynomial() { + let nv = 16; + let num_multiplicands_range = (4, 9); + let num_products = 5; + + test_polynomial(nv, num_multiplicands_range, num_products); + } + #[test] + #[should_panic] + fn zero_polynomial_should_error() { + let nv = 0; + let num_multiplicands_range = (4, 13); + let num_products = 5; + + test_polynomial(nv, num_multiplicands_range, num_products); + } + + #[test] + /// Test that the memory usage of shared-reference is linear to number of + /// unique MLExtensions instead of total number of multiplicands. + fn test_shared_reference() { + let mut rng = test_rng(); + let ml_extensions: Vec<_> = (0..5) + .map(|_| Rc::new(DenseMultilinearExtension::::rand(8, &mut rng))) + .collect(); + let mut poly = VirtualPolynomial::new(8); + poly.add_product( + vec![ + ml_extensions[2].clone(), + ml_extensions[3].clone(), + ml_extensions[0].clone(), + ], + Fr::rand(&mut rng), + ); + poly.add_product( + vec![ + ml_extensions[1].clone(), + ml_extensions[4].clone(), + ml_extensions[4].clone(), + ], + Fr::rand(&mut rng), + ); + poly.add_product( + vec![ + ml_extensions[3].clone(), + ml_extensions[2].clone(), + ml_extensions[1].clone(), + ], + Fr::rand(&mut rng), + ); + poly.add_product( + vec![ml_extensions[0].clone(), ml_extensions[0].clone()], + Fr::rand(&mut rng), + ); + poly.add_product(vec![ml_extensions[4].clone()], Fr::rand(&mut rng)); + + assert_eq!(poly.flattened_ml_extensions.len(), 5); + + let mut transcript = PolyIOP::init_transcript(); + + transcript + .append_message(b"testing", b"initializing transcript for testing") + .unwrap(); + let poly_info = poly.domain_info.clone(); + let proof = PolyIOP::prove(&poly, &mut transcript).expect("fail to prove"); + + let mut transcript = PolyIOP::init_transcript(); + + transcript + .append_message(b"testing", b"initializing transcript for testing") + .unwrap(); + let subclaim = + PolyIOP::verify(&proof, &poly_info, &mut transcript).expect("fail to verify"); + assert!( + poly.evaluate(&subclaim.point) == subclaim.expected_evaluation, + "wrong subclaim" + ); + } +} diff --git a/poly-iop/src/zero_check/prover.rs b/poly-iop/src/zero_check/prover.rs new file mode 100644 index 0000000..af8892d --- /dev/null +++ b/poly-iop/src/zero_check/prover.rs @@ -0,0 +1,17 @@ +use ark_ff::PrimeField; +use ark_poly::DenseMultilinearExtension; + +/// Prover State +pub struct ProverState { + /// sampled randomness given by the verifier + pub challenges: Vec, + /// Stores the list of products that is meant to be added together. Each + /// multiplicand is represented by the index in flattened_ml_extensions + pub list_of_products: Vec<(F, Vec)>, + /// Stores a list of multilinear extensions in which `self.list_of_products` + /// points to + pub flattened_ml_extensions: Vec>, + pub(crate) num_vars: usize, + pub(crate) max_degree: usize, + pub(crate) round: usize, +} diff --git a/poly-iop/src/zero_check/verifier.rs b/poly-iop/src/zero_check/verifier.rs new file mode 100644 index 0000000..b7c5c64 --- /dev/null +++ b/poly-iop/src/zero_check/verifier.rs @@ -0,0 +1,14 @@ +use ark_ff::PrimeField; + +/// Verifier State +pub struct VerifierState { + round: usize, + nv: usize, + max_degree: usize, + finished: bool, + /// a list storing the univariate polynomial in evaluation form sent by the + /// prover at each round + polynomials_received: Vec>, + /// a list storing the randomness sampled by the verifier at each round + challenges: Vec, +}