1use crypto_primitives::{BaseFieldConfig, ProjectPrimitiveIntegersWithConfig, SetConfig};
17use itertools::Itertools;
18#[cfg(feature = "parallel")]
19use rayon::prelude::*;
20use std::marker::PhantomData;
21use zinc_poly::mle::DenseMultilinearExtension;
22use zinc_transcript::traits::{ConstTranscribable, GenTranscribable, Transcribable, Transcript};
23use zinc_utils::{add, cfg_into_iter, cfg_iter, cfg_iter_mut, mul};
24
25use crate::CombFn;
26
27use super::{
28 SumCheckError,
29 prover::{
30 NatEvaluatedPolyWithoutConstant, ProverMsg as SumcheckProverMsg,
31 ProverState as SumcheckProverState,
32 },
33 verifier::VerifierState,
34};
35
36pub struct Round1Output<F> {
45 pub asserted_sum: F,
46 pub tail_evaluations: Vec<F>,
47}
48
49pub trait Round1FastPath<C: SetConfig>: Send + Sync {
59 fn round_1_message(&self, config: &C) -> Round1Output<C::Element>;
62
63 fn fold_with_challenge(
68 self: Box<Self>,
69 challenge: &C::Element,
70 config: &C,
71 ) -> Vec<DenseMultilinearExtension<C::Element>>;
72}
73
74pub struct MultiDegreeSumcheckGroup<C: SetConfig> {
77 degree: usize,
78 poly: Vec<DenseMultilinearExtension<C::Element>>,
79 comb_fn: CombFn<C::Element>,
80 round_1_fast_path: Option<Box<dyn Round1FastPath<C>>>,
81}
82
83impl<C: SetConfig> MultiDegreeSumcheckGroup<C> {
84 pub fn new(
85 degree: usize,
86 poly: Vec<DenseMultilinearExtension<C::Element>>,
87 comb_fn: CombFn<C::Element>,
88 ) -> Self {
89 Self {
90 degree,
91 poly,
92 comb_fn,
93 round_1_fast_path: None,
94 }
95 }
96
97 pub fn new_with_fast_path(
98 degree: usize,
99 poly: Vec<DenseMultilinearExtension<C::Element>>,
100 comb_fn: CombFn<C::Element>,
101 round_1_fast: Box<dyn Round1FastPath<C>>,
102 ) -> Self {
103 Self {
104 degree,
105 poly,
106 comb_fn,
107 round_1_fast_path: Some(round_1_fast),
108 }
109 }
110}
111
112#[derive(Clone, Debug, PartialEq, Eq)]
117pub struct MultiDegreeSumcheckProof<F> {
118 group_messages: Vec<Vec<SumcheckProverMsg<F>>>,
120 claimed_sums: Vec<F>,
122 degrees: Vec<usize>,
124}
125
126impl<F> MultiDegreeSumcheckProof<F> {
127 pub fn try_map<T, E>(
131 &self,
132 f: impl FnMut(&F) -> Result<T, E> + Copy,
133 ) -> Result<MultiDegreeSumcheckProof<T>, E> {
134 Ok(MultiDegreeSumcheckProof {
135 group_messages: self
136 .group_messages
137 .iter()
138 .map(|msgs| msgs.iter().map(|m| m.try_map(f)).try_collect())
139 .try_collect()?,
140 claimed_sums: self.claimed_sums.iter().map(f).try_collect()?,
141 degrees: self.degrees.clone(),
142 })
143 }
144
145 pub fn claimed_sums(&self) -> &[F] {
148 &self.claimed_sums
149 }
150
151 #[cfg(test)]
152 pub fn group_messages_mut(&mut self) -> &mut [Vec<SumcheckProverMsg<F>>] {
153 &mut self.group_messages
154 }
155}
156
157impl<F: ConstTranscribable> GenTranscribable for MultiDegreeSumcheckProof<F> {
161 fn read_transcription_bytes_exact(bytes: &[u8]) -> Self {
162 let (num_groups, bytes) = u32::read_transcription_bytes_subset(bytes);
163 let num_groups = usize::try_from(num_groups).expect("group count must fit into usize");
164
165 let (num_vars, mut bytes) = u32::read_transcription_bytes_subset(bytes);
166 let num_vars = usize::try_from(num_vars).expect("num_vars must fit into usize");
167
168 let mut degrees = Vec::with_capacity(num_groups);
169 for _ in 0..num_groups {
170 let (deg, rest) = u32::read_transcription_bytes_subset(bytes);
171 degrees.push(usize::try_from(deg).expect("degree must fit into usize"));
172 bytes = rest;
173 }
174
175 let mut group_messages = Vec::with_capacity(num_groups);
176 for ° in °rees {
177 let msg_bytes = mul!(deg, F::NUM_BYTES);
178 let mut msgs = Vec::with_capacity(num_vars);
179 for _ in 0..num_vars {
180 let tail_evaluations: Vec<F> =
181 Vec::read_transcription_bytes_exact(&bytes[..msg_bytes]);
182 msgs.push(SumcheckProverMsg(NatEvaluatedPolyWithoutConstant {
183 tail_evaluations,
184 }));
185 bytes = &bytes[msg_bytes..];
186 }
187 group_messages.push(msgs);
188 }
189
190 let mut claimed_sums = Vec::with_capacity(num_groups);
191 for _ in 0..num_groups {
192 let cs = F::read_transcription_bytes_exact(&bytes[..F::NUM_BYTES]);
193 claimed_sums.push(cs);
194 bytes = &bytes[F::NUM_BYTES..];
195 }
196
197 Self {
198 group_messages,
199 claimed_sums,
200 degrees,
201 }
202 }
203
204 fn write_transcription_bytes_exact(&self, mut buf: &mut [u8]) {
205 let num_groups =
206 u32::try_from(self.group_messages.len()).expect("num groups must fit into u32");
207 num_groups.write_transcription_bytes_exact(&mut buf[..u32::NUM_BYTES]);
208 buf = &mut buf[u32::NUM_BYTES..];
209
210 let num_vars =
212 u32::try_from(self.group_messages[0].len()).expect("num_vars must fit into u32");
213 num_vars.write_transcription_bytes_exact(&mut buf[..u32::NUM_BYTES]);
214 buf = &mut buf[u32::NUM_BYTES..];
215
216 for ° in &self.degrees {
217 let deg = u32::try_from(deg).expect("degree must fit into u32");
218 deg.write_transcription_bytes_exact(&mut buf[..u32::NUM_BYTES]);
219 buf = &mut buf[u32::NUM_BYTES..];
220 }
221
222 for group in &self.group_messages {
223 for msg in group {
224 let evals = &msg.0.tail_evaluations;
225 let end = mul!(evals.len(), F::NUM_BYTES);
226 evals.write_transcription_bytes_exact(&mut buf[..end]);
227 buf = &mut buf[end..];
228 }
229 }
230
231 for cs in &self.claimed_sums {
232 cs.write_transcription_bytes_exact(&mut buf[..F::NUM_BYTES]);
233 buf = &mut buf[F::NUM_BYTES..];
234 }
235 }
236}
237
238impl<F: ConstTranscribable> Transcribable for MultiDegreeSumcheckProof<F> {
239 fn get_num_bytes(&self) -> usize {
240 let num_groups = self.group_messages.len();
241 let num_vars = self.group_messages[0].len();
242 let total_evals: usize = self.degrees.iter().map(|&d| mul!(d, num_vars)).sum();
244
245 let header = add!(u32::NUM_BYTES, u32::NUM_BYTES);
247 let degrees = mul!(num_groups, u32::NUM_BYTES);
248 let eval_data = mul!(total_evals, F::NUM_BYTES);
249 let claimed = mul!(num_groups, F::NUM_BYTES);
250
251 add!(header, add!(degrees, add!(eval_data, claimed)))
252 }
253}
254
255#[derive(Debug)]
257pub struct MultiDegreeSubClaims<F> {
258 point: Vec<F>,
259 expected_evaluations: Vec<F>,
260}
261
262impl<F> MultiDegreeSubClaims<F> {
263 pub fn point(&self) -> &[F] {
264 &self.point
265 }
266
267 pub fn expected_evaluations(&self) -> &[F] {
268 &self.expected_evaluations
269 }
270}
271
272pub struct MultiDegreeSumcheck<C>(PhantomData<C>);
277
278impl<C> MultiDegreeSumcheck<C>
279where
280 C: BaseFieldConfig + ProjectPrimitiveIntegersWithConfig,
281 C::Integer: ConstTranscribable,
282{
283 #[allow(clippy::type_complexity)]
334 pub fn prove_as_subprotocol(
335 transcript: &mut impl Transcript,
336 families: Vec<(Vec<MultiDegreeSumcheckGroup<C>>, &C)>,
337 num_vars: usize,
338 q_star_cfg: &C,
339 ) -> Vec<(
340 MultiDegreeSumcheckProof<C::Element>,
341 Vec<SumcheckProverState<C>>,
342 )> {
343 assert!(
344 num_vars > 0,
345 "Attempts to prove a constant: num_vars must be > 0"
346 );
347 assert!(!families.is_empty(), "need at least one family");
348 for (groups, _) in &families {
349 assert!(!groups.is_empty(), "every family needs at least one group");
350 }
351
352 let num_families = families.len();
353 let mut buf = vec![0; <C::Integer as ConstTranscribable>::NUM_BYTES];
354
355 let nvars_field = q_star_cfg.project(&(num_vars as u64));
358 let nfamilies_field = q_star_cfg.project(&(num_families as u64));
359 transcript.absorb_field_element(q_star_cfg, &nvars_field, &mut buf);
360 transcript.absorb_field_element(q_star_cfg, &nfamilies_field, &mut buf);
361
362 let mut per_family_group_messages: Vec<Vec<Vec<SumcheckProverMsg<C::Element>>>> =
364 Vec::with_capacity(num_families);
365 let mut per_family_claimed_sums: Vec<Vec<C::Element>> = Vec::with_capacity(num_families);
366 let mut per_family_prover_states: Vec<Vec<SumcheckProverState<C>>> =
367 Vec::with_capacity(num_families);
368 let mut per_family_comb_fns: Vec<Vec<CombFn<C::Element>>> =
369 Vec::with_capacity(num_families);
370 let mut per_family_fast_paths: Vec<Vec<Option<Box<dyn Round1FastPath<C>>>>> =
371 Vec::with_capacity(num_families);
372 let mut per_family_cfg: Vec<&C> = Vec::with_capacity(num_families);
373
374 for (groups, cfg) in families {
375 let num_groups = groups.len();
376 let ngroups_field = q_star_cfg.project(&(num_groups as u64));
377 transcript.absorb_field_element(q_star_cfg, &ngroups_field, &mut buf);
378
379 let group_messages: Vec<Vec<SumcheckProverMsg<C::Element>>> = (0..num_groups)
380 .map(|_| Vec::with_capacity(num_vars))
381 .collect();
382 let mut prover_states: Vec<SumcheckProverState<C>> = Vec::with_capacity(num_groups);
383 let mut comb_fns: Vec<CombFn<C::Element>> = Vec::with_capacity(num_groups);
384 let mut fast_paths: Vec<Option<Box<dyn Round1FastPath<C>>>> =
385 Vec::with_capacity(num_groups);
386
387 for group in groups {
388 let degree_field = q_star_cfg.project(&(group.degree as u64));
389 transcript.absorb_field_element(q_star_cfg, °ree_field, &mut buf);
390
391 prover_states.push(SumcheckProverState::new(group.poly, num_vars, group.degree));
392 comb_fns.push(group.comb_fn);
393 fast_paths.push(group.round_1_fast_path);
394 }
395
396 per_family_group_messages.push(group_messages);
397 per_family_claimed_sums.push(Vec::with_capacity(num_groups));
398 per_family_prover_states.push(prover_states);
399 per_family_comb_fns.push(comb_fns);
400 per_family_fast_paths.push(fast_paths);
401 per_family_cfg.push(cfg);
402 }
403
404 let mut per_family_verifier_msg: Vec<Option<C::Element>> = vec![None; num_families];
407
408 for round in 1..=num_vars {
409 for b in 0..num_families {
411 let cfg = per_family_cfg[b];
412 let verifier_msg = &per_family_verifier_msg[b];
413 let round_msgs: Vec<SumcheckProverMsg<C::Element>> =
414 cfg_iter_mut!(per_family_prover_states[b])
415 .zip(cfg_iter!(per_family_comb_fns[b]))
416 .zip(cfg_iter!(per_family_fast_paths[b]))
417 .map(|((state, comb_fn), fast_path)| {
418 if round == 1
419 && let Some(fast_path) = fast_path.as_ref()
420 {
421 let out = fast_path.round_1_message(cfg);
423 state.asserted_sum = Some(out.asserted_sum);
424 state.round = 1;
425 SumcheckProverMsg(NatEvaluatedPolyWithoutConstant::new(
426 out.tail_evaluations,
427 ))
428 } else {
429 state.prove_round(verifier_msg, comb_fn, cfg)
430 }
431 })
432 .collect();
433
434 for msg in &round_msgs {
435 transcript.absorb_field_element_slice(cfg, &msg.0.tail_evaluations, &mut buf);
436 }
437
438 for (j, msg) in round_msgs.into_iter().enumerate() {
439 per_family_group_messages[b][j].push(msg);
440 }
441 }
442
443 let shared_chal_q_star: C::Element = transcript.get_field_challenge(q_star_cfg);
445 transcript.absorb_field_element(q_star_cfg, &shared_chal_q_star, &mut buf);
446 let shared_chal_int = q_star_cfg.lift(&shared_chal_q_star);
447
448 for b in 0..num_families {
451 let chal_b = per_family_cfg[b].project(&shared_chal_int);
452 if round == 1 {
453 per_family_prover_states[b]
454 .iter_mut()
455 .zip(per_family_fast_paths[b].iter_mut())
456 .for_each(|(state, fp_slot)| {
457 if let Some(fp) = fp_slot.take() {
458 state.mles = fp.fold_with_challenge(&chal_b, per_family_cfg[b]);
459 state.skip_next_fold = true;
460 }
461 });
462 }
463 per_family_verifier_msg[b] = Some(chal_b);
464 }
465 }
466
467 cfg_into_iter!(per_family_prover_states)
469 .zip(cfg_into_iter!(per_family_group_messages))
470 .zip(cfg_into_iter!(per_family_claimed_sums))
471 .zip(cfg_into_iter!(per_family_verifier_msg))
472 .map(
473 |(((mut prover_states, group_messages), mut claimed_sums), last_chal)| {
474 prover_states.iter_mut().for_each(|state| {
475 let sum = state
476 .asserted_sum
477 .clone()
478 .expect("asserted sum should be recorded after the first prover round");
479 claimed_sums.push(sum);
480
481 if let Some(ref vmsg) = last_chal {
482 state.randomness.push(vmsg.clone());
483 }
484 });
485
486 let degrees = prover_states.iter().map(|s| s.max_degree).collect();
487 let proof = MultiDegreeSumcheckProof {
488 group_messages,
489 claimed_sums,
490 degrees,
491 };
492 (proof, prover_states)
493 },
494 )
495 .collect()
496 }
497
498 pub fn verify_as_subprotocol(
534 transcript: &mut impl Transcript,
535 num_vars: usize,
536 proofs: &[(&MultiDegreeSumcheckProof<C::Element>, &C)],
537 q_star_cfg: &C,
538 ) -> Result<Vec<MultiDegreeSubClaims<C::Element>>, SumCheckError<C::Element>> {
539 assert!(
540 num_vars > 0,
541 "Attempts to prove a constant: num_vars must be > 0"
542 );
543 assert!(!proofs.is_empty(), "need at least one family");
544
545 let num_families = proofs.len();
546 let mut buf = vec![0; <C::Integer as ConstTranscribable>::NUM_BYTES];
547
548 let nvars_field = q_star_cfg.project(&(num_vars as u64));
550 let nfamilies_field = q_star_cfg.project(&(num_families as u64));
551 transcript.absorb_field_element(q_star_cfg, &nvars_field, &mut buf);
552 transcript.absorb_field_element(q_star_cfg, &nfamilies_field, &mut buf);
553
554 let mut per_family_verifier_states: Vec<Vec<VerifierState<C>>> =
555 Vec::with_capacity(num_families);
556 for (proof, cfg) in proofs {
557 let num_groups = proof.degrees.len();
558 assert!(num_groups != 0, "every family needs at least one group");
559 let ngroups_field = q_star_cfg.project(&(num_groups as u64));
560 transcript.absorb_field_element(q_star_cfg, &ngroups_field, &mut buf);
561
562 let states: Vec<VerifierState<C>> = (0..num_groups)
563 .map(|j| {
564 let degree = proof.degrees[j];
565 let degree_field = q_star_cfg.project(&(degree as u64));
566 transcript.absorb_field_element(q_star_cfg, °ree_field, &mut buf);
567 VerifierState::new(num_vars, degree, *cfg)
568 })
569 .collect();
570
571 for msgs in &proof.group_messages {
572 if msgs.len() != num_vars {
573 return Err(SumCheckError::InvalidProofLength {
574 expected: num_vars,
575 got: msgs.len(),
576 });
577 }
578 }
579
580 assert_eq!(
581 states.len(),
582 proof.group_messages.len(),
583 "verifier states ({}) must match proof groups ({})",
584 states.len(),
585 proof.group_messages.len(),
586 );
587
588 per_family_verifier_states.push(states);
589 }
590
591 for i in 0..num_vars {
592 for (proof, cfg) in proofs {
595 proof.group_messages.iter().for_each(|msg| {
596 transcript.absorb_field_element_slice(
597 *cfg,
598 &msg[i].0.tail_evaluations,
599 &mut buf,
600 )
601 });
602 }
603
604 let shared_chal_q_star: C::Element = transcript.get_field_challenge(q_star_cfg);
606 transcript.absorb_field_element(q_star_cfg, &shared_chal_q_star, &mut buf);
607 let shared_chal_int = q_star_cfg.lift(&shared_chal_q_star);
608
609 for (b, (proof, cfg)) in proofs.iter().enumerate() {
610 let chal_b = cfg.project(&shared_chal_int);
611 per_family_verifier_states[b]
612 .iter_mut()
613 .zip(proof.group_messages.iter())
614 .for_each(|(state, msg)| {
615 state.verify_round_with_challenge(&msg[i], chal_b.clone())
616 });
617 }
618 }
619
620 let mut output = Vec::with_capacity(num_families);
622 for (b, (proof, _)) in proofs.iter().enumerate() {
623 let states = std::mem::take(&mut per_family_verifier_states[b]);
624 let mut shared_point: Option<Vec<C::Element>> = None;
625 let mut expected_evaluations = Vec::with_capacity(states.len());
626 for (j, state) in states.into_iter().enumerate() {
627 let subclaim = state.check_and_generate_subclaim(proof.claimed_sums[j].clone())?;
628 if let Some(ref p) = shared_point {
629 debug_assert_eq!(p, &subclaim.point);
630 } else {
631 shared_point = Some(subclaim.point);
632 }
633 expected_evaluations.push(subclaim.expected_evaluation);
634 }
635 output.push(MultiDegreeSubClaims {
636 point: shared_point.expect("at least one group"),
637 expected_evaluations,
638 });
639 }
640
641 Ok(output)
642 }
643}
644
645#[cfg(test)]
650#[allow(
651 clippy::arithmetic_side_effects,
652 clippy::cast_possible_truncation,
653 clippy::cast_possible_wrap,
654 clippy::cast_sign_loss,
655 clippy::redundant_clone
656)]
657mod tests {
658 use super::*;
659 use crypto_bigint::{U128, const_monty_params};
660 use crypto_primitives::{FixedConfig, crypto_bigint_const_monty::ConstMontyField};
661 use zinc_poly::utils::build_eq_x_r;
662 use zinc_transcript::Blake3Transcript;
663
664 const_monty_params!(TestParams, U128, "00000000b933426489189cb5b47d567f");
665 type F = ConstMontyField<TestParams, { U128::LIMBS }>;
666 type Cfg = FixedConfig<F>;
667
668 #[test]
673 fn multi_degree_two_groups() {
674 let num_vars = 3;
675 let cfg = &Cfg::default();
676
677 let a_vals: Vec<F> = (0_u32..8).map(|i| F::from(i + 1)).collect();
678 let b_vals: Vec<F> = (0_u32..8).map(|i| F::from(i + 10)).collect();
679
680 let a_mle =
681 DenseMultilinearExtension::from_evaluations_vec(num_vars, a_vals, F::from(0_u32));
682 let b_mle =
683 DenseMultilinearExtension::from_evaluations_vec(num_vars, b_vals, F::from(0_u32));
684
685 let r: Vec<F> = vec![F::from(5_u32), F::from(7_u32), F::from(11_u32)];
686 let eq_r = build_eq_x_r(cfg, &r).unwrap();
687
688 let g0 = MultiDegreeSumcheckGroup::new(
690 2,
691 vec![eq_r.clone(), a_mle.clone(), b_mle.clone()],
692 Box::new(|v: &[F]| v[0] * (v[1] + v[2])),
693 );
694
695 let g1 = MultiDegreeSumcheckGroup::new(
697 3,
698 vec![eq_r.clone(), a_mle.clone(), b_mle.clone()],
699 Box::new(|v: &[F]| v[0] * v[1] * v[2]),
700 );
701
702 let mut pt = Blake3Transcript::new();
704 let mut outputs = MultiDegreeSumcheck::<Cfg>::prove_as_subprotocol(
705 &mut pt,
706 vec![(vec![g0, g1], cfg)],
707 num_vars,
708 cfg,
709 );
710 let (proof, _states) = outputs.pop().expect("single family");
711
712 let mut vt = Blake3Transcript::new();
714 let mut subclaims_vec = MultiDegreeSumcheck::<Cfg>::verify_as_subprotocol(
715 &mut vt,
716 num_vars,
717 &[(&proof, cfg)],
718 cfg,
719 )
720 .expect("verification should succeed");
721 let subclaims = subclaims_vec.pop().expect("single family");
722
723 assert_eq!(subclaims.expected_evaluations.len(), 2);
724
725 let point = &subclaims.point;
727 let eq_eval = zinc_poly::utils::eq_eval(cfg, point, &r).unwrap();
728 let a_eval = a_mle.evaluate(cfg, point).unwrap();
729 let b_eval = b_mle.evaluate(cfg, point).unwrap();
730
731 assert_eq!(
732 subclaims.expected_evaluations[0],
733 eq_eval * (a_eval + b_eval)
734 );
735 assert_eq!(subclaims.expected_evaluations[1], eq_eval * a_eval * b_eval);
736 }
737
738 #[test]
740 fn multi_degree_single_group() {
741 let num_vars = 2;
742 let cfg = &Cfg::default();
743
744 let vals: Vec<F> = (0_u32..4).map(|i| F::from(i + 1)).collect();
745 let mle = DenseMultilinearExtension::from_evaluations_vec(num_vars, vals, F::from(0_u32));
746
747 let r: Vec<F> = vec![F::from(3_u32), F::from(7_u32)];
748 let eq_r = build_eq_x_r(cfg, &r).unwrap();
749
750 let g = MultiDegreeSumcheckGroup::new(
751 2,
752 vec![eq_r.clone(), mle.clone()],
753 Box::new(|v: &[F]| v[0] * v[1]),
754 );
755
756 let mut pt = Blake3Transcript::new();
757 let mut outputs = MultiDegreeSumcheck::<Cfg>::prove_as_subprotocol(
758 &mut pt,
759 vec![(vec![g], cfg)],
760 num_vars,
761 cfg,
762 );
763 let (proof, _) = outputs.pop().expect("single family");
764
765 let mut vt = Blake3Transcript::new();
766 let mut subclaims_vec = MultiDegreeSumcheck::<Cfg>::verify_as_subprotocol(
767 &mut vt,
768 num_vars,
769 &[(&proof, cfg)],
770 cfg,
771 )
772 .expect("verification should succeed");
773 let subclaims = subclaims_vec.pop().expect("single family");
774
775 let point = &subclaims.point;
776 let eq_eval = zinc_poly::utils::eq_eval(cfg, point, &r).unwrap();
777 let a_eval = mle.clone().evaluate(cfg, point).unwrap();
778
779 assert_eq!(subclaims.expected_evaluations[0], eq_eval * a_eval);
780 }
781
782 #[test]
789 fn two_families_same_cfg() {
790 let num_vars = 3;
791 let cfg = &Cfg::default();
792 let q_star_cfg = cfg;
793
794 let a_vals: Vec<F> = (0_u32..8).map(|i| F::from(i + 1)).collect();
796 let b_vals: Vec<F> = (0_u32..8).map(|i| F::from(i + 10)).collect();
797 let a_mle =
798 DenseMultilinearExtension::from_evaluations_vec(num_vars, a_vals, F::from(0_u32));
799 let b_mle =
800 DenseMultilinearExtension::from_evaluations_vec(num_vars, b_vals, F::from(0_u32));
801 let r_a: Vec<F> = vec![F::from(5_u32), F::from(7_u32), F::from(11_u32)];
802 let eq_r_a = build_eq_x_r(cfg, &r_a).unwrap();
803 let group_a = MultiDegreeSumcheckGroup::new(
804 2,
805 vec![eq_r_a.clone(), a_mle.clone(), b_mle.clone()],
806 Box::new(|v: &[F]| v[0] * (v[1] + v[2])),
807 );
808
809 let r_b: Vec<F> = vec![F::from(2_u32), F::from(3_u32), F::from(5_u32)];
811 let eq_r_b = build_eq_x_r(cfg, &r_b).unwrap();
812 let group_b = MultiDegreeSumcheckGroup::new(
813 3,
814 vec![eq_r_b.clone(), a_mle.clone(), b_mle.clone()],
815 Box::new(|v: &[F]| v[0] * v[1] * v[2]),
816 );
817
818 let mut pt = Blake3Transcript::new();
820 let mut outputs = MultiDegreeSumcheck::<Cfg>::prove_as_subprotocol(
821 &mut pt,
822 vec![(vec![group_a], cfg), (vec![group_b], cfg)],
823 num_vars,
824 q_star_cfg,
825 );
826 assert_eq!(outputs.len(), 2);
827 let (proof_b, _) = outputs.pop().unwrap();
828 let (proof_a, _) = outputs.pop().unwrap();
829
830 let mut vt = Blake3Transcript::new();
832 let subclaims = MultiDegreeSumcheck::<Cfg>::verify_as_subprotocol(
833 &mut vt,
834 num_vars,
835 &[(&proof_a, cfg), (&proof_b, cfg)],
836 q_star_cfg,
837 )
838 .expect("verification should succeed");
839 assert_eq!(subclaims.len(), 2);
840
841 assert_eq!(subclaims[0].point(), subclaims[1].point());
843
844 let point = subclaims[0].point();
846 let eq_a_eval = zinc_poly::utils::eq_eval(cfg, point, &r_a).unwrap();
847 let eq_b_eval = zinc_poly::utils::eq_eval(cfg, point, &r_b).unwrap();
848 let a_eval = a_mle.evaluate(cfg, point).unwrap();
849 let b_eval = b_mle.evaluate(cfg, point).unwrap();
850
851 assert_eq!(
852 subclaims[0].expected_evaluations()[0],
853 eq_a_eval * (a_eval + b_eval),
854 );
855 assert_eq!(
856 subclaims[1].expected_evaluations()[0],
857 eq_b_eval * a_eval * b_eval,
858 );
859 }
860}