diff --git a/ipa-core/src/protocol/boolean/bitwise_equal.rs b/ipa-core/src/protocol/boolean/bitwise_equal.rs deleted file mode 100644 index fb06bb600..000000000 --- a/ipa-core/src/protocol/boolean/bitwise_equal.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::iter::zip; - -use crate::{ - error::Error, - ff::Gf2, - protocol::{boolean::all_zeroes, context::Context, BasicProtocols, RecordId}, - secret_sharing::{Linear as LinearSecretSharing, LinearRefOps}, -}; - -/// -/// # Errors -/// Propagates errors from multiplications -/// -pub async fn bitwise_equal_gf2( - ctx: C, - record_id: RecordId, - a: &[S], - b: &[S], -) -> Result -where - C: Context, - S: LinearSecretSharing + BasicProtocols, - for<'a> &'a S: LinearRefOps<'a, S, Gf2>, -{ - debug_assert!(a.len() == b.len()); - let c = zip(a.iter(), b.iter()) - .map(|(a_bit, b_bit)| a_bit - b_bit) - .collect::>(); - - all_zeroes(ctx, record_id, &c).await -} diff --git a/ipa-core/src/protocol/boolean/bitwise_less_than_prime.rs b/ipa-core/src/protocol/boolean/bitwise_less_than_prime.rs deleted file mode 100644 index ab108960e..000000000 --- a/ipa-core/src/protocol/boolean/bitwise_less_than_prime.rs +++ /dev/null @@ -1,326 +0,0 @@ -use std::cmp::Ordering; - -use futures::future::try_join; -use ipa_macros::Step; - -use crate::{ - error::Error, - ff::PrimeField, - protocol::{ - boolean::{any_ones, multiply_all_shares, or::or}, - context::Context, - step::BitOpStep, - BasicProtocols, RecordId, - }, - secret_sharing::Linear as LinearSecretSharing, -}; - -/// This is an implementation of Bitwise Less-Than on bitwise-shared numbers. -/// -/// `BitwiseLessThan` takes inputs `[x]_B = ([x_1]_p,...,[x_l]_p)` where -/// `x_1,...,x_l ∈ {0,1} ⊆ F_p` then computes `h ∈ {0, 1} <- x (ctx: C, record_id: RecordId, x: &[S]) -> Result - where - F: PrimeField, - C: Context, - S: LinearSecretSharing + BasicProtocols, - { - let one = S::share_known_value(&ctx, F::ONE); - let gtoe = Self::greater_than_or_equal_to_prime(ctx, record_id, x).await?; - Ok(one - >oe) - } - - /// Checks if `x` is greater than or equal to `F::PRIME`. - /// - /// # Errors - /// Fails if the multiplication protocol fails. - /// - /// # Panics - /// it won't - pub async fn greater_than_or_equal_to_prime( - ctx: C, - record_id: RecordId, - x: &[S], - ) -> Result - where - F: PrimeField, - C: Context, - S: LinearSecretSharing + BasicProtocols, - { - let prime = F::PRIME.into(); - let l = u128::BITS - prime.leading_zeros(); - let l_as_usize = l.try_into().unwrap(); - match x.len().cmp(&l_as_usize) { - Ordering::Greater => { - let (leading_ones, normal_check) = try_join( - any_ones( - ctx.narrow(&Step::CheckIfAnyOnes), - record_id, - &x[l_as_usize..], - ), - Self::greater_than_or_equal_to_prime_trimmed( - ctx.narrow(&Step::CheckTrimmed), - record_id, - &x[0..l_as_usize], - ), - ) - .await?; - or( - ctx.narrow(&Step::LeadingOnesOrRest), - record_id, - &leading_ones, - &normal_check, - ) - .await - } - Ordering::Equal => { - Self::greater_than_or_equal_to_prime_trimmed( - ctx.narrow(&Step::CheckTrimmed), - record_id, - x, - ) - .await - } - Ordering::Less => { - panic!(); - } - } - } - - async fn greater_than_or_equal_to_prime_trimmed( - ctx: C, - record_id: RecordId, - x: &[S], - ) -> Result - where - F: PrimeField, - C: Context, - S: LinearSecretSharing + BasicProtocols, - { - let prime = F::PRIME.into(); - let l = u128::BITS - prime.leading_zeros(); - let l_as_usize: usize = l.try_into().unwrap(); - debug_assert!(x.len() == l_as_usize); - - // Check if this is a Mersenne Prime - // In that special case, the only way for `x >= p` is if `x == p`, - // meaning all the bits of `x` are shares of one. - if prime == (1 << l) - 1 { - return multiply_all_shares(ctx.narrow(&Step::CheckIfAllOnes), record_id, x).await; - } - - // Assume this is an Fp32BitPrime - // Meaning the least significant three bits are exactly [1, 1, 0] - if prime == (1 << l) - 5 { - let (check_least_significant_bits, most_significant_bits_all_ones) = try_join( - Self::check_least_significant_bits( - ctx.narrow(&Step::CheckLeastSignificantBits), - record_id, - &x[0..3], - ), - // To check if a list of shares are all shares of one, we just need to multiply them all together (in any order) - multiply_all_shares(ctx.narrow(&Step::CheckIfAllOnes), record_id, &x[3..]), - ) - .await?; - return check_least_significant_bits - .multiply( - &most_significant_bits_all_ones, - ctx.narrow(&Step::AllOnesAndFinalBits), - record_id, - ) - .await; - } - // Not implemented for any other type of prime. Please add to this if you create a new type of Field which - // is neither a Mersenne Prime, nor which is equal to `2^n - 5` for some value of `n` - panic!(); - } - - /// This is a *special case* implementation which assumes the prime is all ones except for the least significant bits which are: `[1 1 0]` (little-endian) - /// This is the case for `Fp32BitPrime`. - /// - /// Assuming that all the more significant bits of the value being checked are all shares of one, Just consider the least significant three bits: - /// Assume those bits are [1 1 0] (little-endian) - /// There are only 5 numbers that are greater than or equal to the prime - /// 1.) Four of them look like [X X 1] (values of X are irrelevant) - /// 2.) The final one is exactly [1 1 0] - /// We can check if either of these conditions is true with just 3 multiplications - /// - /// # Errors - /// Fails if the multiplication protocol fails. - pub async fn check_least_significant_bits( - ctx: C, - record_id: RecordId, - x: &[S], - ) -> Result - where - F: PrimeField, - C: Context, - S: LinearSecretSharing + BasicProtocols, - { - let prime = F::PRIME.into(); - debug_assert!(prime & 0b111 == 0b011); - debug_assert!(x.len() == 3); - - let one = S::share_known_value(&ctx, F::ONE); - let least_significant_two_bits_both_one = x[0] - .multiply(&x[1], ctx.narrow(&BitOpStep::from(0)), record_id) - .await?; - let least_significant_bits_are_one_one_zero = (one - &x[2]) - .multiply( - &least_significant_two_bits_both_one, - ctx.narrow(&BitOpStep::from(1)), - record_id, - ) - .await?; - - Ok(least_significant_bits_are_one_one_zero + &x[2]) - } -} - -#[derive(Step)] -pub(crate) enum Step { - CheckTrimmed, - CheckIfAnyOnes, - LeadingOnesOrRest, - CheckIfAllOnes, - CheckLeastSignificantBits, - AllOnesAndFinalBits, -} - -#[cfg(all(test, unit_test))] -mod tests { - use rand::{distributions::Standard, prelude::Distribution}; - - use super::BitwiseLessThanPrime; - use crate::{ - ff::{Field, Fp31, Fp32BitPrime, PrimeField}, - protocol::{context::Context, RecordId}, - secret_sharing::{replicated::malicious::ExtendableField, SharedValue}, - test_fixture::{get_bits, Reconstruct, Runner, TestWorld}, - }; - - #[tokio::test] - pub async fn fp31() { - let zero = Fp31::ZERO; - let one = Fp31::ONE; - - assert_eq!(one, bitwise_less_than_prime::(30, 5).await); - assert_eq!(one, bitwise_less_than_prime::(30, 6).await); - assert_eq!(zero, bitwise_less_than_prime::(31, 5).await); - assert_eq!(zero, bitwise_less_than_prime::(32, 6).await); - assert_eq!(zero, bitwise_less_than_prime::(64, 7).await); - assert_eq!(zero, bitwise_less_than_prime::(64, 8).await); - assert_eq!(zero, bitwise_less_than_prime::(128, 8).await); - assert_eq!(zero, bitwise_less_than_prime::(224, 8).await); - assert_eq!(one, bitwise_less_than_prime::(29, 5).await); - assert_eq!(one, bitwise_less_than_prime::(0, 5).await); - assert_eq!(one, bitwise_less_than_prime::(1, 5).await); - assert_eq!(one, bitwise_less_than_prime::(3, 5).await); - assert_eq!(one, bitwise_less_than_prime::(15, 5).await); - } - - #[tokio::test] - pub async fn fp32_bit_prime() { - let zero = Fp32BitPrime::ZERO; - let one = Fp32BitPrime::ONE; - - assert_eq!( - zero, - bitwise_less_than_prime::(Fp32BitPrime::PRIME, 32).await - ); - assert_eq!( - zero, - bitwise_less_than_prime::(Fp32BitPrime::PRIME + 1, 32).await - ); - assert_eq!( - zero, - bitwise_less_than_prime::(Fp32BitPrime::PRIME + 2, 32).await - ); - assert_eq!( - zero, - bitwise_less_than_prime::(Fp32BitPrime::PRIME + 3, 32).await - ); - assert_eq!( - zero, - bitwise_less_than_prime::(Fp32BitPrime::PRIME + 4, 32).await - ); - assert_eq!( - one, - bitwise_less_than_prime::(Fp32BitPrime::PRIME - 1, 32).await - ); - assert_eq!( - one, - bitwise_less_than_prime::(Fp32BitPrime::PRIME - 2, 32).await - ); - assert_eq!( - one, - bitwise_less_than_prime::(Fp32BitPrime::PRIME - 3, 32).await - ); - assert_eq!( - one, - bitwise_less_than_prime::(Fp32BitPrime::PRIME - 4, 32).await - ); - assert_eq!(one, bitwise_less_than_prime::(0, 32).await); - assert_eq!(one, bitwise_less_than_prime::(1, 32).await); - assert_eq!( - one, - bitwise_less_than_prime::(65_536_u32, 32).await - ); - assert_eq!( - one, - bitwise_less_than_prime::(65_535_u32, 32).await - ); - } - - async fn bitwise_less_than_prime(a: u32, num_bits: u32) -> F - where - F: PrimeField + ExtendableField + Sized, - Standard: Distribution, - { - let world = TestWorld::default(); - let bits = get_bits::(a, num_bits); - let result = world - .semi_honest(bits.clone(), |ctx, x_share| async move { - BitwiseLessThanPrime::less_than_prime( - ctx.set_total_records(1), - RecordId::from(0), - &x_share, - ) - .await - .unwrap() - }) - .await - .reconstruct(); - - let m_result = world - .upgraded_malicious(bits, |ctx, x_share| async move { - BitwiseLessThanPrime::less_than_prime( - ctx.set_total_records(1), - RecordId::from(0), - &x_share, - ) - .await - .unwrap() - }) - .await - .reconstruct(); - - assert_eq!(result, m_result); - - result - } -} diff --git a/ipa-core/src/protocol/boolean/generate_random_bits.rs b/ipa-core/src/protocol/boolean/generate_random_bits.rs deleted file mode 100644 index 01a0029c3..000000000 --- a/ipa-core/src/protocol/boolean/generate_random_bits.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::marker::PhantomData; - -use futures::stream::{iter as stream_iter, StreamExt}; - -use crate::{ - error::Error, - ff::PrimeField, - helpers::Role, - protocol::{ - basics::SecureMul, - context::{prss::InstrumentedIndexedSharedRandomness, Context, UpgradedContext}, - modulus_conversion::{convert_some_bits, BitConversionTriple, ToBitConversionTriples}, - prss::SharedRandomness, - RecordId, - }, - secret_sharing::{ - replicated::semi_honest::AdditiveShare as Replicated, BitDecomposed, - Linear as LinearSecretSharing, - }, -}; - -#[derive(Debug)] -struct RawRandomBits { - // TODO: use a const generic instead of a field, when generic_const_expr hits stable. - count: u32, - left: u64, - right: u64, -} - -impl RawRandomBits { - fn generate( - prss: &InstrumentedIndexedSharedRandomness, - record_id: RecordId, - ) -> Self { - // This avoids `F::BITS` as that can be larger than we need. - let count = u128::BITS - F::PRIME.into().leading_zeros(); - assert!(count <= u64::BITS); - let (left, right) = prss.generate::<(u128, u128), _>(record_id); - #[allow(clippy::cast_possible_truncation)] // See above for the relevant assertion. - Self { - count, - left: left as u64, - right: right as u64, - } - } -} - -impl ToBitConversionTriples for RawRandomBits { - type Residual = (); - - // TODO const for this in place of the function - fn bits(&self) -> u32 { - self.count - } - - fn triple(&self, role: Role, i: u32) -> BitConversionTriple> { - debug_assert!(u128::BITS - F::PRIME.into().leading_zeros() >= self.count); - assert!(i < self.count); - BitConversionTriple::new( - role, - ((self.left >> i) & 1) == 1, - ((self.right >> i) & 1) == 1, - ) - } - - fn into_triples( - self, - role: Role, - indices: I, - ) -> ( - BitDecomposed>>, - Self::Residual, - ) - where - F: PrimeField, - I: IntoIterator, - { - (self.triple_range(role, indices), ()) - } -} - -struct RawRandomBitIter { - ctx: C, - record_id: RecordId, - _f: PhantomData, -} - -impl Iterator for RawRandomBitIter { - type Item = RawRandomBits; - fn next(&mut self) -> Option { - let v = RawRandomBits::generate::(&self.ctx.prss(), self.record_id); - self.record_id += 1; - Some(v) - } -} - -/// # Errors -/// If the conversion is unsuccessful (usually the result of communication errors). -/// # Panics -/// Never, but the compiler doesn't know that. - -// TODO : remove this hacky function and make people use the streaming version (which might be harder to use, but is cleaner) -pub async fn one_random_bit( - ctx: C, - record_id: RecordId, -) -> Result, Error> -where - F: PrimeField, - C: UpgradedContext, - C::Share: LinearSecretSharing + SecureMul, -{ - let iter = RawRandomBitIter:: { - ctx: ctx.clone(), - record_id, - _f: PhantomData, - }; - let bits = 0..(u128::BITS - F::PRIME.into().leading_zeros()); - Box::pin(convert_some_bits( - ctx, - // TODO: For some reason, the input stream is polled 16 times, despite this function only calling "next()" once. - // That interacts poorly with PRSS, so cap the iterator. - stream_iter(iter.take(1)), - record_id, - bits, - )) - .next() - .await - .unwrap() - .map(|(v, ())| v) -} diff --git a/ipa-core/src/protocol/boolean/mod.rs b/ipa-core/src/protocol/boolean/mod.rs index f0ee8e738..94a0e7aa7 100644 --- a/ipa-core/src/protocol/boolean/mod.rs +++ b/ipa-core/src/protocol/boolean/mod.rs @@ -1,124 +1,2 @@ -use std::iter::repeat; - -use crate::{ - error::Error, - ff::{Field, PrimeField}, - protocol::{ - basics::{SecureMul, ShareKnownValue}, - context::Context, - step::BitOpStep, - BasicProtocols, RecordId, - }, - secret_sharing::{Linear as LinearSecretSharing, SecretSharing}, -}; - pub mod and; -pub mod bitwise_equal; -#[cfg(feature = "descriptive-gate")] -pub mod bitwise_less_than_prime; -#[cfg(feature = "descriptive-gate")] -pub mod comparison; -#[cfg(feature = "descriptive-gate")] -pub mod generate_random_bits; pub mod or; -#[cfg(feature = "descriptive-gate")] -pub mod random_bits_generator; -#[cfg(feature = "descriptive-gate")] -pub mod solved_bits; -mod xor; - -pub use xor::{xor, xor_sparse}; -#[cfg(feature = "descriptive-gate")] -pub use {comparison::greater_than_constant, solved_bits::RandomBitsShare}; - -/// Converts the given number to a sequence of `{0,1} ⊆ F`, and creates a -/// local replicated share. -pub fn local_secret_shared_bits(ctx: &C, x: u128) -> Vec -where - F: PrimeField, - C: Context, - S: SecretSharing + ShareKnownValue, -{ - (0..(u128::BITS - F::PRIME.into().leading_zeros())) - .map(|i| { - if ((x >> i) & 1) == 1 { - S::share_known_value(ctx, F::ONE) - } else { - S::ZERO - } - }) - .collect::>() -} - -/// We can minimize circuit depth by doing this in a binary-tree like fashion, where pairs of shares are multiplied together -/// and those results are recursively multiplied. -pub(crate) async fn multiply_all_shares( - ctx: C, - record_id: RecordId, - x: &[S], -) -> Result -where - F: Field, - C: Context, - S: SecretSharing + SecureMul, -{ - let mut shares_to_multiply = x.to_vec(); - let mut mult_count = 0_u32; - - while shares_to_multiply.len() > 1 { - let half = shares_to_multiply.len() / 2; - let mut multiplications = Vec::with_capacity(half); - for i in 0..half { - multiplications.push(shares_to_multiply[2 * i].multiply( - &shares_to_multiply[2 * i + 1], - ctx.narrow(&BitOpStep::from(mult_count)), - record_id, - )); - mult_count += 1; - } - // This needs to happen in parallel. - let mut results = ctx.parallel_join(multiplications).await?; - if shares_to_multiply.len() % 2 == 1 { - results.push(shares_to_multiply.pop().unwrap()); - } - shares_to_multiply = results; - } - Ok(shares_to_multiply[0].clone()) -} - -fn flip_bits(one: S, x: &[S]) -> Vec -where - F: Field, - S: LinearSecretSharing, -{ - x.iter() - .zip(repeat(one)) - .map(|(a, one)| one - a) - .collect::>() -} - -/// # Errors -/// This does multiplications which can have errors -#[cfg(feature = "descriptive-gate")] -pub(crate) async fn any_ones(ctx: C, record_id: RecordId, x: &[S]) -> Result -where - F: Field, - C: Context, - S: LinearSecretSharing + BasicProtocols, -{ - let one = S::share_known_value(&ctx, F::ONE); - let res = all_zeroes(ctx, record_id, x).await?; - Ok(one - &res) -} - -pub(crate) async fn all_zeroes(ctx: C, record_id: RecordId, x: &[S]) -> Result -where - F: Field, - C: Context, - S: LinearSecretSharing + BasicProtocols, -{ - let one = S::share_known_value(&ctx, F::ONE); - let inverted_elements = flip_bits(one.clone(), x); - // To check if a list of shares are all shares of one, we just need to multiply them all together (in any order) - multiply_all_shares(ctx, record_id, &inverted_elements).await -} diff --git a/ipa-core/src/protocol/boolean/random_bits_generator.rs b/ipa-core/src/protocol/boolean/random_bits_generator.rs deleted file mode 100644 index 314a0ddd2..000000000 --- a/ipa-core/src/protocol/boolean/random_bits_generator.rs +++ /dev/null @@ -1,204 +0,0 @@ -use std::{ - marker::PhantomData, - sync::atomic::{AtomicU32, Ordering}, -}; - -use ipa_macros::Step; - -use crate::{ - error::Error, - ff::PrimeField, - helpers::TotalRecords, - protocol::{ - boolean::solved_bits::{solved_bits, RandomBitsShare}, - context::UpgradedContext, - BasicProtocols, RecordId, - }, - secret_sharing::{Linear as LinearSecretSharing, LinearRefOps}, -}; - -/// A struct that generates random sharings of bits from the -/// `SolvedBits` protocol. Any protocol who wish to use a random-bits can draw -/// one by calling `generate()`. -/// -/// This object is safe to share with multiple threads. It uses an atomic counter -/// to manage concurrent accesses. -#[derive(Debug)] -pub struct RandomBitsGenerator { - ctx: C, - fallback_ctx: C, - fallback_count: AtomicU32, - _marker: PhantomData<(F, S)>, -} - -/// Special context that is used when values generated using the standard method are larger -/// than the prime for the field. It is grossly inefficient to use, because communications -/// are unbuffered, but a prime that is close to a power of 2 helps reduce how often we need it. -#[derive(Step)] -pub(crate) enum FallbackStep { - Fallback, -} - -impl RandomBitsGenerator -where - F: PrimeField, - C: UpgradedContext, - S: LinearSecretSharing + BasicProtocols, - for<'a> &'a S: LinearRefOps<'a, S, F>, -{ - #[must_use] - pub fn new(ctx: C) -> Self { - let fallback_ctx = ctx - .narrow(&FallbackStep::Fallback) - .set_total_records(TotalRecords::Indeterminate); - Self { - ctx, - fallback_ctx, - fallback_count: AtomicU32::new(0), - _marker: PhantomData, - } - } - - /// Takes the next `RandomBitsShare` that is available. As the underlying - /// generator can fail, this will draw from that repeatedly until a value is produced. - /// - /// # Errors - /// This method may fail for number of reasons. Errors include locking the - /// inner members multiple times, I/O errors while executing MPC protocols, - /// read from an empty buffer, etc. - pub async fn generate(&self, record_id: RecordId) -> Result, Error> { - let share = if let Some(v) = solved_bits(self.ctx.clone(), record_id).await? { - v - } else { - loop { - let i = self.fallback_count.fetch_add(1, Ordering::AcqRel); - if let Some(v) = solved_bits(self.fallback_ctx.clone(), RecordId::from(i)).await? { - break v; - } - } - }; - - if self.ctx.total_records().is_last(record_id) { - // TODO: close indeterminate channels - } - - Ok(share) - } - - /// Get the number of aborts for this instance. - #[allow(dead_code)] - pub fn fallbacks(&self) -> u32 { - self.fallback_count.load(Ordering::Acquire) - } -} - -#[cfg(all(test, unit_test))] -mod tests { - use std::iter::zip; - - use futures::future::try_join_all; - - use super::RandomBitsGenerator; - use crate::{ - ff::{Fp31, U128Conversions}, - protocol::{ - context::{Context, UpgradableContext, Validator}, - RecordId, - }, - secret_sharing::{ - replicated::{semi_honest::AdditiveShare, ReplicatedSecretSharing}, - SharedValue, - }, - test_fixture::{join3, join3v, Reconstruct, Runner, TestWorld}, - }; - - #[tokio::test] - pub async fn semi_honest() { - let world = TestWorld::default(); - let contexts = world.contexts().map(|ctx| ctx.set_total_records(1)); - let validators = contexts.map(UpgradableContext::validator); - let [c0, c1, c2] = validators.map(|v| v.context()); - let record_id = RecordId::from(0); - - let rbg0 = RandomBitsGenerator::new(c0); - let rbg1 = RandomBitsGenerator::new(c1); - let rbg2 = RandomBitsGenerator::new(c2); - - let result = join3( - rbg0.generate(record_id), - rbg1.generate(record_id), - rbg2.generate(record_id), - ) - .await; - assert_eq!(rbg0.fallbacks(), rbg1.fallbacks()); - assert_eq!(rbg0.fallbacks(), rbg2.fallbacks()); - let _: Fp31 = result.reconstruct(); // reconstruct() will validate the value. - } - - #[tokio::test] - pub async fn uses_fallback_channel() { - /// The odds of needing a fallback on a field of size 31 is 1/32. - /// 100 iterations will have a fallback with probability of 1-(1-31/32)^100. - /// Repeating that 20 times should make the odds of failure negligible. - const OUTER: u32 = 20; - const INNER: u32 = 100; - - let world = TestWorld::default(); - - for _ in 0..OUTER { - let v = world - .semi_honest((), |ctx, ()| async move { - let validator = ctx.validator(); - let ctx = validator - .context() - .set_total_records(usize::try_from(INNER).unwrap()); - let rbg = RandomBitsGenerator::::new(ctx); - drop( - // This can't use `seq_try_join_all` because this isn't sequential. - try_join_all((0..INNER).map(|i| rbg.generate(RecordId::from(i)))) - .await - .unwrap(), - ); - // Pass the number of fallbacks out as a share in Fp31. - // It will reconstruct as Fp31(3) or Fp31(0), which will let the outer - // code to know when to stop. Reconstruction also ensures that helpers agree. - let f = Fp31::truncate_from(rbg.fallbacks() > 0); - AdditiveShare::new(f, f) - }) - .await - .reconstruct(); - if v != Fp31::ZERO { - return; - } - } - panic!( - "{i} iterations failed to result in a fallback", - i = OUTER * INNER - ); - } - - #[tokio::test] - pub async fn malicious() { - let world = TestWorld::default(); - let contexts = world.malicious_contexts(); - - let validators = contexts.map(UpgradableContext::validator::); - let rbg = validators - .iter() - .map(|v| RandomBitsGenerator::new(v.context().set_total_records(1))) - .collect::>(); - let record_id = RecordId::from(0); - - let m_result = join3( - rbg[0].generate(record_id), - rbg[1].generate(record_id), - rbg[2].generate(record_id), - ) - .await; - assert_eq!(rbg[0].fallbacks(), rbg[1].fallbacks()); - assert_eq!(rbg[0].fallbacks(), rbg[2].fallbacks()); - - let result = join3v(zip(validators, m_result).map(|(v, m)| v.validate(m))).await; - let _: Fp31 = result.reconstruct(); // reconstruct() will validate the value. - } -} diff --git a/ipa-core/src/protocol/boolean/solved_bits.rs b/ipa-core/src/protocol/boolean/solved_bits.rs deleted file mode 100644 index 1cf64c945..000000000 --- a/ipa-core/src/protocol/boolean/solved_bits.rs +++ /dev/null @@ -1,306 +0,0 @@ -use std::marker::PhantomData; - -use async_trait::async_trait; -use ipa_macros::Step; - -use crate::{ - error::Error, - ff::{Field, PrimeField}, - protocol::{ - basics::{reveal, Reveal}, - boolean::{ - bitwise_less_than_prime::BitwiseLessThanPrime, generate_random_bits::one_random_bit, - }, - context::{Context, UpgradedContext}, - BasicProtocols, RecordId, - }, - secret_sharing::{ - replicated::malicious::{ - AdditiveShare as MaliciousReplicated, DowngradeMalicious, ExtendableField, - UnauthorizedDowngradeWrapper, - }, - BitDecomposed, Linear as LinearSecretSharing, LinearRefOps, SecretSharing, Vectorizable, - }, -}; - -#[derive(Debug)] -pub struct RandomBitsShare -where - F: Field, - S: SecretSharing, -{ - pub b_b: BitDecomposed, - pub b_p: S, - _marker: PhantomData, -} - -#[async_trait] -impl DowngradeMalicious for RandomBitsShare> -where - F: ExtendableField, -{ - type Target = - RandomBitsShare>; - - async fn downgrade(self) -> UnauthorizedDowngradeWrapper { - use crate::secret_sharing::replicated::malicious::ThisCodeIsAuthorizedToDowngradeFromMalicious; - - // Note that this clones the values rather than moving them. - // This code is only used in test code, so that's probably OK. - assert!(cfg!(test), "This code isn't ideal outside of tests"); - let Self { b_b, b_p, .. } = self; - let b_b = BitDecomposed::new( - b_b.into_iter() - .map(ThisCodeIsAuthorizedToDowngradeFromMalicious::access_without_downgrade), - ); - UnauthorizedDowngradeWrapper::new(Self::Target { - b_b, - b_p: b_p.access_without_downgrade(), - _marker: PhantomData, - }) - } -} - -/// This protocol tries to generate a sequence of uniformly random sharing of -/// bits in `F_p`. Adding these 3-way secret-sharing will yield the secret -/// `b_i ∈ {0,1}`. This protocol will abort and returns `None` if the secret -/// number from randomly generated bits is not less than the field's prime -/// number. Once aborted, the caller must provide a new narrowed context if -/// they wish to call this protocol again for the same `record_id`. -/// -/// This is an implementation of "3.1 Generating random solved BITS" from I. Damgård -/// et al., but replaces `RAN_2` with our own PRSS implementation in lieu. -/// -/// 3.1 Generating random solved BITS -/// "Unconditionally Secure Constant-Rounds Multi-party Computation for Equality, Comparison, Bits, and Exponentiation" -/// I. Damgård et al. -/// -/// # Errors -/// Many reasons, usually communications-related. -/// # Panics -/// Never, but the compiler can't see that. - -// Try generating random sharing of bits, `[b]_B`, and `l`-bit long. -// Each bit has a 50% chance of being a 0 or 1, so there are -// `F::Integer::MAX - p` cases where `b` may become larger than `p`. -// However, we calculate the number of bits needed to form a random -// number that has the same number of bits as the prime. -// With `Fp32BitPrime` (prime is `2^32 - 5`), that chance is around -// 1 * 10^-9. For Fp31, the chance is 1 out of 32 =~ 3%. -pub async fn solved_bits( - ctx: C, - record_id: RecordId, -) -> Result>, Error> -where - F: PrimeField, - C: UpgradedContext, - S: LinearSecretSharing + BasicProtocols, - for<'a> &'a S: LinearRefOps<'a, S, F>, -{ - // - // step 1 & 2 - // - let b_b = one_random_bit(ctx.narrow(&Step::RandomBits), record_id).await?; - - // - // step 3, 4 & 5 - // - // if b >= p, then abort by returning `None` - if !is_less_than_p(ctx.clone(), record_id, &b_b).await? { - return Ok(None); - } - - // - // step 6 - // - // if success, then compute `[b_p]` by `Σ 2^i * [b_i]_B` - let b_p: S = b_b.iter().enumerate().fold(S::ZERO, |acc, (i, x)| { - acc + &(x * F::try_from(1 << i).unwrap()) - }); - - Ok(Some(RandomBitsShare { - b_b, - b_p, - _marker: PhantomData, - })) -} - -async fn is_less_than_p(ctx: C, record_id: RecordId, b_b: &[S]) -> Result -where - F: PrimeField, - C: Context, - S: LinearSecretSharing - + BasicProtocols - + Reveal>::Array>, -{ - let c_b = - BitwiseLessThanPrime::less_than_prime(ctx.narrow(&Step::IsPLessThanB), record_id, b_b) - .await?; - - if F::from_array(&reveal(ctx.narrow(&Step::RevealC), record_id, &c_b).await?) == F::ZERO { - return Ok(false); - } - Ok(true) -} - -#[derive(Step)] -pub(crate) enum Step { - RandomBits, - IsPLessThanB, - RevealC, -} - -#[cfg(all(test, unit_test))] -mod tests { - use std::iter::{repeat, zip}; - - use rand::{distributions::Standard, prelude::Distribution}; - - use crate::{ - ff::{Field, Fp31, Fp32BitPrime, PrimeField, U128Conversions}, - protocol::{ - boolean::solved_bits::solved_bits, - context::{Context, UpgradableContext, Validator}, - RecordId, - }, - secret_sharing::{replicated::malicious::ExtendableField, BitDecomposed, SharedValue}, - seq_join::SeqJoin, - test_fixture::{bits_to_value, Reconstruct, Runner, TestWorld}, - }; - - /// Execute `solved_bits` `COUNT` times for `F`. The count should be chosen - /// such that the probability of that many consecutive failures in `F` is - /// negligible (less than 2^-100). - async fn random_bits() - where - F: PrimeField + ExtendableField, - Standard: Distribution, - { - let world = TestWorld::default(); - let [rv0, rv1, rv2] = world - .semi_honest((), |ctx, ()| async move { - let validator = ctx.validator(); - let ctx = validator.context().set_total_records(COUNT); - ctx.try_join( - repeat(ctx.clone()) - .take(COUNT) - .enumerate() - .map(|(i, ctx)| solved_bits(ctx, RecordId::from(i))), - ) - .await - .unwrap() - }) - .await; - - let results = zip(rv0.into_iter(), zip(rv1.into_iter(), rv2.into_iter())) - .map(|(r0, (r1, r2))| [r0, r1, r2]) - .collect::>(); - - let mut successes = 0; - - for result in results { - // if one of `SolvedBits` calls aborts, then all must have aborted, too - if result.iter().any(Option::is_none) { - assert!(result.iter().all(Option::is_none)); - continue; // without incrementing successes - } - - let [s0, s1, s2] = result.map(Option::unwrap); - - // [b]_B must be the same bit lengths - assert_eq!(s0.b_b.len(), s1.b_b.len()); - assert_eq!(s1.b_b.len(), s2.b_b.len()); - - // Reconstruct b_B from ([b_1]_p,...,[b_l]_p) bitwise sharings in F_p - let b_b = (0..s0.b_b.len()) - .map(|i| { - let bit = [&s0.b_b[i], &s1.b_b[i], &s2.b_b[i]].reconstruct(); - assert!(bit == F::ZERO || bit == F::ONE); - bit - }) - .collect::>(); - - // Reconstruct b_P - let b_p: F = [&s0.b_p, &s1.b_p, &s2.b_p].reconstruct(); - - // Base10 of `b_B ⊆ Z` must equal `b_P` - assert_eq!(b_p.as_u128(), bits_to_value(&b_b)); - - successes += 1; - } - - assert!(successes > 0); - } - - #[tokio::test] - pub async fn fp31() { - // Probability of failure for one iteration is 2^-5. - // Need 21 runs to reduce failure to < 2^-100. - random_bits::().await; - } - - #[tokio::test] - pub async fn fp_32bit_prime() { - // Probability of failure for one iteration is 5/2^32 =~ 2^-30. - // Need 4 runs to reduce failure to < 2^-100. - random_bits::().await; - } - - #[tokio::test] - pub async fn malicious() { - let world = TestWorld::default(); - let mut success = 0; - - for _ in 0..4 { - let results = world - .upgraded_malicious(Fp32BitPrime::ZERO, |ctx, share_of_zero| async move { - let share_option = solved_bits(ctx.set_total_records(1), RecordId::from(0)) - .await - .unwrap(); - match share_option { - None => { - // This is a 5 in 4B case where `solved_bits()` - // generated a random number > prime. - // - // `malicious()` requires its closure to return `Downgrade` - // so we indicate the abort case with (0, [0]), instead - // of (0, [0; 32]). But this isn't ideal because we can't - // catch a bug where solved_bits returns a 1-bit random bits - // of 0. - ( - share_of_zero.clone(), - BitDecomposed::decompose(1, |_| share_of_zero.clone()), - ) - } - Some(share) => (share.b_p, share.b_b), - } - }) - .await; - - let [result0, result1, result2] = results; - let ((s0, v0), (s1, v1), (s2, v2)) = (result0, result1, result2); - - // bit lengths must be the same - assert_eq!(v0.len(), v1.len()); - assert_eq!(v0.len(), v2.len()); - - let s = [s0, s1, s2].reconstruct(); - let v = zip(v0, zip(v1, v2)) - .map(|(b0, (b1, b2))| { - let bit = [b0, b1, b2].reconstruct(); - assert!(bit == Fp32BitPrime::ZERO || bit == Fp32BitPrime::ONE); - bit - }) - .collect::>(); - - if v.len() > 1 { - // Base10 of `b_B ⊆ Z` must equal `b_P` - assert_eq!(s.as_u128(), bits_to_value(&v)); - success += 1; - } - } - // The chance of this protocol aborting 4 out of 4 tries in Fp32BitPrime - // is about 2^-100. Assert that at least one run has succeeded. - assert!(success > 0); - } -} diff --git a/ipa-core/src/protocol/boolean/xor.rs b/ipa-core/src/protocol/boolean/xor.rs deleted file mode 100644 index ae1da2886..000000000 --- a/ipa-core/src/protocol/boolean/xor.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::{ - error::Error, - ff::{Field, U128Conversions}, - protocol::{ - basics::{MultiplyZeroPositions, SecureMul, ZeroPositions}, - context::Context, - RecordId, - }, - secret_sharing::Linear as LinearSecretSharing, -}; - -/// Secure XOR protocol with two inputs, `a, b ∈ {0,1} ⊆ F_p`. -/// It computes `[a] + [b] - 2[ab]` -/// # Errors -/// When communication fails. -pub async fn xor(ctx: C, record_id: RecordId, a: &S, b: &S) -> Result -where - F: Field + U128Conversions, - C: Context, - S: LinearSecretSharing + SecureMul, -{ - xor_sparse(ctx, record_id, a, b, ZeroPositions::NONE).await -} - -/// Secure XOR protocol with maybe sparse inputs. -/// # Errors -/// When communication fails. -pub async fn xor_sparse( - ctx: C, - record_id: RecordId, - a: &S, - b: &S, - zeros_at: MultiplyZeroPositions, -) -> Result -where - F: Field + U128Conversions, - C: Context, - S: LinearSecretSharing + SecureMul, -{ - let ab = a.multiply_sparse(b, ctx, record_id, zeros_at).await?; - Ok(-(ab * F::truncate_from(2_u128)) + a + b) -} - -#[cfg(all(test, unit_test))] -mod tests { - use rand::distributions::{Distribution, Standard}; - - use super::xor; - use crate::{ - ff::{Field, Fp31, Fp32BitPrime, U128Conversions}, - protocol::{ - basics::{mul::sparse::test::SparseField, MultiplyZeroPositions, ZeroPositions}, - boolean::xor_sparse, - context::Context, - RecordId, - }, - secret_sharing::{replicated::malicious::ExtendableField, SharedValue}, - test_fixture::{Reconstruct, Runner, TestWorld}, - }; - - async fn run(world: &TestWorld, a: F, b: F) -> F - where - F: ExtendableField + U128Conversions, - Standard: Distribution, - { - let result = world - .semi_honest((a, b), |ctx, (a_share, b_share)| async move { - xor( - ctx.set_total_records(1), - RecordId::from(0), - &a_share, - &b_share, - ) - .await - .unwrap() - }) - .await - .reconstruct(); - - let m_result = world - .upgraded_malicious((a, b), |ctx, (a_share, b_share)| async move { - xor( - ctx.set_total_records(1), - RecordId::from(0), - &a_share, - &b_share, - ) - .await - .unwrap() - }) - .await - .reconstruct(); - - assert_eq!(result, m_result); - result - } - - /// Run all combinations of XOR. - /// Note that this is redundant with the `all_sparse()` below, but it uses - /// a larger field and is simpler to reason about. - #[tokio::test] - pub async fn all_combinations() { - type F = Fp32BitPrime; - - let world = TestWorld::default(); - - assert_eq!(F::ZERO, run(&world, F::ZERO, F::ZERO).await); - assert_eq!(F::ONE, run(&world, F::ONE, F::ZERO).await); - assert_eq!(F::ONE, run(&world, F::ZERO, F::ONE).await); - assert_eq!(F::ZERO, run(&world, F::ONE, F::ONE).await); - } - - async fn run_sparse(world: &TestWorld, a: bool, b: bool, zeros: MultiplyZeroPositions) -> bool { - type F = Fp31; - - let a = SparseField::::new(F::truncate_from(u128::from(a)), zeros.0); - let b = SparseField::::new(F::truncate_from(u128::from(b)), zeros.1); - let result = world - .semi_honest((a, b), |ctx, (a_share, b_share)| async move { - xor_sparse( - ctx.set_total_records(1), - RecordId::from(0), - &a_share, - &b_share, - zeros, - ) - .await - .unwrap() - }) - .await - .reconstruct(); - - let m_result = world - .upgraded_malicious((a, b), |ctx, (a_share, b_share)| async move { - xor_sparse( - ctx.set_total_records(1), - RecordId::from(0), - &a_share, - &b_share, - zeros, - ) - .await - .unwrap() - }) - .await - .reconstruct(); - - assert_eq!(result, m_result); - assert!(result.as_u128() <= 1); - result == F::ONE - } - - /// Run all XOR operations with all combinations of sparse inputs. - #[tokio::test] - pub async fn all_sparse() { - let world = TestWorld::default(); - - for &a in ZeroPositions::all() { - for &b in ZeroPositions::all() { - if ZeroPositions::is_pointless((a, b)) { - continue; // Skip a test that should panic. - } - - assert!(!run_sparse(&world, false, false, (a, b)).await); - assert!(run_sparse(&world, true, false, (a, b)).await); - assert!(run_sparse(&world, false, true, (a, b)).await); - assert!(!run_sparse(&world, true, true, (a, b)).await); - } - } - } -} diff --git a/ipa-core/src/protocol/context/upgrade.rs b/ipa-core/src/protocol/context/upgrade.rs index be0907e72..603054e4d 100644 --- a/ipa-core/src/protocol/context/upgrade.rs +++ b/ipa-core/src/protocol/context/upgrade.rs @@ -2,13 +2,7 @@ use std::marker::PhantomData; use async_trait::async_trait; use futures::future::try_join; -#[cfg(feature = "descriptive-gate")] -use futures::future::try_join3; -#[cfg(feature = "descriptive-gate")] -use ipa_macros::Step; -#[cfg(feature = "descriptive-gate")] -use crate::protocol::modulus_conversion::BitConversionTriple; use crate::{ error::Error, ff::Field, @@ -98,44 +92,6 @@ where async fn upgrade(self, input: T) -> Result; } -#[cfg(feature = "descriptive-gate")] -#[derive(Step)] -pub(crate) enum UpgradeTripleStep { - UpgradeBitTriple0, - UpgradeBitTriple1, - UpgradeBitTriple2, -} - -#[cfg(feature = "descriptive-gate")] -#[async_trait] -impl<'a, C, F> - UpgradeToMalicious<'a, BitConversionTriple>, BitConversionTriple> - for UpgradeContext<'a, C, F, RecordId> -where - C: UpgradedContext, - F: ExtendableField, -{ - async fn upgrade( - self, - input: BitConversionTriple>, - ) -> Result, Error> { - let [v0, v1, v2] = input.0; - let (t0, t1, t2) = try_join3( - self.ctx - .narrow(&UpgradeTripleStep::UpgradeBitTriple0) - .upgrade_one(self.record_binding, v0, ZeroPositions::Pvzz), - self.ctx - .narrow(&UpgradeTripleStep::UpgradeBitTriple1) - .upgrade_one(self.record_binding, v1, ZeroPositions::Pzvz), - self.ctx - .narrow(&UpgradeTripleStep::UpgradeBitTriple2) - .upgrade_one(self.record_binding, v2, ZeroPositions::Pzzv), - ) - .await?; - Ok(BitConversionTriple([t0, t1, t2])) - } -} - #[async_trait] impl<'a, C, F> UpgradeToMalicious<'a, (), ()> for UpgradeContext<'a, C, F, NoRecord> where diff --git a/ipa-core/src/protocol/mod.rs b/ipa-core/src/protocol/mod.rs index 3338ccfcd..a5ca59b4a 100644 --- a/ipa-core/src/protocol/mod.rs +++ b/ipa-core/src/protocol/mod.rs @@ -3,8 +3,6 @@ pub mod boolean; pub mod context; pub mod dp; pub mod ipa_prf; -#[cfg(feature = "descriptive-gate")] -pub mod modulus_conversion; pub mod prss; pub mod step; diff --git a/ipa-core/src/protocol/modulus_conversion/convert_shares.rs b/ipa-core/src/protocol/modulus_conversion/convert_shares.rs deleted file mode 100644 index 829c8410b..000000000 --- a/ipa-core/src/protocol/modulus_conversion/convert_shares.rs +++ /dev/null @@ -1,655 +0,0 @@ -//! This takes a replicated secret sharing of a sequence of bits (in a packed format) -//! and converts them, one bit-place at a time, to secret sharings of that bit value (either one or zero) in the target field. -//! -//! This file is somewhat inspired by Algorithm D.3 from -//! "Efficient generation of a pair of random shares for small number of parties" -//! -//! This protocol takes as input such a 3-way random binary replicated secret-sharing, -//! and produces a 3-party replicated secret-sharing of the same value in a target field -//! of the caller's choosing. -//! Example: -//! For input binary sharing: (0, 1, 1) -> which is a sharing of 0 in `Z_2` -//! sample output in `Z_31` could be: (22, 19, 21) -> also a sharing of 0 in `Z_31` -//! This transformation is simple: -//! The original can be conceived of as r = b0 ⊕ b1 ⊕ b2 -//! Each of the 3 bits can be trivially converted into a 3-way secret sharing in `Z_p` -//! So if the second bit is a '1', we can make a 3-way secret sharing of '1' in `Z_p` -//! as (0, 1, 0). -//! Now we simply need to XOR these three sharings together in `Z_p`. This is easy because -//! we know the secret-shared values are all either 0, or 1. As such, the XOR operation -//! is equivalent to fn xor(a, b) { a + b - 2*a*b } - -use std::{ - iter::zip, - marker::PhantomData, - ops::Range, - pin::Pin, - task::{Context as TaskContext, Poll}, -}; - -use futures::stream::{unfold, Stream, StreamExt}; -use ipa_macros::Step; -use pin_project::pin_project; - -use crate::{ - error::Error, - ff::{ArrayAccess, Field, Gf2, PrimeField, U128Conversions}, - helpers::{stream::ExactSizeStream, Role}, - protocol::{ - basics::{SecureMul, ZeroPositions}, - boolean::xor_sparse, - context::{Context, UpgradeContext, UpgradeToMalicious, UpgradedContext}, - RecordId, - }, - secret_sharing::{ - replicated::{semi_honest::AdditiveShare as Replicated, ReplicatedSecretSharing}, - BitDecomposed, Linear as LinearSecretSharing, SharedValue, - }, - seq_join::seq_join, -}; - -#[derive(Step)] -pub(crate) enum ConvertSharesStep { - #[dynamic(64)] - ConvertBit(u32), - Upgrade, - Xor1, - Xor2, -} - -#[derive(Clone)] -pub struct BitConversionTriple(pub(crate) [S; 3]); - -impl BitConversionTriple> { - /// Convert one bit of an XOR sharing into a triple of replicated sharings of that bit. - /// This is not a usable construct, but it can be used with `convert_one_bit` to produce - /// a single replicated sharing of that bit. - /// - /// This is an implementation of "Algorithm 3" from - /// - /// # Panics - /// If any bits in the bitwise shared input cannot be converted into the given field `F` - /// without truncation. - #[must_use] - pub fn new(helper_role: Role, left: bool, right: bool) -> Self { - let left = F::try_from(u128::from(left)).unwrap(); - let right = F::try_from(u128::from(right)).unwrap(); - Self(match helper_role { - Role::H1 => [ - Replicated::new(left, F::ZERO), - Replicated::new(F::ZERO, right), - Replicated::new(F::ZERO, F::ZERO), - ], - Role::H2 => [ - Replicated::new(F::ZERO, F::ZERO), - Replicated::new(left, F::ZERO), - Replicated::new(F::ZERO, right), - ], - Role::H3 => [ - Replicated::new(F::ZERO, right), - Replicated::new(F::ZERO, F::ZERO), - Replicated::new(left, F::ZERO), - ], - }) - } -} - -pub trait ToBitConversionTriples { - /// The type of a collection of fields that need to be carried in the stream without conversion. - type Residual: Send; // TODO: associated type defaults would be nice here. - - /// Get the maximum number of bits that can be produced for this type. - /// - /// Note that this should be an associated constant, but one of the implementations would then need - /// const generics to be more fully available in the language, so this is a method instead. For now. - fn bits(&self) -> u32; - /// Produce a `BitConversionTriple` for the given role and bit index. - fn triple(&self, role: Role, i: u32) -> BitConversionTriple>; - - fn triple_range( - &self, - role: Role, - indices: I, - ) -> BitDecomposed>> - where - F: PrimeField, - I: IntoIterator, - { - BitDecomposed::new(indices.into_iter().map(|i| self.triple(role, i))) - } - - fn into_triples( - self, - role: Role, - indices: I, - ) -> ( - BitDecomposed>>, - Self::Residual, - ) - where - F: PrimeField, - I: IntoIterator; -} - -impl, T: Into> ToBitConversionTriples - for Replicated -{ - type Residual = (); - - fn bits(&self) -> u32 { - B::BITS - } - - fn triple(&self, role: Role, i: u32) -> BitConversionTriple> { - let i = usize::try_from(i).unwrap(); - BitConversionTriple::new( - role, - self.left().get(i).unwrap().into(), - self.right().get(i).unwrap().into(), - ) - } - - fn into_triples( - self, - role: Role, - indices: I, - ) -> ( - BitDecomposed>>, - Self::Residual, - ) - where - F: PrimeField, - I: IntoIterator, - { - (self.triple_range(role, indices), ()) - } -} - -impl ToBitConversionTriples for BitDecomposed> { - type Residual = (); - - fn bits(&self) -> u32 { - u32::try_from(self.len()).unwrap() - } - - fn triple(&self, role: Role, i: u32) -> BitConversionTriple> { - const BIT0: u32 = 0; - let i = usize::try_from(i).unwrap(); - BitConversionTriple::new(role, self[i].left()[BIT0], self[i].right()[BIT0]) - } - - fn into_triples( - self, - role: Role, - indices: I, - ) -> ( - BitDecomposed>>, - Self::Residual, - ) - where - F: PrimeField, - I: IntoIterator, - { - (self.triple_range(role, indices), ()) - } -} - -#[pin_project] -pub struct LocalBitConverter -where - F: PrimeField, - V: ToBitConversionTriples, - S: Stream + Send, -{ - role: Role, - #[pin] - input: S, - bits: Range, - _f: PhantomData, -} - -impl LocalBitConverter -where - F: PrimeField, - V: ToBitConversionTriples, - S: Stream + Send, -{ - pub fn new(role: Role, input: S, bits: Range) -> Self { - Self { - role, - input, - bits, - _f: PhantomData, - } - } -} - -impl Stream for LocalBitConverter -where - F: PrimeField, - V: ToBitConversionTriples, - S: Stream + Send, -{ - type Item = (BitDecomposed>>, R); - - fn poll_next(self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll> { - let mut this = self.project(); - match this.input.as_mut().poll_next(cx) { - Poll::Ready(Some(input)) => { - Poll::Ready(Some(input.into_triples(*this.role, this.bits.clone()))) - } - Poll::Ready(None) => Poll::Ready(None), - Poll::Pending => Poll::Pending, - } - } - - fn size_hint(&self) -> (usize, Option) { - self.input.size_hint() - } -} - -impl ExactSizeStream for LocalBitConverter -where - F: PrimeField, - V: ToBitConversionTriples, - S: Stream + Send, -{ -} - -/// Convert a locally-decomposed single bit into field elements. -/// # Errors -/// Fails only if multiplication fails. -async fn convert_bit( - ctx: C, - record_id: RecordId, - locally_converted_bits: &BitConversionTriple, -) -> Result -where - F: Field + U128Conversions, - C: Context, - S: LinearSecretSharing + SecureMul, -{ - let (sh0, sh1, sh2) = ( - &locally_converted_bits.0[0], - &locally_converted_bits.0[1], - &locally_converted_bits.0[2], - ); - let ctx1 = ctx.narrow(&ConvertSharesStep::Xor1); - let ctx2 = ctx.narrow(&ConvertSharesStep::Xor2); - let sh0_xor_sh1 = xor_sparse(ctx1, record_id, sh0, sh1, ZeroPositions::AVZZ_BZVZ).await?; - debug_assert_eq!( - ZeroPositions::mul_output(ZeroPositions::AVZZ_BZVZ), - ZeroPositions::Pvvz - ); - xor_sparse(ctx2, record_id, &sh0_xor_sh1, sh2, ZeroPositions::AVVZ_BZZV).await -} - -/// Perform modulus conversion. -/// -/// This takes a stream of simple (as in semi-honest or without an extension) replicated `GaloisField` shares. -/// It produces a stream of lists of duplicate (as in malicious or with extension) replicated `PrimeField` shares. -/// Each value in the produced list (or `Vec`) corresponds to a single bit of the input value. -/// -/// The output values are upgraded into the `UpgradedContext` that is provided. The caller is responsible for -/// validating the MAC that this process adds these values to. -/// -/// # Errors -/// Propagates errors from convert shares -/// # Panics -/// If the total record count on the context is unspecified. -#[tracing::instrument(name = "modulus_conversion", skip_all, fields(bits = ?bit_range, gate = %ctx.gate().as_ref()))] -pub fn convert_bits<'a, F, V, C, S, VS>( - ctx: C, - binary_shares: VS, - bit_range: Range, -) -> impl Stream, Error>> + 'a -where - F: PrimeField, - V: ToBitConversionTriples + 'a, - C: UpgradedContext + 'a, - S: LinearSecretSharing + SecureMul, - VS: Stream + Unpin + Send + 'a, - for<'u> UpgradeContext<'u, C, F, RecordId>: - UpgradeToMalicious<'u, BitConversionTriple>, BitConversionTriple>, -{ - convert_some_bits(ctx, binary_shares, RecordId::FIRST, bit_range).map(|v| v.map(|(v, ())| v)) -} - -/// A version of `convert_bits` that allows for the retention of unconverted fields in the input. -/// Note that unconverted fields are not upgraded, so they might need to be upgraded either before or -/// after invoking this function. -#[tracing::instrument(name = "modulus_conversion", skip_all, fields(bits = ?bit_range, gate = %ctx.gate().as_ref()))] -pub fn convert_selected_bits<'a, F, V, C, S, VS, R>( - ctx: C, - binary_shares: VS, - bit_range: Range, -) -> impl Stream, R), Error>> + 'a -where - R: Send + 'static, - F: PrimeField, - V: ToBitConversionTriples + 'a, - C: UpgradedContext + 'a, - S: LinearSecretSharing + SecureMul, - VS: Stream + Unpin + Send + 'a, - for<'u> UpgradeContext<'u, C, F, RecordId>: - UpgradeToMalicious<'u, BitConversionTriple>, BitConversionTriple>, -{ - convert_some_bits(ctx, binary_shares, RecordId::FIRST, bit_range) -} - -pub(crate) fn convert_some_bits<'a, F, V, C, S, VS, R>( - ctx: C, - binary_shares: VS, - first_record: RecordId, - bit_range: Range, -) -> impl Stream, R), Error>> + 'a -where - R: Send + 'static, - F: PrimeField, - V: ToBitConversionTriples + 'a, - C: UpgradedContext + 'a, - S: LinearSecretSharing + SecureMul, - VS: Stream + Unpin + Send + 'a, - for<'u> UpgradeContext<'u, C, F, RecordId>: - UpgradeToMalicious<'u, BitConversionTriple>, BitConversionTriple>, -{ - debug_assert!( - ctx.total_records().is_specified(), - "unspecified record count for modulus conversion at {gate:?}", - gate = ctx.gate() - ); - - let active = ctx.active_work(); - let locally_converted = LocalBitConverter::new(ctx.role(), binary_shares, bit_range); - - let stream = unfold( - (ctx, locally_converted, first_record), - |(ctx, mut locally_converted, record_id)| async move { - let (triple, residual) = locally_converted.next().await?; - let bit_contexts = (0..).map(|i| ctx.narrow(&ConvertSharesStep::ConvertBit(i))); - let converted = - ctx.parallel_join(zip(bit_contexts, triple).map(|(ctx, triple)| async move { - let upgraded = ctx - .narrow(&ConvertSharesStep::Upgrade) - .upgrade_for(record_id, triple) - .await?; - convert_bit(ctx, record_id, &upgraded).await - })); - Some(( - (converted, residual), - (ctx, locally_converted, record_id + 1), - )) - }, - ) - .map(|(row, residual)| async move { - row.await.map(|bits| (BitDecomposed::new(bits), residual)) - }); - seq_join(active, stream) -} - -#[cfg(all(test, unit_test))] -mod tests { - use std::future::ready; - - use futures::stream::{once, StreamExt, TryStreamExt}; - - use crate::{ - error::Error, - ff::{Field, Fp31, Fp32BitPrime, Gf2, PrimeField, U128Conversions}, - helpers::{Direction, Role}, - protocol::{ - context::{Context, UpgradableContext, UpgradedContext, Validator}, - modulus_conversion::{ - convert_bits, convert_selected_bits, BitConversionTriple, LocalBitConverter, - ToBitConversionTriples, - }, - MatchKey, RecordId, - }, - rand::{thread_rng, Rng}, - secret_sharing::{ - replicated::{semi_honest::AdditiveShare as Replicated, ReplicatedSecretSharing}, - IntoShares, - }, - test_fixture::{Reconstruct, Runner, TestWorld}, - }; - - #[tokio::test] - pub async fn one_bit() { - const BITNUM: u32 = 4; - let mut rng = thread_rng(); - - let world = TestWorld::default(); - let match_key = rng.gen::(); - let result: [Replicated; 3] = world - .semi_honest(match_key, |ctx, mk_share| async move { - let v = ctx.validator(); - #[allow(clippy::range_plus_one)] - let bits = convert_bits( - v.context().set_total_records(1), - once(ready(mk_share)), - BITNUM..(BITNUM + 1), - ) - .try_collect::>() - .await - .unwrap(); - assert_eq!(bits.len(), 1); - assert_eq!(bits[0].len(), 1); - bits[0][0].clone() - }) - .await; - assert_eq!(Fp31::truncate_from(match_key[BITNUM]), result.reconstruct()); - } - - struct TwoBits { - convert: Replicated, - keep: Replicated, - } - - impl ToBitConversionTriples for TwoBits { - type Residual = Replicated; - - fn bits(&self) -> u32 { - 1 - } - - fn triple(&self, role: Role, i: u32) -> BitConversionTriple> { - assert_eq!(i, 0, "there is only one convertible bit in TwoBits"); - BitConversionTriple::new( - role, - self.convert.left() == Gf2::ONE, - self.convert.right() == Gf2::ONE, - ) - } - - fn into_triples( - self, - role: Role, - indices: I, - ) -> ( - crate::secret_sharing::BitDecomposed>>, - Self::Residual, - ) - where - F: PrimeField, - I: IntoIterator, - { - (self.triple_range(role, indices), self.keep) - } - } - - #[derive(Clone, Copy)] - struct TwoBitsRaw { - convert: bool, - keep: bool, - } - - impl rand::distributions::Distribution for rand::distributions::Standard { - fn sample(&self, rng: &mut R) -> TwoBitsRaw { - let r = rng.next_u32(); - TwoBitsRaw { - convert: r & 1 == 1, - keep: r & 2 == 2, - } - } - } - - impl IntoShares for TwoBitsRaw { - fn share_with(self, rng: &mut R) -> [TwoBits; 3] { - let r = rng.next_u32(); - let mut offset = 0; - let mut next_bit = || { - let b = (r >> offset) & 1 == 1; - offset += 1; - Gf2::from(b) - }; - - let c0 = next_bit(); - let c1 = next_bit(); - let k0 = next_bit(); - let k1 = next_bit(); - let c2 = c0 + c1 + Gf2::from(self.convert); - let k2 = k0 + k1 + Gf2::from(self.keep); - [ - TwoBits { - convert: Replicated::new(c0, c1), - keep: Replicated::new(k0, k1), - }, - TwoBits { - convert: Replicated::new(c1, c2), - keep: Replicated::new(k1, k2), - }, - TwoBits { - convert: Replicated::new(c2, c0), - keep: Replicated::new(k2, k0), - }, - ] - } - } - - #[tokio::test] - pub async fn retain() { - let mut rng = thread_rng(); - let world = TestWorld::default(); - let two_bits = rng.gen::(); - let result: [(Replicated, Replicated); 3] = world - .semi_honest(two_bits, |ctx, bits_share| async move { - let v = ctx.validator(); - let result = convert_selected_bits( - v.context().set_total_records(1), - once(ready(bits_share)), - 0..1, - ) - .try_collect::>() - .await - .unwrap(); - assert_eq!(result.len(), 1); - let (converted, kept) = result.into_iter().next().unwrap(); - assert_eq!(converted.len(), 1); - (converted.into_iter().next().unwrap(), kept) - }) - .await; - assert_eq!( - ( - Fp31::truncate_from(two_bits.convert), - Gf2::from(two_bits.keep) - ), - result.reconstruct() - ); - } - - #[tokio::test] - pub async fn one_bit_malicious() { - const BITNUM: u32 = 4; - let mut rng = thread_rng(); - - let world = TestWorld::default(); - let match_key = rng.gen::(); - let result: [Replicated; 3] = world - .malicious(match_key, |ctx, mk_share| async move { - let v = ctx.validator(); - #[allow(clippy::range_plus_one)] - let m_bits = convert_bits( - v.context().set_total_records(1), - once(ready(mk_share)), - BITNUM..(BITNUM + 1), - ) - .try_collect::>() - .await - .unwrap(); - assert_eq!(m_bits.len(), 1); - assert_eq!(m_bits[0].len(), 1); - v.validate(m_bits[0][0].clone()).await.unwrap() - }) - .await; - assert_eq!(Fp31::truncate_from(match_key[BITNUM]), result.reconstruct()); - } - - #[tokio::test] - pub async fn one_bit_malicious_tweaks() { - struct Tweak { - role: Role, - index: usize, - dir: Direction, - } - impl Tweak { - fn flip_bit( - &self, - role: Role, - mut triple: BitConversionTriple>, - ) -> BitConversionTriple> { - if role != self.role { - return triple; - } - let v = &mut triple.0[self.index]; - *v = match self.dir { - Direction::Left => Replicated::new(F::ONE - v.left(), v.right()), - Direction::Right => Replicated::new(v.left(), F::ONE - v.right()), - }; - triple - } - } - const fn t(role: Role, index: usize, dir: Direction) -> Tweak { - Tweak { role, index, dir } - } - - const TWEAKS: &[Tweak] = &[ - t(Role::H1, 0, Direction::Left), - t(Role::H1, 1, Direction::Right), - t(Role::H2, 1, Direction::Left), - t(Role::H2, 2, Direction::Right), - t(Role::H3, 2, Direction::Left), - t(Role::H3, 0, Direction::Right), - ]; - - let mut rng = thread_rng(); - let world = TestWorld::default(); - for tweak in TWEAKS { - let match_key = rng.gen::(); - world - .malicious(match_key, |ctx, mk_share| async move { - let triples = - LocalBitConverter::, _, ()>::new( - ctx.role(), - once(ready(mk_share)), - 0..1, - ) - .collect::>() - .await; - let tweaked = tweak.flip_bit(ctx.role(), triples[0].0[0].clone()); - - let v = ctx.validator(); - let m_triples = v.context().upgrade([tweaked]).await.unwrap(); - let m_ctx = v.context().set_total_records(1); - let m_bit = super::convert_bit(m_ctx, RecordId::from(0), &m_triples[0]) - .await - .unwrap(); - let err = v - .validate(m_bit) - .await - .expect_err("This should fail validation"); - assert!(matches!(err, Error::MaliciousSecurityCheckFailed)); - }) - .await; - } - } -} diff --git a/ipa-core/src/protocol/modulus_conversion/mod.rs b/ipa-core/src/protocol/modulus_conversion/mod.rs deleted file mode 100644 index 67a5e8ef9..000000000 --- a/ipa-core/src/protocol/modulus_conversion/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod convert_shares; - -// TODO: wean usage off convert_some_bits. -pub(crate) use convert_shares::convert_some_bits; -pub use convert_shares::{ - convert_bits, convert_selected_bits, BitConversionTriple, LocalBitConverter, - ToBitConversionTriples, -}; diff --git a/ipa-core/src/secret_sharing/replicated/malicious/additive_share.rs b/ipa-core/src/secret_sharing/replicated/malicious/additive_share.rs index 6e8c84b80..6f830900e 100644 --- a/ipa-core/src/secret_sharing/replicated/malicious/additive_share.rs +++ b/ipa-core/src/secret_sharing/replicated/malicious/additive_share.rs @@ -84,12 +84,7 @@ pub trait Downgrade: Send { #[must_use = "You should not be downgrading `replicated::malicious::AdditiveShare` values without calling `MaliciousValidator::validate()`"] pub struct UnauthorizedDowngradeWrapper(T); -impl UnauthorizedDowngradeWrapper { - #[cfg(feature = "descriptive-gate")] - pub(crate) fn new(v: T) -> Self { - Self(v) - } -} +impl UnauthorizedDowngradeWrapper {} pub trait ThisCodeIsAuthorizedToDowngradeFromMalicious { fn access_without_downgrade(self) -> T; diff --git a/ipa-core/src/secret_sharing/replicated/malicious/mod.rs b/ipa-core/src/secret_sharing/replicated/malicious/mod.rs index a8f5418d5..1cfc23106 100644 --- a/ipa-core/src/secret_sharing/replicated/malicious/mod.rs +++ b/ipa-core/src/secret_sharing/replicated/malicious/mod.rs @@ -1,6 +1,4 @@ mod additive_share; pub(crate) use additive_share::ThisCodeIsAuthorizedToDowngradeFromMalicious; -#[cfg(feature = "descriptive-gate")] -pub(crate) use additive_share::UnauthorizedDowngradeWrapper; pub use additive_share::{AdditiveShare, Downgrade as DowngradeMalicious, ExtendableField}; diff --git a/ipa-core/src/test_fixture/sharing.rs b/ipa-core/src/test_fixture/sharing.rs index 6496d7f39..7fccc0d43 100644 --- a/ipa-core/src/test_fixture/sharing.rs +++ b/ipa-core/src/test_fixture/sharing.rs @@ -178,27 +178,6 @@ impl Reconstruct<()> for [(); 3] { fn reconstruct(&self) {} } -#[cfg(feature = "descriptive-gate")] -impl Reconstruct for [crate::protocol::boolean::RandomBitsShare; 3] -where - F: Field + U128Conversions, - S: crate::secret_sharing::SecretSharing, - for<'a> [&'a S; 3]: Reconstruct, -{ - fn reconstruct(&self) -> F { - let bits = zip( - self[0].b_b.iter(), - zip(self[1].b_b.iter(), self[2].b_b.iter()), - ) - .enumerate() - .map(|(i, (b0, (b1, b2)))| [b0, b1, b2].reconstruct() * F::try_from(1 << i).unwrap()) - .fold(F::ZERO, |a, b| a + b); - let value = [&self[0].b_p, &self[1].b_p, &self[2].b_p].reconstruct(); - assert_eq!(bits, value); - value - } -} - pub trait ValidateMalicious { fn validate(&self, r: F::ExtendedField); }