1use std::{cmp::Reverse, iter};
4
5use binius_field::{BinaryField, PackedField, TowerField};
6use binius_hash::PseudoCompressionFunction;
7use binius_math::{ArithExpr, CompositionPoly, EvaluationOrder};
8use binius_utils::{bail, checked_arithmetics::log2_ceil_usize};
9use digest::{core_api::BlockSizeUser, Digest, Output};
10use itertools::{izip, multiunzip, Itertools};
11use tracing::instrument;
12
13use super::{
14 channel::{Boundary, OracleOrConst},
15 error::{Error, VerificationError},
16 exp, ConstraintSystem, Proof,
17};
18use crate::{
19 composition::IndexComposition,
20 constraint_system::{
21 channel::{Flush, FlushDirection},
22 common::{FDomain, FEncode, FExt},
23 },
24 fiat_shamir::{CanSample, Challenger},
25 merkle_tree::BinaryMerkleTreeScheme,
26 oracle::{MultilinearOracleSet, OracleId},
27 piop,
28 polynomial::MultivariatePoly,
29 protocols::{
30 evalcheck::EvalcheckMultilinearClaim,
31 gkr_exp,
32 gkr_gpa::{self, LayerClaim},
33 greedy_evalcheck,
34 sumcheck::{
35 self, constraint_set_zerocheck_claim, zerocheck, BatchSumcheckOutput,
36 CompositeSumClaim, EqIndSumcheckClaim, ZerocheckClaim,
37 },
38 },
39 ring_switch,
40 tower::{PackedTop, TowerFamily, TowerUnderlier},
41 transcript::VerifierTranscript,
42 transparent::eq_ind::EqIndPartialEval,
43};
44
45#[instrument("constraint_system::verify", skip_all, level = "debug")]
47pub fn verify<U, Tower, Hash, Compress, Challenger_>(
48 constraint_system: &ConstraintSystem<FExt<Tower>>,
49 log_inv_rate: usize,
50 security_bits: usize,
51 boundaries: &[Boundary<FExt<Tower>>],
52 proof: Proof,
53) -> Result<(), Error>
54where
55 U: TowerUnderlier<Tower>,
56 Tower: TowerFamily,
57 Tower::B128: PackedTop<Tower>,
58 Hash: Digest + BlockSizeUser,
59 Compress: PseudoCompressionFunction<Output<Hash>, 2> + Default + Sync,
60 Challenger_: Challenger + Default,
61{
62 let ConstraintSystem {
63 mut oracles,
64 mut table_constraints,
65 mut flushes,
66 non_zero_oracle_ids,
67 max_channel_id,
68 mut exponents,
69 ..
70 } = constraint_system.clone();
71
72 table_constraints.sort_by_key(|constraint_set| Reverse(constraint_set.n_vars));
74
75 let Proof { transcript } = proof;
76
77 let mut transcript = VerifierTranscript::<Challenger_>::new(transcript);
78 transcript.observe().write_slice(boundaries);
79
80 let merkle_scheme = BinaryMerkleTreeScheme::<_, Hash, _>::new(Compress::default());
81 let (commit_meta, oracle_to_commit_index) = piop::make_oracle_commit_meta(&oracles)?;
82 let fri_params = piop::make_commit_params_with_optimal_arity::<_, FEncode<Tower>, _>(
83 &commit_meta,
84 &merkle_scheme,
85 security_bits,
86 log_inv_rate,
87 )?;
88
89 let mut reader = transcript.message();
91 let commitment = reader.read::<Output<Hash>>()?;
92
93 exponents.sort_by_key(|b| std::cmp::Reverse(b.n_vars(&oracles)));
95
96 let exp_challenge = transcript.sample_vec(exp::max_n_vars(&exponents, &oracles));
97
98 let mut reader = transcript.message();
99 let exp_evals = reader.read_scalar_slice(exponents.len())?;
100
101 let exp_claims = exp::make_claims(&exponents, &oracles, &exp_challenge, &exp_evals)?
102 .into_iter()
103 .collect::<Vec<_>>();
104
105 let base_exp_output =
106 gkr_exp::batch_verify(EvaluationOrder::HighToLow, &exp_claims, &mut transcript)?;
107
108 let exp_eval_claims = exp::make_eval_claims(&exponents, base_exp_output)?;
109
110 let mut reader = transcript.message();
113 let non_zero_products = reader.read_scalar_slice(non_zero_oracle_ids.len())?;
114 if non_zero_products
115 .iter()
116 .any(|count| *count == Tower::B128::zero())
117 {
118 bail!(Error::Zeros);
119 }
120
121 let non_zero_prodcheck_claims = gkr_gpa::construct_grand_product_claims(
122 &non_zero_oracle_ids,
123 &oracles,
124 &non_zero_products,
125 )?;
126
127 let mixing_challenge = transcript.sample();
129 let permutation_challenges = transcript.sample_vec(max_channel_id + 1);
131
132 flushes.sort_by_key(|flush| flush.channel_id);
133 let flush_oracle_ids =
134 make_flush_oracles(&mut oracles, &flushes, mixing_challenge, &permutation_challenges)?;
135 let flush_selectors = flushes
136 .iter()
137 .map(|flush| flush.selector)
138 .collect::<Vec<_>>();
139
140 let flush_products = transcript
141 .message()
142 .read_scalar_slice(flush_oracle_ids.len())?;
143 verify_channels_balance(
144 &flushes,
145 &flush_products,
146 boundaries,
147 mixing_challenge,
148 &permutation_challenges,
149 )?;
150
151 let flush_prodcheck_claims =
152 gkr_gpa::construct_grand_product_claims(&flush_oracle_ids, &oracles, &flush_products)?;
153
154 let mut final_layer_claims = gkr_gpa::batch_verify(
156 EvaluationOrder::LowToHigh,
157 [flush_prodcheck_claims, non_zero_prodcheck_claims].concat(),
158 &mut transcript,
159 )?;
160
161 let non_zero_final_layer_claims = final_layer_claims.split_off(flush_oracle_ids.len());
162 let flush_final_layer_claims = final_layer_claims;
163
164 let non_zero_prodcheck_eval_claims =
166 gkr_gpa::make_eval_claims(non_zero_oracle_ids, non_zero_final_layer_claims)?;
167
168 let (flush_oracle_ids, flush_selectors, flush_final_layer_claims) =
170 reorder_for_flushing_by_n_vars(
171 &oracles,
172 &flush_oracle_ids,
173 flush_selectors,
174 flush_final_layer_claims,
175 );
176
177 let flush_sumcheck_metas = get_flush_dedup_sumcheck_metas(
178 &oracles,
179 &flush_oracle_ids,
180 &flush_selectors,
181 &flush_final_layer_claims,
182 )?;
183
184 let DedupEqIndSumcheckClaims {
185 eq_ind_sumcheck_claims,
186 gkr_eval_points,
187 flush_selectors_unique_by_claim,
188 flush_oracle_ids_by_claim,
189 } = get_flush_dedup_eq_ind_sumcheck_claims(flush_sumcheck_metas)?;
190
191 let regular_sumcheck_claims =
192 sumcheck::eq_ind::reduce_to_regular_sumchecks(&eq_ind_sumcheck_claims)?;
193
194 let flush_sumcheck_output = sumcheck::batch_verify(
195 EvaluationOrder::LowToHigh,
196 ®ular_sumcheck_claims,
197 &mut transcript,
198 )?;
199
200 let flush_eval_claims = get_post_flush_sumcheck_eval_claims_without_eq(
201 &oracles,
202 &flush_selectors_unique_by_claim,
203 &flush_oracle_ids_by_claim,
204 &flush_sumcheck_output,
205 )?;
206
207 for (gkr_eval_point, evals) in izip!(gkr_eval_points, flush_sumcheck_output.multilinear_evals) {
209 let gkr_eval_point_len = gkr_eval_point.len();
210 let eq_ind = EqIndPartialEval::new(gkr_eval_point);
211
212 let sumcheck_challenges_len = flush_sumcheck_output.challenges.len();
213 let expected_eval = eq_ind.evaluate(
214 &flush_sumcheck_output.challenges[(sumcheck_challenges_len - gkr_eval_point_len)..],
215 )?;
216
217 let &actual_eval = evals
218 .last()
219 .expect("Flush sumcheck composition non-empty by construction");
220
221 if expected_eval != actual_eval {
222 return Err(Error::FalseEqEvaluationClaim);
223 }
224 }
225
226 let (zerocheck_claims, zerocheck_oracle_metas) = table_constraints
228 .iter()
229 .cloned()
230 .map(constraint_set_zerocheck_claim)
231 .collect::<Result<Vec<_>, _>>()?
232 .into_iter()
233 .unzip::<_, _, Vec<_>, Vec<_>>();
234
235 let (max_n_vars, skip_rounds) =
236 max_n_vars_and_skip_rounds(&zerocheck_claims, <FDomain<Tower>>::N_BITS);
237
238 let zerocheck_challenges = transcript.sample_vec(max_n_vars - skip_rounds);
239
240 let univariate_cnt = zerocheck_claims
241 .partition_point(|zerocheck_claim| zerocheck_claim.n_vars() > max_n_vars - skip_rounds);
242
243 let univariate_output = sumcheck::batch_verify_zerocheck_univariate_round(
244 &zerocheck_claims[..univariate_cnt],
245 skip_rounds,
246 &mut transcript,
247 )?;
248
249 let univariate_challenge = univariate_output.univariate_challenge;
250
251 let eq_ind_sumcheck_claims = zerocheck::reduce_to_eq_ind_sumchecks(&zerocheck_claims)?;
252 let regular_sumcheck_claims =
253 sumcheck::eq_ind::reduce_to_regular_sumchecks(&eq_ind_sumcheck_claims)?;
254
255 let sumcheck_output = sumcheck::batch_verify_with_start(
256 EvaluationOrder::LowToHigh,
257 univariate_output.batch_verify_start,
258 ®ular_sumcheck_claims,
259 &mut transcript,
260 )?;
261
262 let zerocheck_output = sumcheck::eq_ind::verify_sumcheck_outputs(
263 &eq_ind_sumcheck_claims,
264 &zerocheck_challenges,
265 sumcheck_output,
266 )?;
267
268 let univariate_cnt =
269 zerocheck_claims.partition_point(|claim| claim.n_vars() > max_n_vars - skip_rounds);
270
271 let mut reduction_claims = Vec::with_capacity(univariate_cnt);
272 for (claim, univariatized_multilinear_evals) in
273 iter::zip(&zerocheck_claims, &zerocheck_output.multilinear_evals)
274 {
275 let claim_skip_rounds = claim.n_vars().saturating_sub(max_n_vars - skip_rounds);
276
277 let reduction_claim = sumcheck::univariate::univariatizing_reduction_claim(
278 claim_skip_rounds,
279 univariatized_multilinear_evals,
280 )?;
281
282 reduction_claims.push(reduction_claim);
283 }
284
285 let univariatizing_output =
286 sumcheck::batch_verify(EvaluationOrder::LowToHigh, &reduction_claims, &mut transcript)?;
287
288 let multilinear_zerocheck_output = sumcheck::univariate::verify_sumcheck_outputs(
289 &reduction_claims,
290 univariate_challenge,
291 &zerocheck_output.challenges,
292 univariatizing_output,
293 )?;
294
295 let zerocheck_eval_claims = sumcheck::make_eval_claims(
296 EvaluationOrder::LowToHigh,
297 zerocheck_oracle_metas,
298 multilinear_zerocheck_output,
299 )?;
300
301 let eval_claims = greedy_evalcheck::verify(
303 &mut oracles,
304 [non_zero_prodcheck_eval_claims, flush_eval_claims]
305 .concat()
306 .into_iter()
307 .chain(zerocheck_eval_claims)
308 .chain(exp_eval_claims),
309 &mut transcript,
310 )?;
311
312 let system = ring_switch::EvalClaimSystem::new(
314 &oracles,
315 &commit_meta,
316 &oracle_to_commit_index,
317 &eval_claims,
318 )?;
319
320 let ring_switch::ReducedClaim {
321 transparents,
322 sumcheck_claims: piop_sumcheck_claims,
323 } = ring_switch::verify::<_, Tower, _>(&system, &mut transcript)?;
324
325 piop::verify(
327 &commit_meta,
328 &merkle_scheme,
329 &fri_params,
330 &commitment,
331 &transparents,
332 &piop_sumcheck_claims,
333 &mut transcript,
334 )?;
335
336 transcript.finalize()?;
337
338 Ok(())
339}
340
341pub fn max_n_vars_and_skip_rounds<F, Composition>(
342 zerocheck_claims: &[ZerocheckClaim<F, Composition>],
343 domain_bits: usize,
344) -> (usize, usize)
345where
346 F: TowerField,
347 Composition: CompositionPoly<F>,
348{
349 let max_n_vars = max_n_vars(zerocheck_claims);
350
351 let domain_max_skip_rounds = zerocheck_claims
356 .iter()
357 .map(|claim| {
358 let log_degree = log2_ceil_usize(claim.max_individual_degree());
359 max_n_vars - claim.n_vars() + domain_bits.saturating_sub(log_degree)
360 })
361 .min()
362 .unwrap_or(0);
363
364 let max_skip_rounds = domain_max_skip_rounds.min(max_n_vars);
365 (max_n_vars, max_skip_rounds)
366}
367
368fn max_n_vars<F, Composition>(zerocheck_claims: &[ZerocheckClaim<F, Composition>]) -> usize
369where
370 F: TowerField,
371 Composition: CompositionPoly<F>,
372{
373 zerocheck_claims
374 .iter()
375 .map(|claim| claim.n_vars())
376 .max()
377 .unwrap_or(0)
378}
379
380fn verify_channels_balance<F: TowerField>(
381 flushes: &[Flush<F>],
382 flush_products: &[F],
383 boundaries: &[Boundary<F>],
384 mixing_challenge: F,
385 permutation_challenges: &[F],
386) -> Result<(), Error> {
387 if flush_products.len() != flushes.len() {
388 return Err(VerificationError::IncorrectNumberOfFlushProducts.into());
389 }
390
391 let mut flush_iter = flushes
392 .iter()
393 .zip(flush_products.iter().copied())
394 .peekable();
395 while let Some((flush, _)) = flush_iter.peek() {
396 let channel_id = flush.channel_id;
397
398 let boundary_products =
399 boundaries
400 .iter()
401 .fold((F::ONE, F::ONE), |(pull_product, push_product), boundary| {
402 let Boundary {
403 channel_id: boundary_channel_id,
404 direction,
405 multiplicity,
406 values,
407 ..
408 } = boundary;
409
410 if *boundary_channel_id == channel_id {
411 let (mixed_values, _) = values.iter().fold(
412 (permutation_challenges[channel_id], F::ONE),
413 |(sum, mixing), values| {
414 (sum + mixing * values, mixing * mixing_challenge)
415 },
416 );
417
418 let mixed_values_with_multiplicity =
419 mixed_values.pow_vartime([*multiplicity]);
420
421 return match direction {
422 FlushDirection::Pull => {
423 (pull_product * mixed_values_with_multiplicity, push_product)
424 }
425 FlushDirection::Push => {
426 (pull_product, push_product * mixed_values_with_multiplicity)
427 }
428 };
429 }
430
431 (pull_product, push_product)
432 });
433
434 let (pull_product, push_product) = flush_iter
435 .peeking_take_while(|(flush, _)| flush.channel_id == channel_id)
436 .fold(boundary_products, |(pull_product, push_product), (flush, flush_product)| {
437 let flush_product_with_multiplicity =
438 flush_product.pow_vartime([flush.multiplicity]);
439 match flush.direction {
440 FlushDirection::Pull => {
441 (pull_product * flush_product_with_multiplicity, push_product)
442 }
443 FlushDirection::Push => {
444 (pull_product, push_product * flush_product_with_multiplicity)
445 }
446 }
447 });
448 if pull_product != push_product {
449 return Err(VerificationError::ChannelUnbalanced { id: channel_id }.into());
450 }
451 }
452
453 Ok(())
454}
455
456pub fn make_flush_oracles<F: TowerField>(
457 oracles: &mut MultilinearOracleSet<F>,
458 flushes: &[Flush<F>],
459 mixing_challenge: F,
460 permutation_challenges: &[F],
461) -> Result<Vec<OracleId>, Error> {
462 let mut mixing_powers = vec![F::ONE];
463 let mut flush_iter = flushes.iter();
464
465 permutation_challenges
466 .iter()
467 .enumerate()
468 .flat_map(|(channel_id, permutation_challenge)| {
469 flush_iter
470 .peeking_take_while(|flush| flush.channel_id == channel_id)
471 .map(|flush| {
472 let mut non_const_oracles =
474 flush.oracles.iter().copied().filter_map(|id| match id {
475 OracleOrConst::Oracle(oracle_id) => Some(oracle_id),
476 _ => None,
477 });
478
479 let first_oracle = non_const_oracles.next().ok_or(Error::EmptyFlushOracles)?;
480 let n_vars = oracles.n_vars(first_oracle);
481
482 for oracle_id in non_const_oracles {
483 let oracle_n_vars = oracles.n_vars(oracle_id);
484 if oracle_n_vars != n_vars {
485 return Err(Error::ChannelFlushNvarsMismatch {
486 expected: n_vars,
487 got: oracle_n_vars,
488 });
489 }
490 }
491
492 while mixing_powers.len() < flush.oracles.len() {
494 let last_power = *mixing_powers.last().expect(
495 "mixing_powers is initialized with one element; \
496 mixing_powers never shrinks; \
497 thus, it must not be empty",
498 );
499 mixing_powers.push(last_power * mixing_challenge);
500 }
501
502 let const_linear_combination = flush
503 .oracles
504 .iter()
505 .copied()
506 .zip(mixing_powers.iter())
507 .filter_map(|(id, coeff)| match id {
508 OracleOrConst::Const { base, .. } => Some(base * coeff),
509 _ => None,
510 })
511 .sum::<F>();
512
513 let id = oracles
515 .add_named(format!("flush channel_id={channel_id}"))
516 .linear_combination_with_offset(
517 n_vars,
518 *permutation_challenge + const_linear_combination,
519 flush
520 .oracles
521 .iter()
522 .zip(mixing_powers.iter().copied())
523 .filter_map(|(id, coeff)| match id {
524 OracleOrConst::Oracle(oracle_id) => Some((*oracle_id, coeff)),
525 _ => None,
526 }),
527 )?;
528 Ok(id)
529 })
530 .collect::<Vec<_>>()
531 })
532 .collect()
533}
534
535#[derive(Debug)]
536pub struct FlushSumcheckMeta<F: TowerField> {
537 pub composite_sum_claims:
538 Vec<CompositeSumClaim<F, IndexComposition<FlushSumcheckComposition, 2>>>,
539 pub flush_selectors_unique: Vec<OracleId>,
540 pub flush_oracle_ids: Vec<OracleId>,
541 pub eval_point: Vec<F>,
542}
543
544pub fn get_flush_dedup_sumcheck_metas<F: TowerField>(
545 oracles: &MultilinearOracleSet<F>,
546 flush_oracle_ids: &[OracleId],
547 flush_selectors: &[OracleId],
548 final_layer_claims: &[LayerClaim<F>],
549) -> Result<Vec<FlushSumcheckMeta<F>>, Error> {
550 let total_flushes = flush_oracle_ids.len();
551
552 debug_assert_eq!(total_flushes, flush_selectors.len());
553 debug_assert_eq!(total_flushes, final_layer_claims.len());
554
555 let mut flush_sumcheck_metas = Vec::new();
556
557 let mut begin = 0;
558 for end in 1..=total_flushes {
559 if end < total_flushes
560 && final_layer_claims[end].eval_point == final_layer_claims[end - 1].eval_point
561 {
562 continue;
563 }
564
565 let eval_point = final_layer_claims[begin].eval_point.clone();
566 let n_vars = eval_point.len();
567
568 let mut flush_selectors_unique = flush_selectors[begin..end].to_vec();
570 flush_selectors_unique.sort();
571 flush_selectors_unique.dedup();
572
573 let n_selectors = flush_selectors_unique.len();
574 let n_multilinears = n_selectors + end - begin;
575
576 let mut composite_sum_claims = Vec::with_capacity(end - begin);
577
578 for (i, (&oracle_id, &flush_selector, layer_claim)) in izip!(
579 &flush_oracle_ids[begin..end],
580 &flush_selectors[begin..end],
581 &final_layer_claims[begin..end]
582 )
583 .enumerate()
584 {
585 debug_assert_eq!(n_vars, oracles.n_vars(oracle_id));
586
587 let Some(selector_index) = flush_selectors_unique
588 .iter()
589 .position(|&flush_selectors_dedup| flush_selectors_dedup == flush_selector)
590 else {
591 panic!(
592 "flush_selectors_unique is a set of unique flush selectors \
593 from a given slice of oracles, search must succeed by construction."
594 );
595 };
596
597 let composite_sum_claim = CompositeSumClaim {
598 composition: IndexComposition::new(
599 n_multilinears,
600 [i + n_selectors, selector_index],
601 FlushSumcheckComposition,
602 )?,
603 sum: layer_claim.eval,
604 };
605
606 composite_sum_claims.push(composite_sum_claim);
607 }
608
609 let flush_sumcheck_meta = FlushSumcheckMeta {
610 composite_sum_claims,
611 flush_selectors_unique,
612 flush_oracle_ids: flush_oracle_ids[begin..end].to_vec(),
613 eval_point,
614 };
615
616 flush_sumcheck_metas.push(flush_sumcheck_meta);
617
618 begin = end;
619 }
620
621 Ok(flush_sumcheck_metas)
622}
623
624#[derive(Debug)]
625pub struct FlushSumcheckComposition;
626
627impl<P: PackedField> CompositionPoly<P> for FlushSumcheckComposition {
628 fn n_vars(&self) -> usize {
629 2
630 }
631
632 fn degree(&self) -> usize {
633 2
634 }
635
636 fn expression(&self) -> ArithExpr<P::Scalar> {
637 ArithExpr::Var(0) * ArithExpr::Var(1) + ArithExpr::one() - ArithExpr::Var(1)
638 }
639
640 fn binary_tower_level(&self) -> usize {
641 0
642 }
643
644 fn evaluate(&self, query: &[P]) -> Result<P, binius_math::Error> {
645 let unmasked_flush = query[0];
646 let selector = query[1];
647
648 Ok(selector * unmasked_flush + (P::one() - selector))
649 }
650}
651
652pub fn get_post_flush_sumcheck_eval_claims_without_eq<F: TowerField>(
653 oracles: &MultilinearOracleSet<F>,
654 flush_selectors_unique_by_claim: &[Vec<OracleId>],
655 flush_oracle_ids_by_claim: &[Vec<OracleId>],
656 sumcheck_output: &BatchSumcheckOutput<F>,
657) -> Result<Vec<EvalcheckMultilinearClaim<F>>, Error> {
658 let n_claims = sumcheck_output.multilinear_evals.len();
659 debug_assert_eq!(n_claims, flush_oracle_ids_by_claim.len());
660 debug_assert_eq!(n_claims, flush_selectors_unique_by_claim.len());
661
662 let max_n_vars = sumcheck_output.challenges.len();
663
664 let mut evalcheck_claims = Vec::new();
665 for (flush_oracle_ids, evals, flush_selectors_unique) in izip!(
666 flush_oracle_ids_by_claim,
667 &sumcheck_output.multilinear_evals,
668 flush_selectors_unique_by_claim
669 ) {
670 let n_selectors = flush_selectors_unique.len();
671 debug_assert_eq!(evals.len(), n_selectors + flush_oracle_ids.len() + 1);
672
673 for (&flush_selector, &eval) in izip!(flush_selectors_unique, evals) {
674 let n_vars = oracles.n_vars(flush_selector);
675 let eval_point = sumcheck_output.challenges[max_n_vars - n_vars..].into();
676
677 evalcheck_claims.push(EvalcheckMultilinearClaim {
678 id: flush_selector,
679 eval_point,
680 eval,
681 });
682 }
683
684 for (&flush_oracle, &eval) in izip!(flush_oracle_ids, &evals[n_selectors..]) {
685 let n_vars = oracles.n_vars(flush_oracle);
686 let eval_point = sumcheck_output.challenges[max_n_vars - n_vars..].into();
687
688 evalcheck_claims.push(EvalcheckMultilinearClaim {
689 id: flush_oracle,
690 eval_point,
691 eval,
692 });
693 }
694 }
695
696 Ok(evalcheck_claims)
697}
698
699pub struct DedupEqIndSumcheckClaims<F: TowerField, Composition: CompositionPoly<F>> {
700 eq_ind_sumcheck_claims: Vec<EqIndSumcheckClaim<F, Composition>>,
701 gkr_eval_points: Vec<Vec<F>>,
702 flush_selectors_unique_by_claim: Vec<Vec<OracleId>>,
703 flush_oracle_ids_by_claim: Vec<Vec<OracleId>>,
704}
705
706#[allow(clippy::type_complexity)]
707pub fn get_flush_dedup_eq_ind_sumcheck_claims<F: TowerField>(
708 flush_sumcheck_metas: Vec<FlushSumcheckMeta<F>>,
709) -> Result<DedupEqIndSumcheckClaims<F, impl CompositionPoly<F>>, Error> {
710 let n_claims = flush_sumcheck_metas.len();
711 let mut eq_ind_sumcheck_claims = Vec::with_capacity(n_claims);
712 let mut gkr_eval_points = Vec::with_capacity(n_claims);
713 let mut flush_oracle_ids_by_claim = Vec::with_capacity(n_claims);
714 let mut flush_selectors_unique_by_claim = Vec::with_capacity(n_claims);
715 for flush_sumcheck_meta in flush_sumcheck_metas {
716 let FlushSumcheckMeta {
717 composite_sum_claims,
718 flush_selectors_unique,
719 flush_oracle_ids,
720 eval_point,
721 } = flush_sumcheck_meta;
722
723 let n_vars = eval_point.len();
724 let n_multilinears = flush_selectors_unique.len() + flush_oracle_ids.len();
725 let eq_ind_sumcheck_claim =
726 EqIndSumcheckClaim::new(n_vars, n_multilinears, composite_sum_claims)?;
727
728 eq_ind_sumcheck_claims.push(eq_ind_sumcheck_claim);
729 gkr_eval_points.push(eval_point);
730 flush_selectors_unique_by_claim.push(flush_selectors_unique);
731 flush_oracle_ids_by_claim.push(flush_oracle_ids);
732 }
733
734 Ok(DedupEqIndSumcheckClaims {
735 eq_ind_sumcheck_claims,
736 gkr_eval_points,
737 flush_selectors_unique_by_claim,
738 flush_oracle_ids_by_claim,
739 })
740}
741
742pub fn reorder_for_flushing_by_n_vars<F: TowerField>(
743 oracles: &MultilinearOracleSet<F>,
744 flush_oracle_ids: &[OracleId],
745 flush_selectors: Vec<OracleId>,
746 flush_final_layer_claims: Vec<LayerClaim<F>>,
747) -> (Vec<OracleId>, Vec<usize>, Vec<LayerClaim<F>>) {
748 let mut zipped: Vec<_> =
749 izip!(flush_oracle_ids.iter().copied(), flush_selectors, flush_final_layer_claims)
750 .collect();
751 zipped.sort_by_key(|&(id, flush_selector, _)| Reverse((oracles.n_vars(id), flush_selector)));
752 multiunzip(zipped)
753}