1mod batched_ideal_check;
3mod combined_poly_builder;
4mod structs;
5
6pub use structs::*;
7
8use crate::projections::{ColumnMajorTrace, RowMajorTrace, column_major_to_row_major};
9use batched_ideal_check::*;
10use crypto_primitives::PrimeField;
11use num_traits::ConstZero;
12use std::collections::HashMap;
13use thiserror::Error;
14use zinc_poly::{
15 EvaluationError, univariate::dynamic::over_field::DynamicPolynomialF,
16 utils::ArithErrors as PolyArithErrors,
17};
18use zinc_transcript::traits::{ConstTranscribable, Transcript};
19use zinc_uair::{
20 Uair,
21 degree_counter::count_constraint_degrees,
22 ideal::{Ideal, IdealCheck},
23 ideal_collector::{IdealOrZero, collect_ideals},
24};
25use zinc_utils::inner_transparent_field::InnerTransparentField;
26
27pub trait IdealCheckProtocol: Uair {
29 #[allow(clippy::type_complexity)]
54 fn prove_mle_first<F>(
55 transcript: &mut impl Transcript,
56 trace_matrix: &ColumnMajorTrace<F>,
57 projected_scalars: &HashMap<Self::Scalar, DynamicPolynomialF<F>>,
58 num_constraints: usize,
59 num_vars: usize,
60 field_cfg: &F::Config,
61 ) -> Result<(Proof<F>, ProverState<F>), IdealCheckError<F, Self::Ideal>>
62 where
63 F: InnerTransparentField,
64 F::Inner: ConstTranscribable,
65 F::Modulus: ConstTranscribable;
66
67 #[allow(clippy::type_complexity)]
83 fn prove_combined<F>(
84 transcript: &mut impl Transcript,
85 trace_matrix: &RowMajorTrace<F>,
86 projected_scalars: &HashMap<Self::Scalar, DynamicPolynomialF<F>>,
87 num_constraints: usize,
88 num_vars: usize,
89 field_cfg: &F::Config,
90 ) -> Result<(Proof<F>, ProverState<F>), IdealCheckError<F, Self::Ideal>>
91 where
92 F: InnerTransparentField,
93 F::Inner: ConstTranscribable,
94 F::Modulus: ConstTranscribable;
95
96 #[allow(clippy::type_complexity)]
121 fn verify_as_subprotocol<F, IdealOverF, IdealOverFFromRef>(
122 transcript: &mut impl Transcript,
123 proof: Proof<F>,
124 num_constraints: usize,
125 num_vars: usize,
126 ideal_over_f_from_ref: IdealOverFFromRef,
127 field_cfg: &F::Config,
128 ) -> Result<VerifierSubclaim<F>, IdealCheckError<F, IdealOverF>>
129 where
130 F: InnerTransparentField,
131 F::Inner: ConstTranscribable,
132 F::Modulus: ConstTranscribable,
133 IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<F>>,
134 IdealOverFFromRef: Fn(&IdealOrZero<Self::Ideal>) -> IdealOverF;
135}
136
137impl<U> IdealCheckProtocol for U
138where
139 U: Uair,
140{
141 #[allow(clippy::type_complexity)]
142 fn prove_mle_first<F>(
143 transcript: &mut impl Transcript,
144 trace_matrix: &ColumnMajorTrace<F>,
145 projected_scalars: &HashMap<U::Scalar, DynamicPolynomialF<F>>,
146 num_constraints: usize,
147 num_vars: usize,
148 field_cfg: &F::Config,
149 ) -> Result<(Proof<F>, ProverState<F>), IdealCheckError<F, U::Ideal>>
150 where
151 F: InnerTransparentField,
152 F::Inner: ConstTranscribable,
153 F::Modulus: ConstTranscribable,
154 {
155 let evaluation_point = transcript.get_field_challenges(num_vars, field_cfg);
156
157 let ideal_collector = collect_ideals::<U>(num_constraints);
164 let degrees = count_constraint_degrees::<U>();
165
166 let mut has_linear_nonzero: bool = false;
167 let mut nonlinear_zero: Vec<usize> = Vec::new();
168 let mut nonlinear_nonzero: Vec<usize> = Vec::new();
169 for (idx, ideal) in ideal_collector.ideals.iter().enumerate() {
170 if degrees[idx] <= 1 {
171 has_linear_nonzero |= !ideal.is_zero_ideal();
172 } else if ideal.is_zero_ideal() {
173 nonlinear_zero.push(idx);
174 } else {
175 nonlinear_nonzero.push(idx);
176 }
177 }
178
179 let mut combined_mle_values: Vec<DynamicPolynomialF<F>> = if has_linear_nonzero {
184 let mut res = combined_poly_builder::evaluate_combined_polynomials::<_, U>(
185 trace_matrix,
186 projected_scalars,
187 num_constraints,
188 &evaluation_point,
189 field_cfg,
190 )?;
191
192 for &i in &nonlinear_zero {
195 res[i] = DynamicPolynomialF::ZERO;
196 }
197
198 res
199 } else {
200 vec![DynamicPolynomialF::ZERO; num_constraints]
201 };
202
203 if !nonlinear_nonzero.is_empty() {
204 let row_major = column_major_to_row_major(trace_matrix);
208 let values = combined_poly_builder::evaluate_for_constraints::<_, U>(
209 &row_major,
210 projected_scalars,
211 num_constraints,
212 field_cfg,
213 &nonlinear_nonzero,
214 &evaluation_point,
215 )?;
216 for (&i, v) in nonlinear_nonzero.iter().zip(values) {
217 combined_mle_values[i] = v;
218 }
219 }
220
221 let mut transcription_buf: Vec<u8> = vec![0; F::Inner::NUM_BYTES];
222
223 combined_mle_values.iter().for_each(|combined_mle_value| {
224 transcript
225 .absorb_random_field_slice(&combined_mle_value.coeffs, &mut transcription_buf);
226 });
227
228 Ok((
229 Proof {
230 combined_mle_values,
231 },
232 ProverState { evaluation_point },
233 ))
234 }
235
236 #[allow(clippy::type_complexity)]
237 fn prove_combined<F>(
238 transcript: &mut impl Transcript,
239 trace_matrix: &RowMajorTrace<F>,
240 projected_scalars: &HashMap<U::Scalar, DynamicPolynomialF<F>>,
241 num_constraints: usize,
242 num_vars: usize,
243 field_cfg: &F::Config,
244 ) -> Result<(Proof<F>, ProverState<F>), IdealCheckError<F, U::Ideal>>
245 where
246 F: InnerTransparentField,
247 F::Inner: ConstTranscribable,
248 F::Modulus: ConstTranscribable,
249 {
250 let ideal_collector = collect_ideals::<U>(num_constraints);
253 let non_zero_indices: Vec<usize> = ideal_collector
254 .ideals
255 .iter()
256 .enumerate()
257 .filter(|(_, i)| !i.is_zero_ideal())
258 .map(|(idx, _)| idx)
259 .collect();
260
261 let evaluation_point = transcript.get_field_challenges(num_vars, field_cfg);
262
263 let mut combined_mle_values = vec![DynamicPolynomialF::ZERO; num_constraints];
264 if !non_zero_indices.is_empty() {
265 let computed = combined_poly_builder::evaluate_for_constraints::<_, U>(
266 trace_matrix,
267 projected_scalars,
268 num_constraints,
269 field_cfg,
270 &non_zero_indices,
271 &evaluation_point,
272 )?;
273
274 for (&ci, val) in non_zero_indices.iter().zip(computed) {
275 combined_mle_values[ci] = val;
276 }
277 };
278
279 let mut transcription_buf: Vec<u8> = vec![0; F::Inner::NUM_BYTES];
280
281 combined_mle_values.iter().for_each(|combined_mle_value| {
282 transcript
283 .absorb_random_field_slice(&combined_mle_value.coeffs, &mut transcription_buf);
284 });
285
286 Ok((
287 Proof {
288 combined_mle_values,
289 },
290 ProverState { evaluation_point },
291 ))
292 }
293
294 fn verify_as_subprotocol<F, IdealOverF, IdealOverFFromRef>(
295 transcript: &mut impl Transcript,
296 proof: Proof<F>,
297 num_constraints: usize,
298 num_vars: usize,
299 ideal_over_f_from_ref: IdealOverFFromRef,
300 field_cfg: &F::Config,
301 ) -> Result<VerifierSubclaim<F>, IdealCheckError<F, IdealOverF>>
302 where
303 F: InnerTransparentField,
304 F::Inner: ConstTranscribable,
305 F::Modulus: ConstTranscribable,
306 IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<F>>,
307 IdealOverFFromRef: Fn(&IdealOrZero<U::Ideal>) -> IdealOverF,
308 {
309 let mut transcription_buf: Vec<u8> = vec![0; F::Inner::NUM_BYTES];
310
311 let combined_mle_values = proof.combined_mle_values;
312
313 let evaluation_point = transcript.get_field_challenges(num_vars, field_cfg);
314
315 for mle_value in &combined_mle_values {
316 transcript.absorb_random_field_slice(&mle_value.coeffs, &mut transcription_buf);
317 }
318
319 let ideal_collector = collect_ideals::<U>(num_constraints);
320
321 let (non_trivial_ideals, non_trivial_values): (Vec<_>, Vec<_>) = ideal_collector
327 .ideals
328 .iter()
329 .zip(combined_mle_values.iter())
330 .filter(|(ideal, _)| !ideal.is_zero_ideal())
331 .map(|(ideal, value)| (ideal_over_f_from_ref(ideal), value.clone()))
332 .unzip();
333
334 batched_ideal_check(&non_trivial_ideals, &non_trivial_values)?;
335
336 Ok(VerifierSubclaim {
337 evaluation_point,
338 values: combined_mle_values,
339 })
340 }
341}
342
343#[derive(Clone, Debug, Error)]
344pub enum IdealCheckError<F: PrimeField, I> {
345 #[error("ideal check prover failed to evaluate an mle: {0}")]
346 MleEvaluationError(#[from] EvaluationError),
347 #[error("mle evaluation ideal check failure: {0}")]
348 IdealCollectorError(#[from] BatchedIdealCheckError<DynamicPolynomialF<F>, I>),
349 #[error("`eq` polynomial construction failure: {0}")]
350 EqPolyConstructionError(#[from] PolyArithErrors),
351}
352
353#[cfg(test)]
354mod tests {
355 use crypto_primitives::{crypto_bigint_int::Int, crypto_bigint_monty::MontyField};
356
357 use rand::rng;
358 use zinc_poly::univariate::{dense::DensePolynomial, dynamic::over_field::DynamicPolynomialF};
359 use zinc_test_uair::{
360 GenerateRandomTrace, TestUairNoMultiplication, TestUairSimpleMultiplication,
361 };
362 use zinc_transcript::Blake3Transcript;
363 use zinc_uair::{
364 constraint_counter::count_constraints,
365 ideal::{DegreeOneIdeal, Ideal, IdealCheck},
366 };
367
368 use crate::test_utils::{
369 LIMBS, run_ideal_check_prover_combined, run_ideal_check_prover_linear, test_config,
370 };
371
372 use super::*;
373
374 fn do_test<U, IdealOverF, IdealOverFFromRef, const DEGREE_PLUS_ONE: usize>(
379 num_vars: usize,
380 ideal_over_f_from_ref: IdealOverFFromRef,
381 ) where
382 U: Uair<Scalar = DensePolynomial<Int<5>, DEGREE_PLUS_ONE>>
383 + GenerateRandomTrace<DEGREE_PLUS_ONE, PolyCoeff = Int<5>, Int = Int<5>>
384 + IdealCheckProtocol,
385 IdealOverF: Ideal + IdealCheck<DynamicPolynomialF<MontyField<LIMBS>>>,
386 IdealOverFFromRef: Fn(&IdealOrZero<U::Ideal>) -> IdealOverF + Copy,
387 {
388 let mut rng = rng();
389
390 {
392 let transcript = Blake3Transcript::new();
393 let (proof, prover_state, ..) = run_ideal_check_prover_combined::<U, DEGREE_PLUS_ONE>(
394 num_vars,
395 &U::generate_random_trace(num_vars, &mut rng),
396 &mut transcript.clone(),
397 );
398
399 let num_constraints = count_constraints::<U>();
400
401 let verifier_result = U::verify_as_subprotocol(
402 &mut transcript.clone(),
403 proof,
404 num_constraints,
405 num_vars,
406 ideal_over_f_from_ref,
407 &test_config(),
408 )
409 .expect("Verification failed");
410
411 assert_eq!(
412 prover_state.evaluation_point,
413 verifier_result.evaluation_point
414 );
415 }
416
417 {
419 let transcript = Blake3Transcript::new();
420 let (proof, prover_state, ..) = run_ideal_check_prover_linear::<U, DEGREE_PLUS_ONE>(
421 num_vars,
422 &U::generate_random_trace(num_vars, &mut rng),
423 &mut transcript.clone(),
424 );
425
426 let num_constraints = count_constraints::<U>();
427
428 let verifier_result = U::verify_as_subprotocol(
429 &mut transcript.clone(),
430 proof,
431 num_constraints,
432 num_vars,
433 ideal_over_f_from_ref,
434 &test_config(),
435 )
436 .expect("Verification failed");
437
438 assert_eq!(
439 prover_state.evaluation_point,
440 verifier_result.evaluation_point
441 );
442 }
443 }
444
445 #[test]
446 fn test_successful_verification() {
447 let field_cfg = test_config();
448
449 let num_vars = 2;
450
451 do_test::<TestUairNoMultiplication<Int<5>>, _, _, 32>(num_vars, |ideal_over_ring| {
453 ideal_over_ring.map(|i| DegreeOneIdeal::from_with_cfg(i, &field_cfg))
454 });
455
456 do_test::<TestUairSimpleMultiplication<Int<5>>, _, _, 32>(num_vars, |_ideal_over_ring| {
458 IdealOrZero::<DegreeOneIdeal<_>>::zero()
459 });
460 }
461}