ark_mpc/algebra/poly/
poly.rs

1//! Defines the base polynomial representation, modeled after the
2//! `ark_poly::DensePolynomial` type
3
4use std::{
5    cmp, iter,
6    ops::{Add, Div, Mul, Neg, Sub},
7    pin::Pin,
8    task::{Context, Poll},
9};
10
11use ark_ec::CurveGroup;
12use ark_ff::{Field, One, Zero};
13use ark_poly::{
14    univariate::{DenseOrSparsePolynomial, DensePolynomial},
15    DenseUVPolynomial,
16};
17use futures::FutureExt;
18use futures::{ready, Future};
19use itertools::Itertools;
20
21use crate::{
22    algebra::{
23        macros::{impl_borrow_variants, impl_commutative},
24        AuthenticatedScalarResult, Scalar, ScalarResult,
25    },
26    MpcFabric, ResultValue,
27};
28
29use super::AuthenticatedDensePoly;
30
31// -----------
32// | Helpers |
33// -----------
34
35/// Return a representation of x^t as a `DensePolynomial`
36fn x_to_t<C: CurveGroup>(t: usize) -> DensePolynomial<C::ScalarField> {
37    let mut coeffs = vec![C::ScalarField::zero(); t];
38    coeffs.push(C::ScalarField::one());
39    DensePolynomial::from_coefficients_vec(coeffs)
40}
41
42// ------------------
43// | Implementation |
44// ------------------
45
46/// A dense polynomial representation allocated in an MPC circuit
47#[derive(Clone, Debug, Default)]
48pub struct DensePolynomialResult<C: CurveGroup> {
49    /// The coefficients of the polynomial, the `i`th coefficient is the
50    /// coefficient of `x^i`
51    pub coeffs: Vec<ScalarResult<C>>,
52}
53
54impl<C: CurveGroup> DensePolynomialResult<C> {
55    /// Constructor
56    pub fn from_coeffs(coeffs: Vec<ScalarResult<C>>) -> Self {
57        assert!(!coeffs.is_empty(), "cannot construct an empty polynomial");
58        Self { coeffs }
59    }
60
61    /// Construct the zero polynomial (additive identity)
62    pub fn zero(fabric: &MpcFabric<C>) -> Self {
63        Self::from_coeffs(vec![fabric.zero()])
64    }
65
66    /// Construct the one polynomial (multiplicative identity)
67    pub fn one(fabric: &MpcFabric<C>) -> Self {
68        Self::from_coeffs(vec![fabric.one()])
69    }
70
71    /// Returns the degree of the polynomial
72    pub fn degree(&self) -> usize {
73        self.coeffs.len() - 1
74    }
75
76    /// Get a reference to the fabric that the polynomial is allocated within
77    pub(crate) fn fabric(&self) -> &MpcFabric<C> {
78        self.coeffs[0].fabric()
79    }
80
81    /// Evaluate the polynomial at a given point
82    pub fn eval(&self, point: ScalarResult<C>) -> ScalarResult<C> {
83        let fabric = self.fabric();
84        let mut deps = self.coeffs.iter().map(|coeff| coeff.id()).collect_vec();
85        deps.push(point.id());
86
87        let n_coeffs = self.coeffs.len();
88        fabric.new_gate_op(deps, move |mut args| {
89            let coeffs: Vec<Scalar<C>> = args.drain(..n_coeffs).map(|res| res.into()).collect_vec();
90            let point: Scalar<C> = args.pop().unwrap().into();
91
92            let mut res = Scalar::zero();
93            for coeff in coeffs.iter().rev() {
94                res = res * point + coeff;
95            }
96
97            ResultValue::Scalar(res)
98        })
99    }
100}
101
102/// Modular inversion implementation
103impl<C: CurveGroup> DensePolynomialResult<C> {
104    /// Compute the multiplicative inverse of the polynomial mod x^t
105    ///
106    /// Done using the extended Euclidean algorithm
107    pub fn mul_inverse_mod_t(&self, t: usize) -> Self {
108        let ids = self.coeffs.iter().map(|c| c.id()).collect_vec();
109        let n_result_coeffs = t;
110
111        let res_coeffs = self.fabric().new_batch_gate_op(
112            ids,
113            n_result_coeffs, // output_arity
114            move |args| {
115                let x_to_t = x_to_t::<C>(t);
116
117                let self_coeffs = args
118                    .into_iter()
119                    .map(|res| Scalar::<C>::from(res).inner())
120                    .collect_vec();
121                let self_poly = DensePolynomial::from_coefficients_vec(self_coeffs);
122
123                // Compute the bezout coefficients of the two polynomials
124                let (inverse_poly, _) = Self::compute_bezout_polynomials(&self_poly, &x_to_t);
125
126                // In a polynomial ring, gcd is defined only up to scalar multiplication, so we
127                // multiply the result by the inverse of the resultant first
128                // coefficient to uniquely define the inverse as f^{-1}(x) such that
129                // f * f^{-1}(x) = 1 \in F[x] / (x^t)
130                let self_constant_coeff = self_poly.coeffs[0];
131                let inverse_constant_coeff = inverse_poly.coeffs[0];
132                let constant_coeff_inv = (self_constant_coeff * inverse_constant_coeff)
133                    .inverse()
134                    .unwrap();
135
136                inverse_poly
137                    .coeffs
138                    .into_iter()
139                    .take(n_result_coeffs)
140                    .map(|c| c * constant_coeff_inv)
141                    .map(Scalar::new)
142                    .map(ResultValue::Scalar)
143                    .collect_vec()
144            },
145        );
146
147        Self::from_coeffs(res_coeffs)
148    }
149
150    /// A helper to compute the Bezout coefficients of the two given polynomials
151    ///
152    /// I.e. for a(x), b(x) as input, we compute f(x), g(x) such that:
153    ///     f(x) * a(x) + g(x) * b(x) = gcd(a, b)
154    /// This is done using the extended Euclidean method
155    fn compute_bezout_polynomials(
156        a: &DensePolynomial<C::ScalarField>,
157        b: &DensePolynomial<C::ScalarField>,
158    ) -> (
159        DensePolynomial<C::ScalarField>,
160        DensePolynomial<C::ScalarField>,
161    ) {
162        if b.is_zero() {
163            return (
164                DensePolynomial::from_coefficients_vec(vec![C::ScalarField::one()]), // f(x) = 1
165                DensePolynomial::zero(),                                             // f(x) = 0
166            );
167        }
168
169        let a_transformed = DenseOrSparsePolynomial::from(a);
170        let b_transformed = DenseOrSparsePolynomial::from(b);
171        let (quotient, remainder) = a_transformed.divide_with_q_and_r(&b_transformed).unwrap();
172
173        let (f, g) = Self::compute_bezout_polynomials(b, &remainder);
174        let next_g = &f - &(&quotient * &g);
175
176        (g, next_g)
177    }
178}
179
180impl<C: CurveGroup> Future for DensePolynomialResult<C>
181where
182    C::ScalarField: Unpin,
183{
184    type Output = DensePolynomial<C::ScalarField>;
185    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
186        let mut coeffs = Vec::with_capacity(self.coeffs.len());
187        for coeff in self.coeffs.iter_mut() {
188            let ready_coeff = ready!(coeff.poll_unpin(cx));
189            coeffs.push(ready_coeff.inner());
190        }
191
192        Poll::Ready(DensePolynomial::from_coefficients_vec(coeffs))
193    }
194}
195
196// --------------
197// | Arithmetic |
198// --------------
199
200// --- Addition --- //
201
202impl<C: CurveGroup> Add<&DensePolynomial<C::ScalarField>> for &DensePolynomialResult<C> {
203    type Output = DensePolynomialResult<C>;
204    fn add(self, rhs: &DensePolynomial<C::ScalarField>) -> Self::Output {
205        assert!(!self.coeffs.is_empty(), "cannot add an empty polynomial");
206        let fabric = self.coeffs[0].fabric();
207
208        let mut coeffs = Vec::new();
209        let max_degree = cmp::max(self.coeffs.len(), rhs.coeffs.len());
210
211        // Pad the coefficients to be of the same length
212        let padded_coeffs0 = self
213            .coeffs
214            .iter()
215            .cloned()
216            .chain(iter::repeat(fabric.zero()));
217        let padded_coeffs1 = rhs
218            .coeffs
219            .iter()
220            .copied()
221            .map(Scalar::<C>::new)
222            .chain(iter::repeat(Scalar::zero()));
223
224        // Add component-wise
225        for (lhs_coeff, rhs_coeff) in padded_coeffs0.zip(padded_coeffs1).take(max_degree) {
226            coeffs.push(lhs_coeff + rhs_coeff);
227        }
228
229        DensePolynomialResult::from_coeffs(coeffs)
230    }
231}
232impl_borrow_variants!(DensePolynomialResult<C>, Add, add, +, DensePolynomial<C::ScalarField>, C: CurveGroup);
233impl_commutative!(DensePolynomialResult<C>, Add, add, +, DensePolynomial<C::ScalarField>, C: CurveGroup);
234
235impl<C: CurveGroup> Add<&DensePolynomialResult<C>> for &DensePolynomialResult<C> {
236    type Output = DensePolynomialResult<C>;
237    fn add(self, rhs: &DensePolynomialResult<C>) -> Self::Output {
238        // We do not pad the coefficients here, it requires fewer gates if we avoid
239        // padding
240        let mut coeffs = Vec::new();
241        let (shorter, longer) = if self.coeffs.len() < rhs.coeffs.len() {
242            (&self.coeffs, &rhs.coeffs)
243        } else {
244            (&rhs.coeffs, &self.coeffs)
245        };
246
247        for (i, longer_coeff) in longer.iter().enumerate() {
248            let new_coeff = if i < shorter.len() {
249                &shorter[i] + longer_coeff
250            } else {
251                longer_coeff.clone()
252            };
253
254            coeffs.push(new_coeff);
255        }
256
257        DensePolynomialResult::from_coeffs(coeffs)
258    }
259}
260impl_borrow_variants!(DensePolynomialResult<C>, Add, add, +, DensePolynomialResult<C>, C: CurveGroup);
261
262// --- Negation --- //
263impl<C: CurveGroup> Neg for &DensePolynomialResult<C> {
264    type Output = DensePolynomialResult<C>;
265    fn neg(self) -> Self::Output {
266        let mut coeffs = Vec::with_capacity(self.coeffs.len());
267        for coeff in self.coeffs.iter() {
268            coeffs.push(-coeff);
269        }
270
271        DensePolynomialResult::from_coeffs(coeffs)
272    }
273}
274impl_borrow_variants!(DensePolynomialResult<C>, Neg, neg, -, C: CurveGroup);
275
276// --- Subtraction --- //
277#[allow(clippy::suspicious_arithmetic_impl)]
278impl<C: CurveGroup> Sub<&DensePolynomial<C::ScalarField>> for &DensePolynomialResult<C> {
279    type Output = DensePolynomialResult<C>;
280    fn sub(self, rhs: &DensePolynomial<C::ScalarField>) -> Self::Output {
281        let negated_rhs_coeffs = rhs.coeffs.iter().map(|coeff| -(*coeff)).collect();
282        let negated_rhs = DensePolynomial::from_coefficients_vec(negated_rhs_coeffs);
283
284        self + negated_rhs
285    }
286}
287impl_borrow_variants!(DensePolynomialResult<C>, Sub, sub, -, DensePolynomial<C::ScalarField>, C: CurveGroup);
288
289#[allow(clippy::suspicious_arithmetic_impl)]
290impl<C: CurveGroup> Sub<&DensePolynomialResult<C>> for &DensePolynomialResult<C> {
291    type Output = DensePolynomialResult<C>;
292    fn sub(self, rhs: &DensePolynomialResult<C>) -> Self::Output {
293        // Negate the rhs then use the `Add` impl
294        self + (-rhs)
295    }
296}
297
298// --- Multiplication --- //
299
300// TODO: For each of the following implementations, we can await all
301// coefficients to be available and then perform an FFT-based polynomial
302// multiplication. This is left as an optimization
303impl<C: CurveGroup> Mul<&DensePolynomial<C::ScalarField>> for &DensePolynomialResult<C> {
304    type Output = DensePolynomialResult<C>;
305
306    fn mul(self, rhs: &DensePolynomial<C::ScalarField>) -> Self::Output {
307        let fabric = self.coeffs[0].fabric();
308
309        let mut coeffs = Vec::with_capacity(self.coeffs.len() + rhs.coeffs.len() - 1);
310        for _ in 0..self.coeffs.len() + rhs.coeffs.len() - 1 {
311            coeffs.push(fabric.zero());
312        }
313
314        for (i, lhs_coeff) in self.coeffs.iter().enumerate() {
315            for (j, rhs_coeff) in rhs.coeffs.iter().copied().map(Scalar::new).enumerate() {
316                coeffs[i + j] = &coeffs[i + j] + lhs_coeff * rhs_coeff;
317            }
318        }
319
320        DensePolynomialResult::from_coeffs(coeffs)
321    }
322}
323impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, DensePolynomial<C::ScalarField>, C: CurveGroup);
324impl_commutative!(DensePolynomialResult<C>, Mul, mul, *, DensePolynomial<C::ScalarField>, C: CurveGroup);
325
326impl<C: CurveGroup> Mul<&DensePolynomialResult<C>> for &DensePolynomialResult<C> {
327    type Output = DensePolynomialResult<C>;
328
329    fn mul(self, rhs: &DensePolynomialResult<C>) -> Self::Output {
330        let fabric = self.coeffs[0].fabric();
331
332        let mut coeffs = Vec::with_capacity(self.coeffs.len() + rhs.coeffs.len() - 1);
333        for _ in 0..self.coeffs.len() + rhs.coeffs.len() - 1 {
334            coeffs.push(fabric.zero());
335        }
336
337        for (i, lhs_coeff) in self.coeffs.iter().enumerate() {
338            for (j, rhs_coeff) in rhs.coeffs.iter().enumerate() {
339                coeffs[i + j] = &coeffs[i + j] + lhs_coeff * rhs_coeff;
340            }
341        }
342
343        DensePolynomialResult::from_coeffs(coeffs)
344    }
345}
346impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, DensePolynomialResult<C>, C: CurveGroup);
347
348// --- Scalar Multiplication --- //
349
350impl<C: CurveGroup> Mul<&Scalar<C>> for &DensePolynomialResult<C> {
351    type Output = DensePolynomialResult<C>;
352
353    fn mul(self, rhs: &Scalar<C>) -> Self::Output {
354        let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec();
355        DensePolynomialResult::from_coeffs(new_coeffs)
356    }
357}
358impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, Scalar<C>, C: CurveGroup);
359impl_commutative!(DensePolynomialResult<C>, Mul, mul, *, Scalar<C>, C: CurveGroup);
360
361impl<C: CurveGroup> Mul<&ScalarResult<C>> for &DensePolynomialResult<C> {
362    type Output = DensePolynomialResult<C>;
363
364    fn mul(self, rhs: &ScalarResult<C>) -> Self::Output {
365        let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec();
366        DensePolynomialResult::from_coeffs(new_coeffs)
367    }
368}
369impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, ScalarResult<C>, C: CurveGroup);
370impl_commutative!(DensePolynomialResult<C>, Mul, mul, *, ScalarResult<C>, C: CurveGroup);
371
372impl<C: CurveGroup> Mul<&AuthenticatedScalarResult<C>> for &DensePolynomialResult<C> {
373    type Output = AuthenticatedDensePoly<C>;
374
375    fn mul(self, rhs: &AuthenticatedScalarResult<C>) -> Self::Output {
376        let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec();
377        AuthenticatedDensePoly::from_coeffs(new_coeffs)
378    }
379}
380impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, AuthenticatedScalarResult<C>, Output=AuthenticatedDensePoly<C>, C: CurveGroup);
381impl_commutative!(DensePolynomialResult<C>, Mul, mul, *, AuthenticatedScalarResult<C>, Output=AuthenticatedDensePoly<C>, C: CurveGroup);
382
383// --- Division --- //
384
385// Floor division, i.e. truncated remainder
386#[allow(clippy::suspicious_arithmetic_impl)]
387impl<C: CurveGroup> Div<&DensePolynomialResult<C>> for &DensePolynomialResult<C> {
388    type Output = DensePolynomialResult<C>;
389
390    fn div(self, rhs: &DensePolynomialResult<C>) -> Self::Output {
391        let fabric = self.coeffs[0].fabric();
392        if self.degree() < rhs.degree() {
393            return DensePolynomialResult::zero(fabric);
394        }
395
396        let n_lhs_coeffs = self.coeffs.len();
397        let n_rhs_coeffs = rhs.coeffs.len();
398
399        let mut deps = self.coeffs.iter().map(|coeff| coeff.id()).collect_vec();
400        deps.extend(rhs.coeffs.iter().map(|coeff| coeff.id()));
401
402        // Allocate a gate to return the coefficients of the quotient polynomial
403        let result_degree = self.degree().saturating_sub(rhs.degree());
404        let coeff_results =
405            fabric.new_batch_gate_op(deps, result_degree + 1 /* arity */, move |mut args| {
406                let lhs_coeffs: Vec<C::ScalarField> = args
407                    .drain(..n_lhs_coeffs)
408                    .map(|res| Scalar::<C>::from(res).inner())
409                    .collect_vec();
410                let rhs_coeffs = args
411                    .drain(..n_rhs_coeffs)
412                    .map(|res| Scalar::<C>::from(res).inner())
413                    .collect_vec();
414
415                let lhs_poly = DensePolynomial::from_coefficients_vec(lhs_coeffs);
416                let rhs_poly = DensePolynomial::from_coefficients_vec(rhs_coeffs);
417
418                let res = &lhs_poly / &rhs_poly;
419                res.coeffs
420                    .iter()
421                    .map(|coeff| ResultValue::Scalar(Scalar::new(*coeff)))
422                    .collect_vec()
423            });
424
425        DensePolynomialResult::from_coeffs(coeff_results)
426    }
427}
428
429#[cfg(test)]
430pub(crate) mod test {
431    use ark_ff::{One, Zero};
432    use ark_poly::Polynomial;
433    use itertools::Itertools;
434    use rand::{thread_rng, Rng};
435
436    use crate::{
437        algebra::{
438            poly_test_helpers::{allocate_poly, random_poly},
439            Scalar,
440        },
441        test_helpers::execute_mock_mpc,
442        PARTY0,
443    };
444
445    /// Degree bound on polynomials used for testing
446    const DEGREE_BOUND: usize = 100;
447
448    /// Test addition between a constant and result polynomial
449    ///
450    /// That is, we only allocate one of the polynomials
451    #[tokio::test]
452    async fn test_constant_poly_add() {
453        let poly1 = random_poly(DEGREE_BOUND);
454        let poly2 = random_poly(DEGREE_BOUND);
455
456        let expected_res = &poly1 + &poly2;
457
458        let (res, _) = execute_mock_mpc(|fabric| {
459            let poly1 = poly1.clone();
460            let poly2 = poly2.clone();
461
462            async move {
463                let poly1 = allocate_poly(&poly1, &fabric);
464                let res = &poly1 + &poly2;
465
466                res.await
467            }
468        })
469        .await;
470
471        assert_eq!(res, expected_res);
472    }
473
474    /// Test addition between two allocated polynomials
475    #[tokio::test]
476    async fn test_poly_add() {
477        let poly1 = random_poly(DEGREE_BOUND);
478        let poly2 = random_poly(DEGREE_BOUND);
479
480        let expected_res = &poly1 + &poly2;
481
482        let (res, _) = execute_mock_mpc(|fabric| {
483            let poly1 = poly1.clone();
484            let poly2 = poly2.clone();
485
486            async move {
487                let poly1 = allocate_poly(&poly1, &fabric);
488                let poly2 = allocate_poly(&poly2, &fabric);
489                let res = &poly1 + &poly2;
490
491                res.await
492            }
493        })
494        .await;
495
496        assert_eq!(res, expected_res);
497    }
498
499    /// Test subtraction between a constant and result polynomial
500    #[tokio::test]
501    async fn test_poly_sub_constant() {
502        let poly1 = random_poly(DEGREE_BOUND);
503        let poly2 = random_poly(DEGREE_BOUND);
504
505        let expected_res = &poly1 - &poly2;
506
507        let (res, _) = execute_mock_mpc(|fabric| {
508            let poly1 = poly1.clone();
509            let poly2 = poly2.clone();
510
511            async move {
512                let poly1 = allocate_poly(&poly1, &fabric);
513                let res = &poly1 - &poly2;
514
515                res.await
516            }
517        })
518        .await;
519
520        assert_eq!(res, expected_res);
521    }
522
523    /// Tests subtraction between two allocated polynomials
524    #[tokio::test]
525    async fn test_poly_sub() {
526        let poly1 = random_poly(DEGREE_BOUND);
527        let poly2 = random_poly(DEGREE_BOUND);
528
529        let expected_res = &poly1 - &poly2;
530
531        let (res, _) = execute_mock_mpc(|fabric| {
532            let poly1 = poly1.clone();
533            let poly2 = poly2.clone();
534
535            async move {
536                let poly1 = allocate_poly(&poly1, &fabric);
537                let poly2 = allocate_poly(&poly2, &fabric);
538                let res = &poly1 - &poly2;
539
540                res.await
541            }
542        })
543        .await;
544
545        assert_eq!(res, expected_res);
546    }
547
548    /// Tests multiplication between a constant and result polynomial
549    #[tokio::test]
550    async fn test_poly_mul_constant() {
551        let poly1 = random_poly(DEGREE_BOUND);
552        let poly2 = random_poly(DEGREE_BOUND);
553
554        let expected_res = &poly1 * &poly2;
555
556        let (res, _) = execute_mock_mpc(|fabric| {
557            let poly1 = poly1.clone();
558            let poly2 = poly2.clone();
559
560            async move {
561                let poly1 = allocate_poly(&poly1, &fabric);
562                let res = &poly1 * &poly2;
563
564                res.await
565            }
566        })
567        .await;
568
569        assert_eq!(res, expected_res);
570    }
571
572    /// Tests multiplication between two allocated polynomials
573    #[tokio::test]
574    async fn test_poly_mul() {
575        let poly1 = random_poly(DEGREE_BOUND);
576        let poly2 = random_poly(DEGREE_BOUND);
577
578        let expected_res = &poly1 * &poly2;
579
580        let (res, _) = execute_mock_mpc(|fabric| {
581            let poly1 = poly1.clone();
582            let poly2 = poly2.clone();
583
584            async move {
585                let poly1 = allocate_poly(&poly1, &fabric);
586                let poly2 = allocate_poly(&poly2, &fabric);
587                let res = &poly1 * &poly2;
588
589                res.await
590            }
591        })
592        .await;
593
594        assert_eq!(res, expected_res);
595    }
596
597    /// Tests dividing one polynomial by another
598    #[tokio::test]
599    async fn test_poly_div() {
600        let poly1 = random_poly(DEGREE_BOUND);
601        let poly2 = random_poly(DEGREE_BOUND);
602
603        let expected_res = &poly1 / &poly2;
604
605        let (res, _) = execute_mock_mpc(|fabric| {
606            let poly1 = poly1.clone();
607            let poly2 = poly2.clone();
608
609            async move {
610                let poly1 = allocate_poly(&poly1, &fabric);
611                let poly2 = allocate_poly(&poly2, &fabric);
612                let res = &poly1 / &poly2;
613
614                res.await
615            }
616        })
617        .await;
618
619        assert_eq!(res, expected_res);
620    }
621
622    /// Tests scalar multiplication with a constant value
623    #[tokio::test]
624    async fn test_scalar_mul_constant() {
625        let poly = random_poly(DEGREE_BOUND);
626
627        let mut rng = thread_rng();
628        let scaling_factor = Scalar::random(&mut rng);
629
630        let expected_res = &poly * scaling_factor.inner();
631
632        let (res, _) = execute_mock_mpc(|fabric| {
633            let poly = poly.clone();
634            async move {
635                let poly = allocate_poly(&poly, &fabric);
636
637                (poly * scaling_factor).await
638            }
639        })
640        .await;
641
642        assert_eq!(res, expected_res);
643    }
644
645    /// Tests scalar multiplication with a public result value
646    #[tokio::test]
647    async fn test_scalar_mul() {
648        let poly = random_poly(DEGREE_BOUND);
649
650        let mut rng = thread_rng();
651        let scaling_factor = Scalar::random(&mut rng);
652
653        let expected_res = &poly * scaling_factor.inner();
654
655        let (res, _) = execute_mock_mpc(|fabric| {
656            let poly = poly.clone();
657            async move {
658                let poly = allocate_poly(&poly, &fabric);
659                let scaling_factor = fabric.allocate_scalar(scaling_factor);
660
661                (poly * scaling_factor).await
662            }
663        })
664        .await;
665
666        assert_eq!(res, expected_res);
667    }
668
669    /// Tests scalar multiplication with a shared result value
670    #[tokio::test]
671    async fn test_scalar_mul_shared() {
672        let poly = random_poly(DEGREE_BOUND);
673
674        let mut rng = thread_rng();
675        let scaling_factor = Scalar::random(&mut rng);
676
677        let expected_res = &poly * scaling_factor.inner();
678
679        let (res, _) = execute_mock_mpc(|fabric| {
680            let poly = poly.clone();
681            async move {
682                let poly = allocate_poly(&poly, &fabric);
683                let scaling_factor = fabric.share_scalar(scaling_factor, PARTY0);
684
685                (poly * scaling_factor).open_authenticated().await
686            }
687        })
688        .await;
689
690        assert!(res.is_ok());
691        assert_eq!(res.unwrap(), expected_res);
692    }
693
694    /// Test evaluating a polynomial in the computation graph
695    #[tokio::test]
696    async fn test_eval() {
697        let poly = random_poly(DEGREE_BOUND);
698
699        let mut rng = thread_rng();
700        let eval_point = Scalar::random(&mut rng);
701
702        let expected_eval = poly.evaluate(&eval_point.inner());
703
704        let (eval, _) = execute_mock_mpc(|fabric| {
705            let poly = poly.clone();
706            async move {
707                let point_res = fabric.allocate_scalar(eval_point);
708                let poly = allocate_poly(&poly, &fabric);
709
710                poly.eval(point_res).await
711            }
712        })
713        .await;
714
715        assert_eq!(eval.inner(), expected_eval);
716    }
717
718    /// Tests computing the modular inverse of a polynomial
719    #[tokio::test]
720    async fn test_mod_inv() {
721        let poly = random_poly(DEGREE_BOUND);
722
723        let mut rng = thread_rng();
724        let t = rng.gen_range(1..(DEGREE_BOUND * 2));
725
726        let (res, _) = execute_mock_mpc(|fabric| {
727            let poly = poly.clone();
728            async move {
729                let poly = allocate_poly(&poly, &fabric);
730
731                poly.mul_inverse_mod_t(t).await
732            }
733        })
734        .await;
735
736        // Check that the result is correct
737        let inverted = &poly * &res;
738        let mut first_t_coeffs = inverted.coeffs.into_iter().take(t).collect_vec();
739
740        assert!(first_t_coeffs.remove(0).is_one());
741        assert!(first_t_coeffs.into_iter().all(|coeff| coeff.is_zero()));
742    }
743}