From 7df7b291d218ed8cb7e6bcf64fe089b2f5a2ff79 Mon Sep 17 00:00:00 2001 From: Andy Leiserson Date: Tue, 27 Feb 2024 17:46:14 -0800 Subject: [PATCH] Vectorized reveal This change also removes the `B: RecordBinding` type parameter on the `Reveal` trait, which is no longer used after removal of IPAv2. --- ipa-core/src/protocol/basics/check_zero.rs | 8 +- ipa-core/src/protocol/basics/mod.rs | 8 +- ipa-core/src/protocol/basics/reveal.rs | 107 ++++++++++++------ ipa-core/src/protocol/boolean/comparison.rs | 8 +- ipa-core/src/protocol/boolean/solved_bits.rs | 9 +- ipa-core/src/protocol/context/validator.rs | 5 +- .../boolean_ops/share_conversion_aby.rs | 4 +- ipa-core/src/protocol/ipa_prf/prf_eval.rs | 9 +- ipa-core/src/protocol/ipa_prf/quicksort.rs | 2 +- 9 files changed, 107 insertions(+), 53 deletions(-) diff --git a/ipa-core/src/protocol/basics/check_zero.rs b/ipa-core/src/protocol/basics/check_zero.rs index 97d9f3723..4bbc5a5ad 100644 --- a/ipa-core/src/protocol/basics/check_zero.rs +++ b/ipa-core/src/protocol/basics/check_zero.rs @@ -57,9 +57,11 @@ pub async fn check_zero( let rv_share = r_sharing .multiply(v, ctx.narrow(&Step::MultiplyWithR), record_id) .await?; - let rv = rv_share - .reveal(ctx.narrow(&Step::RevealR), record_id) - .await?; + let rv = F::from_array( + &rv_share + .reveal(ctx.narrow(&Step::RevealR), record_id) + .await?, + ); Ok(rv == F::ZERO) } diff --git a/ipa-core/src/protocol/basics/mod.rs b/ipa-core/src/protocol/basics/mod.rs index 2b4065dd1..33ef5979c 100644 --- a/ipa-core/src/protocol/basics/mod.rs +++ b/ipa-core/src/protocol/basics/mod.rs @@ -20,7 +20,9 @@ pub use sum_of_product::SumOfProducts; use crate::{ ff::Field, protocol::{context::Context, RecordId}, - secret_sharing::{replicated::semi_honest::AdditiveShare, SecretSharing, SharedValue}, + secret_sharing::{ + replicated::semi_honest::AdditiveShare, SecretSharing, SharedValue, Vectorizable, + }, }; #[cfg(feature = "descriptive-gate")] use crate::{ @@ -30,10 +32,10 @@ use crate::{ }, }; -pub trait BasicProtocols: +pub trait BasicProtocols, const N: usize = 1>: SecretSharing + Reshare - + Reveal + + Reveal>::Array> + SecureMul + ShareKnownValue + SumOfProducts diff --git a/ipa-core/src/protocol/basics/reveal.rs b/ipa-core/src/protocol/basics/reveal.rs index c35a1ffea..5a2c5c8b9 100644 --- a/ipa-core/src/protocol/basics/reveal.rs +++ b/ipa-core/src/protocol/basics/reveal.rs @@ -4,8 +4,10 @@ use embed_doc_image::embed_doc_image; use crate::{ error::Error, helpers::{Direction, Role}, - protocol::{context::Context, RecordBinding, RecordId}, - secret_sharing::{replicated::semi_honest::AdditiveShare as Replicated, SharedValue}, + protocol::{context::Context, RecordId}, + secret_sharing::{ + replicated::semi_honest::AdditiveShare as Replicated, SharedValue, Vectorizable, + }, }; #[cfg(feature = "descriptive-gate")] use crate::{ @@ -17,12 +19,12 @@ use crate::{ /// Trait for reveal protocol to open a shared secret to all helpers inside the MPC ring. #[async_trait] -pub trait Reveal: Sized { +pub trait Reveal: Sized { type Output; /// reveal the secret to all helpers in MPC circuit. Note that after method is called, /// it must be assumed that the secret value has been revealed to at least one of the helpers. /// Even in case when method never terminates, returns an error, etc. - async fn reveal<'fut>(&self, ctx: C, record_binding: B) -> Result + async fn reveal<'fut>(&self, ctx: C, record_id: RecordId) -> Result where C: 'fut; @@ -30,7 +32,7 @@ pub trait Reveal: Sized { async fn partial_reveal<'fut>( &self, ctx: C, - record_binding: B, + record_id: RecordId, left_out: Role, ) -> Result, Error> where @@ -50,26 +52,33 @@ pub trait Reveal: Sized { /// i.e. their own shares and received share. #[async_trait] #[embed_doc_image("reveal", "images/reveal.png")] -impl Reveal for Replicated { - type Output = V; +impl, const N: usize> Reveal + for Replicated +{ + type Output = >::Array; - async fn reveal<'fut>(&self, ctx: C, record_id: RecordId) -> Result + async fn reveal<'fut>( + &self, + ctx: C, + record_id: RecordId, + ) -> Result<>::Array, Error> where C: 'fut, { - let (left, right) = self.as_tuple(); + let left = self.left_arr(); + let right = self.right_arr(); - ctx.send_channel(ctx.role().peer(Direction::Right)) + ctx.send_channel::<>::Array>(ctx.role().peer(Direction::Right)) .send(record_id, left) .await?; // Sleep until `helper's left` sends their share - let share = ctx + let share: >::Array = ctx .recv_channel(ctx.role().peer(Direction::Left)) .receive(record_id) .await?; - Ok(left + right + share) + Ok(share + left + right) } /// TODO: implement reveal through partial reveal where `left_out` is optional @@ -78,15 +87,16 @@ impl Reveal for Replicated { ctx: C, record_id: RecordId, left_out: Role, - ) -> Result, Error> + ) -> Result>::Array>, Error> where C: 'fut, { - let (left, right) = self.as_tuple(); + let left = self.left_arr(); + let right = self.right_arr(); // send except to left_out if ctx.role().peer(Direction::Right) != left_out { - ctx.send_channel(ctx.role().peer(Direction::Right)) + ctx.send_channel::<>::Array>(ctx.role().peer(Direction::Right)) .send(record_id, left) .await?; } @@ -94,12 +104,12 @@ impl Reveal for Replicated { if ctx.role() == left_out { Ok(None) } else { - let share = ctx + let share: >::Array = ctx .recv_channel(ctx.role().peer(Direction::Left)) .receive(record_id) .await?; - Ok(Some(left + right + share)) + Ok(Some(share + left + right)) } } } @@ -110,16 +120,14 @@ impl Reveal for Replicated { /// indeed match. #[cfg(feature = "descriptive-gate")] #[async_trait] -impl<'a, F: ExtendableField> Reveal, RecordId> - for MaliciousReplicated -{ - type Output = F; +impl<'a, F: ExtendableField> Reveal, 1> for MaliciousReplicated { + type Output = >::Array; async fn reveal<'fut>( &self, ctx: UpgradedMaliciousContext<'a, F>, record_id: RecordId, - ) -> Result + ) -> Result<>::Array, Error> where UpgradedMaliciousContext<'a, F>: 'fut, { @@ -147,7 +155,7 @@ impl<'a, F: ExtendableField> Reveal, RecordId> .await?; if share_from_left == share_from_right { - Ok(left + right + share_from_left) + Ok((left + right + share_from_left).into_array()) } else { Err(Error::MaliciousRevealFailed) } @@ -158,7 +166,7 @@ impl<'a, F: ExtendableField> Reveal, RecordId> ctx: UpgradedMaliciousContext<'a, F>, record_id: RecordId, left_out: Role, - ) -> Result, Error> + ) -> Result>::Array>, Error> where UpgradedMaliciousContext<'a, F>: 'fut, { @@ -190,7 +198,7 @@ impl<'a, F: ExtendableField> Reveal, RecordId> .await?; if share_from_left == share_from_right { - Ok(Some(left + right + share_from_left)) + Ok(Some((left + right + share_from_left).into_array())) } else { Err(Error::MaliciousRevealFailed) } @@ -202,11 +210,11 @@ impl<'a, F: ExtendableField> Reveal, RecordId> mod tests { use std::iter::zip; - use futures::future::{try_join, try_join3}; + use futures::future::{join_all, try_join, try_join3}; use crate::{ error::Error, - ff::{Field, Fp31}, + ff::{Field, Fp31, Fp32BitPrime}, helpers::Direction, protocol::{ basics::Reveal, @@ -221,17 +229,45 @@ mod tests { AdditiveShare as MaliciousReplicated, ExtendableField, ThisCodeIsAuthorizedToDowngradeFromMalicious, }, - IntoShares, + IntoShares, SharedValue, }, test_fixture::{join3v, Runner, TestWorld}, }; #[tokio::test] pub async fn simple() -> Result<(), Error> { + type TestField = Fp31; + + let mut rng = thread_rng(); + let world = TestWorld::default(); + + let input = rng.gen::(); + let results = world + .semi_honest(input, |ctx, share| async move { + TestField::from_array( + &share + .reveal(ctx.set_total_records(1), RecordId::from(0)) + .await + .unwrap(), + ) + }) + .await; + + assert_eq!(input, results[0]); + assert_eq!(input, results[1]); + assert_eq!(input, results[2]); + + Ok(()) + } + + #[tokio::test] + pub async fn vectorized() -> Result<(), Error> { + type TestField = [Fp32BitPrime; 32]; + let mut rng = thread_rng(); let world = TestWorld::default(); - let input = rng.gen::(); + let input = rng.gen::(); let results = world .semi_honest(input, |ctx, share| async move { share @@ -250,6 +286,8 @@ mod tests { #[tokio::test] pub async fn malicious() -> Result<(), Error> { + type TestField = Fp31; + let mut rng = thread_rng(); let world = TestWorld::default(); let sh_ctx = world.malicious_contexts(); @@ -262,7 +300,7 @@ mod tests { .unwrap(); let record_id = RecordId::from(0); - let input: Fp31 = rng.gen(); + let input: TestField = rng.gen(); let m_shares = join3v( zip(m_ctx.iter(), input.share_with(&mut rng)) @@ -270,10 +308,11 @@ mod tests { ) .await; - let results = join3v( - zip(m_ctx.clone().into_iter(), m_shares) - .map(|(m_ctx, m_share)| async move { m_share.reveal(m_ctx, record_id).await }), - ) + let results = join_all(zip(m_ctx.clone().into_iter(), m_shares).map( + |(m_ctx, m_share)| async move { + TestField::from_array(&m_share.reveal(m_ctx, record_id).await.unwrap()) + }, + )) .await; assert_eq!(input, results[0]); diff --git a/ipa-core/src/protocol/boolean/comparison.rs b/ipa-core/src/protocol/boolean/comparison.rs index 81e1d05c0..7407b1ab4 100644 --- a/ipa-core/src/protocol/boolean/comparison.rs +++ b/ipa-core/src/protocol/boolean/comparison.rs @@ -82,9 +82,11 @@ where let r = rbg.generate(record_id).await?; // Mask `a` with random `r` and reveal. - let b = (r.b_p + a) - .reveal(ctx.narrow(&Step::Reveal), record_id) - .await?; + let b = F::from_array( + &(r.b_p + a) + .reveal(ctx.narrow(&Step::Reveal), record_id) + .await?, + ); let RBounds { r_lo, r_hi, invert } = compute_r_bounds(b.as_u128(), c, F::PRIME.into()); diff --git a/ipa-core/src/protocol/boolean/solved_bits.rs b/ipa-core/src/protocol/boolean/solved_bits.rs index 452383e24..2f2d3227e 100644 --- a/ipa-core/src/protocol/boolean/solved_bits.rs +++ b/ipa-core/src/protocol/boolean/solved_bits.rs @@ -7,6 +7,7 @@ use crate::{ error::Error, ff::{Field, PrimeField}, protocol::{ + basics::Reveal, boolean::{ bitwise_less_than_prime::BitwiseLessThanPrime, generate_random_bits::one_random_bit, }, @@ -18,7 +19,7 @@ use crate::{ AdditiveShare as MaliciousReplicated, DowngradeMalicious, ExtendableField, UnauthorizedDowngradeWrapper, }, - BitDecomposed, Linear as LinearSecretSharing, LinearRefOps, SecretSharing, + BitDecomposed, Linear as LinearSecretSharing, LinearRefOps, SecretSharing, Vectorizable, }, }; @@ -128,12 +129,14 @@ async fn is_less_than_p(ctx: C, record_id: RecordId, b_b: &[S]) -> Resu where F: PrimeField, C: Context, - S: LinearSecretSharing + BasicProtocols, + S: LinearSecretSharing + + BasicProtocols + + Reveal>::Array>, { let c_b = BitwiseLessThanPrime::less_than_prime(ctx.narrow(&Step::IsPLessThanB), record_id, b_b) .await?; - if c_b.reveal(ctx.narrow(&Step::RevealC), record_id).await? == F::ZERO { + if F::from_array(&c_b.reveal(ctx.narrow(&Step::RevealC), record_id).await?) == F::ZERO { return Ok(false); } Ok(true) diff --git a/ipa-core/src/protocol/context/validator.rs b/ipa-core/src/protocol/context/validator.rs index d38473a33..74353e250 100644 --- a/ipa-core/src/protocol/context/validator.rs +++ b/ipa-core/src/protocol/context/validator.rs @@ -27,6 +27,7 @@ use crate::{ protocol::basics::Reveal, protocol::context::Context, protocol::context::{MaliciousContext, UpgradedMaliciousContext}, + secret_sharing::SharedValue, sync::Arc, }; @@ -229,7 +230,9 @@ impl<'a, F: ExtendableField> Validator, F> for Malicious<'a .validate_ctx .narrow(&ValidateStep::RevealR) .set_total_records(1); - let r = self.r_share.reveal(narrow_ctx, RecordId::FIRST).await?; + let r = ::ExtendedField::from_array( + &self.r_share.reveal(narrow_ctx, RecordId::FIRST).await?, + ); let t = u_share - &(w_share * r); let check_zero_ctx = self diff --git a/ipa-core/src/protocol/ipa_prf/boolean_ops/share_conversion_aby.rs b/ipa-core/src/protocol/ipa_prf/boolean_ops/share_conversion_aby.rs index 9ac7ca68a..8f8ae7ee2 100644 --- a/ipa-core/src/protocol/ipa_prf/boolean_ops/share_conversion_aby.rs +++ b/ipa-core/src/protocol/ipa_prf/boolean_ops/share_conversion_aby.rs @@ -169,10 +169,10 @@ where match ctx.role() { Role::H1 => Ok(AdditiveShare::::new( Fp25519::from(sh_s.left()).neg(), - Fp25519::from(y.unwrap()), + Fp25519::from(BA256::from_array(&y.unwrap())), )), Role::H2 => Ok(AdditiveShare::::new( - Fp25519::from(y.unwrap()), + Fp25519::from(BA256::from_array(&y.unwrap())), Fp25519::from(sh_r.right()).neg(), )), Role::H3 => Ok(AdditiveShare::::new( diff --git a/ipa-core/src/protocol/ipa_prf/prf_eval.rs b/ipa-core/src/protocol/ipa_prf/prf_eval.rs index 2686f01a7..10bbad630 100644 --- a/ipa-core/src/protocol/ipa_prf/prf_eval.rs +++ b/ipa-core/src/protocol/ipa_prf/prf_eval.rs @@ -9,7 +9,10 @@ use crate::{ prss::SharedRandomness, RecordId, }, - secret_sharing::replicated::{semi_honest::AdditiveShare, ReplicatedSecretSharing}, + secret_sharing::{ + replicated::{semi_honest::AdditiveShare, ReplicatedSecretSharing}, + SharedValue, + }, }; #[derive(Step)] @@ -88,8 +91,8 @@ where .await?; //reconstruct (z,R) - let gr: RP25519 = sh_gr.reveal(ctx.narrow(&Step::RevealR), record_id).await?; - let z = y.reveal(ctx.narrow(&Step::Revealz), record_id).await?; + let gr = RP25519::from_array(&sh_gr.reveal(ctx.narrow(&Step::RevealR), record_id).await?); + let z = Fp25519::from_array(&y.reveal(ctx.narrow(&Step::Revealz), record_id).await?); //compute R^(1/z) to u64 Ok(u64::from(gr * (z.invert()))) diff --git a/ipa-core/src/protocol/ipa_prf/quicksort.rs b/ipa-core/src/protocol/ipa_prf/quicksort.rs index dbc0ca189..4f07b7a13 100644 --- a/ipa-core/src/protocol/ipa_prf/quicksort.rs +++ b/ipa-core/src/protocol/ipa_prf/quicksort.rs @@ -112,7 +112,7 @@ where .await?; // desc = true will flip the order of the sort - Ok::<_, Error>(Boolean::from(desc) == comparison) + Ok::<_, Error>(Boolean::from(desc) == Boolean::from_array(&comparison)) } }), ),