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