sonobe/folding-schemes/src/utils/espresso/sum_check/verifier.rs
folding-schemes/src/utils/espresso/sum_check/verifier.rsRaw
1// code forked from:
2// https://github.com/EspressoSystems/hyperplonk/tree/main/subroutines/src/poly_iop/sum_check
3//
4// Copyright (c) 2023 Espresso Systems (espressosys.com)
5// This file is part of the HyperPlonk library.
6
7// You should have received a copy of the MIT License
8// along with the HyperPlonk library. If not, see <https://mit-license.org/>.
9
10//! Verifier subroutines for a SumCheck protocol.
11
12use super::{
13 structs::{IOPProverMessage, IOPVerifierState},
14 SumCheckSubClaim, SumCheckVerifier,
15};
16use crate::{transcript::Transcript, utils::virtual_polynomial::VPAuxInfo};
17use ark_crypto_primitives::sponge::Absorb;
18use ark_ff::PrimeField;
19use ark_poly::Polynomial;
20use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial};
21use ark_std::{end_timer, start_timer};
22use espresso_subroutines::poly_iop::prelude::PolyIOPErrors;
23
24#[cfg(feature = "parallel")]
25use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
26
27impl<F: PrimeField + Absorb> SumCheckVerifier<F> for IOPVerifierState<F> {
28 type VPAuxInfo = VPAuxInfo<F>;
29 type ProverMessage = IOPProverMessage<F>;
30 type Challenge = F;
31 type SumCheckSubClaim = SumCheckSubClaim<F>;
32
33 /// Initialize the verifier's state.
34 fn verifier_init(index_info: &Self::VPAuxInfo) -> Self {
35 let start = start_timer!(|| "sum check verifier init");
36 let res = Self {
37 round: 1,
38 num_vars: index_info.num_variables,
39 finished: false,
40 polynomials_received: Vec::with_capacity(index_info.num_variables),
41 challenges: Vec::with_capacity(index_info.num_variables),
42 };
43 end_timer!(start);
44 res
45 }
46
47 fn verify_round_and_update_state(
48 &mut self,
49 prover_msg: &<IOPVerifierState<F> as SumCheckVerifier<F>>::ProverMessage,
50 transcript: &mut impl Transcript<F>,
51 ) -> Result<<IOPVerifierState<F> as SumCheckVerifier<F>>::Challenge, PolyIOPErrors> {
52 let start =
53 start_timer!(|| format!("sum check verify {}-th round and update state", self.round));
54
55 if self.finished {
56 return Err(PolyIOPErrors::InvalidVerifier(
57 "Incorrect verifier state: Verifier is already finished.".to_string(),
58 ));
59 }
60
61 // In an interactive protocol, the verifier should
62 //
63 // 1. check if the received 'P(0) + P(1) = expected`.
64 // 2. set `expected` to P(r)`
65 //
66 // When we turn the protocol to a non-interactive one, it is sufficient to defer
67 // such checks to `check_and_generate_subclaim` after the last round.
68 let challenge = transcript.get_challenge();
69 self.challenges.push(challenge);
70 self.polynomials_received.push(prover_msg.coeffs.to_vec());
71
72 if self.round == self.num_vars {
73 // accept and close
74 self.finished = true;
75 } else {
76 // proceed to the next round
77 self.round += 1;
78 }
79
80 end_timer!(start);
81 Ok(challenge)
82 }
83
84 fn check_and_generate_subclaim(
85 &self,
86 asserted_sum: &F,
87 ) -> Result<Self::SumCheckSubClaim, PolyIOPErrors> {
88 let start = start_timer!(|| "sum check check and generate subclaim");
89 if !self.finished {
90 return Err(PolyIOPErrors::InvalidVerifier(
91 "Incorrect verifier state: Verifier has not finished.".to_string(),
92 ));
93 }
94
95 if self.polynomials_received.len() != self.num_vars {
96 return Err(PolyIOPErrors::InvalidVerifier(
97 "insufficient rounds".to_string(),
98 ));
99 }
100
101 // the deferred check during the interactive phase:
102 // 2. set `expected` to P(r)`
103 #[cfg(feature = "parallel")]
104 let mut expected_vec = self
105 .polynomials_received
106 .clone()
107 .into_par_iter()
108 .zip(self.challenges.clone().into_par_iter())
109 .map(|(coeffs, challenge)| {
110 // Removed check on number of evaluations here since verifier receives polynomial in coeffs form
111 let prover_poly = DensePolynomial::from_coefficients_slice(&coeffs);
112 Ok(prover_poly.evaluate(&challenge))
113 })
114 .collect::<Result<Vec<_>, PolyIOPErrors>>()?;
115
116 #[cfg(not(feature = "parallel"))]
117 let mut expected_vec = self
118 .polynomials_received
119 .clone()
120 .into_iter()
121 .zip(self.challenges.clone().into_iter())
122 .map(|(coeffs, challenge)| {
123 // Removed check on number of evaluations here since verifier receives polynomial in coeffs form
124 let prover_poly = DensePolynomial::from_coefficients_slice(&coeffs);
125 Ok(prover_poly.evaluate(&challenge))
126 })
127 .collect::<Result<Vec<_>, PolyIOPErrors>>()?;
128
129 // insert the asserted_sum to the first position of the expected vector
130 expected_vec.insert(0, *asserted_sum);
131
132 for (coeffs, &expected) in self
133 .polynomials_received
134 .iter()
135 .zip(expected_vec.iter())
136 .take(self.num_vars)
137 {
138 let poly = DensePolynomial::from_coefficients_slice(coeffs);
139 let eval_at_one: F = poly.iter().sum();
140 let eval_at_zero: F = if poly.coeffs.is_empty() {
141 F::zero()
142 } else {
143 poly.coeffs[0]
144 };
145 let eval = eval_at_one + eval_at_zero;
146
147 // the deferred check during the interactive phase:
148 // 1. check if the received 'P(0) + P(1) = expected`.
149 if eval != 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(SumCheckSubClaim {
157 point: self.challenges.clone(),
158 // the last expected value (not checked within this function) will be included in the
159 // subclaim
160 expected_evaluation: expected_vec[self.num_vars],
161 })
162 }
163}
164
165/// Interpolate a uni-variate degree-`p_i.len()-1` polynomial and evaluate this
166/// polynomial at `eval_at`:
167///
168/// \sum_{i=0}^len p_i * (\prod_{j!=i} (eval_at - j)/(i-j) )
169///
170/// This implementation is linear in number of inputs in terms of field
171/// operations. It also has a quadratic term in primitive operations which is
172/// negligible compared to field operations.
173/// TODO: The quadratic term can be removed by precomputing the lagrange
174/// coefficients.
175pub fn interpolate_uni_poly<F: PrimeField>(p_i: &[F], eval_at: F) -> Result<F, PolyIOPErrors> {
176 let start = start_timer!(|| "sum check interpolate uni poly opt");
177
178 let len = p_i.len();
179 let mut evals = vec![];
180 let mut prod = eval_at;
181 evals.push(eval_at);
182
183 // `prod = \prod_{j} (eval_at - j)`
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 let mut res = F::zero();
190 // we want to compute \prod (j!=i) (i-j) for a given i
191 //
192 // we start from the last step, which is
193 // denom[len-1] = (len-1) * (len-2) *... * 2 * 1
194 // the step before that is
195 // denom[len-2] = (len-2) * (len-3) * ... * 2 * 1 * -1
196 // and the step before that is
197 // denom[len-3] = (len-3) * (len-4) * ... * 2 * 1 * -1 * -2
198 //
199 // i.e., for any i, the one before this will be derived from
200 // denom[i-1] = denom[i] * (len-i) / i
201 //
202 // that is, we only need to store
203 // - the last denom for i = len-1, and
204 // - the ratio between current step and fhe last step, which is the product of
205 // (len-i) / i from all previous steps and we store this product as a fraction
206 // number to reduce field divisions.
207
208 // We know
209 // - 2^61 < factorial(20) < 2^62
210 // - 2^122 < factorial(33) < 2^123
211 // so we will be able to compute the ratio
212 // - for len <= 20 with i64
213 // - for len <= 33 with i128
214 // - for len > 33 with BigInt
215 if p_i.len() <= 20 {
216 let last_denominator = F::from(u64_factorial(len - 1));
217 let mut ratio_numerator = 1i64;
218 let mut ratio_denominator = 1u64;
219
220 for i in (0..len).rev() {
221 let ratio_numerator_f = if ratio_numerator < 0 {
222 -F::from((-ratio_numerator) as u64)
223 } else {
224 F::from(ratio_numerator as u64)
225 };
226
227 res += p_i[i] * prod * F::from(ratio_denominator)
228 / (last_denominator * ratio_numerator_f * evals[i]);
229
230 // compute denom for the next step is current_denom * (len-i)/i
231 if i != 0 {
232 ratio_numerator *= -(len as i64 - i as i64);
233 ratio_denominator *= i as u64;
234 }
235 }
236 } else if p_i.len() <= 33 {
237 let last_denominator = F::from(u128_factorial(len - 1));
238 let mut ratio_numerator = 1i128;
239 let mut ratio_denominator = 1u128;
240
241 for i in (0..len).rev() {
242 let ratio_numerator_f = if ratio_numerator < 0 {
243 -F::from((-ratio_numerator) as u128)
244 } else {
245 F::from(ratio_numerator as u128)
246 };
247
248 res += p_i[i] * prod * F::from(ratio_denominator)
249 / (last_denominator * ratio_numerator_f * evals[i]);
250
251 // compute denom for the next step is current_denom * (len-i)/i
252 if i != 0 {
253 ratio_numerator *= -(len as i128 - i as i128);
254 ratio_denominator *= i as u128;
255 }
256 }
257 } else {
258 let mut denom_up = field_factorial::<F>(len - 1);
259 let mut denom_down = F::one();
260
261 for i in (0..len).rev() {
262 res += p_i[i] * prod * denom_down / (denom_up * evals[i]);
263
264 // compute denom for the next step is current_denom * (len-i)/i
265 if i != 0 {
266 denom_up *= -F::from((len - i) as u64);
267 denom_down *= F::from(i as u64);
268 }
269 }
270 }
271 end_timer!(start);
272 Ok(res)
273}
274
275/// compute the factorial(a) = 1 * 2 * ... * a
276#[inline]
277fn field_factorial<F: PrimeField>(a: usize) -> F {
278 let mut res = F::one();
279 for i in 2..=a {
280 res *= F::from(i as u64);
281 }
282 res
283}
284
285/// compute the factorial(a) = 1 * 2 * ... * a
286#[inline]
287fn u128_factorial(a: usize) -> u128 {
288 let mut res = 1u128;
289 for i in 2..=a {
290 res *= i as u128;
291 }
292 res
293}
294
295/// compute the factorial(a) = 1 * 2 * ... * a
296#[inline]
297fn u64_factorial(a: usize) -> u64 {
298 let mut res = 1u64;
299 for i in 2..=a {
300 res *= i as u64;
301 }
302 res
303}