1use crate::fri::{self, LeafProof, Tree};
2use crate::hash::Hash;
3use crate::utils;
4use anyhow::{Result, anyhow};
5use ff::{Field, PrimeField};
6use primitive_types::U256;
7use starkom_bluesky::Scalar;
8use starkom_poly;
9use std::collections::{BTreeMap, BTreeSet};
10use std::sync::LazyLock;
11
12pub type Polynomial = starkom_poly::Polynomial<Scalar>;
15
16pub const LAMBDA: usize = 128;
18
19static QUERY_DST: LazyLock<Scalar> = LazyLock::new(|| utils::hash_to_scalar(b"starkom/pcs/query"));
21
22static RLC_DST: LazyLock<Scalar> = LazyLock::new(|| utils::hash_to_scalar(b"starkom/pcs/rlc"));
24
25fn num_queries(blowup_log2: usize, num_points: usize) -> usize {
28 let extra = num_points.next_power_of_two().trailing_zeros() as usize;
29 (LAMBDA + extra).div_ceil(blowup_log2)
30}
31
32fn rlc(values: &[Scalar], alpha: Scalar) -> Scalar {
36 let mut rlc = Scalar::ZERO;
37 let mut pow = Scalar::ONE;
38 for &value in values {
39 rlc += value * pow;
40 pow *= alpha;
41 }
42 rlc
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Commitment {
48 tree_roots: Vec<Scalar>,
51 inner: fri::Commitment,
53}
54
55impl Commitment {
56 pub fn tree_roots(&self) -> &[Scalar] {
58 self.tree_roots.as_slice()
59 }
60
61 fn get_query_indices<H: Hash<Scalar>>(
64 &self,
65 degree_bound: usize,
66 blowup_log2: usize,
67 num_points: usize,
68 ) -> Vec<usize> {
69 let n = U256::from((degree_bound << blowup_log2) as u64);
70 let k = num_queries(blowup_log2, num_points);
71 let mut indices = Vec::with_capacity(k);
72 for i in 0..k {
73 let hash = H::hash_many(
74 std::iter::once(*QUERY_DST)
75 .chain(std::iter::once(Scalar::from(self.tree_roots.len() as u64)))
76 .chain(self.tree_roots.iter().cloned())
77 .chain(std::iter::once(Scalar::from(self.inner.len() as u64)))
78 .chain(self.inner.roots().iter().cloned())
79 .chain(std::iter::once(Scalar::from(i as u64)))
80 .collect::<Vec<Scalar>>()
81 .as_slice(),
82 );
83 let index = utils::scalar_to_u256(hash) % n;
84 indices.push(index.as_u64() as usize);
85 }
86 indices
87 }
88}
89
90#[derive(Debug, Clone)]
99pub struct Committer<H: Hash<Scalar>> {
100 degree_bound: usize,
103 blowup_log2: usize,
105 polynomials: Vec<Polynomial>,
107 trees: Vec<Tree<H>>,
111}
112
113impl<H: Hash<Scalar>> Committer<H> {
114 pub fn new(degree_bound: usize, blowup_log2: usize, polynomials: Vec<Polynomial>) -> Self {
120 let mut committer = Self {
121 degree_bound,
122 blowup_log2,
123 polynomials: vec![],
124 trees: vec![],
125 };
126 committer.add_batch(polynomials);
127 committer
128 }
129
130 pub fn degree_bound(&self) -> usize {
132 self.degree_bound
133 }
134
135 pub fn extended_domain_size(&self) -> usize {
137 self.degree_bound << self.blowup_log2
138 }
139
140 pub fn num_trees(&self) -> usize {
143 self.trees.len()
144 }
145
146 pub fn tree(&self, index: usize) -> &Tree<H> {
148 &self.trees[index]
149 }
150
151 pub fn root_hash(&self, index: usize) -> Scalar {
155 self.trees[index].root_hash()
156 }
157
158 pub fn add_batch(&mut self, polynomials: Vec<Polynomial>) -> usize {
166 assert!(!polynomials.is_empty());
167 let k = polynomials.len();
168
169 let degree_bound = polynomials
170 .iter()
171 .map(|polynomial| polynomial.degree_bound())
172 .max()
173 .unwrap()
174 .next_power_of_two();
175 assert!(degree_bound <= self.degree_bound);
176 let n = self.degree_bound << self.blowup_log2;
177 assert!(n.trailing_zeros() <= Scalar::S);
178
179 let leaves = {
180 let evaluations = polynomials
181 .iter()
182 .map(|polynomial| polynomial.clone().shifted_lde2(n))
183 .collect::<Vec<Vec<Scalar>>>();
184 let mut leaves: Vec<Vec<Scalar>> = vec![vec![Scalar::ZERO; k]; n];
185 for i in 0..n {
186 for j in 0..k {
187 leaves[i][j] = evaluations[j][i];
188 }
189 }
190 leaves
191 };
192
193 let index = self.trees.len();
194
195 self.polynomials.extend(polynomials);
196 self.trees.push(Tree::<H>::from_leaves(leaves));
197
198 index
199 }
200
201 pub fn commit(self, points: BTreeSet<Scalar>) -> (Commitment, Prover<H>) {
208 {
209 let n = self.degree_bound << self.blowup_log2;
210 let g = Scalar::MULTIPLICATIVE_GENERATOR.pow_vartime([n as u64, 0, 0, 0]);
211 for &z in &points {
212 assert_ne!(z.pow_vartime([n as u64, 0, 0, 0]), g);
214 }
215 }
216
217 let alpha = H::hash_many(
218 std::iter::once(*RLC_DST)
219 .chain(std::iter::once(Scalar::from(self.trees.len() as u64)))
220 .chain(self.trees.iter().map(|tree| tree.root_hash()))
221 .chain(std::iter::once(Scalar::from(points.len() as u64)))
222 .chain(points.iter().cloned())
223 .collect::<Vec<Scalar>>()
224 .as_slice(),
225 );
226
227 let points: BTreeMap<Scalar, Vec<Scalar>> = points
228 .iter()
229 .map(|&z| {
230 (
231 z,
232 self.polynomials
233 .iter()
234 .map(|polynomial| polynomial.evaluate(z))
235 .collect(),
236 )
237 })
238 .collect();
239
240 let combined = {
241 let mut combined = Polynomial::default();
242 let mut pow = Scalar::ONE;
243 for polynomial in &self.polynomials {
244 combined += polynomial.clone() * pow;
245 pow *= alpha;
246 }
247 combined
248 };
249
250 let quotients = points
251 .iter()
252 .map(|(&z, values)| {
253 let value = rlc(values.as_slice(), alpha);
254 let (quotient, remainder) = (combined.clone() - value).horner(z);
255 assert_eq!(remainder, Scalar::ZERO);
256 quotient
257 })
258 .collect();
259
260 let inner_prover = fri::Prover::<H>::new(quotients, self.degree_bound, self.blowup_log2);
261
262 let commitment = Commitment {
263 tree_roots: self.trees.iter().map(|tree| tree.root_hash()).collect(),
264 inner: inner_prover.commit(),
265 };
266 let prover = Prover {
267 degree_bound: self.degree_bound,
268 blowup_log2: self.blowup_log2,
269 trees: self.trees,
270 points,
271 inner_prover,
272 };
273 (commitment, prover)
274 }
275}
276
277#[derive(Debug, Clone)]
279pub struct Proof<H: Hash<Scalar>> {
280 degree_bound: usize,
283 blowup_log2: usize,
285 num_polys: usize,
287 points: BTreeMap<Scalar, Vec<Scalar>>,
290 openings: Vec<Vec<LeafProof<H>>>,
294 queries: Vec<fri::Query<H>>,
297}
298
299impl<H: Hash<Scalar>> Proof<H> {
300 pub fn degree_bound(&self) -> usize {
302 self.degree_bound
303 }
304
305 pub fn blowup_log2(&self) -> usize {
307 self.blowup_log2
308 }
309
310 pub fn extended_domain_size(&self) -> usize {
312 self.degree_bound << self.blowup_log2
313 }
314
315 pub fn num_polys(&self) -> usize {
317 self.num_polys
318 }
319
320 pub fn points(&self) -> &BTreeMap<Scalar, Vec<Scalar>> {
323 &self.points
324 }
325
326 pub fn verify(&self, commitment: &Commitment) -> Result<()> {
328 let indices = commitment.get_query_indices::<H>(
329 self.degree_bound,
330 self.blowup_log2,
331 self.points.len(),
332 );
333 if self.openings.len() != indices.len() {
334 return Err(anyhow!(
335 "incorrect number of openings (got {}, want {})",
336 self.openings.len(),
337 indices.len()
338 ));
339 }
340 if self.queries.len() != indices.len() {
341 return Err(anyhow!(
342 "incorrect number of queries (got {}, want {})",
343 self.queries.len(),
344 indices.len()
345 ));
346 }
347
348 let alpha = H::hash_many(
349 std::iter::once(*RLC_DST)
350 .chain(std::iter::once(Scalar::from(
351 commitment.tree_roots().len() as u64
352 )))
353 .chain(commitment.tree_roots().iter().cloned())
354 .chain(std::iter::once(Scalar::from(self.points.len() as u64)))
355 .chain(self.points.keys().cloned())
356 .collect::<Vec<Scalar>>()
357 .as_slice(),
358 );
359
360 for ((query, openings), &expected_index) in
361 (self.queries.iter().zip(self.openings.iter())).zip(indices.iter())
362 {
363 let (index, _) = query.indices();
364 if index != expected_index {
365 return Err(anyhow!(
366 "wrong query index (got {index}, want {expected_index})",
367 ));
368 }
369
370 if openings.len() != commitment.tree_roots().len() {
371 return Err(anyhow!(
372 "incorrect number of openings for index {index} (got {}, want {})",
373 openings.len(),
374 commitment.tree_roots().len()
375 ));
376 }
377 for (&root_hash, opening) in commitment.tree_roots().iter().zip(openings.iter()) {
378 if 1usize << opening.len() != self.extended_domain_size() {
379 return Err(anyhow!("invalid opening for index {index}"));
380 }
381 opening.verify(index, root_hash)?;
382 }
383
384 if 1usize << (query.len() - 1) != self.degree_bound {
385 return Err(anyhow!("invalid low-degree proof for index {index}"));
386 }
387 query.verify(&commitment.inner)?;
388
389 let combined = rlc(
390 openings
391 .iter()
392 .map(|proof| proof.leaf().iter().cloned())
393 .flatten()
394 .collect::<Vec<Scalar>>()
395 .as_slice(),
396 alpha,
397 );
398
399 let (quotients, _) = query.values();
400 if quotients.len() != self.points.len() {
401 return Err(anyhow!(
402 "the number of evaluation claims doesn't match the number of FRI quotients (got {}, want {})",
403 self.points.len(),
404 quotients.len()
405 ));
406 }
407
408 let x = query.x();
409 for ((&z, values), "ient) in self.points.iter().zip(quotients.iter()) {
410 let v = rlc(values.as_slice(), alpha);
411 let numerator = combined - v;
412 let denominator = x - z;
413 if quotient * denominator != numerator {
414 return Err(anyhow!("algebraic check failed at query index {index}"));
415 }
416 }
417 }
418
419 Ok(())
420 }
421}
422
423#[derive(Debug, Clone)]
427pub struct Prover<H: Hash<Scalar>> {
428 degree_bound: usize,
430 blowup_log2: usize,
432 trees: Vec<Tree<H>>,
434 points: BTreeMap<Scalar, Vec<Scalar>>,
439 inner_prover: fri::Prover<H>,
442}
443
444impl<H: Hash<Scalar>> Prover<H> {
445 pub fn degree_bound(&self) -> usize {
447 self.degree_bound
448 }
449
450 pub fn extended_domain_size(&self) -> usize {
452 self.degree_bound << self.blowup_log2
453 }
454
455 pub fn num_polys(&self) -> usize {
457 self.trees.iter().map(|tree| tree.num_polys()).sum()
458 }
459
460 pub fn num_trees(&self) -> usize {
462 self.trees.len()
463 }
464
465 pub fn tree(&self, index: usize) -> &Tree<H> {
467 &self.trees[index]
468 }
469
470 pub fn root_hash(&self, index: usize) -> Scalar {
474 self.trees[index].root_hash()
475 }
476
477 pub fn points(&self) -> &BTreeMap<Scalar, Vec<Scalar>> {
480 &self.points
481 }
482
483 pub fn prove(&self, commitment: &Commitment) -> Proof<H> {
486 let indices = commitment.get_query_indices::<H>(
487 self.degree_bound,
488 self.blowup_log2,
489 self.points.len(),
490 );
491 let openings = indices
492 .iter()
493 .map(|&index| self.trees.iter().map(|tree| tree.query(index)).collect())
494 .collect();
495 let queries = indices
496 .iter()
497 .map(|&index| self.inner_prover.query(index))
498 .collect();
499 Proof {
500 degree_bound: self.degree_bound,
501 blowup_log2: self.blowup_log2,
502 num_polys: self.num_polys(),
503 points: self.points.clone(),
504 openings,
505 queries,
506 }
507 }
508}
509
510#[cfg(test)]
513mod tests {
514 use super::*;
515 use crate::hash;
516
517 type Sha2Hash = hash::Sha2Hash<Scalar>;
518 type Poseidon2Hash = hash::Poseidon2Hash<Scalar>;
519
520 fn test_prover_impl<H: Hash<Scalar>>(
521 polynomials: Vec<Polynomial>,
522 points: &[u64],
523 degree_bound: usize,
524 blowup_log2: usize,
525 ) {
526 let num_polys = polynomials.len();
527 let points = BTreeMap::from_iter(points.iter().cloned().map(|z| {
528 (
529 Scalar::from(z),
530 polynomials
531 .iter()
532 .map(|polynomial| polynomial.evaluate(z.into()))
533 .collect::<Vec<Scalar>>(),
534 )
535 }));
536 let committer = Committer::<H>::new(degree_bound, blowup_log2, polynomials);
537 let (commitment, prover) = committer.commit(points.iter().map(|(&z, _)| z).collect());
538 assert_eq!(prover.degree_bound(), degree_bound);
539 assert_eq!(prover.extended_domain_size(), degree_bound << blowup_log2);
540 assert_eq!(prover.num_polys(), num_polys);
541 assert_eq!(prover.num_trees(), 1);
542 assert_eq!(*prover.points(), points);
543 let proof = prover.prove(&commitment);
544 assert_eq!(proof.degree_bound(), degree_bound);
545 assert_eq!(proof.blowup_log2(), blowup_log2);
546 assert_eq!(proof.extended_domain_size(), degree_bound << blowup_log2);
547 assert_eq!(proof.num_polys(), num_polys);
548 assert!(proof.verify(&commitment).is_ok());
549 assert_eq!(*proof.points(), points);
550 }
551
552 fn test_prover(polynomials: Vec<Polynomial>, points: &[u64], degree_bound: usize) {
553 test_prover_impl::<Sha2Hash>(polynomials.clone(), points, degree_bound, 1);
554 test_prover_impl::<Poseidon2Hash>(polynomials.clone(), points, degree_bound, 1);
555 test_prover_impl::<Sha2Hash>(polynomials.clone(), points, degree_bound, 2);
556 test_prover_impl::<Poseidon2Hash>(polynomials.clone(), points, degree_bound, 2);
557 test_prover_impl::<Sha2Hash>(polynomials.clone(), points, degree_bound, 3);
558 test_prover_impl::<Poseidon2Hash>(polynomials, points, degree_bound, 3);
559 }
560
561 #[test]
562 fn test_one_constant_polynomial_one_point_1() {
563 test_prover(
564 vec![Polynomial::with_coefficients(vec![12.into()])],
565 &[123],
566 1,
567 );
568 }
569
570 #[test]
571 fn test_one_constant_polynomial_one_point_2() {
572 test_prover(
573 vec![Polynomial::with_coefficients(vec![12.into()])],
574 &[321],
575 1,
576 );
577 }
578
579 #[test]
580 fn test_one_constant_polynomial_one_point_3() {
581 test_prover(
582 vec![Polynomial::with_coefficients(vec![34.into()])],
583 &[123],
584 1,
585 );
586 }
587
588 #[test]
589 fn test_one_constant_polynomial_two_points() {
590 test_prover(
591 vec![Polynomial::with_coefficients(vec![12.into()])],
592 &[123, 456],
593 1,
594 );
595 }
596
597 #[test]
598 fn test_one_constant_polynomial_three_points() {
599 test_prover(
600 vec![Polynomial::with_coefficients(vec![12.into()])],
601 &[789, 456, 123],
602 1,
603 );
604 }
605
606 #[test]
607 fn test_one_polynomial_degree_one_one_point_1() {
608 test_prover(
609 vec![Polynomial::with_coefficients(vec![12.into(), 34.into()])],
610 &[123],
611 2,
612 );
613 }
614
615 #[test]
616 fn test_one_polynomial_degree_one_one_point_2() {
617 test_prover(
618 vec![Polynomial::with_coefficients(vec![12.into(), 34.into()])],
619 &[321],
620 2,
621 );
622 }
623
624 #[test]
625 fn test_one_polynomial_degree_one_one_point_3() {
626 test_prover(
627 vec![Polynomial::with_coefficients(vec![34.into(), 56.into()])],
628 &[123],
629 2,
630 );
631 }
632
633 #[test]
634 fn test_one_polynomial_degree_one_two_points() {
635 test_prover(
636 vec![Polynomial::with_coefficients(vec![12.into(), 34.into()])],
637 &[123, 456],
638 2,
639 );
640 }
641
642 #[test]
643 fn test_one_polynomial_degree_one_three_points() {
644 test_prover(
645 vec![Polynomial::with_coefficients(vec![12.into(), 34.into()])],
646 &[789, 456, 123],
647 2,
648 );
649 }
650
651 #[test]
652 fn test_two_polynomials_degree_three_one_point_1() {
653 test_prover(
654 vec![
655 Polynomial::with_coefficients(vec![12.into(), 34.into(), 56.into(), 78.into()]),
656 Polynomial::with_coefficients(vec![42.into(), 43.into(), 44.into(), 45.into()]),
657 ],
658 &[123],
659 4,
660 );
661 }
662
663 #[test]
664 fn test_two_polynomials_degree_three_one_point_2() {
665 test_prover(
666 vec![
667 Polynomial::with_coefficients(vec![12.into(), 34.into(), 56.into(), 78.into()]),
668 Polynomial::with_coefficients(vec![42.into(), 43.into(), 44.into(), 45.into()]),
669 ],
670 &[321],
671 4,
672 );
673 }
674
675 #[test]
676 fn test_two_polynomials_degree_three_one_point_3() {
677 test_prover(
678 vec![
679 Polynomial::with_coefficients(vec![45.into(), 44.into(), 43.into(), 42.into()]),
680 Polynomial::with_coefficients(vec![78.into(), 56.into(), 34.into(), 12.into()]),
681 ],
682 &[123],
683 4,
684 );
685 }
686
687 #[test]
688 fn test_two_polynomials_degree_three_two_points() {
689 test_prover(
690 vec![
691 Polynomial::with_coefficients(vec![12.into(), 34.into(), 56.into(), 78.into()]),
692 Polynomial::with_coefficients(vec![42.into(), 43.into(), 44.into(), 45.into()]),
693 ],
694 &[123, 456],
695 4,
696 );
697 }
698
699 #[test]
700 fn test_two_polynomials_degree_three_three_points() {
701 test_prover(
702 vec![
703 Polynomial::with_coefficients(vec![12.into(), 34.into(), 56.into(), 78.into()]),
704 Polynomial::with_coefficients(vec![42.into(), 43.into(), 44.into(), 45.into()]),
705 ],
706 &[789, 456, 123],
707 4,
708 );
709 }
710}