From 346890a8df540431af4e71aa15c234ddbd34c938 Mon Sep 17 00:00:00 2001 From: Joey Kraut Date: Thu, 12 Oct 2023 11:04:14 -0700 Subject: [PATCH 1/3] algebra: poly: Implement polynomial division on results --- src/algebra/poly/poly.rs | 77 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/src/algebra/poly/poly.rs b/src/algebra/poly/poly.rs index 1085307..5fe8262 100644 --- a/src/algebra/poly/poly.rs +++ b/src/algebra/poly/poly.rs @@ -2,7 +2,7 @@ use std::{ cmp, iter, - ops::{Add, Mul, Neg, Sub}, + ops::{Add, Div, Mul, Neg, Sub}, pin::Pin, task::{Context, Poll}, }; @@ -36,12 +36,12 @@ impl DensePolynomialResult { } /// Construct the zero polynomial (additive identity) - pub fn zero(fabric: MpcFabric) -> Self { + pub fn zero(fabric: &MpcFabric) -> Self { Self::from_coeffs(vec![fabric.zero()]) } /// Construct the one polynomial (multiplicative identity) - pub fn one(fabric: MpcFabric) -> Self { + pub fn one(fabric: &MpcFabric) -> Self { Self::from_coeffs(vec![fabric.one()]) } @@ -242,6 +242,52 @@ impl Mul<&DensePolynomialResult> for &DensePolynomialResult } impl_borrow_variants!(DensePolynomialResult, Mul, mul, *, DensePolynomialResult, C: CurveGroup); +// --- Division --- // + +// Floor division, i.e. truncated remainder +#[allow(clippy::suspicious_arithmetic_impl)] +impl Div<&DensePolynomialResult> for &DensePolynomialResult { + type Output = DensePolynomialResult; + + fn div(self, rhs: &DensePolynomialResult) -> 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())); + + // Allocate a gate to return the coefficients of the quotient polynomial + let result_degree = self.degree().saturating_sub(rhs.degree()); + let coeff_results = + fabric.new_batch_gate_op(deps, result_degree + 1 /* arity */, move |mut args| { + let lhs_coeffs: Vec = args + .drain(..n_lhs_coeffs) + .map(|res| Scalar::::from(res).inner()) + .collect_vec(); + let rhs_coeffs = args + .drain(..n_rhs_coeffs) + .map(|res| Scalar::::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)] mod test { use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; @@ -423,6 +469,31 @@ mod test { assert_eq!(res, expected_res); } + /// Tests dividing one polynomial by another + #[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); + } + /// Test evaluating a polynomial in the computation graph #[tokio::test] async fn test_eval() { From 4a48b55fae69f77c40435819c5f80d701da0ee0c Mon Sep 17 00:00:00 2001 From: Joey Kraut Date: Thu, 12 Oct 2023 14:34:49 -0700 Subject: [PATCH 2/3] algebra: poly: authenticated-poly: Implement authenticated poly division --- src/algebra/poly/authenticated_poly.rs | 116 ++++++++++++++---- src/algebra/poly/mod.rs | 32 ++++- src/algebra/poly/poly.rs | 135 +++++++++++++++++---- src/algebra/scalar/authenticated_scalar.rs | 35 +++++- 4 files changed, 272 insertions(+), 46 deletions(-) diff --git a/src/algebra/poly/authenticated_poly.rs b/src/algebra/poly/authenticated_poly.rs index 27d1e2e..85cdf9f 100644 --- a/src/algebra/poly/authenticated_poly.rs +++ b/src/algebra/poly/authenticated_poly.rs @@ -4,7 +4,7 @@ use std::{ cmp, iter, - ops::{Add, Mul, Neg, Sub}, + ops::{Add, Div, Mul, Neg, Sub}, pin::Pin, task::{Context, Poll}, }; @@ -288,39 +288,82 @@ impl Mul<&AuthenticatedDensePoly> for &AuthenticatedDensePoly< } } +// --- Division --- // +/// 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)} +/// +/// To see this, consider that a_1(x) = q_1(x)b(x) + r_1(x) and a_2(x) = q_2(x)b(x) + r_2(x) where: +/// - deg(q_1) = deg(a_1) - deg(b) +/// - deg(q_2) = deg(a_2) - deg(b) +/// - deg(r_1) < deg(b) +/// - deg(r_2) < deg(b) +/// The floor division operator for a(x), b(x) returns q(x) such that there exists r(x): deg(r) < deg(b) +/// where a(x) = q(x)b(x) + r(x) +/// Note that a_1(x) + a_2(x) = (q_1(x) + q_2(x))b(x) + r_1(x) + r_2(x), where of course +/// deg(r_1 + r_2) < deg(b), so \floor{a(x) / b(x)} = q_1(x) + q_2(x); making q_1, q_2 additive +/// secret shares of the result as desired +impl Div<&DensePolynomialResult> for &AuthenticatedDensePoly { + type Output = AuthenticatedDensePoly; + + fn div(self, rhs: &DensePolynomialResult) -> Self::Output { + // We cannot break early if the remainder is exhausted because this will cause the gate + // sequencing to differ between parties in the MPC. Instead we execute the whole computation on + // both ends of the MPC + assert!(!rhs.coeffs.is_empty(), "cannot divide by zero polynomial"); + let fabric = self.coeffs[0].fabric(); + + let quotient_degree = self.degree().saturating_sub(rhs.degree()); + if quotient_degree == 0 { + return AuthenticatedDensePoly::zero(fabric); + } + + let mut remainder = self.clone(); + let mut quotient_coeffs = fabric.ones_authenticated(quotient_degree + 1); + + let divisor_leading_inverse = rhs.coeffs.last().unwrap().inverse(); + for deg in (0..=quotient_degree).rev() { + // Compute the quotient coefficient for this round + let remainder_leading_coeff = remainder.coeffs.last().unwrap(); + let next_quotient_coeff = remainder_leading_coeff * &divisor_leading_inverse; + + // Update the remainder and record the coefficient + for (i, divisor_coeff) in rhs.coeffs.iter().enumerate() { + let remainder_ind = deg + i; + remainder.coeffs[remainder_ind] = + &remainder.coeffs[remainder_ind] - divisor_coeff * &next_quotient_coeff; + } + + quotient_coeffs[deg] = next_quotient_coeff; + + // Pop the leading coefficient (now zero) from the remainder + remainder.coeffs.pop(); + } + + // Reverse the quotient coefficients, long division generates them leading coefficient first, and + // we store them leading coefficient last + // quotient_coeffs.reverse(); + AuthenticatedDensePoly::from_coeffs(quotient_coeffs) + } +} +impl_borrow_variants!(AuthenticatedDensePoly, Div, div, /, DensePolynomialResult, C: CurveGroup); + #[cfg(test)] mod test { - use ark_poly::{univariate::DensePolynomial, Polynomial}; - use itertools::Itertools; + use ark_poly::Polynomial; use rand::thread_rng; use crate::{ algebra::{ - poly_test_helpers::{random_poly, TestPolyField}, + poly_test_helpers::{allocate_poly, random_poly, share_poly}, Scalar, }, - network::PartyId, - test_helpers::{execute_mock_mpc, TestCurve}, - MpcFabric, PARTY0, + test_helpers::execute_mock_mpc, + PARTY0, }; - use super::AuthenticatedDensePoly; - /// The degree bound used for testing const DEGREE_BOUND: usize = 100; - /// Allocate an authenticated polynomial in the given fabric - fn share_poly( - poly: DensePolynomial, - sender: PartyId, - fabric: &MpcFabric, - ) -> AuthenticatedDensePoly { - let coeffs = poly.coeffs.iter().copied().map(Scalar::new).collect_vec(); - let shared_coeffs = fabric.batch_share_scalar(coeffs, sender); - - AuthenticatedDensePoly::from_coeffs(shared_coeffs) - } - /// Test evaluating a polynomial at a given point #[tokio::test] async fn test_eval() { @@ -497,4 +540,35 @@ mod test { 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() { + let poly1 = random_poly(DEGREE_BOUND); + let poly2 = random_poly(DEGREE_BOUND); + + let (poly1, poly2) = if poly1.degree() < poly2.degree() { + (poly2, poly1) + } else { + (poly1, poly2) + }; + + 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 divisor = allocate_poly(&poly2, &fabric); + + let quotient = dividend / divisor; + quotient.open_authenticated().await + } + }) + .await; + + assert!(res.is_ok()); + assert_eq!(res.unwrap(), expected_res); + } } diff --git a/src/algebra/poly/mod.rs b/src/algebra/poly/mod.rs index f164956..d57af4a 100644 --- a/src/algebra/poly/mod.rs +++ b/src/algebra/poly/mod.rs @@ -13,11 +13,14 @@ pub use poly::*; #[cfg(test)] pub mod poly_test_helpers { use ark_ec::Group; - use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; + use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; use ark_std::UniformRand; + use itertools::Itertools; use rand::{thread_rng, Rng}; - use crate::test_helpers::TestCurve; + use crate::{algebra::Scalar, network::PartyId, test_helpers::TestCurve, MpcFabric}; + + use super::{AuthenticatedDensePoly, DensePolynomialResult}; /// The scalar field testing polynomials are defined over pub type TestPolyField = ::ScalarField; @@ -36,4 +39,29 @@ pub mod poly_test_helpers { DensePolynomial::from_coefficients_vec(coeffs) } + + /// Allocate a polynomial in an MPC fabric + pub fn allocate_poly( + poly: &DensePolynomial, + fabric: &MpcFabric, + ) -> DensePolynomialResult { + let mut allocated_coeffs = Vec::with_capacity(poly.degree() + 1); + for coeff in poly.coeffs().iter() { + allocated_coeffs.push(fabric.allocate_scalar(Scalar::new(*coeff))); + } + + DensePolynomialResult::from_coeffs(allocated_coeffs) + } + + /// Allocate an authenticated polynomial in the given fabric + pub fn share_poly( + poly: DensePolynomial, + sender: PartyId, + fabric: &MpcFabric, + ) -> AuthenticatedDensePoly { + let coeffs = poly.coeffs.iter().copied().map(Scalar::new).collect_vec(); + let shared_coeffs = fabric.batch_share_scalar(coeffs, sender); + + AuthenticatedDensePoly::from_coeffs(shared_coeffs) + } } diff --git a/src/algebra/poly/poly.rs b/src/algebra/poly/poly.rs index 5fe8262..08863fc 100644 --- a/src/algebra/poly/poly.rs +++ b/src/algebra/poly/poly.rs @@ -16,11 +16,13 @@ use itertools::Itertools; use crate::{ algebra::{ macros::{impl_borrow_variants, impl_commutative}, - Scalar, ScalarResult, + AuthenticatedScalarResult, Scalar, ScalarResult, }, MpcFabric, ResultValue, }; +use super::AuthenticatedDensePoly; + /// A dense polynomial representation allocated in an MPC circuit #[derive(Clone)] pub struct DensePolynomialResult { @@ -242,6 +244,40 @@ impl Mul<&DensePolynomialResult> for &DensePolynomialResult } impl_borrow_variants!(DensePolynomialResult, Mul, mul, *, DensePolynomialResult, C: CurveGroup); +// --- Scalar Multiplication --- // +impl Mul<&Scalar> for &DensePolynomialResult { + type Output = DensePolynomialResult; + + fn mul(self, rhs: &Scalar) -> Self::Output { + let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec(); + DensePolynomialResult::from_coeffs(new_coeffs) + } +} +impl_borrow_variants!(DensePolynomialResult, Mul, mul, *, Scalar, C: CurveGroup); +impl_commutative!(DensePolynomialResult, Mul, mul, *, Scalar, C: CurveGroup); + +impl Mul<&ScalarResult> for &DensePolynomialResult { + type Output = DensePolynomialResult; + + fn mul(self, rhs: &ScalarResult) -> Self::Output { + let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec(); + DensePolynomialResult::from_coeffs(new_coeffs) + } +} +impl_borrow_variants!(DensePolynomialResult, Mul, mul, *, ScalarResult, C: CurveGroup); +impl_commutative!(DensePolynomialResult, Mul, mul, *, ScalarResult, C: CurveGroup); + +impl Mul<&AuthenticatedScalarResult> for &DensePolynomialResult { + type Output = AuthenticatedDensePoly; + + fn mul(self, rhs: &AuthenticatedScalarResult) -> Self::Output { + let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec(); + AuthenticatedDensePoly::from_coeffs(new_coeffs) + } +} +impl_borrow_variants!(DensePolynomialResult, Mul, mul, *, AuthenticatedScalarResult, Output=AuthenticatedDensePoly, C: CurveGroup); +impl_commutative!(DensePolynomialResult, Mul, mul, *, AuthenticatedScalarResult, Output=AuthenticatedDensePoly, C: CurveGroup); + // --- Division --- // // Floor division, i.e. truncated remainder @@ -289,37 +325,22 @@ impl Div<&DensePolynomialResult> for &DensePolynomialResult } #[cfg(test)] -mod test { - use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; +pub(crate) mod test { + use ark_poly::Polynomial; use rand::thread_rng; use crate::{ algebra::{ - poly_test_helpers::{random_poly, TestPolyField}, + poly_test_helpers::{allocate_poly, random_poly}, Scalar, }, - test_helpers::{execute_mock_mpc, TestCurve}, - MpcFabric, + test_helpers::execute_mock_mpc, + PARTY0, }; - use super::DensePolynomialResult; - /// Degree bound on polynomials used for testing const DEGREE_BOUND: usize = 100; - /// Allocate a polynomial in an MPC fabric - fn allocate_poly( - poly: &DensePolynomial, - fabric: &MpcFabric, - ) -> DensePolynomialResult { - let mut allocated_coeffs = Vec::with_capacity(poly.degree() + 1); - for coeff in poly.coeffs().iter() { - allocated_coeffs.push(fabric.allocate_scalar(Scalar::new(*coeff))); - } - - DensePolynomialResult::from_coeffs(allocated_coeffs) - } - /// Test addition between a constant and result polynomial /// /// That is, we only allocate one of the polynomials @@ -494,6 +515,78 @@ mod test { assert_eq!(res, expected_res); } + /// Tests scalar multiplication with a constant value + #[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); + } + + /// Tests scalar multiplication with a public result value + #[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); + } + + /// Tests scalar multiplication with a shared result value + #[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); + } + /// Test evaluating a polynomial in the computation graph #[tokio::test] async fn test_eval() { diff --git a/src/algebra/scalar/authenticated_scalar.rs b/src/algebra/scalar/authenticated_scalar.rs index 2ac27be..a07545a 100644 --- a/src/algebra/scalar/authenticated_scalar.rs +++ b/src/algebra/scalar/authenticated_scalar.rs @@ -3,7 +3,7 @@ use std::{ fmt::Debug, iter::Sum, - ops::{Add, Mul, Neg, Sub}, + ops::{Add, Div, Mul, Neg, Sub}, pin::Pin, task::{Context, Poll}, }; @@ -1004,7 +1004,18 @@ impl AuthenticatedScalarResult { } } -// === Curve Scalar Multiplication === // +// === Division === // +#[allow(clippy::suspicious_arithmetic_impl)] +impl Div<&ScalarResult> for &AuthenticatedScalarResult { + type Output = AuthenticatedScalarResult; + fn div(self, rhs: &ScalarResult) -> Self::Output { + let rhs_inv = rhs.inverse(); + self * rhs_inv + } +} +impl_borrow_variants!(AuthenticatedScalarResult, Div, div, /, ScalarResult, Output=AuthenticatedScalarResult, C: CurveGroup); + +// === Curve Scalar Multiplication === // impl Mul<&AuthenticatedScalarResult> for &CurvePoint { type Output = AuthenticatedPointResult; @@ -1134,6 +1145,26 @@ mod tests { assert!(res.1) } + /// Tests division between a shared and public scalar + #[tokio::test] + async fn test_public_division() { + let mut rng = thread_rng(); + let value1 = Scalar::random(&mut rng); + let value2 = Scalar::random(&mut rng); + + let expected_res = value1 * value2.inverse(); + + let (res, _) = execute_mock_mpc(|fabric| async move { + let shared_value = fabric.share_scalar(value1, PARTY0); + let public_value = fabric.allocate_scalar(value2); + + (shared_value / public_value).open().await + }) + .await; + + assert_eq!(res, expected_res) + } + /// Test a simple `XOR` circuit #[tokio::test] async fn test_xor_circuit() { From eba4a63408371cadc24b46e782280d4a6769586d Mon Sep 17 00:00:00 2001 From: Joey Kraut Date: Thu, 12 Oct 2023 14:49:55 -0700 Subject: [PATCH 3/3] algebra: authenticated-poly: Implement scalar multiplication --- src/algebra/poly/authenticated_poly.rs | 116 ++++++++++++++++++++++--- src/algebra/poly/poly.rs | 1 + 2 files changed, 107 insertions(+), 10 deletions(-) diff --git a/src/algebra/poly/authenticated_poly.rs b/src/algebra/poly/authenticated_poly.rs index 85cdf9f..35f7a64 100644 --- a/src/algebra/poly/authenticated_poly.rs +++ b/src/algebra/poly/authenticated_poly.rs @@ -288,6 +288,41 @@ impl Mul<&AuthenticatedDensePoly> for &AuthenticatedDensePoly< } } +// --- Scalar Multiplication --- // + +impl Mul<&Scalar> for &AuthenticatedDensePoly { + type Output = AuthenticatedDensePoly; + + fn mul(self, rhs: &Scalar) -> Self::Output { + let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec(); + AuthenticatedDensePoly::from_coeffs(new_coeffs) + } +} +impl_borrow_variants!(AuthenticatedDensePoly, Mul, mul, *, Scalar, C: CurveGroup); +impl_commutative!(AuthenticatedDensePoly, Mul, mul, *, Scalar, C: CurveGroup); + +impl Mul<&ScalarResult> for &AuthenticatedDensePoly { + type Output = AuthenticatedDensePoly; + + fn mul(self, rhs: &ScalarResult) -> Self::Output { + let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec(); + AuthenticatedDensePoly::from_coeffs(new_coeffs) + } +} +impl_borrow_variants!(AuthenticatedDensePoly, Mul, mul, *, ScalarResult, C: CurveGroup); +impl_commutative!(AuthenticatedDensePoly, Mul, mul, *, ScalarResult, C: CurveGroup); + +impl Mul<&AuthenticatedScalarResult> for &AuthenticatedDensePoly { + type Output = AuthenticatedDensePoly; + + fn mul(self, rhs: &AuthenticatedScalarResult) -> Self::Output { + let new_coeffs = self.coeffs.iter().map(|coeff| coeff * rhs).collect_vec(); + AuthenticatedDensePoly::from_coeffs(new_coeffs) + } +} +impl_borrow_variants!(AuthenticatedDensePoly, Mul, mul, *, AuthenticatedScalarResult, C: CurveGroup); +impl_commutative!(AuthenticatedDensePoly, Mul, mul, *, AuthenticatedScalarResult, C: CurveGroup); + // --- Division --- // /// 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)} @@ -318,7 +353,7 @@ impl Div<&DensePolynomialResult> for &AuthenticatedDensePoly Div<&DensePolynomialResult> for &AuthenticatedDensePoly Mul<&DensePolynomialResult> for &DensePolynomialResult impl_borrow_variants!(DensePolynomialResult, Mul, mul, *, DensePolynomialResult, C: CurveGroup); // --- Scalar Multiplication --- // + impl Mul<&Scalar> for &DensePolynomialResult { type Output = DensePolynomialResult;