mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-09 21:38:00 -05:00
Refactor of gf2_128 crate (#123)
* WIP: Refactoring a2m/m2a...
* WIP: Refactor homomorphic subcrate...
* Implemented aio homomorph sender
* Removed integration test for now and renamed things
* Implemented homomorph aio receiver
* Improved API
* Rename crate homomorph -> share_conversion
* Rename errors and traits
* Migrated changes from branch `ghash-refactor-again`
* Improvements to share-conversion
- Renamed share_conversion to share-conversion
- Got rid of PhantomData for aio layer
* Improved variable naming and some doc
* Added aio unit tests and various improvements
- documentation
- variable naming
* Added TODO comment for OT integration test
* Added part of feedback
* Separated share-conversion into {share-converison-core, share-conversion-aio}
* Sample from NonZeroU128 for `random` in a2m
* misc comments and code simplifications
Co-authored-by: themighty1 <you@example.com>
This commit is contained in:
@@ -11,7 +11,8 @@ members = [
|
||||
"utils-aio",
|
||||
"clmul",
|
||||
"matrix-transpose",
|
||||
"gf2-128"
|
||||
"share-conversion-core",
|
||||
"share-conversion-aio"
|
||||
]
|
||||
exclude = ["tls-client"]
|
||||
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
//! This subcrate implements secure two-party (2PC) multiplication-to-addition (M2A) and
|
||||
//! addition-to-multiplication (A2M) algorithms, both with semi-honest security for elements
|
||||
//! of GF(2^128).
|
||||
//!
|
||||
//! ### M2A algorithm
|
||||
//! Let `A` be an element of some finite field with `A = a * b`, where `a` is only known to Alice
|
||||
//! and `b` is only known to Bob. A is unknown to both parties and it is their goal that each of
|
||||
//! them ends up with an additive share of A. So both parties start with `a` and `b` and want to
|
||||
//! end up with `x` and `y`, where `A = a * b = x + y`.
|
||||
//!
|
||||
//! This is an implementation for the extension field GF(2^128), which uses the oblivious transfer
|
||||
//! method in chapter 4.1 of <https://link.springer.com/content/pdf/10.1007/3-540-48405-1_8.pdf>
|
||||
//!
|
||||
//! ### A2M algorithm
|
||||
//! This is the other way round.
|
||||
//! Let `A` be an element of some finite field with `A = x + y`, where `x` is only known to Alice
|
||||
//! and `y` is only known to Bob. A is unknown to both parties and it is their goal that each of
|
||||
//! them ends up with a multiplicative share of A. So both parties start with `x` and `y` and want to
|
||||
//! end up with `a` and `b`, where `A = x + y = a * b`.
|
||||
//!
|
||||
//! This is an implementation for the extension field GF(2^128), which is a semi-honest adaptation
|
||||
//! of the "A2M Protocol" in chapter 4 of <https://www.cs.umd.edu/~fenghao/paper/modexp.pdf>
|
||||
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
/// Masked values for an oblivious transfer
|
||||
pub struct MaskedPartialValue(pub [u128; 128], pub [u128; 128]);
|
||||
|
||||
/// A multiplicative share of `A = a * b`
|
||||
pub struct MulShare(u128);
|
||||
|
||||
impl MulShare {
|
||||
/// Create a new `MulShare` holding a factor of `A`
|
||||
pub fn new(share: u128) -> Self {
|
||||
Self(share)
|
||||
}
|
||||
|
||||
/// Return inner share
|
||||
pub fn inner(&self) -> u128 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Turn into an additive share and masked partial values
|
||||
///
|
||||
/// This function returns
|
||||
/// * `AddShare` - The sender's additive share; this is `y` in the paper
|
||||
/// * `MaskedPartialValue` - Used for oblivious transfer; t0 and t1 in the paper
|
||||
pub fn to_additive(&self) -> (AddShare, MaskedPartialValue) {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
|
||||
let t0: [u128; 128] = std::array::from_fn(|_| rng.gen());
|
||||
let t1: [u128; 128] = std::array::from_fn(|i| mul(self.inner(), 1 << i) ^ t0[i]);
|
||||
|
||||
let add_share = AddShare::new(t0.into_iter().fold(0, |acc, i| acc ^ i));
|
||||
(add_share, MaskedPartialValue(t0, t1))
|
||||
}
|
||||
|
||||
/// Create a multiplicative share from the output of an OT
|
||||
///
|
||||
/// The `value` needs to be built by choices of an oblivious transfer
|
||||
pub fn from_choice(value: [u128; 128]) -> Self {
|
||||
Self::new(value.into_iter().fold(0, |acc, i| acc ^ i))
|
||||
}
|
||||
}
|
||||
|
||||
/// An additive share of `A = x + y`
|
||||
pub struct AddShare(u128);
|
||||
|
||||
impl AddShare {
|
||||
/// Create a new `AddShare` holding a summand of `A`
|
||||
pub fn new(share: u128) -> Self {
|
||||
Self(share)
|
||||
}
|
||||
|
||||
/// Return inner share
|
||||
pub fn inner(&self) -> u128 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Turn into a multiplicative share and masked partial values
|
||||
///
|
||||
/// This function returns
|
||||
/// * `MulShare` - The sender's multiplicative share
|
||||
/// * `MaskedPartialValue` - Used for oblivious transfer
|
||||
pub fn to_multiplicative(&self) -> (MulShare, MaskedPartialValue) {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
|
||||
let random: u128 = rng.gen();
|
||||
if random == 0 {
|
||||
panic!("Random u128 is 0");
|
||||
}
|
||||
|
||||
let mut masks: [u128; 128] = std::array::from_fn(|_| rng.gen());
|
||||
// set the last mask such that the sum of all 128 masks equals 0
|
||||
masks[127] = masks.into_iter().take(127).fold(0, |acc, i| acc ^ i);
|
||||
|
||||
let mul_share = MulShare::new(inverse(random));
|
||||
|
||||
// `self.inner() & (1 << i)` extracts bit of `self.inner()` in position `i` (counting from
|
||||
// the right) shifted left by `i`
|
||||
let b0: [u128; 128] =
|
||||
std::array::from_fn(|i| mul(self.inner() & (1 << i), random) ^ masks[i]);
|
||||
let b1: [u128; 128] =
|
||||
std::array::from_fn(|i| mul((self.inner() & (1 << i)) ^ (1 << i), random) ^ masks[i]);
|
||||
|
||||
(mul_share, MaskedPartialValue(b0, b1))
|
||||
}
|
||||
|
||||
/// Create an additive share from the output of an OT
|
||||
///
|
||||
/// The `value` needs to be built by choices of an oblivious transfer
|
||||
pub fn from_choice(value: [u128; 128]) -> Self {
|
||||
Self::new(value.into_iter().fold(0, |acc, i| acc ^ i))
|
||||
}
|
||||
}
|
||||
|
||||
/// R is GCM polynomial in little-endian. In hex: "E1000000000000000000000000000000"
|
||||
const R: u128 = 299076299051606071403356588563077529600;
|
||||
|
||||
/// Galois field multiplication of two 128-bit blocks reduced by the GCM polynomial
|
||||
pub fn mul(mut x: u128, y: u128) -> u128 {
|
||||
let mut result: u128 = 0;
|
||||
for i in (0..128).rev() {
|
||||
result ^= x * ((y >> i) & 1);
|
||||
x = (x >> 1) ^ ((x & 1) * R);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Galois field inversion of 128-bit block
|
||||
pub fn inverse(mut x: u128) -> u128 {
|
||||
let one = 1 << 127;
|
||||
let mut out = one;
|
||||
|
||||
for _ in 0..127 {
|
||||
x = mul(x, x);
|
||||
out = mul(out, x);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ghash_rc::universal_hash::NewUniversalHash;
|
||||
use ghash_rc::universal_hash::UniversalHash;
|
||||
use ghash_rc::GHash;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
fn ot_mock(envelopes: ([u128; 128], [u128; 128]), choices: u128) -> [u128; 128] {
|
||||
let mut out = [0_u128; 128];
|
||||
for (k, number) in out.iter_mut().enumerate() {
|
||||
let bit = (choices >> k) & 1;
|
||||
*number = (bit * envelopes.1[k]) ^ ((bit ^ 1) * envelopes.0[k]);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_m2a() {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
let a: MulShare = MulShare::new(rng.gen());
|
||||
let b: MulShare = MulShare::new(rng.gen());
|
||||
|
||||
let (x, MaskedPartialValue(t0, t1)) = a.to_additive();
|
||||
|
||||
let choice = ot_mock((t0, t1), b.inner());
|
||||
let y = AddShare::from_choice(choice);
|
||||
|
||||
assert_eq!(mul(a.inner(), b.inner()), x.inner() ^ y.inner());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a2m() {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
let x: AddShare = AddShare::new(rng.gen());
|
||||
let y: AddShare = AddShare::new(rng.gen());
|
||||
|
||||
let (a, MaskedPartialValue(t0, t1)) = x.to_multiplicative();
|
||||
|
||||
let choice = ot_mock((t0, t1), y.inner());
|
||||
let b = MulShare::from_choice(choice);
|
||||
|
||||
assert_eq!(x.inner() ^ y.inner(), mul(a.inner(), b.inner()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Test multiplication against RustCrypto
|
||||
fn test_mul() {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
let a: u128 = rng.gen();
|
||||
let b: u128 = rng.gen();
|
||||
|
||||
let mut g = GHash::new(&a.to_be_bytes().into());
|
||||
g.update(&b.to_be_bytes().into());
|
||||
// Ghash will internally multiply a and b
|
||||
let expected = g.finalize();
|
||||
|
||||
assert_eq!(
|
||||
mul(a, b),
|
||||
u128::from_be_bytes(expected.into_bytes().try_into().unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inverse() {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
let a: u128 = rng.gen();
|
||||
let inverse_a = inverse(a);
|
||||
|
||||
assert_eq!(mul(a, inverse_a), 1_u128 << 127);
|
||||
assert_eq!(inverse(1_u128 << 127), 1_u128 << 127);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
use mpc_core::Block;
|
||||
|
||||
pub fn u128_to_bool(a: u128) -> [bool; 128] {
|
||||
let mut out = [false; 128];
|
||||
for (k, item) in out.iter_mut().enumerate() {
|
||||
*item = (a >> k & 1) == 1
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn interleave_to_blocks(values: ([u128; 128], [u128; 128])) -> Vec<[Block; 2]> {
|
||||
let mut out = vec![];
|
||||
for (first, second) in values.0.into_iter().zip(values.1.into_iter()) {
|
||||
out.push([Block::new(first), Block::new(second)]);
|
||||
}
|
||||
out
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
use gf2_128::{mul, AddShare, MaskedPartialValue, MulShare};
|
||||
use mpc_aio::protocol::ot::kos::receiver::Kos15IOReceiver;
|
||||
use mpc_aio::protocol::ot::kos::sender::Kos15IOSender;
|
||||
use mpc_aio::protocol::ot::{ObliviousReceive, ObliviousSend};
|
||||
use mpc_core::msgs::ot::OTMessage;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
use utils_aio::duplex::DuplexChannel;
|
||||
|
||||
pub mod helper;
|
||||
use helper::{interleave_to_blocks, u128_to_bool};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_m2a_ot() {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
|
||||
// Prepare multiplicative shares and encoding
|
||||
let a: MulShare = MulShare::new(rng.gen());
|
||||
let b: MulShare = MulShare::new(rng.gen());
|
||||
let (x, MaskedPartialValue(t0, t1)) = a.to_additive();
|
||||
|
||||
// Prepare inputs/outputs for OT
|
||||
let choices = u128_to_bool(b.inner());
|
||||
let blocks = interleave_to_blocks((t0, t1));
|
||||
|
||||
//Send via KOS OT
|
||||
let (channel, channel_2) = DuplexChannel::<OTMessage>::new();
|
||||
let (sender, receiver) = (
|
||||
Kos15IOSender::new(Box::new(channel)),
|
||||
Kos15IOReceiver::new(Box::new(channel_2)),
|
||||
);
|
||||
let send = tokio::spawn(async {
|
||||
let mut sender = sender.rand_setup(128).await.unwrap();
|
||||
sender.send(blocks).await.unwrap();
|
||||
});
|
||||
let receive = tokio::spawn(async move {
|
||||
let mut receiver = receiver.rand_setup(128).await.unwrap();
|
||||
receiver.receive(&choices).await.unwrap()
|
||||
});
|
||||
|
||||
let (_, output) = tokio::join!(send, receive);
|
||||
|
||||
// Turn output into additive share for receiver
|
||||
let output: [u128; 128] = output
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|block| block.inner())
|
||||
.collect::<Vec<u128>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let y = AddShare::from_choice(output);
|
||||
|
||||
assert_eq!(mul(a.inner(), b.inner()), x.inner() ^ y.inner());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_a2m_ot() {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
|
||||
// Prepare additive shares and encoding
|
||||
let x: AddShare = AddShare::new(rng.gen());
|
||||
let y: AddShare = AddShare::new(rng.gen());
|
||||
let (a, MaskedPartialValue(t0, t1)) = x.to_multiplicative();
|
||||
|
||||
// Prepare inputs/outputs for OT
|
||||
let choices = u128_to_bool(y.inner());
|
||||
let blocks = interleave_to_blocks((t0, t1));
|
||||
|
||||
//Send via KOS OT
|
||||
let (channel, channel_2) = DuplexChannel::<OTMessage>::new();
|
||||
let (sender, receiver) = (
|
||||
Kos15IOSender::new(Box::new(channel)),
|
||||
Kos15IOReceiver::new(Box::new(channel_2)),
|
||||
);
|
||||
let send = tokio::spawn(async {
|
||||
let mut sender = sender.rand_setup(128).await.unwrap();
|
||||
sender.send(blocks).await.unwrap();
|
||||
});
|
||||
let receive = tokio::spawn(async move {
|
||||
let mut receiver = receiver.rand_setup(128).await.unwrap();
|
||||
receiver.receive(&choices).await.unwrap()
|
||||
});
|
||||
|
||||
let (_, output) = tokio::join!(send, receive);
|
||||
|
||||
// Turn output into multiplicative share for receiver
|
||||
let output: [u128; 128] = output
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|block| block.inner())
|
||||
.collect::<Vec<u128>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let b = MulShare::from_choice(output);
|
||||
|
||||
assert_eq!(x.inner() ^ y.inner(), mul(a.inner(), b.inner()));
|
||||
}
|
||||
@@ -1,7 +1,57 @@
|
||||
use super::{OTError, ObliviousReceive, ObliviousSend};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::{
|
||||
OTError, OTFactoryError, OTReceiverFactory, OTSenderFactory, ObliviousReceive, ObliviousSend,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MockOTFactory<T> {
|
||||
waiting_sender: Option<MockOTSender<T>>,
|
||||
waiting_receiver: Option<MockOTReceiver<T>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Send + 'static> OTSenderFactory for Arc<Mutex<MockOTFactory<T>>> {
|
||||
type Protocol = MockOTSender<T>;
|
||||
|
||||
async fn new_sender(
|
||||
&mut self,
|
||||
_id: String,
|
||||
_count: usize,
|
||||
) -> Result<Self::Protocol, OTFactoryError> {
|
||||
let mut inner = self.lock().unwrap();
|
||||
if inner.waiting_sender.is_some() {
|
||||
Ok(inner.waiting_sender.take().unwrap())
|
||||
} else {
|
||||
let (sender, receiver) = mock_ot_pair::<T>();
|
||||
inner.waiting_receiver = Some(receiver);
|
||||
Ok(sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Send + 'static> OTReceiverFactory for Arc<Mutex<MockOTFactory<T>>> {
|
||||
type Protocol = MockOTReceiver<T>;
|
||||
|
||||
async fn new_receiver(
|
||||
&mut self,
|
||||
_id: String,
|
||||
_count: usize,
|
||||
) -> Result<Self::Protocol, OTFactoryError> {
|
||||
let mut inner = self.lock().unwrap();
|
||||
if inner.waiting_receiver.is_some() {
|
||||
Ok(inner.waiting_receiver.take().unwrap())
|
||||
} else {
|
||||
let (sender, receiver) = mock_ot_pair::<T>();
|
||||
inner.waiting_sender = Some(sender);
|
||||
Ok(receiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MockOTSender<T> {
|
||||
sender: mpsc::Sender<Vec<[T; 2]>>,
|
||||
}
|
||||
|
||||
@@ -32,6 +32,22 @@ pub enum OTError {
|
||||
Unexpected(OTMessage),
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum OTFactoryError {
|
||||
// #[error("muxer error")]
|
||||
// MuxerError(#[from] MuxerError),
|
||||
#[error("ot error")]
|
||||
OTError(#[from] OTError),
|
||||
#[error("io error")]
|
||||
IOError(#[from] std::io::Error),
|
||||
// #[error("unexpected message")]
|
||||
// UnexpectedMessage(OTFactoryMessage),
|
||||
#[error("{0} Sender expects {1} OTs, Receiver expects {2}")]
|
||||
SplitMismatch(String, usize, usize),
|
||||
#[error("other: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ObliviousSend {
|
||||
type Inputs;
|
||||
@@ -73,6 +89,36 @@ pub trait ObliviousVerify {
|
||||
async fn verify(self, input: Vec<Self::Input>) -> Result<(), OTError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait OTSenderFactory {
|
||||
type Protocol: ObliviousSend + Send;
|
||||
|
||||
/// Constructs a new Sender
|
||||
///
|
||||
/// * `id` - Instance id
|
||||
/// * `count` - Number of OTs to provision
|
||||
async fn new_sender(
|
||||
&mut self,
|
||||
id: String,
|
||||
count: usize,
|
||||
) -> Result<Self::Protocol, OTFactoryError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait OTReceiverFactory {
|
||||
type Protocol: ObliviousReceive + Send;
|
||||
|
||||
/// Constructs a new Receiver
|
||||
///
|
||||
/// * `id` - Instance id
|
||||
/// * `count` - Number of OTs to provision
|
||||
async fn new_receiver(
|
||||
&mut self,
|
||||
id: String,
|
||||
count: usize,
|
||||
) -> Result<Self::Protocol, OTFactoryError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mockall::mock! {
|
||||
pub ObliviousSender {}
|
||||
|
||||
18
share-conversion-aio/Cargo.toml
Normal file
18
share-conversion-aio/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "share-conversion-aio"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tlsn-mpc-aio = { path = "../mpc-aio" }
|
||||
tlsn-mpc-core = { path = "../mpc-core" }
|
||||
async-trait.workspace = true
|
||||
rand.workspace = true
|
||||
rand_chacha.workspace = true
|
||||
share-conversion-core = { path = "../share-conversion-core" }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio.workspace = true
|
||||
105
share-conversion-aio/src/gf2_128/mod.rs
Normal file
105
share-conversion-aio/src/gf2_128/mod.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
//! This module implements the IO layer of share-conversion for field elements of
|
||||
//! GF(2^128), using oblivious transfer.
|
||||
|
||||
use share_conversion_core::gf2_128::{AddShare, Gf2_128ShareConvert, MulShare, OTEnvelope};
|
||||
|
||||
mod receiver;
|
||||
mod sender;
|
||||
|
||||
pub use receiver::Receiver;
|
||||
pub use sender::Sender;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::*;
|
||||
use mpc_aio::protocol::ot::mock::MockOTFactory;
|
||||
use mpc_core::Block;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
use share_conversion_core::gf2_128::mul;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aio_a2m() {
|
||||
let (mut sender, mut receiver) = mock_converter_pair();
|
||||
|
||||
// Create some random numbers
|
||||
let random_numbers_1: Vec<u128> = get_random_gf2_128_vec(128);
|
||||
let random_numbers_2: Vec<u128> = get_random_gf2_128_vec(128);
|
||||
let random_numbers: Vec<u128> =
|
||||
std::iter::zip(random_numbers_1.iter(), random_numbers_2.iter())
|
||||
.map(|(a, b)| a ^ b)
|
||||
.collect();
|
||||
|
||||
// Spawn tokio tasks and wait for them to finish
|
||||
let sender_task = tokio::spawn(async move {
|
||||
sender
|
||||
.convert_from::<AddShare>(&random_numbers_1)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
let receiver_task = tokio::spawn(async move {
|
||||
receiver
|
||||
.convert_from::<AddShare>(&random_numbers_2)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
let (sender_output, receiver_output) = tokio::join!(sender_task, receiver_task);
|
||||
let (sender_output, receiver_output) = (sender_output.unwrap(), receiver_output.unwrap());
|
||||
|
||||
// Check result
|
||||
for (k, (a, b)) in std::iter::zip(sender_output, receiver_output).enumerate() {
|
||||
assert_eq!(mul(a, b), random_numbers[k]);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_aio_m2a() {
|
||||
let (mut sender, mut receiver) = mock_converter_pair();
|
||||
|
||||
// Create some random numbers
|
||||
let random_numbers_1: Vec<u128> = get_random_gf2_128_vec(128);
|
||||
let random_numbers_2: Vec<u128> = get_random_gf2_128_vec(128);
|
||||
let random_numbers: Vec<u128> =
|
||||
std::iter::zip(random_numbers_1.iter(), random_numbers_2.iter())
|
||||
.map(|(a, b)| mul(*a, *b))
|
||||
.collect();
|
||||
|
||||
// Spawn tokio tasks and wait for them to finish
|
||||
let sender_task = tokio::spawn(async move {
|
||||
sender
|
||||
.convert_from::<MulShare>(&random_numbers_1)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
let receiver_task = tokio::spawn(async move {
|
||||
receiver
|
||||
.convert_from::<MulShare>(&random_numbers_2)
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
let (sender_output, receiver_output) = tokio::join!(sender_task, receiver_task);
|
||||
let (sender_output, receiver_output) = (sender_output.unwrap(), receiver_output.unwrap());
|
||||
|
||||
// Check result
|
||||
for (k, (a, b)) in std::iter::zip(sender_output, receiver_output).enumerate() {
|
||||
assert_eq!(a ^ b, random_numbers[k]);
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_converter_pair() -> (
|
||||
Sender<Arc<Mutex<MockOTFactory<Block>>>>,
|
||||
Receiver<Arc<Mutex<MockOTFactory<Block>>>>,
|
||||
) {
|
||||
let ot_factory = Arc::new(Mutex::new(MockOTFactory::<Block>::default()));
|
||||
let sender = Sender::new(Arc::clone(&ot_factory), String::from(""));
|
||||
let receiver = Receiver::new(Arc::clone(&ot_factory), String::from(""));
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
fn get_random_gf2_128_vec(len: usize) -> Vec<u128> {
|
||||
let mut rng = ChaCha12Rng::from_seed([0; 32]);
|
||||
(0..len).map(|_| rng.gen::<u128>()).collect()
|
||||
}
|
||||
}
|
||||
90
share-conversion-aio/src/gf2_128/receiver.rs
Normal file
90
share-conversion-aio/src/gf2_128/receiver.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
//! This module implements the async IO receiver
|
||||
|
||||
use super::{AddShare, Gf2_128ShareConvert, MulShare};
|
||||
use crate::{AdditiveToMultiplicative, MultiplicativeToAdditive, ShareConversionError};
|
||||
use async_trait::async_trait;
|
||||
use mpc_aio::protocol::ot::{OTReceiverFactory, ObliviousReceive};
|
||||
use mpc_core::Block;
|
||||
|
||||
/// The receiver for the conversion
|
||||
///
|
||||
/// Will be the OT receiver
|
||||
pub struct Receiver<T: OTReceiverFactory> {
|
||||
receiver_factory: T,
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl<
|
||||
T: OTReceiverFactory<Protocol = U> + Send,
|
||||
U: ObliviousReceive<Choice = bool, Outputs = Vec<Block>>,
|
||||
> Receiver<T>
|
||||
{
|
||||
/// Creates a new receiver
|
||||
pub fn new(receiver_factory: T, id: String) -> Self {
|
||||
Self {
|
||||
receiver_factory,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the shares using oblivious transfer
|
||||
pub(crate) async fn convert_from<V: Gf2_128ShareConvert>(
|
||||
&mut self,
|
||||
shares: &[u128],
|
||||
) -> Result<Vec<u128>, ShareConversionError> {
|
||||
if shares.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let mut choices: Vec<bool> = vec![];
|
||||
shares.iter().for_each(|x| {
|
||||
let share = V::new(*x).choices();
|
||||
choices.extend_from_slice(&share);
|
||||
});
|
||||
let mut ot_receiver = self
|
||||
.receiver_factory
|
||||
.new_receiver(self.id.clone(), choices.len() * 128)
|
||||
.await?;
|
||||
let ot_output = ot_receiver.receive(&choices).await?;
|
||||
|
||||
let converted_shares = ot_output
|
||||
.chunks(128)
|
||||
.map(|chunk| {
|
||||
V::from_choice(&chunk.iter().map(|x| x.inner()).collect::<Vec<u128>>()).inner()
|
||||
})
|
||||
.collect();
|
||||
Ok(converted_shares)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
T: OTReceiverFactory<Protocol = U> + Send,
|
||||
U: ObliviousReceive<Choice = bool, Outputs = Vec<Block>> + Send,
|
||||
> AdditiveToMultiplicative for Receiver<T>
|
||||
{
|
||||
type FieldElement = u128;
|
||||
|
||||
async fn a_to_m(
|
||||
&mut self,
|
||||
input: &[Self::FieldElement],
|
||||
) -> Result<Vec<Self::FieldElement>, ShareConversionError> {
|
||||
self.convert_from::<AddShare>(input).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
T: OTReceiverFactory<Protocol = U> + Send,
|
||||
U: ObliviousReceive<Choice = bool, Outputs = Vec<Block>> + Send,
|
||||
> MultiplicativeToAdditive for Receiver<T>
|
||||
{
|
||||
type FieldElement = u128;
|
||||
|
||||
async fn m_to_a(
|
||||
&mut self,
|
||||
input: &[Self::FieldElement],
|
||||
) -> Result<Vec<Self::FieldElement>, ShareConversionError> {
|
||||
self.convert_from::<MulShare>(input).await
|
||||
}
|
||||
}
|
||||
84
share-conversion-aio/src/gf2_128/sender.rs
Normal file
84
share-conversion-aio/src/gf2_128/sender.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
//! This module implements the async IO sender
|
||||
|
||||
use super::{AddShare, Gf2_128ShareConvert, MulShare, OTEnvelope};
|
||||
use crate::{AdditiveToMultiplicative, MultiplicativeToAdditive, ShareConversionError};
|
||||
use async_trait::async_trait;
|
||||
use mpc_aio::protocol::ot::{OTSenderFactory, ObliviousSend};
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
/// The sender for the conversion
|
||||
///
|
||||
/// Will be the OT sender
|
||||
pub struct Sender<T: OTSenderFactory> {
|
||||
sender_factory: T,
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl<T: OTSenderFactory> Sender<T>
|
||||
where
|
||||
T: Send,
|
||||
<<T as OTSenderFactory>::Protocol as ObliviousSend>::Inputs: From<OTEnvelope>,
|
||||
{
|
||||
/// Creates a new sender
|
||||
pub fn new(sender_factory: T, id: String) -> Self {
|
||||
Self { sender_factory, id }
|
||||
}
|
||||
|
||||
/// Convert the shares using oblivious transfer
|
||||
pub(crate) async fn convert_from<U: Gf2_128ShareConvert>(
|
||||
&mut self,
|
||||
shares: &[u128],
|
||||
) -> Result<Vec<u128>, ShareConversionError> {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
let mut local_shares = vec![];
|
||||
|
||||
if shares.is_empty() {
|
||||
return Ok(local_shares);
|
||||
}
|
||||
|
||||
let mut ot_shares = OTEnvelope::default();
|
||||
shares.iter().for_each(|share| {
|
||||
let share = U::new(*share);
|
||||
let (local, ot) = share.convert(&mut rng);
|
||||
local_shares.push(local.inner());
|
||||
ot_shares.extend(ot);
|
||||
});
|
||||
let mut ot_sender = self
|
||||
.sender_factory
|
||||
.new_sender(self.id.clone(), ot_shares.len())
|
||||
.await?;
|
||||
ot_sender.send(ot_shares.into()).await?;
|
||||
Ok(local_shares)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: OTSenderFactory + Send> AdditiveToMultiplicative for Sender<T>
|
||||
where
|
||||
<<T as OTSenderFactory>::Protocol as ObliviousSend>::Inputs: From<OTEnvelope> + Send,
|
||||
{
|
||||
type FieldElement = u128;
|
||||
|
||||
async fn a_to_m(
|
||||
&mut self,
|
||||
input: &[Self::FieldElement],
|
||||
) -> Result<Vec<Self::FieldElement>, ShareConversionError> {
|
||||
self.convert_from::<AddShare>(input).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: OTSenderFactory + Send> MultiplicativeToAdditive for Sender<T>
|
||||
where
|
||||
<<T as OTSenderFactory>::Protocol as ObliviousSend>::Inputs: From<OTEnvelope> + Send,
|
||||
{
|
||||
type FieldElement = u128;
|
||||
|
||||
async fn m_to_a(
|
||||
&mut self,
|
||||
input: &[Self::FieldElement],
|
||||
) -> Result<Vec<Self::FieldElement>, ShareConversionError> {
|
||||
self.convert_from::<MulShare>(input).await
|
||||
}
|
||||
}
|
||||
36
share-conversion-aio/src/lib.rs
Normal file
36
share-conversion-aio/src/lib.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
//! This subcrate implements the async IO layer for share-conversion
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mpc_aio::protocol::ot::{OTError, OTFactoryError};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod gf2_128;
|
||||
|
||||
/// Allows to convert additive shares of type `FieldElement` into multiplicative ones
|
||||
#[async_trait]
|
||||
pub trait AdditiveToMultiplicative {
|
||||
type FieldElement: Copy + std::fmt::Debug;
|
||||
async fn a_to_m(
|
||||
&mut self,
|
||||
input: &[Self::FieldElement],
|
||||
) -> Result<Vec<Self::FieldElement>, ShareConversionError>;
|
||||
}
|
||||
|
||||
/// Allows to convert multiplicative shares of type `FieldElement` into additive ones
|
||||
#[async_trait]
|
||||
pub trait MultiplicativeToAdditive {
|
||||
type FieldElement: Copy + std::fmt::Debug;
|
||||
async fn m_to_a(
|
||||
&mut self,
|
||||
input: &[Self::FieldElement],
|
||||
) -> Result<Vec<Self::FieldElement>, ShareConversionError>;
|
||||
}
|
||||
|
||||
/// An error for what can go wrong during conversion
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ShareConversionError {
|
||||
#[error("OTFactoryError: {0}")]
|
||||
OTFactoryError(#[from] OTFactoryError),
|
||||
#[error("OTError: {0}")]
|
||||
OTError(#[from] OTError),
|
||||
}
|
||||
1
share-conversion-aio/tests/ot_integration.rs
Normal file
1
share-conversion-aio/tests/ot_integration.rs
Normal file
@@ -0,0 +1 @@
|
||||
// TODO: Add integration test with OTFactory
|
||||
@@ -1,22 +1,20 @@
|
||||
[package]
|
||||
name = "gf2-128"
|
||||
name = "share-conversion-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tlsn-mpc-core = { path = "../mpc-core" }
|
||||
thiserror.workspace = true
|
||||
rand.workspace = true
|
||||
rand_chacha.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
rand_chacha.workspace = true
|
||||
ghash_rc.workspace = true
|
||||
tokio.workspace = true
|
||||
tlsn-mpc-aio = { path = "../mpc-aio" }
|
||||
tlsn-utils-aio = { path = "../utils-aio", features = ["duplex"] }
|
||||
tlsn-mpc-core = { path = "../mpc-core" }
|
||||
criterion.workspace = true
|
||||
|
||||
[[bench]]
|
||||
name = "inverse"
|
||||
name = "inverse_gf2_128"
|
||||
harness = false
|
||||
@@ -1,10 +1,10 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use gf2_128::inverse;
|
||||
use rand::Rng;
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
use share_conversion_core::gf2_128::inverse;
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
fn bench_gf2_128_inverse(c: &mut Criterion) {
|
||||
let mut rng = ChaCha12Rng::from_entropy();
|
||||
let a: u128 = rng.gen();
|
||||
|
||||
@@ -15,5 +15,5 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_group!(benches, bench_gf2_128_inverse);
|
||||
criterion_main!(benches);
|
||||
68
share-conversion-core/src/gf2_128/conversion/a2m.rs
Normal file
68
share-conversion-core/src/gf2_128/conversion/a2m.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
//! This module implements the A2M algorithm.
|
||||
|
||||
use std::num::NonZeroU128;
|
||||
|
||||
use super::MulShare;
|
||||
use super::{Gf2_128ShareConvert, OTEnvelope};
|
||||
use crate::gf2_128::{inverse, mul};
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
/// An additive share of `A = x + y`
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct AddShare(u128);
|
||||
|
||||
impl AddShare {
|
||||
/// Turn into a multiplicative share and get values for OT
|
||||
///
|
||||
/// This function returns
|
||||
/// * `MulShare` - The sender's multiplicative share
|
||||
/// * `OTEnvelope` - Used for oblivious transfer
|
||||
pub fn convert_to_multiplicative<R: Rng + CryptoRng>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
) -> (MulShare, OTEnvelope) {
|
||||
// We need to exclude 0 here, because it does not have an inverse
|
||||
// which is needed later
|
||||
let random: NonZeroU128 = rng.gen();
|
||||
let random = random.get();
|
||||
|
||||
let mut masks: [u128; 128] = std::array::from_fn(|_| rng.gen());
|
||||
// set the last mask such that the sum of all 128 masks equals 0
|
||||
masks[127] = masks.into_iter().take(127).fold(0, |acc, i| acc ^ i);
|
||||
|
||||
let mul_share = MulShare::new(inverse(random));
|
||||
|
||||
// decompose the share into component sums, e.g. if the share is 10110, we decompose it into
|
||||
// 0 + 10 + 100 + 0000 + 10000
|
||||
let components: Vec<u128> = (0..128)
|
||||
.map(|i| {
|
||||
// `self.inner() & (1 << i)` first extracts a bit of `self.inner()` in position `i` (counting from
|
||||
// the right) and then left-shifts that bit by `i`
|
||||
self.inner() & (1 << i)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let b0: [u128; 128] = std::array::from_fn(|i| mul(components[i], random) ^ masks[i]);
|
||||
let b1: [u128; 128] =
|
||||
std::array::from_fn(|i| mul(components[i] ^ (1 << i), random) ^ masks[i]);
|
||||
|
||||
(mul_share, OTEnvelope(b0.to_vec(), b1.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Gf2_128ShareConvert for AddShare {
|
||||
type Output = MulShare;
|
||||
|
||||
fn new(share: u128) -> Self {
|
||||
Self(share)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inner(&self) -> u128 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn convert<R: Rng + CryptoRng>(&self, rng: &mut R) -> (Self::Output, OTEnvelope) {
|
||||
self.convert_to_multiplicative(rng)
|
||||
}
|
||||
}
|
||||
44
share-conversion-core/src/gf2_128/conversion/m2a.rs
Normal file
44
share-conversion-core/src/gf2_128/conversion/m2a.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! This module implements the M2A algorithm.
|
||||
|
||||
use super::a2m::AddShare;
|
||||
use super::{Gf2_128ShareConvert, OTEnvelope};
|
||||
use crate::gf2_128::mul;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
/// A multiplicative share of `A = a * b`
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MulShare(u128);
|
||||
|
||||
impl MulShare {
|
||||
/// Turn into an additive share and get values for OT
|
||||
///
|
||||
/// This function returns
|
||||
/// * `AddShare` - The sender's additive share
|
||||
/// * `OTEnvelope` - Used for oblivious transfer
|
||||
pub fn convert_to_additive<R: Rng + CryptoRng>(&self, rng: &mut R) -> (AddShare, OTEnvelope) {
|
||||
let masks: [u128; 128] = std::array::from_fn(|_| rng.gen());
|
||||
|
||||
let t0: [u128; 128] = std::array::from_fn(|i| masks[i]);
|
||||
let t1: [u128; 128] = std::array::from_fn(|i| mul(self.inner(), 1 << i) ^ masks[i]);
|
||||
|
||||
let add_share = AddShare::new(t0.into_iter().fold(0, |acc, i| acc ^ i));
|
||||
(add_share, OTEnvelope(t0.to_vec(), t1.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Gf2_128ShareConvert for MulShare {
|
||||
type Output = AddShare;
|
||||
|
||||
fn new(share: u128) -> Self {
|
||||
Self(share)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inner(&self) -> u128 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn convert<R: Rng + CryptoRng>(&self, rng: &mut R) -> (Self::Output, OTEnvelope) {
|
||||
self.convert_to_additive(rng)
|
||||
}
|
||||
}
|
||||
133
share-conversion-core/src/gf2_128/conversion/mod.rs
Normal file
133
share-conversion-core/src/gf2_128/conversion/mod.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
//! This module implements the M2A and A2M conversion algorithms for field elements of GF(2^128), using
|
||||
//! oblivious transfer.
|
||||
//!
|
||||
//! * M2A: Implementation of chapter 4.1 in <https://link.springer.com/content/pdf/10.1007/3-540-48405-1_8.pdf>
|
||||
//! * A2M: Adaptation of chapter 4 in <https://www.cs.umd.edu/~fenghao/paper/modexp.pdf>
|
||||
|
||||
mod a2m;
|
||||
mod m2a;
|
||||
|
||||
pub use a2m::AddShare;
|
||||
pub use m2a::MulShare;
|
||||
use mpc_core::Block;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
/// A trait for converting field elements
|
||||
///
|
||||
/// Allows two parties to switch between additively and multiplicatively
|
||||
/// shared representations of a field element.
|
||||
pub trait Gf2_128ShareConvert: Copy
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
type Output: Gf2_128ShareConvert;
|
||||
|
||||
/// Create a new instance
|
||||
fn new(share: u128) -> Self;
|
||||
|
||||
/// Converts '&self' into choices needed for the receiver input to an oblivious transfer.
|
||||
/// The choices are in the "least-bit-first" order.
|
||||
fn choices(&self) -> Vec<bool> {
|
||||
let mut out: Vec<bool> = Vec::with_capacity(128);
|
||||
for k in 0..128 {
|
||||
out.push(((self.inner() >> k) & 1) == 1);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Return the inner value
|
||||
fn inner(&self) -> u128;
|
||||
|
||||
/// Create a share of type `Self::Output` from the result of an oblivious transfer (OT)
|
||||
///
|
||||
/// The `value` needs to be built from the output of an OT
|
||||
fn from_choice(value: &[u128]) -> Self::Output {
|
||||
Self::Output::new(value.iter().fold(0, |acc, i| acc ^ i))
|
||||
}
|
||||
|
||||
/// Prepares a share for conversion in an OT
|
||||
///
|
||||
/// Converts the share to a new share and returns what is needed for sending in an OT.
|
||||
fn convert<R: Rng + CryptoRng>(&self, rng: &mut R) -> (Self::Output, OTEnvelope);
|
||||
}
|
||||
|
||||
/// Batched values for several oblivious transfers
|
||||
///
|
||||
/// The inner vectors `.0` and `.1` belong to the corresponding receiver's choice bit
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct OTEnvelope(pub(crate) Vec<u128>, pub(crate) Vec<u128>);
|
||||
|
||||
impl OTEnvelope {
|
||||
/// Allows to aggregate envelopes
|
||||
pub fn extend(&mut self, other: OTEnvelope) {
|
||||
self.0.extend_from_slice(&other.0);
|
||||
self.1.extend_from_slice(&other.1);
|
||||
}
|
||||
|
||||
/// Get the number of OTs in this envelope
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Check if envelope is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OTEnvelope> for Vec<[Block; 2]> {
|
||||
fn from(value: OTEnvelope) -> Self {
|
||||
let mut out = Vec::with_capacity(value.0.len());
|
||||
for (zero, one) in value.0.iter().zip(value.1.iter()) {
|
||||
out.push([Block::new(*zero), Block::new(*one)])
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::gf2_128::mul;
|
||||
use a2m::AddShare;
|
||||
use m2a::MulShare;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
#[test]
|
||||
fn test_m2a() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0; 32]);
|
||||
let a: MulShare = MulShare::new(rng.gen());
|
||||
let b: MulShare = MulShare::new(rng.gen());
|
||||
|
||||
let (x, sharings) = a.convert_to_additive(&mut rng);
|
||||
|
||||
let choice = mock_ot(sharings, b.inner());
|
||||
let y = AddShare::from_choice(&choice);
|
||||
|
||||
assert_eq!(mul(a.inner(), b.inner()), x.inner() ^ y.inner());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_a2m() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0; 32]);
|
||||
let x: AddShare = AddShare::new(rng.gen());
|
||||
let y: AddShare = AddShare::new(rng.gen());
|
||||
|
||||
let (a, sharings) = x.convert_to_multiplicative(&mut rng);
|
||||
|
||||
let choice = mock_ot(sharings, y.inner());
|
||||
let b = MulShare::from_choice(&choice);
|
||||
|
||||
assert_eq!(x.inner() ^ y.inner(), mul(a.inner(), b.inner()));
|
||||
}
|
||||
|
||||
fn mock_ot(envelopes: OTEnvelope, choices: u128) -> Vec<u128> {
|
||||
let mut out: Vec<u128> = vec![0; 128];
|
||||
for (k, number) in out.iter_mut().enumerate() {
|
||||
let bit = (choices >> k) & 1;
|
||||
*number = (bit * envelopes.1[k]) ^ ((bit ^ 1) * envelopes.0[k]);
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
102
share-conversion-core/src/gf2_128/mod.rs
Normal file
102
share-conversion-core/src/gf2_128/mod.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! This module provides the share-conversion algorithms inside the module `conversion` for
|
||||
//! elements of GF(2^128), as well as some arithmetic functions for these field elements.
|
||||
|
||||
mod conversion;
|
||||
|
||||
pub use conversion::{AddShare, Gf2_128ShareConvert, MulShare, OTEnvelope};
|
||||
|
||||
/// R is the GCM polynomial in little-endian. In hex: "E1000000000000000000000000000000"
|
||||
const R: u128 = 299076299051606071403356588563077529600;
|
||||
|
||||
/// Galois field multiplication of two 128-bit blocks reduced by the GCM polynomial
|
||||
pub fn mul(mut x: u128, y: u128) -> u128 {
|
||||
let mut result: u128 = 0;
|
||||
for i in (0..128).rev() {
|
||||
result ^= x * ((y >> i) & 1);
|
||||
x = (x >> 1) ^ ((x & 1) * R);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Galois field inversion of 128-bit block
|
||||
pub fn inverse(mut x: u128) -> u128 {
|
||||
let one = 1 << 127;
|
||||
let mut out = one;
|
||||
|
||||
for _ in 0..127 {
|
||||
x = mul(x, x);
|
||||
out = mul(out, x);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Iteratively multiplies some field element with another field element
|
||||
///
|
||||
/// This function multiplies the last element in `powers` with some other field element `factor`
|
||||
/// and appends the result to `powers`. This process is repeated `count` times.
|
||||
///
|
||||
/// * `powers` - The vector to which the new higher powers get pushed
|
||||
/// * `factor` - The field element with which the last element of the vector is multiplied
|
||||
/// * `count` - How many products are computed
|
||||
pub fn compute_product_repeated(powers: &mut Vec<u128>, factor: u128, count: usize) {
|
||||
for _ in 0..count {
|
||||
let last_power = *powers
|
||||
.last()
|
||||
.expect("Vector is empty. Cannot compute higher powers");
|
||||
powers.push(mul(factor, last_power));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ghash_rc::{
|
||||
universal_hash::{NewUniversalHash, UniversalHash},
|
||||
GHash,
|
||||
};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha12Rng;
|
||||
|
||||
#[test]
|
||||
// Test multiplication against RustCrypto
|
||||
fn test_mul() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0; 32]);
|
||||
let a: u128 = rng.gen();
|
||||
let b: u128 = rng.gen();
|
||||
|
||||
let mut g = GHash::new(&a.to_be_bytes().into());
|
||||
g.update(&b.to_be_bytes().into());
|
||||
// Ghash will internally multiply a and b
|
||||
let expected = g.finalize();
|
||||
|
||||
assert_eq!(
|
||||
mul(a, b),
|
||||
u128::from_be_bytes(expected.into_bytes().try_into().unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inverse() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0; 32]);
|
||||
let a: u128 = rng.gen();
|
||||
let inverse_a = inverse(a);
|
||||
|
||||
assert_eq!(mul(a, inverse_a), 1_u128 << 127);
|
||||
assert_eq!(inverse(1_u128 << 127), 1_u128 << 127);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_product_repeated() {
|
||||
let mut rng = ChaCha12Rng::from_seed([0; 32]);
|
||||
let a: u128 = rng.gen();
|
||||
|
||||
let mut powers = vec![a];
|
||||
let factor = mul(a, a);
|
||||
|
||||
compute_product_repeated(&mut powers, factor, 2);
|
||||
|
||||
assert_eq!(powers[0], a);
|
||||
assert_eq!(powers[1], mul(powers[0], factor));
|
||||
assert_eq!(powers[2], mul(powers[1], factor));
|
||||
}
|
||||
}
|
||||
17
share-conversion-core/src/lib.rs
Normal file
17
share-conversion-core/src/lib.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
//! This subcrate implements secure two-party (2PC) multiplication-to-addition (M2A) and
|
||||
//! addition-to-multiplication (A2M) algorithms, both with semi-honest security.
|
||||
//!
|
||||
//! ### M2A algorithm
|
||||
//! Let `A` be an element of some finite field with `A = a * b`, where `a` is only known to Alice
|
||||
//! and `b` is only known to Bob. A is unknown to both parties and it is their goal that each of
|
||||
//! them ends up with an additive share of A. So both parties start with `a` and `b` and want to
|
||||
//! end up with `x` and `y`, where `A = a * b = x + y`.
|
||||
//!
|
||||
//! ### A2M algorithm
|
||||
//! This is the other way round.
|
||||
//! Let `A` be an element of some finite field with `A = x + y`, where `x` is only known to Alice
|
||||
//! and `y` is only known to Bob. A is unknown to both parties and it is their goal that each of
|
||||
//! them ends up with a multiplicative share of A. So both parties start with `x` and `y` and want to
|
||||
//! end up with `a` and `b`, where `A = x + y = a * b`.
|
||||
|
||||
pub mod gf2_128;
|
||||
Reference in New Issue
Block a user