From 0cd9ee7e02d964fbdd7ffb63da0eaed1ab946510 Mon Sep 17 00:00:00 2001 From: Alex Koshelev Date: Thu, 10 Nov 2022 10:19:08 -0800 Subject: [PATCH 1/3] Reveal trait Another step to make sort protocol support both semi-honest and malicious security. In semi-honest setting, `CheckZero` and `ConvertShares` will use semi-honest reveal with a cost of 1 multiplication. Malicious reveal currently has a cost of 2 multiplications and will be used only for malicious setting. This change leverages recently stabilized GAT (https://blog.rust-lang.org/2022/10/28/gats-stabilization.html). If we like this implementation, `SecureMul` will be changed accordingly.abilized GAT (https://blog.rust-lang.org/2022/10/28/gats-stabilization.html). If we like this implementation, `SecureMul` will be changed accordingly. --- src/protocol/check_zero.rs | 8 +- src/protocol/context.rs | 2 +- src/protocol/malicious.rs | 10 +- .../modulus_conversion/convert_shares.rs | 7 +- src/protocol/{reveal.rs => reveal/mod.rs} | 161 +++++++++++------- src/test_fixture/mod.rs | 17 +- src/test_fixture/sharing.rs | 20 ++- 7 files changed, 148 insertions(+), 77 deletions(-) rename src/protocol/{reveal.rs => reveal/mod.rs} (51%) diff --git a/src/protocol/check_zero.rs b/src/protocol/check_zero.rs index 4ae91cd9f..8c2b0a996 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 c56b96861..e39d10985 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: UniqueStepId, prss: &'a PrssEndpoint, diff --git a/src/protocol/malicious.rs b/src/protocol/malicious.rs index 89c29efc5..cae7c03e0 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 77a2379dc..212a1a5f2 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/mod.rs similarity index 51% rename from src/protocol/reveal.rs rename to src/protocol/reveal/mod.rs index a8f500768..fad5945b0 100644 --- a/src/protocol/reveal.rs +++ b/src/protocol/reveal/mod.rs @@ -2,17 +2,32 @@ use std::iter::{repeat, zip}; use crate::ff::Field; use crate::protocol::context::ProtocolContext; -use crate::secret_sharing::Replicated; +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}; use permutation::Permutation; -/// This implements a reveal algorithm +/// 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` /// @@ -23,52 +38,66 @@ use permutation::Permutation; /// ![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")] -#[allow(dead_code)] -pub async fn reveal( - ctx: ProtocolContext<'_, Replicated, F>, - record_id: RecordId, - input: Replicated, -) -> Result { - let channel = ctx.mesh(); +impl Reveal for ProtocolContext<'_, Replicated, G> { + type Share = Replicated; - channel - .send(ctx.role().peer(Direction::Right), record_id, input.left()) - .await?; + async fn reveal( + self, + record_id: RecordId, + input: Self::Share, + ) -> Result { + let (role, channel) = (self.role(), self.mesh()); + let (left, right) = input.as_tuple(); - // Sleep until `helper's left` sends their share - let share = channel - .receive(ctx.role().peer(Direction::Left), record_id) - .await?; + channel + .send(role.peer(Direction::Right), record_id, left) + .await?; - Ok(input.left() + input.right() + share) + // Sleep until `helper's left` sends their share + let share = channel + .receive(role.peer(Direction::Left), record_id) + .await?; + + Ok(left + 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?; +/// 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; - 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?; + 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(); - if share_from_left == share_from_right { - Ok(input.left() + input.right() + share_from_left) - } else { - Err(Error::MaliciousRevealFailed) + // 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) + } } } @@ -81,7 +110,7 @@ pub async fn reveal_permutation( ) -> Result { 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; + 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 @@ -99,18 +128,16 @@ mod tests { use proptest::prelude::Rng; use tokio::try_join; - use crate::error::Error; use crate::{ error::BoxError, + error::Error, ff::{Field, Fp31}, helpers::Direction, - protocol::{ - context::ProtocolContext, - reveal::{reveal, reveal_malicious}, - QueryId, RecordId, - }, - secret_sharing::Replicated, + protocol::reveal::Reveal, + protocol::{context::ProtocolContext, QueryId, RecordId}, + secret_sharing::MaliciousReplicated, test_fixture::{make_contexts, make_world, share, TestWorld}, + test_fixture::{make_malicious_contexts, share_malicious}, }; #[tokio::test] @@ -125,9 +152,9 @@ mod tests { 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]), + 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?; @@ -142,17 +169,19 @@ mod tests { pub async fn malicious() -> Result<(), BoxError> { let mut rng = rand::thread_rng(); let world: TestWorld = make_world(QueryId); - let ctx = make_contexts::(&world); + let ctx = make_malicious_contexts::(&world); for i in 0..10_u32 { let secret = rng.gen::(); let input = Fp31::from(secret); - let share = share(input, &mut rng); + // r*x value is not used inside malicious reveal, so it can be set to any value + let share = share_malicious(input, share(Fp31::ZERO, &mut rng), &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]), + 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?; @@ -167,16 +196,17 @@ mod tests { 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); + let ctx = make_malicious_contexts(&world); for i in 0..10_u32 { let secret = rng.gen::(); let input = Fp31::from(secret); - let share = share(input, &mut rng); + // r*x value is not used inside malicious reveal, so it can be set to any value + let share = share_malicious(input, share(Fp31::ZERO, &mut rng), &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]), + 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), ); @@ -186,20 +216,21 @@ mod tests { } pub async fn reveal_with_additive_attack( - ctx: ProtocolContext<'_, Replicated, F>, + ctx: ProtocolContext<'_, MaliciousReplicated, F>, record_id: RecordId, - input: Replicated, + 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, input.right()), + channel.send(ctx.role().peer(Direction::Left), record_id, right), channel.send( ctx.role().peer(Direction::Right), record_id, - input.left() + additive_error, + left + additive_error, ), ) .await?; @@ -210,6 +241,6 @@ mod tests { ) .await?; - Ok(input.left() + input.right() + share_from_left) + Ok(left + right + share_from_left) } } diff --git a/src/test_fixture/mod.rs b/src/test_fixture/mod.rs index e5e362121..afdd81356 100644 --- a/src/test_fixture/mod.rs +++ b/src/test_fixture/mod.rs @@ -9,11 +9,12 @@ use crate::helpers::Role; use crate::protocol::context::ProtocolContext; use crate::protocol::prss::Endpoint as PrssEndpoint; use crate::protocol::Step; -use crate::secret_sharing::{Replicated, SecretSharing}; +use crate::secret_sharing::{MaliciousReplicated, Replicated, SecretSharing}; use rand::rngs::mock::StepRng; use rand::thread_rng; -pub use sharing::{share, validate_and_reconstruct, validate_list_of_shares}; +use crate::protocol::malicious::SecurityValidator; +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, }; @@ -37,6 +38,18 @@ pub fn make_contexts( .unwrap() } +/// Creates malicious protocol contexts for 3 helpers +pub 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) + }) +} + /// Narrows a set of contexts all at once. /// Use by assigning like so: `let [c0, c1, c2] = narrow_contexts(&contexts, "test")` /// diff --git a/src/test_fixture/sharing.rs b/src/test_fixture/sharing.rs index d83709705..f5f922046 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::Rng; use rand::RngCore; @@ -18,6 +18,24 @@ pub fn share(input: F, rng: &mut R) -> [Replicated; 3] ] } +/// Shares `input` into 3 maliciously secure replicated secret shares using the provided `rng` implementation +/// +#[allow(clippy::missing_panics_doc)] +pub fn share_malicious( + input: F, + r: [Replicated; 3], + rng: &mut R, +) -> [MaliciousReplicated; 3] { + share(input, rng) + .iter() + .enumerate() + .map(|(i, share)| MaliciousReplicated::new(*share, r[i])) + // TODO: each_ref when stable + .collect::>() + .try_into() + .unwrap() +} + /// Validates correctness of the secret sharing scheme. /// /// # Panics From 0d272cd2b26b0ef049ce837facd5079966363fc2 Mon Sep 17 00:00:00 2001 From: Alex Koshelev Date: Fri, 11 Nov 2022 22:30:51 -0800 Subject: [PATCH 2/3] Move make_malicious_contexts to reveal tests It drops `SecurityValidator` so it is not really that useful. Reveal does not care, so it can use it --- src/protocol/reveal/mod.rs | 17 +++++++++++++++-- src/test_fixture/mod.rs | 15 +-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/protocol/reveal/mod.rs b/src/protocol/reveal/mod.rs index fad5945b0..029234893 100644 --- a/src/protocol/reveal/mod.rs +++ b/src/protocol/reveal/mod.rs @@ -128,6 +128,7 @@ mod tests { use proptest::prelude::Rng; use tokio::try_join; + use crate::protocol::malicious::SecurityValidator; use crate::{ error::BoxError, error::Error, @@ -136,8 +137,7 @@ mod tests { protocol::reveal::Reveal, protocol::{context::ProtocolContext, QueryId, RecordId}, secret_sharing::MaliciousReplicated, - test_fixture::{make_contexts, make_world, share, TestWorld}, - test_fixture::{make_malicious_contexts, share_malicious}, + test_fixture::{make_contexts, make_world, share, share_malicious, TestWorld}, }; #[tokio::test] @@ -243,4 +243,17 @@ mod tests { 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 afdd81356..9c383736f 100644 --- a/src/test_fixture/mod.rs +++ b/src/test_fixture/mod.rs @@ -9,11 +9,10 @@ use crate::helpers::Role; use crate::protocol::context::ProtocolContext; use crate::protocol::prss::Endpoint as PrssEndpoint; use crate::protocol::Step; -use crate::secret_sharing::{MaliciousReplicated, Replicated, SecretSharing}; +use crate::secret_sharing::{Replicated, SecretSharing}; use rand::rngs::mock::StepRng; use rand::thread_rng; -use crate::protocol::malicious::SecurityValidator; 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, @@ -38,18 +37,6 @@ pub fn make_contexts( .unwrap() } -/// Creates malicious protocol contexts for 3 helpers -pub 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) - }) -} - /// Narrows a set of contexts all at once. /// Use by assigning like so: `let [c0, c1, c2] = narrow_contexts(&contexts, "test")` /// From 24db6284420e2531464605c26682d2efb00a05a5 Mon Sep 17 00:00:00 2001 From: Alex Koshelev Date: Mon, 14 Nov 2022 20:56:53 -0800 Subject: [PATCH 3/3] Address feedback --- src/protocol/reveal/mod.rs | 4 ++-- src/test_fixture/sharing.rs | 15 ++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/protocol/reveal/mod.rs b/src/protocol/reveal/mod.rs index 029234893..17af493dd 100644 --- a/src/protocol/reveal/mod.rs +++ b/src/protocol/reveal/mod.rs @@ -175,7 +175,7 @@ mod tests { 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, share(Fp31::ZERO, &mut rng), &mut rng); + let share = share_malicious(input, &mut rng); let record_id = RecordId::from(i); let results = try_join_all(vec![ @@ -202,7 +202,7 @@ mod tests { 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, share(Fp31::ZERO, &mut rng), &mut rng); + 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]), diff --git a/src/test_fixture/sharing.rs b/src/test_fixture/sharing.rs index f5f922046..4b3039d71 100644 --- a/src/test_fixture/sharing.rs +++ b/src/test_fixture/sharing.rs @@ -21,16 +21,13 @@ pub fn share(input: F, rng: &mut R) -> [Replicated; 3] /// Shares `input` into 3 maliciously secure replicated secret shares using the provided `rng` implementation /// #[allow(clippy::missing_panics_doc)] -pub fn share_malicious( - input: F, - r: [Replicated; 3], - rng: &mut R, -) -> [MaliciousReplicated; 3] { - share(input, rng) +pub fn share_malicious(x: F, rng: &mut R) -> [MaliciousReplicated; 3] { + let rx = F::from(rng.gen::()) * x; + share(x, rng) + // TODO: array::zip/each_ref when stable .iter() - .enumerate() - .map(|(i, share)| MaliciousReplicated::new(*share, r[i])) - // TODO: each_ref when stable + .zip(share(rx, rng)) + .map(|(x, rx)| MaliciousReplicated::new(*x, rx)) .collect::>() .try_into() .unwrap()