From cd95659a89a7ec3fd1fb7ae8220cf29475dc9158 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 11 Oct 2023 12:39:34 +0000 Subject: [PATCH 1/2] chore: fix doc link (#76) --- garble/mpz-garble/src/protocol/deap/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/garble/mpz-garble/src/protocol/deap/mod.rs b/garble/mpz-garble/src/protocol/deap/mod.rs index 1add51e2..ab41a81c 100644 --- a/garble/mpz-garble/src/protocol/deap/mod.rs +++ b/garble/mpz-garble/src/protocol/deap/mod.rs @@ -1,6 +1,6 @@ //! An implementation of the Dual-execution with Asymmetric Privacy (DEAP) protocol. //! -//! For more information, see the [DEAP specification](https://docs.tlsnotary.org/protocol/2pc/deap.html). +//! For more information, see the [DEAP specification](https://docs.tlsnotary.org/mpc/deap.html). mod error; mod memory; From 4736ae0949cbb7f34447ccc4ec05edf283de5adb Mon Sep 17 00:00:00 2001 From: Xiang Xie Date: Sat, 14 Oct 2023 02:16:49 +0800 Subject: [PATCH 2/2] Add LPN and GGM related (#54) * add lpn and optimizations with multi threads * update comments * finish ggm reconstruction, still need optimization * clippy * cargo ciplly * add comments on public functions * add pado as a contributor * add assertion and modify documentation * fix minor bugs * rebase dev * fmt --- README.md | 1 + mpz-core/Cargo.toml | 9 +- mpz-core/benches/ggm.rs | 19 +++- mpz-core/benches/lpn.rs | 57 ++++++++++++ mpz-core/src/ggm_tree.rs | 149 +++++++++++++++++++++++--------- mpz-core/src/lib.rs | 2 + mpz-core/src/lpn.rs | 181 +++++++++++++++++++++++++++++++++++++++ mpz-core/src/prp.rs | 32 +++++++ mpz-core/src/tkprp.rs | 6 +- 9 files changed, 409 insertions(+), 47 deletions(-) create mode 100644 mpz-core/benches/lpn.rs create mode 100644 mpz-core/src/lpn.rs create mode 100644 mpz-core/src/prp.rs diff --git a/README.md b/README.md index 68a2531d..0fc50dfe 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). ## Contributors - [TLSNotary](https://github.com/tlsnotary) +- [PADO Labs](https://github.com/pado-labs) ### Pronounciation diff --git a/mpz-core/Cargo.toml b/mpz-core/Cargo.toml index 7b8a55ea..ed7af602 100644 --- a/mpz-core/Cargo.toml +++ b/mpz-core/Cargo.toml @@ -7,8 +7,9 @@ edition = "2021" name = "mpz_core" [features] -default = ["cointoss"] +default = ["cointoss", "rayon"] cointoss = ["dep:rand_chacha"] +rayon = ["dep:rayon"] [dependencies] aes = { workspace = true, features = [] } @@ -26,6 +27,8 @@ bcs = "0.1.5" rand_core = "0.6.4" bytemuck = { workspace = true, features = ["derive"] } generic-array.workspace = true +rayon = { workspace = true, optional = true } +cfg-if.workspace = true [dev-dependencies] rstest.workspace = true @@ -42,3 +45,7 @@ harness = false [[bench]] name = "prg" harness = false + +[[bench]] +name = "lpn" +harness = false diff --git a/mpz-core/benches/ggm.rs b/mpz-core/benches/ggm.rs index 83b57352..3b902caf 100644 --- a/mpz-core/benches/ggm.rs +++ b/mpz-core/benches/ggm.rs @@ -4,11 +4,11 @@ use mpz_core::{block::Block, ggm_tree::GgmTree}; #[allow(clippy::all)] fn criterion_benchmark(c: &mut Criterion) { c.bench_function("ggm::gen::1K", move |bench| { - let depth = 11; + let depth = 10; let ggm = GgmTree::new(depth); - let mut tree = vec![Block::ZERO; 1 << (depth - 1)]; - let mut k0 = vec![Block::ZERO; depth - 1]; - let mut k1 = vec![Block::ZERO; depth - 1]; + let mut tree = vec![Block::ZERO; 1 << (depth)]; + let mut k0 = vec![Block::ZERO; depth]; + let mut k1 = vec![Block::ZERO; depth]; let seed = rand::random::(); bench.iter(|| { black_box(ggm.gen( @@ -19,6 +19,17 @@ fn criterion_benchmark(c: &mut Criterion) { )); }); }); + + c.bench_function("ggm::reconstruction::1K", move |bench| { + let depth = 10; + let ggm = GgmTree::new(depth); + let mut tree = vec![Block::ZERO; 1 << (depth)]; + let k = vec![Block::ZERO; depth]; + let alpha = vec![false; depth]; + bench.iter(|| { + black_box(ggm.reconstruct(black_box(&mut tree), black_box(&k), black_box(&alpha))) + }); + }); } criterion_group!(benches, criterion_benchmark); diff --git a/mpz-core/benches/lpn.rs b/mpz-core/benches/lpn.rs new file mode 100644 index 00000000..c1a97ce0 --- /dev/null +++ b/mpz-core/benches/lpn.rs @@ -0,0 +1,57 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use mpz_core::{lpn::LpnEncoder, prg::Prg, Block}; +use std::time::Duration; + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("lpn-rayon-small", move |bench| { + let seed = Block::ZERO; + let k = 5_060; + let n = 166_400; + let lpn = LpnEncoder::<10>::new(seed, k); + let mut x = vec![Block::ZERO; k as usize]; + let mut y = vec![Block::ZERO; n]; + let mut prg = Prg::new(); + prg.random_blocks(&mut x); + prg.random_blocks(&mut y); + bench.iter(|| { + black_box(lpn.compute(&mut y, &x)); + }); + }); + + c.bench_function("lpn-rayon-medium", move |bench| { + let seed = Block::ZERO; + let k = 158_000; + let n = 10_168_320; + let lpn = LpnEncoder::<10>::new(seed, k); + let mut x = vec![Block::ZERO; k as usize]; + let mut y = vec![Block::ZERO; n]; + let mut prg = Prg::new(); + prg.random_blocks(&mut x); + prg.random_blocks(&mut y); + bench.iter(|| { + black_box(lpn.compute(&mut y, &x)); + }); + }); + + c.bench_function("lpn-rayon-large", move |bench| { + let seed = Block::ZERO; + let k = 588_160; + let n = 10_616_092; + let lpn = LpnEncoder::<10>::new(seed, k); + let mut x = vec![Block::ZERO; k as usize]; + let mut y = vec![Block::ZERO; n]; + let mut prg = Prg::new(); + prg.random_blocks(&mut x); + prg.random_blocks(&mut y); + bench.iter(|| { + black_box(lpn.compute(&mut y, &x)); + }); + }); +} + +criterion_group! { + name = lpn; + config = Criterion::default().warm_up_time(Duration::from_millis(1000)).sample_size(10); + targets = criterion_benchmark +} +criterion_main!(lpn); diff --git a/mpz-core/src/ggm_tree.rs b/mpz-core/src/ggm_tree.rs index 9b5f5ffd..913fffb6 100644 --- a/mpz-core/src/ggm_tree.rs +++ b/mpz-core/src/ggm_tree.rs @@ -18,16 +18,20 @@ impl GgmTree { Self { tkprp, depth } } - /// Input: `seed`: a seed. - /// Output: `tree`: a GGM (binary tree) `tree`, with size `2^{depth-1}` - /// Output: `k0`: XORs of all the left-node values in each level, with size `depth-1`. - /// Output: `k1`: XORs of all the right-node values in each level, with size `depth-1`. - /// This implementation is adapted from EMP Toolkit. + /// Create a GGM tree in-place. + /// + /// # Arguments + /// + /// * `seed` - a seed. + /// * `tree` - the destination to write the GGM (binary tree) `tree`, with size `2^{depth}`. + /// * `k0` - XORs of all the left-node values in each level, with size `depth`. + /// * `k1`- XORs of all the right-node values in each level, with size `depth`. + // This implementation is adapted from EMP Toolkit. pub fn gen(&self, seed: Block, tree: &mut [Block], k0: &mut [Block], k1: &mut [Block]) { - assert!(tree.len() == 1 << (self.depth - 1)); - assert!(k0.len() == self.depth - 1); - assert!(k1.len() == self.depth - 1); - let mut buf = vec![Block::ZERO; 8]; + assert_eq!(tree.len(), 1 << (self.depth)); + assert_eq!(k0.len(), self.depth); + assert_eq!(k1.len(), self.depth); + let mut buf = [Block::ZERO; 8]; self.tkprp.expand_1to2(tree, seed); k0[0] = tree[0]; k1[0] = tree[1]; @@ -37,9 +41,11 @@ impl GgmTree { k1[1] = buf[1] ^ buf[3]; tree[0..4].copy_from_slice(&buf[0..4]); - for h in 2..self.depth - 1 { + for h in 2..self.depth { k0[h] = Block::ZERO; k1[h] = Block::ZERO; + + // How many nodes there are in this layer let sz = 1 << h; for i in (0..=sz - 4).rev().step_by(4) { self.tkprp.expand_4to8(&mut buf, &tree[i..]); @@ -56,43 +62,108 @@ impl GgmTree { } } } + + /// Reconstruct the GGM tree except the value in a given position. + /// + /// This reconstructs the GGM tree entirely except `tree[pos] == Block::ZERO`. The bit decomposition of `pos` is the complement of `alpha`. i.e., `pos[i] = 1 xor alpha[i]`. + /// + /// # Arguments + /// + /// * `k` - a slice of blocks with length `depth`, the values of k are chosen via OT from k0 and k1. For the i-th value, if alpha[i] == 1, k[i] = k1[i]; else k[i] = k0[i]. + /// * `alpha` - a slice of bits with length `depth`. + /// * `tree` - the destination to write the GGM tree. + pub fn reconstruct(&self, tree: &mut [Block], k: &[Block], alpha: &[bool]) { + assert_eq!(tree.len(), 1 << (self.depth)); + assert_eq!(k.len(), self.depth); + assert_eq!(alpha.len(), self.depth); + + let mut pos = 0; + for i in 1..=self.depth { + pos *= 2; + tree[pos] = Block::ZERO; + tree[pos + 1] = Block::ZERO; + if !alpha[i - 1] { + self.reconstruct_layer(i, false, pos, k[i - 1], tree); + pos += 1; + } else { + self.reconstruct_layer(i, true, pos + 1, k[i - 1], tree); + } + } + } + + // Handle each layer. + fn reconstruct_layer( + &self, + depth: usize, + left_or_right: bool, + pos: usize, + k: Block, + tree: &mut [Block], + ) { + // How many nodes there are in this layer + let sz = 1 << depth; + + let mut sum = Block::ZERO; + let start = if left_or_right { 1 } else { 0 }; + + for i in (start..sz).step_by(2) { + sum ^= tree[i]; + } + tree[pos] = sum ^ k; + + if depth == (self.depth) { + return; + } + + let mut buf = [Block::ZERO; 8]; + if sz == 2 { + self.tkprp.expand_2to4(&mut buf, tree); + tree[0..4].copy_from_slice(&buf[0..4]); + } else { + for i in (0..=sz - 4).rev().step_by(4) { + self.tkprp.expand_4to8(&mut buf, &tree[i..]); + tree[2 * i..2 * i + 8].copy_from_slice(&buf); + } + } + } } #[test] fn ggm_test() { + use crate::ggm_tree::GgmTree; + use crate::Block; + let depth = 3; - let mut tree = vec![Block::ZERO; 1 << (depth - 1)]; - let mut k0 = vec![Block::ZERO; depth - 1]; - let mut k1 = vec![Block::ZERO; depth - 1]; + let mut tree = vec![Block::ZERO; 1 << depth]; + let mut k0 = vec![Block::ZERO; depth]; + let mut k1 = vec![Block::ZERO; depth]; + let mut k = vec![Block::ZERO; depth]; + let alpha = [false, true, false]; + let mut pos = 0; + + for a in alpha { + pos <<= 1; + if !a { + pos += 1; + } + } let ggm = GgmTree::new(depth); ggm.gen(Block::ZERO, &mut tree, &mut k0, &mut k1); - // Test vectors are from EMP Toolkit. - assert_eq!( - tree, - [ - Block::from(0x92A6DDEAA3E99F9BECB268BD9EF67C91_u128.to_le_bytes()), - Block::from(0x9E7E9C02ED1E62385EE8A9EDDC63A2B5_u128.to_le_bytes()), - Block::from(0xBD4B85E90AACBD106694537DB6251264_u128.to_le_bytes()), - Block::from(0x230485DC4360014833E07D8D914411A2_u128.to_le_bytes()), - ] - ); - - assert_eq!( - k0, - [ - Block::from(0x2E2B34CA59FA4C883B2C8AEFD44BE966_u128.to_le_bytes()), - Block::from(0x2FED5803A945228B8A263BC028D36EF5_u128.to_le_bytes()), - ] - ); - - assert_eq!( - k1, - [ - Block::from(0x7E46C568D1CD4972BB1A61F95DD80EDC_u128.to_le_bytes()), - Block::from(0xBD7A19DEAE7E63706D08D4604D27B317_u128.to_le_bytes()), - ] - ); + for i in 0..depth { + if alpha[i] { + k[i] = k1[i]; + } else { + k[i] = k0[i]; + } + } + + let mut tree_reconstruct = vec![Block::ZERO; 1 << depth]; + ggm.reconstruct(&mut tree_reconstruct, &k, &alpha); + + assert_eq!(tree_reconstruct[pos], Block::ZERO); + tree_reconstruct[pos] = tree[pos]; + assert_eq!(tree, tree_reconstruct); } diff --git a/mpz-core/src/lib.rs b/mpz-core/src/lib.rs index 968e7ce3..c5eda19d 100644 --- a/mpz-core/src/lib.rs +++ b/mpz-core/src/lib.rs @@ -8,7 +8,9 @@ pub mod cointoss; pub mod commit; pub mod ggm_tree; pub mod hash; +pub mod lpn; pub mod prg; +pub mod prp; pub mod serialize; pub mod tkprp; pub mod utils; diff --git a/mpz-core/src/lpn.rs b/mpz-core/src/lpn.rs new file mode 100644 index 00000000..4292a56a --- /dev/null +++ b/mpz-core/src/lpn.rs @@ -0,0 +1,181 @@ +//! Implement LPN with local linear code. +//! More especifically, 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 rayon::prelude::*; +/// An LPN encoder. +/// +/// The `seed` defines a sparse binary matrix `A` with at most `D` non-zero values in each row. +/// +/// Given a vector `x` and `e`, compute `y = Ax + e`. +/// +/// `A` - is a binary matrix with `k` columns and `n` rows. The concrete number of `n` is determined by the input length. `A` will be generated on-the-fly. +/// +/// `x` - is a `F_{2^128}` vector with length `k`. +/// +/// `e` - is a `F_{2^128}` vector with length `n`. +/// +/// Note that in the standard LPN problem, `x` is a binary vector, `e` is a sparse binary vector. The way we difined here is a more generic way in term of computing `y`. +pub struct LpnEncoder { + /// The seed to generate the random sparse matrix A. + seed: Block, + + /// The length of the secret, i.e., x. + k: u32, + + /// A mask to optimize reduction operation. + mask: u32, +} + +impl LpnEncoder { + /// Create a new LPN instance. + pub fn new(seed: Block, k: u32) -> Self { + let mut mask = 1; + while mask < k { + mask <<= 1; + mask |= 0x1; + } + Self { seed, k, mask } + } + + /// Compute 4 rows as a batch, this is for the `compute` function. + #[inline] + fn compute_four_rows_indep(&self, y: &mut [Block], x: &[Block], pos: usize, prp: &Prp) { + let mut cnt = 0u64; + let mut index: [Block; D] = std::array::from_fn(|_| { + let i = cnt; + cnt += 1; + Block::from(bytemuck::cast::<_, [u8; 16]>([pos as u64, i])) + }); + + prp.permute_many_blocks(&mut index); + let index = bytemuck::cast_slice_mut::<_, u32>(&mut index); + + for (i, y) in y.iter_mut().enumerate().take(4) { + for ind in index[i * D..(i + 1) * D].iter_mut() { + *ind &= self.mask; + *ind = if *ind >= self.k { *ind - self.k } else { *ind }; + + *y ^= x[*ind as usize]; + } + } + } + + #[inline] + fn compute_one_row(&self, y: &mut [Block], x: &[Block], pos: usize, prp: &Prp) { + let block_size = (D + 4 - 1) / 4; + let mut index = (0..block_size) + .map(|i| Block::from(bytemuck::cast::<_, [u8; 16]>([pos as u64, i as u64]))) + .collect::>(); + prp.permute_block_inplace(&mut index); + let index = bytemuck::cast_slice_mut::<_, u32>(&mut index); + + for ind in index.iter_mut().take(D) { + *ind &= self.mask; + *ind = if *ind >= self.k { *ind - self.k } else { *ind }; + y[pos] ^= x[*ind as usize]; + } + } + + /// Compute `Ax + e`, writing the result in-place into `y`. + /// + /// # Arguments + /// + /// * `x` - Secret vector with length `k`. + /// * `y` - Error vector with length `n`, this is actually `e` in LPN. + /// + /// # Panics + /// + /// Panics if `x.len() !=k` or `y.len() != n`. + pub fn compute(&self, y: &mut [Block], x: &[Block]) { + assert_eq!(x.len() as u32, self.k); + assert!(x.len() >= D); + let prp = Prp::new(self.seed); + let size = y.len() - (y.len() % 4); + + cfg_if::cfg_if! { + if #[cfg(feature = "rayon")]{ + let iter = y.par_chunks_exact_mut(4).enumerate(); + }else{ + let iter = y.chunks_exact_mut(4).enumerate(); + } + } + + iter.for_each(|(i, y)| { + self.compute_four_rows_indep(y, x, i * 4, &prp); + }); + + for i in size..y.len() { + self.compute_one_row(y, x, i, &prp); + } + } +} + +#[cfg(test)] +mod tests { + use crate::lpn::LpnEncoder; + use crate::prp::Prp; + use crate::Block; + + impl LpnEncoder { + #[allow(dead_code)] + fn compute_four_rows_non_indep(&self, y: &mut [Block], x: &[Block], pos: usize, prp: &Prp) { + let mut cnt = 0u64; + let mut index = [0; D].map(|_| { + let i: u64 = cnt; + cnt += 1; + Block::from(bytemuck::cast::<_, [u8; 16]>([pos as u64, i])) + }); + + prp.permute_many_blocks(&mut index); + let index: &mut [u32] = bytemuck::cast_slice_mut::<_, u32>(&mut index); + + for (i, y) in y[pos..].iter_mut().enumerate().take(4) { + for ind in index[i * D..(i + 1) * D].iter_mut() { + *ind &= self.mask; + *ind = if *ind >= self.k { *ind - self.k } else { *ind }; + + *y ^= x[*ind as usize]; + } + } + } + + #[allow(dead_code)] + pub(crate) fn compute_naive(&self, y: &mut [Block], x: &[Block]) { + assert_eq!(x.len() as u32, self.k); + assert!(x.len() >= D); + let prp = Prp::new(self.seed); + let batch_size = y.len() / 4; + + for i in 0..batch_size { + self.compute_four_rows_non_indep(y, x, i * 4, &prp); + } + + for i in batch_size * 4..y.len() { + self.compute_one_row(y, x, i, &prp); + } + } + } + + #[test] + fn lpn_test() { + use crate::lpn::LpnEncoder; + use crate::prg::Prg; + use crate::Block; + + let k = 20; + let n = 200; + let lpn = LpnEncoder::<10>::new(Block::ZERO, k); + let mut x = vec![Block::ONES; k as usize]; + let mut y = vec![Block::ONES; n]; + let mut prg = Prg::new(); + prg.random_blocks(&mut x); + prg.random_blocks(&mut y); + let mut z = y.clone(); + + lpn.compute_naive(&mut y, &x); + lpn.compute(&mut z, &x); + + assert_eq!(y, z); + } +} diff --git a/mpz-core/src/prp.rs b/mpz-core/src/prp.rs new file mode 100644 index 00000000..e6347132 --- /dev/null +++ b/mpz-core/src/prp.rs @@ -0,0 +1,32 @@ +//! An implementation of Pseudo Random Permutation (PRP) based on AES. + +use crate::{aes::AesEncryptor, Block}; + +/// Pseudo Random Permutation (PRP) based on AES. +pub struct Prp(AesEncryptor); + +impl Prp { + /// Creates a new instance of Prp. + #[inline(always)] + pub fn new(seed: Block) -> Self { + Prp(AesEncryptor::new(seed)) + } + + /// Permute one block. + #[inline(always)] + pub fn permute_block(&self, blk: Block) -> Block { + self.0.encrypt_block(blk) + } + + /// Permute many blocks. + #[inline(always)] + pub fn permute_many_blocks(&self, blks: &mut [Block; N]) { + self.0.encrypt_many_blocks(blks) + } + + /// Permute block slice. + #[inline(always)] + pub fn permute_block_inplace(&self, blks: &mut [Block]) { + self.0.encrypt_blocks(blks); + } +} diff --git a/mpz-core/src/tkprp.rs b/mpz-core/src/tkprp.rs index d3444c71..0760aed6 100644 --- a/mpz-core/src/tkprp.rs +++ b/mpz-core/src/tkprp.rs @@ -16,7 +16,7 @@ impl TwoKeyPrp { /// expand 1 to 2 #[inline(always)] - pub fn expand_1to2(&self, children: &mut [Block], parent: Block) { + pub(crate) fn expand_1to2(&self, children: &mut [Block], parent: Block) { children[0] = parent; children[1] = parent; AesEncryptor::para_encrypt::<2, 1>(&self.0, children); @@ -29,7 +29,7 @@ impl TwoKeyPrp { // c[0] c[1] c[2] c[3] // t[0] t[2] t[1] t[3] #[inline(always)] - pub fn expand_2to4(&self, children: &mut [Block], parent: &[Block]) { + pub(crate) fn expand_2to4(&self, children: &mut [Block], parent: &[Block]) { let mut tmp = [Block::ZERO; 4]; children[3] = parent[1]; children[2] = parent[1]; @@ -54,7 +54,7 @@ impl TwoKeyPrp { // c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] // t[0] t[4] t[1] t[5] t[2] t[6] t[3] t[7] #[inline(always)] - pub fn expand_4to8(&self, children: &mut [Block], parent: &[Block]) { + pub(crate) fn expand_4to8(&self, children: &mut [Block], parent: &[Block]) { let mut tmp = [Block::ZERO; 8]; children[7] = parent[3]; children[6] = parent[3];