diff --git a/Cargo.toml b/Cargo.toml index 1767e8e24..f65c44f08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,8 @@ default = [ "tracing/max_level_trace", "tracing/release_max_level_info", "descriptive-gate", - "aggregate-circuit" + "aggregate-circuit", + "ipa-prf" ] cli = ["comfy-table", "clap"] enable-serde = ["serde", "serde_json"] @@ -41,6 +42,8 @@ compact-gate = ["ipa-macros/compact-gate"] # Standalone aggregation protocol. We use IPA infra for communication # but it has nothing to do with IPA. aggregate-circuit = [] +# IPA protocol based on OPRF +ipa-prf = [] [dependencies] aes = "0.8.3" @@ -54,6 +57,7 @@ clap = { version = "4.3.2", optional = true, features = ["derive"] } comfy-table = { version = "7.0", optional = true } config = "0.13.2" criterion = { version = "0.5.1", optional = true, default-features = false, features = ["async_tokio", "plotters", "html_reports"] } +curve25519-dalek = "4.1.1" dashmap = "5.4" dhat = "0.3.2" embed-doc-image = "0.1.4" diff --git a/src/error.rs b/src/error.rs index c68b325a8..de41eda4b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -58,6 +58,8 @@ pub enum Error { InvalidReport(#[from] InvalidReportError), #[error("unsupported: {0}")] Unsupported(String), + #[error("Decompressing invalid elliptic curve point: {0}")] + DecompressingInvalidCurvePoint(String), } impl Default for Error { diff --git a/src/ff/curve_points.rs b/src/ff/curve_points.rs new file mode 100644 index 000000000..8eaa57623 --- /dev/null +++ b/src/ff/curve_points.rs @@ -0,0 +1,239 @@ +use curve25519_dalek::{ + ristretto::{CompressedRistretto, RistrettoPoint}, + Scalar, +}; +use generic_array::GenericArray; +use typenum::U32; + +use crate::{ + ff::{ec_prime_field::Fp25519, Serializable}, + secret_sharing::{Block, WeakSharedValue}, +}; + +impl Block for CompressedRistretto { + type Size = U32; +} + +///ristretto point for curve 25519, +/// we store it in compressed format since it is 3 times smaller and we do a limited amount of +/// arithmetic operations on the curve points +/// +/// We use ristretto points such that we have a prime order elliptic curve, +/// This is needed for the Dodis Yampolski PRF +/// +/// decompressing invalid curve points will cause panics, +/// since we always generate curve points from scalars (elements in Fp25519) and +/// only deserialize previously serialized valid points, panics will not occur +/// However, we still added a debug assert to deserialize since values are sent by other servers +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct RP25519(CompressedRistretto); + +/// Implementing trait for secret sharing +impl WeakSharedValue for RP25519 { + type Storage = CompressedRistretto; + const BITS: u32 = 256; + const ZERO: Self = Self(CompressedRistretto([0_u8; 32])); +} + +impl Serializable for RP25519 { + type Size = <::Storage as Block>::Size; + + fn serialize(&self, buf: &mut GenericArray) { + *buf.as_mut() = self.0.to_bytes(); + } + + fn deserialize(buf: &GenericArray) -> Self { + debug_assert!(CompressedRistretto((*buf).into()).decompress().is_some()); + RP25519(CompressedRistretto((*buf).into())) + } +} + +///## Panics +/// Panics when decompressing invalid curve point. This can happen when deserialize curve point +/// from bit array that does not have a valid representation on the curve +impl std::ops::Add for RP25519 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self((self.0.decompress().unwrap() + rhs.0.decompress().unwrap()).compress()) + } +} + +impl std::ops::AddAssign for RP25519 { + #[allow(clippy::assign_op_pattern)] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +///## Panics +/// Panics when decompressing invalid curve point. This can happen when deserialize curve point +/// from bit array that does not have a valid representation on the curve +impl std::ops::Neg for RP25519 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(self.0.decompress().unwrap().neg().compress()) + } +} + +///## Panics +/// Panics when decompressing invalid curve point. This can happen when deserialize curve point +/// from bit array that does not have a valid representation on the curve +impl std::ops::Sub for RP25519 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self((self.0.decompress().unwrap() - rhs.0.decompress().unwrap()).compress()) + } +} + +impl std::ops::SubAssign for RP25519 { + #[allow(clippy::assign_op_pattern)] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +///Scalar Multiplication +/// allows to multiply curve points with scalars from Fp25519 +///## Panics +/// Panics when decompressing invalid curve point. This can happen when deserialize curve point +/// from bit array that does not have a valid representation on the curve +impl std::ops::Mul for RP25519 { + type Output = Self; + + fn mul(self, rhs: Fp25519) -> RP25519 { + (self.0.decompress().unwrap() * Scalar::from(rhs)) + .compress() + .into() + } +} + +impl std::ops::MulAssign for RP25519 { + #[allow(clippy::assign_op_pattern)] + fn mul_assign(&mut self, rhs: Fp25519) { + *self = *self * rhs; + } +} + +impl From for RP25519 { + fn from(s: Scalar) -> Self { + RP25519(RistrettoPoint::mul_base(&s).compress()) + } +} + +impl From for RP25519 { + fn from(s: Fp25519) -> Self { + RP25519(RistrettoPoint::mul_base(&s.into()).compress()) + } +} + +impl From for RP25519 { + fn from(s: CompressedRistretto) -> Self { + RP25519(s) + } +} + +impl From for CompressedRistretto { + fn from(s: RP25519) -> Self { + s.0 + } +} + +///allows to convert curve points into unsigned integers, preserving high entropy +macro_rules! cp_hash_impl { + ( $u_type:ty) => { + impl From for $u_type { + fn from(s: RP25519) -> Self { + use hkdf::Hkdf; + use sha2::Sha256; + let hk = Hkdf::::new(None, s.0.as_bytes()); + let mut okm = <$u_type>::MIN.to_le_bytes(); + //error invalid length from expand only happens when okm is very large + hk.expand(&[], &mut okm).unwrap(); + <$u_type>::from_le_bytes(okm) + } + } + }; +} + +cp_hash_impl!(u128); + +cp_hash_impl!(u64); + +/// implementing random curve point generation for testing purposes, +/// in the actual IPA protocol, we generate them from scalars, i.e. Fp25519 +#[cfg(test)] +impl rand::distributions::Distribution for rand::distributions::Standard { + fn sample(&self, rng: &mut R) -> RP25519 { + let mut scalar_bytes = [0u8; 64]; + rng.fill_bytes(&mut scalar_bytes); + RP25519(RistrettoPoint::from_uniform_bytes(&scalar_bytes).compress()) + } +} + +#[cfg(all(test, unit_test))] +mod test { + use curve25519_dalek::{constants, scalar::Scalar}; + use generic_array::GenericArray; + use rand::{thread_rng, Rng}; + use typenum::U32; + + use crate::{ + ff::{curve_points::RP25519, ec_prime_field::Fp25519, Serializable}, + secret_sharing::WeakSharedValue, + }; + + cp_hash_impl!(u32); + + ///testing serialize and deserialize + #[test] + fn serde_25519() { + let mut rng = thread_rng(); + let input = rng.gen::(); + let mut a: GenericArray = [0u8; 32].into(); + input.serialize(&mut a); + let output = RP25519::deserialize(&a); + assert_eq!(input, output); + } + + ///testing conversion from scalar to Fp25519 and curve point, i.e. RP25519 + #[test] + fn scalar_to_point() { + let a = Scalar::ONE; + let b: RP25519 = a.into(); + let d: Fp25519 = a.into(); + let c: RP25519 = RP25519::from(d); + assert_eq!(b, RP25519(constants::RISTRETTO_BASEPOINT_COMPRESSED)); + assert_eq!(c, RP25519(constants::RISTRETTO_BASEPOINT_COMPRESSED)); + } + + ///testing simple curve arithmetics to check that `curve25519_dalek` library is used correctly + #[test] + fn curve_arithmetics() { + let mut rng = thread_rng(); + let fp_a = rng.gen::(); + let fp_b = rng.gen::(); + let fp_c = fp_a + fp_b; + let fp_d = RP25519::from(fp_a) + RP25519::from(fp_b); + assert_eq!(fp_d, RP25519::from(fp_c)); + assert_ne!(fp_d, RP25519(constants::RISTRETTO_BASEPOINT_COMPRESSED)); + let fp_e = rng.gen::(); + let fp_f = rng.gen::(); + let fp_g = fp_e * fp_f; + let fp_h = RP25519::from(fp_e) * fp_f; + assert_eq!(fp_h, RP25519::from(fp_g)); + assert_ne!(fp_h, RP25519(constants::RISTRETTO_BASEPOINT_COMPRESSED)); + assert_eq!(RP25519::ZERO, fp_h * Scalar::ZERO.into()); + } + + ///testing curve to unsigned integer conversion has entropy (!= 0) + #[test] + fn curve_point_to_hash() { + let mut rng = thread_rng(); + let fp_a = rng.gen::(); + assert_ne!(0u64, u64::from(fp_a)); + assert_ne!(0u32, u32::from(fp_a)); + } +} diff --git a/src/ff/ec_prime_field.rs b/src/ff/ec_prime_field.rs new file mode 100644 index 000000000..3385f10e8 --- /dev/null +++ b/src/ff/ec_prime_field.rs @@ -0,0 +1,269 @@ +use curve25519_dalek::scalar::Scalar; +use generic_array::GenericArray; +use hkdf::Hkdf; +use sha2::Sha256; +use typenum::U32; + +use crate::{ + ff::{Field, Serializable}, + secret_sharing::{Block, SharedValue}, +}; + +impl Block for Scalar { + type Size = U32; +} + +///implements the Scalar field for elliptic curve 25519 +/// we use elements in Fp25519 to generate curve points and operate on the curve +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct Fp25519(::Storage); + +impl Fp25519 { + pub const ONE: Self = Self(Scalar::ONE); + + ///allow invert for scalars, i.e. computes 1/a mod p + ///# Panics + /// Panics when self is zero + #[must_use] + pub fn invert(&self) -> Fp25519 { + assert_ne!(*self, Fp25519::ZERO); + Fp25519(self.0.invert()) + } +} + +///trait for secret sharing +impl SharedValue for Fp25519 { + type Storage = Scalar; + const BITS: u32 = 256; + const ZERO: Self = Self(Scalar::ZERO); +} + +///conversion to Scalar struct of `curve25519_dalek` +impl From for Scalar { + fn from(s: Fp25519) -> Self { + s.0 + } +} + +impl Serializable for Fp25519 { + type Size = <::Storage as Block>::Size; + + fn serialize(&self, buf: &mut GenericArray) { + *buf.as_mut() = self.0.to_bytes(); + } + + fn deserialize(buf: &GenericArray) -> Self { + Fp25519(Scalar::from_bytes_mod_order((*buf).into())) + } +} + +///generate random elements in Fp25519 +impl rand::distributions::Distribution for rand::distributions::Standard { + fn sample(&self, rng: &mut R) -> Fp25519 { + let mut scalar_bytes = [0u8; 32]; + rng.fill_bytes(&mut scalar_bytes); + Fp25519(Scalar::from_bytes_mod_order(scalar_bytes)) + } +} + +impl std::ops::Add for Fp25519 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl std::ops::AddAssign for Fp25519 { + #[allow(clippy::assign_op_pattern)] + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl std::ops::Neg for Fp25519 { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(self.0.neg()) + } +} + +impl std::ops::Sub for Fp25519 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl std::ops::SubAssign for Fp25519 { + #[allow(clippy::assign_op_pattern)] + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl std::ops::Mul for Fp25519 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + Self(self.0 * rhs.0) + } +} + +impl std::ops::MulAssign for Fp25519 { + #[allow(clippy::assign_op_pattern)] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl From for Fp25519 { + fn from(s: Scalar) -> Self { + Fp25519(s) + } +} + +///conversion from and to unsigned integers, preserving entropy, for testing purposes only +#[cfg(test)] +macro_rules! sc_hash_impl { + ( $u_type:ty) => { + impl From for $u_type { + fn from(s: Fp25519) -> Self { + use hkdf::Hkdf; + use sha2::Sha256; + let hk = Hkdf::::new(None, s.0.as_bytes()); + let mut okm = <$u_type>::MIN.to_le_bytes(); + //error invalid length from expand only happens when okm is very large + hk.expand(&[], &mut okm).unwrap(); + <$u_type>::from_le_bytes(okm) + } + } + + impl From<$u_type> for Fp25519 { + fn from(s: $u_type) -> Self { + use hkdf::Hkdf; + use sha2::Sha256; + let hk = Hkdf::::new(None, &s.to_le_bytes()); + let mut okm = [0u8; 32]; + //error invalid length from expand only happens when okm is very large + hk.expand(&[], &mut okm).unwrap(); + Fp25519::deserialize(&okm.into()) + } + } + }; +} + +#[cfg(test)] +sc_hash_impl!(u64); + +///implement Field because required by PRSS +impl Field for Fp25519 { + const ONE: Fp25519 = Fp25519::ONE; + + ///both following methods are based on hashing and do not allow to actually convert elements in Fp25519 + /// from or into u128. However it is sufficient to generate random elements in Fp25519 + fn as_u128(&self) -> u128 { + let hk = Hkdf::::new(None, self.0.as_bytes()); + let mut okm = [0u8; 16]; + //error invalid length from expand only happens when okm is very large + hk.expand(&[], &mut okm).unwrap(); + u128::from_le_bytes(okm) + } + + ///PRSS uses `truncate_from function`, we need to expand the u128 using a PRG (Sha256) to a [u8;32] + fn truncate_from>(v: T) -> Self { + let hk = Hkdf::::new(None, &v.into().to_le_bytes()); + let mut okm = [0u8; 32]; + //error invalid length from expand only happens when okm is very large + hk.expand(&[], &mut okm).unwrap(); + Fp25519::deserialize(&okm.into()) + } +} + +///implement `TryFrom` since required by Field +impl TryFrom for Fp25519 { + type Error = crate::error::Error; + + fn try_from(v: u128) -> Result { + let mut bits = [0u8; 32]; + bits[..].copy_from_slice(&v.to_le_bytes()); + let f: Fp25519 = Fp25519::ONE; + f.serialize((&mut bits).into()); + Ok(f) + } +} + +#[cfg(all(test, unit_test))] +mod test { + use curve25519_dalek::scalar::Scalar; + use generic_array::GenericArray; + use rand::{thread_rng, Rng}; + use typenum::U32; + + use crate::{ + ff::{ec_prime_field::Fp25519, Serializable}, + secret_sharing::SharedValue, + }; + + sc_hash_impl!(u32); + + ///test serialize and deserialize + #[test] + fn serde_25519() { + let mut rng = thread_rng(); + let input = rng.gen::(); + let mut a: GenericArray = [0u8; 32].into(); + input.serialize(&mut a); + let output = Fp25519::deserialize(&a); + assert_eq!(input, output); + } + + ///test simple arithmetics to check that `curve25519_dalek` is used correctly + #[test] + fn simple_arithmetics_25519() { + let a = Fp25519(Scalar::from_bytes_mod_order([ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ])); + let b = Fp25519(Scalar::from_bytes_mod_order([ + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ])); + let d = Fp25519(Scalar::from_bytes_mod_order([ + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ])); + let e = Fp25519(Scalar::from_bytes_mod_order([ + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ])); + let cc = b - a; + let dc = a + b; + let ec = a * b; + assert_eq!(cc, Fp25519::ONE); + assert_eq!(dc, d); + assert_eq!(ec, e); + } + + ///test random field element generation (!= 0) + #[test] + fn simple_random_25519() { + let mut rng = thread_rng(); + assert_ne!(Fp25519::ZERO, rng.gen::()); + } + + ///test inversion for field elements + #[test] + fn invert_25519() { + let mut rng = thread_rng(); + let a = rng.gen::(); + let ia = a.invert(); + assert_eq!(a * ia, Fp25519(Scalar::ONE)); + } +} diff --git a/src/ff/mod.rs b/src/ff/mod.rs index 5f18196c7..1ced6caf0 100644 --- a/src/ff/mod.rs +++ b/src/ff/mod.rs @@ -2,6 +2,8 @@ // // This is where we store arithmetic shared secret data models. +pub mod curve_points; +pub mod ec_prime_field; mod field; mod galois_field; mod prime_field; diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 359bf305f..1c510485e 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -40,7 +40,7 @@ use crate::{ Role::{H1, H2, H3}, }, protocol::{step::Gate, RecordId}, - secret_sharing::SharedValue, + secret_sharing::WeakSharedValue, }; // TODO work with ArrayLength only @@ -409,7 +409,7 @@ impl Debug for ChannelId { pub trait Message: Debug + Send + Serializable + 'static + Sized {} /// Any shared value can be send as a message -impl Message for V {} +impl Message for V {} impl Serializable for PublicKey { type Size = typenum::U32; diff --git a/src/protocol/basics/reveal.rs b/src/protocol/basics/reveal.rs index 672c8a473..9fddb65b8 100644 --- a/src/protocol/basics/reveal.rs +++ b/src/protocol/basics/reveal.rs @@ -18,7 +18,7 @@ use crate::{ malicious::{AdditiveShare as MaliciousReplicated, ExtendableField}, semi_honest::AdditiveShare as Replicated, }, - SecretSharing, SharedValue, + SecretSharing, WeakSharedValue, }, }; @@ -47,7 +47,7 @@ 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 { +impl Reveal for Replicated { type Output = V; async fn reveal<'fut>(&self, ctx: C, record_id: RecordId) -> Result diff --git a/src/protocol/ipa_prf/mod.rs b/src/protocol/ipa_prf/mod.rs new file mode 100644 index 000000000..f3ee4a5f9 --- /dev/null +++ b/src/protocol/ipa_prf/mod.rs @@ -0,0 +1,3 @@ +#[cfg(feature = "descriptive-gate")] +pub mod prf_eval; +pub mod prf_sharding; diff --git a/src/protocol/ipa_prf/prf_eval.rs b/src/protocol/ipa_prf/prf_eval.rs new file mode 100644 index 000000000..ff37e30eb --- /dev/null +++ b/src/protocol/ipa_prf/prf_eval.rs @@ -0,0 +1,198 @@ +use ipa_macros::Step; + +use crate::{ + error::Error, + ff::{curve_points::RP25519, ec_prime_field::Fp25519}, + protocol::{ + basics::{Reveal, SecureMul}, + context::Context, + prss::SharedRandomness, + RecordId, + }, + secret_sharing::replicated::{semi_honest::AdditiveShare, ReplicatedSecretSharing}, +}; + +#[derive(Step)] +pub(crate) enum Step { + PRFKeyGen, + GenRandomMask, + MultMaskWithPRFInput, + RevealR, + Revealz, +} + +/// generates match key pseudonyms from match keys (in Fp25519 format) and PRF key +/// PRF key needs to be generated separately using `gen_prf_key` +/// +/// `gen_prf_key` is not included such that `compute_match_key_pseudonym` can be tested for correctness +/// # Errors +/// Propagates errors from multiplications +pub async fn compute_match_key_pseudonym( + sh_ctx: C, + prf_key: AdditiveShare, + input_match_keys: Vec>, +) -> Result, Error> +where + C: Context, +{ + let ctx = sh_ctx.set_total_records(input_match_keys.len()); + let futures = input_match_keys + .iter() + .enumerate() + .map(|(i, x)| eval_dy_prf(ctx.clone(), i.into(), &prf_key, x)); + ctx.try_join(futures).await +} + +impl From> for AdditiveShare { + fn from(s: AdditiveShare) -> Self { + AdditiveShare::new(RP25519::from(s.left()), RP25519::from(s.right())) + } +} + +/// generates PRF key k as secret sharing over Fp25519 +pub fn gen_prf_key(ctx: &C) -> AdditiveShare +where + C: Context, +{ + ctx.narrow(&Step::PRFKeyGen) + .prss() + .generate_replicated(RecordId(0)) +} + +/// evaluates the Dodis-Yampolski PRF g^(1/(k+x)) +/// the input x and k are secret shared over finite field Fp25519, i.e. the scalar field of curve 25519 +/// PRF key k needs to be generated using `gen_prf_key` +/// x is the match key in Fp25519 format +/// outputs a u64 as specified in `protocol/prf_sharding/mod.rs`, all parties learn the output +/// # Errors +/// Propagates errors from multiplications, reveal and scalar multiplication + +pub async fn eval_dy_prf( + ctx: C, + record_id: RecordId, + k: &AdditiveShare, + x: &AdditiveShare, +) -> Result +where + C: Context, +{ + let sh_r: AdditiveShare = ctx + .narrow(&Step::GenRandomMask) + .prss() + .generate_replicated(record_id); + + //compute (g^left, g^right) + let sh_gr = AdditiveShare::::from(sh_r.clone()); + + //compute x+k + let mut y = x + k; + + //compute y <- r*y + y = y + .multiply(&sh_r, ctx.narrow(&Step::MultMaskWithPRFInput), record_id) + .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?; + + //compute R^(1/z) to u64 + Ok(u64::from(gr * (z.invert()))) +} + +#[cfg(all(test, unit_test))] +mod test { + use rand::Rng; + + use crate::{ + ff::{curve_points::RP25519, ec_prime_field::Fp25519}, + protocol::ipa_prf::prf_eval::compute_match_key_pseudonym, + secret_sharing::{replicated::semi_honest::AdditiveShare, IntoShares}, + test_executor::run, + test_fixture::{Reconstruct, Runner, TestWorld}, + }; + + ///defining test input struct + #[derive(Copy, Clone)] + struct ShuffledTestInput { + match_key: Fp25519, + } + + ///defining test output struct + #[derive(Debug, PartialEq)] + struct TestOutput { + match_key_pseudonym: u64, + } + + fn test_input(mk: u64) -> ShuffledTestInput { + ShuffledTestInput { + match_key: Fp25519::from(mk), + } + } + + impl IntoShares> for ShuffledTestInput { + fn share_with(self, rng: &mut R) -> [AdditiveShare; 3] { + self.match_key.share_with(rng) + } + } + + impl Reconstruct for [&u64; 3] { + fn reconstruct(&self) -> TestOutput { + TestOutput { + match_key_pseudonym: if *self[0] == *self[1] && *self[0] == *self[2] { + *self[0] + } else { + 0u64 + }, + } + } + } + + ///testing correctness of DY PRF evaluation + /// by checking MPC generated pseudonym with pseudonym generated in the clear + #[test] + fn semi_honest() { + run(|| async move { + let world = TestWorld::default(); + + //first two need to be identical for test to succeed + let records: Vec = vec![ + test_input(3), + test_input(3), + test_input(23_443_524_523), + test_input(56), + test_input(895_764_542), + test_input(456_764_576), + test_input(56), + test_input(3), + test_input(56), + test_input(23_443_524_523), + ]; + + //PRF Key Gen + let u = 3_216_412_445u64; + let k: Fp25519 = Fp25519::from(u); + + let expected: Vec = records + .iter() + .map(|&x| TestOutput { + match_key_pseudonym: (RP25519::from((x.match_key + k).invert())).into(), + }) + .collect(); + + let result: Vec<_> = world + .semi_honest( + (records.into_iter(), k), + |ctx, (input_match_keys, prf_key)| async move { + compute_match_key_pseudonym::<_>(ctx, prf_key, input_match_keys) + .await + .unwrap() + }, + ) + .await + .reconstruct(); + assert_eq!(result, expected); + assert_eq!(result[0], result[1]); + }); + } +} diff --git a/src/protocol/prf_sharding/bucket.rs b/src/protocol/ipa_prf/prf_sharding/bucket.rs similarity index 98% rename from src/protocol/prf_sharding/bucket.rs rename to src/protocol/ipa_prf/prf_sharding/bucket.rs index d2ad77a11..a1e62d1a2 100644 --- a/src/protocol/prf_sharding/bucket.rs +++ b/src/protocol/ipa_prf/prf_sharding/bucket.rs @@ -5,7 +5,8 @@ use crate::{ error::Error, ff::{GaloisField, PrimeField, Serializable}, protocol::{ - basics::SecureMul, context::UpgradedContext, prf_sharding::BinaryTreeDepthStep, RecordId, + basics::SecureMul, context::UpgradedContext, ipa_prf::prf_sharding::BinaryTreeDepthStep, + RecordId, }, secret_sharing::{ replicated::malicious::ExtendableField, BitDecomposed, Linear as LinearSecretSharing, @@ -124,7 +125,7 @@ pub mod tests { ff::{Field, Fp32BitPrime, Gf8Bit, Gf9Bit}, protocol::{ context::{Context, UpgradableContext, Validator}, - prf_sharding::bucket::move_single_value_to_bucket, + ipa_prf::prf_sharding::bucket::move_single_value_to_bucket, RecordId, }, rand::Rng, diff --git a/src/protocol/prf_sharding/feature_label_dot_product.rs b/src/protocol/ipa_prf/prf_sharding/feature_label_dot_product.rs similarity index 99% rename from src/protocol/prf_sharding/feature_label_dot_product.rs rename to src/protocol/ipa_prf/prf_sharding/feature_label_dot_product.rs index f87f788d8..bc492b62c 100644 --- a/src/protocol/prf_sharding/feature_label_dot_product.rs +++ b/src/protocol/ipa_prf/prf_sharding/feature_label_dot_product.rs @@ -371,7 +371,7 @@ where pub mod tests { use crate::{ ff::{Field, Fp32BitPrime, GaloisField, Gf2, Gf32Bit}, - protocol::prf_sharding::feature_label_dot_product::{ + protocol::ipa_prf::prf_sharding::feature_label_dot_product::{ compute_feature_label_dot_product, PrfShardedIpaInputRow, }, rand::Rng, diff --git a/src/protocol/prf_sharding/mod.rs b/src/protocol/ipa_prf/prf_sharding/mod.rs similarity index 99% rename from src/protocol/prf_sharding/mod.rs rename to src/protocol/ipa_prf/prf_sharding/mod.rs index 9763ab0f6..db62c95e0 100644 --- a/src/protocol/prf_sharding/mod.rs +++ b/src/protocol/ipa_prf/prf_sharding/mod.rs @@ -12,16 +12,15 @@ use futures_util::{ }; use ipa_macros::Step; -use super::{boolean::saturating_sum::SaturatingSum, modulus_conversion::ToBitConversionTriples}; use crate::{ error::Error, ff::{Field, GaloisField, Gf2, PrimeField, Serializable}, helpers::Role, protocol::{ basics::{if_else, SecureMul, ShareKnownValue}, - boolean::{comparison::bitwise_less_than_constant, or::or}, + boolean::{comparison::bitwise_less_than_constant, or::or, saturating_sum::SaturatingSum}, context::{Context, UpgradableContext, UpgradedContext, Validator}, - modulus_conversion::{convert_bits, BitConversionTriple}, + modulus_conversion::{convert_bits, BitConversionTriple, ToBitConversionTriples}, step::BitOpStep, RecordId, }, @@ -831,7 +830,7 @@ pub mod tests { use super::{CappedAttributionOutputs, PrfShardedIpaInputRow}; use crate::{ ff::{Field, Fp32BitPrime, GaloisField, Gf2, Gf20Bit, Gf3Bit, Gf5Bit}, - protocol::prf_sharding::attribution_and_capping_and_aggregation, + protocol::ipa_prf::prf_sharding::attribution_and_capping_and_aggregation, rand::Rng, secret_sharing::{ replicated::semi_honest::AdditiveShare as Replicated, BitDecomposed, IntoShares, diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 6ee9ae59d..dc8f89fa1 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -5,8 +5,8 @@ pub mod boolean; pub mod context; pub mod dp; pub mod ipa; +pub mod ipa_prf; pub mod modulus_conversion; -pub mod prf_sharding; pub mod prss; pub mod sort; pub mod step; diff --git a/src/query/runner/oprf_ipa.rs b/src/query/runner/oprf_ipa.rs index c4e0f25f2..f3f7136ea 100644 --- a/src/query/runner/oprf_ipa.rs +++ b/src/query/runner/oprf_ipa.rs @@ -12,7 +12,7 @@ use crate::{ protocol::{ basics::ShareKnownValue, context::{UpgradableContext, UpgradedContext}, - prf_sharding::{ + ipa_prf::prf_sharding::{ attribution_and_capping_and_aggregation, compute_histogram_of_users_with_row_count, PrfShardedIpaInputRow, }, diff --git a/src/report.rs b/src/report.rs index f4a2bd4d6..f0f827746 100644 --- a/src/report.rs +++ b/src/report.rs @@ -17,7 +17,7 @@ use crate::{ open_in_place, seal_in_place, CryptError, FieldShareCrypt, Info, KeyPair, KeyRegistry, PublicKeyRegistry, }, - protocol::prf_sharding::GroupingKey, + protocol::ipa_prf::prf_sharding::GroupingKey, secret_sharing::replicated::semi_honest::AdditiveShare as Replicated, }; diff --git a/src/secret_sharing/mod.rs b/src/secret_sharing/mod.rs index f366c870c..441fe1f21 100644 --- a/src/secret_sharing/mod.rs +++ b/src/secret_sharing/mod.rs @@ -23,22 +23,25 @@ pub use scheme::{Bitwise, Linear, LinearRefOps, SecretSharing}; use crate::ff::{AddSub, AddSubAssign, Serializable}; +/// Operations supported for weak shared values. +pub trait Additive: + AddSub + AddSubAssign + Neg +{ +} + +impl Additive for T where + T: AddSub + AddSubAssign + Neg +{ +} + /// Operations supported for shared values. pub trait Arithmetic: - AddSub - + AddSubAssign - + Mul - + MulAssign - + Neg + Additive + Mul + MulAssign { } impl Arithmetic for T where - T: AddSub - + AddSubAssign - + Mul - + MulAssign - + Neg + T: Additive + Mul + MulAssign { } @@ -48,6 +51,18 @@ pub trait Block: Sized + Copy + Debug { type Size: ArrayLength; } +///allows basic secret sharing operations +pub trait WeakSharedValue: + Clone + Copy + PartialEq + Debug + Send + Sync + Sized + Additive + Serializable + 'static +{ + type Storage: Block; + + const BITS: u32; + + const ZERO: Self; +} + +///allows advanced secret sharing operations, requires multiplication pub trait SharedValue: Clone + Copy + PartialEq + Debug + Send + Sync + Sized + Arithmetic + Serializable + 'static { @@ -58,10 +73,22 @@ pub trait SharedValue: const ZERO: Self; } +///any `SharedValue` is also a `WeakSharedValue` +impl WeakSharedValue for T +where + T: SharedValue, +{ + type Storage = T::Storage; + + const BITS: u32 = T::BITS; + + const ZERO: Self = T::ZERO; +} + #[cfg(any(test, feature = "test-fixture", feature = "cli"))] impl IntoShares> for V where - V: SharedValue, + V: WeakSharedValue, Standard: Distribution, { fn share_with(self, rng: &mut R) -> [AdditiveShare; 3] { diff --git a/src/secret_sharing/replicated/mod.rs b/src/secret_sharing/replicated/mod.rs index dcf51494e..80c8167b0 100644 --- a/src/secret_sharing/replicated/mod.rs +++ b/src/secret_sharing/replicated/mod.rs @@ -1,9 +1,9 @@ pub mod malicious; pub mod semi_honest; -use super::{SecretSharing, SharedValue}; +use super::{SecretSharing, SharedValue, WeakSharedValue}; -pub trait ReplicatedSecretSharing: SecretSharing { +pub trait ReplicatedSecretSharing: SecretSharing { fn new(a: V, b: V) -> Self; fn left(&self) -> V; fn right(&self) -> V; diff --git a/src/secret_sharing/replicated/semi_honest/additive_share.rs b/src/secret_sharing/replicated/semi_honest/additive_share.rs index 10b8b2b39..0ae455ea9 100644 --- a/src/secret_sharing/replicated/semi_honest/additive_share.rs +++ b/src/secret_sharing/replicated/semi_honest/additive_share.rs @@ -10,32 +10,32 @@ use crate::{ ff::Serializable, secret_sharing::{ replicated::ReplicatedSecretSharing, Linear as LinearSecretSharing, SecretSharing, - SharedValue, + SharedValue, WeakSharedValue, }, }; #[derive(Clone, PartialEq, Eq)] -pub struct AdditiveShare(V, V); +pub struct AdditiveShare(V, V); -impl SecretSharing for AdditiveShare { +impl SecretSharing for AdditiveShare { const ZERO: Self = AdditiveShare::ZERO; } impl LinearSecretSharing for AdditiveShare {} -impl Debug for AdditiveShare { +impl Debug for AdditiveShare { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "({:?}, {:?})", self.0, self.1) } } -impl Default for AdditiveShare { +impl Default for AdditiveShare { fn default() -> Self { AdditiveShare::new(V::ZERO, V::ZERO) } } -impl AdditiveShare { +impl AdditiveShare { /// Replicated secret share where both left and right values are `F::ZERO` pub const ZERO: Self = Self(V::ZERO, V::ZERO); @@ -44,7 +44,7 @@ impl AdditiveShare { } } -impl ReplicatedSecretSharing for AdditiveShare { +impl ReplicatedSecretSharing for AdditiveShare { fn new(a: V, b: V) -> Self { Self(a, b) } @@ -58,7 +58,7 @@ impl ReplicatedSecretSharing for AdditiveShare { } } -impl AdditiveShare +impl AdditiveShare where Self: Serializable, { @@ -73,7 +73,7 @@ where } } -impl<'a, 'b, V: SharedValue> Add<&'b AdditiveShare> for &'a AdditiveShare { +impl<'a, 'b, V: WeakSharedValue> Add<&'b AdditiveShare> for &'a AdditiveShare { type Output = AdditiveShare; fn add(self, rhs: &'b AdditiveShare) -> Self::Output { @@ -81,7 +81,7 @@ impl<'a, 'b, V: SharedValue> Add<&'b AdditiveShare> for &'a AdditiveShare } } -impl Add for AdditiveShare { +impl Add for AdditiveShare { type Output = Self; fn add(self, rhs: Self) -> Self::Output { @@ -89,7 +89,7 @@ impl Add for AdditiveShare { } } -impl Add> for &AdditiveShare { +impl Add> for &AdditiveShare { type Output = AdditiveShare; fn add(self, rhs: AdditiveShare) -> Self::Output { @@ -97,7 +97,7 @@ impl Add> for &AdditiveShare { } } -impl Add<&AdditiveShare> for AdditiveShare { +impl Add<&AdditiveShare> for AdditiveShare { type Output = Self; fn add(self, rhs: &Self) -> Self::Output { @@ -105,20 +105,20 @@ impl Add<&AdditiveShare> for AdditiveShare { } } -impl AddAssign<&Self> for AdditiveShare { +impl AddAssign<&Self> for AdditiveShare { fn add_assign(&mut self, rhs: &Self) { self.0 += rhs.0; self.1 += rhs.1; } } -impl AddAssign for AdditiveShare { +impl AddAssign for AdditiveShare { fn add_assign(&mut self, rhs: Self) { AddAssign::add_assign(self, &rhs); } } -impl Neg for &AdditiveShare { +impl Neg for &AdditiveShare { type Output = AdditiveShare; fn neg(self) -> Self::Output { @@ -126,7 +126,7 @@ impl Neg for &AdditiveShare { } } -impl Neg for AdditiveShare { +impl Neg for AdditiveShare { type Output = Self; fn neg(self) -> Self::Output { @@ -134,7 +134,7 @@ impl Neg for AdditiveShare { } } -impl Sub for &AdditiveShare { +impl Sub for &AdditiveShare { type Output = AdditiveShare; fn sub(self, rhs: Self) -> Self::Output { @@ -142,7 +142,7 @@ impl Sub for &AdditiveShare { } } -impl Sub for AdditiveShare { +impl Sub for AdditiveShare { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { @@ -150,7 +150,7 @@ impl Sub for AdditiveShare { } } -impl Sub<&Self> for AdditiveShare { +impl Sub<&Self> for AdditiveShare { type Output = Self; fn sub(self, rhs: &Self) -> Self::Output { @@ -158,7 +158,7 @@ impl Sub<&Self> for AdditiveShare { } } -impl Sub> for &AdditiveShare { +impl Sub> for &AdditiveShare { type Output = AdditiveShare; fn sub(self, rhs: AdditiveShare) -> Self::Output { @@ -166,14 +166,14 @@ impl Sub> for &AdditiveShare { } } -impl SubAssign<&Self> for AdditiveShare { +impl SubAssign<&Self> for AdditiveShare { fn sub_assign(&mut self, rhs: &Self) { self.0 -= rhs.0; self.1 -= rhs.1; } } -impl SubAssign for AdditiveShare { +impl SubAssign for AdditiveShare { fn sub_assign(&mut self, rhs: Self) { SubAssign::sub_assign(self, &rhs); } diff --git a/src/secret_sharing/scheme.rs b/src/secret_sharing/scheme.rs index cd6556211..0d2131eeb 100644 --- a/src/secret_sharing/scheme.rs +++ b/src/secret_sharing/scheme.rs @@ -3,11 +3,11 @@ use std::{ ops::{Mul, Neg}, }; -use super::SharedValue; +use super::{SharedValue, WeakSharedValue}; use crate::ff::{AddSub, AddSubAssign, GaloisField}; /// Secret sharing scheme i.e. Replicated secret sharing -pub trait SecretSharing: Clone + Debug + Sized + Send + Sync { +pub trait SecretSharing: Clone + Debug + Sized + Send + Sync { const ZERO: Self; } diff --git a/src/test_fixture/ipa.rs b/src/test_fixture/ipa.rs index 0e2b0ecb1..5529079ee 100644 --- a/src/test_fixture/ipa.rs +++ b/src/test_fixture/ipa.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, num::NonZeroU32, ops::Deref}; -use crate::protocol::prf_sharding::GroupingKey; +use crate::protocol::ipa_prf::prf_sharding::GroupingKey; #[cfg(feature = "in-memory-infra")] use crate::{ ff::{PrimeField, Serializable}, @@ -250,7 +250,7 @@ pub async fn test_oprf_ipa( ff::{Field, Gf2}, protocol::{ basics::ShareKnownValue, - prf_sharding::{ + ipa_prf::prf_sharding::{ attribution_and_capping_and_aggregation, compute_histogram_of_users_with_row_count, PrfShardedIpaInputRow, },