1use alloc::collections::BTreeMap;
2use alloc::vec;
3use alloc::vec::Vec;
4use core::marker::PhantomData;
5
6use itertools::{izip, Itertools};
7use p3_challenger::{CanObserve, FieldChallenger, GrindingChallenger};
8use p3_commit::{Mmcs, OpenedValues, Pcs, PolynomialSpace};
9use p3_field::extension::ComplexExtendable;
10use p3_field::{ExtensionField, Field};
11use p3_fri::verifier::FriError;
12use p3_fri::{FriConfig, FriProof};
13use p3_matrix::dense::RowMajorMatrix;
14use p3_matrix::{Dimensions, Matrix};
15use p3_maybe_rayon::prelude::*;
16use p3_util::log2_strict_usize;
17use serde::{Deserialize, Serialize};
18use tracing::info_span;
19
20use crate::deep_quotient::{deep_quotient_reduce_row, extract_lambda};
21use crate::domain::CircleDomain;
22use crate::folding::{fold_y, fold_y_row, CircleFriConfig, CircleFriGenericConfig};
23use crate::point::Point;
24use crate::{cfft_permute_index, CfftPermutable, CircleEvaluations};
25
26#[derive(Debug)]
27pub struct CirclePcs<Val: Field, InputMmcs, FriMmcs> {
28 pub mmcs: InputMmcs,
29 pub fri_config: FriConfig<FriMmcs>,
30 pub _phantom: PhantomData<Val>,
31}
32
33#[derive(Serialize, Deserialize, Clone)]
34#[serde(bound = "")]
35pub struct BatchOpening<Val: Field, InputMmcs: Mmcs<Val>> {
36 pub(crate) opened_values: Vec<Vec<Val>>,
37 pub(crate) opening_proof: <InputMmcs as Mmcs<Val>>::Proof,
38}
39
40#[derive(Serialize, Deserialize, Clone)]
41#[serde(bound = "")]
42pub struct InputProof<Val: Field, Challenge: Field, InputMmcs: Mmcs<Val>, FriMmcs: Mmcs<Challenge>>
43{
44 input_openings: Vec<BatchOpening<Val, InputMmcs>>,
45 first_layer_siblings: Vec<Challenge>,
46 first_layer_proof: FriMmcs::Proof,
47}
48
49#[derive(Debug)]
50pub enum InputError<InputMmcsError, FriMmcsError> {
51 InputMmcsError(InputMmcsError),
52 FirstLayerMmcsError(FriMmcsError),
53}
54
55#[derive(Serialize, Deserialize, Clone)]
56#[serde(bound(
57 serialize = "Witness: Serialize",
58 deserialize = "Witness: Deserialize<'de>"
59))]
60pub struct CirclePcsProof<
61 Val: Field,
62 Challenge: Field,
63 InputMmcs: Mmcs<Val>,
64 FriMmcs: Mmcs<Challenge>,
65 Witness,
66> {
67 first_layer_commitment: FriMmcs::Commitment,
68 lambdas: Vec<Challenge>,
69 fri_proof:
70 FriProof<Challenge, FriMmcs, Witness, InputProof<Val, Challenge, InputMmcs, FriMmcs>>,
71}
72
73impl<Val, InputMmcs, FriMmcs, Challenge, Challenger> Pcs<Challenge, Challenger>
74 for CirclePcs<Val, InputMmcs, FriMmcs>
75where
76 Val: ComplexExtendable,
77 Challenge: ExtensionField<Val>,
78 InputMmcs: Mmcs<Val>,
79 FriMmcs: Mmcs<Challenge>,
80 Challenger: FieldChallenger<Val> + GrindingChallenger + CanObserve<FriMmcs::Commitment>,
81{
82 type Domain = CircleDomain<Val>;
83 type Commitment = InputMmcs::Commitment;
84 type ProverData = InputMmcs::ProverData<RowMajorMatrix<Val>>;
85 type Proof = CirclePcsProof<Val, Challenge, InputMmcs, FriMmcs, Challenger::Witness>;
86 type Error = FriError<FriMmcs::Error, InputError<InputMmcs::Error, FriMmcs::Error>>;
87
88 fn natural_domain_for_degree(&self, degree: usize) -> Self::Domain {
89 CircleDomain::standard(log2_strict_usize(degree))
90 }
91
92 fn commit(
93 &self,
94 evaluations: Vec<(Self::Domain, RowMajorMatrix<Val>)>,
95 ) -> (Self::Commitment, Self::ProverData) {
96 let ldes = evaluations
97 .into_iter()
98 .map(|(domain, evals)| {
99 assert!(
100 domain.log_n >= 2,
101 "CirclePcs cannot commit to a matrix with fewer than 4 rows.",
102 );
104 CircleEvaluations::from_natural_order(domain, evals)
105 .extrapolate(CircleDomain::standard(
106 domain.log_n + self.fri_config.log_blowup,
107 ))
108 .to_cfft_order()
109 })
110 .collect_vec();
111 let (comm, mmcs_data) = self.mmcs.commit(ldes);
112 (comm, mmcs_data)
113 }
114
115 fn get_evaluations_on_domain<'a>(
116 &self,
117 data: &'a Self::ProverData,
118 idx: usize,
119 domain: Self::Domain,
120 ) -> impl Matrix<Val> + 'a {
121 let mat = self.mmcs.get_matrices(data)[idx].as_view();
122 let committed_domain = CircleDomain::standard(log2_strict_usize(mat.height()));
123 if domain == committed_domain {
124 mat.as_cow().cfft_perm_rows()
125 } else {
126 CircleEvaluations::from_cfft_order(committed_domain, mat)
127 .extrapolate(domain)
128 .to_cfft_order()
129 .as_cow()
130 .cfft_perm_rows()
131 }
132 }
133
134 fn open(
135 &self,
136 rounds: Vec<(
138 &Self::ProverData,
139 Vec<
141 Vec<Challenge>,
143 >,
144 )>,
145 challenger: &mut Challenger,
146 ) -> (OpenedValues<Challenge>, Self::Proof) {
147 let alpha: Challenge = challenger.sample_ext_element();
149
150 let mut reduced_openings: BTreeMap<usize, (Challenge, Vec<Challenge>)> = BTreeMap::new();
162
163 let values: OpenedValues<Challenge> = rounds
164 .iter()
165 .map(|(data, points_for_mats)| {
166 let mats = self.mmcs.get_matrices(data);
167 izip!(mats, points_for_mats)
168 .map(|(mat, points_for_mat)| {
169 let log_height = log2_strict_usize(mat.height());
170 let evals = CircleEvaluations::from_cfft_order(
172 CircleDomain::standard(log_height),
173 mat.as_view(),
174 );
175
176 let (alpha_offset, reduced_opening_for_log_height) =
177 reduced_openings.entry(log_height).or_insert_with(|| {
178 (Challenge::one(), vec![Challenge::zero(); 1 << log_height])
179 });
180
181 points_for_mat
182 .iter()
183 .map(|&zeta| {
184 let zeta = Point::from_projective_line(zeta);
185
186 let ps_at_zeta: Vec<Challenge> =
190 info_span!("compute opened values with Lagrange interpolation")
191 .in_scope(|| evals.evaluate_at_point(zeta));
192
193 let mat_ros = evals.deep_quotient_reduce(alpha, zeta, &ps_at_zeta);
195
196 reduced_opening_for_log_height
198 .par_iter_mut()
199 .zip(mat_ros)
200 .for_each(|(ro, mat_ro)| {
201 *ro += *alpha_offset * mat_ro;
202 });
203
204 *alpha_offset *= alpha.exp_u64(2 * evals.values.width() as u64);
206
207 ps_at_zeta
208 })
209 .collect()
210 })
211 .collect()
212 })
213 .collect();
214
215 let mut lambdas = vec![];
219 let mut log_heights = vec![];
220 let first_layer_mats: Vec<RowMajorMatrix<Challenge>> = reduced_openings
221 .into_iter()
222 .map(|(log_height, (_, mut ro))| {
223 assert!(log_height > 0);
224 log_heights.push(log_height);
225 let lambda = extract_lambda(&mut ro, self.fri_config.log_blowup);
226 lambdas.push(lambda);
227 RowMajorMatrix::new(ro, 2)
229 })
230 .collect();
231 let log_max_height = log_heights.iter().max().copied().unwrap();
232
233 let (first_layer_commitment, first_layer_data) =
239 self.fri_config.mmcs.commit(first_layer_mats);
240 challenger.observe(first_layer_commitment.clone());
241 let bivariate_beta: Challenge = challenger.sample_ext_element();
242
243 let fri_input: Vec<Vec<Challenge>> = self
246 .fri_config
247 .mmcs
248 .get_matrices(&first_layer_data)
249 .into_iter()
250 .map(|m| fold_y(bivariate_beta, m.as_view()))
251 .rev()
253 .collect();
254
255 let g: CircleFriConfig<Val, Challenge, InputMmcs, FriMmcs> =
256 CircleFriGenericConfig(PhantomData);
257
258 let fri_proof =
259 p3_fri::prover::prove(&g, &self.fri_config, fri_input, challenger, |index| {
260 let input_openings = rounds
265 .iter()
266 .map(|(data, _)| {
267 let log_max_batch_height =
268 log2_strict_usize(self.mmcs.get_max_height(data));
269 let reduced_index = index >> (log_max_height - log_max_batch_height);
270 let (opened_values, opening_proof) =
271 self.mmcs.open_batch(reduced_index, data);
272 BatchOpening {
273 opened_values,
274 opening_proof,
275 }
276 })
277 .collect();
278
279 let (first_layer_values, first_layer_proof) = self
282 .fri_config
283 .mmcs
284 .open_batch(index >> 1, &first_layer_data);
285 let first_layer_siblings = izip!(&first_layer_values, &log_heights)
286 .map(|(v, log_height)| {
287 let reduced_index = index >> (log_max_height - log_height);
288 let sibling_index = (reduced_index & 1) ^ 1;
289 v[sibling_index]
290 })
291 .collect();
292 InputProof {
293 input_openings,
294 first_layer_siblings,
295 first_layer_proof,
296 }
297 });
298
299 (
300 values,
301 CirclePcsProof {
302 first_layer_commitment,
303 lambdas,
304 fri_proof,
305 },
306 )
307 }
308
309 fn verify(
310 &self,
311 rounds: Vec<(
313 Self::Commitment,
314 Vec<(
316 Self::Domain,
318 Vec<(
320 Challenge,
322 Vec<Challenge>,
324 )>,
325 )>,
326 )>,
327 proof: &Self::Proof,
328 challenger: &mut Challenger,
329 ) -> Result<(), Self::Error> {
330 let alpha: Challenge = challenger.sample_ext_element();
332 challenger.observe(proof.first_layer_commitment.clone());
333 let bivariate_beta: Challenge = challenger.sample_ext_element();
334
335 let log_global_max_height =
337 proof.fri_proof.commit_phase_commits.len() + self.fri_config.log_blowup + 1;
338
339 let g: CircleFriConfig<Val, Challenge, InputMmcs, FriMmcs> =
340 CircleFriGenericConfig(PhantomData);
341
342 p3_fri::verifier::verify(
343 &g,
344 &self.fri_config,
345 &proof.fri_proof,
346 challenger,
347 |index, input_proof| {
348 let mut reduced_openings = BTreeMap::<usize, (Challenge, Challenge)>::new();
350
351 let InputProof {
352 input_openings,
353 first_layer_siblings,
354 first_layer_proof,
355 } = input_proof;
356
357 for (batch_opening, (batch_commit, mats)) in izip!(input_openings, &rounds) {
358 let batch_heights: Vec<usize> = mats
359 .iter()
360 .map(|(domain, _)| (domain.size() << self.fri_config.log_blowup))
361 .collect_vec();
362 let batch_dims: Vec<Dimensions> = batch_heights
363 .iter()
364 .map(|&height| Dimensions { width: 0, height })
366 .collect_vec();
367
368 let log_batch_max_height =
369 log2_strict_usize(batch_heights.iter().max().copied().unwrap());
370
371 self.mmcs
372 .verify_batch(
373 batch_commit,
374 &batch_dims,
375 index >> (log_global_max_height - log_batch_max_height),
376 &batch_opening.opened_values,
377 &batch_opening.opening_proof,
378 )
379 .map_err(InputError::InputMmcsError)?;
380
381 for (ps_at_x, (mat_domain, mat_points_and_values)) in
382 izip!(&batch_opening.opened_values, mats)
383 {
384 let log_height = mat_domain.log_n + self.fri_config.log_blowup;
385 let bits_reduced = log_global_max_height - log_height;
386 let orig_idx = cfft_permute_index(index >> bits_reduced, log_height);
387
388 let committed_domain = CircleDomain::standard(log_height);
389 let x = committed_domain.nth_point(orig_idx);
390
391 let (alpha_offset, ro) = reduced_openings
392 .entry(log_height)
393 .or_insert((Challenge::one(), Challenge::zero()));
394 let alpha_pow_width_2 = alpha.exp_u64(ps_at_x.len() as u64).square();
395
396 for (zeta_uni, ps_at_zeta) in mat_points_and_values {
397 let zeta = Point::from_projective_line(*zeta_uni);
398
399 *ro += *alpha_offset
400 * deep_quotient_reduce_row(alpha, x, zeta, ps_at_x, ps_at_zeta);
401
402 *alpha_offset *= alpha_pow_width_2;
403 }
404 }
405 }
406
407 let (mut fri_input, fl_dims, fl_leaves): (Vec<_>, Vec<_>, Vec<_>) =
410 izip!(reduced_openings, first_layer_siblings, &proof.lambdas)
411 .map(|((log_height, (_, ro)), &fl_sib, &lambda)| {
412 assert!(log_height > 0);
413
414 let orig_size = log_height - self.fri_config.log_blowup;
415 let bits_reduced = log_global_max_height - log_height;
416 let orig_idx = cfft_permute_index(index >> bits_reduced, log_height);
417
418 let lde_domain = CircleDomain::standard(log_height);
419 let p: Point<Val> = lde_domain.nth_point(orig_idx);
420
421 let lambda_corrected = ro - lambda * p.v_n(orig_size);
422
423 let mut fl_values = vec![lambda_corrected; 2];
424 fl_values[((index >> bits_reduced) & 1) ^ 1] = fl_sib;
425
426 let fri_input = (
427 log_height - 1,
429 fold_y_row(
430 index >> (bits_reduced + 1),
431 log_height - 1,
433 bivariate_beta,
434 fl_values.iter().cloned(),
435 ),
436 );
437
438 let fl_dims = Dimensions {
439 width: 0,
440 height: 1 << (log_height - 1),
441 };
442
443 (fri_input, fl_dims, fl_values)
444 })
445 .multiunzip();
446
447 fri_input.reverse();
449
450 self.fri_config
451 .mmcs
452 .verify_batch(
453 &proof.first_layer_commitment,
454 &fl_dims,
455 index >> 1,
456 &fl_leaves,
457 first_layer_proof,
458 )
459 .map_err(InputError::FirstLayerMmcsError)?;
460
461 Ok(fri_input)
462 },
463 )
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use p3_challenger::{HashChallenger, SerializingChallenger32};
470 use p3_commit::ExtensionMmcs;
471 use p3_field::extension::BinomialExtensionField;
472 use p3_keccak::Keccak256Hash;
473 use p3_merkle_tree::FieldMerkleTreeMmcs;
474 use p3_mersenne_31::Mersenne31;
475 use p3_symmetric::{CompressionFunctionFromHasher, SerializingHasher32};
476 use rand::{Rng, SeedableRng};
477 use rand_chacha::ChaCha8Rng;
478
479 use super::*;
480
481 #[test]
482 fn circle_pcs() {
483 let mut rng = ChaCha8Rng::from_seed([0; 32]);
486
487 type Val = Mersenne31;
488 type Challenge = BinomialExtensionField<Mersenne31, 3>;
489
490 type ByteHash = Keccak256Hash;
491 type FieldHash = SerializingHasher32<ByteHash>;
492 let byte_hash = ByteHash {};
493 let field_hash = FieldHash::new(byte_hash);
494
495 type MyCompress = CompressionFunctionFromHasher<u8, ByteHash, 2, 32>;
496 let compress = MyCompress::new(byte_hash);
497
498 type ValMmcs = FieldMerkleTreeMmcs<Val, u8, FieldHash, MyCompress, 32>;
499 let val_mmcs = ValMmcs::new(field_hash, compress);
500
501 type ChallengeMmcs = ExtensionMmcs<Val, Challenge, ValMmcs>;
502 let challenge_mmcs = ChallengeMmcs::new(val_mmcs.clone());
503
504 type Challenger = SerializingChallenger32<Val, HashChallenger<u8, ByteHash, 32>>;
505
506 let fri_config = FriConfig {
507 log_blowup: 1,
508 num_queries: 2,
509 proof_of_work_bits: 1,
510 mmcs: challenge_mmcs,
511 };
512
513 type Pcs = CirclePcs<Val, ValMmcs, ChallengeMmcs>;
514 let pcs = Pcs {
515 mmcs: val_mmcs,
516 fri_config,
517 _phantom: PhantomData,
518 };
519
520 let log_n = 10;
521
522 let d = <Pcs as p3_commit::Pcs<Challenge, Challenger>>::natural_domain_for_degree(
523 &pcs,
524 1 << log_n,
525 );
526
527 let evals = RowMajorMatrix::rand(&mut rng, 1 << log_n, 1);
528
529 let (comm, data) =
530 <Pcs as p3_commit::Pcs<Challenge, Challenger>>::commit(&pcs, vec![(d, evals)]);
531
532 let zeta: Challenge = rng.gen();
533
534 let mut chal = Challenger::from_hasher(vec![], byte_hash);
535 let (values, proof) = pcs.open(vec![(&data, vec![vec![zeta]])], &mut chal);
536
537 let mut chal = Challenger::from_hasher(vec![], byte_hash);
538 pcs.verify(
539 vec![(comm, vec![(d, vec![(zeta, values[0][0][0].clone())])])],
540 &proof,
541 &mut chal,
542 )
543 .expect("verify err");
544 }
545}