1use alloc::vec::Vec;
9use core::borrow::Borrow;
10
11use p3_binary_field::TowerLevel;
12use p3_challenger::fs::TranscriptField;
13use p3_challenger::{FieldChallenger, GrindingChallenger};
14use p3_field::{Field, PackedValue};
15use p3_maybe_rayon::prelude::*;
16use p3_multilinear_util::point::Point;
17use p3_multilinear_util::poly::Poly;
18use serde::{Deserialize, Serialize};
19
20use super::basis::{Coefficients, CoordinateSums};
21use super::equality::{FactoredEquality, scaled_sums_into};
22use super::packing::BitPacking;
23use super::products::LeftFactors;
24use super::tensor::BitTensor;
25use super::transcript::{
26 BitRingSwitchProverTranscript, BitRingSwitchShape, BitRingSwitchVerifierTranscript,
27 TranscriptWidth,
28};
29use crate::data::SumcheckData;
30use crate::error::SumcheckError;
31use crate::strategy::{Basis, FromTable, IntoTranscriptField, ReprSumcheckProver, VariableOrder};
32use crate::transcript::{ProverTranscript, SumcheckShape};
33
34#[cfg(not(test))]
40const LOG_CHUNK: usize = PRODUCTION_LOG_CHUNK;
41
42#[cfg(test)]
46const LOG_CHUNK: usize = 1;
47
48const PRODUCTION_LOG_CHUNK: usize = 14;
50
51const CHUNK: usize = 1 << LOG_CHUNK;
53
54const COMPACT_PRODUCTION_ROUNDS: usize = 4;
58const COMPACT_PRODUCTION_MIN_ROUNDS: usize = 2;
62const COMPACT_MAX_ROUNDS: usize = 6;
64const COMPACT_MIN_BLOCK_BITS: usize = 6;
66#[cfg(not(test))]
68const COMPACT_TOTAL_SCRATCH: usize = 1 << 14;
69
70#[cfg(test)]
74const COMPACT_TOTAL_SCRATCH: usize = 1 << 4;
75
76const fn compact_size_floor(requested_k: usize, log_chunk: usize) -> usize {
81 requested_k + log_chunk + COMPACT_MIN_BLOCK_BITS
82}
83
84fn bind_scratch_prefix<R: Field>(values: &mut Vec<R>, challenges: &[R]) {
86 for &challenge in challenges {
87 let half = values.len() / 2;
88 assert!(half > 0, "a compact prefix must leave a variable to bind");
89 let (low, high) = values.split_at_mut(half);
90 let (low_packed, low_tail) = R::Packing::pack_slice_with_suffix_mut(low);
91 let (high_packed, high_tail) = R::Packing::pack_slice_with_suffix(high);
92 let challenge_packed = R::Packing::broadcast(challenge);
93 for (low, &high) in low_packed.iter_mut().zip(high_packed) {
94 let old = *low;
95 *low = old + (high - old) * challenge_packed;
96 }
97 for (low, &high) in low_tail.iter_mut().zip(high_tail) {
98 *low += (high - *low) * challenge;
99 }
100 values.truncate(half);
101 }
102}
103
104fn materialize_compact_packing<EF, R, S>(
106 packing: &BitPacking<EF, S>,
107 offset: usize,
108 n: usize,
109 compact_rounds: usize,
110 challenges: &[EF],
111) -> Poly<R>
112where
113 EF: TowerLevel + Send + Sync,
114 R: IntoTranscriptField<EF> + Sync,
115 S: Borrow<[EF]>,
116{
117 let _span = tracing::debug_span!("compact_packing").entered();
118 let banks = 1usize << compact_rounds;
119 let tail_evals = 1usize << (n - compact_rounds);
120 let output_chunk = (COMPACT_TOTAL_SCRATCH >> compact_rounds).max(1);
121 let prefix_challenges = challenges.iter().copied().map(R::from).collect::<Vec<_>>();
122 let source = packing.poly().as_slice();
123 let mut output = R::zero_vec(tail_evals);
124 let output_chunk_len = output_chunk;
125 output
126 .par_chunks_mut(output_chunk)
127 .enumerate()
128 .for_each(|(chunk_index, output_chunk)| {
129 let start = chunk_index * output_chunk_len;
130 let mut scratch = Vec::with_capacity(banks * output_chunk.len());
131 for bank in 0..banks {
132 let source_start = offset + bank * tail_evals + start;
133 scratch.extend_from_slice(&source[source_start..source_start + output_chunk.len()]);
134 }
135 let mut converted = R::from_table(scratch);
136 bind_scratch_prefix(&mut converted, &prefix_challenges);
137 output_chunk.copy_from_slice(&converted[..output_chunk.len()]);
138 });
139 Poly::new(output)
140}
141
142pub(super) fn slot_packing<EF, R, S>(
147 packing: &BitPacking<EF, S>,
148 offset: usize,
149 len: usize,
150) -> Poly<R>
151where
152 EF: TowerLevel,
153 R: FromTable<EF>,
154 S: Borrow<[EF]>,
155{
156 let values = &packing.poly().as_slice()[offset..offset + len];
157 let mut slot = EF::zero_vec(len);
161 slot.par_chunks_mut(CHUNK)
162 .zip(values.par_chunks(CHUNK))
163 .for_each(|(slot, values)| slot.copy_from_slice(values));
164 Poly::new(R::from_table(slot))
165}
166
167#[derive(Clone, Debug)]
247pub struct BitRingSwitch<EF> {
248 point: Point<EF>,
252 eq_low: Poly<EF>,
254 successor: Option<usize>,
256}
257
258impl<EF: TowerLevel> BitRingSwitch<EF> {
259 pub const ABSORBED: usize = Coefficients::<EF>::LOG_DIMENSION;
261
262 pub fn new(r: &Point<EF>) -> Result<Self, BitRingSwitchError> {
275 if r.num_variables() < Self::ABSORBED {
276 return Err(BitRingSwitchError::PointTooNarrow {
277 needed: Self::ABSORBED,
278 actual: r.num_variables(),
279 });
280 }
281
282 let (_, low) = r.split_at(r.num_variables() - Self::ABSORBED);
284
285 Ok(Self {
286 eq_low: Poly::new_from_point(low.as_slice(), EF::ONE),
287 point: r.clone(),
288 successor: None,
289 })
290 }
291
292 pub fn with_successor(r: &Point<EF>, row_variables: usize) -> Result<Self, BitRingSwitchError> {
303 if row_variables > r.num_variables() {
304 return Err(BitRingSwitchError::RowVariables {
305 row_variables,
306 num_variables: r.num_variables(),
307 });
308 }
309 Ok(Self {
310 successor: Some(row_variables),
311 ..Self::new(r)?
312 })
313 }
314
315 pub fn num_variables(&self) -> usize {
320 self.point.num_variables() - Self::ABSORBED
321 }
322
323 pub(super) const fn point(&self) -> &Point<EF> {
325 &self.point
326 }
327
328 pub(super) fn high(&self) -> &[EF] {
332 &self.point.as_slice()[..self.num_variables()]
333 }
334
335 fn kept_row_variables(&self) -> Option<usize> {
339 self.successor
340 .filter(|&rows| rows > Self::ABSORBED)
341 .map(|rows| rows - Self::ABSORBED)
342 }
343
344 #[must_use]
348 pub fn sends_successor_tensors(&self) -> bool {
349 self.kept_row_variables().is_some()
350 }
351
352 pub(super) fn shape(&self) -> BitRingSwitchShape {
354 let num_variables = self.point.num_variables();
355 match self.successor {
356 Some(rows) if self.sends_successor_tensors() => {
357 BitRingSwitchShape::with_successor_rows(num_variables, rows)
358 }
359 _ => BitRingSwitchShape::new(num_variables),
360 }
361 }
362
363 pub fn batch<'a>(
370 &'a self,
371 r_batch: &Point<EF>,
372 ) -> Result<BitRingSwitchBatch<'a, EF>, BitRingSwitchError> {
373 if self.sends_successor_tensors() {
374 return Err(BitRingSwitchError::SuccessorBatching { expected: true });
375 }
376 self.batch_stage(r_batch, None)
377 }
378
379 pub fn batch_with_successor<'a>(
392 &'a self,
393 r_batch: &Point<EF>,
394 alpha: EF,
395 ) -> Result<BitRingSwitchBatch<'a, EF>, BitRingSwitchError> {
396 if !self.sends_successor_tensors() {
397 return Err(BitRingSwitchError::SuccessorBatching { expected: false });
398 }
399 self.batch_stage(r_batch, Some(alpha))
400 }
401
402 pub(super) fn batch_drawn(
404 &self,
405 r_batch: &Point<EF>,
406 alpha: Option<EF>,
407 ) -> Result<BitRingSwitchBatch<'_, EF>, BitRingSwitchError> {
408 alpha.map_or_else(
409 || self.batch(r_batch),
410 |alpha| self.batch_with_successor(r_batch, alpha),
411 )
412 }
413
414 fn batch_stage(
416 &self,
417 r_batch: &Point<EF>,
418 alpha: Option<EF>,
419 ) -> Result<BitRingSwitchBatch<'_, EF>, BitRingSwitchError> {
420 if r_batch.num_variables() != Self::ABSORBED {
421 return Err(BitRingSwitchError::BatchWidthMismatch {
422 expected: Self::ABSORBED,
423 actual: r_batch.num_variables(),
424 });
425 }
426 Ok(BitRingSwitchBatch {
427 reduction: self,
428 eq_batch: Poly::new_from_point(r_batch.as_slice(), EF::ONE),
429 alpha,
430 })
431 }
432
433 fn prefix_limit(&self) -> usize {
439 let limit = self.num_variables();
440 self.successor
441 .map_or(limit, |rows| limit.min(self.point.num_variables() - rows))
442 }
443
444 pub(super) fn fixed_prefix(&self) -> (usize, usize) {
448 let mut prefix = 0;
450 let mut address = 0usize;
451 for &coordinate in &self.high()[..self.prefix_limit()] {
452 if coordinate == EF::ZERO {
454 address <<= 1;
455 } else if coordinate == EF::ONE {
457 address = (address << 1) | 1;
458 } else {
460 break;
461 }
462 prefix += 1;
464 }
465 (prefix, address)
467 }
468
469 pub(super) fn support(&self) -> (usize, usize, FactoredEquality<EF>) {
496 let (prefix, address) = self.fixed_prefix();
499
500 let table = FactoredEquality::new(&self.high()[prefix..], LOG_CHUNK);
502 (prefix, address * table.num_evals(), table)
503 }
504
505 fn restricted_packing<R: FromTable<EF>, S: Borrow<[EF]>>(
512 &self,
513 packing: &BitPacking<EF, S>,
514 ) -> Poly<R> {
515 let (prefix, address) = self.fixed_prefix();
517 let len = 1usize << (self.num_variables() - prefix);
518 slot_packing(packing, address * len, len)
519 }
520
521 fn restore_prefix(&self, point: &Point<EF>) -> Point<EF> {
523 let (prefix, _) = self.fixed_prefix();
525 let mut coordinates = self.high()[..prefix].to_vec();
526
527 coordinates.extend_from_slice(point.as_slice());
529 Point::new(coordinates)
530 }
531
532 pub fn tensor<S: Borrow<[EF]>>(
561 &self,
562 packing: &BitPacking<EF, S>,
563 ) -> Result<BitTensor<EF>, BitRingSwitchError>
564 where
565 EF: Send + Sync,
566 {
567 self.check_width(packing.num_variables())?;
568 let (_, offset, equality) = self.support();
569 Ok(Self::tensor_over(packing, offset, &equality))
570 }
571
572 pub(super) fn tensor_over<S: Borrow<[EF]>>(
576 packing: &BitPacking<EF, S>,
577 offset: usize,
578 equality: &FactoredEquality<EF>,
579 ) -> BitTensor<EF>
580 where
581 EF: Send + Sync,
582 {
583 let mut banks = Self::bank_tensors_over(packing, offset, 1, equality);
584 banks.pop().expect("one bank has one element")
585 }
586
587 fn bank_tensors_over<S: Borrow<[EF]>>(
607 packing: &BitPacking<EF, S>,
608 offset: usize,
609 banks: usize,
610 equality: &FactoredEquality<EF>,
611 ) -> Vec<BitTensor<EF>>
612 where
613 EF: Send + Sync,
614 {
615 let values = &packing.poly().as_slice()[offset..offset + banks * equality.num_evals()];
616 let blocks = equality.outer().len();
617 let inner = LeftFactors::new(equality.inner());
619
620 values
621 .par_chunks(equality.block_len())
622 .enumerate()
623 .par_fold_reduce(
624 || (alloc::vec![BitTensor::zero(); banks], None),
626 |(mut totals, mut scratch), (index, values)| {
627 let (bank, block) = (index / blocks, index % blocks);
628 totals[bank].add_scaled_columns(
629 &inner.sum(values, &mut scratch),
630 equality.outer()[block],
631 );
632 (totals, scratch)
633 },
634 |(mut totals, scratch), (partials, _)| {
635 for (total, partial) in totals.iter_mut().zip(partials) {
637 *total += partial;
638 }
639 (totals, scratch)
640 },
641 )
642 .0
643 }
644
645 #[must_use]
658 pub fn incoming_claim(&self, tensor: &BitTensor<EF>) -> EF {
659 tensor
660 .columns()
661 .iter()
662 .zip(self.eq_low.as_slice())
663 .map(|(&column, &weight)| column * weight)
664 .sum()
665 }
666
667 pub fn successor_tensors<S: Borrow<[EF]>>(
688 &self,
689 packing: &BitPacking<EF, S>,
690 ) -> Result<Option<SuccessorTensors<EF>>, BitRingSwitchError>
691 where
692 EF: Send + Sync,
693 {
694 self.check_width(packing.num_variables())?;
695 if self.kept_row_variables().is_none() {
696 return Ok(None);
697 }
698 let (_, offset, equality) = self.support();
699 Ok(self.successor_tensors_over(packing, offset, &equality))
700 }
701
702 pub(super) fn successor_tensors_over<S: Borrow<[EF]>>(
706 &self,
707 packing: &BitPacking<EF, S>,
708 offset: usize,
709 equality: &FactoredEquality<EF>,
710 ) -> Option<SuccessorTensors<EF>>
711 where
712 EF: Send + Sync,
713 {
714 let mut banks = self.bank_successor_tensors_over(packing, offset, 1, equality)?;
715 Some(banks.pop().expect("one bank has one pair of elements"))
716 }
717
718 fn bank_successor_tensors_over<S: Borrow<[EF]>>(
724 &self,
725 packing: &BitPacking<EF, S>,
726 offset: usize,
727 banks: usize,
728 equality: &FactoredEquality<EF>,
729 ) -> Option<Vec<SuccessorTensors<EF>>>
730 where
731 EF: Send + Sync,
732 {
733 let kept = self.kept_row_variables()?;
734
735 let block = equality.block_len();
737 let blocks = equality.outer().len();
738 let values = &packing.poly().as_slice()[offset..offset + banks * equality.num_evals()];
739 let column = 1usize << kept;
741 let max = column - 1;
742 let inner = equality.inner();
743
744 let shifted = (0..block)
749 .map(|j| if j & max == 0 { EF::ZERO } else { inner[j - 1] })
750 .collect::<Vec<_>>();
751 let shifted = LeftFactors::new(&shifted);
752
753 let zero = SuccessorTensors {
754 carry: BitTensor::zero(),
755 last: BitTensor::zero(),
756 };
757 let (totals, _) = values.par_chunks(block).enumerate().par_fold_reduce(
758 || (alloc::vec![zero.clone(); banks], None),
760 |(mut totals, mut scratch), (index, values)| {
761 let (bank, index) = (index / blocks, index % blocks);
763 let SuccessorTensors { carry, last } = &mut totals[bank];
764 let weight = equality.outer()[index];
765 let start = (index * block) & max;
766 if start != 0 {
769 carry.add_exterior_product(equality.before_block(index), values[0]);
770 }
771 carry.add_scaled_columns(&shifted.sum(values, &mut scratch), weight);
772 for j in (max - start..values.len()).step_by(column) {
775 last.add_exterior_product(weight * inner[j], values[j]);
776 }
777 (totals, scratch)
778 },
779 |(mut totals, scratch), (partials, _)| {
780 for (total, partial) in totals.iter_mut().zip(partials) {
782 total.carry += partial.carry;
783 total.last += partial.last;
784 }
785 (totals, scratch)
786 },
787 );
788 Some(totals)
789 }
790
791 fn successor_low_weights(&self, row_variables: usize) -> Vec<EF> {
809 let eq_low = self.eq_low.as_slice();
810 if self.sends_successor_tensors() {
811 return (0..eq_low.len())
812 .map(|v| if v == 0 { EF::ZERO } else { eq_low[v - 1] })
813 .collect();
814 }
815
816 let last = (1usize << row_variables) - 1;
817 (0..eq_low.len())
818 .map(|v| {
819 let row = v & last;
820 let settled = if row == 0 { EF::ZERO } else { eq_low[v - 1] };
821 let repeated = if row == last { eq_low[v] } else { EF::ZERO };
822 settled + repeated
823 })
824 .collect()
825 }
826
827 pub fn successor_claim(
846 &self,
847 tensor: &BitTensor<EF>,
848 successor: Option<&SuccessorTensors<EF>>,
849 ) -> Result<EF, BitRingSwitchError> {
850 let rows = self.successor.ok_or(BitRingSwitchError::NoSuccessorView)?;
851 assert_eq!(
852 successor.is_some(),
853 self.sends_successor_tensors(),
854 "the successor elements are supplied exactly when the reduction sends them"
855 );
856
857 let settled: EF = tensor
858 .columns()
859 .iter()
860 .zip(self.successor_low_weights(rows))
861 .map(|(&column, weight)| column * weight)
862 .sum();
863 Ok(successor.map_or(settled, |elements| {
864 let d = BitTensor::<EF>::DIMENSION;
865 let ripple = elements.carry.column(0) + elements.last.column(d - 1);
866 settled + self.eq_low.as_slice()[d - 1] * ripple
867 }))
868 }
869
870 fn check_width(&self, actual: usize) -> Result<(), BitRingSwitchError> {
872 if actual == self.num_variables() {
873 Ok(())
874 } else {
875 Err(BitRingSwitchError::WidthMismatch {
876 expected: self.num_variables(),
877 actual,
878 })
879 }
880 }
881
882 fn equality_element(&self, r_prime: &Point<EF>) -> BitTensor<EF> {
903 let (prefix, _) = self.fixed_prefix();
906 let mut element = BitTensor::one();
907 for (&a, &b) in self.high()[prefix..].iter().zip(r_prime.as_slice()) {
908 let mut agree = element.clone();
909 agree.scale_columns(a);
910 agree.scale_rows(b);
911
912 element.scale_columns(EF::ONE - a);
913 element.scale_rows(EF::ONE - b);
914 element += agree;
915 }
916 element
917 }
918
919 fn successor_elements(&self, r_prime: &Point<EF>) -> (BitTensor<EF>, BitTensor<EF>) {
941 let kept = self
942 .kept_row_variables()
943 .expect("only a reduction sending successor elements closes on them");
944 let (prefix, _) = self.fixed_prefix();
945 let rest = &self.high()[prefix..];
946 let (selector, rho) = rest.split_at(rest.len() - kept);
947 let (r_selector, r_rows) = r_prime.as_slice().split_at(r_prime.num_variables() - kept);
948
949 let mut carry = BitTensor::successor_element(rho, r_rows);
950 let mut last = BitTensor::exterior_product(
951 rho.iter().copied().product(),
952 r_rows.iter().copied().product(),
953 );
954 for (&a, &b) in selector.iter().zip(r_selector) {
955 carry.mul_equality_factor(a, b);
956 last.mul_equality_factor(a, b);
957 }
958 (carry, last)
959 }
960
961 fn production_depth(&self, n: usize) -> usize {
967 (COMPACT_PRODUCTION_MIN_ROUNDS..=COMPACT_PRODUCTION_ROUNDS)
968 .rev()
969 .find(|&depth| self.compact_depth_is_eligible(n, depth, false))
970 .unwrap_or(COMPACT_PRODUCTION_MIN_ROUNDS)
971 }
972
973 fn compact_depth_is_eligible(&self, n: usize, requested_k: usize, force: bool) -> bool {
975 if requested_k == 0 || requested_k > COMPACT_MAX_ROUNDS || n < requested_k {
976 return false;
977 }
978 if let Some(kept_rows) = self.kept_row_variables()
979 && (n < kept_rows || requested_k > n - kept_rows)
980 {
981 return false;
982 }
983 force || n >= compact_size_floor(requested_k, LOG_CHUNK)
987 }
988}
989
990#[derive(Clone, Debug)]
995pub struct BitRingSwitchBatch<'a, EF> {
996 reduction: &'a BitRingSwitch<EF>,
998 eq_batch: Poly<EF>,
1000 alpha: Option<EF>,
1002}
1003
1004impl<EF: TowerLevel> BitRingSwitchBatch<'_, EF> {
1005 pub fn weights(&self) -> Poly<EF>
1033 where
1034 EF: Send + Sync,
1035 {
1036 let (_, _, equality) = self.reduction.support();
1037 self.weights_over(&equality)
1038 }
1039
1040 pub(super) fn weights_over<R>(&self, equality: &FactoredEquality<EF>) -> Poly<R>
1048 where
1049 EF: Send + Sync,
1050 R: IntoTranscriptField<EF> + Sync,
1051 {
1052 let mut table = Poly::zero(equality.num_variables());
1053 let batching: Vec<R> = self
1054 .eq_batch
1055 .as_slice()
1056 .iter()
1057 .copied()
1058 .map(R::from)
1059 .collect();
1060 let sums = CoordinateSums::<EF, R>::new(&batching);
1061
1062 table
1073 .as_mut_slice()
1074 .par_chunks_mut(equality.block_len())
1075 .zip(equality.outer().par_iter())
1076 .for_each_init(
1077 || sums.clone(),
1079 |scaled, (block, &weight)| {
1080 scaled_sums_into(scaled, &sums, weight);
1081 scaled.apply_into(equality.inner(), block);
1082 },
1083 );
1084
1085 let Some(alpha) = self.alpha else {
1086 return table;
1087 };
1088 let kept = self
1089 .reduction
1090 .kept_row_variables()
1091 .expect("a batch carries alpha only for a reduction sending successor elements");
1092 let column = 1usize << kept;
1093 let max = column - 1;
1094 let alpha = R::from(alpha);
1095 let alpha_squared = alpha.square();
1096
1097 let stride = equality.block_len().max(column).min(table.num_evals());
1105 debug_assert!(column.is_power_of_two() && stride.is_multiple_of(column));
1106 debug_assert!(table.num_evals().is_multiple_of(stride));
1107 table
1108 .as_mut_slice()
1109 .par_chunks_mut(stride)
1110 .for_each(|part| {
1111 for entries in part.chunks_mut(column) {
1112 for row in (0..column).rev() {
1113 let settled = entries[row];
1114 let mut value = settled;
1115 if row != 0 {
1116 value += alpha * entries[row - 1];
1117 }
1118 if row == max {
1119 value += alpha_squared * settled;
1120 }
1121 entries[row] = value;
1122 }
1123 }
1124 });
1125 table
1126 }
1127
1128 fn weights_from_generators<R>(
1130 &self,
1131 equality: &FactoredEquality<EF>,
1132 generators: &[R],
1133 ) -> Poly<R>
1134 where
1135 EF: Send + Sync,
1136 R: IntoTranscriptField<EF> + Sync,
1137 {
1138 let sums = CoordinateSums::<EF, R>::new(generators);
1139 let mut table = Poly::zero(equality.num_variables());
1140 table
1141 .as_mut_slice()
1142 .par_chunks_mut(equality.block_len())
1143 .zip(equality.outer().par_iter())
1144 .for_each_init(
1145 || sums.clone(),
1146 |scaled, (block, &weight)| {
1147 scaled_sums_into(scaled, &sums, weight);
1148 scaled.apply_into(equality.inner(), block);
1149 },
1150 );
1151
1152 let Some(alpha) = self.alpha else {
1153 return table;
1154 };
1155 let kept = self
1156 .reduction
1157 .kept_row_variables()
1158 .expect("a compact alpha batch has successor rows");
1159 let column = 1usize << kept;
1160 let alpha = R::from(alpha);
1161 let alpha_squared = alpha.square();
1162 let max = column - 1;
1163 let stride = equality.block_len().max(column).min(table.num_evals());
1167 debug_assert!(
1168 column.is_power_of_two() && stride.is_multiple_of(column),
1169 "the compact tail is shorter than one column; see compact_depth_is_eligible"
1170 );
1171 debug_assert!(
1172 table.num_evals().is_multiple_of(stride),
1173 "the compact tail does not split into whole strides; see compact_depth_is_eligible"
1174 );
1175 table
1176 .as_mut_slice()
1177 .par_chunks_mut(stride)
1178 .for_each(|part| {
1179 for entries in part.chunks_mut(column) {
1180 for row in (0..column).rev() {
1181 let settled = entries[row];
1182 let mut value = settled;
1183 if row != 0 {
1184 value += alpha * entries[row - 1];
1185 }
1186 if row == max {
1187 value += alpha_squared * settled;
1188 }
1189 entries[row] = value;
1190 }
1191 }
1192 });
1193 table
1194 }
1195
1196 #[must_use]
1209 pub fn initial_sum(
1210 &self,
1211 tensor: &BitTensor<EF>,
1212 successor: Option<&SuccessorTensors<EF>>,
1213 ) -> EF {
1214 let sum = self.batch_rows(tensor);
1215 match (self.alpha, successor) {
1216 (None, None) => sum,
1217 (Some(alpha), Some(elements)) => {
1218 sum + self.successor_rows(alpha, &elements.carry, &elements.last)
1219 }
1220 _ => panic!("the successor elements are supplied exactly when the batch carries alpha"),
1221 }
1222 }
1223
1224 pub fn closing_weight(&self, r_prime: &Point<EF>) -> Result<EF, BitRingSwitchError> {
1235 let (prefix, _) = self.reduction.fixed_prefix();
1237 self.reduction
1238 .check_width(prefix + r_prime.num_variables())?;
1239 let equality = self.batch_rows(&self.reduction.equality_element(r_prime));
1240 Ok(self.alpha.map_or(equality, |alpha| {
1241 let (carry, last) = self.reduction.successor_elements(r_prime);
1242 equality + self.successor_rows(alpha, &carry, &last)
1243 }))
1244 }
1245
1246 fn batch_rows(&self, tensor: &BitTensor<EF>) -> EF {
1250 tensor
1251 .rows()
1252 .iter()
1253 .zip(self.eq_batch.as_slice())
1254 .map(|(&row, &weight)| row * weight)
1255 .sum()
1256 }
1257
1258 fn successor_rows(&self, alpha: EF, carry: &BitTensor<EF>, last: &BitTensor<EF>) -> EF {
1260 alpha * (self.batch_rows(carry) + alpha * self.batch_rows(last))
1261 }
1262}
1263
1264#[derive(Debug, thiserror::Error, PartialEq, Eq)]
1266pub enum BitRingSwitchError {
1267 #[error("the evaluation point names {actual} variables, at least {needed} are needed")]
1269 PointTooNarrow {
1270 needed: usize,
1272 actual: usize,
1274 },
1275 #[error("the batching challenge names {actual} variables, expected {expected}")]
1280 BatchWidthMismatch {
1281 expected: usize,
1283 actual: usize,
1285 },
1286 #[error("the reduction runs over {expected} variables, got {actual}")]
1288 WidthMismatch {
1289 expected: usize,
1291 actual: usize,
1293 },
1294 #[error("the successor view names {row_variables} row variables, the point {num_variables}")]
1296 RowVariables {
1297 row_variables: usize,
1299 num_variables: usize,
1301 },
1302 #[error(
1304 "the batching stage does not fit the reduction, successor challenge expected: {expected}"
1305 )]
1306 SuccessorBatching {
1307 expected: bool,
1309 },
1310 #[error("the reduction was set up without a successor view")]
1312 NoSuccessorView,
1313 #[error("a batch of ring-switch claims needs at least one claim")]
1315 NoClaims,
1316}
1317
1318#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1329#[serde(bound(serialize = "EF: TowerLevel", deserialize = "EF: TowerLevel"))]
1330pub struct SuccessorTensors<EF> {
1331 pub carry: BitTensor<EF>,
1333 pub last: BitTensor<EF>,
1335}
1336
1337#[derive(Clone, Debug, Serialize, Deserialize)]
1348#[serde(bound(serialize = "EF: TowerLevel", deserialize = "EF: TowerLevel"))]
1349pub struct BitRingSwitchProof<EF> {
1350 pub tensor: BitTensor<EF>,
1352 pub successor: Option<SuccessorTensors<EF>>,
1356 pub sumcheck: SumcheckData<EF, EF>,
1358 pub final_eval: EF,
1360}
1361
1362#[derive(Debug, thiserror::Error, PartialEq, Eq)]
1364#[non_exhaustive]
1365pub enum BitRingSwitchProofError {
1366 #[error(transparent)]
1368 Width(#[from] TranscriptWidth),
1369
1370 #[error(transparent)]
1372 Reduction(#[from] BitRingSwitchError),
1373
1374 #[error("the claimed evaluation is not the column reading of the tensor element")]
1376 ClaimMismatch,
1377
1378 #[error("the claimed successor evaluation is not the column reading of the elements")]
1380 SuccessorClaimMismatch,
1381
1382 #[error("no claimed evaluation was supplied")]
1384 NoReading,
1385
1386 #[error("a successor evaluation was supplied to a reduction without a successor view")]
1388 NoSuccessorView,
1389
1390 #[error(transparent)]
1392 Sumcheck(#[from] SumcheckError),
1393
1394 #[error("the surviving claim does not close the sumcheck")]
1396 FinalCheck,
1397
1398 #[error("the batch carries {actual} claims, its setup {expected}")]
1400 ClaimCount {
1401 expected: usize,
1403 actual: usize,
1405 },
1406
1407 #[error("the sumcheck carries {actual} proof-of-work witnesses, expected none")]
1409 NonEmptyPowWitnesses {
1410 actual: usize,
1412 },
1413}
1414
1415impl<EF: TranscriptField + TowerLevel> BitRingSwitch<EF> {
1459 pub fn prove<R, Challenger, S>(
1488 &self,
1489 packing: &BitPacking<EF, S>,
1490 challenger: &mut Challenger,
1491 ) -> (BitRingSwitchProof<EF>, Point<EF>, EF)
1492 where
1493 EF: Send + Sync,
1494 R: IntoTranscriptField<EF>,
1495 Challenger: FieldChallenger<EF> + GrindingChallenger<Witness = EF>,
1496 S: Borrow<[EF]>,
1497 {
1498 let (prefix, _) = self.fixed_prefix();
1499 let depth = self.production_depth(self.num_variables() - prefix);
1500 self.prove_with_compact_depth::<R, Challenger, S>(packing, challenger, depth, false)
1501 }
1502
1503 pub(crate) fn prove_with_compact_depth<R, Challenger, S>(
1506 &self,
1507 packing: &BitPacking<EF, S>,
1508 challenger: &mut Challenger,
1509 requested_k: usize,
1510 force: bool,
1511 ) -> (BitRingSwitchProof<EF>, Point<EF>, EF)
1512 where
1513 EF: Send + Sync,
1514 R: IntoTranscriptField<EF>,
1515 Challenger: FieldChallenger<EF> + GrindingChallenger<Witness = EF>,
1516 S: Borrow<[EF]>,
1517 {
1518 let max_rounds = self.num_variables();
1519 assert_eq!(
1520 packing.num_variables(),
1521 max_rounds,
1522 "the packing must have the {max_rounds} variables the evaluation point leaves"
1523 );
1524
1525 let (prefix, address) = self.fixed_prefix();
1528 let n = max_rounds - prefix;
1529 let compact = self.compact_depth_is_eligible(n, requested_k, force);
1530 let offset = address * (1usize << n);
1531 let equality = (!compact).then(|| FactoredEquality::new(&self.high()[prefix..], LOG_CHUNK));
1532 let compact_equality =
1533 compact.then(|| FactoredEquality::new(&self.high()[prefix + requested_k..], LOG_CHUNK));
1534 let (tensor, successor, bank_tensors, bank_successors) = if compact {
1535 let _span = tracing::debug_span!("compact_stats", depth = requested_k).entered();
1536 let banks = 1usize << requested_k;
1537 let head = &self.high()[prefix..prefix + requested_k];
1538 let tail_equality = compact_equality.as_ref().expect("compact equality exists");
1539 let head_equality = Poly::new_from_point(head, EF::ONE);
1540 let bank_tensors = Self::bank_tensors_over(packing, offset, banks, tail_equality);
1541 let bank_successors =
1542 self.bank_successor_tensors_over(packing, offset, banks, tail_equality);
1543 let mut tensor = BitTensor::zero();
1544 for (&weight, bank) in head_equality.as_slice().iter().zip(&bank_tensors) {
1545 tensor.add_scaled_columns(bank, weight);
1546 }
1547 let successor = bank_successors.as_ref().map(|banks| {
1548 let mut carry = BitTensor::zero();
1549 let mut last = BitTensor::zero();
1550 for (&weight, bank) in head_equality.as_slice().iter().zip(banks) {
1551 carry.add_scaled_columns(&bank.carry, weight);
1552 last.add_scaled_columns(&bank.last, weight);
1553 }
1554 SuccessorTensors { carry, last }
1555 });
1556 (tensor, successor, Some(bank_tensors), bank_successors)
1557 } else {
1558 (
1559 Self::tensor_over(
1560 packing,
1561 offset,
1562 equality.as_ref().expect("dense equality exists"),
1563 ),
1564 self.successor_tensors_over(
1565 packing,
1566 offset,
1567 equality.as_ref().expect("dense equality exists"),
1568 ),
1569 None,
1570 None,
1571 )
1572 };
1573
1574 let mut transcript =
1577 BitRingSwitchProverTranscript::<Challenger, EF>::new(challenger, self.shape());
1578 let (r_batch, alpha) = transcript.statement(
1579 &self.point,
1580 tensor.rows(),
1581 successor
1582 .as_ref()
1583 .map(|elements| (elements.carry.rows(), elements.last.rows())),
1584 );
1585
1586 let batch = self
1588 .batch_drawn(&r_batch, alpha)
1589 .expect("the transcript draws what the reduction's kind batches with");
1590 let mut sumcheck = SumcheckData::default();
1591
1592 let (r_prime, final_eval) = if compact {
1595 let factors_span =
1596 tracing::debug_span!("compact_factors", depth = requested_k).entered();
1597 let bank_tensors = bank_tensors.expect("compact banks are present");
1598 let bank_successors = bank_successors.as_ref();
1599 let banks = 1usize << requested_k;
1600 let head = &self.high()[prefix..prefix + requested_k];
1601 let tail_equality = compact_equality.as_ref().expect("compact equality exists");
1602 let head_equality = Poly::new_from_point(head, EF::ONE);
1603 let mut batching = Vec::with_capacity(Coefficients::<EF>::DIMENSION);
1604 batching.extend(batch.eq_batch.as_slice().iter().copied().map(R::from));
1605 let coordinate_sums = CoordinateSums::<EF, R>::new(&batching);
1606 let mut factors = Vec::with_capacity(banks * BitTensor::<EF>::DIMENSION);
1607 let alpha = batch.alpha.map(R::from);
1608 let alpha_squared = alpha.map(|value| value.square());
1609 for bank in 0..banks {
1610 let successor = bank_successors.map(|all| &all[bank]);
1611 for coordinate in 0..BitTensor::<EF>::DIMENSION {
1612 let mut value = R::from(bank_tensors[bank].rows()[coordinate]);
1613 if let (Some(elements), Some(alpha), Some(alpha_squared)) =
1614 (successor, alpha, alpha_squared)
1615 {
1616 value += alpha * R::from(elements.carry.rows()[coordinate])
1617 + alpha_squared * R::from(elements.last.rows()[coordinate]);
1618 }
1619 factors.push(value);
1620 }
1621 }
1622 let mut generators = Vec::with_capacity(banks * BitTensor::<EF>::DIMENSION);
1623 for &weight in head_equality.as_slice() {
1624 for coordinate in 0..BitTensor::<EF>::DIMENSION {
1625 let mut basis = Coefficients::<EF>::zero();
1626 basis.set(coordinate);
1627 generators.push(coordinate_sums.sum(weight * basis.element()));
1628 }
1629 }
1630 drop(bank_tensors);
1631 drop(factors_span);
1632 transcript.batched_sumcheck(|challenger| {
1633 let mut rounds_transcript = ProverTranscript::<Challenger, EF, EF>::new(
1634 challenger,
1635 SumcheckShape::new(n, 0, Basis::Evaluation),
1636 );
1637 let head_span = tracing::debug_span!("compact_head", depth = requested_k).entered();
1638 let mut compact_prover = ReprSumcheckProver::<EF, EF, R>::from_repr_tables(
1639 VariableOrder::Prefix,
1640 Poly::new(factors),
1641 Poly::new(generators),
1642 batch.initial_sum(&tensor, successor.as_ref()),
1643 );
1644 let mut challenges = Vec::with_capacity(n);
1645 for _ in 0..requested_k {
1646 challenges.push(compact_prover.round(&mut sumcheck, &mut rounds_transcript));
1647 }
1648 let handoff_sum = compact_prover.claimed_sum();
1649 let generators = compact_prover.weights().into_evals();
1650 drop(compact_prover);
1651 drop(head_span);
1652
1653 let bound_packing = materialize_compact_packing::<EF, R, _>(
1654 packing,
1655 offset,
1656 n,
1657 requested_k,
1658 &challenges,
1659 );
1660 let bound_weights = {
1661 let _span =
1662 tracing::debug_span!("compact_weights", depth = requested_k).entered();
1663 batch.weights_from_generators::<R>(tail_equality, &generators)
1664 };
1665 let _span = tracing::debug_span!("compact_tail", depth = requested_k).entered();
1666 let mut tail_prover = ReprSumcheckProver::<EF, EF, R>::from_repr_tables(
1667 VariableOrder::Prefix,
1668 bound_packing,
1669 bound_weights,
1670 handoff_sum,
1671 );
1672 for _ in requested_k..n {
1673 challenges.push(tail_prover.round(&mut sumcheck, &mut rounds_transcript));
1674 }
1675 let final_eval = tail_prover.evals().as_slice()[0].into_transcript();
1676 rounds_transcript.finish();
1677 (Point::new(challenges), final_eval)
1678 })
1679 } else {
1680 let restricted = self.restricted_packing::<R, _>(packing);
1681 let rounds = restricted.num_variables();
1682 let weights =
1683 batch.weights_over::<R>(equality.as_ref().expect("dense equality exists"));
1684 let mut prover = ReprSumcheckProver::<EF, EF, R>::from_repr_tables(
1685 VariableOrder::Prefix,
1686 restricted,
1687 weights,
1688 batch.initial_sum(&tensor, successor.as_ref()),
1689 );
1690 let r_prime = transcript.batched_sumcheck(|challenger| {
1691 prover.compute_sumcheck_polynomials(&mut sumcheck, challenger, rounds, 0)
1692 });
1693 let final_eval = prover.evals().as_slice()[0].into_transcript();
1694 (r_prime, final_eval)
1695 };
1696
1697 transcript.surviving_claim(final_eval);
1700 transcript.finish();
1701
1702 (
1703 BitRingSwitchProof {
1704 tensor,
1705 successor,
1706 sumcheck,
1707 final_eval,
1708 },
1709 self.restore_prefix(&r_prime),
1710 final_eval,
1711 )
1712 }
1713
1714 pub fn verify<Challenger>(
1722 &self,
1723 proof: &BitRingSwitchProof<EF>,
1724 claimed_sum: EF,
1725 challenger: &mut Challenger,
1726 ) -> Result<(Point<EF>, EF), BitRingSwitchProofError>
1727 where
1728 Challenger: FieldChallenger<EF> + GrindingChallenger<Witness = EF>,
1729 {
1730 self.verify_readings(proof, Some(claimed_sum), None, challenger)
1731 }
1732
1733 pub fn verify_readings<Challenger>(
1758 &self,
1759 proof: &BitRingSwitchProof<EF>,
1760 current: Option<EF>,
1761 next: Option<EF>,
1762 challenger: &mut Challenger,
1763 ) -> Result<(Point<EF>, EF), BitRingSwitchProofError>
1764 where
1765 Challenger: FieldChallenger<EF> + GrindingChallenger<Witness = EF>,
1766 {
1767 self.check_structure(proof, current, next)?;
1770
1771 let (prefix, _) = self.fixed_prefix();
1774 let rounds = self.num_variables() - prefix;
1775 let mut transcript =
1776 BitRingSwitchVerifierTranscript::<Challenger, EF>::new(challenger, self.shape());
1777 let (r_batch, alpha) = transcript.statement(
1778 &self.point,
1779 proof.tensor.rows(),
1780 proof
1781 .successor
1782 .as_ref()
1783 .map(|elements| (elements.carry.rows(), elements.last.rows())),
1784 )?;
1785
1786 if let Err(error) = self.check_readings(proof, current, next) {
1787 transcript.abort();
1788 return Err(error);
1789 }
1790
1791 let batch = self
1792 .batch_drawn(&r_batch, alpha)
1793 .expect("the transcript draws what the reduction's kind batches with");
1794
1795 let mut sum = batch.initial_sum(&proof.tensor, proof.successor.as_ref());
1798 let replay = transcript.batched_sumcheck(|challenger| {
1799 proof
1800 .sumcheck
1801 .verify_rounds(challenger, &mut sum, rounds, 0, Basis::Evaluation)
1802 });
1803 let r_prime = match replay {
1804 Ok(point) => point,
1805 Err(error) => {
1806 transcript.abort();
1807 return Err(error.into());
1808 }
1809 };
1810
1811 transcript.surviving_claim(proof.final_eval);
1812 transcript.finish();
1813
1814 let closing = batch.closing_weight(&r_prime)?;
1817 if sum != closing * proof.final_eval {
1818 return Err(BitRingSwitchProofError::FinalCheck);
1819 }
1820
1821 Ok((self.restore_prefix(&r_prime), proof.final_eval))
1822 }
1823
1824 fn check_structure(
1826 &self,
1827 proof: &BitRingSwitchProof<EF>,
1828 current: Option<EF>,
1829 next: Option<EF>,
1830 ) -> Result<(), BitRingSwitchProofError> {
1831 self.check_elements(&proof.tensor, proof.successor.as_ref(), current, next)?;
1832 check_no_grinding(&proof.sumcheck)
1833 }
1834
1835 pub(super) fn check_elements(
1839 &self,
1840 tensor: &BitTensor<EF>,
1841 successor: Option<&SuccessorTensors<EF>>,
1842 current: Option<EF>,
1843 next: Option<EF>,
1844 ) -> Result<(), BitRingSwitchProofError> {
1845 let successor_elements = successor
1847 .into_iter()
1848 .flat_map(|elements| [&elements.carry, &elements.last]);
1849 if let Some(malformed) = core::iter::once(tensor)
1850 .chain(successor_elements)
1851 .find(|element| !element.is_well_formed())
1852 {
1853 return Err(TranscriptWidth::TensorRows {
1854 expected: BitTensor::<EF>::DIMENSION,
1855 actual: malformed.rows().len(),
1856 }
1857 .into());
1858 }
1859 let expected = self.sends_successor_tensors();
1861 if successor.is_some() != expected {
1862 return Err(TranscriptWidth::successor_elements(expected, successor.is_some()).into());
1863 }
1864 if current.is_none() && next.is_none() {
1865 return Err(BitRingSwitchProofError::NoReading);
1866 }
1867 if next.is_some() && self.successor.is_none() {
1868 return Err(BitRingSwitchProofError::NoSuccessorView);
1869 }
1870 Ok(())
1871 }
1872
1873 fn check_readings(
1875 &self,
1876 proof: &BitRingSwitchProof<EF>,
1877 current: Option<EF>,
1878 next: Option<EF>,
1879 ) -> Result<(), BitRingSwitchProofError> {
1880 self.check_element_readings(&proof.tensor, proof.successor.as_ref(), current, next)
1881 }
1882
1883 pub(super) fn check_element_readings(
1885 &self,
1886 tensor: &BitTensor<EF>,
1887 successor: Option<&SuccessorTensors<EF>>,
1888 current: Option<EF>,
1889 next: Option<EF>,
1890 ) -> Result<(), BitRingSwitchProofError> {
1891 if current.is_some_and(|claim| self.incoming_claim(tensor) != claim) {
1894 return Err(BitRingSwitchProofError::ClaimMismatch);
1895 }
1896 if let Some(claim) = next
1898 && self.successor_claim(tensor, successor)? != claim
1899 {
1900 return Err(BitRingSwitchProofError::SuccessorClaimMismatch);
1901 }
1902 Ok(())
1903 }
1904}
1905
1906pub(super) const fn check_no_grinding<EF: Field>(
1908 sumcheck: &SumcheckData<EF, EF>,
1909) -> Result<(), BitRingSwitchProofError> {
1910 if sumcheck.pow_witnesses.is_empty() {
1911 Ok(())
1912 } else {
1913 Err(BitRingSwitchProofError::NonEmptyPowWitnesses {
1914 actual: sumcheck.pow_witnesses.len(),
1915 })
1916 }
1917}
1918
1919#[cfg(test)]
1920mod tests {
1921 use alloc::vec;
1922 use alloc::vec::Vec;
1923
1924 use p3_binary_field::{BinaryChallenger, BinaryField16, BinaryField128, Ghash128};
1925 use p3_challenger::{CanSample, HashChallenger};
1926 use p3_field::{Field, PrimeCharacteristicRing};
1927 use p3_keccak::Keccak256Hash;
1928 use proptest::prelude::*;
1929 use rand::rngs::SmallRng;
1930 use rand::{RngExt, SeedableRng};
1931
1932 use super::*;
1933
1934 type EF = BinaryField16;
1935 type Chal = BinaryChallenger<EF, HashChallenger<u8, Keccak256Hash, 32>>;
1936 type WideChal = BinaryChallenger<BinaryField128, HashChallenger<u8, Keccak256Hash, 32>>;
1937
1938 fn challenger() -> Chal {
1940 Chal::from_hasher(Vec::new(), Keccak256Hash)
1941 }
1942
1943 fn wide_challenger() -> WideChal {
1945 WideChal::from_hasher(Vec::new(), Keccak256Hash)
1946 }
1947
1948 fn bits(seed: u64, bytes: usize) -> Vec<u8> {
1950 let mut rng = SmallRng::seed_from_u64(seed);
1951 (0..bytes).map(|_| rng.random::<u8>()).collect()
1952 }
1953
1954 fn fixture(
1959 seed: u64,
1960 bytes: usize,
1961 ) -> (BitRingSwitch<EF>, BitPacking<EF>, Point<EF>, Point<EF>) {
1962 let packing = BitPacking::<EF>::new(&bits(seed, bytes)).unwrap();
1963 let mut rng = SmallRng::seed_from_u64(seed ^ 0xFFFF);
1964
1965 let r = Point::<EF>::rand(
1967 &mut rng,
1968 packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
1969 );
1970 let r_batch = Point::<EF>::rand(&mut rng, BitRingSwitch::<EF>::ABSORBED);
1971 let reduction = BitRingSwitch::new(&r).unwrap();
1972
1973 (reduction, packing, r, r_batch)
1974 }
1975
1976 fn assert_successor_proofs_match_dense(
1977 reduction: &BitRingSwitch<EF>,
1978 packing: &BitPacking<EF>,
1979 requested_k: usize,
1980 ) {
1981 let tensor = reduction.tensor(packing).unwrap();
1982 let successor = reduction.successor_tensors(packing).unwrap();
1983 let current = reduction.incoming_claim(&tensor);
1984 let next = reduction
1985 .successor_claim(&tensor, successor.as_ref())
1986 .unwrap();
1987 let mut dense_challenger = challenger();
1988 let dense =
1989 reduction.prove_with_compact_depth::<EF, _, _>(packing, &mut dense_challenger, 0, true);
1990 let dense_next: EF = dense_challenger.sample();
1991 let mut requested_challenger = challenger();
1992 let requested = reduction.prove_with_compact_depth::<EF, _, _>(
1993 packing,
1994 &mut requested_challenger,
1995 requested_k,
1996 true,
1997 );
1998 let requested_next: EF = requested_challenger.sample();
1999 assert_eq!(requested.0.tensor, dense.0.tensor);
2000 assert_eq!(requested.0.successor, dense.0.successor);
2001 assert_eq!(
2002 requested.0.sumcheck.polynomial_evaluations,
2003 dense.0.sumcheck.polynomial_evaluations
2004 );
2005 assert_eq!(
2006 requested.0.sumcheck.pow_witnesses,
2007 dense.0.sumcheck.pow_witnesses
2008 );
2009 assert_eq!(requested.0.final_eval, dense.0.final_eval);
2010 assert_eq!(requested.1, dense.1);
2011 assert_eq!(requested.2, dense.2);
2012 assert_eq!(requested_next, dense_next);
2013 assert!(
2014 reduction
2015 .verify_readings(&requested.0, Some(current), Some(next), &mut challenger())
2016 .is_ok()
2017 );
2018 }
2019
2020 fn embedded(witness: &[u8]) -> Poly<EF> {
2022 embedded_over(witness)
2023 }
2024
2025 fn embedded_over<K: Field>(witness: &[u8]) -> Poly<K> {
2027 Poly::new(
2028 (0..witness.len() * 8)
2029 .map(|cell| {
2030 if (witness[cell / 8] >> (cell % 8)) & 1 == 1 {
2031 K::ONE
2032 } else {
2033 K::ZERO
2034 }
2035 })
2036 .collect::<Vec<_>>(),
2037 )
2038 }
2039
2040 #[test]
2041 fn compact_prefix_fold_matches_the_existing_prefix_kernel() {
2042 let mut rng = SmallRng::seed_from_u64(0xC0A7);
2043 for (variables, rounds) in [(0, 0), (1, 1), (4, 2), (7, 3), (8, 4)] {
2044 let mut rng_challenges = SmallRng::seed_from_u64(0xF01D + variables as u64);
2045 let challenges = if rounds >= 2 {
2046 vec![BinaryField128::ZERO, BinaryField128::ONE]
2047 .into_iter()
2048 .chain((2..rounds).map(|_| rng_challenges.random::<BinaryField128>()))
2049 .collect::<Vec<_>>()
2050 } else {
2051 (0..rounds)
2052 .map(|_| rng_challenges.random::<BinaryField128>())
2053 .collect::<Vec<_>>()
2054 };
2055 let mut values: Vec<BinaryField128> = (0..(1usize << variables))
2056 .map(|_| rng.random::<BinaryField128>())
2057 .collect();
2058 let mut reference = Poly::new(values.clone());
2059 for &challenge in &challenges {
2060 reference.fix_prefix_var_mut(challenge);
2061 }
2062 bind_scratch_prefix(&mut values, &challenges);
2063 assert_eq!(values, reference.into_evals());
2064 }
2065 }
2066
2067 #[test]
2068 fn compact_composed_weight_map_matches_dense_at_zero_and_one_heads() {
2069 let packing = BitPacking::<EF>::new(&bits(0xC01F, 16)).unwrap();
2070 let mut rng = SmallRng::seed_from_u64(0xD01F);
2071 let reduction = BitRingSwitch::new(&Point::<EF>::rand(
2072 &mut rng,
2073 packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2074 ))
2075 .unwrap();
2076 let r_batch = Point::<EF>::rand(&mut rng, BitRingSwitch::<EF>::ABSORBED);
2077 let batch = reduction.batch(&r_batch).unwrap();
2078 let dense_equality = FactoredEquality::new(reduction.high(), LOG_CHUNK);
2079 let dense_weights = batch.weights_over::<EF>(&dense_equality);
2080 let head = &reduction.high()[..2];
2081 let head_equality = Poly::new_from_point(head, EF::ONE);
2082 let tail_equality = FactoredEquality::new(&reduction.high()[2..], LOG_CHUNK);
2083 let batching = batch.eq_batch.as_slice().to_vec();
2084 let coordinate_sums = CoordinateSums::<EF, EF>::new(&batching);
2085
2086 for challenges in [
2087 [EF::ZERO, EF::ZERO],
2088 [EF::ZERO, EF::ONE],
2089 [EF::ONE, EF::ZERO],
2090 [EF::ONE, EF::ONE],
2091 ] {
2092 let lambda = Poly::new_from_point(&challenges, EF::ONE);
2093 let mut generators = Vec::with_capacity(BitTensor::<EF>::DIMENSION);
2094 for coordinate in 0..BitTensor::<EF>::DIMENSION {
2095 let mut basis = Coefficients::<EF>::zero();
2096 basis.set(coordinate);
2097 let mut generator = EF::ZERO;
2098 for bank in 0..4 {
2099 generator += lambda.as_slice()[bank]
2100 * coordinate_sums.sum(head_equality.as_slice()[bank] * basis.element());
2101 }
2102 generators.push(generator);
2103 }
2104 let compact_weights = batch.weights_from_generators(&tail_equality, &generators);
2105 let mut folded_dense = dense_weights.clone();
2106 for &challenge in &challenges {
2107 folded_dense.fix_prefix_var_mut(challenge);
2108 }
2109 assert_eq!(compact_weights, folded_dense);
2110 }
2111 }
2112
2113 #[test]
2114 fn forced_compact_depths_match_the_dense_transcript() {
2115 for k in [1, 2, 4, 6] {
2116 for remaining_n in [k, k + 1] {
2117 let bytes = 2usize << remaining_n;
2118 let packing =
2119 BitPacking::<EF>::new(&bits(0xC011 + k as u64 + remaining_n as u64, bytes))
2120 .unwrap();
2121 let mut rng = SmallRng::seed_from_u64(0xD00D + k as u64 + remaining_n as u64);
2122 let mut point = Point::<EF>::rand(
2123 &mut rng,
2124 packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2125 )
2126 .as_slice()
2127 .to_vec();
2128 point[0] = loop {
2129 let value = rng.random::<EF>();
2130 if value != EF::ZERO && value != EF::ONE {
2131 break value;
2132 }
2133 };
2134 let reduction = BitRingSwitch::new(&Point::new(point)).unwrap();
2135 assert_eq!(packing.num_variables(), remaining_n);
2136 assert_eq!(reduction.fixed_prefix().0, 0);
2137 assert!(reduction.compact_depth_is_eligible(remaining_n, k, true));
2138 let current = reduction.incoming_claim(&reduction.tensor(&packing).unwrap());
2139 let mut dense_challenger = challenger();
2140 let (dense, dense_point, dense_value) = reduction
2141 .prove_with_compact_depth::<EF, _, _>(&packing, &mut dense_challenger, 0, true);
2142 let dense_next: EF = dense_challenger.sample();
2143 let mut compact_challenger = challenger();
2144 let (compact, compact_point, compact_value) = reduction
2145 .prove_with_compact_depth::<EF, _, _>(
2146 &packing,
2147 &mut compact_challenger,
2148 k,
2149 true,
2150 );
2151 let compact_next: EF = compact_challenger.sample();
2152
2153 assert_eq!(compact.tensor, dense.tensor, "k={k}, n={remaining_n}");
2154 assert_eq!(compact.successor, dense.successor, "k={k}, n={remaining_n}");
2155 assert_eq!(
2156 compact.sumcheck.polynomial_evaluations, dense.sumcheck.polynomial_evaluations,
2157 "k={k}, n={remaining_n}"
2158 );
2159 assert_eq!(
2160 compact.sumcheck.pow_witnesses, dense.sumcheck.pow_witnesses,
2161 "k={k}, n={remaining_n}"
2162 );
2163 assert_eq!(compact_point, dense_point, "k={k}, n={remaining_n}");
2164 assert_eq!(compact_value, dense_value, "k={k}, n={remaining_n}");
2165 assert_eq!(
2166 compact.final_eval, dense.final_eval,
2167 "k={k}, n={remaining_n}"
2168 );
2169 assert_eq!(compact_next, dense_next, "k={k}, n={remaining_n}");
2170 assert!(
2171 reduction
2172 .verify(&compact, current, &mut challenger())
2173 .is_ok()
2174 );
2175 }
2176 }
2177
2178 let packing = BitPacking::<EF>::new(&bits(0xC012, 4)).unwrap();
2181 let mut rng = SmallRng::seed_from_u64(0xD012);
2182 let reduction = BitRingSwitch::new(&Point::<EF>::rand(
2183 &mut rng,
2184 packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2185 ))
2186 .unwrap();
2187 let mut dense_challenger = challenger();
2188 let dense = reduction.prove_with_compact_depth::<EF, _, _>(
2189 &packing,
2190 &mut dense_challenger,
2191 0,
2192 true,
2193 );
2194 let mut compact_challenger = challenger();
2195 let compact = reduction.prove_with_compact_depth::<EF, _, _>(
2196 &packing,
2197 &mut compact_challenger,
2198 2,
2199 true,
2200 );
2201 assert_eq!(
2202 dense.0.sumcheck.polynomial_evaluations,
2203 compact.0.sumcheck.polynomial_evaluations
2204 );
2205 assert_eq!(dense.1, compact.1);
2206 assert_eq!(dense.2, compact.2);
2207 }
2208
2209 #[test]
2210 fn compact_prefix_uses_a_nonzero_selected_slot() {
2211 let packing = BitPacking::<EF>::new(&bits(0xC016, 64)).unwrap();
2212 let mut rng = SmallRng::seed_from_u64(0xD016);
2213 let mut coordinates = Point::<EF>::rand(
2214 &mut rng,
2215 packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2216 )
2217 .as_slice()
2218 .to_vec();
2219 coordinates[0] = EF::ONE;
2220 let reduction = BitRingSwitch::new(&Point::new(coordinates)).unwrap();
2221 let current = reduction.incoming_claim(&reduction.tensor(&packing).unwrap());
2222 let mut dense_challenger = challenger();
2223 let dense = reduction.prove_with_compact_depth::<EF, _, _>(
2224 &packing,
2225 &mut dense_challenger,
2226 0,
2227 true,
2228 );
2229 let dense_next: EF = dense_challenger.sample();
2230 let mut compact_challenger = challenger();
2231 let compact = reduction.prove_with_compact_depth::<EF, _, _>(
2232 &packing,
2233 &mut compact_challenger,
2234 2,
2235 true,
2236 );
2237 let compact_next: EF = compact_challenger.sample();
2238 assert_eq!(
2239 reduction.fixed_prefix().1,
2240 1,
2241 "the test must select the upper slot"
2242 );
2243 assert_eq!(compact.0.tensor, dense.0.tensor);
2244 assert_eq!(compact.0.successor, dense.0.successor);
2245 assert_eq!(
2246 compact.0.sumcheck.polynomial_evaluations,
2247 dense.0.sumcheck.polynomial_evaluations
2248 );
2249 assert_eq!(
2250 compact.0.sumcheck.pow_witnesses,
2251 dense.0.sumcheck.pow_witnesses
2252 );
2253 assert_eq!(compact.0.final_eval, dense.0.final_eval);
2254 assert_eq!(compact.1, dense.1);
2255 assert_eq!(compact.2, dense.2);
2256 assert_eq!(compact_next, dense_next);
2257 assert!(
2258 reduction
2259 .verify(&compact.0, current, &mut challenger())
2260 .is_ok()
2261 );
2262 }
2263
2264 #[test]
2265 fn successor_compact_depth_refuses_fixed_selector_prefixes() {
2266 let n = 4;
2267 let kept_rows = 2;
2268 let row_variables = BitRingSwitch::<EF>::ABSORBED + kept_rows;
2269 let total = n + BitRingSwitch::<EF>::ABSORBED;
2270 let mut rng = SmallRng::seed_from_u64(0xD017);
2271 let mut non_boolean = || loop {
2272 let value = rng.random::<EF>();
2273 if value != EF::ZERO && value != EF::ONE {
2274 break value;
2275 }
2276 };
2277
2278 let mut partial = vec![EF::ZERO; total];
2280 partial[0] = EF::ONE;
2281 partial[1] = non_boolean();
2282 partial[2..n].fill_with(&mut non_boolean);
2283 let partial_reduction =
2284 BitRingSwitch::with_successor(&Point::new(partial), row_variables).unwrap();
2285 let partial_prefix = partial_reduction.fixed_prefix().0;
2286 assert_eq!(partial_prefix, 1);
2287 assert!(!partial_reduction.compact_depth_is_eligible(n - partial_prefix, 2, true,));
2288 let fallback_packing = BitPacking::<EF>::new(&bits(0xD01B, 32)).unwrap();
2289 assert_successor_proofs_match_dense(&partial_reduction, &fallback_packing, 2);
2290
2291 let mut all = vec![EF::ZERO; total];
2293 all[..n - kept_rows].fill(EF::ONE);
2294 all[n - kept_rows..n].fill_with(&mut non_boolean);
2295 let all_reduction = BitRingSwitch::with_successor(&Point::new(all), row_variables).unwrap();
2296 let all_prefix = all_reduction.fixed_prefix().0;
2297 assert_eq!(all_prefix, n - kept_rows);
2298 assert!(!all_reduction.compact_depth_is_eligible(n - all_prefix, 1, true));
2299 assert_successor_proofs_match_dense(&all_reduction, &fallback_packing, 2);
2300
2301 let inside_packing = BitPacking::<EF>::new(&bits(0xC018, 4)).unwrap();
2304 let inside_point = Point::<EF>::rand(
2305 &mut rng,
2306 inside_packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2307 );
2308 let inside =
2309 BitRingSwitch::with_successor(&inside_point, BitRingSwitch::<EF>::ABSORBED - 1)
2310 .unwrap();
2311 assert!(!inside.compact_depth_is_eligible(inside.num_variables(), 2, true));
2312 assert_successor_proofs_match_dense(&inside, &inside_packing, 2);
2313
2314 let wide_packing = BitPacking::<EF>::new(&bits(0xC019, 32)).unwrap();
2317 let mut wide_point = Point::<EF>::rand(
2318 &mut rng,
2319 wide_packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2320 )
2321 .as_slice()
2322 .to_vec();
2323 wide_point[0] = loop {
2324 let value = rng.random::<EF>();
2325 if value != EF::ZERO && value != EF::ONE {
2326 break value;
2327 }
2328 };
2329 let wide = BitRingSwitch::with_successor(
2330 &Point::new(wide_point),
2331 BitRingSwitch::<EF>::ABSORBED + 2,
2332 )
2333 .unwrap();
2334 assert_eq!(wide.fixed_prefix().0, 0);
2335 assert!(wide.compact_depth_is_eligible(wide.num_variables(), 2, true));
2336 let wide_tensor = wide.tensor(&wide_packing).unwrap();
2337 let wide_successor = wide.successor_tensors(&wide_packing).unwrap();
2338 let wide_current = wide.incoming_claim(&wide_tensor);
2339 let wide_next = wide
2340 .successor_claim(&wide_tensor, wide_successor.as_ref())
2341 .unwrap();
2342 let mut wide_dense_challenger = challenger();
2343 let wide_dense = wide.prove_with_compact_depth::<EF, _, _>(
2344 &wide_packing,
2345 &mut wide_dense_challenger,
2346 0,
2347 true,
2348 );
2349 let mut wide_compact_challenger = challenger();
2350 let wide_compact = wide.prove_with_compact_depth::<EF, _, _>(
2351 &wide_packing,
2352 &mut wide_compact_challenger,
2353 2,
2354 true,
2355 );
2356 assert_eq!(wide_compact.0.successor, wide_dense.0.successor);
2357 assert_eq!(
2358 wide_compact.0.sumcheck.polynomial_evaluations,
2359 wide_dense.0.sumcheck.polynomial_evaluations
2360 );
2361 assert!(
2362 wide.verify_readings(
2363 &wide_compact.0,
2364 Some(wide_current),
2365 Some(wide_next),
2366 &mut challenger(),
2367 )
2368 .is_ok()
2369 );
2370 }
2371
2372 #[test]
2373 fn compact_plain_path_handles_zero_one_and_sparse_packing() {
2374 let mut sparse = vec![0u8; 16];
2375 sparse[0] = 1;
2376 sparse[9] = 0x80;
2377 for (seed, witness) in [
2378 (0xD018, vec![0u8; 16]),
2379 (0xD019, vec![0xFFu8; 16]),
2380 (0xD01A, sparse),
2381 ] {
2382 let packing = BitPacking::<EF>::new(&witness).unwrap();
2383 let mut rng = SmallRng::seed_from_u64(seed);
2384 let reduction = BitRingSwitch::new(&Point::<EF>::rand(
2385 &mut rng,
2386 packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2387 ))
2388 .unwrap();
2389 let mut dense_challenger = challenger();
2390 let dense = reduction.prove_with_compact_depth::<EF, _, _>(
2391 &packing,
2392 &mut dense_challenger,
2393 0,
2394 true,
2395 );
2396 let mut compact_challenger = challenger();
2397 let compact = reduction.prove_with_compact_depth::<EF, _, _>(
2398 &packing,
2399 &mut compact_challenger,
2400 2,
2401 true,
2402 );
2403 assert_eq!(compact.0.tensor, dense.0.tensor);
2404 assert_eq!(
2405 compact.0.sumcheck.polynomial_evaluations,
2406 dense.0.sumcheck.polynomial_evaluations
2407 );
2408 assert_eq!(
2409 compact.0.sumcheck.pow_witnesses,
2410 dense.0.sumcheck.pow_witnesses
2411 );
2412 assert_eq!(compact.1, dense.1);
2413 assert_eq!(compact.2, dense.2);
2414 }
2415 }
2416
2417 #[test]
2418 fn compact_successor_and_ghash_paths_match_dense() {
2419 let packing = BitPacking::<EF>::new(&bits(0xC013, 16)).unwrap();
2420 let mut rng = SmallRng::seed_from_u64(0xD013);
2421 let reduction = BitRingSwitch::with_successor(
2422 &Point::<EF>::rand(
2423 &mut rng,
2424 packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2425 ),
2426 BitRingSwitch::<EF>::ABSORBED + 1,
2427 )
2428 .unwrap();
2429 let tensor = reduction.tensor(&packing).unwrap();
2430 let successor = reduction.successor_tensors(&packing).unwrap();
2431 let current = reduction.incoming_claim(&tensor);
2432 let next = reduction
2433 .successor_claim(&tensor, successor.as_ref())
2434 .unwrap();
2435 let mut dense_challenger = challenger();
2436 let (dense, dense_point, dense_value) = reduction.prove_with_compact_depth::<EF, _, _>(
2437 &packing,
2438 &mut dense_challenger,
2439 0,
2440 true,
2441 );
2442 let mut compact_challenger = challenger();
2443 let (compact, compact_point, compact_value) = reduction
2444 .prove_with_compact_depth::<EF, _, _>(&packing, &mut compact_challenger, 2, true);
2445 assert_eq!(compact.successor, dense.successor);
2446 assert_eq!(
2447 compact.sumcheck.polynomial_evaluations,
2448 dense.sumcheck.polynomial_evaluations
2449 );
2450 assert_eq!(compact_point, dense_point);
2451 assert_eq!(compact_value, dense_value);
2452 assert!(
2453 reduction
2454 .verify_readings(&compact, Some(current), Some(next), &mut challenger())
2455 .is_ok()
2456 );
2457
2458 for (bytes, requested_k, has_tail) in [(64, 2, false), (64, 1, true), (128, 2, true)] {
2459 let wide_packing =
2460 BitPacking::<BinaryField128>::new(&bits(0xC014 + bytes as u64, bytes)).unwrap();
2461 let mut wide_rng = SmallRng::seed_from_u64(0xD014 + bytes as u64);
2462 let mut wide_point = Point::<BinaryField128>::rand(
2463 &mut wide_rng,
2464 wide_packing.num_variables() + BitRingSwitch::<BinaryField128>::ABSORBED,
2465 )
2466 .as_slice()
2467 .to_vec();
2468 wide_point[0] = loop {
2469 let value = wide_rng.random::<BinaryField128>();
2470 if value != BinaryField128::ZERO && value != BinaryField128::ONE {
2471 break value;
2472 }
2473 };
2474 let wide_reduction = BitRingSwitch::new(&Point::new(wide_point)).unwrap();
2475 assert_eq!(wide_reduction.fixed_prefix().0, 0);
2476 assert!(wide_packing.num_variables() >= requested_k);
2477 assert_eq!(wide_packing.num_variables() > requested_k, has_tail);
2478 assert!(wide_reduction.compact_depth_is_eligible(
2479 wide_packing.num_variables(),
2480 requested_k,
2481 true,
2482 ));
2483 let mut wide_dense_challenger = wide_challenger();
2484 let (wide_dense, wide_dense_point, wide_dense_value) = wide_reduction
2485 .prove_with_compact_depth::<BinaryField128, _, _>(
2486 &wide_packing,
2487 &mut wide_dense_challenger,
2488 0,
2489 true,
2490 );
2491 let mut wide_compact_challenger = wide_challenger();
2492 let (wide_compact, wide_compact_point, wide_compact_value) = wide_reduction
2493 .prove_with_compact_depth::<Ghash128, _, _>(
2494 &wide_packing,
2495 &mut wide_compact_challenger,
2496 requested_k,
2497 true,
2498 );
2499 let wide_dense_next: BinaryField128 = wide_dense_challenger.sample();
2500 let wide_compact_next: BinaryField128 = wide_compact_challenger.sample();
2501 assert_eq!(wide_compact.tensor, wide_dense.tensor);
2502 assert_eq!(wide_compact.successor, wide_dense.successor);
2503 assert_eq!(
2504 wide_compact.sumcheck.polynomial_evaluations,
2505 wide_dense.sumcheck.polynomial_evaluations
2506 );
2507 assert_eq!(
2508 wide_compact.sumcheck.pow_witnesses,
2509 wide_dense.sumcheck.pow_witnesses
2510 );
2511 assert_eq!(wide_compact_point, wide_dense_point);
2512 assert_eq!(wide_compact_value, wide_dense_value);
2513 assert_eq!(wide_compact.final_eval, wide_dense.final_eval);
2514 assert_eq!(wide_compact_next, wide_dense_next);
2515 let wide_current = wide_reduction.incoming_claim(&wide_compact.tensor);
2516 assert!(
2517 wide_reduction
2518 .verify(&wide_compact, wide_current, &mut wide_challenger())
2519 .is_ok()
2520 );
2521 }
2522 }
2523
2524 #[test]
2525 fn production_gate_uses_compact_two_rounds_at_the_minimum_shape() {
2526 let packing = BitPacking::<EF>::new(&bits(0xC015, 1024)).unwrap();
2527 let mut rng = SmallRng::seed_from_u64(0xD015);
2528 let mut point = Point::<EF>::rand(
2529 &mut rng,
2530 packing.num_variables() + BitRingSwitch::<EF>::ABSORBED,
2531 )
2532 .as_slice()
2533 .to_vec();
2534 point[0] = loop {
2535 let value = rng.random::<EF>();
2536 if value != EF::ZERO && value != EF::ONE {
2537 break value;
2538 }
2539 };
2540 let reduction = BitRingSwitch::new(&Point::new(point)).unwrap();
2541 assert_eq!(
2543 compact_size_floor(COMPACT_PRODUCTION_MIN_ROUNDS, PRODUCTION_LOG_CHUNK),
2544 22
2545 );
2546 assert_eq!(
2547 compact_size_floor(COMPACT_PRODUCTION_MIN_ROUNDS, LOG_CHUNK),
2548 9
2549 );
2550 assert_eq!(reduction.num_variables(), 9);
2551 assert!(reduction.compact_depth_is_eligible(
2552 reduction.num_variables(),
2553 COMPACT_PRODUCTION_MIN_ROUNDS,
2554 false,
2555 ));
2556 assert!(!reduction.compact_depth_is_eligible(
2557 reduction.num_variables() - 1,
2558 COMPACT_PRODUCTION_MIN_ROUNDS,
2559 false,
2560 ));
2561 assert_eq!(
2562 reduction.production_depth(reduction.num_variables()),
2563 COMPACT_PRODUCTION_MIN_ROUNDS
2564 );
2565 let mut dense_challenger = challenger();
2566 let dense = reduction.prove_with_compact_depth::<EF, _, _>(
2567 &packing,
2568 &mut dense_challenger,
2569 0,
2570 true,
2571 );
2572 let mut production_challenger = challenger();
2573 let production = reduction.prove::<EF, _, _>(&packing, &mut production_challenger);
2574 assert_eq!(
2575 production.0.sumcheck.polynomial_evaluations,
2576 dense.0.sumcheck.polynomial_evaluations
2577 );
2578 assert_eq!(production.1, dense.1);
2579 assert_eq!(production.2, dense.2);
2580 }
2581
2582 #[test]
2583 fn the_production_depth_steps_down_to_the_deepest_admitted_head() {
2584 let (reduction, ..) = fixture(0xDE97, 16);
2589 let floor = |depth| compact_size_floor(depth, LOG_CHUNK);
2590 let below = floor(COMPACT_PRODUCTION_MIN_ROUNDS) - 1;
2591 assert!(!reduction.compact_depth_is_eligible(below, COMPACT_PRODUCTION_MIN_ROUNDS, false));
2592 assert_eq!(
2593 reduction.production_depth(below),
2594 COMPACT_PRODUCTION_MIN_ROUNDS
2595 );
2596 for depth in COMPACT_PRODUCTION_MIN_ROUNDS..=COMPACT_PRODUCTION_ROUNDS {
2597 assert_eq!(reduction.production_depth(floor(depth)), depth);
2598 }
2599 assert_eq!(
2600 reduction.production_depth(floor(COMPACT_PRODUCTION_ROUNDS) + 3),
2601 COMPACT_PRODUCTION_ROUNDS
2602 );
2603
2604 let mut rng = SmallRng::seed_from_u64(0xDE98);
2606 let absorbed = BitRingSwitch::<EF>::ABSORBED;
2607 let r = Point::<EF>::rand(&mut rng, floor(COMPACT_PRODUCTION_ROUNDS) + absorbed);
2608 let kept_rows = floor(COMPACT_PRODUCTION_ROUNDS) - (COMPACT_PRODUCTION_ROUNDS - 1);
2609 let successor = BitRingSwitch::with_successor(&r, kept_rows + absorbed).unwrap();
2610 assert_eq!(
2611 successor.production_depth(floor(COMPACT_PRODUCTION_ROUNDS)),
2612 COMPACT_PRODUCTION_ROUNDS - 1
2613 );
2614 }
2615
2616 #[test]
2617 fn the_reduction_runs_over_the_variables_the_packing_keeps() {
2618 let (reduction, packing, _, _) = fixture(0x5AE, 16);
2620
2621 assert_eq!(BitRingSwitch::<EF>::ABSORBED, 4);
2622 assert_eq!(reduction.num_variables(), 3);
2623 assert_eq!(packing.num_variables(), 3);
2624 }
2625
2626 fn dense_eq_high(reduction: &BitRingSwitch<EF>) -> Poly<EF> {
2631 Poly::new_from_point(
2632 &reduction.point.as_slice()[..reduction.num_variables()],
2633 EF::ONE,
2634 )
2635 }
2636
2637 #[test]
2638 fn the_incoming_claim_is_the_witness_at_the_point() {
2639 let witness = bits(0xC1A1, 16);
2645 let packing = BitPacking::<EF>::new(&witness).unwrap();
2646 let mut rng = SmallRng::seed_from_u64(0xF001);
2647 let r = Point::<EF>::rand(&mut rng, 7);
2648 let reduction = BitRingSwitch::new(&r).unwrap();
2649
2650 let tensor = reduction.tensor(&packing).unwrap();
2651
2652 assert_eq!(
2653 reduction.incoming_claim(&tensor),
2654 embedded(&witness).eval_base(&r)
2655 );
2656 }
2657
2658 #[test]
2659 fn the_columns_are_the_bit_planes_at_the_kept_coordinates() {
2660 let (reduction, packing, _, _) = fixture(0xB1A, 16);
2663 let eq = dense_eq_high(&reduction);
2664
2665 for (v, &column) in reduction
2666 .tensor(&packing)
2667 .unwrap()
2668 .columns()
2669 .iter()
2670 .enumerate()
2671 {
2672 let expected: EF = (0..packing.len())
2673 .filter(|&w| packing.coefficients(w).get(v))
2674 .map(|w| eq.as_slice()[w])
2675 .sum();
2676 assert_eq!(column, expected, "column {v}");
2677 }
2678 }
2679
2680 #[test]
2681 fn each_row_is_its_own_weighted_sum() {
2682 let (reduction, packing, _, _) = fixture(0x0A5, 16);
2686 let eq = dense_eq_high(&reduction);
2687
2688 for (u, &row) in reduction
2689 .tensor(&packing)
2690 .unwrap()
2691 .rows()
2692 .iter()
2693 .enumerate()
2694 {
2695 let expected: EF = (0..packing.len())
2696 .filter(|&w| Coefficients::of(eq.as_slice()[w]).get(u))
2697 .map(|w| packing.poly().as_slice()[w])
2698 .sum();
2699 assert_eq!(row, expected, "row {u}");
2700 }
2701 }
2702
2703 #[test]
2704 fn a_weight_is_the_subset_sum_the_coordinates_select() {
2705 let (reduction, _, _, r_batch) = fixture(0x5E7, 16);
2707 let batch = reduction.batch(&r_batch).unwrap();
2708 let weights = batch.weights();
2709
2710 let eq_batch = Poly::<EF>::new_from_point(r_batch.as_slice(), EF::ONE);
2712
2713 for (w, &value) in dense_eq_high(&reduction).as_slice().iter().enumerate() {
2714 let expected: EF = Coefficients::of(value)
2715 .iter()
2716 .zip(eq_batch.as_slice())
2717 .filter(|&(bit, _)| bit)
2718 .map(|(_, &weight)| weight)
2719 .sum();
2720 assert_eq!(weights.as_slice()[w], expected, "point {w}");
2721 }
2722 assert_eq!(weights.num_variables(), reduction.num_variables());
2723 }
2724
2725 #[test]
2726 fn the_initial_sum_is_the_sum_the_sumcheck_proves() {
2727 let (reduction, packing, _, r_batch) = fixture(0xBA7, 32);
2733 let batch = reduction.batch(&r_batch).unwrap();
2734 let tensor = reduction.tensor(&packing).unwrap();
2735
2736 let dot: EF = batch
2737 .weights()
2738 .as_slice()
2739 .iter()
2740 .zip(packing.poly().as_slice())
2741 .map(|(&weight, &value)| weight * value)
2742 .sum();
2743
2744 assert_eq!(dot, batch.initial_sum(&tensor, None));
2745 }
2746
2747 #[test]
2748 fn the_batched_rows_weigh_by_the_challenge_itself() {
2749 let (reduction, packing, _, r_batch) = fixture(0xC4A, 16);
2760 let batch = reduction.batch(&r_batch).unwrap();
2761 let tensor = reduction.tensor(&packing).unwrap();
2762 let absorbed = BitRingSwitch::<EF>::ABSORBED;
2763
2764 let expected: EF = tensor
2765 .rows()
2766 .iter()
2767 .enumerate()
2768 .map(|(u, &row)| {
2769 let point = Point::<EF>::hypercube(u, absorbed);
2770 row * Point::eval_eq(point.as_slice(), r_batch.as_slice())
2771 })
2772 .sum();
2773
2774 assert_eq!(batch.initial_sum(&tensor, None), expected);
2775 }
2776
2777 #[test]
2778 fn the_closing_weight_is_the_weight_multilinear_at_the_point() {
2779 let (reduction, _, _, r_batch) = fixture(0xA7, 16);
2783 let batch = reduction.batch(&r_batch).unwrap();
2784 let r_prime = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0xA8), 3);
2785
2786 assert_eq!(
2787 batch.closing_weight(&r_prime).unwrap(),
2788 batch.weights().eval_base(&r_prime)
2789 );
2790 }
2791
2792 #[test]
2793 fn the_equality_element_matches_its_hypercube_definition() {
2794 let mut rng = SmallRng::seed_from_u64(0xE9);
2799 for num_variables in 0..5 {
2800 let r = Point::<EF>::rand(&mut rng, num_variables + BitRingSwitch::<EF>::ABSORBED);
2801 let reduction = BitRingSwitch::new(&r).unwrap();
2802 let r_prime = Point::<EF>::rand(&mut rng, num_variables);
2803
2804 let eq_prime = Poly::<EF>::new_from_point(r_prime.as_slice(), EF::ONE);
2805 let mut expected = BitTensor::zero();
2806 let eq_high = dense_eq_high(&reduction);
2807 for (&a, &b) in eq_high.as_slice().iter().zip(eq_prime.as_slice()) {
2808 expected.add_exterior_product(a, b);
2809 }
2810
2811 assert_eq!(
2812 reduction.equality_element(&r_prime),
2813 expected,
2814 "{num_variables} variables"
2815 );
2816 }
2817 }
2818
2819 #[test]
2820 fn a_point_narrower_than_one_element_is_refused() {
2821 let mut rng = SmallRng::seed_from_u64(0xBAD);
2823 let r = Point::<EF>::rand(&mut rng, 3);
2824
2825 assert_eq!(
2826 BitRingSwitch::new(&r).unwrap_err(),
2827 BitRingSwitchError::PointTooNarrow {
2828 needed: 4,
2829 actual: 3
2830 }
2831 );
2832 }
2833
2834 #[test]
2835 fn a_batching_challenge_of_the_wrong_width_is_refused() {
2836 let mut rng = SmallRng::seed_from_u64(0xBAD2);
2839 let r = Point::<EF>::rand(&mut rng, 7);
2840 let reduction = BitRingSwitch::new(&r).unwrap();
2841
2842 for width in [3usize, 5] {
2843 let r_batch = Point::<EF>::rand(&mut rng, width);
2844 assert_eq!(
2845 reduction.batch(&r_batch).unwrap_err(),
2846 BitRingSwitchError::BatchWidthMismatch {
2847 expected: 4,
2848 actual: width,
2849 }
2850 );
2851 }
2852 }
2853
2854 #[test]
2855 fn a_packing_of_the_wrong_width_is_refused() {
2856 let (reduction, _, _, r_batch) = fixture(0xBAD3, 16);
2858 let batch = reduction.batch(&r_batch).unwrap();
2859 let wider = BitPacking::<EF>::new(&bits(0xBAD4, 32)).unwrap();
2860
2861 assert_eq!(
2862 reduction.tensor(&wider).unwrap_err(),
2863 BitRingSwitchError::WidthMismatch {
2864 expected: 3,
2865 actual: 4
2866 }
2867 );
2868 assert_eq!(
2869 batch
2870 .closing_weight(&Point::<EF>::new(alloc::vec![EF::ONE; 4]))
2871 .unwrap_err(),
2872 BitRingSwitchError::WidthMismatch {
2873 expected: 3,
2874 actual: 4
2875 }
2876 );
2877 }
2878
2879 #[test]
2880 fn a_boolean_prefix_restricts_the_sumcheck_without_changing_the_claim() {
2881 let witness = bits(0xB0015, 16);
2897 let packing = BitPacking::<EF>::new(&witness).unwrap();
2898 let mut rng = SmallRng::seed_from_u64(0xB0016);
2899
2900 for prefix in 0..=3usize {
2901 for address in 0..1usize << prefix {
2902 let mut coordinates: Vec<EF> = (0..prefix)
2904 .map(|bit| {
2905 if (address >> (prefix - 1 - bit)) & 1 == 1 {
2906 EF::ONE
2907 } else {
2908 EF::ZERO
2909 }
2910 })
2911 .collect();
2912 coordinates.extend((prefix..7).map(|_| {
2915 loop {
2916 let coordinate = rng.random::<EF>();
2917 if coordinate != EF::ZERO && coordinate != EF::ONE {
2918 break coordinate;
2919 }
2920 }
2921 }));
2922 let r = Point::new(coordinates);
2923
2924 let reduction = BitRingSwitch::new(&r).unwrap();
2925 let r_batch = Point::<EF>::rand(&mut rng, BitRingSwitch::<EF>::ABSORBED);
2926 let batch = reduction.batch(&r_batch).unwrap();
2927
2928 let mut expected = BitTensor::zero();
2930 for (&weight, &value) in dense_eq_high(&reduction)
2931 .as_slice()
2932 .iter()
2933 .zip(packing.poly().as_slice())
2934 {
2935 expected.add_exterior_product(weight, value);
2936 }
2937 assert_eq!(
2938 reduction.tensor(&packing).unwrap(),
2939 expected,
2940 "prefix {prefix} address {address}"
2941 );
2942
2943 let eq_batch = Poly::<EF>::new_from_point(r_batch.as_slice(), EF::ONE);
2945 let weights = batch.weights();
2946 assert_eq!(weights.num_variables(), reduction.num_variables() - prefix);
2947
2948 let len = 1usize << (reduction.num_variables() - prefix);
2950 let offset = address * len;
2951 let dense = dense_eq_high(&reduction);
2952 for (w, &value) in dense.as_slice()[offset..offset + len].iter().enumerate() {
2953 let want: EF = Coefficients::of(value)
2954 .iter()
2955 .zip(eq_batch.as_slice())
2956 .filter(|&(bit, _)| bit)
2957 .map(|(_, &weight)| weight)
2958 .sum();
2959 assert_eq!(weights.as_slice()[w], want, "prefix {prefix} point {w}");
2960 }
2961
2962 let claim = embedded(&witness).eval_base(&r);
2965 let (proof, surviving_point, surviving_value) =
2966 reduction.prove::<EF, _, _>(&packing, &mut challenger());
2967 assert_eq!(
2968 proof.sumcheck.num_rounds(),
2969 reduction.num_variables() - prefix
2970 );
2971 assert_eq!(
2972 &surviving_point.as_slice()[..prefix],
2973 &r.as_slice()[..prefix]
2974 );
2975 assert_eq!(surviving_value, packing.poly().eval_base(&surviving_point));
2976
2977 let (verified_point, verified_value) =
2979 reduction.verify(&proof, claim, &mut challenger()).unwrap();
2980 assert_eq!(verified_point, surviving_point);
2981 assert_eq!(verified_value, surviving_value);
2982 }
2983 }
2984 }
2985
2986 proptest! {
2987 #[test]
2988 fn the_chunked_accumulation_matches_the_term_by_term_sum(seed: u64, log_n in 1usize..6) {
2989 let (reduction, packing, _, _) = fixture(seed, (1 << log_n) * 2);
2992
2993 let mut expected = BitTensor::zero();
2994 for (&weight, &value) in dense_eq_high(&reduction)
2995 .as_slice()
2996 .iter()
2997 .zip(packing.poly().as_slice())
2998 {
2999 expected += BitTensor::exterior_product(weight, value);
3000 }
3001
3002 prop_assert_eq!(reduction.tensor(&packing).unwrap(), expected);
3003 }
3004 }
3005
3006 #[test]
3007 fn the_reduction_round_trips_and_leaves_a_true_claim() {
3008 let witness = bits(0x81A5, 32);
3013 let packing = BitPacking::<EF>::new(&witness).unwrap();
3014 let r = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0x81A6), 8);
3015 let reduction = BitRingSwitch::new(&r).unwrap();
3016 let claim = embedded(&witness).eval_base(&r);
3017
3018 let mut prover_chal = challenger();
3019 let (proof, r_prime_p, s_prime_p) = reduction.prove::<EF, _, _>(&packing, &mut prover_chal);
3020
3021 let mut verifier_chal = challenger();
3022 let (r_prime_v, s_prime_v) = reduction.verify(&proof, claim, &mut verifier_chal).unwrap();
3023
3024 assert_eq!(r_prime_p, r_prime_v);
3025 assert_eq!(s_prime_p, s_prime_v);
3026 assert_eq!(s_prime_v, packing.poly().eval_base(&r_prime_v));
3027 }
3028
3029 #[test]
3030 fn a_claim_the_element_does_not_support_is_rejected() {
3031 let witness = bits(0x0AD, 32);
3033 let packing = BitPacking::<EF>::new(&witness).unwrap();
3034 let r = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0x0AE), 8);
3035 let reduction = BitRingSwitch::new(&r).unwrap();
3036 let claim = embedded(&witness).eval_base(&r);
3037
3038 let (proof, _, _) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3039
3040 let err = reduction
3041 .verify(&proof, claim + EF::ONE, &mut challenger())
3042 .unwrap_err();
3043 assert_eq!(err, BitRingSwitchProofError::ClaimMismatch);
3044 }
3045
3046 #[test]
3047 fn a_tampered_element_is_caught_by_whichever_reading_it_moves() {
3048 let witness = bits(0x7A17, 32);
3056 let packing = BitPacking::<EF>::new(&witness).unwrap();
3057 let r = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0x7A18), 8);
3058 let reduction = BitRingSwitch::new(&r).unwrap();
3059 let claim = embedded(&witness).eval_base(&r);
3060 let (honest, _, _) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3061
3062 let mut flipped = honest.clone();
3069 let mut rows = flipped.tensor.rows().to_vec();
3070 rows[3] += EF::ONE;
3071 flipped.tensor = BitTensor::try_from(rows).unwrap();
3072
3073 assert_eq!(
3074 reduction
3075 .verify(&flipped, claim, &mut challenger())
3076 .unwrap_err(),
3077 BitRingSwitchProofError::ClaimMismatch,
3078 );
3079
3080 let eq_low = reduction.eq_low.as_slice();
3092 assert_ne!(
3093 eq_low[1],
3094 EF::ZERO,
3095 "the construction divides by this entry"
3096 );
3097
3098 let d_0 = EF::from_le_byte_iter([0x2C, 0x9B].into_iter());
3099 let d_1 = d_0 * eq_low[0] * eq_low[1].inverse();
3100 assert_ne!(d_0, EF::ZERO);
3101
3102 let mut delta_columns = alloc::vec![EF::ZERO; BitTensor::<EF>::DIMENSION];
3104 delta_columns[0] = d_0;
3105 delta_columns[1] = d_1;
3106 let delta = BitTensor::try_from(delta_columns).unwrap().columns();
3107
3108 let mut hidden = honest.clone();
3109 let rows = hidden
3110 .tensor
3111 .rows()
3112 .iter()
3113 .zip(&delta)
3114 .map(|(&row, &shift)| row + shift)
3115 .collect::<Vec<_>>();
3116 hidden.tensor = BitTensor::try_from(rows).unwrap();
3117
3118 assert_ne!(hidden.tensor, honest.tensor);
3120 assert_eq!(
3121 reduction.incoming_claim(&hidden.tensor),
3122 reduction.incoming_claim(&honest.tensor),
3123 );
3124
3125 assert_eq!(
3135 reduction
3136 .verify(&hidden, claim, &mut challenger())
3137 .unwrap_err(),
3138 BitRingSwitchProofError::FinalCheck,
3139 );
3140
3141 assert!(reduction.verify(&honest, claim, &mut challenger()).is_ok());
3143 }
3144
3145 #[test]
3146 fn a_stray_grinding_witness_is_refused_before_the_transcript() {
3147 let witness = bits(0x5407, 32);
3156 let packing = BitPacking::<EF>::new(&witness).unwrap();
3157 let r = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0x5408), 8);
3158 let reduction = BitRingSwitch::new(&r).unwrap();
3159 let claim = embedded(&witness).eval_base(&r);
3160
3161 let (mut proof, _, _) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3162 proof.sumcheck.pow_witnesses.push(EF::ONE);
3163
3164 assert_eq!(
3165 reduction
3166 .verify(&proof, claim, &mut challenger())
3167 .unwrap_err(),
3168 BitRingSwitchProofError::NonEmptyPowWitnesses { actual: 1 }
3169 );
3170
3171 assert!(BitTensor::<EF>::try_from(alloc::vec![EF::ONE; 4]).is_err());
3173 }
3174
3175 #[test]
3176 fn a_point_the_prover_did_not_run_over_is_rejected() {
3177 let witness = bits(0x9107, 32);
3185 let packing = BitPacking::<EF>::new(&witness).unwrap();
3186 let r = Point::<EF>::rand(&mut SmallRng::seed_from_u64(0x9108), 8);
3187 let claim = embedded(&witness).eval_base(&r);
3188 let reduction = BitRingSwitch::new(&r).unwrap();
3189 let (proof, _, _) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3190
3191 let mut moved = r.as_slice().to_vec();
3192 moved[0] += EF::ONE;
3193 let elsewhere = BitRingSwitch::new(&Point::new(moved)).unwrap();
3194
3195 let err = elsewhere
3196 .verify(&proof, claim, &mut challenger())
3197 .unwrap_err();
3198 assert_ne!(err, BitRingSwitchProofError::ClaimMismatch, "{err:?}");
3199 }
3200
3201 proptest! {
3202 #![proptest_config(ProptestConfig { cases: 24, ..ProptestConfig::default() })]
3204
3205 #[test]
3207 fn the_reduction_round_trips_over_random_inputs(
3208 log_bytes in 1usize..=6,
3209 witness_seed: u64,
3210 point_seed: u64,
3211 ) {
3212 let witness = bits(witness_seed, 1 << log_bytes);
3213 let packing = BitPacking::<EF>::new(&witness).unwrap();
3214 let variables = log_bytes + 3;
3215 let r = Point::<EF>::rand(&mut SmallRng::seed_from_u64(point_seed), variables);
3216 let reduction = BitRingSwitch::new(&r).unwrap();
3217 let claim = embedded(&witness).eval_base(&r);
3218
3219 let (proof, _, s_prime_p) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3220 let (r_prime, s_prime) =
3221 reduction.verify(&proof, claim, &mut challenger()).unwrap();
3222
3223 prop_assert_eq!(s_prime, s_prime_p);
3224 prop_assert_eq!(s_prime, packing.poly().eval_base(&r_prime));
3225 }
3226
3227 #[test]
3229 fn a_successor_view_round_trips_over_random_inputs(
3230 (log_bytes, row_variables) in (1usize..=6)
3231 .prop_flat_map(|log_bytes| (Just(log_bytes), 0..=log_bytes + 3)),
3232 witness_seed: u64,
3233 point_seed: u64,
3234 ) {
3235 let witness = bits(witness_seed, 1 << log_bytes);
3236 let packing = BitPacking::<EF>::new(&witness).unwrap();
3237 let variables = log_bytes + 3;
3238 let r = Point::<EF>::rand(&mut SmallRng::seed_from_u64(point_seed), variables);
3239 let reduction = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3240 let current = embedded(&witness).eval_base(&r);
3241 let next = dense_successor_claim(&witness, &r, row_variables);
3242
3243 let (proof, r_prime_p, s_prime_p) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3244 let (r_prime, s_prime) = reduction
3245 .verify_readings(&proof, Some(current), Some(next), &mut challenger())
3246 .unwrap();
3247
3248 prop_assert_eq!(&r_prime, &r_prime_p);
3249 prop_assert_eq!(s_prime, s_prime_p);
3250 prop_assert_eq!(s_prime, packing.poly().eval_base(&r_prime));
3251 }
3252 }
3253
3254 fn dense_successor_claim(witness: &[u8], point: &Point<EF>, row_variables: usize) -> EF {
3260 let cells = embedded(witness);
3261 let (selector, rho) = point.split_at(point.num_variables() - row_variables);
3262 let eq_selector = Poly::<EF>::new_from_point(selector.as_slice(), EF::ONE);
3263 let eq_rho = Poly::<EF>::new_from_point(rho.as_slice(), EF::ONE);
3264 let rows = 1usize << row_variables;
3265 let mut claim = EF::ZERO;
3266 for (c, &gate) in eq_selector.as_slice().iter().enumerate() {
3267 for (z, &weight) in eq_rho.as_slice().iter().enumerate() {
3268 let x = (z + 1).min(rows - 1);
3269 claim += gate * weight * cells.as_slice()[c * rows + x];
3270 }
3271 }
3272 claim
3273 }
3274
3275 fn non_boolean(rng: &mut SmallRng) -> EF {
3279 loop {
3280 let coordinate = rng.random::<EF>();
3281 if coordinate != EF::ZERO && coordinate != EF::ONE {
3282 return coordinate;
3283 }
3284 }
3285 }
3286
3287 #[derive(Clone, Copy, Debug)]
3289 enum Selector {
3290 Random,
3292 Boolean,
3294 Mixed,
3296 }
3297
3298 impl Selector {
3299 const ALL: [Self; 3] = [Self::Random, Self::Boolean, Self::Mixed];
3301 }
3302
3303 fn successor_point(
3307 seed: u64,
3308 num_variables: usize,
3309 row_variables: usize,
3310 selector: Selector,
3311 ) -> Point<EF> {
3312 let mut rng = SmallRng::seed_from_u64(seed);
3313 let selector_variables = num_variables - row_variables;
3314 Point::new(
3315 (0..num_variables)
3316 .map(|i| {
3317 let bit = if i % 2 == 0 { EF::ONE } else { EF::ZERO };
3318 match selector {
3319 _ if i >= selector_variables => non_boolean(&mut rng),
3320 Selector::Random => non_boolean(&mut rng),
3321 Selector::Boolean => bit,
3322 Selector::Mixed if i % 3 == 2 => non_boolean(&mut rng),
3323 Selector::Mixed => bit,
3324 }
3325 })
3326 .collect(),
3327 )
3328 }
3329
3330 const ROW_VARIABLES: [usize; 5] = [0, 2, 4, 6, 9];
3338
3339 #[test]
3340 fn the_successor_claim_is_the_witness_one_row_on() {
3341 let witness = bits(0x5CC1, 64);
3350 let packing = BitPacking::<EF>::new(&witness).unwrap();
3351
3352 for selector in Selector::ALL {
3353 for (seed, &row_variables) in (0x5CC2..).zip(&ROW_VARIABLES) {
3354 let r = successor_point(seed, 9, row_variables, selector);
3355 let reduction = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3356 let tensor = reduction.tensor(&packing).unwrap();
3357 let successor = reduction.successor_tensors(&packing).unwrap();
3358
3359 assert_eq!(
3360 successor.is_some(),
3361 row_variables > BitRingSwitch::<EF>::ABSORBED,
3362 "{selector:?} selector, {row_variables} row variables"
3363 );
3364 assert_eq!(
3365 reduction
3366 .successor_claim(&tensor, successor.as_ref())
3367 .unwrap(),
3368 dense_successor_claim(&witness, &r, row_variables),
3369 "{selector:?} selector, {row_variables} row variables"
3370 );
3371 }
3372 }
3373 }
3374
3375 #[test]
3376 fn the_successor_elements_are_their_term_by_term_sums() {
3377 let witness = bits(0x7E24, 64);
3384 let packing = BitPacking::<EF>::new(&witness).unwrap();
3385
3386 for &row_variables in &ROW_VARIABLES {
3387 for selector in Selector::ALL {
3388 let r = successor_point(0x7E25, 9, row_variables, selector);
3389 let reduction = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3390 let Some(kept) = reduction.kept_row_variables() else {
3391 continue;
3392 };
3393 let table = dense_eq_high(&reduction);
3394 let max = (1usize << kept) - 1;
3395
3396 let mut carry = BitTensor::zero();
3397 let mut last = BitTensor::zero();
3398 for (w, &value) in packing.poly().as_slice().iter().enumerate() {
3399 let row = w & max;
3400 if row != 0 {
3401 carry += BitTensor::exterior_product(table.as_slice()[w - 1], value);
3402 }
3403 if row == max {
3404 last += BitTensor::exterior_product(table.as_slice()[w], value);
3405 }
3406 }
3407
3408 let elements = reduction.successor_tensors(&packing).unwrap().unwrap();
3409 assert_eq!(elements.carry, carry, "{selector:?}, {row_variables} rows");
3410 assert_eq!(elements.last, last, "{selector:?}, {row_variables} rows");
3411 }
3412 }
3413 }
3414
3415 #[test]
3416 fn the_successor_weights_are_the_dense_table_entry_by_entry() {
3417 let mut rng = SmallRng::seed_from_u64(0xDE96);
3427 let r = successor_point(0xDE97, 9, 6, Selector::Random);
3428 let reduction = BitRingSwitch::with_successor(&r, 6).unwrap();
3429 let r_batch = Point::<EF>::rand(&mut rng, BitRingSwitch::<EF>::ABSORBED);
3430 let alpha = non_boolean(&mut rng);
3431
3432 let eq_batch = Poly::<EF>::new_from_point(r_batch.as_slice(), EF::ONE);
3434 let base = dense_eq_high(&reduction)
3435 .as_slice()
3436 .iter()
3437 .map(|&value| {
3438 Coefficients::of(value)
3439 .iter_set()
3440 .map(|u| eq_batch.as_slice()[u])
3441 .sum::<EF>()
3442 })
3443 .collect::<Vec<_>>();
3444
3445 let max = (1usize << reduction.kept_row_variables().unwrap()) - 1;
3446 let expected = (0..base.len())
3447 .map(|w| {
3448 let row = w & max;
3449 let mut value = base[w];
3450 if row != 0 {
3451 value += alpha * base[w - 1];
3452 }
3453 if row == max {
3454 value += alpha.square() * base[w];
3455 }
3456 value
3457 })
3458 .collect::<Vec<_>>();
3459
3460 let batch = reduction.batch_with_successor(&r_batch, alpha).unwrap();
3461 assert_eq!(batch.weights().as_slice(), expected.as_slice());
3462 }
3463
3464 #[test]
3465 fn the_sweeps_read_the_same_table_at_every_block_size() {
3466 let mut rng = SmallRng::seed_from_u64(0xB10C5);
3475 let packing = BitPacking::<EF>::new(&bits(0xB10C6, 64)).unwrap();
3476 let r_batch = Point::<EF>::rand(&mut rng, BitRingSwitch::<EF>::ABSORBED);
3477 let alpha = non_boolean(&mut rng);
3478
3479 for &row_variables in &ROW_VARIABLES {
3480 for selector in Selector::ALL {
3481 let r = successor_point(0xB10C7, 9, row_variables, selector);
3482 let reduction = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3483 let (prefix, offset, _) = reduction.support();
3484 let run = &reduction.high()[prefix..];
3485 let batch = reduction
3486 .batch_drawn(
3487 &r_batch,
3488 reduction.sends_successor_tensors().then_some(alpha),
3489 )
3490 .unwrap();
3491
3492 let dense = FactoredEquality::new(run, 0);
3494 let tensor = BitRingSwitch::tensor_over(&packing, offset, &dense);
3495 let successor = reduction.successor_tensors_over(&packing, offset, &dense);
3496 let weights = batch.weights_over::<EF>(&dense);
3497
3498 for log_block in 0..=run.len() + 2 {
3499 let equality = FactoredEquality::new(run, log_block);
3500 let case =
3501 alloc::format!("{selector:?}, {row_variables} rows, 2^{log_block} block");
3502
3503 assert_eq!(
3504 BitRingSwitch::tensor_over(&packing, offset, &equality),
3505 tensor,
3506 "{case}"
3507 );
3508 assert_eq!(
3509 reduction.successor_tensors_over(&packing, offset, &equality),
3510 successor,
3511 "{case}"
3512 );
3513 assert_eq!(
3514 batch.weights_over::<EF>(&equality).as_slice(),
3515 weights.as_slice(),
3516 "{case}"
3517 );
3518 }
3519 }
3520 }
3521 }
3522
3523 #[test]
3524 fn the_wide_sweeps_read_the_same_elements_at_every_block_size() {
3525 type Wide = BinaryField128;
3530 let mut rng = SmallRng::seed_from_u64(0x31DE);
3531 let packing = BitPacking::<Wide>::new(&bits(0x31DF, 1 << 13)).unwrap();
3532 let num_variables = packing.num_variables() + BitRingSwitch::<Wide>::ABSORBED;
3533
3534 for row_variables in [8, 10, 12, num_variables] {
3535 let r = Point::<Wide>::rand(&mut rng, num_variables);
3536 let reduction = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3537 let (prefix, offset, _) = reduction.support();
3538 let run = &reduction.high()[prefix..];
3539
3540 let dense = FactoredEquality::new(run, 0);
3542 let tensor = BitRingSwitch::tensor_over(&packing, offset, &dense);
3543 let successor = reduction.successor_tensors_over(&packing, offset, &dense);
3544
3545 for log_block in 0..=run.len() {
3546 let equality = FactoredEquality::new(run, log_block);
3547 let case = alloc::format!("{row_variables} rows, 2^{log_block} block");
3548 assert_eq!(
3549 BitRingSwitch::tensor_over(&packing, offset, &equality),
3550 tensor,
3551 "{case}"
3552 );
3553 assert_eq!(
3554 reduction.successor_tensors_over(&packing, offset, &equality),
3555 successor,
3556 "{case}"
3557 );
3558 }
3559 }
3560 }
3561
3562 #[test]
3563 fn one_sweep_over_the_banks_gives_each_bank_its_own_elements() {
3564 type Wide = BinaryField128;
3571 let absorbed = BitRingSwitch::<Wide>::ABSORBED;
3572 let (head, tail, banks) = (2, 8, 4);
3573 let bank_len = 1 << tail;
3574 let packing = BitPacking::<Wide>::new(&bits(0xBA4C, banks * bank_len * 16)).unwrap();
3575 let num_variables = head + tail + absorbed;
3576 assert_eq!(packing.num_variables() + absorbed, num_variables);
3577 let mut rng = SmallRng::seed_from_u64(0xBA4D);
3578
3579 for row_variables in [absorbed + 1, absorbed + 3, absorbed + 6, absorbed + tail] {
3580 let r = Point::<Wide>::rand(&mut rng, num_variables);
3581 let reduction = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3582 let run = &reduction.high()[head..];
3583
3584 let single = FactoredEquality::new(run, 0);
3585 let tensors = (0..banks)
3586 .map(|bank| BitRingSwitch::tensor_over(&packing, bank * bank_len, &single))
3587 .collect::<Vec<_>>();
3588 let successors = (0..banks)
3589 .map(|bank| {
3590 reduction
3591 .successor_tensors_over(&packing, bank * bank_len, &single)
3592 .unwrap()
3593 })
3594 .collect::<Vec<_>>();
3595
3596 for log_block in 3..=6 {
3597 let equality = FactoredEquality::new(run, log_block);
3598 let case = alloc::format!("{row_variables} rows, 2^{log_block} block");
3599 assert_eq!(
3600 BitRingSwitch::bank_tensors_over(&packing, 0, banks, &equality),
3601 tensors,
3602 "{case}"
3603 );
3604 assert_eq!(
3605 reduction.bank_successor_tensors_over(&packing, 0, banks, &equality),
3606 Some(successors.clone()),
3607 "{case}"
3608 );
3609 }
3610 }
3611 }
3612
3613 #[test]
3614 fn the_deepest_production_head_proves_what_the_dense_path_proves() {
3615 type Wide = BinaryField128;
3621 let absorbed = BitRingSwitch::<Wide>::ABSORBED;
3622 let n = compact_size_floor(COMPACT_PRODUCTION_ROUNDS, LOG_CHUNK);
3623 let packing = BitPacking::<Wide>::new(&bits(0xDE4, (1 << n) * 16)).unwrap();
3624 let mut rng = SmallRng::seed_from_u64(0xDE5);
3625 let r = Point::<Wide>::rand(&mut rng, n + absorbed);
3626
3627 for reduction in [
3628 BitRingSwitch::new(&r).unwrap(),
3629 BitRingSwitch::with_successor(&r, absorbed + 3).unwrap(),
3630 ] {
3631 assert_eq!(reduction.fixed_prefix().0, 0);
3632 assert_eq!(reduction.production_depth(n), COMPACT_PRODUCTION_ROUNDS);
3633
3634 let mut dense_challenger = wide_challenger();
3635 let dense = reduction.prove_with_compact_depth::<Ghash128, _, _>(
3636 &packing,
3637 &mut dense_challenger,
3638 0,
3639 true,
3640 );
3641 let mut production_challenger = wide_challenger();
3642 let production =
3643 reduction.prove::<Ghash128, _, _>(&packing, &mut production_challenger);
3644
3645 assert_eq!(production.0.tensor, dense.0.tensor);
3646 assert_eq!(production.0.successor, dense.0.successor);
3647 assert_eq!(
3648 production.0.sumcheck.polynomial_evaluations,
3649 dense.0.sumcheck.polynomial_evaluations
3650 );
3651 assert_eq!(production.0.final_eval, dense.0.final_eval);
3652 assert_eq!(production.1, dense.1);
3653 assert_eq!(production.2, dense.2);
3654 assert_eq!(
3655 CanSample::<Wide>::sample(&mut production_challenger),
3656 CanSample::<Wide>::sample(&mut dense_challenger)
3657 );
3658
3659 let current = reduction.incoming_claim(&production.0.tensor);
3660 let next = reduction.sends_successor_tensors().then(|| {
3661 reduction
3662 .successor_claim(&production.0.tensor, production.0.successor.as_ref())
3663 .unwrap()
3664 });
3665 assert!(
3666 reduction
3667 .verify_readings(&production.0, Some(current), next, &mut wide_challenger())
3668 .is_ok()
3669 );
3670 }
3671 }
3672
3673 #[test]
3674 fn the_successor_weights_close_the_batched_sum() {
3675 let witness = bits(0xC105, 64);
3682 let packing = BitPacking::<EF>::new(&witness).unwrap();
3683 let mut rng = SmallRng::seed_from_u64(0xC106);
3684 let r = successor_point(0xC107, 9, 6, Selector::Random);
3685 let reduction = BitRingSwitch::with_successor(&r, 6).unwrap();
3686 let tensor = reduction.tensor(&packing).unwrap();
3687 let successor = reduction.successor_tensors(&packing).unwrap().unwrap();
3688
3689 let r_batch = Point::<EF>::rand(&mut rng, BitRingSwitch::<EF>::ABSORBED);
3690 let alpha = non_boolean(&mut rng);
3691 let batch = reduction.batch_with_successor(&r_batch, alpha).unwrap();
3692 let weights = batch.weights();
3693
3694 let dot: EF = weights
3695 .as_slice()
3696 .iter()
3697 .zip(packing.poly().as_slice())
3698 .map(|(&weight, &value)| weight * value)
3699 .sum();
3700 assert_eq!(batch.initial_sum(&tensor, Some(&successor)), dot);
3701
3702 let r_prime = Point::<EF>::rand(&mut rng, reduction.num_variables());
3703 assert_eq!(
3704 batch.closing_weight(&r_prime).unwrap(),
3705 weights.eval_base(&r_prime)
3706 );
3707 }
3708
3709 #[test]
3710 fn a_successor_view_round_trips() {
3711 let witness = bits(0x7219, 64);
3716 let packing = BitPacking::<EF>::new(&witness).unwrap();
3717 let cells = embedded(&witness);
3718
3719 for selector in Selector::ALL {
3720 for (seed, &row_variables) in (0x721A..).zip(&ROW_VARIABLES) {
3721 let r = successor_point(seed, 9, row_variables, selector);
3722 let reduction = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3723 let current = cells.eval_base(&r);
3724 let next = dense_successor_claim(&witness, &r, row_variables);
3725
3726 let (proof, r_prime, value) =
3727 reduction.prove::<EF, _, _>(&packing, &mut challenger());
3728 let verified = reduction
3729 .verify_readings(&proof, Some(current), Some(next), &mut challenger())
3730 .unwrap();
3731
3732 let case = alloc::format!("{selector:?} selector, {row_variables} row variables");
3733 assert_eq!(verified, (r_prime.clone(), value), "{case}");
3734 assert_eq!(value, packing.poly().eval_base(&r_prime), "{case}");
3735 }
3736 }
3737 }
3738
3739 #[test]
3740 fn a_successor_view_inside_one_element_plays_the_plain_transcript() {
3741 let witness = bits(0x1D3, 64);
3746 let packing = BitPacking::<EF>::new(&witness).unwrap();
3747
3748 for row_variables in 0..=BitRingSwitch::<EF>::ABSORBED {
3749 let r = successor_point(0x1D4, 9, row_variables, Selector::Random);
3750 let plain = BitRingSwitch::new(&r).unwrap();
3751 let successor = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3752 assert!(!successor.sends_successor_tensors());
3753
3754 let mut plain_challenger = challenger();
3755 let (plain_proof, plain_point, plain_value) =
3756 plain.prove::<EF, _, _>(&packing, &mut plain_challenger);
3757 let mut successor_challenger = challenger();
3758 let (successor_proof, successor_point, successor_value) =
3759 successor.prove::<EF, _, _>(&packing, &mut successor_challenger);
3760
3761 assert_eq!(successor_proof.tensor, plain_proof.tensor);
3762 assert!(successor_proof.successor.is_none());
3763 assert_eq!(
3764 successor_point, plain_point,
3765 "{row_variables} row variables"
3766 );
3767 assert_eq!(
3768 successor_value, plain_value,
3769 "{row_variables} row variables"
3770 );
3771 assert_eq!(
3772 CanSample::<EF>::sample(&mut successor_challenger),
3773 CanSample::<EF>::sample(&mut plain_challenger),
3774 "{row_variables} row variables"
3775 );
3776 }
3777 }
3778
3779 #[test]
3780 fn the_prefix_stops_before_the_row_coordinates() {
3781 let witness = bits(0x9EF1, 64);
3793 let packing = BitPacking::<EF>::new(&witness).unwrap();
3794 let mut rng = SmallRng::seed_from_u64(0x9EF2);
3795 let mut coordinates = alloc::vec![EF::ONE, EF::ZERO, EF::ONE, EF::ZERO];
3796 coordinates.extend((4..9).map(|_| non_boolean(&mut rng)));
3797 let r = Point::new(coordinates);
3798
3799 let reduction = BitRingSwitch::with_successor(&r, 6).unwrap();
3800 let current = embedded(&witness).eval_base(&r);
3801 let next = dense_successor_claim(&witness, &r, 6);
3802
3803 let tensor = reduction.tensor(&packing).unwrap();
3804 let successor = reduction.successor_tensors(&packing).unwrap();
3805 assert_eq!(
3806 reduction
3807 .successor_claim(&tensor, successor.as_ref())
3808 .unwrap(),
3809 next
3810 );
3811
3812 let (proof, r_prime, value) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3813 assert_eq!(proof.sumcheck.num_rounds(), 2);
3814 assert_eq!(
3815 reduction
3816 .verify_readings(&proof, Some(current), Some(next), &mut challenger())
3817 .unwrap(),
3818 (r_prime, value)
3819 );
3820
3821 let (plain, _, _) = BitRingSwitch::new(&r)
3823 .unwrap()
3824 .prove::<EF, _, _>(&packing, &mut challenger());
3825 assert_eq!(plain.sumcheck.num_rounds(), 1);
3826 }
3827
3828 #[test]
3829 fn a_false_successor_claim_is_rejected() {
3830 let witness = bits(0xFA15, 64);
3834 let packing = BitPacking::<EF>::new(&witness).unwrap();
3835
3836 for row_variables in [2, 6] {
3837 let r = successor_point(0xFA16, 9, row_variables, Selector::Random);
3838 let reduction = BitRingSwitch::with_successor(&r, row_variables).unwrap();
3839 let current = embedded(&witness).eval_base(&r);
3840 let next = dense_successor_claim(&witness, &r, row_variables);
3841 let (proof, _, _) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3842
3843 assert_eq!(
3844 reduction
3845 .verify_readings(
3846 &proof,
3847 Some(current),
3848 Some(next + EF::ONE),
3849 &mut challenger()
3850 )
3851 .unwrap_err(),
3852 BitRingSwitchProofError::SuccessorClaimMismatch,
3853 "{row_variables} row variables"
3854 );
3855 }
3856 }
3857
3858 #[test]
3859 fn a_tampered_carry_element_is_caught_by_the_closing_check() {
3860 let witness = bits(0x7A3, 64);
3872 let packing = BitPacking::<EF>::new(&witness).unwrap();
3873 let r = successor_point(0x7A4, 9, 6, Selector::Random);
3874 let reduction = BitRingSwitch::with_successor(&r, 6).unwrap();
3875 let current = embedded(&witness).eval_base(&r);
3876 let next = dense_successor_claim(&witness, &r, 6);
3877 let (honest, _, _) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3878 let elements = honest.successor.clone().unwrap();
3879
3880 let flipped = |element: &BitTensor<EF>, row: usize, bit: EF| {
3881 let mut rows = element.rows().to_vec();
3882 rows[row] += bit;
3883 BitTensor::try_from(rows).unwrap()
3884 };
3885 let mut carry = honest.clone();
3886 carry.successor = Some(SuccessorTensors {
3887 carry: flipped(&elements.carry, 3, EF::from_repr(1 << 5)),
3888 last: elements.last.clone(),
3889 });
3890 let mut last = honest.clone();
3891 last.successor = Some(SuccessorTensors {
3892 carry: elements.carry.clone(),
3893 last: flipped(&elements.last, 5, EF::ONE),
3894 });
3895
3896 for (element, tampered) in [("carry", carry), ("last", last)] {
3897 assert_ne!(tampered.successor, honest.successor, "{element}");
3899 assert_eq!(
3900 reduction
3901 .successor_claim(&tampered.tensor, tampered.successor.as_ref())
3902 .unwrap(),
3903 next,
3904 "{element}"
3905 );
3906 assert_eq!(
3907 reduction
3908 .verify_readings(&tampered, Some(current), Some(next), &mut challenger())
3909 .unwrap_err(),
3910 BitRingSwitchProofError::FinalCheck,
3911 "{element}"
3912 );
3913 }
3914
3915 assert!(
3917 reduction
3918 .verify_readings(&honest, Some(current), Some(next), &mut challenger())
3919 .is_ok()
3920 );
3921 }
3922
3923 #[test]
3924 fn a_successor_element_count_disagreeing_with_the_setup_is_refused() {
3925 let witness = bits(0xC0DE, 64);
3932 let packing = BitPacking::<EF>::new(&witness).unwrap();
3933
3934 let r_sending = successor_point(0xC0DF, 9, 6, Selector::Random);
3935 let sending = BitRingSwitch::with_successor(&r_sending, 6).unwrap();
3936 let (mut missing, _, _) = sending.prove::<EF, _, _>(&packing, &mut challenger());
3937 let spare = missing.successor.take();
3938
3939 let r_fitting = successor_point(0xC0E0, 9, 2, Selector::Random);
3940 let fitting = BitRingSwitch::with_successor(&r_fitting, 2).unwrap();
3941 let (mut spurious, _, _) = fitting.prove::<EF, _, _>(&packing, &mut challenger());
3942 spurious.successor = spare;
3943
3944 for (reduction, proof, r, expected, actual) in [
3945 (&sending, &missing, &r_sending, 2, 0),
3946 (&fitting, &spurious, &r_fitting, 0, 2),
3947 ] {
3948 let current = embedded(&witness).eval_base(r);
3949 let mut used = challenger();
3950 let err = reduction
3951 .verify_readings(proof, Some(current), None, &mut used)
3952 .unwrap_err();
3953
3954 assert_eq!(
3955 err,
3956 BitRingSwitchProofError::Width(TranscriptWidth::SuccessorElements {
3957 expected,
3958 actual
3959 })
3960 );
3961 assert_eq!(
3962 CanSample::<EF>::sample(&mut used),
3963 CanSample::<EF>::sample(&mut challenger()),
3964 "the rejection advanced the sponge"
3965 );
3966 }
3967 }
3968
3969 #[test]
3970 fn a_reading_the_setup_does_not_answer_is_refused() {
3971 let (reduction, packing, _, _) = fixture(0x2EAD, 32);
3974 let (proof, _, _) = reduction.prove::<EF, _, _>(&packing, &mut challenger());
3975
3976 assert_eq!(
3977 reduction
3978 .verify_readings(&proof, None, Some(EF::ONE), &mut challenger())
3979 .unwrap_err(),
3980 BitRingSwitchProofError::NoSuccessorView
3981 );
3982 assert_eq!(
3983 reduction
3984 .verify_readings(&proof, None, None, &mut challenger())
3985 .unwrap_err(),
3986 BitRingSwitchProofError::NoReading
3987 );
3988 }
3989
3990 #[test]
3991 fn a_successor_setup_of_the_wrong_kind_is_refused() {
3992 let mut rng = SmallRng::seed_from_u64(0x5E7C);
3996 let r = Point::<EF>::rand(&mut rng, 9);
3997 let r_batch = Point::<EF>::rand(&mut rng, BitRingSwitch::<EF>::ABSORBED);
3998
3999 assert_eq!(
4000 BitRingSwitch::with_successor(&r, 10).unwrap_err(),
4001 BitRingSwitchError::RowVariables {
4002 row_variables: 10,
4003 num_variables: 9
4004 }
4005 );
4006 assert_eq!(
4007 BitRingSwitch::with_successor(&r, 6)
4008 .unwrap()
4009 .batch(&r_batch)
4010 .unwrap_err(),
4011 BitRingSwitchError::SuccessorBatching { expected: true }
4012 );
4013 assert_eq!(
4014 BitRingSwitch::with_successor(&r, 4)
4015 .unwrap()
4016 .batch_with_successor(&r_batch, EF::ONE)
4017 .unwrap_err(),
4018 BitRingSwitchError::SuccessorBatching { expected: false }
4019 );
4020 assert_eq!(
4021 BitRingSwitch::new(&r)
4022 .unwrap()
4023 .successor_claim(&BitTensor::zero(), None)
4024 .unwrap_err(),
4025 BitRingSwitchError::NoSuccessorView
4026 );
4027 }
4028
4029 #[allow(clippy::type_complexity)]
4039 fn both_representations(
4040 reduction: &BitRingSwitch<BinaryField128>,
4041 packing: &BitPacking<BinaryField128>,
4042 ) -> [(
4043 BitRingSwitchProof<BinaryField128>,
4044 Point<BinaryField128>,
4045 BinaryField128,
4046 WideChal,
4047 ); 2] {
4048 let mut tower_sponge = WideChal::from_hasher(Vec::new(), Keccak256Hash);
4049 let tower = reduction.prove::<BinaryField128, _, _>(packing, &mut tower_sponge);
4050 let mut poly_sponge = WideChal::from_hasher(Vec::new(), Keccak256Hash);
4051 let poly = reduction.prove::<Ghash128, _, _>(packing, &mut poly_sponge);
4052
4053 [
4054 (tower.0, tower.1, tower.2, tower_sponge),
4055 (poly.0, poly.1, poly.2, poly_sponge),
4056 ]
4057 }
4058
4059 #[test]
4060 fn a_borrowed_packing_plays_the_owned_packing_transcript() {
4061 let (reduction, packing, _, _) = fixture(0x5AE, 16);
4066 let borrowed = BitPacking::from_packed(packing.poly().as_view()).unwrap();
4067 assert_eq!(borrowed.num_variables(), packing.num_variables());
4068
4069 let (owned_proof, owned_point, owned_value) =
4070 reduction.prove::<EF, _, _>(&packing, &mut challenger());
4071 let (borrowed_proof, borrowed_point, borrowed_value) =
4072 reduction.prove::<EF, _, _>(&borrowed, &mut challenger());
4073
4074 assert_eq!(borrowed_proof.tensor, owned_proof.tensor);
4075 assert_eq!(borrowed_proof.successor, owned_proof.successor);
4076 assert_eq!(
4077 borrowed_proof.sumcheck.polynomial_evaluations,
4078 owned_proof.sumcheck.polynomial_evaluations
4079 );
4080 assert_eq!(borrowed_proof.final_eval, owned_proof.final_eval);
4081 assert_eq!(borrowed_point, owned_point);
4082 assert_eq!(borrowed_value, owned_value);
4083 }
4084
4085 #[test]
4086 fn the_polynomial_basis_rounds_play_the_tower_basis_transcript() {
4087 let witness = bits(0x9F17, 512);
4092 let packing = BitPacking::<BinaryField128>::new(&witness).unwrap();
4093 let absorbed = BitRingSwitch::<BinaryField128>::ABSORBED;
4094 let mut rng = SmallRng::seed_from_u64(0x2C0D);
4095 let num_variables = packing.num_variables() + absorbed;
4096
4097 let random = Point::<BinaryField128>::rand(&mut rng, num_variables);
4100 let mut addressed = random.as_slice().to_vec();
4101 addressed[0] = BinaryField128::ONE;
4102 addressed[1] = BinaryField128::ZERO;
4103 let addressed = Point::new(addressed);
4104 let reductions = [
4105 (BitRingSwitch::new(&random).unwrap(), &random),
4106 (BitRingSwitch::new(&addressed).unwrap(), &addressed),
4107 (
4108 BitRingSwitch::with_successor(&random, absorbed + 1).unwrap(),
4109 &random,
4110 ),
4111 ];
4112 let embedding = embedded_over::<BinaryField128>(&witness);
4113
4114 for (case, (reduction, point)) in reductions.iter().enumerate() {
4115 let [
4116 (tower, tower_point, tower_value, mut tower_sponge),
4117 (poly, poly_point, poly_value, mut poly_sponge),
4118 ] = both_representations(reduction, &packing);
4119
4120 assert!(poly.sumcheck.num_rounds() > 0, "case {case} runs no round");
4122 assert_eq!(poly.tensor, tower.tensor, "case {case}");
4123 assert_eq!(poly.successor, tower.successor, "case {case}");
4124 assert_eq!(
4125 poly.sumcheck.polynomial_evaluations, tower.sumcheck.polynomial_evaluations,
4126 "case {case}"
4127 );
4128 assert_eq!(
4129 poly.sumcheck.pow_witnesses, tower.sumcheck.pow_witnesses,
4130 "case {case}"
4131 );
4132 assert_eq!(poly.final_eval, tower.final_eval, "case {case}");
4133 assert_eq!(poly_point, tower_point, "case {case}");
4134 assert_eq!(poly_value, tower_value, "case {case}");
4135 assert_eq!(
4136 CanSample::<BinaryField128>::sample(&mut poly_sponge),
4137 CanSample::<BinaryField128>::sample(&mut tower_sponge),
4138 "case {case}"
4139 );
4140
4141 let claim = embedding.eval_base(*point);
4144 let mut verifier_sponge = WideChal::from_hasher(Vec::new(), Keccak256Hash);
4145 assert!(
4146 reduction.verify(&poly, claim, &mut verifier_sponge).is_ok(),
4147 "case {case}"
4148 );
4149 }
4150 }
4151}