1use crate::{
2 ZipError,
3 code::LinearCode,
4 pcs::{
5 structs::{ZipPlus, ZipPlusHint, ZipPlusParams, ZipTypes},
6 utils::{point_to_tensor, validate_input},
7 },
8 pcs_transcript::PcsProverTranscript,
9};
10use crypto_primitives::{BaseFieldConfig, ProjectElementWithConfig};
11use itertools::Itertools;
12use num_traits::{ConstOne, ConstZero, Zero};
13#[cfg(feature = "parallel")]
14use rayon::prelude::*;
15use zinc_poly::{Polynomial, mle::DenseMultilinearExtension};
16use zinc_transcript::traits::{ConstTranscribable, Transcript};
17use zinc_utils::{
18 UNCHECKED, cfg_chunks, cfg_iter, cfg_iter_mut,
19 inner_product::{FieldInnerProduct, InnerProduct},
20 mul_by_scalar::MulByScalar,
21};
22
23impl<Zt: ZipTypes, Lc: LinearCode<Zt>> ZipPlus<Zt, Lc> {
24 pub fn prove<C, const CHECK_FOR_OVERFLOW: bool>(
85 transcript: &mut PcsProverTranscript,
86 pp: &ZipPlusParams<Zt, Lc>,
87 polys: &[DenseMultilinearExtension<Zt::Eval>],
88 point: &[Zt::Pt],
89 commit_hint: &ZipPlusHint<Zt::Cw>,
90 field_cfg: &C,
91 ) -> Result<C::Element, ZipError>
92 where
93 C: BaseFieldConfig
94 + ProjectElementWithConfig<Zt::Pt>
95 + ProjectElementWithConfig<Zt::CombR>
96 + Sync,
97 C::Integer: ConstTranscribable,
98 Zt::CombR: MulByScalar<Zt::Chal>,
99 {
100 let point = point
101 .iter()
102 .map(|v| field_cfg.project(v))
103 .collect::<Vec<C::Element>>();
104 Self::prove_f::<C, CHECK_FOR_OVERFLOW>(
105 transcript,
106 pp,
107 polys,
108 &point,
109 commit_hint,
110 field_cfg,
111 )
112 }
113
114 #[allow(clippy::arithmetic_side_effects)]
117 pub fn prove_f<C, const CHECK_FOR_OVERFLOW: bool>(
118 transcript: &mut PcsProverTranscript,
119 pp: &ZipPlusParams<Zt, Lc>,
120 polys: &[DenseMultilinearExtension<Zt::Eval>],
121 point: &[C::Element],
122 commit_hint: &ZipPlusHint<Zt::Cw>,
123 field_cfg: &C,
124 ) -> Result<C::Element, ZipError>
125 where
126 C: BaseFieldConfig + ProjectElementWithConfig<Zt::CombR> + Sync,
127 C::Integer: ConstTranscribable,
128 Zt::CombR: MulByScalar<Zt::Chal>,
129 {
130 let batch_size = polys.len();
131 validate_input::<Zt, Lc, _>(
132 "prove",
133 pp.num_vars,
134 pp.linear_code.row_len(),
135 batch_size,
136 polys,
137 &[point],
138 )?;
139
140 let num_rows = pp.num_rows;
141 let row_len = pp.linear_code.row_len();
142
143 let (q_0, q_1) = point_to_tensor(field_cfg, point, num_rows)?;
147
148 let degree_bound = Zt::Comb::DEGREE_BOUND;
149 let polys_as_comb_r: Vec<Vec<Zt::CombR>> = polys
150 .iter()
151 .map(|poly| {
152 let alphas = if degree_bound.is_zero() {
153 vec![Zt::Chal::ONE]
154 } else {
155 transcript.fs_transcript.get_challenges(degree_bound + 1)
156 };
157
158 cfg_iter!(poly.evaluations)
159 .map(|eval| {
160 Zt::EvalDotChal::inner_product::<CHECK_FOR_OVERFLOW>(
161 &(),
162 eval,
163 &alphas,
164 Zt::CombR::ZERO,
165 )
166 .map_err(ZipError::from)
167 })
168 .collect()
169 })
170 .try_collect()?;
171
172 let zero_f = field_cfg.zero();
173
174 let b = {
176 let per_poly_b: Vec<Vec<C::Element>> = cfg_iter!(polys_as_comb_r)
177 .map(|poly_comb_r| {
178 cfg_chunks!(poly_comb_r, row_len)
179 .map(|row| {
180 FieldInnerProduct::inner_product::<UNCHECKED>(
181 field_cfg,
182 &q_1,
183 row,
184 zero_f.clone(),
185 )
186 })
187 .collect::<Result<Vec<C::Element>, _>>()
188 })
189 .collect::<Result<_, _>>()?;
190
191 let mut b = vec![zero_f.clone(); num_rows];
192 for poly_b in &per_poly_b {
193 b.iter_mut()
194 .zip(poly_b)
195 .for_each(|(a, d)| field_cfg.add_assign(a, d));
196 }
197 b
198 };
199
200 transcript.write_field_elements(field_cfg, &b)?;
201 let eval = q_0.iter().zip(&b).fold(zero_f, |mut acc, (l, r)| {
203 field_cfg.add_assign(&mut acc, &field_cfg.mul(l, r));
204 acc
205 });
206
207 let coeffs = if pp.num_rows == 1 {
213 vec![Zt::Chal::ONE]
214 } else {
215 transcript
216 .fs_transcript
217 .get_challenges::<Zt::Chal>(num_rows)
218 };
219
220 let combined_row: Vec<Zt::CombR> = {
221 let mut combined = vec![Zt::CombR::ZERO; row_len];
222 cfg_iter_mut!(combined).enumerate().try_for_each(
223 |(col, acc)| -> Result<(), ZipError> {
224 for poly_comb_r in &polys_as_comb_r {
225 for (eval, coeff) in poly_comb_r
228 .iter()
229 .skip(col)
230 .step_by(row_len)
231 .zip(coeffs.iter())
232 {
233 let scaled: Zt::CombR = eval
234 .clone()
235 .mul_by_scalar::<CHECK_FOR_OVERFLOW>(coeff)
236 .expect("Cannot multiply evaluation by coefficient");
237 if CHECK_FOR_OVERFLOW {
238 *acc = zinc_utils::add!(
239 *acc,
240 &scaled,
241 "Addition overflow while combining rows across polys"
242 );
243 } else {
244 *acc += scaled;
245 }
246 }
247 }
248 Ok(())
249 },
250 )?;
251 combined
252 };
253
254 transcript.write_const_many(&combined_row)?;
255 for _ in 0..Zt::NUM_COLUMN_OPENINGS {
256 let column_idx = transcript.squeeze_challenge_idx(pp.linear_code.codeword_len());
257 Self::open_merkle_trees_for_column(transcript, commit_hint, column_idx)?;
258 }
259
260 Ok(eval)
261 }
262
263 #[inline(always)]
265 pub fn prove_single<C, const CHECK_FOR_OVERFLOW: bool>(
266 transcript: &mut PcsProverTranscript,
267 pp: &ZipPlusParams<Zt, Lc>,
268 poly: &DenseMultilinearExtension<Zt::Eval>,
269 point: &[Zt::Pt],
270 commit_hint: &ZipPlusHint<Zt::Cw>,
271 field_cfg: &C,
272 ) -> Result<C::Element, ZipError>
273 where
274 C: BaseFieldConfig
275 + ProjectElementWithConfig<Zt::Pt>
276 + ProjectElementWithConfig<Zt::CombR>
277 + Sync,
278 C::Integer: ConstTranscribable,
279 Zt::CombR: MulByScalar<Zt::Chal>,
280 {
281 Self::prove::<C, CHECK_FOR_OVERFLOW>(
282 transcript,
283 pp,
284 std::slice::from_ref(poly),
285 point,
286 commit_hint,
287 field_cfg,
288 )
289 }
290
291 pub(super) fn open_merkle_trees_for_column(
292 transcript: &mut PcsProverTranscript,
293 commit_hint: &ZipPlusHint<Zt::Cw>,
294 column_idx: usize,
295 ) -> Result<(), ZipError> {
296 for cw_matrix in &commit_hint.cw_matrices {
297 let column_values = cw_matrix.as_rows().map(|row| &row[column_idx]);
298 transcript.write_const_many_iter::<Zt::Cw, _>(column_values, cw_matrix.num_rows)?;
299 }
300
301 let merkle_proof = commit_hint
302 .merkle_tree
303 .prove(column_idx)
304 .map_err(|_| ZipError::InvalidPcsOpen("Failed to open merkle tree".into()))?;
305 transcript
306 .write_merkle_proof(&merkle_proof)
307 .map_err(|_| ZipError::InvalidPcsOpen("Failed to write a merkle tree proof".into()))?;
308
309 Ok(())
310 }
311}
312
313#[cfg(test)]
314#[allow(
315 clippy::arithmetic_side_effects,
316 clippy::cast_possible_truncation,
317 clippy::cast_possible_wrap
318)]
319mod tests {
320 use crate::{
321 code::iprs::IprsCode,
322 merkle::MerkleTree,
323 pcs::{
324 structs::{ZipPlus, ZipPlusHint},
325 test_utils::*,
326 },
327 pcs_transcript::PcsProverTranscript,
328 };
329 use crypto_primitives::{
330 FixedConfig, ProjectElementWithConfig, crypto_bigint_int::Int,
331 crypto_bigint_monty::MontyField, crypto_bigint_uint::U64,
332 };
333 use num_traits::ConstOne;
334 use zinc_poly::mle::DenseMultilinearExtension;
335 use zinc_utils::{CHECKED, from_ref::FromRef};
336
337 const INT_LIMBS: usize = U64::LIMBS;
338
339 const N: usize = INT_LIMBS;
340 const K: usize = INT_LIMBS * 4;
341 const M: usize = INT_LIMBS * 8;
342 const DEGREE_PLUS_ONE: usize = 3;
343
344 type Cfg = MontyField<K>;
345
346 type Zt = TestZipTypes<N, K, M>;
347 type C = IprsCode<Zt, TestIprsConfig, REP_FACTOR, CHECKED>;
348
349 type PolyZt = TestBinPolyZipTypes<K, M, DEGREE_PLUS_ONE>;
350 type PolyC = IprsCode<PolyZt, TestIprsConfig, REP_FACTOR, CHECKED>;
351
352 type TestZip = ZipPlus<Zt, C>;
353 type TestPolyZip = ZipPlus<PolyZt, PolyC>;
354
355 fn test_point(num_vars: usize) -> Vec<Int<INT_LIMBS>> {
356 (0..num_vars).map(|i| Int::from(i as i32 + 2)).collect()
357 }
358
359 #[test]
360 fn prove_succeeds_for_single_poly() {
361 let num_vars = 10;
362 let (pp, poly) = setup_test_params(num_vars);
363 let (hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
364 let point = test_point(num_vars);
365
366 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
367 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
368
369 let result = TestZip::prove_single::<Cfg, CHECKED>(
370 &mut transcript,
371 &pp,
372 &poly,
373 &point,
374 &hint,
375 &field_cfg,
376 );
377 assert!(result.is_ok());
378 }
379
380 #[test]
381 fn prove_succeeds_for_poly_type() {
382 let num_vars = 10;
383 let (pp, poly) = setup_poly_test_params(num_vars);
384 let (hint, comm) = TestPolyZip::commit_single(&pp, &poly).unwrap();
385 let point: Vec<i128> = (0..num_vars).map(|i| i as i128 + 2).collect();
386
387 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
388 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
389
390 let result = TestPolyZip::prove_single::<Cfg, CHECKED>(
391 &mut transcript,
392 &pp,
393 &poly,
394 &point,
395 &hint,
396 &field_cfg,
397 );
398 assert!(result.is_ok());
399 }
400
401 #[test]
402 fn prove_succeeds_with_corrupted_codeword() {
403 let num_vars = 10;
404 let (pp, poly) = setup_test_params(num_vars);
405 let (mut hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
406
407 {
408 let mut rows = hint.cw_matrices[0].to_rows_slices_mut();
409 assert!(!rows.is_empty());
410 rows[0][0] += Int::ONE;
411 }
412
413 let corrupted_tree = {
414 let all_rows: Vec<&[_]> = hint.cw_matrices.iter().flat_map(|m| m.as_rows()).collect();
415 MerkleTree::new(&all_rows)
416 };
417 let corrupted_hint = ZipPlusHint::new(hint.cw_matrices, corrupted_tree);
418
419 let point = test_point(num_vars);
420
421 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
422 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
423
424 let result = TestZip::prove_single::<Cfg, CHECKED>(
425 &mut transcript,
426 &pp,
427 &poly,
428 &point,
429 &corrupted_hint,
430 &field_cfg,
431 );
432 assert!(result.is_ok());
433 }
434
435 #[test]
436 fn prove_rejects_oversized_polynomial() {
437 let num_vars = 10;
438 let (pp, _) = setup_test_params(num_vars);
439 let oversized_poly: DenseMultilinearExtension<_> =
440 (0..1 << (num_vars + 1)).map(Int::from).collect();
441
442 let (hint, comm) =
443 TestZip::commit_single(&pp, &setup_test_params::<N, K, M>(num_vars).1).unwrap();
444
445 let point = test_point(num_vars);
446
447 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
448 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
449
450 let result = TestZip::prove_single::<Cfg, CHECKED>(
451 &mut transcript,
452 &pp,
453 &oversized_poly,
454 &point,
455 &hint,
456 &field_cfg,
457 );
458 assert!(result.is_err());
459 }
460
461 #[test]
464 fn prove_returns_correct_evaluation() {
465 let num_vars = 10;
466 let (pp, poly) = setup_test_params(num_vars);
467 let (hint, comm) = TestZip::commit_single(&pp, &poly).unwrap();
468 let point = test_point(num_vars);
469
470 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
471 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
472
473 let eval_f = TestZip::prove_single::<Cfg, CHECKED>(
474 &mut transcript,
475 &pp,
476 &poly,
477 &point,
478 &hint,
479 &field_cfg,
480 )
481 .unwrap();
482
483 let poly_wide: DenseMultilinearExtension<Int<M>> =
484 poly.evaluations.iter().map(Int::from_ref).collect();
485 let point_wide: Vec<Int<M>> = point.iter().map(Int::from_ref).collect();
486 let expected_int = poly_wide
487 .evaluate(&FixedConfig::default(), &point_wide)
488 .unwrap();
489 let expected_f = field_cfg.project(&expected_int);
490
491 assert_eq!(eval_f, expected_f);
492 }
493
494 fn make_batch_polys(
495 num_vars: usize,
496 batch_size: usize,
497 ) -> Vec<DenseMultilinearExtension<Int<INT_LIMBS>>> {
498 let poly_size = 1 << num_vars;
499 (0..batch_size)
500 .map(|b| {
501 let base = (b * poly_size) as i32;
502 (base + 1..=base + poly_size as i32)
503 .map(Int::from)
504 .collect()
505 })
506 .collect()
507 }
508
509 #[test]
510 fn prove_succeeds_for_batch() {
511 let num_vars = 10;
512 let (pp, _) = setup_test_params(num_vars);
513 let polys = make_batch_polys(num_vars, 2);
514
515 let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
516 let point = test_point(num_vars);
517
518 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
519 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
520
521 let result =
522 TestZip::prove::<Cfg, CHECKED>(&mut transcript, &pp, &polys, &point, &hint, &field_cfg);
523 assert!(result.is_ok())
524 }
525
526 #[test]
527 fn prove_succeeds_for_batch_5() {
528 let num_vars = 10;
529 let (pp, _) = setup_test_params(num_vars);
530 let polys = make_batch_polys(num_vars, 5);
531
532 let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
533 let point = test_point(num_vars);
534
535 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
536 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
537
538 let result =
539 TestZip::prove::<Cfg, CHECKED>(&mut transcript, &pp, &polys, &point, &hint, &field_cfg);
540 assert!(result.is_ok())
541 }
542
543 #[test]
544 fn prove_with_corrupted_codeword_for_batch() {
545 let num_vars = 10;
546 let (pp, _) = setup_test_params(num_vars);
547 let polys = make_batch_polys(num_vars, 2);
548
549 let (mut hint, comm) = TestZip::commit(&pp, &polys).unwrap();
550
551 hint.cw_matrices[0].to_rows_slices_mut()[0][0] += Int::ONE;
552
553 let corrupted_tree = {
554 let all_rows: Vec<&[_]> = hint.cw_matrices.iter().flat_map(|m| m.as_rows()).collect();
555 MerkleTree::new(&all_rows)
556 };
557 let corrupted_hint = ZipPlusHint::new(hint.cw_matrices, corrupted_tree);
558
559 let point = test_point(num_vars);
560
561 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
562 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
563
564 let result = TestZip::prove::<Cfg, CHECKED>(
565 &mut transcript,
566 &pp,
567 &polys,
568 &point,
569 &corrupted_hint,
570 &field_cfg,
571 );
572 assert!(result.is_ok());
573 }
574
575 #[test]
576 fn prove_rejects_oversized_polynomial_in_batch() {
577 let num_vars = 10;
578 let (pp, _) = setup_test_params(num_vars);
579 let oversized: DenseMultilinearExtension<_> = (0..1 << 5).map(Int::from).collect();
580 let normal: DenseMultilinearExtension<_> = (1..=16).map(Int::from).collect();
581 let polys = vec![normal, oversized];
582
583 let (hint, comm) = TestZip::commit(&pp, &make_batch_polys(num_vars, 2)).unwrap();
584
585 let point = test_point(num_vars);
586
587 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
588 let field_cfg = get_field_cfg::<Zt, Cfg>(&mut transcript.fs_transcript);
589
590 let result =
591 TestZip::prove::<Cfg, CHECKED>(&mut transcript, &pp, &polys, &point, &hint, &field_cfg);
592 assert!(result.is_err());
593 }
594}