diff --git a/mpz-core/src/block.rs b/mpz-core/src/block.rs index fdd3fd60..2f7a0105 100644 --- a/mpz-core/src/block.rs +++ b/mpz-core/src/block.rs @@ -18,6 +18,8 @@ impl Block { pub const LEN: usize = 16; /// A zero block pub const ZERO: Self = Self([0; 16]); + /// A one block + pub const ONE: Self = Self([1u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); /// A block with all bits set to 1 pub const ONES: Self = Self([0xff; 16]); /// A length 2 array of zero and one blocks diff --git a/mpz-core/src/lpn.rs b/mpz-core/src/lpn.rs index b326fed0..cd99ca3d 100644 --- a/mpz-core/src/lpn.rs +++ b/mpz-core/src/lpn.rs @@ -2,6 +2,7 @@ //! More specifically, a local linear code is a random boolean matrix with at most D non-zero values in each row. use crate::{prp::Prp, Block}; +use rand::{seq::SliceRandom, thread_rng}; use rayon::prelude::*; /// An LPN encoder. /// @@ -111,6 +112,48 @@ impl LpnEncoder { } } +/// Lpn paramters +#[derive(Copy, Clone, Debug)] +pub struct LpnParameters { + /// The length of output vecotrs. + pub n: usize, + /// The length of the secret vector + pub k: usize, + /// The Hamming Weight of error vectors + pub t: usize, +} + +impl LpnParameters { + /// Create a new LpnParameters instance. + pub fn new(n: usize, k: usize, t: usize) -> Self { + assert!(t <= n); + LpnParameters { n, k, t } + } + + /// Sample a uniform error vector with HW t. + pub fn sample_uniform_error_vector(&self) -> Vec { + let one: Block = bytemuck::cast(1_u128); + let mut res = vec![Block::ZERO; self.n]; + res[0..self.t].iter_mut().for_each(|x| *x = one); + let mut rng = thread_rng(); + res.shuffle(&mut rng); + res + } + + /// Sample a regular error vector with HW t + pub fn sample_regular_error_vector(&self) -> Vec { + assert_eq!(self.n % self.t, 0); + let one: Block = bytemuck::cast(1_u128); + let mut res = vec![Block::ZERO; self.n]; + res.chunks_exact_mut(self.n / self.t).for_each(|x| { + x[0] = one; + let mut rng = thread_rng(); + x.shuffle(&mut rng); + }); + res + } +} + #[cfg(test)] mod tests { use crate::lpn::LpnEncoder; diff --git a/ot/mpz-ot-core/src/ferret/error.rs b/ot/mpz-ot-core/src/ferret/error.rs new file mode 100644 index 00000000..d209b77d --- /dev/null +++ b/ot/mpz-ot-core/src/ferret/error.rs @@ -0,0 +1,11 @@ +//! Errors that can occur when using the Ferret protocol. + +/// Errors that can occur when using the Ferret sender. +#[derive(Debug, thiserror::Error)] +#[error("invalid input: expected {0}")] +pub struct SenderError(pub String); + +/// Errors that can occur when using the Ferret receiver. +#[derive(Debug, thiserror::Error)] +#[error("invalid input: expected {0}")] +pub struct ReceiverError(pub String); diff --git a/ot/mpz-ot-core/src/ferret/mod.rs b/ot/mpz-ot-core/src/ferret/mod.rs index 25331695..9b52fded 100644 --- a/ot/mpz-ot-core/src/ferret/mod.rs +++ b/ot/mpz-ot-core/src/ferret/mod.rs @@ -1,7 +1,13 @@ //! An implementation of the [`Ferret`](https://eprint.iacr.org/2020/924.pdf) protocol. +use mpz_core::lpn::LpnParameters; + pub mod cuckoo; +pub mod error; pub mod mpcot; +pub mod msgs; +pub mod receiver; +pub mod sender; pub mod spcot; /// Computational security parameter @@ -12,3 +18,131 @@ pub const CUCKOO_HASH_NUM: usize = 3; /// Trial numbers in Cuckoo hash insertion. pub const CUCKOO_TRIAL_NUM: usize = 100; + +/// LPN parameters with regular noise. +/// Derived from https://github.com/emp-toolkit/emp-ot/blob/master/emp-ot/ferret/constants.h +pub const LPN_PARAMETERS_REGULAR: LpnParameters = LpnParameters { + n: 10180608, + k: 124000, + t: 4971, +}; + +/// LPN parameters with uniform noise. +/// Derived from Table 2. +pub const LPN_PARAMETERS_UNIFORM: LpnParameters = LpnParameters { + n: 10616092, + k: 588160, + t: 1324, +}; + +/// The type of Lpn parameters. +pub enum LpnType { + /// Uniform error distribution. + Uniform, + /// Regular error distribution. + Regular, +} + +#[cfg(test)] +mod tests { + use super::{ + msgs::LpnMatrixSeed, receiver::Receiver as FerretReceiver, sender::Sender as FerretSender, + LpnType, + }; + use crate::ideal::{ + ideal_cot::{CotMsgForReceiver, CotMsgForSender, IdealCOT}, + ideal_mpcot::{IdealMpcot, MpcotMsgForReceiver, MpcotMsgForSender}, + }; + use mpz_core::{lpn::LpnParameters, prg::Prg}; + + const LPN_PARAMETERS_TEST: LpnParameters = LpnParameters { + n: 9600, + k: 1220, + t: 600, + }; + + #[test] + fn ferret_test() { + let mut prg = Prg::new(); + let delta = prg.random_block(); + let mut ideal_cot = IdealCOT::new_with_delta(delta); + let mut ideal_mpcot = IdealMpcot::init_with_delta(delta); + + let sender = FerretSender::new(); + let receiver = FerretReceiver::new(); + + // Invoke Ideal COT to init the Ferret setup phase. + let (sender_cot, receiver_cot) = ideal_cot.extend(LPN_PARAMETERS_TEST.k); + + let CotMsgForSender { qs: v } = sender_cot; + let CotMsgForReceiver { rs: u, ts: w } = receiver_cot; + + // receiver generates the random seed of lpn matrix. + let lpn_matrix_seed = prg.random_block(); + + // init the setup of sender and receiver. + let (mut receiver, seed) = receiver + .setup( + LPN_PARAMETERS_TEST, + LpnType::Regular, + lpn_matrix_seed, + &u, + &w, + ) + .unwrap(); + + let LpnMatrixSeed { + seed: lpn_matrix_seed, + } = seed; + + let mut sender = sender + .setup( + delta, + LPN_PARAMETERS_TEST, + LpnType::Regular, + lpn_matrix_seed, + &v, + ) + .unwrap(); + + // extend once + let _ = sender.get_mpcot_query(); + let query = receiver.get_mpcot_query(); + + let (sender_mpcot, receiver_mpcot) = ideal_mpcot.extend(&query.0, query.1, query.2); + + let MpcotMsgForSender { s } = sender_mpcot; + let MpcotMsgForReceiver { r } = receiver_mpcot; + + let sender_out = sender.extend(&s).unwrap(); + let receiver_out = receiver.extend(&r).unwrap(); + + assert!(ideal_cot.check( + CotMsgForSender { qs: sender_out }, + CotMsgForReceiver { + rs: receiver_out.0, + ts: receiver_out.1, + }, + )); + + // extend twice + let _ = sender.get_mpcot_query(); + let query = receiver.get_mpcot_query(); + + let (sender_mpcot, receiver_mpcot) = ideal_mpcot.extend(&query.0, query.1, query.2); + + let MpcotMsgForSender { s } = sender_mpcot; + let MpcotMsgForReceiver { r } = receiver_mpcot; + + let sender_out = sender.extend(&s).unwrap(); + let receiver_out = receiver.extend(&r).unwrap(); + + assert!(ideal_cot.check( + CotMsgForSender { qs: sender_out }, + CotMsgForReceiver { + rs: receiver_out.0, + ts: receiver_out.1, + }, + )); + } +} diff --git a/ot/mpz-ot-core/src/ferret/msgs.rs b/ot/mpz-ot-core/src/ferret/msgs.rs new file mode 100644 index 00000000..da2b1e52 --- /dev/null +++ b/ot/mpz-ot-core/src/ferret/msgs.rs @@ -0,0 +1,10 @@ +//! Messages for the Ferret protocol. +use mpz_core::Block; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// The seed to generate Lpn matrix. +pub struct LpnMatrixSeed { + /// The seed. + pub seed: Block, +} diff --git a/ot/mpz-ot-core/src/ferret/receiver.rs b/ot/mpz-ot-core/src/ferret/receiver.rs new file mode 100644 index 00000000..4f94e6e6 --- /dev/null +++ b/ot/mpz-ot-core/src/ferret/receiver.rs @@ -0,0 +1,188 @@ +//! Ferret receiver +use mpz_core::{ + lpn::{LpnEncoder, LpnParameters}, + Block, +}; + +use crate::ferret::{error::ReceiverError, LpnType}; + +use super::msgs::LpnMatrixSeed; + +/// Ferret receiver. +#[derive(Debug, Default)] +pub struct Receiver { + state: T, +} + +impl Receiver { + /// Create a new Receiver. + pub fn new() -> Self { + Receiver { + state: state::Initialized::default(), + } + } + + /// Completes the setup phase of the protocol. + /// + /// See step 1 and 2 in Figure 9. + /// + /// # Arguments + /// + /// * `lpn_parameters` - The lpn parameters. + /// * `seed` - The seed to generate lpn matrix. + /// * `lpn_type` - The lpn type. + /// * `u` - The bits received from the COT ideal functionality. + /// * `w` - The vector received from the COT ideal functionality. + pub fn setup( + self, + lpn_parameters: LpnParameters, + lpn_type: LpnType, + seed: Block, + u: &[bool], + w: &[Block], + ) -> Result<(Receiver, LpnMatrixSeed), ReceiverError> { + if u.len() != lpn_parameters.k || w.len() != lpn_parameters.k { + return Err(ReceiverError( + "the length of u and w should be k".to_string(), + )); + } + + let lpn_encoder = LpnEncoder::<10>::new(seed, lpn_parameters.k as u32); + + Ok(( + Receiver { + state: state::Extension { + counter: 0, + lpn_parameters, + lpn_encoder, + lpn_type, + u: u.to_vec(), + w: w.to_vec(), + e: Vec::default(), + }, + }, + LpnMatrixSeed { seed }, + )) + } +} + +impl Receiver { + /// The prepare precedure of extension, sample error vectors and outputs information for MPCOT. + /// See step 3 and 4. + /// + /// # Arguments. + /// + /// * `lpn_type` - The type of LPN parameters. + pub fn get_mpcot_query(&mut self) -> (Vec, usize, usize) { + match self.state.lpn_type { + LpnType::Uniform => { + self.state.e = self.state.lpn_parameters.sample_uniform_error_vector(); + } + + LpnType::Regular => { + self.state.e = self.state.lpn_parameters.sample_regular_error_vector(); + } + } + let mut alphas = Vec::with_capacity(self.state.lpn_parameters.t); + for (i, x) in self.state.e.iter().enumerate() { + if *x != Block::ZERO { + alphas.push(i as u32); + } + } + ( + alphas, + self.state.lpn_parameters.t, + self.state.lpn_parameters.n, + ) + } + + /// Performs the Ferret extension. + /// Outputs exactly l = n - t COTs. + /// + /// See step 5 and 6. + /// + /// # Arguments. + /// + /// * `r` - The vector received from the MPCOT protocol. + pub fn extend(&mut self, r: &[Block]) -> Result<(Vec, Vec), ReceiverError> { + if r.len() != self.state.lpn_parameters.n { + return Err(ReceiverError("the length of r should be n".to_string())); + } + + // Compute z = A * w + r. + let mut z = r.to_vec(); + self.state.lpn_encoder.compute(&mut z, &self.state.w); + + // Compute x = A * u + e. + let u_block = self + .state + .u + .iter() + .map(|x| if *x { Block::ONE } else { Block::ZERO }) + .collect::>(); + let mut x = self.state.e.clone(); + self.state.lpn_encoder.compute(&mut x, &u_block); + + let mut x = x.iter().map(|a| a.lsb() == 1).collect::>(); + + let x_ = x.split_off(self.state.lpn_parameters.k); + let z_ = z.split_off(self.state.lpn_parameters.k); + + // Update u, w + self.state.u = x; + self.state.w = z; + + // Update counter + self.state.counter += 1; + + Ok((x_, z_)) + } +} + +/// The receiver's state. +pub mod state { + use super::*; + + mod sealed { + pub trait Sealed {} + impl Sealed for super::Initialized {} + impl Sealed for super::Extension {} + } + + /// The receiver's state. + pub trait State: sealed::Sealed {} + + /// The receiver's initial state. + #[derive(Default)] + pub struct Initialized {} + + impl State for Initialized {} + + opaque_debug::implement!(Initialized); + + /// The receiver's state after the setup phase. + /// + /// In this state the sender performs Ferret extension (potentially multiple times). + pub struct Extension { + /// Current Ferret counter. + pub(super) counter: usize, + + /// Lpn parameters. + pub(super) lpn_parameters: LpnParameters, + /// Lpn encoder. + pub(super) lpn_encoder: LpnEncoder<10>, + /// Lpn type. + pub(super) lpn_type: LpnType, + + /// Receiver's COT messages in the setup phase. + pub(super) u: Vec, + pub(super) w: Vec, + + /// Receiver's lpn error vector. + pub(super) e: Vec, + } + + impl State for Extension {} + + opaque_debug::implement!(Extension); +} diff --git a/ot/mpz-ot-core/src/ferret/sender.rs b/ot/mpz-ot-core/src/ferret/sender.rs new file mode 100644 index 00000000..9e8db180 --- /dev/null +++ b/ot/mpz-ot-core/src/ferret/sender.rs @@ -0,0 +1,149 @@ +//! Ferret sender. +use mpz_core::{ + lpn::{LpnEncoder, LpnParameters}, + Block, +}; + +use crate::ferret::{error::SenderError, LpnType}; + +/// Ferret sender. +#[derive(Debug, Default)] +pub struct Sender { + state: T, +} + +impl Sender { + /// Creates a new Sender. + pub fn new() -> Self { + Sender { + state: state::Initialized::default(), + } + } + + /// Completes the setup phase of the protocol. + /// + /// See step 1 and 2 in Figure 9. + /// + /// # Arguments + /// + /// * `delta` - The sender's global secret. + /// * `lpn_parameters` - The lpn parameters. + /// * `lpn_type` - The lpn type. + /// * `seed` - The seed received from receiver to generate lpn matrix. + /// * `v` - The vector received from the COT ideal functionality. + pub fn setup( + self, + delta: Block, + lpn_parameters: LpnParameters, + lpn_type: LpnType, + seed: Block, + v: &[Block], + ) -> Result, SenderError> { + if v.len() != lpn_parameters.k { + return Err(SenderError( + "the length of v should be equal to k".to_string(), + )); + } + let lpn_encoder = LpnEncoder::<10>::new(seed, lpn_parameters.k as u32); + + Ok(Sender { + state: state::Extension { + delta, + counter: 0, + lpn_parameters, + lpn_type, + lpn_encoder, + v: v.to_vec(), + }, + }) + } +} + +impl Sender { + /// Outputs the information for MPCOT. + /// + /// See step 3 and 4. + pub fn get_mpcot_query(&self) -> (u32, u32) { + ( + self.state.lpn_parameters.t as u32, + self.state.lpn_parameters.n as u32, + ) + } + + /// Performs the Ferret extension. + /// Outputs exactly l = n-t COTs. + /// + /// See step 5 and 6. + /// + /// # Arguments. + /// + /// * `s` - The vector received from the MPCOT protocol. + pub fn extend(&mut self, s: &[Block]) -> Result, SenderError> { + if s.len() != self.state.lpn_parameters.n { + return Err(SenderError("the length of s should be n".to_string())); + } + + // Compute y = A * v + s + let mut y = s.to_vec(); + self.state.lpn_encoder.compute(&mut y, &self.state.v); + + let y_ = y.split_off(self.state.lpn_parameters.k); + + // Update v to y[0..k] + self.state.v = y; + + // Update counter + self.state.counter += 1; + + Ok(y_) + } +} + +/// The sender's state. +pub mod state { + use super::*; + + mod sealed { + pub trait Sealed {} + + impl Sealed for super::Initialized {} + impl Sealed for super::Extension {} + } + + /// The sender's state. + pub trait State: sealed::Sealed {} + + /// The sender's initial state. + #[derive(Default)] + pub struct Initialized {} + + impl State for Initialized {} + + opaque_debug::implement!(Initialized); + + /// The sender's state after the setup phase. + /// + /// In this state the sender performs Ferret extension (potentially multiple times). + pub struct Extension { + /// Sender's global secret. + #[allow(dead_code)] + pub(super) delta: Block, + /// Current Ferret counter. + pub(super) counter: usize, + + /// Lpn type. + #[allow(dead_code)] + pub(super) lpn_type: LpnType, + /// Lpn parameters. + pub(super) lpn_parameters: LpnParameters, + /// Lpn encoder. + pub(super) lpn_encoder: LpnEncoder<10>, + + /// Sender's COT message in the setup phase. + pub(super) v: Vec, + } + + impl State for Extension {} + + opaque_debug::implement!(Extension); +} diff --git a/ot/mpz-ot-core/src/ideal/ideal_mpcot.rs b/ot/mpz-ot-core/src/ideal/ideal_mpcot.rs new file mode 100644 index 00000000..8eca097e --- /dev/null +++ b/ot/mpz-ot-core/src/ideal/ideal_mpcot.rs @@ -0,0 +1,118 @@ +//! Define ideal functionality of MPCOT. + +use mpz_core::{prg::Prg, Block}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// The message that sender receives from the MPCOT functionality. +pub struct MpcotMsgForSender { + /// The random blocks that sender receives from the MPCOT functionality. + pub s: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// The message that receiver receives from the MPCOT functionality. +pub struct MpcotMsgForReceiver { + /// The random blocks that receiver receives from the MPCOT functionality. + pub r: Vec, +} + +#[allow(missing_docs)] +pub struct IdealMpcot { + pub delta: Block, + pub counter: usize, + pub prg: Prg, +} + +impl IdealMpcot { + /// Initiate the functionality. + pub fn init() -> Self { + let mut prg = Prg::new(); + let delta = prg.random_block(); + IdealMpcot { + delta, + counter: 0, + prg, + } + } + + /// Initiate with a given delta. + pub fn init_with_delta(delta: Block) -> Self { + let prg = Prg::new(); + IdealMpcot { + delta, + counter: 0, + prg, + } + } + + /// Performs the extension of MPCOT. + /// + /// # Argument + /// + /// * `alphas` - The positions in each extension. + /// * `n` - The length of the vector. + pub fn extend( + &mut self, + alphas: &[u32], + t: usize, + n: usize, + ) -> (MpcotMsgForSender, MpcotMsgForReceiver) { + assert_eq!(alphas.len(), t); + assert!(t < n); + let mut s = vec![Block::ZERO; n]; + let mut r = vec![Block::ZERO; n]; + self.prg.random_blocks(&mut s); + r.copy_from_slice(&s); + + for alpha in alphas { + assert!((*alpha as usize) < n); + r[*alpha as usize] ^= self.delta; + + self.counter += 1; + } + (MpcotMsgForSender { s }, MpcotMsgForReceiver { r }) + } + + /// Performs the checks. + /// + /// # Arguments + /// + /// * `sender_msg` - The message that the ideal MPCOT sends to the sender. + /// * `receiver_msg` - The message that the ideal MPCOT sends to the receiver. + /// * `alphas` - The positions in each extension. + /// * `n` - The length of the vector. + pub fn check( + &self, + sender_msg: MpcotMsgForSender, + receiver_msg: MpcotMsgForReceiver, + alphas: &[u32], + t: usize, + n: usize, + ) -> bool { + assert_eq!(alphas.len(), t); + let MpcotMsgForSender { mut s } = sender_msg; + let MpcotMsgForReceiver { r } = receiver_msg; + + for alpha in alphas { + assert!((*alpha as usize) < n); + s[*alpha as usize] ^= self.delta; + } + + let res = s.iter_mut().zip(r.iter()).all(|(s, r)| *s == *r); + res + } +} + +#[cfg(test)] +mod tests { + use crate::ideal::ideal_mpcot::IdealMpcot; + + #[test] + fn ideal_mpcot_test() { + let mut ideal_mpcot = IdealMpcot::init(); + + let (sender_msg, receiver_msg) = ideal_mpcot.extend(&[1, 3, 4, 6], 4, 20); + assert!(ideal_mpcot.check(sender_msg, receiver_msg, &[1, 3, 4, 6], 4, 20)); + } +} diff --git a/ot/mpz-ot-core/src/ideal/mod.rs b/ot/mpz-ot-core/src/ideal/mod.rs index a6945a37..1fb56072 100644 --- a/ot/mpz-ot-core/src/ideal/mod.rs +++ b/ot/mpz-ot-core/src/ideal/mod.rs @@ -1,4 +1,4 @@ //! Define ideal functionalities of OTs. - pub mod ideal_cot; +pub mod ideal_mpcot; pub mod ideal_spcot;