1use std::slice::from_ref;
2
3use crate::{
4 ZipError,
5 code::LinearCode,
6 merkle::MerkleTree,
7 pcs::{
8 structs::{ZipPlus, ZipPlusCommitment, ZipPlusHint, ZipPlusParams, ZipTypes},
9 utils::validate_input,
10 },
11};
12use crypto_primitives::DenseRowMatrix;
13use uninit::out_ref::Out;
14use zinc_utils::{cfg_chunks, cfg_chunks_mut, cfg_iter};
15
16#[cfg(feature = "parallel")]
17use rayon::prelude::*;
18use zinc_poly::mle::DenseMultilinearExtension;
19
20impl<Zt: ZipTypes, Lc: LinearCode<Zt>> ZipPlus<Zt, Lc> {
21 #[allow(clippy::arithmetic_side_effects)]
61 pub fn commit(
62 pp: &ZipPlusParams<Zt, Lc>,
63 polys: &[DenseMultilinearExtension<Zt::Eval>],
64 ) -> Result<(ZipPlusHint<Zt::Cw>, ZipPlusCommitment), ZipError> {
65 assert!(
66 !polys.is_empty(),
67 "Batch must contain at least one polynomial"
68 );
69 let batch_size = polys.len();
70 let row_len = pp.linear_code.row_len();
71 validate_input::<Zt, Lc, bool>(
72 "commit",
73 pp.num_vars,
74 pp.linear_code.row_len(),
75 batch_size,
76 polys,
77 &[],
78 )?;
79
80 let expected_num_evals = pp.num_rows * row_len;
81 let cw_matrices: Vec<DenseRowMatrix<Zt::Cw>> = cfg_iter!(polys).map(|poly| {
82 assert_eq!(
83 poly.len(),
84 expected_num_evals,
85 "Polynomial has an incorrect number of evaluations ({}) for the expected matrix size ({})",
86 poly.len(),
87 expected_num_evals
88 );
89
90 Self::encode_rows(pp, poly)
91 }).collect();
92
93 let all_rows: Vec<&[Zt::Cw]> = cw_matrices.iter().flat_map(|m| m.as_rows()).collect();
94 let merkle_tree = MerkleTree::new(&all_rows);
95 let root = merkle_tree.root();
96
97 Ok((
98 ZipPlusHint::new(cw_matrices, merkle_tree),
99 ZipPlusCommitment { root, batch_size },
100 ))
101 }
102
103 #[allow(dead_code)]
119 pub fn commit_no_merkle(
120 pp: &ZipPlusParams<Zt, Lc>,
121 poly: &DenseMultilinearExtension<Zt::Eval>,
122 ) -> Result<DenseRowMatrix<Zt::Cw>, ZipError> {
123 validate_input::<Zt, Lc, bool>(
124 "commit",
125 pp.num_vars,
126 pp.linear_code.row_len(),
127 1,
128 from_ref(poly),
129 &[],
130 )?;
131
132 Ok(Self::encode_rows(pp, poly))
133 }
134
135 pub fn commit_single(
136 pp: &ZipPlusParams<Zt, Lc>,
137 poly: &DenseMultilinearExtension<Zt::Eval>,
138 ) -> Result<(ZipPlusHint<Zt::Cw>, ZipPlusCommitment), ZipError> {
139 Self::commit(pp, std::slice::from_ref(poly))
140 }
141
142 #[allow(clippy::arithmetic_side_effects)]
159 pub fn encode_rows(pp: &ZipPlusParams<Zt, Lc>, evals: &[Zt::Eval]) -> DenseRowMatrix<Zt::Cw> {
160 let row_len = pp.linear_code.row_len();
161 let codeword_len = pp.linear_code.codeword_len();
162
163 let mut encoded_matrix = DenseRowMatrix::<Zt::Cw>::uninit(pp.num_rows, codeword_len);
166
167 cfg_chunks_mut!(encoded_matrix.data, codeword_len)
168 .zip(cfg_chunks!(evals, row_len))
169 .for_each(|(row, evals)| {
170 let encoded: Vec<Zt::Cw> = pp.linear_code.encode(evals);
171 Out::from(row).copy_from_slice(encoded.as_slice());
172 });
173
174 unsafe { encoded_matrix.init() }
176 }
177}
178
179#[cfg(test)]
181#[allow(
182 clippy::arithmetic_side_effects,
183 clippy::cast_possible_truncation,
184 clippy::cast_possible_wrap
185)]
186mod tests {
187 use crate::{
188 code::{LinearCode, iprs::IprsCode},
189 merkle::{MerkleTree, MtHash},
190 pcs::{
191 structs::{ZipPlus, ZipPlusParams, ZipTypes},
192 test_utils::*,
193 },
194 pcs_transcript::PcsProverTranscript,
195 };
196 use crypto_bigint::{Random, U64, U256, Word};
197 use crypto_primitives::{
198 Matrix, boolean::Boolean, crypto_bigint_boxed_monty::BoxedMontyField,
199 crypto_bigint_int::Int,
200 };
201 use itertools::Itertools;
202 use num_traits::Zero;
203 use rand::{Rng, rng};
204 use std::sync::LazyLock;
205 use zinc_poly::{mle::DenseMultilinearExtension, univariate::binary::BinaryPoly};
206 use zinc_utils::CHECKED;
207
208 const INT_LIMBS: usize = U64::LIMBS;
209
210 const N: usize = INT_LIMBS;
211 const K: usize = INT_LIMBS * 4;
212 const M: usize = INT_LIMBS * 8;
213 const DEGREE_PLUS_ONE: usize = 3;
214
215 type Zt = TestZipTypes<N, K, M>;
216 type C = IprsCode<Zt, TestIprsConfig, REP_FACTOR, CHECKED>;
217 static C: LazyLock<C> = LazyLock::new(|| C::new(IPRS_ROW_LEN, IPRS_DEPTH).unwrap());
218
219 type PolyZt = TestBinPolyZipTypes<K, M, DEGREE_PLUS_ONE>;
220 type PolyC = IprsCode<PolyZt, TestIprsConfig, REP_FACTOR, CHECKED>;
221 static POLY_C: LazyLock<PolyC> =
222 LazyLock::new(|| PolyC::new(IPRS_ROW_LEN, IPRS_DEPTH).unwrap());
223
224 type TestZip = ZipPlus<Zt, C>;
225 type TestPolyZip = ZipPlus<PolyZt, PolyC>;
226
227 #[test]
228 fn commit_rejects_too_many_variables() {
229 let num_vars = 10;
230 let (pp, _) = setup_test_params(num_vars);
231
232 let poly: DenseMultilinearExtension<_> =
234 (1..=(1 << (num_vars + 1))).map(Int::from).collect();
235
236 let result = TestZip::commit_single(&pp, &poly);
237 assert!(result.is_err());
238 }
239
240 #[test]
241 fn commit_is_deterministic() {
242 let num_vars = 10;
243 let (pp, poly) = setup_test_params(num_vars);
244
245 let result1 = TestZip::commit_single(&pp, &poly).unwrap();
246 let result2 = TestZip::commit_single(&pp, &poly).unwrap();
247
248 assert_eq!(result1.1.root, result2.1.root);
249 }
250
251 #[test]
252 fn different_polynomials_produce_different_commitments() {
253 let num_vars = 10;
254 let (pp, _) = setup_test_params(num_vars);
255 let poly_size = 1 << num_vars;
256
257 let poly1 = DenseMultilinearExtension::from_evaluations_vec(
258 num_vars,
259 vec![Int::from(1); poly_size],
260 Zero::zero(),
261 );
262 let poly2 = DenseMultilinearExtension::from_evaluations_vec(
263 num_vars,
264 vec![Int::from(2); poly_size],
265 Zero::zero(),
266 );
267
268 let (_, commitment1) = TestZip::commit_single(&pp, &poly1).unwrap();
269 let (_, commitment2) = TestZip::commit_single(&pp, &poly2).unwrap();
270
271 assert_ne!(commitment1.root, commitment2.root);
272 }
273
274 #[test]
275 fn commit_succeeds_for_small_polynomial() {
276 let num_vars = 4;
277 let num_rows = (1_usize << num_vars).div_ceil(C.row_len());
278 let pp = ZipPlusParams::new(num_vars, num_rows, C.clone());
279
280 let evaluations = vec![Int::from(42); 1 << num_vars];
281 let mut poly =
282 DenseMultilinearExtension::from_evaluations_vec(num_vars, evaluations, Zero::zero());
283 poly.evaluations
285 .resize(pp.num_rows * pp.linear_code.row_len(), Zero::zero());
286
287 let result = TestZip::commit_single(&pp, &poly);
288 assert!(result.is_ok());
289 }
290
291 #[test]
292 fn commit_succeeds_for_two_variables() {
293 let num_vars = 2;
294 let num_rows = (1_usize << num_vars).div_ceil(C.row_len());
295 let pp = ZipPlusParams::new(num_vars, num_rows, C.clone());
296
297 let poly_size = 1 << num_vars;
298 let evaluations: Vec<_> = (1..=poly_size).map(Int::from).collect();
299 let mut poly =
300 DenseMultilinearExtension::from_evaluations_vec(num_vars, evaluations, Zero::zero());
301 poly.evaluations
303 .resize(pp.num_rows * pp.linear_code.row_len(), Zero::zero());
304
305 let result = TestZip::commit_single(&pp, &poly);
306 assert!(result.is_ok());
307 }
308
309 #[test]
310 fn batch_commit_produces_different_root_than_individual_commits() {
311 let num_vars = 10;
312 let (pp, _) = setup_test_params(num_vars);
313 let poly_size = 1 << num_vars;
314
315 let poly1: DenseMultilinearExtension<_> = (1..=poly_size).map(Int::from).collect();
316 let poly2: DenseMultilinearExtension<_> =
317 (poly_size + 1..=2 * poly_size).map(Int::from).collect();
318
319 let (_, batched_comm) = TestZip::commit(&pp, &[poly1.clone(), poly2.clone()]).unwrap();
320 let (_, comm1) = TestZip::commit_single(&pp, &poly1).unwrap();
321 let (_, comm2) = TestZip::commit_single(&pp, &poly2).unwrap();
322
323 assert_ne!(batched_comm.root, comm1.root);
324 assert_ne!(batched_comm.root, comm2.root);
325 }
326
327 #[test]
328 fn batch_commit_is_deterministic() {
329 let num_vars = 10;
330 let (pp, _) = setup_test_params(num_vars);
331 let poly_size = 1 << num_vars;
332
333 let poly1: DenseMultilinearExtension<_> = (1..=poly_size).map(Int::from).collect();
334 let poly2: DenseMultilinearExtension<_> =
335 (poly_size + 1..=2 * poly_size).map(Int::from).collect();
336
337 let (_, comm_a) = TestZip::commit(&pp, &[poly1.clone(), poly2.clone()]).unwrap();
338 let (_, comm_b) = TestZip::commit(&pp, &[poly1, poly2]).unwrap();
339
340 assert_eq!(comm_a.root, comm_b.root);
341 }
342
343 #[test]
344 fn batch_commit_batch_size_is_correct() {
345 let num_vars = 10;
346 let (pp, _) = setup_test_params(num_vars);
347 let poly_size = 1 << num_vars;
348
349 let polys: Vec<DenseMultilinearExtension<_>> = (0..5)
350 .map(|offset| {
351 let start = offset * poly_size + 1;
352 (start..start + poly_size).map(Int::from).collect()
353 })
354 .collect();
355
356 let (hint, comm) = TestZip::commit(&pp, &polys).unwrap();
357 assert_eq!(comm.batch_size, 5);
358 assert_eq!(hint.cw_matrices.len(), 5);
359 }
360
361 #[test]
362 fn encode_rows_produces_correct_size() {
363 let num_vars = 10;
364 let (pp, poly) = setup_test_params(num_vars);
365 let encoded = TestZip::encode_rows(&pp, &poly);
366
367 assert_eq!(encoded.num_rows, pp.num_rows);
368 assert_eq!(encoded.num_cols, pp.linear_code.codeword_len());
369 }
370
371 #[test]
374 fn encoded_rows_match_linear_code_definition() {
375 let num_vars = 10;
376 let (pp, poly) = setup_test_params(num_vars);
377 let encoded = TestZip::encode_rows(&pp, &poly);
378
379 for (i, row_chunk) in encoded.as_rows().enumerate() {
380 let start = i * pp.linear_code.row_len();
381 let end = start + pp.linear_code.row_len();
382 let row_evals = &poly[start..end];
383 let expected_encoding = pp.linear_code.encode(row_evals);
384 assert_eq!(
385 row_chunk,
386 expected_encoding.as_slice(),
387 "Row {i} encoding mismatch",
388 );
389 }
390 }
391
392 #[test]
395 fn corrupted_encoding_changes_merkle_root() {
396 let num_vars = 10;
397 let (pp, poly) = setup_test_params(num_vars);
398 let (data, commitment) = TestZip::commit_single(&pp, &poly).unwrap();
399
400 assert!(!data.cw_matrices[0].is_empty());
401 let mut cw_matrix = data.cw_matrices[0].to_rows();
402 cw_matrix[0][0] = Int::from(999999);
403 let corrupted_row = cw_matrix[0].clone();
404 let new_tree = MerkleTree::new(&[corrupted_row.as_slice()]);
405 assert_ne!(
406 new_tree.root(),
407 commitment.root,
408 "Corruption should change Merkle root"
409 );
410 }
411
412 #[test]
413 fn batch_commit_single_poly_matches_single_commit() {
414 let num_vars = 10;
415 let (pp, poly) = setup_test_params(num_vars);
416
417 let polys = std::slice::from_ref(&poly);
418 let (batched_hint, batched_comm) = TestZip::commit(&pp, polys).unwrap();
419 let (single_hint, single_comm) = TestZip::commit_single(&pp, &poly).unwrap();
420
421 assert_eq!(batched_comm.root, single_comm.root);
422 assert_eq!(batched_hint.cw_matrices.len(), 1);
423 assert_eq!(batched_hint.cw_matrices[0], single_hint.cw_matrices[0]);
424 }
425
426 #[test]
427 fn encoded_rows_are_nonzero_for_nonzero_input() {
428 let num_vars = 10;
429 let (pp, poly) = setup_test_params(num_vars);
430 let encoded = TestZip::encode_rows(&pp, &poly.evaluations);
431
432 assert_eq!(encoded.num_rows, pp.num_rows);
433 assert_eq!(encoded.num_cols, pp.linear_code.codeword_len());
434
435 let non_zero_count = encoded.as_rows().flatten().filter(|x| !x.is_zero()).count();
436 assert!(non_zero_count > 0);
437 }
438
439 #[test]
440 fn commit_produces_correct_merkle_tree_count() {
441 let num_vars = 10;
442 let (pp, poly) = setup_test_params(num_vars);
443 let (hint, _) = TestZip::commit_single(&pp, &poly).unwrap();
444
445 assert_eq!(hint.cw_matrices[0].num_rows, pp.num_rows);
446 assert_eq!(hint.cw_matrices[0].num_cols, pp.linear_code.codeword_len());
447 }
448
449 #[test]
450 #[cfg(feature = "parallel")]
451 fn encoding_is_consistent_across_threads() {
452 use rayon::prelude::*;
453
454 let num_vars = 10;
455 let poly_size = 1 << num_vars;
456 let evaluations = (1..=poly_size).map(|v| Int::from(v as i32)).collect();
457 let poly =
458 DenseMultilinearExtension::from_evaluations_vec(num_vars, evaluations, Zero::zero());
459
460 let results: Vec<Vec<Vec<Int<4>>>> = (0..10)
461 .into_par_iter()
462 .map(|_| {
463 let row_len = 1 << (num_vars / 2);
464 let pp = ZipPlusParams::new(num_vars, poly_size / row_len, C.clone());
465
466 let rows = TestZip::encode_rows(&pp, &poly.evaluations);
467 let rows: Vec<Vec<_>> = rows
468 .data
469 .chunks_exact(pp.linear_code.codeword_len())
470 .map(|chunk| chunk.to_vec())
471 .collect();
472 rows
473 })
474 .collect();
475
476 for w in results.windows(2) {
477 assert_eq!(
478 w[0], w[1],
479 "Parallel encoding runs produced inconsistent results: {:?} vs {:?}",
480 w[0], w[1]
481 );
482 }
483 }
484
485 #[test]
486 fn commit_succeeds_for_zero_polynomial() {
487 let num_vars = 10;
488 let (pp, _) = setup_test_params(num_vars);
489 let poly_size = 1 << num_vars;
490 let zero_poly = DenseMultilinearExtension::from_evaluations_vec(
491 num_vars,
492 vec![Zero::zero(); poly_size],
493 Zero::zero(),
494 );
495 let result = TestZip::commit_single(&pp, &zero_poly);
496 assert!(result.is_ok());
497 }
498
499 #[test]
500 fn commit_succeeds_for_alternating_values() {
501 let num_vars = 10;
502 let (pp, _) = setup_test_params(num_vars);
503 let poly_size = 1 << num_vars;
504 let alternating = (0..poly_size)
505 .map(|i| Int::from(if i % 2 == 0 { 1 } else { -1 }))
506 .collect();
507 let poly =
508 DenseMultilinearExtension::from_evaluations_vec(num_vars, alternating, Zero::zero());
509 let result = TestZip::commit_single(&pp, &poly);
510 assert!(result.is_ok());
511 }
512
513 #[test]
514 #[should_panic(expected = "Batch must contain at least one polynomial")]
515 fn batch_commit_on_empty_slice_panics() {
516 let num_vars = 10;
517 let (pp, _) = setup_test_params(num_vars);
518 let empty_polys: Vec<DenseMultilinearExtension<Int<INT_LIMBS>>> = vec![];
519 let _ = TestZip::commit(&pp, &empty_polys);
520 }
521
522 #[test]
523 fn encode_rows_succeeds_for_single_row() {
524 let num_vars = 10;
525 let poly_size = 1 << num_vars;
526 let pp = ZipPlusParams::new(num_vars, 1, C.clone());
527
528 let evaluations = vec![Int::from(5); poly_size];
530 let poly =
531 DenseMultilinearExtension::from_evaluations_vec(num_vars, evaluations, Zero::zero());
532 let encoded = TestZip::encode_rows(&pp, &poly.evaluations);
533 assert_eq!(encoded.num_rows, 1);
534 assert_eq!(encoded.num_cols, pp.linear_code.codeword_len());
535 }
536
537 #[test]
538 fn encode_rows_succeeds_for_single_poly_row() {
539 let num_vars = 10;
540 let pp = ZipPlusParams::new(num_vars, 1, POLY_C.clone());
541
542 let evaluations = vec![
544 BinaryPoly::new(vec![Boolean::FALSE, Boolean::FALSE]),
545 BinaryPoly::new(vec![Boolean::FALSE, Boolean::TRUE]),
546 BinaryPoly::new(vec![Boolean::TRUE, Boolean::FALSE]),
547 ];
548 let poly =
549 DenseMultilinearExtension::from_evaluations_vec(num_vars, evaluations, Zero::zero());
550 let encoded = TestPolyZip::encode_rows(&pp, &poly.evaluations);
551 assert_eq!(encoded.num_rows, 1);
552 assert_eq!(encoded.num_cols, pp.linear_code.codeword_len());
553 }
554
555 #[test]
556 fn matrix_dimensions_are_invariant() {
557 let test_cases = vec![(8, 1), (10, 4), (12, 16)];
558 for (num_vars, expected_rows) in test_cases {
559 let (pp, poly) = setup_test_params(num_vars);
560 assert_eq!(pp.num_rows, expected_rows);
561 let result = TestZip::commit_single(&pp, &poly);
562 assert!(result.is_ok());
563 }
564 }
565
566 #[test]
567 #[should_panic]
568 fn reject_incompatible_dimensions() {
569 let num_vars = 10;
570 let (pp, poly) = setup_test_params(num_vars);
571 let incompatible_pp = ZipPlusParams::new(8, 8, pp.linear_code);
572 TestZip::commit_single(&incompatible_pp, &poly).unwrap();
573 }
574
575 #[test]
576 fn linear_code_preserves_linearity() {
577 let num_vars = 10;
578 let (pp, poly) = setup_test_params(num_vars);
579 let encoded = TestZip::encode_rows(&pp, &poly.evaluations);
580 let row_len = pp.linear_code.row_len();
581 let codeword_len = pp.linear_code.codeword_len();
582 let row1_evals = &poly.evaluations[0..row_len];
583 let row2_evals = &poly.evaluations[row_len..2 * row_len];
584 let a = Int::from(3);
585 let b = Int::<4>::from(5);
586 let combined_evals: Vec<_> = (0..row_len)
587 .map(|i| a * row1_evals[i] + b.resize() * row2_evals[i])
588 .collect();
589 let combined_encoded = pp.linear_code.encode(&combined_evals);
590 let rows = encoded.as_rows().collect_vec();
591 let row1_encoded = rows[0];
592 let row2_encoded = rows[1];
593 let expected_combined: Vec<_> = (0..codeword_len)
594 .map(|i| a.resize() * row1_encoded[i] + b.resize() * row2_encoded[i])
595 .collect();
596 assert_eq!(combined_encoded, expected_combined);
597 }
598
599 #[test]
600 #[should_panic]
601 fn commit_panics_if_evaluations_not_multiple_of_row_len() {
602 let num_vars = 10;
603 let (pp, mut poly) = setup_test_params(num_vars);
604 poly.evaluations.truncate(15);
605 assert_eq!(poly.evaluations.len(), 15);
606 let _ = TestZip::commit_single(&pp, &poly);
607 }
608
609 #[test]
610 fn commit_with_many_variables() {
611 let num_vars = 16;
612 let (pp, poly) = setup_test_params(num_vars);
613 assert_eq!(pp.num_vars, num_vars);
614 let result = TestZip::commit_single(&pp, &poly);
615 assert!(result.is_ok());
616 }
617
618 #[test]
619 fn commit_with_smallest_matrix_arrangement() {
620 let num_vars = 8;
621 let (pp, poly) = setup_test_params(num_vars);
622 let poly_size = 1 << num_vars;
623 assert_eq!(pp.num_rows, 1);
624 assert_eq!(pp.linear_code.row_len(), poly_size);
625 let result = TestZip::commit_single(&pp, &poly);
626 assert!(result.is_ok());
627 }
628
629 #[test]
630 fn encode_rows_handles_large_integer_values() {
631 let num_vars = 10;
632 let (pp, _) = setup_test_params(num_vars);
633 let poly_size = 1 << num_vars;
634 let max_val = Int::<INT_LIMBS>::from(i64::MAX);
635 let poly = DenseMultilinearExtension::from_evaluations_vec(
636 num_vars,
637 vec![max_val; poly_size],
638 Zero::zero(),
639 );
640 let encoded_rows = TestZip::encode_rows(&pp, &poly.evaluations);
641 assert_eq!(encoded_rows.num_rows, pp.num_rows);
642 assert_eq!(encoded_rows.num_cols, pp.linear_code.codeword_len());
643 }
644
645 #[test]
646 #[should_panic(expected = "row_width.is_power_of_two()")]
647 fn merkle_tree_new_panics_on_non_power_of_two_leaves() {
648 let leaves_data: Vec<Int<INT_LIMBS>> = (0..7).map(Int::from).collect();
649 let _ = MerkleTree::new(&[leaves_data.as_slice()]);
650 }
651
652 fn make_poly_batch(
653 num_vars: usize,
654 batch_size: usize,
655 ) -> Vec<DenseMultilinearExtension<BinaryPoly<DEGREE_PLUS_ONE>>> {
656 let poly_size = 1 << num_vars;
657 let d = DEGREE_PLUS_ONE - 1;
658 (0..batch_size)
659 .map(|b| {
660 let coeffs: Vec<Boolean> = (0..poly_size * d)
661 .map(|i| ((i + b * 7) % 3 != 0).into())
662 .collect();
663 let evals = coeffs.chunks_exact(d).map(BinaryPoly::new).collect_vec();
664 DenseMultilinearExtension::from_evaluations_vec(num_vars, evals, Zero::zero())
665 })
666 .collect()
667 }
668
669 #[test]
670 fn batch_commit_poly_succeeds() {
671 let num_vars = 8;
672 let (pp, _) = setup_poly_test_params::<K, M, DEGREE_PLUS_ONE>(num_vars);
673 let polys = make_poly_batch(num_vars, 3);
674
675 let (hint, comm) = TestPolyZip::commit(&pp, &polys).unwrap();
676 assert_eq!(comm.batch_size, 3);
677 assert_eq!(hint.cw_matrices.len(), 3);
678 }
679
680 #[test]
681 fn batch_commit_poly_is_deterministic() {
682 let num_vars = 8;
683 let (pp, _) = setup_poly_test_params::<K, M, DEGREE_PLUS_ONE>(num_vars);
684 let polys = make_poly_batch(num_vars, 2);
685
686 let (_, comm_a) = TestPolyZip::commit(&pp, &polys).unwrap();
687 let (_, comm_b) = TestPolyZip::commit(&pp, &polys).unwrap();
688 assert_eq!(comm_a.root, comm_b.root);
689 }
690
691 #[test]
692 fn batch_commit_poly_single_matches_commit_single() {
693 let num_vars = 10;
694 let (pp, poly) = setup_poly_test_params::<K, M, DEGREE_PLUS_ONE>(num_vars);
695
696 let polys = std::slice::from_ref(&poly);
697 let (single_hint, single_comm) = TestPolyZip::commit_single(&pp, &poly).unwrap();
698 let (batched_hint, batched_comm) = TestPolyZip::commit(&pp, polys).unwrap();
699
700 assert_eq!(batched_comm.root, single_comm.root);
701 assert_eq!(batched_hint.cw_matrices.len(), 1);
702 assert_eq!(batched_hint.cw_matrices[0], single_hint.cw_matrices[0]);
703 }
704
705 #[test]
706 fn batch_commit_poly_different_polys_produce_different_roots() {
707 let num_vars = 10;
708 let (pp, _) = setup_poly_test_params::<K, M, DEGREE_PLUS_ONE>(num_vars);
709 let polys = make_poly_batch(num_vars, 2);
710
711 let (_, batched) = TestPolyZip::commit(&pp, &polys).unwrap();
712 let (_, single0) = TestPolyZip::commit_single(&pp, &polys[0]).unwrap();
713 let (_, single1) = TestPolyZip::commit_single(&pp, &polys[1]).unwrap();
714
715 assert_ne!(batched.root, single0.root);
716 assert_ne!(batched.root, single1.root);
717 }
718
719 #[test]
720 fn batch_commit_cw_matrices_are_distinct_per_poly() {
721 let num_vars = 10;
722 let (pp, _) = setup_test_params(num_vars);
723 let poly_size = 1 << num_vars;
724 let polys: Vec<DenseMultilinearExtension<_>> = vec![
725 (1..=poly_size).map(Int::from).collect(),
726 (poly_size + 1..=poly_size * 2).map(Int::from).collect(),
727 ];
728
729 let (hint, _) = TestZip::commit(&pp, &polys).unwrap();
730 assert_eq!(hint.cw_matrices.len(), 2);
731 assert_ne!(hint.cw_matrices[0], hint.cw_matrices[1]);
732 }
733
734 #[test]
735 fn proof_size_is_correct_for_parameters() {
736 use std::mem::size_of;
737
738 fn calculate_expected_proof_size_bytes(
739 pp: &ZipPlusParams<Zt, C>,
740 batch_size: usize,
741 ) -> usize {
742 let size_of_zt_k = K * size_of::<Word>();
744 let size_of_zt_m = M * size_of::<Word>();
746 let size_of_f = 2 * U256::LIMBS * size_of::<Word>();
748 let size_of_usize_field = size_of::<u64>();
749 let size_of_path_elem = size_of::<MtHash>();
750
751 let codeword_len = pp.linear_code.codeword_len();
752 let merkle_depth = codeword_len.next_power_of_two().ilog2() as usize;
753
754 let b_phase_size = batch_size * (1 + pp.num_rows * size_of_f);
756 let combined_row_size = pp.linear_code.row_len() * size_of_zt_m;
757
758 let column_values_size = batch_size * pp.num_rows * size_of_zt_k;
761 let single_merkle_proof_size =
762 size_of_usize_field * 3 + merkle_depth * size_of_path_elem;
763 let column_opening_phase_size =
764 Zt::NUM_COLUMN_OPENINGS * (column_values_size + single_merkle_proof_size);
765
766 b_phase_size + combined_row_size + column_opening_phase_size
767 }
768
769 type F = BoxedMontyField;
770
771 let mut rng = rng();
772 let num_vars = 10;
773 let poly_size: usize = 1 << num_vars;
774 let param = TestZip::setup(poly_size, C.clone());
775 let evaluations: Vec<_> = (0..poly_size)
776 .map(|_| <Zt as ZipTypes>::Eval::from(rng.random::<i8>()))
777 .collect();
778 let mle =
779 DenseMultilinearExtension::from_evaluations_slice(num_vars, &evaluations, Zero::zero());
780 let point: Vec<_> = (0..num_vars)
781 .map(|_| <Zt as ZipTypes>::Pt::random(&mut rng))
782 .collect();
783
784 let (hint, comm) = TestZip::commit_single(¶m, &mle).unwrap();
785 let mut transcript = PcsProverTranscript::new_from_commitment(&comm);
786 let field_cfg = get_field_cfg::<Zt, F>(&mut transcript.fs_transcript);
787
788 let _eval_f = TestZip::prove_single::<F, CHECKED>(
789 &mut transcript,
790 ¶m,
791 &mle,
792 &point,
793 &hint,
794 &field_cfg,
795 )
796 .unwrap();
797 let actual_proof_size_bytes = transcript.stream.get_ref().len();
798 let expected_proof_size_bytes = calculate_expected_proof_size_bytes(¶m, 1);
799 assert_eq!(actual_proof_size_bytes, expected_proof_size_bytes);
800 }
801}