Skip to main content

starkom_pcs/
deep.rs

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
12/// Re-exported type alias for polynomials over BlueSky. The current implementation only works on
13/// that field.
14pub type Polynomial = starkom_poly::Polynomial<Scalar>;
15
16/// Target security level in bits.
17pub const LAMBDA: usize = 128;
18
19/// Domain separator tag for the Fiat-Shamir challenge used to derive query indices.
20static QUERY_DST: LazyLock<Scalar> = LazyLock::new(|| utils::hash_to_scalar(b"starkom/pcs/query"));
21
22/// Domain separator tag for the Fiat-Shamir challenge used to build the random linear combination.
23static RLC_DST: LazyLock<Scalar> = LazyLock::new(|| utils::hash_to_scalar(b"starkom/pcs/rlc"));
24
25/// Returns the number of FRI queries required to achieve 128-bit security using a blowup factor of
26/// `2^blowup_log2` when opening `num_points` evaluation points.
27fn 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
32/// Computes a random linear combination of a list of values.
33///
34/// `alpha` is a Fiat-Shamir challenge of some sort.
35fn 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/// A batched DEEP-FRI polynomial commitment (see `Committer` for details).
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct Commitment {
48    /// The root hashes of the Merkle trees where the evaluations of all batched polynomials are
49    /// stored. There is one root hash per polynomial batch.
50    tree_roots: Vec<Scalar>,
51    /// The underlying FRI commitment.
52    inner: fri::Commitment,
53}
54
55impl Commitment {
56    /// Returns the root hashes of the Merkle trees where all batched polynomials are stored.
57    pub fn tree_roots(&self) -> &[Scalar] {
58        self.tree_roots.as_slice()
59    }
60
61    /// Returns the FRI query indices derived via Fiat-Shamir from the full commitment transcript
62    /// (all polynomial and FRI Merkle root hashes).
63    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/// Collects batches of polynomials and allows building a DEEP-FRI prover for them.
91///
92/// This works by building Merkle trees on the batched polynomials, one tree per batch, and
93/// eventually handing everything over to a newly constructed `Prover` (see the `commit` method).
94///
95/// This two-stage Committer-Prover architecture allows getting Merkle roots for the proven
96/// polynomials before running the FRI folding argument and even before batching all polynomials, so
97/// that Fiat-Shamir challenges can be derived before any quotients are built.
98#[derive(Debug, Clone)]
99pub struct Committer<H: Hash<Scalar>> {
100    /// The proven degree bound. The degree of all batched polynomials must be strictly less than
101    /// this value.
102    degree_bound: usize,
103    /// The base-2 logarithm of the blowup factor.
104    blowup_log2: usize,
105    /// All polynomials batched so far.
106    polynomials: Vec<Polynomial>,
107    /// The Merkle trees built so far.
108    ///
109    /// The sum of all `num_polys` of all trees must match the number of `polynomials`.
110    trees: Vec<Tree<H>>,
111}
112
113impl<H: Hash<Scalar>> Committer<H> {
114    /// Constructs a `Committer` with the given degree bound, blowup factor, and first batch of
115    /// polynomials.
116    ///
117    /// We require specifying the first batch because our DEEP-FRI protocol requires at least one
118    /// committed polynomial to work.
119    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    /// Returns the proven degree bound.
131    pub fn degree_bound(&self) -> usize {
132        self.degree_bound
133    }
134
135    /// Returns the size of the extended evaluation domain.
136    pub fn extended_domain_size(&self) -> usize {
137        self.degree_bound << self.blowup_log2
138    }
139
140    /// Returns the number of Merkle trees constructed so far, corresponding to the number of
141    /// polynomial batches.
142    pub fn num_trees(&self) -> usize {
143        self.trees.len()
144    }
145
146    /// Returns the i-th Merkle tree. `index` must be less than `num_trees()`.
147    pub fn tree(&self, index: usize) -> &Tree<H> {
148        &self.trees[index]
149    }
150
151    /// Returns the root hash of the i-th Merkle tree. `index` must be less than `num_trees()`.
152    ///
153    /// This value can be used to derive Fiat-Shamir challenges.
154    pub fn root_hash(&self, index: usize) -> Scalar {
155        self.trees[index].root_hash()
156    }
157
158    /// Adds a batch of polynomials, returnin the index of the newly created batch.
159    ///
160    /// The returned index can be used with the `tree` and `root_hash` methods to get the Merkle
161    /// tree and root hash for the batch, respectively.
162    ///
163    /// REQUIRES: the degree of all specified polynomials must be strictly less than
164    /// `degree_bound()`.
165    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    /// Consumes the `Committer`, calculates all DEEP quotients, and returns a polynomial
202    /// `Commitment` and a DEEP-FRI `Prover`.
203    ///
204    /// `points` is the set of points to open in the `Prover`. The contained scalars are
205    /// (off-domain) X-coordinates; the corresponding Y-coordinates will be computed automatically
206    /// for every batched polynomial.
207    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                // All opened points must lie outside the evaluation domain.
213                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/// A DEEP-FRI proof.
278#[derive(Debug, Clone)]
279pub struct Proof<H: Hash<Scalar>> {
280    /// The proven degree bound. If the proof is valid the degree of all batched polynomials is
281    /// guaranteed to be strictly less than this value.
282    degree_bound: usize,
283    /// The base-2 logarithm of the blowup factor.
284    blowup_log2: usize,
285    /// Number of committed polynomials.
286    num_polys: usize,
287    /// The opened points. Keys are (off-domain) X-coordinates, values are the corresponding
288    /// evaluations (one for every committed polynomial).
289    points: BTreeMap<Scalar, Vec<Scalar>>,
290    /// Merkle proofs of the opened points, relative to the raw Merkle trees (not the FRI folds).
291    /// The outer array has one entry for every FRI query (`openings.len() == queries.len()`), and
292    /// the inner arrays contain one proof for every Merkle tree.
293    openings: Vec<Vec<LeafProof<H>>>,
294    /// FRI queries on the DEEP quotients. The number of queries is calculated by `num_queries`
295    /// above and is tuned so as to achieve 128-bit security.
296    queries: Vec<fri::Query<H>>,
297}
298
299impl<H: Hash<Scalar>> Proof<H> {
300    /// Returns the proven degree bound.
301    pub fn degree_bound(&self) -> usize {
302        self.degree_bound
303    }
304
305    /// Returns the base-2 logarithm of the blowup factor used in the proof.
306    pub fn blowup_log2(&self) -> usize {
307        self.blowup_log2
308    }
309
310    /// Returns the size of the extended evaluation domain.
311    pub fn extended_domain_size(&self) -> usize {
312        self.degree_bound << self.blowup_log2
313    }
314
315    /// Returns the number of committed polynomials.
316    pub fn num_polys(&self) -> usize {
317        self.num_polys
318    }
319
320    /// Returns a reference to the opened points. Keys are (off-domain) X-coordinates, values are
321    /// the corresponding evaluations (one for every committed polynomial).
322    pub fn points(&self) -> &BTreeMap<Scalar, Vec<Scalar>> {
323        &self.points
324    }
325
326    /// Verifies this proof against the given commitment.
327    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), &quotient) 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/// A DEEP-FRI prover.
424///
425/// `Prover`s are constructed by `Committer::commit()`; see that method for details.
426#[derive(Debug, Clone)]
427pub struct Prover<H: Hash<Scalar>> {
428    /// The degree bound to prove.
429    degree_bound: usize,
430    /// The base-2 logarithm of the blowup factor.
431    blowup_log2: usize,
432    /// Raw Merkle trees for the committed polynomials, one for each batch.
433    trees: Vec<Tree<H>>,
434    /// The opened points.
435    ///
436    /// The keys of the map are the (off-domain) X-coordinates of the points, while values are lists
437    /// of polynomial evaluations at that point (one for every committed polynomial).
438    points: BTreeMap<Scalar, Vec<Scalar>>,
439    /// The underlying FRI prover for the DEEP quotients. There's one quotient for every opened
440    /// point, and all quotients are batched into the same FRI folding argument.
441    inner_prover: fri::Prover<H>,
442}
443
444impl<H: Hash<Scalar>> Prover<H> {
445    /// Returns the proven degree bound.
446    pub fn degree_bound(&self) -> usize {
447        self.degree_bound
448    }
449
450    /// Returns the size of the extended evaluation domain.
451    pub fn extended_domain_size(&self) -> usize {
452        self.degree_bound << self.blowup_log2
453    }
454
455    /// Returns the number of committed polynomials.
456    pub fn num_polys(&self) -> usize {
457        self.trees.iter().map(|tree| tree.num_polys()).sum()
458    }
459
460    /// Returns the number of Merkle trees, corresponding to the number of polynomial batches.
461    pub fn num_trees(&self) -> usize {
462        self.trees.len()
463    }
464
465    /// Returns the i-th Merkle tree. `index` must be less than `num_trees()`.
466    pub fn tree(&self, index: usize) -> &Tree<H> {
467        &self.trees[index]
468    }
469
470    /// Returns the root hash of the i-th Merkle tree. `index` must be less than `num_trees()`.
471    ///
472    /// This value can be used to derive Fiat-Shamir challenges.
473    pub fn root_hash(&self, index: usize) -> Scalar {
474        self.trees[index].root_hash()
475    }
476
477    /// Returns a reference to the opened points. Keys are (off-domain) X-coordinates, values are
478    /// the corresponding evaluations (one for every committed polynomial).
479    pub fn points(&self) -> &BTreeMap<Scalar, Vec<Scalar>> {
480        &self.points
481    }
482
483    /// Makes a DEEP-FRI proof opening the committed polynomials at the points specified at
484    /// commitment time (see `Committer::commit()`).
485    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// TODO
511
512#[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}