use std::{
cmp, iter,
ops::{Add, Div, Mul, Neg, Sub},
pin::Pin,
task::{Context, Poll},
};
use ark_ec::CurveGroup;
use ark_ff::{Field, One, Zero};
use ark_poly::{
univariate::{DenseOrSparsePolynomial, DensePolynomial},
DenseUVPolynomial,
};
use futures::FutureExt;
use futures::{ready, Future};
use itertools::Itertools;
use crate::{
algebra::{
macros::{impl_borrow_variants, impl_commutative},
AuthenticatedScalarResult, Scalar, ScalarResult,
},
MpcFabric, ResultValue,
};
use super::AuthenticatedDensePoly;
fn x_to_t<C: CurveGroup>(t: usize) -> DensePolynomial<C::ScalarField> {
let mut coeffs = vec![C::ScalarField::zero(); t];
coeffs.push(C::ScalarField::one());
DensePolynomial::from_coefficients_vec(coeffs)
}
#[derive(Clone, Debug, Default)]
pub struct DensePolynomialResult<C: CurveGroup> {
pub coeffs: Vec<ScalarResult<C>>,
}
impl<C: CurveGroup> DensePolynomialResult<C> {
pub fn from_coeffs(coeffs: Vec<ScalarResult<C>>) -> Self {
assert!(!coeffs.is_empty(), "cannot construct an empty polynomial");
Self { coeffs }
}
pub fn zero(fabric: &MpcFabric<C>) -> Self {
Self::from_coeffs(vec![fabric.zero()])
}
pub fn one(fabric: &MpcFabric<C>) -> Self {
Self::from_coeffs(vec![fabric.one()])
}
pub fn degree(&self) -> usize {
self.coeffs.len() - 1
}
pub(crate) fn fabric(&self) -> &MpcFabric<C> {
self.coeffs[0].fabric()
}
pub fn eval(&self, point: ScalarResult<C>) -> ScalarResult<C> {
let fabric = self.fabric();
let mut deps = self.coeffs.iter().map(|coeff| coeff.id()).collect_vec();
deps.push(point.id());
let n_coeffs = self.coeffs.len();
fabric.new_gate_op(deps, move |mut args| {
let coeffs: Vec<Scalar<C>> = args.drain(..n_coeffs).map(|res| res.into()).collect_vec();
let point: Scalar<C> = args.pop().unwrap().into();
let mut res = Scalar::zero();
for coeff in coeffs.iter().rev() {
res = res * point + coeff;
}
ResultValue::Scalar(res)
})
}
}
impl<C: CurveGroup> DensePolynomialResult<C> {
pub fn mul_inverse_mod_t(&self, t: usize) -> Self {
let ids = self.coeffs.iter().map(|c| c.id()).collect_vec();
let n_result_coeffs = t;
let res_coeffs = self.fabric().new_batch_gate_op(
ids,
n_result_coeffs, move |args| {
let x_to_t = x_to_t::<C>(t);
let self_coeffs = args
.into_iter()
.map(|res| Scalar::<C>::from(res).inner())
.collect_vec();
let self_poly = DensePolynomial::from_coefficients_vec(self_coeffs);
let (inverse_poly, _) = Self::compute_bezout_polynomials(&self_poly, &x_to_t);
let self_constant_coeff = self_poly.coeffs[0];
let inverse_constant_coeff = inverse_poly.coeffs[0];
let constant_coeff_inv = (self_constant_coeff * inverse_constant_coeff)
.inverse()
.unwrap();
inverse_poly
.coeffs
.into_iter()
.take(n_result_coeffs)
.map(|c| c * constant_coeff_inv)
.map(Scalar::new)
.map(ResultValue::Scalar)
.collect_vec()
},
);
Self::from_coeffs(res_coeffs)
}
fn compute_bezout_polynomials(
a: &DensePolynomial<C::ScalarField>,
b: &DensePolynomial<C::ScalarField>,
) -> (
DensePolynomial<C::ScalarField>,
DensePolynomial<C::ScalarField>,
) {
if b.is_zero() {
return (
DensePolynomial::from_coefficients_vec(vec![C::ScalarField::one()]), DensePolynomial::zero(), );
}
let a_transformed = DenseOrSparsePolynomial::from(a);
let b_transformed = DenseOrSparsePolynomial::from(b);
let (quotient, remainder) = a_transformed.divide_with_q_and_r(&b_transformed).unwrap();
let (f, g) = Self::compute_bezout_polynomials(b, &remainder);
let next_g = &f - &("ient * &g);
(g, next_g)
}
}
impl<C: CurveGroup> Future for DensePolynomialResult<C>
where
C::ScalarField: Unpin,
{
type Output = DensePolynomial<C::ScalarField>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut coeffs = Vec::with_capacity(self.coeffs.len());
for coeff in self.coeffs.iter_mut() {
let ready_coeff = ready!(coeff.poll_unpin(cx));
coeffs.push(ready_coeff.inner());
}
Poll::Ready(DensePolynomial::from_coefficients_vec(coeffs))
}
}
impl<C: CurveGroup> Add<&DensePolynomial<C::ScalarField>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn add(self, rhs: &DensePolynomial<C::ScalarField>) -> Self::Output {
assert!(!self.coeffs.is_empty(), "cannot add an empty polynomial");
let fabric = self.coeffs[0].fabric();
let mut coeffs = Vec::new();
let max_degree = cmp::max(self.coeffs.len(), rhs.coeffs.len());
let padded_coeffs0 = self
.coeffs
.iter()
.cloned()
.chain(iter::repeat(fabric.zero()));
let padded_coeffs1 = rhs
.coeffs
.iter()
.copied()
.map(Scalar::<C>::new)
.chain(iter::repeat(Scalar::zero()));
for (lhs_coeff, rhs_coeff) in padded_coeffs0.zip(padded_coeffs1).take(max_degree) {
coeffs.push(lhs_coeff + rhs_coeff);
}
DensePolynomialResult::from_coeffs(coeffs)
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Add, add, +, DensePolynomial<C::ScalarField>, C: CurveGroup);
impl_commutative!(DensePolynomialResult<C>, Add, add, +, DensePolynomial<C::ScalarField>, C: CurveGroup);
impl<C: CurveGroup> Add<&DensePolynomialResult<C>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn add(self, rhs: &DensePolynomialResult<C>) -> Self::Output {
let mut coeffs = Vec::new();
let (shorter, longer) = if self.coeffs.len() < rhs.coeffs.len() {
(&self.coeffs, &rhs.coeffs)
} else {
(&rhs.coeffs, &self.coeffs)
};
for (i, longer_coeff) in longer.iter().enumerate() {
let new_coeff = if i < shorter.len() {
&shorter[i] + longer_coeff
} else {
longer_coeff.clone()
};
coeffs.push(new_coeff);
}
DensePolynomialResult::from_coeffs(coeffs)
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Add, add, +, DensePolynomialResult<C>, C: CurveGroup);
impl<C: CurveGroup> Neg for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn neg(self) -> Self::Output {
let mut coeffs = Vec::with_capacity(self.coeffs.len());
for coeff in self.coeffs.iter() {
coeffs.push(-coeff);
}
DensePolynomialResult::from_coeffs(coeffs)
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Neg, neg, -, C: CurveGroup);
#[allow(clippy::suspicious_arithmetic_impl)]
impl<C: CurveGroup> Sub<&DensePolynomial<C::ScalarField>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn sub(self, rhs: &DensePolynomial<C::ScalarField>) -> Self::Output {
let negated_rhs_coeffs = rhs.coeffs.iter().map(|coeff| -(*coeff)).collect();
let negated_rhs = DensePolynomial::from_coefficients_vec(negated_rhs_coeffs);
self + negated_rhs
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Sub, sub, -, DensePolynomial<C::ScalarField>, C: CurveGroup);
#[allow(clippy::suspicious_arithmetic_impl)]
impl<C: CurveGroup> Sub<&DensePolynomialResult<C>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn sub(self, rhs: &DensePolynomialResult<C>) -> Self::Output {
self + (-rhs)
}
}
impl<C: CurveGroup> Mul<&DensePolynomial<C::ScalarField>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn mul(self, rhs: &DensePolynomial<C::ScalarField>) -> Self::Output {
let fabric = self.coeffs[0].fabric();
let mut coeffs = Vec::with_capacity(self.coeffs.len() + rhs.coeffs.len() - 1);
for _ in 0..self.coeffs.len() + rhs.coeffs.len() - 1 {
coeffs.push(fabric.zero());
}
for (i, lhs_coeff) in self.coeffs.iter().enumerate() {
for (j, rhs_coeff) in rhs.coeffs.iter().copied().map(Scalar::new).enumerate() {
coeffs[i + j] = &coeffs[i + j] + lhs_coeff * rhs_coeff;
}
}
DensePolynomialResult::from_coeffs(coeffs)
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, DensePolynomial<C::ScalarField>, C: CurveGroup);
impl_commutative!(DensePolynomialResult<C>, Mul, mul, *, DensePolynomial<C::ScalarField>, C: CurveGroup);
impl<C: CurveGroup> Mul<&DensePolynomialResult<C>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn mul(self, rhs: &DensePolynomialResult<C>) -> Self::Output {
let fabric = self.coeffs[0].fabric();
let mut coeffs = Vec::with_capacity(self.coeffs.len() + rhs.coeffs.len() - 1);
for _ in 0..self.coeffs.len() + rhs.coeffs.len() - 1 {
coeffs.push(fabric.zero());
}
for (i, lhs_coeff) in self.coeffs.iter().enumerate() {
for (j, rhs_coeff) in rhs.coeffs.iter().enumerate() {
coeffs[i + j] = &coeffs[i + j] + lhs_coeff * rhs_coeff;
}
}
DensePolynomialResult::from_coeffs(coeffs)
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, DensePolynomialResult<C>, C: CurveGroup);
impl<C: CurveGroup> Mul<&Scalar<C>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn mul(self, rhs: &Scalar<C>) -> Self::Output {
let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec();
DensePolynomialResult::from_coeffs(new_coeffs)
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, Scalar<C>, C: CurveGroup);
impl_commutative!(DensePolynomialResult<C>, Mul, mul, *, Scalar<C>, C: CurveGroup);
impl<C: CurveGroup> Mul<&ScalarResult<C>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn mul(self, rhs: &ScalarResult<C>) -> Self::Output {
let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec();
DensePolynomialResult::from_coeffs(new_coeffs)
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, ScalarResult<C>, C: CurveGroup);
impl_commutative!(DensePolynomialResult<C>, Mul, mul, *, ScalarResult<C>, C: CurveGroup);
impl<C: CurveGroup> Mul<&AuthenticatedScalarResult<C>> for &DensePolynomialResult<C> {
type Output = AuthenticatedDensePoly<C>;
fn mul(self, rhs: &AuthenticatedScalarResult<C>) -> Self::Output {
let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec();
AuthenticatedDensePoly::from_coeffs(new_coeffs)
}
}
impl_borrow_variants!(DensePolynomialResult<C>, Mul, mul, *, AuthenticatedScalarResult<C>, Output=AuthenticatedDensePoly<C>, C: CurveGroup);
impl_commutative!(DensePolynomialResult<C>, Mul, mul, *, AuthenticatedScalarResult<C>, Output=AuthenticatedDensePoly<C>, C: CurveGroup);
#[allow(clippy::suspicious_arithmetic_impl)]
impl<C: CurveGroup> Div<&DensePolynomialResult<C>> for &DensePolynomialResult<C> {
type Output = DensePolynomialResult<C>;
fn div(self, rhs: &DensePolynomialResult<C>) -> Self::Output {
let fabric = self.coeffs[0].fabric();
if self.degree() < rhs.degree() {
return DensePolynomialResult::zero(fabric);
}
let n_lhs_coeffs = self.coeffs.len();
let n_rhs_coeffs = rhs.coeffs.len();
let mut deps = self.coeffs.iter().map(|coeff| coeff.id()).collect_vec();
deps.extend(rhs.coeffs.iter().map(|coeff| coeff.id()));
let result_degree = self.degree().saturating_sub(rhs.degree());
let coeff_results =
fabric.new_batch_gate_op(deps, result_degree + 1 , move |mut args| {
let lhs_coeffs: Vec<C::ScalarField> = args
.drain(..n_lhs_coeffs)
.map(|res| Scalar::<C>::from(res).inner())
.collect_vec();
let rhs_coeffs = args
.drain(..n_rhs_coeffs)
.map(|res| Scalar::<C>::from(res).inner())
.collect_vec();
let lhs_poly = DensePolynomial::from_coefficients_vec(lhs_coeffs);
let rhs_poly = DensePolynomial::from_coefficients_vec(rhs_coeffs);
let res = &lhs_poly / &rhs_poly;
res.coeffs
.iter()
.map(|coeff| ResultValue::Scalar(Scalar::new(*coeff)))
.collect_vec()
});
DensePolynomialResult::from_coeffs(coeff_results)
}
}
#[cfg(test)]
pub(crate) mod test {
use ark_ff::{One, Zero};
use ark_poly::Polynomial;
use itertools::Itertools;
use rand::{thread_rng, Rng};
use crate::{
algebra::{
poly_test_helpers::{allocate_poly, random_poly},
Scalar,
},
test_helpers::execute_mock_mpc,
PARTY0,
};
const DEGREE_BOUND: usize = 100;
#[tokio::test]
async fn test_constant_poly_add() {
let poly1 = random_poly(DEGREE_BOUND);
let poly2 = random_poly(DEGREE_BOUND);
let expected_res = &poly1 + &poly2;
let (res, _) = execute_mock_mpc(|fabric| {
let poly1 = poly1.clone();
let poly2 = poly2.clone();
async move {
let poly1 = allocate_poly(&poly1, &fabric);
let res = &poly1 + &poly2;
res.await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_poly_add() {
let poly1 = random_poly(DEGREE_BOUND);
let poly2 = random_poly(DEGREE_BOUND);
let expected_res = &poly1 + &poly2;
let (res, _) = execute_mock_mpc(|fabric| {
let poly1 = poly1.clone();
let poly2 = poly2.clone();
async move {
let poly1 = allocate_poly(&poly1, &fabric);
let poly2 = allocate_poly(&poly2, &fabric);
let res = &poly1 + &poly2;
res.await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_poly_sub_constant() {
let poly1 = random_poly(DEGREE_BOUND);
let poly2 = random_poly(DEGREE_BOUND);
let expected_res = &poly1 - &poly2;
let (res, _) = execute_mock_mpc(|fabric| {
let poly1 = poly1.clone();
let poly2 = poly2.clone();
async move {
let poly1 = allocate_poly(&poly1, &fabric);
let res = &poly1 - &poly2;
res.await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_poly_sub() {
let poly1 = random_poly(DEGREE_BOUND);
let poly2 = random_poly(DEGREE_BOUND);
let expected_res = &poly1 - &poly2;
let (res, _) = execute_mock_mpc(|fabric| {
let poly1 = poly1.clone();
let poly2 = poly2.clone();
async move {
let poly1 = allocate_poly(&poly1, &fabric);
let poly2 = allocate_poly(&poly2, &fabric);
let res = &poly1 - &poly2;
res.await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_poly_mul_constant() {
let poly1 = random_poly(DEGREE_BOUND);
let poly2 = random_poly(DEGREE_BOUND);
let expected_res = &poly1 * &poly2;
let (res, _) = execute_mock_mpc(|fabric| {
let poly1 = poly1.clone();
let poly2 = poly2.clone();
async move {
let poly1 = allocate_poly(&poly1, &fabric);
let res = &poly1 * &poly2;
res.await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_poly_mul() {
let poly1 = random_poly(DEGREE_BOUND);
let poly2 = random_poly(DEGREE_BOUND);
let expected_res = &poly1 * &poly2;
let (res, _) = execute_mock_mpc(|fabric| {
let poly1 = poly1.clone();
let poly2 = poly2.clone();
async move {
let poly1 = allocate_poly(&poly1, &fabric);
let poly2 = allocate_poly(&poly2, &fabric);
let res = &poly1 * &poly2;
res.await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_poly_div() {
let poly1 = random_poly(DEGREE_BOUND);
let poly2 = random_poly(DEGREE_BOUND);
let expected_res = &poly1 / &poly2;
let (res, _) = execute_mock_mpc(|fabric| {
let poly1 = poly1.clone();
let poly2 = poly2.clone();
async move {
let poly1 = allocate_poly(&poly1, &fabric);
let poly2 = allocate_poly(&poly2, &fabric);
let res = &poly1 / &poly2;
res.await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_scalar_mul_constant() {
let poly = random_poly(DEGREE_BOUND);
let mut rng = thread_rng();
let scaling_factor = Scalar::random(&mut rng);
let expected_res = &poly * scaling_factor.inner();
let (res, _) = execute_mock_mpc(|fabric| {
let poly = poly.clone();
async move {
let poly = allocate_poly(&poly, &fabric);
(poly * scaling_factor).await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_scalar_mul() {
let poly = random_poly(DEGREE_BOUND);
let mut rng = thread_rng();
let scaling_factor = Scalar::random(&mut rng);
let expected_res = &poly * scaling_factor.inner();
let (res, _) = execute_mock_mpc(|fabric| {
let poly = poly.clone();
async move {
let poly = allocate_poly(&poly, &fabric);
let scaling_factor = fabric.allocate_scalar(scaling_factor);
(poly * scaling_factor).await
}
})
.await;
assert_eq!(res, expected_res);
}
#[tokio::test]
async fn test_scalar_mul_shared() {
let poly = random_poly(DEGREE_BOUND);
let mut rng = thread_rng();
let scaling_factor = Scalar::random(&mut rng);
let expected_res = &poly * scaling_factor.inner();
let (res, _) = execute_mock_mpc(|fabric| {
let poly = poly.clone();
async move {
let poly = allocate_poly(&poly, &fabric);
let scaling_factor = fabric.share_scalar(scaling_factor, PARTY0);
(poly * scaling_factor).open_authenticated().await
}
})
.await;
assert!(res.is_ok());
assert_eq!(res.unwrap(), expected_res);
}
#[tokio::test]
async fn test_eval() {
let poly = random_poly(DEGREE_BOUND);
let mut rng = thread_rng();
let eval_point = Scalar::random(&mut rng);
let expected_eval = poly.evaluate(&eval_point.inner());
let (eval, _) = execute_mock_mpc(|fabric| {
let poly = poly.clone();
async move {
let point_res = fabric.allocate_scalar(eval_point);
let poly = allocate_poly(&poly, &fabric);
poly.eval(point_res).await
}
})
.await;
assert_eq!(eval.inner(), expected_eval);
}
#[tokio::test]
async fn test_mod_inv() {
let poly = random_poly(DEGREE_BOUND);
let mut rng = thread_rng();
let t = rng.gen_range(1..(DEGREE_BOUND * 2));
let (res, _) = execute_mock_mpc(|fabric| {
let poly = poly.clone();
async move {
let poly = allocate_poly(&poly, &fabric);
poly.mul_inverse_mod_t(t).await
}
})
.await;
let inverted = &poly * &res;
let mut first_t_coeffs = inverted.coeffs.into_iter().take(t).collect_vec();
assert!(first_t_coeffs.remove(0).is_one());
assert!(first_t_coeffs.into_iter().all(|coeff| coeff.is_zero()));
}
}