diff --git a/src/protocol/boolean/prefix_or.rs b/src/protocol/boolean/prefix_or.rs index f99fcf949..13430e604 100644 --- a/src/protocol/boolean/prefix_or.rs +++ b/src/protocol/boolean/prefix_or.rs @@ -330,13 +330,17 @@ mod tests { test_fixture::{make_contexts, make_world, share, validate_and_reconstruct, TestWorld}, }; use futures::future::try_join_all; + use rand::distributions::{Distribution, Standard}; use rand::{rngs::mock::StepRng, Rng}; use std::iter::zip; const BITS: [usize; 2] = [16, 32]; const TEST_TRIES: usize = 16; - async fn prefix_or(input: &[F]) -> Result, BoxError> { + async fn prefix_or(input: &[F]) -> Result, BoxError> + where + Standard: Distribution, + { let world: TestWorld = make_world(QueryId); let ctx = make_contexts::(&world); let mut rand = StepRng::new(1, 1); diff --git a/src/protocol/check_zero.rs b/src/protocol/check_zero.rs index 239699248..913a062b0 100644 --- a/src/protocol/check_zero.rs +++ b/src/protocol/check_zero.rs @@ -1,8 +1,9 @@ use crate::protocol::mul::SecureMul; +use crate::protocol::reveal::Reveal; use crate::{ error::BoxError, ff::Field, - protocol::{context::ProtocolContext, reveal::reveal, RecordId}, + protocol::{context::ProtocolContext, RecordId}, secret_sharing::Replicated, }; use serde::{Deserialize, Serialize}; @@ -72,7 +73,10 @@ pub async fn check_zero( .narrow(&Step::MultiplyWithR) .multiply(record_id, r_sharing, v) .await?; - let rv = reveal(ctx.narrow(&Step::RevealR), record_id, rv_share).await?; + let rv = ctx + .narrow(&Step::RevealR) + .reveal(record_id, rv_share) + .await?; Ok(rv == F::ZERO) } diff --git a/src/protocol/context.rs b/src/protocol/context.rs index 4c1423873..199121983 100644 --- a/src/protocol/context.rs +++ b/src/protocol/context.rs @@ -19,7 +19,7 @@ use crate::secret_sharing::{MaliciousReplicated, Replicated, SecretSharing}; /// Context used by each helper to perform computation. Currently they need access to shared /// randomness generator (see `Participant`) and communication trait to send messages to each other. #[derive(Clone, Debug)] -pub struct ProtocolContext<'a, S: SecretSharing, F> { +pub struct ProtocolContext<'a, S, F> { role: Role, step: Step, prss: &'a PrssEndpoint, diff --git a/src/protocol/malicious.rs b/src/protocol/malicious.rs index 4e5ab196c..171721f5a 100644 --- a/src/protocol/malicious.rs +++ b/src/protocol/malicious.rs @@ -1,10 +1,11 @@ +use crate::protocol::reveal::Reveal; use crate::{ error::{BoxError, Error}, ff::Field, helpers::Direction, protocol::{ - check_zero::check_zero, context::ProtocolContext, prss::IndexedSharedRandomness, - reveal::reveal, RecordId, RECORD_0, RECORD_1, RECORD_2, + check_zero::check_zero, context::ProtocolContext, prss::IndexedSharedRandomness, RecordId, + RECORD_0, RECORD_1, RECORD_2, }, secret_sharing::{MaliciousReplicated, Replicated}, }; @@ -183,7 +184,10 @@ impl SecurityValidator { let w_share = Replicated::new(w_left, state.w); // This should probably be done in parallel with the futures above - let r = reveal(ctx.narrow(&Step::RevealR), RECORD_0, self.r_share).await?; + let r = ctx + .narrow(&Step::RevealR) + .reveal(RECORD_0, self.r_share) + .await?; let t = u_share - (w_share * r); let is_valid = check_zero(ctx.narrow(&Step::CheckZero), RECORD_0, t).await?; diff --git a/src/protocol/modulus_conversion/convert_shares.rs b/src/protocol/modulus_conversion/convert_shares.rs index 9ab9b77a4..e21541dbf 100644 --- a/src/protocol/modulus_conversion/convert_shares.rs +++ b/src/protocol/modulus_conversion/convert_shares.rs @@ -2,12 +2,12 @@ use crate::{ error::BoxError, ff::{Field, Fp2}, protocol::{ - context::ProtocolContext, modulus_conversion::double_random::DoubleRandom, - reveal::reveal_malicious, RecordId, + context::ProtocolContext, modulus_conversion::double_random::DoubleRandom, RecordId, }, secret_sharing::Replicated, }; +use crate::protocol::reveal::Reveal; use futures::future::{try_join, try_join_all}; use std::iter::{repeat, zip}; @@ -78,7 +78,8 @@ impl ConvertShares { let input_xor_r = input + r_binary; let (r_big_field, revealed_output) = try_join( DoubleRandom::execute(ctx.narrow(&Step::DoubleRandom), record_id, r_binary), - reveal_malicious::(ctx.narrow(&Step::BinaryReveal), record_id, input_xor_r), + ctx.narrow(&Step::BinaryReveal) + .reveal(record_id, input_xor_r), ) .await?; diff --git a/src/protocol/reveal.rs b/src/protocol/reveal.rs deleted file mode 100644 index 9c31cd3fe..000000000 --- a/src/protocol/reveal.rs +++ /dev/null @@ -1,211 +0,0 @@ -use std::iter::{repeat, zip}; - -use crate::ff::Field; -use crate::protocol::context::ProtocolContext; -use crate::secret_sharing::Replicated; -use crate::{ - error::{BoxError, Error}, - helpers::Direction, - protocol::RecordId, -}; -use embed_doc_image::embed_doc_image; -use futures::future::{try_join, try_join_all}; - -/// This implements a reveal algorithm -/// For simplicity, we consider a simple revealing in which each `P_i` sends `\[a\]_i` to `P_i+1` after which -/// each helper has all three shares and can reconstruct `a` -/// -/// Input: Each helpers know their own secret shares -/// Output: At the end of the protocol, all 3 helpers know a revealed (or opened) secret -/// -/// Steps -/// ![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. -#[embed_doc_image("reveal", "images/reveal.png")] -#[allow(dead_code)] -pub async fn reveal( - ctx: ProtocolContext<'_, Replicated, F>, - record_id: RecordId, - input: Replicated, -) -> Result { - let channel = ctx.mesh(); - - channel - .send(ctx.role().peer(Direction::Right), record_id, input.left()) - .await?; - - // Sleep until `helper's left` sends their share - let share = channel - .receive(ctx.role().peer(Direction::Left), record_id) - .await?; - - Ok(input.left() + input.right() + share) -} - -#[allow(dead_code)] -pub async fn reveal_malicious( - ctx: ProtocolContext<'_, Replicated, F>, - record_id: RecordId, - input: Replicated, -) -> Result { - let channel = ctx.mesh(); - - // Send share to helpers to the right and left - try_join( - channel.send(ctx.role().peer(Direction::Left), record_id, input.right()), - channel.send(ctx.role().peer(Direction::Right), record_id, input.left()), - ) - .await?; - - let (share_from_left, share_from_right) = try_join( - channel.receive(ctx.role().peer(Direction::Left), record_id), - channel.receive(ctx.role().peer(Direction::Right), record_id), - ) - .await?; - - if share_from_left == share_from_right { - Ok(input.left() + input.right() + share_from_left) - } else { - Err(Error::MaliciousRevealFailed) - } -} - -/// Given a vector containing secret shares of a permutation, this returns a revealed permutation. -/// This executes `reveal` protocol on each row of the vector and then constructs a `Permutation` object -/// from the revealed rows. -pub async fn reveal_permutation( - ctx: ProtocolContext<'_, Replicated, F>, - permutation: &[Replicated], -) -> Result, BoxError> { - let revealed_permutation = try_join_all(zip(repeat(ctx), permutation).enumerate().map( - |(index, (ctx, input))| async move { - let reveal_value = reveal(ctx, RecordId::from(index), *input).await; - - // safety: we wouldn't use fields larger than 64 bits and there are checks that enforce it - // in the field module - reveal_value.map(|val| val.as_u128().try_into().unwrap()) - }, - )) - .await?; - - Ok(revealed_permutation) -} - -#[cfg(test)] -mod tests { - use futures::future::{try_join, try_join_all}; - use proptest::prelude::Rng; - use tokio::try_join; - - use crate::error::Error; - use crate::{ - error::BoxError, - ff::{Field, Fp31}, - helpers::Direction, - protocol::{ - context::ProtocolContext, - reveal::{reveal, reveal_malicious}, - QueryId, RecordId, - }, - secret_sharing::Replicated, - test_fixture::{make_contexts, make_world, share, TestWorld}, - }; - - #[tokio::test] - pub async fn simple() -> Result<(), BoxError> { - let mut rng = rand::thread_rng(); - let world: TestWorld = make_world(QueryId); - let ctx = make_contexts::(&world); - - for i in 0..10_u32 { - let input = rng.gen::(); - let share = share(input, &mut rng); - let record_id = RecordId::from(i); - let results = try_join_all(vec![ - reveal(ctx[0].clone(), record_id, share[0]), - reveal(ctx[1].clone(), record_id, share[1]), - reveal(ctx[2].clone(), record_id, share[2]), - ]) - .await?; - - assert_eq!(input, results[0]); - assert_eq!(input, results[1]); - assert_eq!(input, results[2]); - } - Ok(()) - } - - #[tokio::test] - pub async fn malicious() -> Result<(), BoxError> { - let mut rng = rand::thread_rng(); - let world: TestWorld = make_world(QueryId); - let ctx = make_contexts::(&world); - - for i in 0..10_u32 { - let input = rng.gen::(); - let share = share(input, &mut rng); - let record_id = RecordId::from(i); - let results = try_join_all(vec![ - reveal_malicious(ctx[0].clone(), record_id, share[0]), - reveal_malicious(ctx[1].clone(), record_id, share[1]), - reveal_malicious(ctx[2].clone(), record_id, share[2]), - ]) - .await?; - - assert_eq!(input, results[0]); - assert_eq!(input, results[1]); - assert_eq!(input, results[2]); - } - Ok(()) - } - - #[tokio::test] - pub async fn malicious_validation_fail() -> Result<(), BoxError> { - let mut rng = rand::thread_rng(); - let world: TestWorld = make_world(QueryId); - let ctx = make_contexts::(&world); - - for i in 0..10_u32 { - let input = rng.gen::(); - let share = share(input, &mut rng); - let record_id = RecordId::from(i); - let result = try_join!( - reveal_malicious(ctx[0].clone(), record_id, share[0]), - reveal_malicious(ctx[1].clone(), record_id, share[1]), - reveal_with_additive_attack(ctx[2].clone(), record_id, share[2], Fp31::ONE), - ); - - assert!(matches!(result, Err(Error::MaliciousRevealFailed))); - } - Ok(()) - } - - pub async fn reveal_with_additive_attack( - ctx: ProtocolContext<'_, Replicated, F>, - record_id: RecordId, - input: Replicated, - additive_error: F, - ) -> Result { - let channel = ctx.mesh(); - - // Send share to helpers to the right and left - try_join( - channel.send(ctx.role().peer(Direction::Left), record_id, input.right()), - channel.send( - ctx.role().peer(Direction::Right), - record_id, - input.left() + additive_error, - ), - ) - .await?; - - let (share_from_left, _share_from_right): (F, F) = try_join( - channel.receive(ctx.role().peer(Direction::Left), record_id), - channel.receive(ctx.role().peer(Direction::Right), record_id), - ) - .await?; - - Ok(input.left() + input.right() + share_from_left) - } -} diff --git a/src/protocol/reveal/mod.rs b/src/protocol/reveal/mod.rs new file mode 100644 index 000000000..0b9cfad5a --- /dev/null +++ b/src/protocol/reveal/mod.rs @@ -0,0 +1,258 @@ +use std::iter::{repeat, zip}; + +use crate::ff::Field; +use crate::protocol::context::ProtocolContext; +use crate::secret_sharing::{MaliciousReplicated, Replicated, SecretSharing}; +use crate::{ + error::{BoxError, Error}, + helpers::Direction, + protocol::RecordId, +}; +use async_trait::async_trait; +use embed_doc_image::embed_doc_image; +use futures::future::{try_join, try_join_all}; + +/// Trait for reveal protocol to open a shared secret to all helpers inside the MPC ring. +#[async_trait] +pub trait Reveal { + /// Secret sharing type that reveal implementation works with. Note that field type does not + /// matter - implementations must be able to reveal secret value from any field. + type Share: SecretSharing; + + /// 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(self, record_id: RecordId, input: Self::Share) + -> Result; +} + +/// This implements a semi-honest reveal algorithm for replicated secret sharing. +/// For simplicity, we consider a simple revealing in which each `P_i` sends `\[a\]_i` to `P_i+1` after which +/// each helper has all three shares and can reconstruct `a` +/// +/// Input: Each helpers know their own secret shares +/// Output: At the end of the protocol, all 3 helpers know a revealed (or opened) secret +/// +/// Steps +/// ![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 Reveal for ProtocolContext<'_, Replicated, G> { + type Share = Replicated; + + async fn reveal( + self, + record_id: RecordId, + input: Self::Share, + ) -> Result { + let (role, channel) = (self.role(), self.mesh()); + let (left, right) = input.as_tuple(); + + channel + .send(role.peer(Direction::Right), record_id, left) + .await?; + + // Sleep until `helper's left` sends their share + let share = channel + .receive(role.peer(Direction::Left), record_id) + .await?; + + Ok(left + right + share) + } +} + +/// This implements the malicious reveal protocol over replicated secret sharings. +/// It works similarly to semi-honest reveal, the key difference is that each helper sends its share +/// to both helpers (right and left) and upon receiving 2 shares from peers it validates that they +/// indeed match. +#[async_trait] +impl Reveal for ProtocolContext<'_, MaliciousReplicated, G> { + type Share = MaliciousReplicated; + + async fn reveal( + self, + record_id: RecordId, + input: Self::Share, + ) -> Result { + let (role, channel) = (self.role(), self.mesh()); + let (left, right) = input.x().as_tuple(); + + // Send share to helpers to the right and left + try_join( + channel.send(role.peer(Direction::Left), record_id, right), + channel.send(role.peer(Direction::Right), record_id, left), + ) + .await?; + + let (share_from_left, share_from_right) = try_join( + channel.receive(role.peer(Direction::Left), record_id), + channel.receive(role.peer(Direction::Right), record_id), + ) + .await?; + + if share_from_left == share_from_right { + Ok(left + right + share_from_left) + } else { + Err(Error::MaliciousRevealFailed) + } + } +} + +/// Given a vector containing secret shares of a permutation, this returns a revealed permutation. +/// This executes `reveal` protocol on each row of the vector and then constructs a `Permutation` object +/// from the revealed rows. +pub async fn reveal_permutation( + ctx: ProtocolContext<'_, Replicated, F>, + permutation: &[Replicated], +) -> Result, BoxError> { + let revealed_permutation = try_join_all(zip(repeat(ctx), permutation).enumerate().map( + |(index, (ctx, input))| async move { + let reveal_value = ctx.reveal(RecordId::from(index), *input).await; + + // safety: we wouldn't use fields larger than 64 bits and there are checks that enforce it + // in the field module + reveal_value.map(|val| val.as_u128().try_into().unwrap()) + }, + )) + .await?; + + Ok(revealed_permutation) +} + +#[cfg(test)] +mod tests { + use futures::future::{try_join, try_join_all}; + use proptest::prelude::Rng; + use tokio::try_join; + + use crate::protocol::malicious::SecurityValidator; + use crate::{ + error::BoxError, + error::Error, + ff::{Field, Fp31}, + helpers::Direction, + protocol::reveal::Reveal, + protocol::{context::ProtocolContext, QueryId, RecordId}, + secret_sharing::MaliciousReplicated, + test_fixture::{make_contexts, make_world, share, share_malicious, TestWorld}, + }; + + #[tokio::test] + pub async fn simple() -> Result<(), BoxError> { + let mut rng = rand::thread_rng(); + let world: TestWorld = make_world(QueryId); + let ctx = make_contexts::(&world); + + for i in 0..10_u32 { + let secret = rng.gen::(); + let input = Fp31::from(secret); + let share = share(input, &mut rng); + let record_id = RecordId::from(i); + let results = try_join_all(vec![ + ctx[0].clone().reveal(record_id, share[0]), + ctx[1].clone().reveal(record_id, share[1]), + ctx[2].clone().reveal(record_id, share[2]), + ]) + .await?; + + assert_eq!(input, results[0]); + assert_eq!(input, results[1]); + assert_eq!(input, results[2]); + } + Ok(()) + } + + #[tokio::test] + pub async fn malicious() -> Result<(), BoxError> { + let mut rng = rand::thread_rng(); + let world: TestWorld = make_world(QueryId); + let ctx = make_malicious_contexts::(&world); + + for i in 0..10_u32 { + let secret = rng.gen::(); + let input = Fp31::from(secret); + // r*x value is not used inside malicious reveal, so it can be set to any value + let share = share_malicious(input, &mut rng); + + let record_id = RecordId::from(i); + let results = try_join_all(vec![ + ctx[0].clone().reveal(record_id, share[0]), + ctx[1].clone().reveal(record_id, share[1]), + ctx[2].clone().reveal(record_id, share[2]), + ]) + .await?; + + assert_eq!(input, results[0]); + assert_eq!(input, results[1]); + assert_eq!(input, results[2]); + } + Ok(()) + } + + #[tokio::test] + pub async fn malicious_validation_fail() -> Result<(), BoxError> { + let mut rng = rand::thread_rng(); + let world: TestWorld = make_world(QueryId); + let ctx = make_malicious_contexts(&world); + + for i in 0..10_u32 { + let secret = rng.gen::(); + let input = Fp31::from(secret); + // r*x value is not used inside malicious reveal, so it can be set to any value + let share = share_malicious(input, &mut rng); + let record_id = RecordId::from(i); + let result = try_join!( + ctx[0].clone().reveal(record_id, share[0]), + ctx[1].clone().reveal(record_id, share[1]), + reveal_with_additive_attack(ctx[2].clone(), record_id, share[2], Fp31::ONE), + ); + + assert!(matches!(result, Err(Error::MaliciousRevealFailed))); + } + Ok(()) + } + + pub async fn reveal_with_additive_attack( + ctx: ProtocolContext<'_, MaliciousReplicated, F>, + record_id: RecordId, + input: MaliciousReplicated, + additive_error: F, + ) -> Result { + let channel = ctx.mesh(); + let (left, right) = input.x().as_tuple(); + + // Send share to helpers to the right and left + try_join( + channel.send(ctx.role().peer(Direction::Left), record_id, right), + channel.send( + ctx.role().peer(Direction::Right), + record_id, + left + additive_error, + ), + ) + .await?; + + let (share_from_left, _share_from_right): (F, F) = try_join( + channel.receive(ctx.role().peer(Direction::Left), record_id), + channel.receive(ctx.role().peer(Direction::Right), record_id), + ) + .await?; + + Ok(left + right + share_from_left) + } + + /// Creates malicious protocol contexts for 3 helpers. We drop security validator because + /// it is not used + fn make_malicious_contexts( + test_world: &TestWorld, + ) -> [ProtocolContext<'_, MaliciousReplicated, F>; 3] { + make_contexts(test_world).map(|ctx| { + let v = SecurityValidator::new(ctx.narrow("MaliciousValidate")); + let acc = v.accumulator(); + + ctx.upgrade_to_malicious(acc) + }) + } +} diff --git a/src/test_fixture/mod.rs b/src/test_fixture/mod.rs index 6e33ab254..e93af24fd 100644 --- a/src/test_fixture/mod.rs +++ b/src/test_fixture/mod.rs @@ -15,7 +15,7 @@ use rand::prelude::Distribution; use rand::rngs::mock::StepRng; use rand::thread_rng; -pub use sharing::{share, validate_and_reconstruct, validate_list_of_shares}; +pub use sharing::{share, share_malicious, validate_and_reconstruct, validate_list_of_shares}; pub use world::{ make as make_world, make_with_config as make_world_with_config, TestWorld, TestWorldConfig, }; diff --git a/src/test_fixture/sharing.rs b/src/test_fixture/sharing.rs index 0e62a67d4..7c5951940 100644 --- a/src/test_fixture/sharing.rs +++ b/src/test_fixture/sharing.rs @@ -1,5 +1,5 @@ use crate::ff::Field; -use crate::secret_sharing::Replicated; +use crate::secret_sharing::{MaliciousReplicated, Replicated}; use rand::{ distributions::{Distribution, Standard}, Rng, RngCore, @@ -23,6 +23,24 @@ where ] } +/// Shares `input` into 3 maliciously secure replicated secret shares using the provided `rng` implementation +/// +#[allow(clippy::missing_panics_doc)] +pub fn share_malicious(x: F, rng: &mut R) -> [MaliciousReplicated; 3] +where + Standard: Distribution, +{ + let rx = rng.gen::() * x; + share(x, rng) + // TODO: array::zip/each_ref when stable + .iter() + .zip(share(rx, rng)) + .map(|(x, rx)| MaliciousReplicated::new(*x, rx)) + .collect::>() + .try_into() + .unwrap() +} + /// Validates correctness of the secret sharing scheme. /// /// # Panics