1mod batched_ideal_check;
3mod combined_poly_builder;
4mod structs;
5
6pub use structs::*;
7
8#[cfg(feature = "parallel")]
9use rayon::prelude::*;
10
11use crate::projections::{ColumnMajorTrace, RowMajorTrace};
12use batched_ideal_check::*;
13use crypto_primitives::PrimeField;
14use num_traits::ConstZero;
15use std::collections::HashMap;
16use thiserror::Error;
17use zinc_poly::{
18 EvaluationError,
19 univariate::dynamic::over_field::DynamicPolynomialF,
20 utils::{ArithErrors as PolyArithErrors, build_eq_x_r_vec},
21};
22use zinc_transcript::traits::{ConstTranscribable, Transcript};
23use zinc_uair::{
24 Uair,
25 ideal::{Ideal, IdealCheck},
26 ideal_collector::{IdealOrZero, collect_ideals},
27};
28use zinc_utils::{cfg_into_iter, inner_transparent_field::InnerTransparentField};
29
30pub trait IdealCheckProtocol: Uair {
32 #[allow(clippy::type_complexity)]
49 fn prove_linear<F>(
50 transcript: &mut impl Transcript,
51 trace_matrix: &ColumnMajorTrace<F>,
52 projected_scalars: &HashMap<Self::Scalar, DynamicPolynomialF<F>>,
53 num_constraints: usize,
54 num_vars: usize,
55 field_cfg: &F::Config,
56 ) -> Result<(Proof<F>, ProverState<F>), IdealCheckError<F, Self::Ideal>>
57 where
58 F: InnerTransparentField,
59 F::Inner: ConstTranscribable,
60 F::Modulus: ConstTranscribable;
61
62 #[allow(clippy::type_complexity)]
78 fn prove_combined<F>(
79 transcript: &mut impl Transcript,
80 trace_matrix: &RowMajorTrace<F>,
81 projected_scalars: &HashMap<Self::Scalar, DynamicPolynomialF<F>>,
82 num_constraints: usize,
83 num_vars: usize,
84 field_cfg: &F::Config,
85 ) -> Result<(Proof<F>, ProverState<F>), IdealCheckError<F, Self::Ideal>>
86 where
87 F: InnerTransparentField,
88 F::Inner: ConstTranscribable,
89 F::Modulus: ConstTranscribable;
90
91 #[allow(clippy::type_complexity)]
116 fn verify_as_subprotocol<F, IdealOverF, IdealOverFFromRef>(
117 transcript: &mut impl Transcript,
118 proof: Proof<F>,
119 num_constraints: usize,
120 num_vars: usize,
121 ideal_over_f_from_ref: IdealOverFFromRef,
122 field_cfg: &F::Config,
123 ) -> Result<VerifierSubclaim<F>, IdealCheckError<F, IdealOverF>>
124 where
125 F: InnerTransparentField,
126 F::Inner: ConstTranscribable,
127 F::Modulus: ConstTranscribable,
128 IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<F>>,
129 IdealOverFFromRef: Fn(&IdealOrZero<Self::Ideal>) -> IdealOverF;
130}
131
132impl<U> IdealCheckProtocol for U
133where
134 U: Uair,
135{
136 #[allow(clippy::type_complexity)]
137 fn prove_linear<F>(
138 transcript: &mut impl Transcript,
139 trace_matrix: &ColumnMajorTrace<F>,
140 projected_scalars: &HashMap<U::Scalar, DynamicPolynomialF<F>>,
141 num_constraints: usize,
142 num_vars: usize,
143 field_cfg: &F::Config,
144 ) -> Result<(Proof<F>, ProverState<F>), IdealCheckError<F, U::Ideal>>
145 where
146 F: InnerTransparentField,
147 F::Inner: ConstTranscribable,
148 F::Modulus: ConstTranscribable,
149 {
150 let evaluation_point = transcript.get_field_challenges(num_vars, field_cfg);
151
152 let combined_mle_values = combined_poly_builder::evaluate_combined_polynomials::<_, U>(
155 trace_matrix,
156 projected_scalars,
157 num_constraints,
158 &evaluation_point,
159 field_cfg,
160 )?;
161
162 let mut transcription_buf: Vec<u8> = vec![0; F::Inner::NUM_BYTES];
163
164 combined_mle_values.iter().for_each(|combined_mle_value| {
165 transcript
166 .absorb_random_field_slice(&combined_mle_value.coeffs, &mut transcription_buf);
167 });
168
169 Ok((
170 Proof {
171 combined_mle_values,
172 },
173 ProverState { evaluation_point },
174 ))
175 }
176
177 #[allow(clippy::type_complexity)]
178 fn prove_combined<F>(
179 transcript: &mut impl Transcript,
180 trace_matrix: &RowMajorTrace<F>,
181 projected_scalars: &HashMap<U::Scalar, DynamicPolynomialF<F>>,
182 num_constraints: usize,
183 num_vars: usize,
184 field_cfg: &F::Config,
185 ) -> Result<(Proof<F>, ProverState<F>), IdealCheckError<F, U::Ideal>>
186 where
187 F: InnerTransparentField,
188 F::Inner: ConstTranscribable,
189 F::Modulus: ConstTranscribable,
190 {
191 let ideal_collector = collect_ideals::<U>(num_constraints);
194 let is_zero_ideal: Vec<bool> = ideal_collector
195 .ideals
196 .iter()
197 .map(|i| i.is_zero_ideal())
198 .collect();
199
200 let evaluation_point = transcript.get_field_challenges(num_vars, field_cfg);
201
202 let combined_mles = combined_poly_builder::compute_combined_polynomials::<_, U>(
204 trace_matrix,
205 projected_scalars,
206 num_constraints,
207 field_cfg,
208 &is_zero_ideal,
209 );
210
211 let eq_table = build_eq_x_r_vec(&evaluation_point, field_cfg)?;
212
213 let combined_mle_values: Vec<DynamicPolynomialF<F>> = cfg_into_iter!(combined_mles)
215 .enumerate()
216 .map(|(i, coeff_mles)| {
217 if is_zero_ideal[i] {
220 return DynamicPolynomialF::ZERO;
221 }
222 let coeffs = coeff_mles
223 .into_iter()
224 .map(|coeff_mle| {
225 zinc_poly::utils::mle_eval_with_eq_table(
226 &coeff_mle.evaluations,
227 &eq_table,
228 field_cfg,
229 )
230 })
231 .collect::<Vec<_>>();
232 DynamicPolynomialF::new_trimmed(coeffs)
233 })
234 .collect::<Vec<_>>();
235
236 let mut transcription_buf: Vec<u8> = vec![0; F::Inner::NUM_BYTES];
237
238 combined_mle_values.iter().for_each(|combined_mle_value| {
239 transcript
240 .absorb_random_field_slice(&combined_mle_value.coeffs, &mut transcription_buf);
241 });
242
243 Ok((
244 Proof {
245 combined_mle_values,
246 },
247 ProverState { evaluation_point },
248 ))
249 }
250
251 fn verify_as_subprotocol<F, IdealOverF, IdealOverFFromRef>(
252 transcript: &mut impl Transcript,
253 proof: Proof<F>,
254 num_constraints: usize,
255 num_vars: usize,
256 ideal_over_f_from_ref: IdealOverFFromRef,
257 field_cfg: &F::Config,
258 ) -> Result<VerifierSubclaim<F>, IdealCheckError<F, IdealOverF>>
259 where
260 F: InnerTransparentField,
261 F::Inner: ConstTranscribable,
262 F::Modulus: ConstTranscribable,
263 IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<F>>,
264 IdealOverFFromRef: Fn(&IdealOrZero<U::Ideal>) -> IdealOverF,
265 {
266 let mut transcription_buf: Vec<u8> = vec![0; F::Inner::NUM_BYTES];
267
268 let combined_mle_values = proof.combined_mle_values;
269
270 let evaluation_point = transcript.get_field_challenges(num_vars, field_cfg);
271
272 for mle_value in &combined_mle_values {
273 transcript.absorb_random_field_slice(&mle_value.coeffs, &mut transcription_buf);
274 }
275
276 let ideal_collector = collect_ideals::<U>(num_constraints);
277
278 let (non_trivial_ideals, non_trivial_values): (Vec<_>, Vec<_>) = ideal_collector
284 .ideals
285 .iter()
286 .zip(combined_mle_values.iter())
287 .filter(|(ideal, _)| !ideal.is_zero_ideal())
288 .map(|(ideal, value)| (ideal_over_f_from_ref(ideal), value.clone()))
289 .unzip();
290
291 batched_ideal_check(&non_trivial_ideals, &non_trivial_values)?;
292
293 Ok(VerifierSubclaim {
294 evaluation_point,
295 values: combined_mle_values,
296 })
297 }
298}
299
300#[derive(Clone, Debug, Error)]
301pub enum IdealCheckError<F: PrimeField, I> {
302 #[error("ideal check prover failed to evaluate an mle: {0}")]
303 MleEvaluationError(#[from] EvaluationError),
304 #[error("mle evaluation ideal check failure: {0}")]
305 IdealCollectorError(#[from] BatchedIdealCheckError<DynamicPolynomialF<F>, I>),
306 #[error("`eq` polynomial construction failure: {0}")]
307 EqPolyConstructionError(#[from] PolyArithErrors),
308}
309
310#[cfg(test)]
311mod tests {
312 use crypto_primitives::{crypto_bigint_int::Int, crypto_bigint_monty::MontyField};
313
314 use rand::rng;
315 use zinc_poly::univariate::{dense::DensePolynomial, dynamic::over_field::DynamicPolynomialF};
316 use zinc_test_uair::{
317 GenerateRandomTrace, TestUairNoMultiplication, TestUairSimpleMultiplication,
318 };
319 use zinc_transcript::Blake3Transcript;
320 use zinc_uair::{
321 constraint_counter::count_constraints,
322 ideal::{DegreeOneIdeal, Ideal, IdealCheck},
323 };
324
325 use crate::test_utils::{
326 LIMBS, run_ideal_check_prover_combined, run_ideal_check_prover_linear, test_config,
327 };
328
329 use super::*;
330
331 fn test_successful_verification_linear<
336 U,
337 IdealOverF,
338 IdealOverFFromRef,
339 const DEGREE_PLUS_ONE: usize,
340 >(
341 num_vars: usize,
342 ideal_over_f_from_ref: IdealOverFFromRef,
343 ) where
344 U: Uair<Scalar = DensePolynomial<Int<5>, DEGREE_PLUS_ONE>>
345 + GenerateRandomTrace<DEGREE_PLUS_ONE, PolyCoeff = Int<5>, Int = Int<5>>
346 + IdealCheckProtocol,
347 IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<MontyField<LIMBS>>>,
348 IdealOverFFromRef: Fn(&IdealOrZero<U::Ideal>) -> IdealOverF,
349 {
350 let mut rng = rng();
351 let transcript = Blake3Transcript::new();
352
353 let (proof, prover_state, ..) = run_ideal_check_prover_linear::<U, DEGREE_PLUS_ONE>(
354 num_vars,
355 &U::generate_random_trace(num_vars, &mut rng),
356 &mut transcript.clone(),
357 );
358
359 let num_constraints = count_constraints::<U>();
360
361 let verifier_result = U::verify_as_subprotocol(
362 &mut transcript.clone(),
363 proof,
364 num_constraints,
365 num_vars,
366 ideal_over_f_from_ref,
367 &test_config(),
368 )
369 .expect("Verification failed");
370
371 assert_eq!(
372 prover_state.evaluation_point,
373 verifier_result.evaluation_point
374 );
375 }
376
377 fn test_successful_verification_combined<
378 U,
379 IdealOverF,
380 IdealOverFFromRef,
381 const DEGREE_PLUS_ONE: usize,
382 >(
383 num_vars: usize,
384 ideal_over_f_from_ref: IdealOverFFromRef,
385 ) where
386 U: Uair<Scalar = DensePolynomial<Int<5>, DEGREE_PLUS_ONE>>
387 + GenerateRandomTrace<DEGREE_PLUS_ONE, PolyCoeff = Int<5>, Int = Int<5>>
388 + IdealCheckProtocol,
389 IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<MontyField<LIMBS>>>,
390 IdealOverFFromRef: Fn(&IdealOrZero<U::Ideal>) -> IdealOverF,
391 {
392 let mut rng = rng();
393 let transcript = Blake3Transcript::new();
394
395 let (proof, prover_state, ..) = run_ideal_check_prover_combined::<U, DEGREE_PLUS_ONE>(
396 num_vars,
397 &U::generate_random_trace(num_vars, &mut rng),
398 &mut transcript.clone(),
399 );
400
401 let num_constraints = count_constraints::<U>();
402
403 let verifier_result = U::verify_as_subprotocol(
404 &mut transcript.clone(),
405 proof,
406 num_constraints,
407 num_vars,
408 ideal_over_f_from_ref,
409 &test_config(),
410 )
411 .expect("Verification failed");
412
413 assert_eq!(
414 prover_state.evaluation_point,
415 verifier_result.evaluation_point
416 );
417 }
418
419 #[test]
420 fn test_successful_verification() {
421 let field_cfg = test_config();
422
423 let num_vars = 2;
424
425 test_successful_verification_linear::<TestUairNoMultiplication<Int<5>>, _, _, 32>(
427 num_vars,
428 |ideal_over_ring| ideal_over_ring.map(|i| DegreeOneIdeal::from_with_cfg(i, &field_cfg)),
429 );
430 test_successful_verification_combined::<TestUairNoMultiplication<Int<5>>, _, _, 32>(
431 num_vars,
432 |ideal_over_ring| ideal_over_ring.map(|i| DegreeOneIdeal::from_with_cfg(i, &field_cfg)),
433 );
434
435 test_successful_verification_combined::<TestUairSimpleMultiplication<Int<5>>, _, _, 32>(
437 num_vars,
438 |_ideal_over_ring| IdealOrZero::<DegreeOneIdeal<_>>::zero(),
439 );
440 }
441}