Skip to content

Commit

Permalink
Support array and left/right access via references
Browse files Browse the repository at this point in the history
There are a few things going on here:
* The members of `AdditiveShare` are made private again. This reduces
  the number of things that will need to be updated for vectorization.
* Some vectorized `AdditiveShare`s will be large, creating a need
  for access to the contents by reference in addition to by value.
* Lays some groundwork for supporting simultaneous horizontal and
  vertical vectorization using `BitDecomposed<AdditiveShare<T>>`,
  where `T` is a Boolean array or similar bit vector type.
  • Loading branch information
andyleiserson committed Jan 3, 2024
1 parent 3c5d48b commit 28836e3
Show file tree
Hide file tree
Showing 12 changed files with 290 additions and 174 deletions.
6 changes: 6 additions & 0 deletions ipa-core/src/ff/galois_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,5 +685,11 @@ bit_array_impl!(
v
}
}

impl From<Gf2> for bool {
fn from(value: Gf2) -> Self {
!(value == Gf2::ZERO)
}
}
}
);
23 changes: 23 additions & 0 deletions ipa-core/src/ff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use generic_array::{ArrayLength, GenericArray};
pub use prime_field::Fp31;
pub use prime_field::{Fp32BitPrime, PrimeField};

use crate::secret_sharing::replicated::semi_honest::BorrowReplicated;

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[error("unknown field type {type_str}")]
Expand Down Expand Up @@ -69,6 +71,27 @@ pub trait ArrayAccess {
fn iter(&self) -> Self::Iter<'_>;
}

// There are currently a few places that the by-reference access of `ArrayAccessReplicated` is
// required, and many places that the by-value access of `ArrayAccess` is used.
//
// If all uses of `ArrayAccess` were changed to operate by-reference, the replicated vs. not
// distinction may not be a good reason to have separate traits. Uses of the replicated API would
// just specify an additional `BorrowReplicated` bound. Note that there is at least one
// `ArrayAccess` implementation where a straightforward adaptation will not make it work with
// references: Galois fields, which have `ArrayAccess::Output = bool`. It probably would be possible
// to provide by-reference access for Galois fields using the bit-reference functionality in the
// bitvec crate.
//
// No reason this shouldn't have iter too, but deferring that because it's not needed yet, and will
// be messy to support for Galois fields because of the `bool` value type.
pub trait ArrayAccessReplicated {
type Ref<'a>: BorrowReplicated where Self: 'a;

fn get(&self, index: usize) -> Option<Self::Ref<'_>>;

fn set(&mut self, index: usize, e: Self::Ref<'_>);
}

pub trait Expand {
type Input;

Expand Down
4 changes: 4 additions & 0 deletions ipa-core/src/protocol/basics/share_known_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ use crate::{
},
};

/// Produce a share of some pre-determined constant.
///
/// The context is only used to determine the helper role. It is not used for communication or PRSS,
/// and it is not necessary to use a uniquely narrowed context.
pub trait ShareKnownValue<C: Context, V: SharedValue> {
fn share_known_value(ctx: &C, value: V) -> Self;
}
Expand Down
64 changes: 45 additions & 19 deletions ipa-core/src/protocol/boolean/generate_random_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use futures::stream::{iter as stream_iter, StreamExt};

use crate::{
error::Error,
ff::PrimeField,
ff::{PrimeField, ArrayAccessReplicated},
helpers::Role,
protocol::{
basics::SecureMul,
Expand All @@ -14,8 +14,8 @@ use crate::{
RecordId,
},
secret_sharing::{
replicated::semi_honest::AdditiveShare as Replicated, BitDecomposed,
Linear as LinearSecretSharing,
replicated::semi_honest::{AdditiveShare as Replicated, BorrowReplicated},
BitDecomposed, Linear as LinearSecretSharing,
},
};

Expand All @@ -27,6 +27,45 @@ struct RawRandomBits {
right: u64,
}

struct RawRandomBitsIndex<'a> {
bits: &'a RawRandomBits,
index: usize,
}

impl ArrayAccessReplicated for RawRandomBits {
type Ref<'a> = RawRandomBitsIndex<'a>;

fn get<'a>(&'a self, index: usize) -> Option<Self::Ref<'a>> {
(index < self.count.try_into().unwrap())
.then(|| RawRandomBitsIndex { bits: self, index })
}

fn set(&mut self, _index: usize, _e: Self::Ref<'_>) {
unimplemented!("if this is needed, RawRandomBits should use bitvec")
// ... or perhaps be replaced with `AdditiveShare<BooleanArray>`.
}
}

impl<'a> BorrowReplicated for RawRandomBitsIndex<'a> {
type Output = bool;

fn borrow_left(&self) -> &bool {
if ((self.bits.left >> self.index) & 1) == 1 {
&true
} else {
&false
}
}

fn borrow_right(&self) -> &bool {
if ((self.bits.right >> self.index) & 1) == 1 {
&true
} else {
&false
}
}
}

impl RawRandomBits {
fn generate<F: PrimeField>(
prss: &InstrumentedIndexedSharedRandomness,
Expand Down Expand Up @@ -55,27 +94,14 @@ impl ToBitConversionTriples for RawRandomBits {

fn triple<F: PrimeField>(&self, role: Role, i: u32) -> BitConversionTriple<Replicated<F>> {
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,
self.get(i.try_into().unwrap()).expect("index out of bounds while converting RawRandom"),
)
}

fn into_triples<F, I>(
self,
role: Role,
indices: I,
) -> (
BitDecomposed<BitConversionTriple<Replicated<F>>>,
Self::Residual,
)
where
F: PrimeField,
I: IntoIterator<Item = u32>,
{
(self.triple_range(role, indices), ())
fn into_residual(self) -> Self::Residual {
Self::Residual::default()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ use crate::ff::Expand;
use crate::{
error::Error,
ff::{ArrayAccess, CustomArray, Field},
protocol::{basics::SecureMul, context::Context, step::BitOpStep, RecordId},
protocol::{
basics::{SecureMul, ShareKnownValue},
context::Context,
step::BitOpStep,
RecordId,
},
secret_sharing::{replicated::semi_honest::AdditiveShare, SharedValue},
};

Expand All @@ -32,11 +37,11 @@ where
C: Context,
YS: SharedValue + CustomArray<Element = XS::Element>,
XS: SharedValue + CustomArray + Field,
XS::Element: Field + std::ops::Not<Output = XS::Element>,
XS::Element: Field,
AdditiveShare<XS::Element>: std::ops::Not<Output = AdditiveShare<XS::Element>>,
{
// we need to initialize carry to 1 for x>=y,
// since there are three shares 1+1+1 = 1 mod 2, so setting left = 1 and right = 1 works
let mut carry = AdditiveShare(XS::Element::ONE, XS::Element::ONE);
let mut carry = AdditiveShare::<XS::Element>::share_known_value(&ctx, XS::Element::ONE);
// we don't care about the subtraction, we just want the carry
let _ = subtraction_circuit(ctx, record_id, x, y, &mut carry).await;
Ok(carry)
Expand All @@ -56,7 +61,8 @@ where
C: Context,
YS: SharedValue + CustomArray<Element = XS::Element>,
XS: SharedValue + CustomArray + Field,
XS::Element: Field + std::ops::Not<Output = XS::Element>,
XS::Element: Field,
AdditiveShare<XS::Element>: std::ops::Not<Output = AdditiveShare<XS::Element>>,
{
// we need to initialize carry to 0 for x>y
let mut carry = AdditiveShare::<XS::Element>::ZERO;
Expand All @@ -80,10 +86,11 @@ where
C: Context,
YS: SharedValue + CustomArray<Element = XS::Element>,
XS: SharedValue + CustomArray + Field,
XS::Element: Field + std::ops::Not<Output = XS::Element>,
XS::Element: Field,
AdditiveShare<XS::Element>: std::ops::Not<Output = AdditiveShare<XS::Element>>,
{
// we need to initialize carry to 1 for a subtraction
let mut carry = AdditiveShare(XS::Element::ONE, XS::Element::ONE);
let mut carry = AdditiveShare::<XS::Element>::share_known_value(&ctx, XS::Element::ONE);
subtraction_circuit(ctx, record_id, x, y, &mut carry).await
}

Expand All @@ -102,9 +109,10 @@ pub async fn integer_sat_sub<C, S>(
where
C: Context,
S: CustomArray + Field,
S::Element: Field + std::ops::Not<Output = S::Element>,
S::Element: Field,
AdditiveShare<S::Element>: std::ops::Not<Output = AdditiveShare<S::Element>>,
{
let mut carry = AdditiveShare(S::Element::ONE, S::Element::ONE);
let mut carry = AdditiveShare::<S::Element>::share_known_value(&ctx, S::Element::ONE);
let result = subtraction_circuit(
ctx.narrow(&Step::SaturatedSubtraction),
record_id,
Expand Down Expand Up @@ -139,7 +147,8 @@ where
C: Context,
XS: SharedValue + CustomArray,
YS: SharedValue + CustomArray<Element = XS::Element>,
XS::Element: Field + std::ops::Not<Output = XS::Element>,
XS::Element: Field,
AdditiveShare<XS::Element>: std::ops::Not<Output = AdditiveShare<XS::Element>>,
{
let mut result = AdditiveShare::<XS>::ZERO;
for (i, v) in x.iter().enumerate() {
Expand Down Expand Up @@ -182,7 +191,8 @@ async fn bit_subtractor<C, S>(
) -> Result<AdditiveShare<S>, Error>
where
C: Context,
S: Field + std::ops::Not<Output = S>,
S: Field,
AdditiveShare<S>: std::ops::Not<Output = AdditiveShare<S>>,
{
let output = x + !(y.unwrap_or(&AdditiveShare::<S>::ZERO) + &*carry);

Expand Down Expand Up @@ -216,7 +226,7 @@ mod test {
},
},
rand::thread_rng,
secret_sharing::{replicated::semi_honest::AdditiveShare, SharedValue},
secret_sharing::{replicated::{semi_honest::AdditiveShare, ReplicatedSecretSharing}, SharedValue},
test_executor::run,
test_fixture::{Reconstruct, Runner, TestWorld},
};
Expand All @@ -228,25 +238,25 @@ mod test {
assert_eq!(<Boolean>::ONE, !(<Boolean>::ZERO));
assert_eq!(<Boolean>::ZERO, !(<Boolean>::ONE));
assert_eq!(
AdditiveShare(<Boolean>::ZERO, <Boolean>::ZERO),
!AdditiveShare(<Boolean>::ONE, <Boolean>::ONE)
AdditiveShare::new(<Boolean>::ZERO, <Boolean>::ZERO),
!AdditiveShare::new(<Boolean>::ONE, <Boolean>::ONE)
);
assert_eq!(
AdditiveShare(
AdditiveShare::new(
<BA64>::expand(&<Boolean>::ZERO),
<BA64>::expand(&<Boolean>::ZERO)
),
!AdditiveShare(
!AdditiveShare::new(
<BA64>::expand(&<Boolean>::ONE),
<BA64>::expand(&<Boolean>::ONE)
)
);
assert_eq!(
!AdditiveShare(
!AdditiveShare::new(
<BA64>::expand(&<Boolean>::ZERO),
<BA64>::expand(&<Boolean>::ZERO)
),
AdditiveShare(
AdditiveShare::new(
<BA64>::expand(&<Boolean>::ONE),
<BA64>::expand(&<Boolean>::ONE)
)
Expand Down
59 changes: 16 additions & 43 deletions ipa-core/src/protocol/ipa_prf/boolean_ops/share_conversion_aby.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,16 @@ where
// sh_s: H1: (r1,0), H2: (0,0), H3: (0, r1)
match ctx.role() {
Role::H1 => (
AdditiveShare(<BA256 as SharedValue>::ZERO, <BA256 as SharedValue>::ZERO),
AdditiveShare(r.0, <BA256 as SharedValue>::ZERO),
AdditiveShare::new(<BA256 as SharedValue>::ZERO, <BA256 as SharedValue>::ZERO),
AdditiveShare::new(r.left(), <BA256 as SharedValue>::ZERO),
),
Role::H2 => (
AdditiveShare(<BA256 as SharedValue>::ZERO, r.1),
AdditiveShare(<BA256 as SharedValue>::ZERO, <BA256 as SharedValue>::ZERO),
AdditiveShare::new(<BA256 as SharedValue>::ZERO, r.right()),
AdditiveShare::new(<BA256 as SharedValue>::ZERO, <BA256 as SharedValue>::ZERO),
),
Role::H3 => (
AdditiveShare(r.0, <BA256 as SharedValue>::ZERO),
AdditiveShare(<BA256 as SharedValue>::ZERO, r.1),
AdditiveShare::new(r.left(), <BA256 as SharedValue>::ZERO),
AdditiveShare::new(<BA256 as SharedValue>::ZERO, r.right()),
),
}
};
Expand Down Expand Up @@ -162,22 +162,22 @@ where
.await?;

// this leaks information, but with negligible probability
let y = AdditiveShare::<BA256>(sh_y.left(), sh_y.right())
let y = AdditiveShare::<BA256>::new(sh_y.left(), sh_y.right())
.partial_reveal(ctx.narrow(&Step::RevealY), record_id, Role::H3)
.await?;

match ctx.role() {
Role::H1 => Ok(AdditiveShare::<Fp25519>(
Fp25519::from(sh_s.0).neg(),
Role::H1 => Ok(AdditiveShare::<Fp25519>::new(
Fp25519::from(sh_s.left()).neg(),
Fp25519::from(y.unwrap()),
)),
Role::H2 => Ok(AdditiveShare::<Fp25519>(
Role::H2 => Ok(AdditiveShare::<Fp25519>::new(
Fp25519::from(y.unwrap()),
Fp25519::from(sh_r.1).neg(),
Fp25519::from(sh_r.right()).neg(),
)),
Role::H3 => Ok(AdditiveShare::<Fp25519>(
Fp25519::from(sh_r.0).neg(),
Fp25519::from(sh_s.1).neg(),
Role::H3 => Ok(AdditiveShare::<Fp25519>::new(
Fp25519::from(sh_r.left()).neg(),
Fp25519::from(sh_s.right()).neg(),
)),
}
}
Expand Down Expand Up @@ -236,25 +236,6 @@ where
y
}

/// inserts a smaller array into a larger
/// allows share conversion between secret shared Boolean Array types like 'BA64' and 'BA256'
/// only used for testing purposes
#[cfg(all(test, unit_test))]
pub fn expand_shared_array<XS, YS>(
x: &AdditiveShare<XS>,
offset: Option<usize>,
) -> AdditiveShare<YS>
where
XS: CustomArray + SharedValue,
YS: CustomArray<Element = XS::Element> + SharedValue,
XS::Element: SharedValue,
{
AdditiveShare::<YS>(
expand_array(&x.left(), offset),
expand_array(&x.right(), offset),
)
}

#[cfg(all(test, unit_test))]
mod tests {
use curve25519_dalek::Scalar;
Expand All @@ -273,11 +254,11 @@ mod tests {
protocol::{
context::Context,
ipa_prf::boolean_ops::share_conversion_aby::{
convert_to_fp25519, expand_array, expand_shared_array,
convert_to_fp25519, expand_array,
},
},
rand::thread_rng,
secret_sharing::{replicated::semi_honest::AdditiveShare, SharedValue},
secret_sharing::SharedValue,
test_executor::run,
test_fixture::{Reconstruct, Runner, TestWorld},
};
Expand Down Expand Up @@ -319,21 +300,13 @@ mod tests {

let a = rng.gen::<BA64>();

let shared_a = AdditiveShare::<BA64>(rng.gen::<BA64>(), rng.gen::<BA64>());

let b = expand_array::<_, BA256>(&a, None);

let shared_b = expand_shared_array::<_, BA256>(&shared_a, None);

for i in 0..BA256::BITS as usize {
assert_eq!(
(i, b.get(i).unwrap_or(Boolean::ZERO)),
(i, a.get(i).unwrap_or(Boolean::ZERO))
);
assert_eq!(
(i, shared_b.get(i).unwrap_or(AdditiveShare::<Boolean>::ZERO)),
(i, shared_a.get(i).unwrap_or(AdditiveShare::<Boolean>::ZERO))
);
}
}
}
Loading

0 comments on commit 28836e3

Please sign in to comment.