Skip to content

Commit

Permalink
algebra: authenticated-poly: Implement public poly division
Browse files Browse the repository at this point in the history
  • Loading branch information
joeykraut committed Jan 6, 2024
1 parent 6bc3995 commit cc650a5
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 57 deletions.
59 changes: 58 additions & 1 deletion src/algebra/poly/authenticated_poly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use itertools::Itertools;
use crate::{
algebra::{
macros::{impl_borrow_variants, impl_commutative},
AuthenticatedScalarOpenResult, AuthenticatedScalarResult, Scalar, ScalarResult,
poly_inverse_mod_xt, rev_coeffs, AuthenticatedScalarOpenResult, AuthenticatedScalarResult,
Scalar, ScalarResult,
},
error::MpcError,
MpcFabric, ResultValue,
Expand Down Expand Up @@ -443,6 +444,38 @@ impl_borrow_variants!(AuthenticatedDensePoly<C>, Mul, mul, *, AuthenticatedScala
impl_commutative!(AuthenticatedDensePoly<C>, Mul, mul, *, AuthenticatedScalarResult<C>, C: CurveGroup);

// --- Division --- //
/// Division by a public polynomial
impl<C: CurveGroup> Div<&DensePolynomial<C::ScalarField>> for &AuthenticatedDensePoly<C> {
type Output = AuthenticatedDensePoly<C>;

fn div(self, rhs: &DensePolynomial<C::ScalarField>) -> Self::Output {
// For shared polynomials each party can simply divide their local shares by the
// public divisor
assert!(!rhs.coeffs.is_empty(), "cannot divide by zero polynomial");

let n = self.degree();
let m = rhs.degree();
if n < m {
return AuthenticatedDensePoly::zero(self.fabric());
}

let modulus = n - m + 1;

// Apply the rev transformation
let rev_f = self.rev();
let rev_g = rev_coeffs(rhs);

// Invert `rev_g` in the quotient ring
let rev_g_inv = poly_inverse_mod_xt(&rev_g, modulus);

// Compute rev_f * rev_g_inv and "mod out" rev_r; what is left is `rev_q`
let rev_q = (&rev_f * &rev_g_inv).mod_xn(modulus);

// Undo the `rev` transformation
rev_q.rev()
}
}

/// Given a public divisor b(x) and shared dividend a(x) = a_1(x) + a_2(x) for
/// party shares a_1, a_2 We can divide each share locally to obtain a secret
/// sharing of \floor{a(x) / b(x)}
Expand Down Expand Up @@ -857,6 +890,30 @@ mod test {
assert_eq!(res.unwrap(), expected_res);
}

/// Tests dividing by a constant polynomial
#[tokio::test]
async fn test_div_polynomial_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 dividend = share_poly(poly1, PARTY0, &fabric);

let res = &dividend / &poly2;
res.open_authenticated().await
}
})
.await;

assert!(res.is_ok());
assert_eq!(res.unwrap(), expected_res);
}

/// Tests dividing a shared polynomial by a public polynomial
#[tokio::test]
async fn test_div_polynomial_public() {
Expand Down
63 changes: 63 additions & 0 deletions src/algebra/poly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,72 @@
mod authenticated_poly;
mod poly;

use ark_ff::{FftField, Field};
use ark_poly::{
univariate::{DenseOrSparsePolynomial, DensePolynomial},
DenseUVPolynomial,
};
use ark_std::Zero;
pub use authenticated_poly::*;
pub use poly::*;

/// Return a representation of x^t as a `DensePolynomial`
fn x_to_t<F: Field>(t: usize) -> DensePolynomial<F> {
let mut coeffs = vec![F::zero(); t];
coeffs.push(F::one());
DensePolynomial::from_coefficients_vec(coeffs)
}

/// Reverse the coefficients of an Arkworks polynomial
pub fn rev_coeffs<F: Field>(poly: &DensePolynomial<F>) -> DensePolynomial<F> {
let mut coeffs = poly.coeffs().to_vec();
coeffs.reverse();

DensePolynomial::from_coefficients_vec(coeffs)
}

/// A helper to compute the Bezout coefficients of the two given polynomials
///
/// I.e. for a(x), b(x) as input, we compute f(x), g(x) such that:
/// f(x) * a(x) + g(x) * b(x) = gcd(a, b)
/// This is done using the extended Euclidean method
fn compute_bezout_polynomials<F: FftField>(
a: &DensePolynomial<F>,
b: &DensePolynomial<F>,
) -> (DensePolynomial<F>, DensePolynomial<F>) {
if b.is_zero() {
return (
DensePolynomial::from_coefficients_vec(vec![F::one()]), // f(x) = 1
DensePolynomial::zero(), // f(x) = 0
);
}

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) = compute_bezout_polynomials(b, &remainder);
let next_g = &f - &(&quotient * &g);

(g, next_g)
}
/// Compute the multiplicative inverse of a polynomial mod x^t
pub fn poly_inverse_mod_xt<F: FftField>(poly: &DensePolynomial<F>, t: usize) -> DensePolynomial<F> {
// Compute the Bezout coefficients of the two polynomials
let x_to_t = x_to_t(t);
let (inverse_poly, _) = compute_bezout_polynomials(poly, &x_to_t);

// In a polynomial ring, gcd is defined only up to scalar multiplication, so we
// multiply the result by the inverse of the resultant first
// coefficient to uniquely define the inverse as f^{-1}(x) such that
// f * f^{-1}(x) = 1 \in F[x] / (x^t)
let self_constant_coeff = 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 * constant_coeff_inv
}

#[cfg(test)]
pub mod poly_test_helpers {
use ark_ec::Group;
Expand Down
61 changes: 5 additions & 56 deletions src/algebra/poly/poly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use std::{
};

use ark_ec::CurveGroup;
use ark_ff::{Field, One, Zero};
use ark_ff::Zero;
use ark_poly::{
univariate::{DenseOrSparsePolynomial, DensePolynomial},
DenseUVPolynomial, EvaluationDomain, Polynomial, Radix2EvaluationDomain,
univariate::DensePolynomial, DenseUVPolynomial, EvaluationDomain, Polynomial,
Radix2EvaluationDomain,
};
use futures::FutureExt;
use futures::{ready, Future};
Expand All @@ -26,18 +26,7 @@ use crate::{
MpcFabric, ResultValue,
};

use super::AuthenticatedDensePoly;

// -----------
// | Helpers |
// -----------

/// Return a representation of x^t as a `DensePolynomial`
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)
}
use super::{poly_inverse_mod_xt, AuthenticatedDensePoly};

// ------------------
// | Implementation |
Expand Down Expand Up @@ -129,29 +118,15 @@ impl<C: CurveGroup> DensePolynomialResult<C> {
ids,
n_result_coeffs, // output_arity
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);

// Compute the bezout coefficients of the two polynomials
let (inverse_poly, _) = Self::compute_bezout_polynomials(&self_poly, &x_to_t);

// In a polynomial ring, gcd is defined only up to scalar multiplication, so we
// multiply the result by the inverse of the resultant first
// coefficient to uniquely define the inverse as f^{-1}(x) such that
// f * f^{-1}(x) = 1 \in F[x] / (x^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();

let inverse_poly = poly_inverse_mod_xt(&self_poly, t);
inverse_poly
.coeffs
.into_iter()
.take(n_result_coeffs)
.map(|c| c * constant_coeff_inv)
.map(Scalar::new)
.map(ResultValue::Scalar)
.collect_vec()
Expand All @@ -160,32 +135,6 @@ impl<C: CurveGroup> DensePolynomialResult<C> {

Self::from_coeffs(res_coeffs)
}

/// A helper to compute the Bezout coefficients of the two given polynomials
///
/// I.e. for a(x), b(x) as input, we compute f(x), g(x) such that:
/// f(x) * a(x) + g(x) * b(x) = gcd(a, b)
/// This is done using the extended Euclidean method
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()]), // f(x) = 1
DensePolynomial::zero(), // f(x) = 0
);
}

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 - &(&quotient * &g);

(g, next_g)
}
}

impl<C: CurveGroup> Future for DensePolynomialResult<C>
Expand Down
60 changes: 60 additions & 0 deletions src/algebra/scalar/scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,26 @@ impl<C: CurveGroup> ScalarResult<C> {
res
})
}

/// Subtract a batch of `Scalar`s from a batch of `ScalarResult`s
pub fn batch_sub_constant(a: &[ScalarResult<C>], b: &[Scalar<C>]) -> Vec<ScalarResult<C>> {
assert_eq!(a.len(), b.len(), "Batch add constant requires equal length inputs");

let n = a.len();
let fabric = &a[0].fabric;
let b = b.to_vec();

let ids = a.iter().map(|v| v.id).collect_vec();
fabric.new_batch_gate_op(ids, n /* output_arity */, move |args| {
let a_vals = args.into_iter().map(Scalar::from).collect_vec();
a_vals
.into_iter()
.zip(b.iter())
.map(|(a, b)| a - b)
.map(ResultValue::Scalar)
.collect_vec()
})
}
}

// === SubAssign === //
Expand Down Expand Up @@ -663,6 +683,21 @@ impl<C: CurveGroup> Product for Scalar<C> {
}
}

impl<C: CurveGroup> Product for ScalarResult<C> {
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
let values: Vec<Self> = iter.collect_vec();
assert!(!values.is_empty(), "Cannot compute product of empty iterator");

let ids = values.iter().map(|v| v.id()).collect_vec();
let fabric = values[0].fabric();

fabric.new_gate_op(ids, move |args| {
let res = args.map(Scalar::from).product();
ResultValue::Scalar(res)
})
}
}

#[cfg(test)]
mod test {
use crate::{
Expand Down Expand Up @@ -741,6 +776,31 @@ mod test {
fabric.shutdown();
}

/// Tests batch subtraction with constant values
#[tokio::test]
async fn test_batch_sub_constant() {
const N: usize = 1000;
let mut rng = thread_rng();

let a = (0..N).map(|_| Scalar::random(&mut rng)).collect_vec();
let b = (0..N).map(|_| Scalar::random(&mut rng)).collect_vec();
let expected_res = a.iter().zip(b.iter()).map(|(a, b)| a - b).collect_vec();

let (res, _) = execute_mock_mpc(move |fabric| {
let a = a.clone();
let b = b.clone();
async move {
let a_alloc = a.iter().map(|x| fabric.allocate_scalar(*x)).collect_vec();

let res = ScalarResult::batch_sub_constant(&a_alloc, &b);
future::join_all(res.into_iter()).await
}
})
.await;

assert_eq!(res, expected_res);
}

/// Tests negation of raw scalars in a circuit
#[tokio::test]
async fn test_scalar_neg() {
Expand Down

0 comments on commit cc650a5

Please sign in to comment.