arnaucube
arnaucube/hyperplonkPublic Self-hosted repository
hyperplonk/poly-iop/src/sum_check/verifier.rs
poly-iop/src/sum_check/verifier.rsRaw
  1// TODO: some of the struct is generic for Sum Checks and Zero Checks.
  2// If so move them to src/structs.rs
  3
  4use super::SumCheckVerifier;
  5use crate::{
  6    errors::PolyIOPErrors,
  7    structs::{DomainInfo, IOPProverMessage, IOPVerifierState, SubClaim},
  8    transcript::IOPTranscript,
  9};
 10use ark_ff::PrimeField;
 11use ark_std::{end_timer, start_timer};
 12
 13#[cfg(feature = "parallel")]
 14use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
 15
 16impl<F: PrimeField> SumCheckVerifier<F> for IOPVerifierState<F> {
 17    type DomainInfo = DomainInfo<F>;
 18    type ProverMessage = IOPProverMessage<F>;
 19    type Challenge = F;
 20    type Transcript = IOPTranscript<F>;
 21    type SubClaim = SubClaim<F>;
 22
 23    /// initialize the verifier
 24    fn verifier_init(index_info: &Self::DomainInfo) -> Self {
 25        let start = start_timer!(|| "sum check verifier init");
 26        let res = Self {
 27            round: 1,
 28            num_vars: index_info.num_variables,
 29            max_degree: index_info.max_degree,
 30            finished: false,
 31            polynomials_received: Vec::with_capacity(index_info.num_variables),
 32            challenges: Vec::with_capacity(index_info.num_variables),
 33        };
 34        end_timer!(start);
 35        res
 36    }
 37
 38    /// Run verifier at current round, given prover message
 39    ///
 40    /// Normally, this function should perform actual verification. Instead,
 41    /// `verify_round` only samples and stores randomness and perform
 42    /// verifications altogether in `check_and_generate_subclaim` at
 43    /// the last step.
 44    fn verify_round_and_update_state(
 45        &mut self,
 46        prover_msg: &Self::ProverMessage,
 47        transcript: &mut Self::Transcript,
 48    ) -> Result<Self::Challenge, PolyIOPErrors> {
 49        let start =
 50            start_timer!(|| format!("sum check verify {}-th round and update state", self.round));
 51
 52        if self.finished {
 53            return Err(PolyIOPErrors::InvalidVerifier(
 54                "Incorrect verifier state: Verifier is already finished.".to_string(),
 55            ));
 56        }
 57
 58        // Now, verifier should check if the received P(0) + P(1) = expected. The check
 59        // is moved to `check_and_generate_subclaim`, and will be done after the
 60        // last round.
 61
 62        let challenge = transcript.get_and_append_challenge(b"Internal round")?;
 63        self.challenges.push(challenge);
 64        self.polynomials_received
 65            .push(prover_msg.evaluations.to_vec());
 66
 67        // Now, verifier should set `expected` to P(r).
 68        // This operation is also moved to `check_and_generate_subclaim`,
 69        // and will be done after the last round.
 70
 71        if self.round == self.num_vars {
 72            // accept and close
 73            self.finished = true;
 74        } else {
 75            self.round += 1;
 76        }
 77
 78        end_timer!(start);
 79        Ok(challenge)
 80    }
 81
 82    /// verify the sumcheck phase, and generate the subclaim
 83    ///
 84    /// If the asserted sum is correct, then the multilinear polynomial
 85    /// evaluated at `subclaim.point` is `subclaim.expected_evaluation`.
 86    /// Otherwise, it is highly unlikely that those two will be equal.
 87    /// Larger field size guarantees smaller soundness error.
 88    fn check_and_generate_subclaim(
 89        &self,
 90        asserted_sum: &F,
 91    ) -> Result<Self::SubClaim, PolyIOPErrors> {
 92        let start = start_timer!(|| "sum check check and generate subclaim");
 93        if !self.finished {
 94            return Err(PolyIOPErrors::InvalidVerifier(
 95                "Incorrect verifier state: Verifier has not finished.".to_string(),
 96            ));
 97        }
 98
 99        if self.polynomials_received.len() != self.num_vars {
100            return Err(PolyIOPErrors::InvalidVerifier(
101                "insufficient rounds".to_string(),
102            ));
103        }
104
105        #[cfg(feature = "parallel")]
106        let mut expected_vec = self
107            .polynomials_received
108            .clone()
109            .into_par_iter()
110            .zip(self.challenges.clone().into_par_iter())
111            .map(|(evaluations, challenge)| {
112                if evaluations.len() != self.max_degree + 1 {
113                    return Err(PolyIOPErrors::InvalidVerifier(format!(
114                        "incorrect number of evaluations: {} vs {}",
115                        evaluations.len(),
116                        self.max_degree + 1
117                    )));
118                }
119                interpolate_uni_poly::<F>(&evaluations, challenge)
120            })
121            .collect::<Result<Vec<_>, PolyIOPErrors>>()?;
122
123        #[cfg(not(feature = "parallel"))]
124        let mut expected_vec = self
125            .polynomials_received
126            .clone()
127            .into_iter()
128            .zip(self.challenges.clone().into_iter())
129            .map(|(evaluations, challenge)| {
130                if evaluations.len() != self.max_degree + 1 {
131                    return Err(PolyIOPErrors::InvalidVerifier(format!(
132                        "incorrect number of evaluations: {} vs {}",
133                        evaluations.len(),
134                        self.max_degree + 1
135                    )));
136                }
137                interpolate_uni_poly::<F>(&evaluations, challenge)
138            })
139            .collect::<Result<Vec<_>, PolyIOPErrors>>()?;
140        // insert the asserted_sum to the first position of the expected vector
141        expected_vec.insert(0, *asserted_sum);
142
143        for (evaluations, &expected) in self
144            .polynomials_received
145            .iter()
146            .zip(expected_vec.iter())
147            .take(self.num_vars)
148        {
149            if evaluations[0] + evaluations[1] != expected {
150                return Err(PolyIOPErrors::InvalidProof(
151                    "Prover message is not consistent with the claim.".to_string(),
152                ));
153            }
154        }
155        end_timer!(start);
156        Ok(SubClaim {
157            point: self.challenges.to_vec(),
158            // the last expected value (unchecked) will be included in the subclaim
159            expected_evaluation: expected_vec[self.num_vars],
160        })
161    }
162}
163
164/// Interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this
165/// polynomial at `eval_at`:
166///   \sum_{i=0}^len p_i * (\prod_{j!=i} (eval_at - j)/(i-j) )
167/// This implementation is linear in number of inputs in terms of field
168/// operations. It also has a quadratic term in primitive operations which is
169/// negligible compared to field operations.
170pub(crate) fn interpolate_uni_poly<F: PrimeField>(
171    p_i: &[F],
172    eval_at: F,
173) -> Result<F, PolyIOPErrors> {
174    let start = start_timer!(|| "sum check interpolate uni poly opt");
175
176    let mut res = F::zero();
177
178    // prod = \prod_{j!=i} (eval_at - j)
179    let mut evals = vec![];
180    let len = p_i.len();
181    let mut prod = eval_at;
182    evals.push(eval_at);
183
184    for e in 1..len {
185        let tmp = eval_at - F::from(e as u64);
186        evals.push(tmp);
187        prod *= tmp;
188    }
189
190    for i in 0..len {
191        let divisor = get_divisor(i, len)?;
192        let divisor_f = {
193            if divisor < 0 {
194                -F::from((-divisor) as u128)
195            } else {
196                F::from(divisor as u128)
197            }
198        };
199        res += p_i[i] * prod / (divisor_f * evals[i]);
200    }
201
202    end_timer!(start);
203    Ok(res)
204}
205
206/// Compute \prod_{j!=i)^len (i-j). This function takes O(n^2) number of
207/// primitive operations which is negligible compared to field operations.
208// We know
209//  - factorial(20) ~ 2^61
210//  - factorial(33) ~ 2^123
211// so we will be able to store the result for len<=20 with i64;
212// for len<=33 with i128; and we do not currently support len>33.
213#[inline]
214fn get_divisor(i: usize, len: usize) -> Result<i128, PolyIOPErrors> {
215    if len <= 20 {
216        let mut res = 1i64;
217        for j in 0..len {
218            if j != i {
219                res *= i as i64 - j as i64;
220            }
221        }
222        Ok(res as i128)
223    } else if len <= 33 {
224        let mut res = 1i128;
225        for j in 0..len {
226            if j != i {
227                res *= i as i128 - j as i128;
228            }
229        }
230        Ok(res)
231    } else {
232        Err(PolyIOPErrors::InvalidParameters(
233            "Do not support number variable > 33".to_string(),
234        ))
235    }
236}