1use alloc::collections::BTreeMap;
2use alloc::vec;
3use alloc::vec::Vec;
4use core::marker::PhantomData;
5
6use itertools::{Itertools, izip};
7use p3_challenger::{CanObserve, FieldChallenger, GrindingChallenger};
8use p3_commit::{BatchOpening, BatchOpeningRef, Mmcs, OpenedValues, Pcs, PolynomialSpace};
9use p3_field::extension::ComplexExtendable;
10use p3_field::{ExtensionField, Field};
11use p3_fri::FriParameters;
12use p3_fri::verifier::FriError;
13use p3_matrix::dense::{RowMajorMatrix, RowMajorMatrixCow};
14use p3_matrix::row_index_mapped::RowIndexMappedView;
15use p3_matrix::{Dimensions, Matrix};
16use p3_maybe_rayon::prelude::*;
17use p3_util::log2_strict_usize;
18use p3_util::zip_eq::zip_eq;
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21use tracing::info_span;
22
23use crate::deep_quotient::{deep_quotient_reduce_row, extract_lambda};
24use crate::domain::CircleDomain;
25use crate::folding::{CircleFriFolding, CircleFriFoldingForMmcs, fold_y, fold_y_row};
26use crate::point::Point;
27use crate::prover::prove;
28use crate::verifier::verify;
29use crate::{CfftPerm, CfftPermutable, CircleEvaluations, CircleFriProof, cfft_permute_index};
30
31#[derive(Debug)]
32pub struct CirclePcs<Val: Field, InputMmcs, FriMmcs> {
33 pub mmcs: InputMmcs,
34 pub fri_params: FriParameters<FriMmcs>,
35 pub _phantom: PhantomData<Val>,
36}
37
38impl<Val: Field, InputMmcs, FriMmcs> CirclePcs<Val, InputMmcs, FriMmcs> {
39 pub const fn new(mmcs: InputMmcs, fri_params: FriParameters<FriMmcs>) -> Self {
40 Self {
41 mmcs,
42 fri_params,
43 _phantom: PhantomData,
44 }
45 }
46}
47
48#[derive(Serialize, Deserialize, Clone)]
49#[serde(bound = "")]
50pub struct CircleInputProof<
51 Val: Field,
52 Challenge: Field,
53 InputMmcs: Mmcs<Val>,
54 FriMmcs: Mmcs<Challenge>,
55> {
56 input_openings: Vec<BatchOpening<Val, InputMmcs>>,
57 first_layer_siblings: Vec<Challenge>,
58 first_layer_proof: FriMmcs::Proof,
59}
60
61#[derive(Debug, Error)]
62pub enum InputError<InputMmcsError, FriMmcsError>
63where
64 InputMmcsError: core::fmt::Debug,
65 FriMmcsError: core::fmt::Debug,
66{
67 #[error("input MMCS error: {0:?}")]
68 InputMmcsError(InputMmcsError),
69 #[error("first layer MMCS error: {0:?}")]
70 FirstLayerMmcsError(FriMmcsError),
71 #[error("input shape error: mismatched dimensions")]
72 InputShapeError,
73}
74
75#[derive(Serialize, Deserialize, Clone)]
76#[serde(bound(
77 serialize = "Witness: Serialize",
78 deserialize = "Witness: Deserialize<'de>"
79))]
80pub struct CirclePcsProof<
81 Val: Field,
82 Challenge: Field,
83 InputMmcs: Mmcs<Val>,
84 FriMmcs: Mmcs<Challenge>,
85 Witness,
86> {
87 first_layer_commitment: FriMmcs::Commitment,
88 lambdas: Vec<Challenge>,
89 fri_proof: CircleFriProof<
90 Challenge,
91 FriMmcs,
92 Witness,
93 CircleInputProof<Val, Challenge, InputMmcs, FriMmcs>,
94 >,
95}
96
97impl<Val, InputMmcs, FriMmcs, Challenge, Challenger> Pcs<Challenge, Challenger>
98 for CirclePcs<Val, InputMmcs, FriMmcs>
99where
100 Val: ComplexExtendable,
101 Challenge: ExtensionField<Val>,
102 InputMmcs: Mmcs<Val>,
103 FriMmcs: Mmcs<Challenge>,
104 Challenger: FieldChallenger<Val> + GrindingChallenger + CanObserve<FriMmcs::Commitment>,
105{
106 type Domain = CircleDomain<Val>;
107 type Commitment = InputMmcs::Commitment;
108 type ProverData = InputMmcs::ProverData<RowMajorMatrix<Val>>;
109 type EvaluationsOnDomain<'a> = RowIndexMappedView<CfftPerm, RowMajorMatrixCow<'a, Val>>;
110 type Proof = CirclePcsProof<Val, Challenge, InputMmcs, FriMmcs, Challenger::Witness>;
111 type Error = FriError<FriMmcs::Error, InputError<InputMmcs::Error, FriMmcs::Error>>;
112 const ZK: bool = false;
113
114 fn natural_domain_for_degree(&self, degree: usize) -> Self::Domain {
115 CircleDomain::standard(log2_strict_usize(degree))
116 }
117
118 fn commit(
119 &self,
120 evaluations: impl IntoIterator<Item = (Self::Domain, RowMajorMatrix<Val>)>,
121 ) -> (Self::Commitment, Self::ProverData) {
122 let ldes = evaluations
123 .into_iter()
124 .map(|(domain, evals)| {
125 assert!(
126 domain.log_n >= 2,
127 "CirclePcs cannot commit to a matrix with fewer than 4 rows.",
128 );
130 CircleEvaluations::from_natural_order(domain, evals)
131 .extrapolate(CircleDomain::standard(
132 domain.log_n + self.fri_params.log_blowup,
133 ))
134 .to_cfft_order()
135 })
136 .collect_vec();
137 let (comm, mmcs_data) = self.mmcs.commit(ldes);
138 (comm, mmcs_data)
139 }
140
141 fn get_evaluations_on_domain<'a>(
142 &self,
143 data: &'a Self::ProverData,
144 idx: usize,
145 domain: Self::Domain,
146 ) -> Self::EvaluationsOnDomain<'a> {
147 let mat = self.mmcs.get_matrices(data)[idx].as_view();
148 let committed_domain = CircleDomain::standard(log2_strict_usize(mat.height()));
149 if domain == committed_domain {
150 mat.as_cow().cfft_perm_rows()
151 } else {
152 CircleEvaluations::from_cfft_order(committed_domain, mat)
153 .extrapolate(domain)
154 .to_cfft_order()
155 .as_cow()
156 .cfft_perm_rows()
157 }
158 }
159
160 fn open(
161 &self,
162 rounds: Vec<(
164 &Self::ProverData,
165 Vec<
167 Vec<Challenge>,
169 >,
170 )>,
171 challenger: &mut Challenger,
172 ) -> (OpenedValues<Challenge>, Self::Proof) {
173 let values: OpenedValues<Challenge> = rounds
175 .iter()
176 .map(|(data, points_for_mats)| {
177 let mats = self.mmcs.get_matrices(data);
178 debug_assert_eq!(
179 mats.len(),
180 points_for_mats.len(),
181 "Mismatched number of matrices and points"
182 );
183 izip!(mats, points_for_mats)
184 .map(|(mat, points_for_mat)| {
185 let log_height = log2_strict_usize(mat.height());
186 let evals = CircleEvaluations::from_cfft_order(
188 CircleDomain::standard(log_height),
189 mat.as_view(),
190 );
191 points_for_mat
192 .iter()
193 .map(|&zeta| {
194 let zeta = Point::from_projective_line(zeta);
195 let ps_at_zeta =
196 info_span!("compute opened values with Lagrange interpolation")
197 .in_scope(|| evals.evaluate_at_point(zeta));
198 challenger.observe_algebra_slice(&ps_at_zeta);
199 ps_at_zeta
200 })
201 .collect()
202 })
203 .collect()
204 })
205 .collect();
206
207 let alpha: Challenge = challenger.sample_algebra_element();
209
210 let mut reduced_openings: BTreeMap<usize, (Challenge, Vec<Challenge>)> = BTreeMap::new();
222
223 rounds
224 .iter()
225 .zip(values.iter())
226 .for_each(|((data, points_for_mats), values)| {
227 let mats = self.mmcs.get_matrices(data);
228 izip!(mats, points_for_mats, values).for_each(|(mat, points_for_mat, values)| {
229 let log_height = log2_strict_usize(mat.height());
230 let evals = CircleEvaluations::from_cfft_order(
232 CircleDomain::standard(log_height),
233 mat.as_view(),
234 );
235
236 let (alpha_offset, reduced_opening_for_log_height) =
237 reduced_openings.entry(log_height).or_insert_with(|| {
238 (Challenge::ONE, vec![Challenge::ZERO; 1 << log_height])
239 });
240
241 points_for_mat
242 .iter()
243 .zip(values.iter())
244 .for_each(|(&zeta, ps_at_zeta)| {
245 let zeta = Point::from_projective_line(zeta);
246
247 let mat_ros = evals.deep_quotient_reduce(alpha, zeta, ps_at_zeta);
249
250 reduced_opening_for_log_height
252 .par_iter_mut()
253 .zip(mat_ros)
254 .for_each(|(ro, mat_ro)| {
255 *ro += *alpha_offset * mat_ro;
256 });
257
258 *alpha_offset *= alpha.exp_u64(2 * evals.values.width() as u64);
260 });
261 });
262 });
263
264 let mut lambdas = vec![];
268 let mut log_heights = vec![];
269 let first_layer_mats: Vec<RowMajorMatrix<Challenge>> = reduced_openings
270 .into_iter()
271 .map(|(log_height, (_, mut ro))| {
272 assert!(log_height > 0);
273 log_heights.push(log_height);
274 let lambda = extract_lambda(&mut ro, self.fri_params.log_blowup);
275 lambdas.push(lambda);
276 RowMajorMatrix::new(ro, 2)
278 })
279 .collect();
280 let log_max_height = log_heights.iter().max().copied().unwrap();
281
282 let (first_layer_commitment, first_layer_data) =
288 self.fri_params.mmcs.commit(first_layer_mats);
289 challenger.observe(first_layer_commitment.clone());
290 let bivariate_beta: Challenge = challenger.sample_algebra_element();
291
292 let fri_input: Vec<Vec<Challenge>> = self
295 .fri_params
296 .mmcs
297 .get_matrices(&first_layer_data)
298 .into_iter()
299 .map(|m| fold_y(bivariate_beta, m))
300 .rev()
302 .collect();
303
304 let folding: CircleFriFoldingForMmcs<Val, Challenge, InputMmcs, FriMmcs> =
305 CircleFriFolding(PhantomData);
306
307 let fri_proof = prove(&folding, &self.fri_params, fri_input, challenger, |index| {
308 let input_openings = rounds
313 .iter()
314 .map(|(data, _)| {
315 let log_max_batch_height = log2_strict_usize(self.mmcs.get_max_height(data));
316 let reduced_index = index >> (log_max_height - log_max_batch_height);
317 self.mmcs.open_batch(reduced_index, data)
318 })
319 .collect();
320
321 let (first_layer_values, first_layer_proof) = self
324 .fri_params
325 .mmcs
326 .open_batch(index >> 1, &first_layer_data)
327 .unpack();
328 let first_layer_siblings = izip!(&first_layer_values, &log_heights)
329 .map(|(v, log_height)| {
330 let reduced_index = index >> (log_max_height - log_height);
331 let sibling_index = (reduced_index & 1) ^ 1;
332 v[sibling_index]
333 })
334 .collect();
335 CircleInputProof {
336 input_openings,
337 first_layer_siblings,
338 first_layer_proof,
339 }
340 });
341
342 (
343 values,
344 CirclePcsProof {
345 first_layer_commitment,
346 lambdas,
347 fri_proof,
348 },
349 )
350 }
351
352 fn verify(
353 &self,
354 rounds: Vec<(
356 Self::Commitment,
357 Vec<(
359 Self::Domain,
361 Vec<(
363 Challenge,
365 Vec<Challenge>,
367 )>,
368 )>,
369 )>,
370 proof: &Self::Proof,
371 challenger: &mut Challenger,
372 ) -> Result<(), Self::Error> {
373 for (_, round) in &rounds {
375 for (_, mat) in round {
376 for (_, point) in mat {
377 challenger.observe_algebra_slice(point);
378 }
379 }
380 }
381
382 let alpha: Challenge = challenger.sample_algebra_element();
384 challenger.observe(proof.first_layer_commitment.clone());
385 let bivariate_beta: Challenge = challenger.sample_algebra_element();
386
387 let log_global_max_height =
389 proof.fri_proof.commit_phase_commits.len() + self.fri_params.log_blowup + 1;
390
391 let folding: CircleFriFoldingForMmcs<Val, Challenge, InputMmcs, FriMmcs> =
392 CircleFriFolding(PhantomData);
393
394 verify(
395 &folding,
396 &self.fri_params,
397 &proof.fri_proof,
398 challenger,
399 |index, input_proof| {
400 let mut reduced_openings = BTreeMap::new();
402
403 let CircleInputProof {
404 input_openings,
405 first_layer_siblings,
406 first_layer_proof,
407 } = input_proof;
408
409 for (batch_opening, (batch_commit, mats)) in
410 zip_eq(input_openings, &rounds, InputError::InputShapeError)?
411 {
412 let batch_heights: Vec<usize> = mats
413 .iter()
414 .map(|(domain, _)| domain.size() << self.fri_params.log_blowup)
415 .collect_vec();
416 let batch_dims: Vec<Dimensions> = batch_heights
417 .iter()
418 .map(|&height| Dimensions { width: 0, height })
420 .collect_vec();
421
422 let (dims, idx) = batch_heights
423 .iter()
424 .max()
425 .map(|x| log2_strict_usize(*x))
426 .map_or_else(
427 ||
428 (&[][..], 0),
430 |log_batch_max_height| {
431 (
432 &batch_dims[..],
433 index >> (log_global_max_height - log_batch_max_height),
434 )
435 },
436 );
437
438 self.mmcs
439 .verify_batch(batch_commit, dims, idx, batch_opening.into())
440 .map_err(InputError::InputMmcsError)?;
441
442 for (ps_at_x, (mat_domain, mat_points_and_values)) in zip_eq(
443 &batch_opening.opened_values,
444 mats,
445 InputError::InputShapeError,
446 )? {
447 let log_height = mat_domain.log_n + self.fri_params.log_blowup;
448 let bits_reduced = log_global_max_height - log_height;
449 let orig_idx = cfft_permute_index(index >> bits_reduced, log_height);
450
451 let committed_domain = CircleDomain::standard(log_height);
452 let x = committed_domain.nth_point(orig_idx);
453
454 let (alpha_offset, ro) = reduced_openings
455 .entry(log_height)
456 .or_insert((Challenge::ONE, Challenge::ZERO));
457 let alpha_pow_width_2 = alpha.exp_u64(ps_at_x.len() as u64).square();
458
459 for (zeta_uni, ps_at_zeta) in mat_points_and_values {
460 let zeta = Point::from_projective_line(*zeta_uni);
461
462 *ro += *alpha_offset
463 * deep_quotient_reduce_row(alpha, x, zeta, ps_at_x, ps_at_zeta);
464
465 *alpha_offset *= alpha_pow_width_2;
466 }
467 }
468 }
469
470 let (mut fri_input, fl_dims, fl_leaves): (Vec<_>, Vec<_>, Vec<_>) = zip_eq(
473 zip_eq(
474 reduced_openings,
475 first_layer_siblings,
476 InputError::InputShapeError,
477 )?,
478 &proof.lambdas,
479 InputError::InputShapeError,
480 )?
481 .map(|(((log_height, (_, ro)), &fl_sib), &lambda)| {
482 assert!(log_height > 0);
483
484 let orig_size = log_height - self.fri_params.log_blowup;
485 let bits_reduced = log_global_max_height - log_height;
486 let orig_idx = cfft_permute_index(index >> bits_reduced, log_height);
487
488 let lde_domain = CircleDomain::standard(log_height);
489 let p: Point<Val> = lde_domain.nth_point(orig_idx);
490
491 let lambda_corrected = ro - lambda * p.v_n(orig_size);
492
493 let mut fl_values = vec![lambda_corrected; 2];
494 fl_values[((index >> bits_reduced) & 1) ^ 1] = fl_sib;
495
496 let fri_input = (
497 log_height - 1,
499 fold_y_row(
500 index >> (bits_reduced + 1),
501 log_height - 1,
503 bivariate_beta,
504 fl_values.iter().copied(),
505 ),
506 );
507
508 let fl_dims = Dimensions {
509 width: 0,
510 height: 1 << (log_height - 1),
511 };
512
513 (fri_input, fl_dims, fl_values)
514 })
515 .multiunzip();
516
517 fri_input.reverse();
519
520 self.fri_params
521 .mmcs
522 .verify_batch(
523 &proof.first_layer_commitment,
524 &fl_dims,
525 index >> 1,
526 BatchOpeningRef::new(&fl_leaves, first_layer_proof),
527 )
528 .map_err(InputError::FirstLayerMmcsError)?;
529
530 Ok(fri_input)
531 },
532 )
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use p3_challenger::{HashChallenger, SerializingChallenger32};
539 use p3_commit::ExtensionMmcs;
540 use p3_field::extension::BinomialExtensionField;
541 use p3_fri::create_test_fri_params;
542 use p3_keccak::Keccak256Hash;
543 use p3_merkle_tree::MerkleTreeMmcs;
544 use p3_mersenne_31::Mersenne31;
545 use p3_symmetric::{CompressionFunctionFromHasher, SerializingHasher};
546 use rand::rngs::SmallRng;
547 use rand::{Rng, SeedableRng};
548
549 use super::*;
550
551 #[test]
552 fn circle_pcs() {
553 let mut rng = SmallRng::seed_from_u64(0);
556
557 type Val = Mersenne31;
558 type Challenge = BinomialExtensionField<Mersenne31, 3>;
559
560 type ByteHash = Keccak256Hash;
561 type FieldHash = SerializingHasher<ByteHash>;
562 let byte_hash = ByteHash {};
563 let field_hash = FieldHash::new(byte_hash);
564
565 type MyCompress = CompressionFunctionFromHasher<ByteHash, 2, 32>;
566 let compress = MyCompress::new(byte_hash);
567
568 type ValMmcs = MerkleTreeMmcs<Val, u8, FieldHash, MyCompress, 32>;
569 let val_mmcs = ValMmcs::new(field_hash, compress);
570
571 type ChallengeMmcs = ExtensionMmcs<Val, Challenge, ValMmcs>;
572 let challenge_mmcs = ChallengeMmcs::new(val_mmcs.clone());
573
574 type Challenger = SerializingChallenger32<Val, HashChallenger<u8, ByteHash, 32>>;
575
576 let fri_params = create_test_fri_params(challenge_mmcs, 0);
577
578 type Pcs = CirclePcs<Val, ValMmcs, ChallengeMmcs>;
579 let pcs = Pcs {
580 mmcs: val_mmcs,
581 fri_params,
582 _phantom: PhantomData,
583 };
584
585 let log_n = 10;
586
587 let d = <Pcs as p3_commit::Pcs<Challenge, Challenger>>::natural_domain_for_degree(
588 &pcs,
589 1 << log_n,
590 );
591
592 let evals = RowMajorMatrix::rand(&mut rng, 1 << log_n, 1);
593
594 let (comm, data) =
595 <Pcs as p3_commit::Pcs<Challenge, Challenger>>::commit(&pcs, [(d, evals)]);
596
597 let zeta: Challenge = rng.random();
598
599 let mut chal = Challenger::from_hasher(vec![], byte_hash);
600 let (values, proof) = pcs.open(vec![(&data, vec![vec![zeta]])], &mut chal);
601
602 let mut chal = Challenger::from_hasher(vec![], byte_hash);
603 pcs.verify(
604 vec![(comm, vec![(d, vec![(zeta, values[0][0][0].clone())])])],
605 &proof,
606 &mut chal,
607 )
608 .expect("verify err");
609 }
610}