From 457c6468e49062f18a6c19a6165fa8055b363a9b Mon Sep 17 00:00:00 2001 From: Andy Leiserson Date: Mon, 4 Mar 2024 16:23:44 -0800 Subject: [PATCH 1/5] Remove async_trait for Reveal --- ipa-core/src/protocol/basics/mod.rs | 2 +- ipa-core/src/protocol/basics/reveal.rs | 48 ++++++++++++++------ ipa-core/src/protocol/boolean/comparison.rs | 7 +-- ipa-core/src/protocol/boolean/solved_bits.rs | 5 +- 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/ipa-core/src/protocol/basics/mod.rs b/ipa-core/src/protocol/basics/mod.rs index 33ef5979c..7e0ba176f 100644 --- a/ipa-core/src/protocol/basics/mod.rs +++ b/ipa-core/src/protocol/basics/mod.rs @@ -13,7 +13,7 @@ pub use check_zero::check_zero; pub use if_else::{if_else, select}; pub use mul::{BooleanArrayMul, MultiplyZeroPositions, SecureMul, ZeroPositions}; pub use reshare::Reshare; -pub use reveal::Reveal; +pub use reveal::{reveal, Reveal}; pub use share_known_value::ShareKnownValue; pub use sum_of_product::SumOfProducts; diff --git a/ipa-core/src/protocol/basics/reveal.rs b/ipa-core/src/protocol/basics/reveal.rs index fc7f1ab87..f95cdae9c 100644 --- a/ipa-core/src/protocol/basics/reveal.rs +++ b/ipa-core/src/protocol/basics/reveal.rs @@ -1,4 +1,5 @@ -use async_trait::async_trait; +use std::future::Future; + use embed_doc_image::embed_doc_image; use crate::{ @@ -18,23 +19,26 @@ use crate::{ }; /// Trait for reveal protocol to open a shared secret to all helpers inside the MPC ring. -#[async_trait] 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_id: RecordId) -> Result + fn reveal<'fut>( + &'fut self, + ctx: C, + record_id: RecordId, + ) -> impl Future> + Send + 'fut where C: 'fut; /// partial reveal protocol to open a shared secret to all helpers except helper `left_out` inside the MPC ring. - async fn partial_reveal<'fut>( - &self, + fn partial_reveal<'fut>( + &'fut self, ctx: C, record_id: RecordId, left_out: Role, - ) -> Result, Error> + ) -> impl Future, Error>> + Send + 'fut where C: 'fut; } @@ -50,7 +54,6 @@ pub trait Reveal: Sized { /// ![Reveal steps][reveal] /// Each helper sends their left share to the right helper. The helper then reconstructs their secret by adding the three shares /// i.e. their own shares and received share. -#[async_trait] #[embed_doc_image("reveal", "images/reveal.png")] impl, const N: usize> Reveal for Replicated @@ -58,7 +61,7 @@ impl, const N: usize> Reveal type Output = >::Array; async fn reveal<'fut>( - &self, + &'fut self, ctx: C, record_id: RecordId, ) -> Result<>::Array, Error> @@ -83,7 +86,7 @@ impl, const N: usize> Reveal /// TODO: implement reveal through partial reveal where `left_out` is optional async fn partial_reveal<'fut>( - &self, + &'fut self, ctx: C, record_id: RecordId, left_out: Role, @@ -96,9 +99,11 @@ impl, const N: usize> Reveal // send except to left_out if ctx.role().peer(Direction::Right) != left_out { - ctx.send_channel::<>::Array>(ctx.role().peer(Direction::Right)) - .send(record_id, left) - .await?; + ctx.send_channel::<>::Array>( + ctx.role().peer(Direction::Right), + ) + .send(record_id, left) + .await?; } if ctx.role() == left_out { @@ -119,12 +124,11 @@ impl, const N: usize> Reveal /// to both helpers (right and left) and upon receiving 2 shares from peers it validates that they /// indeed match. #[cfg(feature = "descriptive-gate")] -#[async_trait] impl<'a, F: ExtendableField> Reveal, 1> for MaliciousReplicated { type Output = >::Array; async fn reveal<'fut>( - &self, + &'fut self, ctx: UpgradedMaliciousContext<'a, F>, record_id: RecordId, ) -> Result<>::Array, Error> @@ -162,7 +166,7 @@ impl<'a, F: ExtendableField> Reveal, 1> for Mali } async fn partial_reveal<'fut>( - &self, + &'fut self, ctx: UpgradedMaliciousContext<'a, F>, record_id: RecordId, left_out: Role, @@ -206,6 +210,20 @@ impl<'a, F: ExtendableField> Reveal, 1> for Mali } } +// Workaround for https://github.com/rust-lang/rust/issues/100013. Calling this wrapper function +// instead of `Reveal::reveal` seems to hide the `impl Future` GAT. +pub fn reveal<'fut, C, S>( + ctx: C, + record_id: RecordId, + v: &'fut S, +) -> impl Future> + Send + 'fut +where + C: Context + 'fut, + S: Reveal, +{ + S::reveal(v, ctx, record_id) +} + #[cfg(all(test, unit_test))] mod tests { use std::iter::zip; diff --git a/ipa-core/src/protocol/boolean/comparison.rs b/ipa-core/src/protocol/boolean/comparison.rs index 7407b1ab4..94046be95 100644 --- a/ipa-core/src/protocol/boolean/comparison.rs +++ b/ipa-core/src/protocol/boolean/comparison.rs @@ -5,6 +5,7 @@ use crate::{ error::Error, ff::{Field, PrimeField}, protocol::{ + basics::reveal, boolean::random_bits_generator::RandomBitsGenerator, context::{Context, UpgradedContext}, step::BitOpStep, @@ -82,11 +83,7 @@ where let r = rbg.generate(record_id).await?; // Mask `a` with random `r` and reveal. - let b = F::from_array( - &(r.b_p + a) - .reveal(ctx.narrow(&Step::Reveal), record_id) - .await?, - ); + let b = F::from_array(&reveal(ctx.narrow(&Step::Reveal), record_id, &(r.b_p + a)).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 2f2d3227e..1cf64c945 100644 --- a/ipa-core/src/protocol/boolean/solved_bits.rs +++ b/ipa-core/src/protocol/boolean/solved_bits.rs @@ -7,7 +7,7 @@ use crate::{ error::Error, ff::{Field, PrimeField}, protocol::{ - basics::Reveal, + basics::{reveal, Reveal}, boolean::{ bitwise_less_than_prime::BitwiseLessThanPrime, generate_random_bits::one_random_bit, }, @@ -136,7 +136,8 @@ where let c_b = BitwiseLessThanPrime::less_than_prime(ctx.narrow(&Step::IsPLessThanB), record_id, b_b) .await?; - if F::from_array(&c_b.reveal(ctx.narrow(&Step::RevealC), record_id).await?) == F::ZERO { + + if F::from_array(&reveal(ctx.narrow(&Step::RevealC), record_id, &c_b).await?) == F::ZERO { return Ok(false); } Ok(true) From 08d75206cd71250cff7fdfe15563de5420f9dd64 Mon Sep 17 00:00:00 2001 From: Andy Leiserson Date: Mon, 4 Mar 2024 15:32:28 -0800 Subject: [PATCH 2/5] Use a generic backend for full and partial reveals --- ipa-core/src/protocol/basics/reveal.rs | 135 +++++++++---------------- 1 file changed, 49 insertions(+), 86 deletions(-) diff --git a/ipa-core/src/protocol/basics/reveal.rs b/ipa-core/src/protocol/basics/reveal.rs index f95cdae9c..dfe83e7e7 100644 --- a/ipa-core/src/protocol/basics/reveal.rs +++ b/ipa-core/src/protocol/basics/reveal.rs @@ -1,6 +1,7 @@ use std::future::Future; use embed_doc_image::embed_doc_image; +use futures::TryFutureExt; use crate::{ error::Error, @@ -9,6 +10,7 @@ use crate::{ secret_sharing::{ replicated::semi_honest::AdditiveShare as Replicated, SharedValue, Vectorizable, }, + seq_join::SeqJoin, }; #[cfg(feature = "descriptive-gate")] use crate::{ @@ -21,16 +23,23 @@ use crate::{ /// Trait for reveal protocol to open a shared secret to all helpers inside the MPC ring. 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. + /// Reveal a shared secret to all helpers in the MPC ring. + /// + /// 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. fn reveal<'fut>( &'fut self, ctx: C, record_id: RecordId, ) -> impl Future> + Send + 'fut where - C: 'fut; + C: 'fut, + { + // Passing `left_out = None` guarantees any ok result is `Some`. + self.generic_reveal(ctx, record_id, None) + .map_ok(Option::unwrap) + } /// partial reveal protocol to open a shared secret to all helpers except helper `left_out` inside the MPC ring. fn partial_reveal<'fut>( @@ -39,6 +48,23 @@ pub trait Reveal: Sized { record_id: RecordId, left_out: Role, ) -> impl Future, Error>> + Send + 'fut + where + C: 'fut, + { + self.generic_reveal(ctx, record_id, Some(left_out)) + } + + /// Generic reveal implementation usable for both `reveal` and `partial_reveal`. + /// + /// When `left_out` is `None`, open a shared secret to all helpers in the MPC ring. + /// When `left_out` is `Some`, open a shared secret to all helpers except the helper + /// specified in `left_out`. + fn generic_reveal<'fut>( + &'fut self, + ctx: C, + record_id: RecordId, + left_out: Option, + ) -> impl Future, Error>> + Send + 'fut where C: 'fut; } @@ -60,36 +86,11 @@ impl, const N: usize> Reveal { type Output = >::Array; - async fn reveal<'fut>( - &'fut self, - ctx: C, - record_id: RecordId, - ) -> Result<>::Array, Error> - where - C: 'fut, - { - let left = self.left_arr(); - let right = self.right_arr(); - - ctx.send_channel::<>::Array>(ctx.role().peer(Direction::Right)) - .send(record_id, left) - .await?; - - // Sleep until `helper's left` sends their share - let share: >::Array = ctx - .recv_channel(ctx.role().peer(Direction::Left)) - .receive(record_id) - .await?; - - Ok(share + left + right) - } - - /// TODO: implement reveal through partial reveal where `left_out` is optional - async fn partial_reveal<'fut>( + async fn generic_reveal<'fut>( &'fut self, ctx: C, record_id: RecordId, - left_out: Role, + left_out: Option, ) -> Result>::Array>, Error> where C: 'fut, @@ -97,18 +98,17 @@ impl, const N: usize> Reveal 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::<>::Array>( - ctx.role().peer(Direction::Right), - ) - .send(record_id, left) - .await?; + // send except to excluded helper (if any) + if Some(ctx.role().peer(Direction::Right)) != left_out { + ctx.send_channel::<>::Array>(ctx.role().peer(Direction::Right)) + .send(record_id, left) + .await?; } - if ctx.role() == left_out { + if Some(ctx.role()) == left_out { Ok(None) } else { + // Sleep until `helper's left` sends their share let share: >::Array = ctx .recv_channel(ctx.role().peer(Direction::Left)) .receive(record_id) @@ -127,49 +127,11 @@ impl, const N: usize> Reveal impl<'a, F: ExtendableField> Reveal, 1> for MaliciousReplicated { type Output = >::Array; - async fn reveal<'fut>( + async fn generic_reveal<'fut>( &'fut self, ctx: UpgradedMaliciousContext<'a, F>, record_id: RecordId, - ) -> Result<>::Array, Error> - where - UpgradedMaliciousContext<'a, F>: 'fut, - { - use futures::future::try_join; - - use crate::secret_sharing::replicated::malicious::ThisCodeIsAuthorizedToDowngradeFromMalicious; - - let (left, right) = self.x().access_without_downgrade().as_tuple(); - let left_sender = ctx.send_channel(ctx.role().peer(Direction::Left)); - let left_receiver = ctx.recv_channel::(ctx.role().peer(Direction::Left)); - let right_sender = ctx.send_channel(ctx.role().peer(Direction::Right)); - let right_receiver = ctx.recv_channel::(ctx.role().peer(Direction::Right)); - - // Send share to helpers to the right and left - try_join( - left_sender.send(record_id, right), - right_sender.send(record_id, left), - ) - .await?; - - let (share_from_left, share_from_right) = try_join( - left_receiver.receive(record_id), - right_receiver.receive(record_id), - ) - .await?; - - if share_from_left == share_from_right { - Ok((left + right + share_from_left).into_array()) - } else { - Err(Error::MaliciousRevealFailed) - } - } - - async fn partial_reveal<'fut>( - &'fut self, - ctx: UpgradedMaliciousContext<'a, F>, - record_id: RecordId, - left_out: Role, + left_out: Option, ) -> Result>::Array>, Error> where UpgradedMaliciousContext<'a, F>: 'fut, @@ -186,13 +148,14 @@ impl<'a, F: ExtendableField> Reveal, 1> for Mali // Send share to helpers to the right and left // send except to left_out - if ctx.role().peer(Direction::Left) != left_out { - left_sender.send(record_id, right).await?; - } - if ctx.role().peer(Direction::Right) != left_out { - right_sender.send(record_id, left).await?; - } - if ctx.role() == left_out { + let send_left_fut = (Some(ctx.role().peer(Direction::Left)) != left_out) + .then(|| left_sender.send(record_id, right)); + let send_right_fut = (Some(ctx.role().peer(Direction::Right)) != left_out) + .then(|| right_sender.send(record_id, left)); + ctx.parallel_join(send_left_fut.into_iter().chain(send_right_fut)) + .await?; + + if Some(ctx.role()) == left_out { Ok(None) } else { let (share_from_left, share_from_right) = try_join( From fdb9dc213b34fa1a40ea41f17f667f906cd2a22c Mon Sep 17 00:00:00 2001 From: Andy Leiserson Date: Tue, 5 Mar 2024 19:14:14 -0800 Subject: [PATCH 3/5] Use test_executor for reveal error tests --- ipa-core/src/protocol/basics/reveal.rs | 125 +++++++++++++------------ 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/ipa-core/src/protocol/basics/reveal.rs b/ipa-core/src/protocol/basics/reveal.rs index dfe83e7e7..16d678d01 100644 --- a/ipa-core/src/protocol/basics/reveal.rs +++ b/ipa-core/src/protocol/basics/reveal.rs @@ -212,6 +212,7 @@ mod tests { }, IntoShares, SharedValue, }, + test_executor::run, test_fixture::{join3v, Runner, TestWorld}, }; @@ -382,76 +383,76 @@ mod tests { Ok(()) } - #[tokio::test] - pub async fn malicious_validation_fail() -> Result<(), Error> { - let mut rng = thread_rng(); - let world = TestWorld::default(); - let sh_ctx = world.malicious_contexts(); - let v = sh_ctx.map(UpgradableContext::validator); - let m_ctx: [_; 3] = v - .iter() - .map(|v| v.context().set_total_records(1)) - .collect::>() - .try_into() - .unwrap(); - - let record_id = RecordId::from(0); - let input: Fp31 = rng.gen(); + #[test] + pub fn malicious_validation_fail() { + run(|| async { + let mut rng = thread_rng(); + let world = TestWorld::default(); + let sh_ctx = world.malicious_contexts(); + let v = sh_ctx.map(UpgradableContext::validator); + let m_ctx: [_; 3] = v + .iter() + .map(|v| v.context().set_total_records(1)) + .collect::>() + .try_into() + .unwrap(); - let m_shares = join3v( - zip(m_ctx.iter(), input.share_with(&mut rng)) - .map(|(m_ctx, share)| async { m_ctx.upgrade(share).await }), - ) - .await; - let result = try_join3( - m_shares[0].reveal(m_ctx[0].clone(), record_id), - m_shares[1].reveal(m_ctx[1].clone(), record_id), - reveal_with_additive_attack( - m_ctx[2].clone(), - record_id, - &m_shares[2], - false, - Fp31::ONE, - ), - ) - .await; + let record_id = RecordId::from(0); + let input: Fp31 = rng.gen(); - assert!(matches!(result, Err(Error::MaliciousRevealFailed))); + let m_shares = join3v( + zip(m_ctx.iter(), input.share_with(&mut rng)) + .map(|(m_ctx, share)| async { m_ctx.upgrade(share).await }), + ) + .await; + let result = try_join3( + m_shares[0].reveal(m_ctx[0].clone(), record_id), + m_shares[1].reveal(m_ctx[1].clone(), record_id), + reveal_with_additive_attack( + m_ctx[2].clone(), + record_id, + &m_shares[2], + false, + Fp31::ONE, + ), + ) + .await; - Ok(()) + assert!(matches!(result, Err(Error::MaliciousRevealFailed))); + }) } - #[tokio::test] - pub async fn malicious_partial_validation_fail() -> Result<(), Error> { - let mut rng = thread_rng(); - let world = TestWorld::default(); - let sh_ctx = world.malicious_contexts(); - let v = sh_ctx.map(UpgradableContext::validator); - let m_ctx: [_; 3] = v - .iter() - .map(|v| v.context().set_total_records(1)) - .collect::>() - .try_into() - .unwrap(); - - let record_id = RecordId::from(0); - let input: Fp31 = rng.gen(); + #[test] + pub fn malicious_partial_validation_fail() { + run(|| async { + let mut rng = thread_rng(); + let world = TestWorld::default(); + let sh_ctx = world.malicious_contexts(); + let v = sh_ctx.map(UpgradableContext::validator); + let m_ctx: [_; 3] = v + .iter() + .map(|v| v.context().set_total_records(1)) + .collect::>() + .try_into() + .unwrap(); - let m_shares = join3v( - zip(m_ctx.iter(), input.share_with(&mut rng)) - .map(|(m_ctx, share)| async { m_ctx.upgrade(share).await }), - ) - .await; - let result = try_join3( - m_shares[0].partial_reveal(m_ctx[0].clone(), record_id, Role::H3), - m_shares[1].partial_reveal(m_ctx[1].clone(), record_id, Role::H3), - reveal_with_additive_attack(m_ctx[2].clone(), record_id, &m_shares[2], true, Fp31::ONE), - ) - .await; + let record_id = RecordId::from(0); + let input: Fp31 = rng.gen(); - assert!(matches!(result, Err(Error::MaliciousRevealFailed))); + let m_shares = join3v( + zip(m_ctx.iter(), input.share_with(&mut rng)) + .map(|(m_ctx, share)| async { m_ctx.upgrade(share).await }), + ) + .await; + let result = try_join3( + m_shares[0].partial_reveal(m_ctx[0].clone(), record_id, Role::H3), + m_shares[1].partial_reveal(m_ctx[1].clone(), record_id, Role::H3), + reveal_with_additive_attack(m_ctx[2].clone(), record_id, &m_shares[2], true, Fp31::ONE), + ) + .await; - Ok(()) + assert!(matches!(result, Err(Error::MaliciousRevealFailed))); + }) } pub async fn reveal_with_additive_attack( From fa59f74bbfde084f9d5c9d2207a8f78a2d705ed9 Mon Sep 17 00:00:00 2001 From: Andy Leiserson Date: Fri, 8 Mar 2024 14:21:15 -0800 Subject: [PATCH 4/5] MaybeFuture --- ipa-core/src/helpers/futures.rs | 44 +++++++++++++++++++++++++++++++++ ipa-core/src/helpers/mod.rs | 2 ++ 2 files changed, 46 insertions(+) create mode 100644 ipa-core/src/helpers/futures.rs diff --git a/ipa-core/src/helpers/futures.rs b/ipa-core/src/helpers/futures.rs new file mode 100644 index 000000000..70bcf4ca3 --- /dev/null +++ b/ipa-core/src/helpers/futures.rs @@ -0,0 +1,44 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use pin_project::pin_project; + +#[pin_project(project = MaybeFutureProj)] +pub enum MaybeFuture { + Future(#[pin] Fut), + Value(Option), +} + +impl Future for MaybeFuture { + type Output = Fut::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.project() { + MaybeFutureProj::Future(fut) => fut.poll(cx), + MaybeFutureProj::Value(val) => Poll::Ready(val.take().unwrap()), + } + } +} + +impl MaybeFuture { + pub fn future(fut: Fut) -> Self { + MaybeFuture::Future(fut) + } + + pub fn value(val: Fut::Output) -> Self { + MaybeFuture::Value(Some(val)) + } +} + +impl>, E> MaybeFuture { + pub fn future_or_ok Fut>(condition: bool, f: F) -> Self { + if condition { + MaybeFuture::Future(f()) + } else { + MaybeFuture::Value(Some(Ok(()))) + } + } +} diff --git a/ipa-core/src/helpers/mod.rs b/ipa-core/src/helpers/mod.rs index b2b15f305..4544c2795 100644 --- a/ipa-core/src/helpers/mod.rs +++ b/ipa-core/src/helpers/mod.rs @@ -8,6 +8,7 @@ use generic_array::GenericArray; mod buffers; mod error; +mod futures; mod gateway; pub(crate) mod prss_protocol; mod transport; @@ -18,6 +19,7 @@ use std::ops::{Index, IndexMut}; #[cfg(test)] pub use buffers::OrderingSender; pub use error::{Error, Result}; +pub use futures::MaybeFuture; #[cfg(feature = "stall-detection")] mod gateway_exports { From 096179e99ad8b65a70e9a31d2cf0a6cd32b47621 Mon Sep 17 00:00:00 2001 From: Andy Leiserson Date: Fri, 8 Mar 2024 14:44:13 -0800 Subject: [PATCH 5/5] PR feedback --- ipa-core/src/protocol/basics/reveal.rs | 79 ++++++++++++++------------ 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/ipa-core/src/protocol/basics/reveal.rs b/ipa-core/src/protocol/basics/reveal.rs index 16d678d01..4b9c21da5 100644 --- a/ipa-core/src/protocol/basics/reveal.rs +++ b/ipa-core/src/protocol/basics/reveal.rs @@ -10,10 +10,10 @@ use crate::{ secret_sharing::{ replicated::semi_honest::AdditiveShare as Replicated, SharedValue, Vectorizable, }, - seq_join::SeqJoin, }; #[cfg(feature = "descriptive-gate")] use crate::{ + helpers::MaybeFuture, protocol::context::UpgradedMaliciousContext, secret_sharing::replicated::malicious::{ AdditiveShare as MaliciousReplicated, ExtendableField, @@ -36,34 +36,34 @@ pub trait Reveal: Sized { where C: 'fut, { - // Passing `left_out = None` guarantees any ok result is `Some`. + // Passing `excluded = None` guarantees any ok result is `Some`. self.generic_reveal(ctx, record_id, None) .map_ok(Option::unwrap) } - /// partial reveal protocol to open a shared secret to all helpers except helper `left_out` inside the MPC ring. + /// Partial reveal protocol to open a shared secret to all helpers except helper `excluded` inside the MPC ring. fn partial_reveal<'fut>( &'fut self, ctx: C, record_id: RecordId, - left_out: Role, + excluded: Role, ) -> impl Future, Error>> + Send + 'fut where C: 'fut, { - self.generic_reveal(ctx, record_id, Some(left_out)) + self.generic_reveal(ctx, record_id, Some(excluded)) } /// Generic reveal implementation usable for both `reveal` and `partial_reveal`. /// - /// When `left_out` is `None`, open a shared secret to all helpers in the MPC ring. - /// When `left_out` is `Some`, open a shared secret to all helpers except the helper - /// specified in `left_out`. + /// When `excluded` is `None`, open a shared secret to all helpers in the MPC ring. + /// When `excluded` is `Some`, open a shared secret to all helpers except the helper + /// specified in `excluded`. fn generic_reveal<'fut>( &'fut self, ctx: C, record_id: RecordId, - left_out: Option, + excluded: Option, ) -> impl Future, Error>> + Send + 'fut where C: 'fut; @@ -90,7 +90,7 @@ impl, const N: usize> Reveal &'fut self, ctx: C, record_id: RecordId, - left_out: Option, + excluded: Option, ) -> Result>::Array>, Error> where C: 'fut, @@ -98,14 +98,14 @@ impl, const N: usize> Reveal let left = self.left_arr(); let right = self.right_arr(); - // send except to excluded helper (if any) - if Some(ctx.role().peer(Direction::Right)) != left_out { + // Send shares, unless the target helper is excluded + if Some(ctx.role().peer(Direction::Right)) != excluded { ctx.send_channel::<>::Array>(ctx.role().peer(Direction::Right)) .send(record_id, left) .await?; } - if Some(ctx.role()) == left_out { + if Some(ctx.role()) == excluded { Ok(None) } else { // Sleep until `helper's left` sends their share @@ -131,7 +131,7 @@ impl<'a, F: ExtendableField> Reveal, 1> for Mali &'fut self, ctx: UpgradedMaliciousContext<'a, F>, record_id: RecordId, - left_out: Option, + excluded: Option, ) -> Result>::Array>, Error> where UpgradedMaliciousContext<'a, F>: 'fut, @@ -146,16 +146,19 @@ impl<'a, F: ExtendableField> Reveal, 1> for Mali let right_sender = ctx.send_channel(ctx.role().peer(Direction::Right)); let right_receiver = ctx.recv_channel::(ctx.role().peer(Direction::Right)); - // Send share to helpers to the right and left - // send except to left_out - let send_left_fut = (Some(ctx.role().peer(Direction::Left)) != left_out) - .then(|| left_sender.send(record_id, right)); - let send_right_fut = (Some(ctx.role().peer(Direction::Right)) != left_out) - .then(|| right_sender.send(record_id, left)); - ctx.parallel_join(send_left_fut.into_iter().chain(send_right_fut)) - .await?; + // Send shares to the left and right helpers, unless excluded. + let send_left_fut = + MaybeFuture::future_or_ok(Some(ctx.role().peer(Direction::Left)) != excluded, || { + left_sender.send(record_id, right) + }); + + let send_right_fut = + MaybeFuture::future_or_ok(Some(ctx.role().peer(Direction::Right)) != excluded, || { + right_sender.send(record_id, left) + }); + try_join(send_left_fut, send_right_fut).await?; - if Some(ctx.role()) == left_out { + if Some(ctx.role()) == excluded { Ok(None) } else { let (share_from_left, share_from_right) = try_join( @@ -249,12 +252,12 @@ mod tests { let mut rng = thread_rng(); let world = TestWorld::default(); - for &left_out in Role::all() { + for &excluded in Role::all() { let input = rng.gen::(); let results = world .semi_honest(input, |ctx, share| async move { share - .partial_reveal(ctx.set_total_records(1), RecordId::from(0), left_out) + .partial_reveal(ctx.set_total_records(1), RecordId::from(0), excluded) .await .unwrap() .map(|revealed| TestField::from_array(&revealed)) @@ -262,7 +265,7 @@ mod tests { .await; for &helper in Role::all() { - if helper == left_out { + if helper == excluded { assert_eq!(None, results[helper]); } else { assert_eq!(Some(input), results[helper]); @@ -342,7 +345,7 @@ mod tests { let mut rng = thread_rng(); let world = TestWorld::default(); - for &left_out in Role::all() { + for &excluded in Role::all() { let sh_ctx = world.malicious_contexts(); let v = sh_ctx.map(UpgradableContext::validator); let m_ctx: [_; 3] = v @@ -364,7 +367,7 @@ mod tests { let results = join_all(zip(m_ctx.clone().into_iter(), m_shares).map( |(m_ctx, m_share)| async move { m_share - .partial_reveal(m_ctx, record_id, left_out) + .partial_reveal(m_ctx, record_id, excluded) .await .unwrap() }, @@ -372,7 +375,7 @@ mod tests { .await; for &helper in Role::all() { - if helper == left_out { + if helper == excluded { assert_eq!(None, results[helper]); } else { assert_eq!(Some(input.into_array()), results[helper]); @@ -419,11 +422,11 @@ mod tests { .await; assert!(matches!(result, Err(Error::MaliciousRevealFailed))); - }) + }); } #[test] - pub fn malicious_partial_validation_fail() { + pub fn malicious_partial_validation_fail() { run(|| async { let mut rng = thread_rng(); let world = TestWorld::default(); @@ -447,19 +450,25 @@ mod tests { let result = try_join3( m_shares[0].partial_reveal(m_ctx[0].clone(), record_id, Role::H3), m_shares[1].partial_reveal(m_ctx[1].clone(), record_id, Role::H3), - reveal_with_additive_attack(m_ctx[2].clone(), record_id, &m_shares[2], true, Fp31::ONE), + reveal_with_additive_attack( + m_ctx[2].clone(), + record_id, + &m_shares[2], + true, + Fp31::ONE, + ), ) .await; assert!(matches!(result, Err(Error::MaliciousRevealFailed))); - }) + }); } pub async fn reveal_with_additive_attack( ctx: UpgradedMaliciousContext<'_, F>, record_id: RecordId, input: &MaliciousReplicated, - left_out: bool, + excluded: bool, additive_error: F, ) -> Result, Error> { let (left, right) = input.x().access_without_downgrade().as_tuple(); @@ -475,7 +484,7 @@ mod tests { ) .await?; - if left_out { + if excluded { Ok(None) } else { let (share_from_left, _share_from_right): (F, F) =