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