diff --git a/ipa-core/src/error.rs b/ipa-core/src/error.rs index f3033651b..eedc8842a 100644 --- a/ipa-core/src/error.rs +++ b/ipa-core/src/error.rs @@ -71,6 +71,8 @@ pub enum Error { ContextUnsafe(String), #[error("DZKP Validation failed")] DZKPValidationFailed, + #[error("Inconsistent shares")] + InconsistentShares, } impl Default for Error { diff --git a/ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs b/ipa-core/src/helpers/hashing.rs similarity index 97% rename from ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs rename to ipa-core/src/helpers/hashing.rs index c6afa9938..bc08e6283 100644 --- a/ipa-core/src/protocol/ipa_prf/malicious_security/hashing.rs +++ b/ipa-core/src/helpers/hashing.rs @@ -8,6 +8,7 @@ use sha2::{ use crate::{ ff::{PrimeField, Serializable}, + helpers::MpcMessage, protocol::prss::FromRandomU128, }; @@ -28,6 +29,12 @@ impl Serializable for Hash { } } +impl MpcMessage for Hash {} + +/// Computes Hash of serializable values from an iterator +/// +/// ## Panics +/// Panics when Iterator is empty. pub fn compute_hash<'a, I, S>(input: I) -> Hash where I: IntoIterator, @@ -106,7 +113,7 @@ mod test { use super::{compute_hash, Hash}; use crate::{ ff::{Fp31, Fp32BitPrime, Serializable}, - protocol::ipa_prf::malicious_security::hashing::hash_to_field, + helpers::hashing::hash_to_field, }; #[test] diff --git a/ipa-core/src/helpers/mod.rs b/ipa-core/src/helpers/mod.rs index 2d82b336e..a6453fbfd 100644 --- a/ipa-core/src/helpers/mod.rs +++ b/ipa-core/src/helpers/mod.rs @@ -10,6 +10,7 @@ mod buffers; mod error; mod futures; mod gateway; +pub mod hashing; pub(crate) mod prss_protocol; pub mod stream; mod transport; diff --git a/ipa-core/src/protocol/basics/mod.rs b/ipa-core/src/protocol/basics/mod.rs index fe0018c47..ae3b049b8 100644 --- a/ipa-core/src/protocol/basics/mod.rs +++ b/ipa-core/src/protocol/basics/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod mul; mod reshare; mod reveal; mod share_known_value; +pub mod share_validation; pub mod sum_of_product; use std::ops::Not; diff --git a/ipa-core/src/protocol/basics/share_validation.rs b/ipa-core/src/protocol/basics/share_validation.rs new file mode 100644 index 000000000..83465ba9b --- /dev/null +++ b/ipa-core/src/protocol/basics/share_validation.rs @@ -0,0 +1,174 @@ +use futures_util::future::try_join; + +use crate::{ + error::Error, + helpers::{ + hashing::{compute_hash, Hash}, + Direction, + }, + protocol::{context::Context, RecordId}, + secret_sharing::SharedValue, +}; + +/// This function checks that a vector of shares are consistent across helpers +/// i.e. `H1` holds `(x0,x1)`, `H2` holds `(x1,x2)`, `H3` holds `(x2,x0)` +/// the function verifies that `H1.x0 == H3.x0`, `H1.x1 == H2.x1`, `H2.x2 == H3.x2` +/// +/// We use a hash based approach that is secure in the random oracle model +/// further, only one of left and right helper check the equality of the shares +/// this is might not be sufficient in some applications to prevent malicious behavior +/// +/// The left helper simply hashes the vector and sends it to the right, +/// the right helper hashes his vector and compares it to the received hash +/// +/// # Errors +/// propagates errors from send and receive +pub async fn validate_replicated_shares<'a, 'b, C, I, J, S>( + ctx: C, + input_left: I, + input_right: J, +) -> Result<(), Error> +where + C: Context, + I: IntoIterator, + J: IntoIterator, + S: SharedValue, +{ + // compute hash of `left` + let hash_left = compute_hash(input_left); + + // set up context + let ctx_new = &(ctx.set_total_records(1usize)); + // set up channels + let send_channel = ctx_new.send_channel::(ctx.role().peer(Direction::Right)); + let receive_channel = ctx_new.recv_channel::(ctx.role().peer(Direction::Left)); + + let ((), hash_received) = try_join( + // send hash + send_channel.send(RecordId::FIRST, compute_hash(input_right)), + receive_channel.receive(RecordId::FIRST), + ) + .await?; + + if hash_left == hash_received { + Ok(()) + } else { + Err(Error::InconsistentShares) + } +} + +/// This function is similar to validate the consistency of shares with the difference +/// that it validates that tuple of shares sum to zero rather than being identical +/// i.e. `H1` holds `(H1_x0,H1_x1)`, `H2` holds `(H2_x1,H2_x2)`, `H3` holds `(H3_x2,H3_x0)` +/// the function verifies that `H1_x0 == -H3_x0`, `H1_x1 == -H2_x1`, `H2_x2 == -H3_x2` +/// +/// We use a hash based approach that is secure in the random oracle model +/// further, only one of left and right helper check that it is zero +/// this is sufficient for Distributed Zero Knowledge Proofs +/// but might not be sufficient for other applications +/// +/// The left helper simply hashes the vector and sends it to the right, +/// the right helper negates his vector, hashes it and compares it to the received hash +/// +/// # Errors +/// propagates errors from `validate_replicated_shares` +pub async fn validate_three_two_way_sharing_of_zero<'a, C, I, S>( + ctx: C, + input_left: I, + input_right: I, +) -> Result<(), Error> +where + C: Context, + I: IntoIterator, + S: SharedValue, +{ + // compute left.neg + let left_neg = input_left.into_iter().map(|x| x.neg()).collect::>(); + + validate_replicated_shares(ctx, &left_neg, input_right).await +} + +#[cfg(all(test, unit_test))] +mod test { + use std::ops::Neg; + + use ipa_macros::Step; + use rand::{thread_rng, Rng}; + + use crate::{ + error::Error, + ff::{Field, Fp61BitPrime}, + protocol::{ + basics::share_validation::validate_three_two_way_sharing_of_zero, context::Context, + }, + secret_sharing::replicated::ReplicatedSecretSharing, + test_executor::run, + test_fixture::{Runner, TestWorld}, + }; + + #[derive(Step)] + pub(crate) enum Step { + Correctness, + Misaligned, + Changed, + } + + // Test three two way shares of zero + // we generated replicated shares of a vector of random values + // each helper party negates one of its shares + // we then check whether validate_three_two_way_sharing_of_zero succeeds + // we also test for failure when the shares are misaligned or one of them has been changed + #[test] + fn three_two_way_shares_of_zero() { + run(|| async move { + let world = TestWorld::default(); + let mut rng = thread_rng(); + + let len: usize = rng.gen_range(50..100); + + let r = (0..len) + .map(|_| rng.gen::()) + .collect::>(); + + let _ = world + .semi_honest(r.into_iter(), |ctx, input| async move { + let r_right = input.iter().map(|x| x.right().neg()).collect::>(); + let mut r_left = input + .iter() + .map(ReplicatedSecretSharing::left) + .collect::>(); + + validate_three_two_way_sharing_of_zero( + ctx.narrow(&Step::Correctness), + &r_left, + &r_right, + ) + .await + .unwrap(); + + // check misaligned causes error + let error = validate_three_two_way_sharing_of_zero( + ctx.narrow(&Step::Misaligned), + &r_left[0..len - 1], + &r_right[1..len], + ) + .await; + + assert!(matches!(error, Err(Error::InconsistentShares))); + + // check changing causes error + r_left[5] += Fp61BitPrime::ONE; + + let error = validate_three_two_way_sharing_of_zero( + ctx.narrow(&Step::Changed), + &r_left[0..len - 1], + &r_right[1..len], + ) + .await; + + assert!(matches!(error, Err(Error::InconsistentShares))); + }) + .await; + }); + } +} diff --git a/ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs b/ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs index 9fd7a8b01..607827b1a 100644 --- a/ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs +++ b/ipa-core/src/protocol/ipa_prf/malicious_security/mod.rs @@ -1,4 +1,3 @@ -mod hashing; pub mod lagrange; pub mod prover; pub mod verifier; diff --git a/ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs b/ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs index 7b5958840..01fcb2139 100644 --- a/ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs +++ b/ipa-core/src/protocol/ipa_prf/malicious_security/prover.rs @@ -6,9 +6,9 @@ use std::{ use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; use typenum::{Diff, Sum, U1}; -use super::hashing::{compute_hash, hash_to_field}; use crate::{ ff::PrimeField, + helpers::hashing::{compute_hash, hash_to_field}, protocol::ipa_prf::malicious_security::lagrange::{ CanonicalLagrangeDenominator, LagrangeTable, },