arnaucube
arnaucube/hyperplonkPublic Self-hosted repository
polish IOP code base (#24)

zhenfei zhenfei.zhang@hotmail.com
Fri, 20 May 2022 12:30:32 -0400
commit

97a89d7ecc18271117a585a591c2c45174c47622

parent

b9527f8e3748bd5be51fc5990d64730c79bebc36

M poly-iop/benches/bench.rs → poly-iop/benches/bench.rs

@@ -23,7 +23,7 @@ };

let (poly, asserted_sum) = VirtualPolynomial::rand(nv, (degree, degree + 1), 2, &mut rng)?; - let poly_info = poly.domain_info.clone(); + let poly_info = poly.aux_info.clone(); let proof = { let start = Instant::now(); for _ in 0..repetition {

@@ -84,7 +84,7 @@ 10

}; let poly = VirtualPolynomial::rand_zero(nv, (degree, degree + 1), 2, &mut rng)?; - let poly_info = poly.domain_info.clone(); + let poly_info = poly.aux_info.clone(); let proof = { let start = Instant::now(); let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();
M poly-iop/src/lib.rs → poly-iop/src/lib.rs

@@ -12,12 +12,23 @@

pub use errors::PolyIOPErrors; pub use sum_check::SumCheck; pub use virtual_poly::VirtualPolynomial; -pub use zero_check::{build_eq_x_r, ZeroCheck}; +pub use zero_check::ZeroCheck; +#[derive(Clone, Debug, Default, Copy)] /// Struct for PolyIOP protocol. -/// It is instantiated with +/// It has an associated type `F` that defines the prime field the multi-variate +/// polynomial operates on. +/// +/// An PolyIOP may be instantiated with one of the following: /// - SumCheck protocol. /// - ZeroCheck protocol. +/// - PermutationCheck protocol. +/// +/// Those individual protocol may have similar or identical APIs. +/// The systematic way to invoke specific protocol is, for example +/// `<PolyIOP<F> as SumCheck<F>>::prove()` pub struct PolyIOP<F: PrimeField> { + /// Associated field + #[doc(hidden)] phantom: PhantomData<F>, }
M poly-iop/src/structs.rs → poly-iop/src/structs.rs

@@ -1,22 +1,10 @@

-//! Structs for polynomials and extensions. +//! This module defines structs that are shared by all sub protocols. use crate::VirtualPolynomial; use ark_ff::PrimeField; -use std::marker::PhantomData; -#[derive(Clone, Debug, Default, PartialEq)] -/// Auxiliary information about the multilinear polynomial -pub struct DomainInfo<F: PrimeField> { - /// 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<F>, -} - -/// Subclaim when verifier is convinced +/// A Subclaim is a claim generated by the verifier at the end of verification +/// when it is convinced. pub struct SubClaim<F: PrimeField> { /// the multi-dimensional point that this multilinear extension is evaluated /// to

@@ -25,9 +13,8 @@ /// 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. +/// An IOP proof is a collections of messages from prover to verifier at each +/// round through the interactive protocol. #[derive(Clone, Debug, Default, PartialEq)] pub struct IOPProof<F: PrimeField> { pub proofs: Vec<IOPProverMessage<F>>,

@@ -40,7 +27,7 @@ pub struct IOPProverMessage<F: PrimeField> {

pub(crate) evaluations: Vec<F>, } -/// Prover State of a PolyIOP +/// Prover State of a PolyIOP. pub struct IOPProverState<F: PrimeField> { /// sampled randomness given by the verifier pub challenges: Vec<F>,
M poly-iop/src/sum_check/mod.rs → poly-iop/src/sum_check/mod.rs

@@ -1,12 +1,10 @@

//! 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, IOPProverState, IOPVerifierState, SubClaim}, + structs::{IOPProof, IOPProverState, IOPVerifierState, SubClaim}, transcript::IOPTranscript, - virtual_poly::VirtualPolynomial, + virtual_poly::{VPAuxInfo, VirtualPolynomial}, PolyIOP, }; use ark_ff::PrimeField;

@@ -18,12 +16,12 @@

/// Trait for doing sum check protocols. pub trait SumCheck<F: PrimeField> { type Proof; - type PolyList; - type DomainInfo; + type VirtualPolynomial; + type VPAuxInfo; type SubClaim; type Transcript; - /// extract sum from the proof + /// Extract sum from the proof fn extract_sum(proof: &Self::Proof) -> F; /// Initialize the system with a transcript

@@ -38,7 +36,7 @@ /// Generate proof of the sum of polynomial over {0,1}^`num_vars`

/// /// The polynomial is represented in the form of a VirtualPolynomial. fn prove( - poly: &Self::PolyList, + poly: &Self::VirtualPolynomial, transcript: &mut Self::Transcript, ) -> Result<Self::Proof, PolyIOPErrors>;

@@ -46,7 +44,7 @@ /// Verify the claimed sum using the proof

fn verify( sum: F, proof: &Self::Proof, - domain_info: &Self::DomainInfo, + aux_info: &Self::VPAuxInfo, transcript: &mut Self::Transcript, ) -> Result<Self::SubClaim, PolyIOPErrors>; }

@@ -56,17 +54,15 @@ pub trait SumCheckProver<F: PrimeField>

where Self: Sized, { - type PolyList; + type VirtualPolynomial; type ProverMessage; - /// Initialize the prover to argue for the sum of polynomial over - /// {0,1}^`num_vars` - /// - /// The polynomial is represented in the form of a VirtualPolynomial. - fn prover_init(polynomial: &Self::PolyList) -> Result<Self, PolyIOPErrors>; + /// Initialize the prover state to argue for the sum of the input polynomial + /// over {0,1}^`num_vars`. + fn prover_init(polynomial: &Self::VirtualPolynomial) -> Result<Self, PolyIOPErrors>; - /// receive message from verifier, generate prover message, and proceed to - /// next round + /// 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(

@@ -77,31 +73,33 @@ }

/// Trait for sum check protocol verifier side APIs. pub trait SumCheckVerifier<F: PrimeField> { - type DomainInfo; + type VPAuxInfo; type ProverMessage; type Challenge; type Transcript; type SubClaim; - /// initialize the verifier - fn verifier_init(index_info: &Self::DomainInfo) -> Self; + /// Initialize the verifier's state. + fn verifier_init(index_info: &Self::VPAuxInfo) -> Self; - /// Run verifier at current round, given prover message + /// Run verifier for the current round, given a 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. + /// Note that `verify_round_and_update_state` only samples and stores + /// challenges; and update the verifier's state accordingly. The actual + /// verifications are deferred (in batch) to `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<Self::Challenge, PolyIOPErrors>; - /// verify the sumcheck phase, and generate the subclaim + /// This function verifies the deferred checks in the interactive version of + /// the protocol; and generate the subclaim. Returns an error if the + /// proof failed to verify. /// /// If the asserted sum is correct, then the multilinear polynomial - /// evaluated at `subclaim.point` is `subclaim.expected_evaluation`. + /// evaluated at `subclaim.point` will be `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(

@@ -112,15 +110,12 @@ }

impl<F: PrimeField> SumCheck<F> for PolyIOP<F> { type Proof = IOPProof<F>; - - type PolyList = VirtualPolynomial<F>; - - type DomainInfo = DomainInfo<F>; - + type VirtualPolynomial = VirtualPolynomial<F>; + type VPAuxInfo = VPAuxInfo<F>; type SubClaim = SubClaim<F>; - type Transcript = IOPTranscript<F>; + /// Extract sum from the proof 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];

@@ -145,17 +140,17 @@ /// Generate proof of the sum of polynomial over {0,1}^`num_vars`

/// /// The polynomial is represented in the form of a VirtualPolynomial. fn prove( - poly: &Self::PolyList, + poly: &Self::VirtualPolynomial, transcript: &mut Self::Transcript, ) -> Result<Self::Proof, PolyIOPErrors> { let start = start_timer!(|| "sum check prove"); - transcript.append_domain_info(&poly.domain_info)?; + transcript.append_aux_info(&poly.aux_info)?; let mut prover_state = IOPProverState::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 mut prover_msgs = Vec::with_capacity(poly.aux_info.num_variables); + for _ in 0..poly.aux_info.num_variables { let prover_msg = IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)?; transcript.append_prover_message(&prover_msg)?;

@@ -169,18 +164,18 @@ proofs: prover_msgs,

}) } - /// verify the claimed sum using the proof + /// Verify the claimed sum using the proof fn verify( claimed_sum: F, proof: &Self::Proof, - domain_info: &Self::DomainInfo, + aux_info: &Self::VPAuxInfo, transcript: &mut Self::Transcript, ) -> Result<Self::SubClaim, PolyIOPErrors> { let start = start_timer!(|| "sum check verify"); - transcript.append_domain_info(domain_info)?; - let mut verifier_state = IOPVerifierState::verifier_init(domain_info); - for i in 0..domain_info.num_variables { + transcript.append_aux_info(aux_info)?; + let mut verifier_state = IOPVerifierState::verifier_init(aux_info); + for i in 0..aux_info.num_variables { let prover_msg = proof.proofs.get(i).expect("proof is incomplete"); transcript.append_prover_message(prover_msg)?; IOPVerifierState::verify_round_and_update_state(

@@ -218,7 +213,7 @@

let (poly, asserted_sum) = VirtualPolynomial::rand(nv, num_multiplicands_range, num_products, &mut rng)?; let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?; - let poly_info = poly.domain_info.clone(); + let poly_info = poly.aux_info.clone(); let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript(); let subclaim = <PolyIOP<Fr> as SumCheck<Fr>>::verify( asserted_sum,

@@ -241,7 +236,7 @@ ) -> Result<(), PolyIOPErrors> {

let mut rng = test_rng(); let (poly, asserted_sum) = VirtualPolynomial::<Fr>::rand(nv, num_multiplicands_range, num_products, &mut rng)?; - let poly_info = poly.domain_info.clone(); + let poly_info = poly.aux_info.clone(); let mut prover_state = IOPProverState::prover_init(&poly)?; let mut verifier_state = IOPVerifierState::verifier_init(&poly_info); let mut challenge = None;

@@ -249,7 +244,7 @@ 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 { + for _ in 0..poly.aux_info.num_variables { let prover_message = IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge) .unwrap();

@@ -362,7 +357,7 @@ assert_eq!(prover.poly.flattened_ml_extensions.len(), 5);

drop(prover); let mut transcript = <PolyIOP<Fr> as SumCheck<Fr>>::init_transcript(); - let poly_info = poly.domain_info.clone(); + let poly_info = poly.aux_info.clone(); let proof = <PolyIOP<Fr> as SumCheck<Fr>>::prove(&poly, &mut transcript)?; let asserted_sum = <PolyIOP<Fr> as SumCheck<Fr>>::extract_sum(&proof);
M poly-iop/src/sum_check/prover.rs → poly-iop/src/sum_check/prover.rs

@@ -1,4 +1,4 @@

-//! Prover +//! Prover subroutines for a SumCheck protocol. use super::SumCheckProver; use crate::{

@@ -15,14 +15,14 @@ #[cfg(feature = "parallel")]

use rayon::iter::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; impl<F: PrimeField> SumCheckProver<F> for IOPProverState<F> { - type PolyList = VirtualPolynomial<F>; + type VirtualPolynomial = VirtualPolynomial<F>; type ProverMessage = IOPProverMessage<F>; - /// Initialize the prover to argue for the sum of polynomial over - /// {0,1}^`num_vars` - fn prover_init(polynomial: &Self::PolyList) -> Result<Self, PolyIOPErrors> { + /// Initialize the prover state to argue for the sum of the input polynomial + /// over {0,1}^`num_vars`. + fn prover_init(polynomial: &Self::VirtualPolynomial) -> Result<Self, PolyIOPErrors> { let start = start_timer!(|| "sum check prover init"); - if polynomial.domain_info.num_variables == 0 { + if polynomial.aux_info.num_variables == 0 { return Err(PolyIOPErrors::InvalidParameters( "Attempt to prove a constant.".to_string(), ));

@@ -30,14 +30,14 @@ }

end_timer!(start); Ok(Self { - challenges: Vec::with_capacity(polynomial.domain_info.num_variables), + challenges: Vec::with_capacity(polynomial.aux_info.num_variables), round: 0, poly: polynomial.clone(), }) } /// Receive message from verifier, generate prover message, and proceed to - /// next round + /// 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(

@@ -47,8 +47,25 @@ ) -> Result<Self::ProverMessage, PolyIOPErrors> {

let start = start_timer!(|| format!("sum check prove {}-th round and update state", self.round)); + if self.round >= self.poly.aux_info.num_variables { + return Err(PolyIOPErrors::InvalidProver( + "Prover is not active".to_string(), + )); + } + let fix_argument = start_timer!(|| "fix argument"); + // Step 1: + // fix argument and evaluate f(x) over x_m = r; where r is the challenge + // for the current round, and m is the round number, indexed from 1 + // + // i.e.: + // at round m <=n, for each mle g(x_1, ... x_n) within the flattened_mle + // which has already been evaluated to + // + // g(r_1, ..., r_{m-1}, x_m ... x_n) + // + // eval g over r_m, and mutate g to g(r_1, ... r_m,, x_{m+1}... x_n) let mut flattened_ml_extensions: Vec<DenseMultilinearExtension<F>> = self .poly .flattened_ml_extensions

@@ -64,18 +81,16 @@ ));

} self.challenges.push(*chal); - // fix argument - let i = self.round; - let r = self.challenges[i - 1]; + let r = self.challenges[self.round - 1]; #[cfg(feature = "parallel")] flattened_ml_extensions .par_iter_mut() - .for_each(|multiplicand| *multiplicand = multiplicand.fix_variables(&[r])); + .for_each(|mle| *mle = mle.fix_variables(&[r])); #[cfg(not(feature = "parallel"))] flattened_ml_extensions .iter_mut() - .for_each(|multiplicand| *multiplicand = multiplicand.fix_variables(&[r])); + .for_each(|mle| *mle = mle.fix_variables(&[r])); } else if self.round > 0 { return Err(PolyIOPErrors::InvalidProver( "verifier message is empty".to_string(),

@@ -85,30 +100,22 @@ 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 products_list = self.poly.products.clone(); - 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(self.poly.aux_info.max_degree + 1); + products_sum.resize(self.poly.aux_info.max_degree + 1, F::zero()); - let mut products_sum = Vec::with_capacity(degree + 1); - products_sum.resize(degree + 1, F::zero()); + let compute_sum = start_timer!(|| "compute sum"); + // Step 2: generate sum for the partial evaluated polynomial: + // f(r_1, ... r_m,, x_{m+1}... x_n) - let compute_sum = start_timer!(|| "compute sum"); - // generate sum #[cfg(feature = "parallel")] products_sum.par_iter_mut().enumerate().for_each(|(t, e)| { - for b in 0..1 << (nv - i) { + for b in 0..1 << (self.poly.aux_info.num_variables - self.round) { // evaluate P_round(t) for (coefficient, products) in products_list.iter() { - let num_multiplicands = products.len(); + let num_mles = products.len(); let mut product = *coefficient; - for &f in products.iter().take(num_multiplicands) { + for &f in products.iter().take(num_mles) { let table = &flattened_ml_extensions[f]; // f's range is checked in init product *= table[b << 1] * (F::one() - F::from(t as u64)) + table[(b << 1) + 1] * F::from(t as u64);

@@ -119,26 +126,23 @@ }

}); #[cfg(not(feature = "parallel"))] - for b in 0..1 << (nv - i) { - products_sum - .iter_mut() - .take(degree + 1) - .enumerate() - .for_each(|(t, e)| { - // evaluate P_round(t) - for (coefficient, products) in products_list.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(t as u64)) - + table[(b << 1) + 1] * F::from(t as u64); - } - *e += product; + products_sum.iter_mut().enumerate().for_each(|(t, e)| { + for b in 0..1 << (self.poly.aux_info.num_variables - self.round) { + // evaluate P_round(t) + for (coefficient, products) in products_list.iter() { + let num_mles = products.len(); + let mut product = *coefficient; + for &f in products.iter().take(num_mles) { + let table = &flattened_ml_extensions[f]; // f's range is checked in init + product *= table[b << 1] * (F::one() - F::from(t as u64)) + + table[(b << 1) + 1] * F::from(t as u64); } - }); - } + *e += product; + } + } + }); + // update prover's state to the partial evaluated polynomial self.poly.flattened_ml_extensions = flattened_ml_extensions .iter() .map(|x| Rc::new(x.clone()))
M poly-iop/src/sum_check/verifier.rs → poly-iop/src/sum_check/verifier.rs

@@ -1,11 +1,11 @@

-// TODO: some of the struct is generic for Sum Checks and Zero Checks. -// If so move them to src/structs.rs +//! Verifier subroutines for a SumCheck protocol. use super::SumCheckVerifier; use crate::{ errors::PolyIOPErrors, - structs::{DomainInfo, IOPProverMessage, IOPVerifierState, SubClaim}, + structs::{IOPProverMessage, IOPVerifierState, SubClaim}, transcript::IOPTranscript, + virtual_poly::VPAuxInfo, }; use ark_ff::PrimeField; use ark_std::{end_timer, start_timer};

@@ -14,14 +14,14 @@ #[cfg(feature = "parallel")]

use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> { - type DomainInfo = DomainInfo<F>; + type VPAuxInfo = VPAuxInfo<F>; type ProverMessage = IOPProverMessage<F>; type Challenge = F; type Transcript = IOPTranscript<F>; type SubClaim = SubClaim<F>; - /// initialize the verifier - fn verifier_init(index_info: &Self::DomainInfo) -> Self { + /// Initialize the verifier's state. + fn verifier_init(index_info: &Self::VPAuxInfo) -> Self { let start = start_timer!(|| "sum check verifier init"); let res = Self { round: 1,

@@ -35,12 +35,12 @@ end_timer!(start);

res } - /// Run verifier at current round, given prover message + /// Run verifier for the current round, given a 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. + /// Note that `verify_round_and_update_state` only samples and stores + /// challenges; and update the verifier's state accordingly. The actual + /// verifications are deferred (in batch) to `check_and_generate_subclaim` + /// at the last step. fn verify_round_and_update_state( &mut self, prover_msg: &Self::ProverMessage,

@@ -55,23 +55,24 @@ "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. + // In an interactive protocol, the verifier should + // + // 1. check if the received 'P(0) + P(1) = expected`. + // 2. set `expected` to P(r)` + // + // When we turn the protocol to a non-interactive one, it is sufficient to defer + // such checks to `check_and_generate_subclaim` 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 { + // proceed to the next round self.round += 1; }

@@ -79,10 +80,12 @@ end_timer!(start);

Ok(challenge) } - /// verify the sumcheck phase, and generate the subclaim + /// This function verifies the deferred checks in the interactive version of + /// the protocol; and generate the subclaim. Returns an error if the + /// proof failed to verify. /// /// If the asserted sum is correct, then the multilinear polynomial - /// evaluated at `subclaim.point` is `subclaim.expected_evaluation`. + /// evaluated at `subclaim.point` will be `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(

@@ -102,6 +105,8 @@ "insufficient rounds".to_string(),

)); } + // the deferred check during the interactive phase: + // 2. set `expected` to P(r)` #[cfg(feature = "parallel")] let mut expected_vec = self .polynomials_received

@@ -137,6 +142,7 @@ }

interpolate_uni_poly::<F>(&evaluations, challenge) }) .collect::<Result<Vec<_>, PolyIOPErrors>>()?; + // insert the asserted_sum to the first position of the expected vector expected_vec.insert(0, *asserted_sum);

@@ -146,6 +152,8 @@ .iter()

.zip(expected_vec.iter()) .take(self.num_vars) { + // the deferred check during the interactive phase: + // 1. check if the received 'P(0) + P(1) = expected`. if evaluations[0] + evaluations[1] != expected { return Err(PolyIOPErrors::InvalidProof( "Prover message is not consistent with the claim.".to_string(),

@@ -154,8 +162,9 @@ }

} end_timer!(start); Ok(SubClaim { - point: self.challenges.to_vec(), - // the last expected value (unchecked) will be included in the subclaim + point: self.challenges.clone(), + // the last expected value (not checked within this function) will be included in the + // subclaim expected_evaluation: expected_vec[self.num_vars], }) }

@@ -163,19 +172,20 @@ }

/// Interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this /// polynomial at `eval_at`: +/// /// \sum_{i=0}^len p_i * (\prod_{j!=i} (eval_at - j)/(i-j) ) +/// /// This implementation is linear in number of inputs in terms of field /// operations. It also has a quadratic term in primitive operations which is /// negligible compared to field operations. -pub(crate) fn interpolate_uni_poly<F: PrimeField>( - p_i: &[F], - eval_at: F, -) -> Result<F, PolyIOPErrors> { +fn interpolate_uni_poly<F: PrimeField>(p_i: &[F], eval_at: F) -> Result<F, PolyIOPErrors> { let start = start_timer!(|| "sum check interpolate uni poly opt"); let mut res = F::zero(); - // prod = \prod_{j!=i} (eval_at - j) + // compute + // - prod = \prod (eval_at - j) + // - evals = [eval_at - j] let mut evals = vec![]; let len = p_i.len(); let mut prod = eval_at;

@@ -188,6 +198,7 @@ prod *= tmp;

} for i in 0..len { + // res += p_i * prod / (divisor * (eval_at - j)) let divisor = get_divisor(i, len)?; let divisor_f = { if divisor < 0 {
M poly-iop/src/transcript.rs → poly-iop/src/transcript.rs

@@ -1,14 +1,24 @@

-use std::marker::PhantomData; +//! Module for PolyIOP transcript. +//! TODO(ZZ): move this module to HyperPlonk where the transcript will also be +//! useful. +//! TODO(ZZ): decide which APIs need to be public. use ark_ff::PrimeField; use merlin::Transcript; +use std::marker::PhantomData; -use crate::{ - errors::PolyIOPErrors, - structs::{DomainInfo, IOPProverMessage}, - to_bytes, -}; +use crate::{errors::PolyIOPErrors, structs::IOPProverMessage, to_bytes, virtual_poly::VPAuxInfo}; +/// An IOP transcript consists of a Merlin transcript and a flag `is_empty` to +/// indicate that if the transcript is empty. +/// +/// It is associated with a prime field `F` for which challenges are generated +/// over. +/// +/// The `is_empty` flag is useful in the case where a protocol is initiated by +/// the verifier, in which case the prover should start its phase by receiving a +/// `non-empty` transcript. +#[derive(Clone)] pub struct IOPTranscript<F: PrimeField> { transcript: Transcript, is_empty: bool,

@@ -17,7 +27,7 @@ phantom: PhantomData<F>,

} impl<F: PrimeField> IOPTranscript<F> { - /// create a new IOP transcript + /// Create a new IOP transcript. pub(crate) fn new(label: &'static [u8]) -> Self { Self { transcript: Transcript::new(label),

@@ -26,7 +36,7 @@ phantom: PhantomData::default(),

} } - // append the message to the transcript + // Append the message to the transcript. pub fn append_message( &mut self, label: &'static [u8],

@@ -37,20 +47,18 @@ self.is_empty = false;

Ok(()) } - pub(crate) fn append_domain_info( - &mut self, - domain_info: &DomainInfo<F>, - ) -> Result<(), PolyIOPErrors> { + // Append the aux information for a virtual polynomial. + pub(crate) fn append_aux_info(&mut self, aux_info: &VPAuxInfo<F>) -> Result<(), PolyIOPErrors> { let message = format!( "max_mul {} num_var {}", - domain_info.max_degree, domain_info.num_variables + aux_info.max_degree, aux_info.num_variables ); self.append_message(b"aux info", message.as_bytes())?; Ok(()) } - // append the message to the transcript + // Append the message to the transcript. pub(crate) fn append_field_element( &mut self, label: &'static [u8],

@@ -59,6 +67,7 @@ ) -> Result<(), PolyIOPErrors> {

self.append_message(label, &to_bytes!(field_elem)?) } + // Append a prover message to the transcript. pub(crate) fn append_prover_message( &mut self, prover_message: &IOPProverMessage<F>,

@@ -69,12 +78,16 @@ }

Ok(()) } - // generate the challenge for the current transcript - // and append it to the transcript + // Generate the challenge from the current transcript + // and append it to the transcript. + // + // The output field element is statistical uniform as long + // as the field has a size less than 2^384. pub(crate) fn get_and_append_challenge( &mut self, label: &'static [u8], ) -> Result<F, PolyIOPErrors> { + // we need to reject when transcript is empty if self.is_empty { return Err(PolyIOPErrors::InvalidTranscript( "transcript is empty".to_string(),

@@ -89,14 +102,23 @@ .append_message(label, &to_bytes!(&challenge)?);

Ok(challenge) } - // generate a list of challenges for the current transcript - // and append it to the transcript + // Generate a list of challenges from the current transcript + // and append them to the transcript. + // + // The output field element are statistical uniform as long + // as the field has a size less than 2^384. pub(crate) fn get_and_append_challenge_vectors( &mut self, label: &'static [u8], len: usize, ) -> Result<Vec<F>, PolyIOPErrors> { // we need to reject when transcript is empty + if self.is_empty { + return Err(PolyIOPErrors::InvalidTranscript( + "transcript is empty".to_string(), + )); + } + let mut res = vec![]; for _ in 0..len { res.push(self.get_and_append_challenge(label)?)
M poly-iop/src/virtual_poly.rs → poly-iop/src/virtual_poly.rs

@@ -1,4 +1,7 @@

-use crate::{errors::PolyIOPErrors, structs::DomainInfo}; +//! This module defines our main mathematical object `VirtualPolynomial`; and +//! various functions associated with it. + +use crate::errors::PolyIOPErrors; use ark_ff::PrimeField; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_std::{

@@ -38,7 +41,7 @@ ///

#[derive(Clone, Debug, Default, PartialEq)] pub struct VirtualPolynomial<F: PrimeField> { /// Aux information about the multilinear polynomial - pub domain_info: DomainInfo<F>, + pub aux_info: VPAuxInfo<F>, /// list of reference to products (as usize) of multilinear extension pub products: Vec<(F, Vec<usize>)>, /// Stores multilinear extensions in which product multiplicand can refer

@@ -48,6 +51,18 @@ /// Pointers to the above poly extensions

raw_pointers_lookup_table: HashMap<*const DenseMultilinearExtension<F>, usize>, } +#[derive(Clone, Debug, Default, PartialEq)] +/// Auxiliary information about the multilinear polynomial +pub struct VPAuxInfo<F: PrimeField> { + /// 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<F>, +} + impl<F: PrimeField> Add for &VirtualPolynomial<F> { type Output = VirtualPolynomial<F>; fn add(self, other: &VirtualPolynomial<F>) -> Self::Output {

@@ -69,10 +84,10 @@ }

} impl<F: PrimeField> VirtualPolynomial<F> { - /// Returns an empty polynomial + /// Creates an empty virtual polynomial with `num_variables`. pub fn new(num_variables: usize) -> Self { VirtualPolynomial { - domain_info: DomainInfo { + aux_info: VPAuxInfo { max_degree: 0, num_variables, phantom: PhantomData::default(),

@@ -83,27 +98,31 @@ raw_pointers_lookup_table: HashMap::new(),

} } - /// Returns an new virtual polynomial from a MLE + /// Creates an new virtual polynomial from a MLE and its coefficient. pub fn new_from_mle(mle: Rc<DenseMultilinearExtension<F>>, coefficient: F) -> Self { let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle); let mut hm = HashMap::new(); hm.insert(mle_ptr, 0); VirtualPolynomial { - domain_info: DomainInfo { + aux_info: VPAuxInfo { // The max degree is the max degree of any individual variable max_degree: 1, num_variables: mle.num_vars, phantom: PhantomData::default(), }, + // here `0` points to the first polynomial of `flattened_ml_extensions` products: vec![(coefficient, vec![0])], flattened_ml_extensions: vec![mle], raw_pointers_lookup_table: hm, } } - /// Add a list of multilinear extensions that is meant to be multiplied - /// together. The resulting polynomial will be multiplied by the scalar + /// Add a product of list of multilinear extensions to self + /// Returns an error if the list is empty, or the MLE has a different + /// `num_vars` from self. + /// + /// The MLEs will be multiplied together, and then multiplied by the scalar /// `coefficient`. pub fn add_mle_list( &mut self,

@@ -112,13 +131,20 @@ coefficient: F,

) -> Result<(), PolyIOPErrors> { let mle_list: Vec<Rc<DenseMultilinearExtension<F>>> = mle_list.into_iter().collect(); let mut indexed_product = Vec::with_capacity(mle_list.len()); - assert!(!mle_list.is_empty()); - self.domain_info.max_degree = max(self.domain_info.max_degree, mle_list.len()); + + if mle_list.is_empty() { + return Err(PolyIOPErrors::InvalidParameters( + "input mle_list is empty".to_string(), + )); + } + + self.aux_info.max_degree = max(self.aux_info.max_degree, mle_list.len()); + for mle in mle_list { - if mle.num_vars != self.domain_info.num_variables { + if mle.num_vars != self.aux_info.num_variables { return Err(PolyIOPErrors::InvalidParameters(format!( "product has a multiplicand with wrong number of variables {} vs {}", - mle.num_vars, self.domain_info.num_variables + mle.num_vars, self.aux_info.num_variables ))); }

@@ -137,16 +163,26 @@ Ok(())

} /// Multiple the current VirtualPolynomial by an MLE: - /// - add the MLE to the MLE list - /// - multiple each product by MLE and its coefficient + /// - add the MLE to the MLE list; + /// - multiple each product by MLE and its coefficient. + /// Returns an error if the MLE has a different `num_vars` from self. pub fn mul_by_mle( &mut self, mle: Rc<DenseMultilinearExtension<F>>, coefficient: F, ) -> Result<(), PolyIOPErrors> { let start = start_timer!(|| "mul by mle"); + + if mle.num_vars != self.aux_info.num_variables { + return Err(PolyIOPErrors::InvalidParameters(format!( + "product has a multiplicand with wrong number of variables {} vs {}", + mle.num_vars, self.aux_info.num_variables + ))); + } + let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle); + // check if this mle already exists in the virtual polynomial let mle_index = match self.raw_pointers_lookup_table.get(&mle_ptr) { Some(&p) => p, None => {

@@ -158,22 +194,27 @@ },

}; for (prod_coef, indices) in self.products.iter_mut() { + // - add the MLE to the MLE list; + // - multiple each product by MLE and its coefficient. indices.push(mle_index); *prod_coef *= coefficient; } - self.domain_info.max_degree += 1; + + // increase the max degree by one as the MLE has degree 1. + self.aux_info.max_degree += 1; end_timer!(start); Ok(()) } - /// Evaluate the polynomial at point `point` + /// Evaluate the virtual polynomial at point `point`. + /// Returns an error is point.len() does not match `num_variables`. pub fn evaluate(&self, point: &[F]) -> Result<F, PolyIOPErrors> { let start = start_timer!(|| "evaluation"); - if self.domain_info.num_variables != point.len() { + if self.aux_info.num_variables != point.len() { return Err(PolyIOPErrors::InvalidParameters(format!( "wrong number of variables {} vs {}", - self.domain_info.num_variables, + self.aux_info.num_variables, point.len() ))); }

@@ -222,8 +263,8 @@ end_timer!(start);

Ok((poly, sum)) } - /// Sample a random virtual polynomial that evaluates to zero everywhere on - /// the boolean hypercube. + /// Sample a random virtual polynomial that evaluates to zero everywhere + /// over the boolean hypercube. pub fn rand_zero<R: RngCore>( nv: usize, num_multiplicands_range: (usize, usize),

@@ -236,11 +277,36 @@ let num_multiplicands =

rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1); let product = random_zero_mle_list(nv, num_multiplicands, rng); let coefficient = F::rand(rng); - poly.add_mle_list(product.into_iter(), coefficient).unwrap(); + poly.add_mle_list(product.into_iter(), coefficient)?; } Ok(poly) } + + // 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)) + // + // This function is used in ZeroCheck. + pub(crate) fn build_f_hat(&self, r: &[F]) -> Result<Self, PolyIOPErrors> { + let start = start_timer!(|| "zero check build hat f"); + + if self.aux_info.num_variables != r.len() { + return Err(PolyIOPErrors::InvalidParameters(format!( + "r.len() is different from number of variables: {} vs {}", + r.len(), + self.aux_info.num_variables + ))); + } + + let eq_x_r = build_eq_x_r(r)?; + let mut res = self.clone(); + res.mul_by_mle(eq_x_r, F::one())?; + + end_timer!(start); + Ok(res) + } } /// Sample a random list of multilinear polynomials.

@@ -307,6 +373,68 @@ end_timer!(start);

list } +// This function build the eq(x, r) polynomial for any given r. +// +// 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<F: PrimeField>(r: &[F]) -> Result<Rc<DenseMultilinearExtension<F>>, PolyIOPErrors> { + let start = start_timer!(|| "zero check build eq_x_r"); + + // 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 + + let mut eval = Vec::new(); + build_eq_x_r_helper(r, &mut eval)?; + + let mle = DenseMultilinearExtension::from_evaluations_vec(r.len(), eval); + + let res = Rc::new(mle); + end_timer!(start); + Ok(res) +} + +/// A helper function to build eq(x, r) recursively. +/// This function takes `r.len()` steps, and for each step it requires a maximum +/// `r.len()-1` multiplications. +fn build_eq_x_r_helper<F: PrimeField>(r: &[F], buf: &mut Vec<F>) -> Result<(), PolyIOPErrors> { + if r.is_empty() { + return Err(PolyIOPErrors::InvalidParameters( + "r length is 0".to_string(), + )); + } else if r.len() == 1 { + // initializing the buffer with [1-r_0, r_0] + buf.push(F::one() - r[0]); + buf.push(r[0]); + } else { + build_eq_x_r_helper(&r[1..], buf)?; + + // suppose at the previous step we received [b_1, ..., b_k] + // for the current step we will need + // if x_0 = 0: (1-r0) * [b_1, ..., b_k] + // if x_0 = 1: r0 * [b_1, ..., b_k] + + let mut res = vec![]; + for &b_i in buf.iter() { + let tmp = r[0] * b_i; + res.push(b_i - tmp); + res.push(tmp); + } + *buf = res; + } + + Ok(()) +} + #[cfg(test)] pub(crate) mod test { use super::*;

@@ -363,5 +491,71 @@ }

} Ok(()) + } + + #[test] + fn test_eq_xr() { + let mut rng = test_rng(); + for nv in 4..10 { + let r: Vec<Fr> = (0..nv).map(|_| Fr::rand(&mut rng)).collect(); + let eq_x_r = build_eq_x_r(r.as_ref()).unwrap(); + let eq_x_r2 = build_eq_x_r_for_test(r.as_ref()); + assert_eq!(eq_x_r, eq_x_r2); + } + } + + /// Naive method to build eq(x, r). + /// Only used for testing purpose. + // 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_for_test<F: PrimeField>(r: &[F]) -> Rc<DenseMultilinearExtension<F>> { + let start = start_timer!(|| "zero check naive build eq_x_r"); + + // 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<F> = r.iter().map(|ri| F::one() - ri).collect(); + + let num_var = r.len(); + let mut eval = vec![]; + + 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())) + { + current_eval *= if bit { *ri } else { *one_minus_ri }; + } + eval.push(current_eval); + } + + let mle = DenseMultilinearExtension::from_evaluations_vec(num_var, eval); + + let res = Rc::new(mle); + end_timer!(start); + res + } + + fn bit_decompose(input: u64, num_var: usize) -> Vec<bool> { + 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 } }
M poly-iop/src/zero_check/mod.rs → poly-iop/src/zero_check/mod.rs

@@ -1,21 +1,20 @@

-use ark_ff::PrimeField; -use ark_poly::DenseMultilinearExtension; -use ark_std::{end_timer, start_timer}; -use std::rc::Rc; +//! Main module for the ZeroCheck protocol. use crate::{ errors::PolyIOPErrors, - structs::{DomainInfo, IOPProof, SubClaim}, + structs::{IOPProof, SubClaim}, sum_check::SumCheck, transcript::IOPTranscript, - virtual_poly::VirtualPolynomial, + virtual_poly::{VPAuxInfo, VirtualPolynomial}, PolyIOP, }; +use ark_ff::PrimeField; +use ark_std::{end_timer, start_timer}; pub trait ZeroCheck<F: PrimeField> { type Proof; - type PolyList; - type DomainInfo; + type VirtualPolynomial; + type VPAuxInfo; type SubClaim; type Transcript;

@@ -30,22 +29,22 @@

/// initialize the prover to argue for the sum of polynomial over /// {0,1}^`num_vars` is zero. fn prove( - poly: &Self::PolyList, + poly: &Self::VirtualPolynomial, transcript: &mut Self::Transcript, ) -> Result<Self::Proof, PolyIOPErrors>; /// verify the claimed sum using the proof fn verify( proof: &Self::Proof, - domain_info: &Self::DomainInfo, + aux_info: &Self::VPAuxInfo, transcript: &mut Self::Transcript, ) -> Result<Self::SubClaim, PolyIOPErrors>; } impl<F: PrimeField> ZeroCheck<F> for PolyIOP<F> { type Proof = IOPProof<F>; - type PolyList = VirtualPolynomial<F>; - type DomainInfo = DomainInfo<F>; + type VirtualPolynomial = VirtualPolynomial<F>; + type VPAuxInfo = VPAuxInfo<F>; /// A ZeroCheck SubClaim consists of /// - the SubClaim from the ZeroCheck

@@ -63,29 +62,41 @@ fn init_transcript() -> Self::Transcript {

IOPTranscript::<F>::new(b"Initializing ZeroCheck transcript") } - /// initialize the prover to argue for the sum of polynomial over + /// Initialize the prover to argue for the sum of polynomial f(x) over /// {0,1}^`num_vars` is zero. + /// + /// f(x) is zero if \hat f(x) := f(x) * eq(x,r) is also a zero polynomial + /// for a random r sampled from transcript. + /// + /// This function will build the \hat f(x) and then invoke the sumcheck + /// protocol to generate a proof for which the sum of \hat f(x) is zero fn prove( - poly: &Self::PolyList, + poly: &Self::VirtualPolynomial, transcript: &mut Self::Transcript, ) -> Result<Self::Proof, PolyIOPErrors> { let start = start_timer!(|| "zero check prove"); - let length = poly.domain_info.num_variables; + let length = poly.aux_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())?; + let f_hat = poly.build_f_hat(r.as_ref())?; let res = <Self as SumCheck<F>>::prove(&f_hat, transcript); end_timer!(start); res } - /// Verify the claimed sum using the proof. - /// the initial challenge `r` is also returned. - /// The caller needs to makes sure that `\hat f = f * eq(x, r)` + /// Verify that the polynomial's sum is zero using the proof. + /// Return a Self::Subclaim that consists of the + /// + /// - a Subclaim that the sum is zero + /// - the initial challenge `r` that is used to build `eq(x, r)` + /// + /// This function will check that \hat f(x)'s sum is zero. It does not check + /// `\hat f(x)` is build correctly. The caller needs to makes sure that + /// `\hat f(x) = f(x) * eq(x, r)` fn verify( proof: &Self::Proof, - fx_domain_info: &Self::DomainInfo, + fx_aux_info: &Self::VPAuxInfo, transcript: &mut Self::Transcript, ) -> Result<Self::SubClaim, PolyIOPErrors> { let start = start_timer!(|| "zero check verify");

@@ -99,112 +110,27 @@ )));

} // generate `r` and pass it to the caller for correctness check - let length = fx_domain_info.num_variables; + let length = fx_aux_info.num_variables; let r = transcript.get_and_append_challenge_vectors(b"vector r", length)?; // hat_fx's max degree is increased by eq(x, r).degree() which is 1 - let mut hat_fx_domain_info = fx_domain_info.clone(); - hat_fx_domain_info.max_degree += 1; + let mut hat_fx_aux_info = fx_aux_info.clone(); + hat_fx_aux_info.max_degree += 1; let subclaim = - <Self as SumCheck<F>>::verify(F::zero(), proof, &hat_fx_domain_info, transcript)?; + <Self as SumCheck<F>>::verify(F::zero(), proof, &hat_fx_aux_info, transcript)?; end_timer!(start); Ok((subclaim, r)) } } -// 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<F: PrimeField>( - poly: &VirtualPolynomial<F>, - r: &[F], -) -> Result<VirtualPolynomial<F>, PolyIOPErrors> { - let start = start_timer!(|| "zero check build hat f"); - - assert_eq!(poly.domain_info.num_variables, r.len()); - - let eq_x_r = build_eq_x_r(r)?; - let mut res = poly.clone(); - res.mul_by_mle(eq_x_r, F::one())?; - - end_timer!(start); - Ok(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)) -pub fn build_eq_x_r<F: PrimeField>( - r: &[F], -) -> Result<Rc<DenseMultilinearExtension<F>>, PolyIOPErrors> { - let start = start_timer!(|| "zero check build eq_x_r"); - - // 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 - - let mut eval = Vec::new(); - build_eq_x_r_helper(r, &mut eval)?; - - let mle = DenseMultilinearExtension::from_evaluations_vec(r.len(), eval); - - let res = Rc::new(mle); - end_timer!(start); - Ok(res) -} - -/// A helper function to build eq(x, r) recursively. -/// This function takes `r.len()` steps, and for each step it requires a maximum -/// `r.len()-1` multiplications. -fn build_eq_x_r_helper<F: PrimeField>(r: &[F], buf: &mut Vec<F>) -> Result<(), PolyIOPErrors> { - if r.is_empty() { - return Err(PolyIOPErrors::InvalidParameters( - "r length is 0".to_string(), - )); - } else if r.len() == 1 { - // initializing the buffer with [1-r0, r0] - buf.push(F::one() - r[0]); - buf.push(r[0]); - } else { - build_eq_x_r_helper(&r[1..], buf)?; - - // suppose in the previous step we have [b1, ..., b_k] - // for the current step we will need - // if x0 = 0: (1-r0) * [b_1, ..., b_k] - // if x0 = 1: r0 * [b1, ..., b_k] - - let mut res = vec![]; - for &e in buf.iter() { - let tmp = e * r[0]; - res.push(e - tmp); - res.push(tmp); - } - *buf = res; - } - - Ok(()) -} - #[cfg(test)] mod test { - use super::{build_eq_x_r, ZeroCheck}; + use super::ZeroCheck; use crate::{errors::PolyIOPErrors, PolyIOP, VirtualPolynomial}; use ark_bls12_381::Fr; - use ark_ff::{PrimeField, UniformRand}; - use ark_poly::DenseMultilinearExtension; - use ark_std::{end_timer, start_timer, test_rng}; - use std::rc::Rc; + use ark_std::test_rng; fn test_zerocheck( nv: usize,

@@ -222,7 +148,7 @@ let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();

transcript.append_message(b"testing", b"initializing transcript for testing")?; let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?; - let poly_info = poly.domain_info.clone(); + let poly_info = poly.aux_info.clone(); let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?; let subclaim =

@@ -242,7 +168,7 @@ let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript();

transcript.append_message(b"testing", b"initializing transcript for testing")?; let proof = <PolyIOP<Fr> as ZeroCheck<Fr>>::prove(&poly, &mut transcript)?; - let poly_info = poly.domain_info.clone(); + let poly_info = poly.aux_info.clone(); let mut transcript = <PolyIOP<Fr> as ZeroCheck<Fr>>::init_transcript(); transcript.append_message(b"testing", b"initializing transcript for testing")?;

@@ -280,71 +206,5 @@ let num_products = 5;

assert!(test_zerocheck(nv, num_multiplicands_range, num_products).is_err()); Ok(()) - } - - #[test] - fn test_eq_xr() { - let mut rng = test_rng(); - for nv in 4..10 { - let r: Vec<Fr> = (0..nv).map(|_| Fr::rand(&mut rng)).collect(); - let eq_x_r = build_eq_x_r(r.as_ref()).unwrap(); - let eq_x_r2 = build_eq_x_r_for_test(r.as_ref()); - assert_eq!(eq_x_r, eq_x_r2); - } - } - - /// Naive method to build eq(x, r). - /// Only used for testing purpose. - // 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_for_test<F: PrimeField>(r: &[F]) -> Rc<DenseMultilinearExtension<F>> { - let start = start_timer!(|| "zero check naive build eq_x_r"); - - // 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<F> = r.iter().map(|ri| F::one() - ri).collect(); - - let num_var = r.len(); - let mut eval = vec![]; - - 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())) - { - current_eval *= if bit { *ri } else { *one_minus_ri }; - } - eval.push(current_eval); - } - - let mle = DenseMultilinearExtension::from_evaluations_vec(num_var, eval); - - let res = Rc::new(mle); - end_timer!(start); - res - } - - fn bit_decompose(input: u64, num_var: usize) -> Vec<bool> { - 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 } }