1mod prefix;
9mod suffix;
10
11use alloc::vec::Vec;
12
13use p3_challenger::{CanObserve, FieldChallenger, GrindingChallenger};
14use p3_commit::Mmcs;
15use p3_dft::TwoAdicSubgroupDft;
16use p3_field::{ExtensionField, TwoAdicField};
17use p3_matrix::dense::DenseMatrix;
18use p3_multilinear_util::point::Point;
19pub use prefix::PrefixProver;
20pub use suffix::SuffixProver;
21
22use crate::SumcheckData;
23use crate::layout::{LayoutStrategy, Table, Witness};
24use crate::strategy::{SumcheckProver, VariableOrder};
25
26pub trait Layout<F: TwoAdicField, EF: ExtensionField<F>>: Sized {
28 fn from_witness(witness: Witness<F>) -> Self;
30
31 fn new_witness(tables: Vec<Table<F>>, folding: usize) -> Witness<F>;
33
34 fn commit<Dft, MT, Challenger>(
45 dft: &Dft,
46 mmcs: &MT,
47 challenger: &mut Challenger,
48 witness: Witness<F>,
49 folding: usize,
50 starting_log_inv_rate: usize,
51 ) -> (Self, MT::Commitment, MT::ProverData<DenseMatrix<F>>)
52 where
53 Dft: TwoAdicSubgroupDft<F>,
54 MT: Mmcs<F>,
55 Challenger: CanObserve<MT::Commitment>;
56
57 fn num_claims(&self) -> usize;
59
60 fn strategy() -> LayoutStrategy;
62
63 fn variable_order() -> VariableOrder {
65 Self::strategy().variable_order
66 }
67
68 fn folding(&self) -> usize;
70
71 fn num_variables(&self) -> usize;
73
74 fn num_variables_table(&self, id: usize) -> usize;
76
77 fn eval<Ch>(&mut self, table_idx: usize, polys: &[usize], challenger: &mut Ch) -> Vec<EF>
79 where
80 Ch: FieldChallenger<F> + GrindingChallenger<Witness = F>;
81
82 fn add_virtual_eval<Ch>(&mut self, challenger: &mut Ch) -> EF
84 where
85 Ch: FieldChallenger<F> + GrindingChallenger<Witness = F>;
86
87 fn into_sumcheck<Ch>(
94 self,
95 sumcheck_data: &mut SumcheckData<F, EF>,
96 pow_bits: usize,
97 challenger: &mut Ch,
98 ) -> (SumcheckProver<F, EF>, Point<EF>)
99 where
100 Ch: FieldChallenger<F> + GrindingChallenger<Witness = F>;
101}
102
103#[cfg(test)]
104pub(super) mod test_utils {
105 use alloc::vec;
112 use alloc::vec::Vec;
113
114 use p3_challenger::FieldChallenger;
115 use p3_field::PrimeCharacteristicRing;
116 use p3_multilinear_util::point::Point;
117 use p3_multilinear_util::poly::Poly;
118 use proptest::prelude::*;
119 use rand::SeedableRng;
120 use rand::rngs::SmallRng;
121
122 use crate::SumcheckData;
123 use crate::layout::{Layout, LayoutStrategy, Table, TableShape, Verifier, Witness};
124 use crate::strategy::VariableOrder;
125 use crate::tests::*;
126
127 pub(crate) const FOLDING: usize = 4;
129 pub(crate) const POW_BITS: usize = 10;
131 pub(crate) const ROUND_EQ_POINTS: usize = 10;
133 pub(crate) const ROUND_SEL_POINTS: usize = 100;
135
136 pub(crate) const ASCENDING_POLYS: &[(usize, &[usize])] =
138 &[(0, &[0, 1]), (0, &[0]), (1, &[0, 1]), (1, &[1])];
139
140 pub(crate) const NON_ASCENDING_POLYS: &[(usize, &[usize])] =
144 &[(0, &[1, 0]), (0, &[0]), (1, &[0, 1]), (1, &[1])];
145
146 pub(crate) fn build_tables() -> Vec<Table<F>> {
153 let mut rng = SmallRng::seed_from_u64(1);
154 let a0 = Poly::<F>::rand(&mut rng, 10);
156 let a1 = Poly::<F>::rand(&mut rng, 10);
157 let b0 = Poly::<F>::rand(&mut rng, 9);
159 let b1 = Poly::<F>::rand(&mut rng, 9);
160 vec![Table::new(vec![b0, b1]), Table::new(vec![a0, a1])]
161 }
162
163 pub(crate) fn table_shapes() -> Vec<TableShape> {
165 vec![TableShape::new(9, 2), TableShape::new(10, 2)]
166 }
167
168 pub(crate) fn arb_opening_schedule() -> impl Strategy<Value = Vec<(usize, Vec<usize>)>> {
176 let one_call =
178 (0usize..2, prop::collection::vec(0usize..2, 1..=2)).prop_map(|(table_idx, polys)| {
179 let mut seen = [false; 2];
182 let dedup: Vec<usize> = polys
183 .into_iter()
184 .filter(|&p| {
185 let first = !seen[p];
186 seen[p] = true;
187 first
188 })
189 .collect();
190 (table_idx, dedup)
191 });
192 prop::collection::vec(one_call, 1..=6)
193 }
194
195 #[allow(clippy::too_many_arguments)]
201 pub(crate) fn verify_roundtrip(
202 strategy: LayoutStrategy,
203 shapes: &[TableShape],
204 stacked_num_variables: usize,
205 opening_claims: Vec<(usize, Vec<usize>, Vec<EF>)>,
206 virtual_eval: EF,
207 proof0: &SumcheckData<F, EF>,
208 proof1: &SumcheckData<F, EF>,
209 proof2: &SumcheckData<F, EF>,
210 intermediate_evals: &[EF],
211 expected_randomness: &Point<EF>,
212 final_folded_value: EF,
213 ) {
214 let mut verifier_challenger = challenger();
216 let mut verifier: Verifier<F, EF> = Verifier::new(shapes, strategy);
217
218 for (table_idx, polys, evals) in opening_claims {
222 verifier.add_claim(table_idx, &polys, &evals, &mut verifier_challenger);
223 }
224
225 verifier.add_virtual_eval(virtual_eval, &mut verifier_challenger);
227
228 let alpha = verifier_challenger.sample_algebra_element();
230 let initial_constraint = verifier.constraint(alpha);
231 let mut sum = EF::ZERO;
232 initial_constraint.combine_evals(&mut sum);
233 assert_eq!(sum, verifier.sum(alpha));
234
235 let mut constraints = vec![initial_constraint];
237 let mut verifier_challenge = Point::new(vec![]);
238 verifier_challenge.extend(
239 &proof0
240 .verify_rounds(&mut verifier_challenger, &mut sum, 0)
241 .unwrap(),
242 );
243
244 let intermediate_constraint = read_constraint(
245 &mut verifier_challenger,
246 intermediate_evals,
247 stacked_num_variables - FOLDING,
248 ROUND_EQ_POINTS,
249 ROUND_SEL_POINTS,
250 );
251 intermediate_constraint.combine_evals(&mut sum);
252 constraints.push(intermediate_constraint);
253 verifier_challenge.extend(
254 &proof1
255 .verify_rounds(&mut verifier_challenger, &mut sum, POW_BITS)
256 .unwrap(),
257 );
258 verifier_challenge.extend(
259 &proof2
260 .verify_rounds(&mut verifier_challenger, &mut sum, 0)
261 .unwrap(),
262 );
263
264 assert_eq!(expected_randomness, &verifier_challenge);
268 let weights = strategy
269 .variable_order
270 .eval_constraints_poly(&constraints, &verifier_challenge);
271 assert_eq!(sum, final_folded_value * weights);
272 }
273
274 pub(crate) fn drive_intermediate_and_final(
280 prover: &mut crate::strategy::SumcheckProver<F, EF>,
281 prover_challenger: &mut MyChallenger,
282 prover_randomness: &mut Point<EF>,
283 stacked_num_variables: usize,
284 ) -> (SumcheckData<F, EF>, SumcheckData<F, EF>, Vec<EF>, EF) {
285 let mut intermediate_evals: Vec<EF> = Vec::new();
287 let constraint = make_constraint_ext(
288 prover_challenger,
289 &mut intermediate_evals,
290 prover.num_variables(),
291 ROUND_EQ_POINTS,
292 ROUND_SEL_POINTS,
293 &prover.evals(),
294 );
295
296 let mut proof1 = SumcheckData::<F, EF>::default();
297 prover_randomness.extend(&prover.compute_sumcheck_polynomials(
298 &mut proof1,
299 prover_challenger,
300 FOLDING,
301 POW_BITS,
302 Some(constraint),
303 ));
304 let remaining_vars = stacked_num_variables - FOLDING - FOLDING;
305 assert_eq!(proof1.num_rounds(), FOLDING);
306 assert_eq!(prover.num_variables(), remaining_vars);
307
308 let mut proof2 = SumcheckData::<F, EF>::default();
310 prover_randomness.extend(&prover.compute_sumcheck_polynomials(
311 &mut proof2,
312 prover_challenger,
313 remaining_vars,
314 0,
315 None,
316 ));
317 assert_eq!(proof2.num_rounds(), remaining_vars);
318 assert_eq!(prover.num_variables(), 0);
319
320 let final_folded_value = prover.evals().as_constant().unwrap();
321 (proof1, proof2, intermediate_evals, final_folded_value)
322 }
323
324 fn replay_schedule<F>(
331 calls: &[(usize, &[usize])],
332 mut step: F,
333 ) -> Vec<(usize, Vec<usize>, Vec<EF>)>
334 where
335 F: FnMut(usize, &[usize]) -> Vec<EF>,
336 {
337 calls
338 .iter()
339 .map(|&(table_idx, polys)| (table_idx, polys.to_vec(), step(table_idx, polys)))
340 .collect()
341 }
342
343 pub(crate) fn run_roundtrip_test<L>(
345 witness: Witness<F>,
346 shapes: &[TableShape],
347 calls: &[(usize, &[usize])],
348 ) where
349 L: Layout<F, EF>,
350 {
351 let mut prover_challenger = challenger();
352 let stacked_num_variables = witness.num_variables();
353 let stacked_poly = witness.poly().clone();
355
356 let mut prover_state = L::from_witness(witness);
358 let strategy = L::strategy();
359 let order = strategy.variable_order;
360 let opening_claims = replay_schedule(calls, |t, polys| {
361 prover_state.eval(t, polys, &mut prover_challenger)
362 });
363 let virtual_eval = prover_state.add_virtual_eval(&mut prover_challenger);
364
365 let mut proof0 = SumcheckData::<F, EF>::default();
367 let (mut prover, mut prover_randomness) =
368 prover_state.into_sumcheck(&mut proof0, 0, &mut prover_challenger);
369 assert_eq!(proof0.num_rounds(), FOLDING);
370 assert_eq!(prover.num_variables(), stacked_num_variables - FOLDING);
371
372 let (proof1, proof2, intermediate_evals, final_folded_value) = drive_intermediate_and_final(
374 &mut prover,
375 &mut prover_challenger,
376 &mut prover_randomness,
377 stacked_num_variables,
378 );
379
380 let final_eval = match order {
381 VariableOrder::Prefix => stacked_poly.eval_base(&prover_randomness),
382 VariableOrder::Suffix => stacked_poly.eval_base(&prover_randomness.reversed()),
383 };
384 assert_eq!(final_eval, final_folded_value);
385
386 verify_roundtrip(
387 strategy,
388 shapes,
389 stacked_num_variables,
390 opening_claims,
391 virtual_eval,
392 &proof0,
393 &proof1,
394 &proof2,
395 &intermediate_evals,
396 &prover_randomness,
397 final_folded_value,
398 );
399 }
400
401 const SHAPE_MIN_ARITY: usize = 4;
409
410 const SHAPE_MAX_ARITY: usize = 8;
412
413 const SHAPE_MAX_COLUMNS: usize = 3;
415
416 const SHAPE_MAX_TABLES: usize = 3;
418
419 const SHAPE_MAX_CALLS: usize = 5;
421
422 pub(crate) type WitnessShape = Vec<(usize, usize)>;
424
425 pub(crate) type OpeningSchedule = Vec<(usize, Vec<usize>)>;
427
428 pub(crate) fn tables_from_shape(shape: &[(usize, usize)]) -> Vec<Table<F>> {
434 let mut rng = SmallRng::seed_from_u64(42);
436
437 shape
439 .iter()
440 .map(|&(arity, num_cols)| {
441 let polys: Vec<Poly<F>> = (0..num_cols)
442 .map(|_| Poly::<F>::rand(&mut rng, arity))
443 .collect();
444 Table::new(polys)
445 })
446 .collect()
447 }
448
449 pub(crate) fn table_shapes_from(shape: &[(usize, usize)]) -> Vec<TableShape> {
451 shape
452 .iter()
453 .map(|&(arity, num_cols)| TableShape::new(arity, num_cols))
454 .collect()
455 }
456
457 pub(crate) fn arb_witness_and_schedule()
471 -> impl Strategy<Value = (WitnessShape, OpeningSchedule)> {
472 let shape = prop::collection::vec(
477 (
478 SHAPE_MIN_ARITY..=SHAPE_MAX_ARITY,
479 1usize..=SHAPE_MAX_COLUMNS,
480 ),
481 1..=SHAPE_MAX_TABLES,
482 )
483 .prop_filter(
484 "stacked polynomial must support two phases of FOLDING rounds",
485 |shape| {
486 let total: usize = shape.iter().map(|&(a, c)| (1usize << a) * c).sum();
487 total > (1usize << (2 * FOLDING - 1))
489 },
490 );
491
492 shape.prop_flat_map(|shape| {
493 let num_tables = shape.len();
495 let shape_for_calls = shape.clone();
496
497 let one_call = (0..num_tables).prop_flat_map(move |t_idx| {
499 let num_cols = shape_for_calls[t_idx].1;
500 prop::collection::vec(0..num_cols, 1..=num_cols).prop_map(move |cols| {
503 let mut seen = vec![false; num_cols];
504 let dedup: Vec<usize> = cols
505 .into_iter()
506 .filter(|&c| {
507 let first = !seen[c];
508 seen[c] = true;
509 first
510 })
511 .collect();
512 (t_idx, dedup)
513 })
514 });
515
516 prop::collection::vec(one_call, 1..=SHAPE_MAX_CALLS)
518 .prop_map(move |sched| (shape.clone(), sched))
519 })
520 }
521}
522
523#[cfg(test)]
524mod tests {
525
526 use alloc::vec::Vec;
527
528 use proptest::prelude::*;
529
530 use super::test_utils::{
531 ASCENDING_POLYS, NON_ASCENDING_POLYS, arb_opening_schedule, arb_witness_and_schedule,
532 table_shapes_from,
533 };
534 use super::{PrefixProver, SuffixProver};
535 use crate::layout::Layout;
536 use crate::layout::prover::test_utils::{
537 FOLDING, build_tables, run_roundtrip_test, table_shapes, tables_from_shape,
538 };
539 use crate::tests::*;
540
541 #[test]
542 fn num_claims_counts_every_recorded_opening() {
543 fn run_num_claims_test_with<L>(witness: crate::layout::Witness<F>)
544 where
545 L: Layout<F, EF>,
546 {
547 let mut prover = L::from_witness(witness);
548 assert_eq!(prover.num_claims(), 0);
549
550 let mut ch = challenger();
551 prover.eval(0, &[0, 1], &mut ch);
552 assert_eq!(prover.num_claims(), 2);
553
554 prover.eval(1, &[0], &mut ch);
555 assert_eq!(prover.num_claims(), 3);
556 }
557
558 run_num_claims_test_with::<SuffixProver<F, EF>>(SuffixProver::<F, EF>::new_witness(
559 build_tables(),
560 FOLDING,
561 ));
562 run_num_claims_test_with::<PrefixProver<F, EF>>(PrefixProver::<F, EF>::new_witness(
563 build_tables(),
564 FOLDING,
565 ));
566 }
567
568 #[test]
569 fn roundtrip_ascending_polys() {
570 run_roundtrip_test::<PrefixProver<F, EF>>(
571 PrefixProver::<F, EF>::new_witness(build_tables(), FOLDING),
572 &table_shapes(),
573 ASCENDING_POLYS,
574 );
575
576 run_roundtrip_test::<SuffixProver<F, EF>>(
577 SuffixProver::<F, EF>::new_witness(build_tables(), FOLDING),
578 &table_shapes(),
579 ASCENDING_POLYS,
580 );
581 }
582
583 #[test]
584 fn roundtrip_non_ascending_polys() {
585 run_roundtrip_test::<PrefixProver<F, EF>>(
586 PrefixProver::<F, EF>::new_witness(build_tables(), FOLDING),
587 &table_shapes(),
588 NON_ASCENDING_POLYS,
589 );
590
591 run_roundtrip_test::<SuffixProver<F, EF>>(
592 SuffixProver::<F, EF>::new_witness(build_tables(), FOLDING),
593 &table_shapes(),
594 NON_ASCENDING_POLYS,
595 );
596 }
597
598 fn run_shape_test<L>(shape: &[(usize, usize)], schedule: &[(usize, Vec<usize>)])
599 where
600 L: Layout<F, EF>,
601 {
602 let witness = L::new_witness(tables_from_shape(shape), FOLDING);
603 let shapes = table_shapes_from(shape);
604 let borrowed: Vec<(usize, &[usize])> = schedule
605 .iter()
606 .map(|(t, polys)| (*t, polys.as_slice()))
607 .collect();
608 run_roundtrip_test::<L>(witness, &shapes, &borrowed);
609 }
610
611 proptest! {
612 #![proptest_config(ProptestConfig { cases: 16, ..ProptestConfig::default() })]
613
614 #[test]
621 fn roundtrip_proptest(schedule in arb_opening_schedule()) {
622 let borrowed: Vec<(usize, &[usize])> = schedule
623 .iter()
624 .map(|(t, polys)| (*t, polys.as_slice()))
625 .collect();
626
627 run_roundtrip_test::<PrefixProver<F, EF>>(
628 PrefixProver::<F, EF>::new_witness(build_tables(), FOLDING),
629 &table_shapes(),
630 &borrowed,
631 );
632 run_roundtrip_test::<SuffixProver<F, EF>>(
633 SuffixProver::<F, EF>::new_witness(build_tables(), FOLDING),
634 &table_shapes(),
635 &borrowed,
636 );
637 }
638 }
639
640 proptest! {
641 #![proptest_config(ProptestConfig { cases: 8, ..ProptestConfig::default() })]
642
643 #[test]
647 fn roundtrip_shape_proptest((shape, schedule) in arb_witness_and_schedule()) {
648 run_shape_test::<PrefixProver<F, EF>>(&shape, &schedule);
649 run_shape_test::<SuffixProver<F, EF>>(&shape, &schedule);
650 }
651 }
652}