Skip to content

Commit

Permalink
Add IO wrapper for OLE (#138)
Browse files Browse the repository at this point in the history
* Add `mpz-ole` content of old branch.

* Reworked message enum.

* Refactored to work with new `mpz-ole-core`.

* Add part of feedback.

* Add more feedback.

* Add opaque error type.

* Add `Display` for `OLEErrorKind`

* Use `ok_or_elese` for lazy heap alloc.

* Adapted `mpz-ole` to `hybrid-array`.

* WIP: Improving API of const generics...

* Add random OT for `hybrid-array`.

* Adapt `mpz-ole` to use new random OT.

* Added feedback.

* Use random OT over field elements instead of arrays.

* Refactored ideal implementation to use `mpz-common`.

* Added more feedback.
  • Loading branch information
th4s committed May 31, 2024
1 parent 5cb1aec commit 8f0b298
Show file tree
Hide file tree
Showing 13 changed files with 569 additions and 10 deletions.
2 changes: 2 additions & 0 deletions crates/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"matrix-transpose",
"clmul",
"mpz-ole-core",
"mpz-ole"
]
resolver = "2"

Expand All @@ -39,6 +40,7 @@ mpz-garble = { path = "mpz-garble" }
mpz-garble-core = { path = "mpz-garble-core" }
mpz-share-conversion = { path = "mpz-share-conversion" }
mpz-share-conversion-core = { path = "mpz-share-conversion-core" }
mpz-ole = { path = "mpz-ole" }
mpz-ole-core = { path = "mpz-ole-core" }
clmul = { path = "clmul" }
matrix-transpose = { path = "matrix-transpose" }
Expand Down
1 change: 1 addition & 0 deletions crates/mpz-fields/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ serde.workspace = true
itybity.workspace = true
typenum.workspace = true
hybrid-array.workspace = true
thiserror.workspace = true

[dev-dependencies]
ghash_rc.workspace = true
Expand Down
20 changes: 16 additions & 4 deletions crates/mpz-fields/src/gf2_128.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//! This module implements the extension field GF(2^128).

use std::ops::{Add, Mul, Neg};

use hybrid_array::Array;
use itybity::{BitLength, FromBitIterator, GetBit, Lsb0, Msb0};
use rand::{distributions::Standard, prelude::Distribution};
use serde::{Deserialize, Serialize};
use std::ops::{Add, Mul, Neg};

use mpz_core::Block;
use typenum::U128;
use typenum::{U128, U16};

use crate::Field;
use crate::{Field, FieldError};

/// A type for holding field elements of Gf(2^128).
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -44,6 +44,16 @@ impl From<Block> for Gf2_128 {
}
}

impl TryFrom<Array<u8, U16>> for Gf2_128 {
type Error = FieldError;

fn try_from(value: Array<u8, U16>) -> Result<Self, Self::Error> {
let inner: [u8; 16] = value.into();

Ok(Gf2_128(u128::from_be_bytes(inner)))
}
}

impl Distribution<Gf2_128> for Standard {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Gf2_128 {
Gf2_128(self.sample(rng))
Expand Down Expand Up @@ -101,6 +111,8 @@ impl Neg for Gf2_128 {
impl Field for Gf2_128 {
type BitSize = U128;

type ByteSize = U16;

fn zero() -> Self {
Self::new(0)
}
Expand Down
16 changes: 15 additions & 1 deletion crates/mpz-fields/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ pub mod gf2_128;
pub mod p256;

use std::{
error::Error,
fmt::Debug,
ops::{Add, Mul, Neg},
};

use hybrid_array::ArraySize;
use hybrid_array::{Array, ArraySize};
use itybity::{BitLength, FromBitIterator, GetBit, Lsb0, Msb0};
use rand::{distributions::Standard, prelude::Distribution, Rng};
use thiserror::Error;
use typenum::Unsigned;

/// A trait for finite fields.
Expand All @@ -38,13 +40,20 @@ pub trait Field:
+ GetBit<Msb0>
+ BitLength
+ Unpin
+ TryFrom<Array<u8, Self::ByteSize>, Error = FieldError>
{
/// The number of bits of a field element.
const BIT_SIZE: usize = <Self::BitSize as Unsigned>::USIZE;

/// The number of bytes of a field element.
const BYTE_SIZE: usize = <Self::ByteSize as Unsigned>::USIZE;

/// The number of bits of a field element as a type number.
type BitSize: ArraySize;

/// The number of bytes of a field element as a type number.
type ByteSize: ArraySize;

/// Return the additive identity element.
fn zero() -> Self;

Expand All @@ -64,6 +73,11 @@ pub trait Field:
fn to_be_bytes(&self) -> Vec<u8>;
}

/// Error type for finite fields.
#[derive(Debug, Error)]
#[error(transparent)]
pub struct FieldError(Box<dyn Error + Send + Sync + 'static>);

/// A trait for sampling random elements of the field.
///
/// This is helpful, because we do not need to import other traits since this is a supertrait of
Expand Down
33 changes: 28 additions & 5 deletions crates/mpz-fields/src/p256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ use std::ops::{Add, Mul, Neg};

use ark_ff::{BigInt, BigInteger, Field as ArkField, FpConfig, MontBackend, One, Zero};
use ark_secp256r1::{fq::Fq, FqConfig};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate};
use ark_serialize::{
CanonicalDeserialize, CanonicalSerialize, Compress, SerializationError, Validate,
};
use hybrid_array::Array;
use itybity::{BitLength, FromBitIterator, GetBit, Lsb0, Msb0};
use num_bigint::ToBigUint;
use rand::{distributions::Standard, prelude::Distribution};
use serde::{Deserialize, Serialize};
use typenum::U256;
use thiserror::Error;
use typenum::{U256, U32};

use crate::Field;
use crate::{Field, FieldError};

/// A type for holding field elements of P256.
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -42,11 +46,23 @@ impl From<P256> for [u8; 32] {
}

impl TryFrom<[u8; 32]> for P256 {
type Error = ark_serialize::SerializationError;
type Error = FieldError;

/// Converts little-endian bytes into a P256 field element.
fn try_from(value: [u8; 32]) -> Result<Self, Self::Error> {
Fq::deserialize_with_mode(&value[..], Compress::No, Validate::Yes).map(P256)
Fq::deserialize_with_mode(&value[..], Compress::No, Validate::Yes)
.map(P256)
.map_err(|err| FieldError(Box::new(P256Error(err))))
}
}

impl TryFrom<Array<u8, U32>> for P256 {
type Error = FieldError;

fn try_from(value: Array<u8, U32>) -> Result<Self, Self::Error> {
let inner: [u8; 32] = value.into();

P256::try_from(inner)
}
}

Expand Down Expand Up @@ -83,6 +99,8 @@ impl Neg for P256 {
impl Field for P256 {
type BitSize = U256;

type ByteSize = U32;

fn zero() -> Self {
P256(<Fq as Zero>::zero())
}
Expand Down Expand Up @@ -139,6 +157,11 @@ impl FromBitIterator for P256 {
}
}

/// Helper type because [`SerializationError`] does not implement std::error::Error.
#[derive(Debug, Error)]
#[error("{0}")]
pub struct P256Error(SerializationError);

#[cfg(test)]
mod tests {
use super::*;
Expand Down
5 changes: 5 additions & 0 deletions crates/mpz-ole-core/src/receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ impl<F: Field> OLEReceiver<F> {

Some((receiver_adjust, adjustments))
}

/// Returns the number of preprocessed OLEs that are available.
pub fn cache_size(&self) -> usize {
self.cache.len()
}
}

/// Receiver adjustments waiting for [`BatchAdjust`] from the sender.
Expand Down
5 changes: 5 additions & 0 deletions crates/mpz-ole-core/src/sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ impl<F: Field> OLESender<F> {

Some((sender_adjust, adjustments))
}

/// Returns the number of preprocessed OLEs that are available.
pub fn cache_size(&self) -> usize {
self.cache.len()
}
}

/// Sender adjustments waiting for [`BatchAdjust`] from the receiver.
Expand Down
37 changes: 37 additions & 0 deletions crates/mpz-ole/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "mpz-ole"
version = "0.1.0"
edition = "2021"

[lib]
name = "mpz_ole"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
ideal = ["mpz-common/ideal"]

[dependencies]
mpz-fields.workspace = true
mpz-ot.workspace = true
mpz-core.workspace = true
mpz-ole-core.workspace = true
mpz-common.workspace = true

serio.workspace = true

thiserror.workspace = true
async-trait.workspace = true
futures.workspace = true
rand.workspace = true
itybity.workspace = true

[dev-dependencies]
tokio = { workspace = true, features = [
"net",
"macros",
"rt",
"rt-multi-thread",
] }
mpz-common = { workspace = true, features = ["test-utils", "ideal"] }
mpz-ot = { workspace = true, features = ["ideal"] }
87 changes: 87 additions & 0 deletions crates/mpz-ole/src/ideal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Ideal OLE implementation.

use crate::{OLEError, OLEReceiver, OLESender};
use async_trait::async_trait;
use mpz_common::{
ideal::{ideal_f2p, Alice, Bob},
Context,
};
use mpz_fields::Field;
use rand::thread_rng;

/// Ideal OLESender.
pub struct IdealOLESender(Alice<()>);

/// Ideal OLEReceiver.
pub struct IdealOLEReceiver(Bob<()>);

/// Returns an OLE sender and receiver pair.
pub fn ideal_ole() -> (IdealOLESender, IdealOLEReceiver) {
let (alice, bob) = ideal_f2p(());

(IdealOLESender(alice), IdealOLEReceiver(bob))
}

fn ole<F: Field>(_: &mut (), alice_input: Vec<F>, bob_input: Vec<F>) -> (Vec<F>, Vec<F>) {
let mut rng = thread_rng();
let alice_output: Vec<F> = (0..alice_input.len()).map(|_| F::rand(&mut rng)).collect();

let bob_output: Vec<F> = alice_input
.iter()
.zip(bob_input.iter())
.zip(alice_output.iter().copied())
.map(|((&a, &b), x)| a * b + x)
.collect();

(alice_output, bob_output)
}

#[async_trait]
impl<F: Field, Ctx: Context> OLESender<Ctx, F> for IdealOLESender {
async fn send(&mut self, ctx: &mut Ctx, a_k: Vec<F>) -> Result<Vec<F>, OLEError> {
Ok(self.0.call(ctx, a_k, ole).await)
}
}

#[async_trait]
impl<F: Field, Ctx: Context> OLEReceiver<Ctx, F> for IdealOLEReceiver {
async fn receive(&mut self, ctx: &mut Ctx, b_k: Vec<F>) -> Result<Vec<F>, OLEError> {
Ok(self.0.call(ctx, b_k, ole).await)
}
}

#[cfg(test)]
mod tests {
use crate::{ideal::ideal_ole, OLEReceiver, OLESender};
use mpz_common::executor::test_st_executor;
use mpz_core::{prg::Prg, Block};
use mpz_fields::{p256::P256, UniformRand};
use rand::SeedableRng;

#[tokio::test]
async fn test_ideal_ole() {
let count = 12;
let mut rng = Prg::from_seed(Block::ZERO);

let a_k: Vec<P256> = (0..count).map(|_| P256::rand(&mut rng)).collect();
let b_k: Vec<P256> = (0..count).map(|_| P256::rand(&mut rng)).collect();

let (mut ctx_sender, mut ctx_receiver) = test_st_executor(10);

let (mut sender, mut receiver) = ideal_ole();

let (x_k, y_k) = tokio::try_join!(
sender.send(&mut ctx_sender, a_k.clone()),
receiver.receive(&mut ctx_receiver, b_k.clone())
)
.unwrap();

assert_eq!(x_k.len(), count);
assert_eq!(y_k.len(), count);
a_k.iter()
.zip(b_k)
.zip(x_k)
.zip(y_k)
.for_each(|(((&a, b), x), y)| assert_eq!(y, a * b + x));
}
}
Loading

0 comments on commit 8f0b298

Please sign in to comment.