Skip to content

Commit

Permalink
merge building blocks (#43)
Browse files Browse the repository at this point in the history
* update aes/block, add prg/ggm

* add cr/ccr hashes

* add reduce operation

* add reduce to block

* add gfmul/inner_product to block

* add bench of reduce

* add bench of aes/prg/ggm

* minor

* handling unsafe and documentation

* handling unsafe and documentation

* update minor changes

* update minor changes

* fmt

* update comments

* minor

* minor

* update sigma, add test
xiangxiecrypto authored and sinui0 committed Aug 30, 2023
1 parent ab4f98d commit a036064
Showing 19 changed files with 872 additions and 9 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -62,6 +62,7 @@ bincode = "1.3.3"
prost-build = "0.9"
bytes = "1"
yamux = "0.10"
bytemuck = {version = "1.13", features = ["derive"]}

# testing
prost = "0.9"
1 change: 1 addition & 0 deletions clmul/Cargo.toml
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ edition = "2021"

[dependencies]
cfg-if.workspace = true
bytemuck = {workspace = true, features = ["derive"]}

[target.'cfg(any(target_arch = "aarch64", target_arch = "x86_64", target_arch = "x86"))'.dependencies]
cpufeatures.workspace = true
4 changes: 4 additions & 0 deletions clmul/benches/clmul.rs
Original file line number Diff line number Diff line change
@@ -20,6 +20,10 @@ fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("clmul_reuse", move |bench| {
bench.iter(|| a.clmul_reuse(&mut b));
});

c.bench_function("reduce", move |bench| {
bench.iter(|| black_box(Clmul::reduce_gcm(a, b)));
});
}

criterion_group!(benches, criterion_benchmark);
93 changes: 93 additions & 0 deletions clmul/src/backend.rs
Original file line number Diff line number Diff line change
@@ -9,6 +9,39 @@ use core::ops::{BitXor, BitXorAssign};
#[cfg_attr(target_pointer_width = "64", path = "backend/soft64.rs")]
mod soft;

impl soft::Clmul {
/// Reduces the polynomial represented in bits modulo the GCM polynomial x^128 + x^7 + x^2 + x + 1.
/// x and y are resp. upper and lower bits of the polynomial.
///
/// Page 16 of [Intel® Carry-Less Multiplication Instruction and its Usage for Computing the GCM Mode rev 2.02]
/// (https://www.intel.com/content/dam/develop/external/us/en/documents/clmul-wp-rev-2-02-2014-04-20.pdf)
pub fn reduce_gcm(x: Self, y: Self) -> Self {
fn sep(x: u128) -> (u64, u64) {
// (high, low)
((x >> 64) as u64, x as u64)
}
fn join(u: u64, l: u64) -> u128 {
((u as u128) << 64) | (l as u128)
}

let x: u128 = bytemuck::cast(x);
let y: u128 = bytemuck::cast(y);

let (x3, x2) = sep(y);
let (x1, x0) = sep(x);
let a = x3 >> 63;
let b = x3 >> 62;
let c = x3 >> 57;
let d = x2 ^ a ^ b ^ c;
let (e1, e0) = sep(join(x3, d) << 1);
let (f1, f0) = sep(join(x3, d) << 2);
let (g1, g0) = sep(join(x3, d) << 7);
let h1 = x3 ^ e1 ^ f1 ^ g1;
let h0 = d ^ e0 ^ f0 ^ g0;
bytemuck::cast(join(x1 ^ h1, x0 ^ h0))
}
}

cfg_if! {
if #[cfg(all(target_arch = "aarch64", feature = "armv8"))] {
#[path = "backend/pmull.rs"]
@@ -150,6 +183,42 @@ impl Clmul {
},
}
}

/// Reduces the polynomial represented in bits modulo the GCM polynomial x^128 + x^7 + x^2 + x + 1.
/// x and y are resp. upper and lower bits of the polynomial.
pub fn reduce_gcm(x: Self, y: Self) -> Self {
match x.intrinsics {
Some(x_intr) => match y.intrinsics {
Some(y_intr) => {
cfg_if! {
if #[cfg(any(all(target_arch = "aarch64", feature = "armv8"), any(target_arch = "x86_64", target_arch = "x86")))]{
let r = intrinsics::Clmul::reduce_gcm(x_intr, y_intr);
}else{
let r = soft::Clmul::reduce_gcm(x_intr, y_intr);
}
}
Self {
intrinsics: Some(r),
soft: None,
}
}
None => unreachable!(),
},
None => match x.soft {
Some(x_soft) => match y.soft {
Some(y_soft) => {
let r = soft::Clmul::reduce_gcm(x_soft, y_soft);
Self {
intrinsics: None,
soft: Some(r),
}
}
None => unreachable!(),
},
None => unreachable!(),
},
}
}
}

impl From<Clmul> for [u8; 16] {
@@ -232,3 +301,27 @@ impl PartialEq for Clmul {
}
}
}

#[test]
fn reduce_test() {
use rand::Rng;
use rand_chacha::{rand_core::SeedableRng, ChaCha12Rng};

let mut rng = ChaCha12Rng::from_seed([0; 32]);
let x: [u8; 16] = rng.gen();
let y: [u8; 16] = rng.gen();

let xx = soft::Clmul::new(&x);
let yy = soft::Clmul::new(&y);

let zz = soft::Clmul::reduce_gcm(xx, yy);
let zz: [u8; 16] = zz.into();

let xxx = Clmul::new(&x);
let yyy = Clmul::new(&y);

let zzz = Clmul::reduce_gcm(xxx, yyy);
let zzz: [u8; 16] = zzz.into();

assert_eq!(zz, zzz);
}
36 changes: 36 additions & 0 deletions clmul/src/backend/clmul.rs
Original file line number Diff line number Diff line change
@@ -84,6 +84,42 @@ impl ClmulX86 {
ClmulX86(_mm_unpacklo_epi64(v2, v3)),
)
}

#[inline(always)]
pub fn reduce_gcm(x: Self, y: Self) -> ClmulX86 {
unsafe { Self::reduce_gcm_unsafe(&x, &y) }
}

// This implementation is adapted from EMP Toolkit.
#[inline]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe fn reduce_gcm_unsafe(x: &Self, y: &Self) -> ClmulX86 {
let tmp3 = x.0;
let tmp6 = y.0;
let xmmmask = _mm_setr_epi32(-1, 0x0, 0x0, 0x0);
let tmp7 = _mm_srli_epi32(tmp6, 31);
let tmp8 = _mm_srli_epi32(tmp6, 30);
let tmp9 = _mm_srli_epi32(tmp6, 25);

let tmp7 = _mm_xor_si128(tmp7, tmp8);
let tmp7 = _mm_xor_si128(tmp7, tmp9);

let tmp8 = _mm_shuffle_epi32(tmp7, 147);

let tmp7 = _mm_and_si128(xmmmask, tmp8);
let tmp8 = _mm_andnot_si128(xmmmask, tmp8);
let tmp3 = _mm_xor_si128(tmp3, tmp8);
let tmp6 = _mm_xor_si128(tmp6, tmp7);

let tmp10 = _mm_slli_epi32(tmp6, 1);
let tmp3 = _mm_xor_si128(tmp3, tmp10);
let tmp11 = _mm_slli_epi32(tmp6, 2);
let tmp3 = _mm_xor_si128(tmp3, tmp11);
let tmp12 = _mm_slli_epi32(tmp6, 7);
let tmp3 = _mm_xor_si128(tmp3, tmp12);

ClmulX86(_mm_xor_si128(tmp3, tmp6))
}
}

#[cfg(test)]
57 changes: 57 additions & 0 deletions clmul/src/backend/pmull.rs
Original file line number Diff line number Diff line change
@@ -83,6 +83,63 @@ impl ClmulArm {

(ClmulArm(r0), ClmulArm(r1))
}

#[inline(always)]
pub fn reduce_gcm(x: Self, y: Self) -> ClmulArm {
unsafe { Self::reduce_gcm_unsafe(&x, &y) }
}

// This implementation is adapted from EMP Toolkit.
#[inline]
#[target_feature(enable = "neon")]
unsafe fn reduce_gcm_unsafe(x: &Self, y: &Self) -> ClmulArm {
macro_rules! _mm_shuffle_epi32 {
($a:expr,$IMM8:expr) => {{
let ret = vmovq_n_u32(vgetq_lane_u32(vreinterpretq_u32_u8($a), $IMM8 & (0x3)));
let ret = vsetq_lane_u32(
vgetq_lane_u32(vreinterpretq_u32_u8($a), ($IMM8 >> 2) & (0x3)),
ret,
1,
);

let ret = vsetq_lane_u32(
vgetq_lane_u32(vreinterpretq_u32_u8($a), ($IMM8 >> 4) & (0x3)),
ret,
2,
);
let ret = vreinterpretq_u8_u32(vsetq_lane_u32(
vgetq_lane_u32(vreinterpretq_u32_u8($a), ($IMM8 >> 6) & (0x3)),
ret,
3,
));
ret
}};
}

let tmp3 = x.0;
let tmp6 = y.0;
let xmmmask = vreinterpretq_u8_u32(vld1q_u32([0xffffffff, 0x0, 0x0, 0x0].as_ptr()));
let tmp7 = vreinterpretq_u8_u32(vshlq_u32(vreinterpretq_u32_u8(tmp6), vdupq_n_s32(-31)));
let tmp8 = vreinterpretq_u8_u32(vshlq_u32(vreinterpretq_u32_u8(tmp6), vdupq_n_s32(-30)));
let tmp9 = vreinterpretq_u8_u32(vshlq_u32(vreinterpretq_u32_u8(tmp6), vdupq_n_s32(-25)));

let tmp7 = veorq_u8(tmp7, tmp8);
let tmp7 = veorq_u8(tmp7, tmp9);
let tmp8 = _mm_shuffle_epi32!(tmp7, 147);

let tmp7 = vandq_u8(xmmmask, tmp8);
let tmp8 = vbicq_u8(tmp8, xmmmask);
let tmp3 = veorq_u8(tmp3, tmp8);
let tmp6 = veorq_u8(tmp6, tmp7);

let tmp10 = vreinterpretq_u8_u32(vshlq_u32(vreinterpretq_u32_u8(tmp6), vdupq_n_s32(1)));
let tmp3 = veorq_u8(tmp3, tmp10);
let tmp11 = vreinterpretq_u8_u32(vshlq_u32(vreinterpretq_u32_u8(tmp6), vdupq_n_s32(2)));
let tmp3 = veorq_u8(tmp3, tmp11);
let tmp12 = vreinterpretq_u8_u32(vshlq_u32(vreinterpretq_u32_u8(tmp6), vdupq_n_s32(7)));
let tmp3 = veorq_u8(tmp3, tmp12);
ClmulArm(veorq_u8(tmp3, tmp6))
}
}

/// Wrapper for the ARM64 `PMULL` instruction.
5 changes: 3 additions & 2 deletions clmul/src/backend/soft32.rs
Original file line number Diff line number Diff line change
@@ -25,12 +25,13 @@
//! In other words, if we bit-reverse (over 32 bits) the operands, then we
//! bit-reverse (over 64 bits) the result.
use bytemuck::{Pod, Zeroable};
use core::{num::Wrapping, ops::BitXor};

pub type Clmul = U32x4;

/// 4 x `u32` values
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Pod, Zeroable)]
pub struct U32x4(u32, u32, u32, u32);

impl From<U32x4> for [u8; 16] {
5 changes: 3 additions & 2 deletions clmul/src/backend/soft64.rs
Original file line number Diff line number Diff line change
@@ -5,12 +5,13 @@
//!
//! Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
//!
use bytemuck::{Pod, Zeroable};
use core::{num::Wrapping, ops::BitXor};

pub type Clmul = U64x2;

/// 2 x `u64` values
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Pod, Zeroable)]
pub struct U64x2(u64, u64);

impl From<U64x2> for [u8; 16] {
14 changes: 14 additions & 0 deletions mpz-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -23,7 +23,21 @@ once_cell.workspace = true
itybity.workspace = true
opaque-debug.workspace = true
bcs = "0.1.5"
rand_core = "0.6.4"
bytemuck = {workspace = true, features = ["derive"]}

[dev-dependencies]
rstest.workspace = true
criterion.workspace = true

[[bench]]
name = "aes"
harness = false

[[bench]]
name = "ggm"
harness = false

[[bench]]
name = "prg"
harness = false
42 changes: 42 additions & 0 deletions mpz-core/benches/aes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};

use mpz_core::{aes::AesEncryptor, block::Block};

fn criterion_benchmark(c: &mut Criterion) {
let x = rand::random::<Block>();
let aes = AesEncryptor::new(x);
let blk = rand::random::<Block>();

c.bench_function("aes::encrypt_block", move |bench| {
bench.iter(|| {
let z = aes.encrypt_block(black_box(blk));
black_box(z);
});
});

c.bench_function("aes::encrypt_many_blocks::<8>", move |bench| {
let key = rand::random::<Block>();
let aes = AesEncryptor::new(key);
let blks = rand::random::<[Block; 8]>();

bench.iter(|| {
let z = aes.encrypt_many_blocks(black_box(blks));
black_box(z);
});
});

c.bench_function("aes::para_encrypt::<1,8>", move |bench| {
let key = rand::random::<Block>();
let aes = AesEncryptor::new(key);
let aes = [aes];
let mut blks = rand::random::<[Block; 8]>();

bench.iter(|| {
let z = AesEncryptor::para_encrypt::<1, 8>(black_box(&aes), black_box(&mut blks));
black_box(z);
});
});
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
24 changes: 24 additions & 0 deletions mpz-core/benches/ggm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use mpz_core::{block::Block, ggm_tree::GgmTree};

fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("ggm::gen::1K", move |bench| {
let depth = 11;
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 seed = rand::random::<Block>();
bench.iter(|| {
black_box(ggm.gen(
black_box(seed),
black_box(&mut tree),
black_box(&mut k0),
black_box(&mut k1),
));
});
});
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
47 changes: 47 additions & 0 deletions mpz-core/benches/prg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};

use mpz_core::{block::Block, prg::Prg};
use rand_core::RngCore;

fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("Prg::byte", move |bench| {
let mut prg = Prg::new();
let mut x = 0u8;
bench.iter(|| {
x = prg.random_byte();
black_box(x);
});
});

c.bench_function("Prg::bytes", move |bench| {
let mut prg = Prg::new();
let mut x = (0..16 * 1024)
.map(|_| rand::random::<u8>())
.collect::<Vec<u8>>();
bench.iter(|| {
prg.fill_bytes(black_box(&mut x));
});
});

c.bench_function("Prg::block", move |bench| {
let mut prg = Prg::new();
let mut x = Block::ZERO;
bench.iter(|| {
x = prg.random_block();
black_box(x);
});
});

c.bench_function("Prg::blocks", move |bench| {
let mut prg = Prg::new();
let mut x = (0..16 * 1024)
.map(|_| rand::random::<Block>())
.collect::<Vec<Block>>();
bench.iter(|| {
prg.random_blocks(black_box(&mut x));
});
});
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
92 changes: 91 additions & 1 deletion mpz-core/src/aes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Fixed-key AES cipher
use aes::{cipher::generic_array::GenericArray, Aes128};
use aes::{cipher::generic_array::GenericArray, Aes128, Aes128Enc};
use cipher::{generic_array::functional::FunctionalSequence, BlockEncrypt, KeyInit};
use once_cell::sync::Lazy;

@@ -42,4 +42,94 @@ impl FixedKeyAes {

Block::from(out)
}

/// Correlation-robust hash function for 128-bit inputs
/// (cf. <https://eprint.iacr.org/2019/074>, §7.2).
/// The function computes `π(x) ⊕ x`.
/// π(x) = AES(fixedkey,x)
#[inline]
pub fn cr(&self, block: Block) -> Block {
let mut x = GenericArray::from(block);
self.aes.encrypt_block(&mut x);
Block::from(x) ^ block
}

/// Circular correlation-robust hash function
/// (cf.<https://eprint.iacr.org/2019/074>, §7.3).
///
/// The function computes `H(sigma(x))`, where `H` is a correlation-robust hash
/// function and `sigma( x = x0 || x1 ) = x1 || (x0 xor x1)`.
/// `x0` and `x1` are the lower and higher halves of `x`, respectively.
#[inline]
pub fn ccr(&self, block: Block) -> Block {
self.cr(Block::sigma(block))
}
}

/// A wrapper of aes, only for encryption.
#[derive(Clone)]
pub struct AesEncryptor(Aes128Enc);

impl AesEncryptor {
/// Constant number of AES blocks, always set to 8.
pub const AES_BLOCK_COUNT: usize = 8;

/// Initiate an AesEncryptor instance with key.
#[inline(always)]
pub fn new(key: Block) -> Self {
let _key: [u8; 16] = key.into();
AesEncryptor(Aes128Enc::new_from_slice(&_key).unwrap())
}

/// Encrypt a block.
#[inline(always)]
pub fn encrypt_block(&self, blk: Block) -> Block {
let mut ctxt = GenericArray::from(blk);
self.0.encrypt_block(&mut ctxt);
Block::from(ctxt)
}

/// Encrypt many blocks.
#[inline(always)]
pub fn encrypt_many_blocks<const N: usize>(&self, blks: [Block; N]) -> [Block; N] {
let mut ctxt = [Block::default(); N];
for i in 0..N {
ctxt[i] = self.encrypt_block(blks[i]);
}
ctxt
}

/// Encrypt many blocks with many keys.
/// Input: `NK` AES keys `keys`, and `NK * NM` blocks `blks`
/// Output: each batch of NM blocks encrypted by a corresponding AES key.
/// Only handle the first NK * NM blocks of blks, do not handle the rest.
#[inline(always)]
pub fn para_encrypt<const NK: usize, const NM: usize>(keys: &[Self; NK], blks: &mut [Block]) {
assert!(blks.len() >= NM * NK);
let mut ctxt = [Block::default(); NM];
keys.iter().enumerate().for_each(|(i, key)| {
ctxt.copy_from_slice(&blks[i * NM..(i + 1) * NM]);
blks[i * NM..(i + 1) * NM].copy_from_slice(&key.encrypt_many_blocks(ctxt))
});
}
}

#[test]
fn aes_test() {
let aes = AesEncryptor::new(Block::default());
let aes1 = AesEncryptor::new(Block::ONES);

let mut blks = [Block::default(); 4];
blks[1] = Block::ONES;
blks[3] = Block::ONES;
AesEncryptor::para_encrypt::<2, 2>(&[aes, aes1], &mut blks);
assert_eq!(
blks,
[
Block::from((0x2E2B34CA59FA4C883B2C8AEFD44BE966_u128).to_le_bytes()),
Block::from((0x4E668D3ED24773FA0A5A85EAC98C5B3F_u128).to_le_bytes()),
Block::from((0x2CC9BF3845486489CD5F7D878C25F6A1_u128).to_le_bytes()),
Block::from((0x79B93A19527051B230CF80B27C21BFBC_u128).to_le_bytes())
]
);
}
117 changes: 115 additions & 2 deletions mpz-core/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
//! A block of 128 bits and its operations.
use bytemuck::{Pod, Zeroable};
use cipher::{consts::U16, generic_array::GenericArray};
use clmul::Clmul;
use core::ops::{BitAnd, BitXor};
use core::ops::{BitAnd, BitAndAssign, BitXor, BitXorAssign};
use itybity::{BitIterable, BitLength, GetBit, Lsb0, Msb0};
use rand::{distributions::Standard, prelude::Distribution, CryptoRng, Rng};
use serde::{Deserialize, Serialize};
use std::convert::From;

/// A block of 128 bits
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize, Pod, Zeroable)]
pub struct Block([u8; 16]);

impl Block {
@@ -58,6 +61,40 @@ impl Block {
(Self::new(a.into()), Self::new(b.into()))
}

#[inline]
/// Reduces the polynomial represented in bits modulo the GCM polynomial x^128 + x^7 + x^2 + x + 1.
/// `x` and `y` are resp. upper and lower bits of the polynomial.
pub fn reduce_gcm(x: Self, y: Self) -> Self {
let r = Clmul::reduce_gcm(Clmul::new(&x.0), Clmul::new(&y.0));
Self::new(r.into())
}

/// The multiplication of two Galois field elements.
#[inline]
pub fn gfmul(self, x: Self) -> Self {
let (a, b) = self.clmul(x);
Block::reduce_gcm(a, b)
}

/// Compute the inner product of two block vectors, without reducing the polynomial.
#[inline]
pub fn inn_prdt_no_red(a: &[Block], b: &[Block]) -> (Block, Block) {
assert_eq!(a.len(), b.len());
a.iter()
.zip(b.iter())
.fold((Block::ZERO, Block::ZERO), |acc, (x, y)| {
let t = x.clmul(*y);
(t.0 ^ acc.0, t.1 ^ acc.1)
})
}

/// Compute the inner product of two block vectors.
#[inline]
pub fn inn_prdt_red(a: &[Block], b: &[Block]) -> Block {
let (x, y) = Block::inn_prdt_no_red(a, b);
Block::reduce_gcm(x, y)
}

/// Reverses the bits of the block
#[inline]
pub fn reverse_bits(self) -> Self {
@@ -75,6 +112,15 @@ impl Block {
pub fn lsb(&self) -> usize {
((self.0[0] & 1) == 1) as usize
}

/// Let `x0` and `x1` be the lower and higher halves of `x`, respectively.
/// This function compute ``sigma( x = x0 || x1 ) = x1 || (x0 xor x1)``.
#[inline(always)]
pub fn sigma(a: Self) -> Self {
let mut x: [u64; 2] = bytemuck::cast(a);
x[0] ^= x[1];
bytemuck::cast([x[1], x[0]])
}
}

/// A trait for converting a type to blocks
@@ -152,6 +198,13 @@ impl BitXor for Block {
}
}

impl BitXorAssign for Block {
#[inline(always)]
fn bitxor_assign(&mut self, rhs: Self) {
*self = *self ^ rhs;
}
}

impl BitAnd for Block {
type Output = Self;

@@ -161,12 +214,26 @@ impl BitAnd for Block {
}
}

impl BitAndAssign for Block {
#[inline(always)]
fn bitand_assign(&mut self, rhs: Self) {
*self = *self & rhs
}
}

impl Distribution<Block> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Block {
Block::new(rng.gen())
}
}

impl AsMut<[u8]> for Block {
#[inline(always)]
fn as_mut(&mut self) -> &mut [u8] {
self.0.as_mut()
}
}

#[cfg(test)]
mod tests {
use itybity::ToBits;
@@ -224,4 +291,50 @@ mod tests {

assert_eq!(a.reverse_bits().to_lsb0_vec(), expected_bits);
}

#[test]
fn inn_prdt_test() {
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha12Rng;
let mut rng = ChaCha12Rng::from_seed([0; 32]);

const SIZE: usize = 1000;
let mut a = Vec::new();
let mut b = Vec::new();
let mut c = (Block::ZERO, Block::ZERO);
let mut d = Block::ZERO;
for i in 0..SIZE {
let r: [u8; 16] = rng.gen();
a.push(Block::from(r));
let r: [u8; 16] = rng.gen();
b.push(Block::from(r));

let z = a[i].clmul(b[i]);
c.0 = c.0 ^ z.0;
c.1 = c.1 ^ z.1;

let x = a[i].gfmul(b[i]);
d ^= x;
}

assert_eq!(c, Block::inn_prdt_no_red(&a, &b));
assert_eq!(d, Block::inn_prdt_red(&a, &b));
}

#[test]
fn sigma_test() {
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha12Rng;
let mut rng = ChaCha12Rng::from_seed([0; 32]);
let mut x: [u8; 16] = rng.gen();
let bx = Block::sigma(Block::from(x));
let (xl, xr) = x.split_at_mut(8);

for (x, y) in xl.iter_mut().zip(xr.iter_mut()) {
*x ^= *y;
std::mem::swap(x, y);
}
let expected_sigma = Block::from(x);
assert_eq!(bx, expected_sigma);
}
}
98 changes: 98 additions & 0 deletions mpz-core/src/ggm_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Implement GGM tree for OT.
//! Implementation of GGM based on the procedure explained in the write-up
//! (<https://eprint.iacr.org/2020/925.pdf>, Page 14)
use crate::{tkprp::TwoKeyPrp, Block};

/// Struct of GGM
pub struct GgmTree {
tkprp: TwoKeyPrp,
depth: usize,
}

impl GgmTree {
///New GgmTree instance.
#[inline(always)]
pub fn new(depth: usize) -> Self {
let tkprp = TwoKeyPrp::new([Block::ZERO, Block::from(1u128.to_le_bytes())]);
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.
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];
self.tkprp.expand_1to2(tree, seed);
k0[0] = tree[0];
k1[0] = tree[1];

self.tkprp.expand_2to4(&mut buf, tree);
k0[1] = buf[0] ^ buf[2];
k1[1] = buf[1] ^ buf[3];
tree[0..4].copy_from_slice(&buf[0..4]);

for h in 2..self.depth - 1 {
k0[h] = Block::ZERO;
k1[h] = Block::ZERO;
let sz = 1 << h;
for i in (0..=sz - 4).rev().step_by(4) {
self.tkprp.expand_4to8(&mut buf, &tree[i..]);
k0[h] ^= buf[0];
k0[h] ^= buf[2];
k0[h] ^= buf[4];
k0[h] ^= buf[6];
k1[h] ^= buf[1];
k1[h] ^= buf[3];
k1[h] ^= buf[5];
k1[h] ^= buf[7];

tree[2 * i..2 * i + 8].copy_from_slice(&buf);
}
}
}
}

#[test]
fn ggm_test() {
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 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 as u128).to_le_bytes()),
Block::from((0x9E7E9C02ED1E62385EE8A9EDDC63A2B5 as u128).to_le_bytes()),
Block::from((0xBD4B85E90AACBD106694537DB6251264 as u128).to_le_bytes()),
Block::from((0x230485DC4360014833E07D8D914411A2 as u128).to_le_bytes()),
]
);

assert_eq!(
k0,
[
Block::from((0x2E2B34CA59FA4C883B2C8AEFD44BE966 as u128).to_le_bytes()),
Block::from((0x2FED5803A945228B8A263BC028D36EF5 as u128).to_le_bytes()),
]
);

assert_eq!(
k1,
[
Block::from((0x7E46C568D1CD4972BB1A61F95DD80EDC as u128).to_le_bytes()),
Block::from((0xBD7A19DEAE7E63706D08D4604D27B317 as u128).to_le_bytes()),
]
);
}
5 changes: 4 additions & 1 deletion mpz-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -3,11 +3,14 @@
#![deny(clippy::all)]

pub mod aes;
mod block;
pub mod block;
pub mod cointoss;
pub mod commit;
pub mod ggm_tree;
pub mod hash;
pub mod prg;
pub mod serialize;
pub mod tkprp;
pub mod utils;
pub mod value;

149 changes: 149 additions & 0 deletions mpz-core/src/prg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//! Implement AES-based PRG.
use crate::{aes::AesEncryptor, Block};
use rand::Rng;
use rand_core::{
block::{BlockRng, BlockRngCore},
CryptoRng, RngCore, SeedableRng,
};
/// Struct of PRG Core
#[derive(Clone)]
struct PrgCore {
aes: AesEncryptor,
state: u64,
}

// This implementation is somehow standard, and is adapted from Swanky.
impl BlockRngCore for PrgCore {
type Item = u32;
type Results = [u32; 4 * AesEncryptor::AES_BLOCK_COUNT];

// Compute [AES(state)..AES(state+8)]
#[inline(always)]
fn generate(&mut self, results: &mut Self::Results) {
let states = [0; AesEncryptor::AES_BLOCK_COUNT].map(
#[inline(always)]
|_| {
let x = self.state;
self.state += 1;
Block::from(bytemuck::cast::<_, [u8; 16]>([x, 0u64]))
},
);
*results = bytemuck::cast(self.aes.encrypt_many_blocks(states))
}
}

impl SeedableRng for PrgCore {
type Seed = Block;

#[inline(always)]
fn from_seed(seed: Self::Seed) -> Self {
let aes = AesEncryptor::new(seed);
Self { aes, state: 0u64 }
}
}

impl CryptoRng for PrgCore {}

/// Struct of PRG
#[derive(Clone)]
pub struct Prg(BlockRng<PrgCore>);

impl RngCore for Prg {
#[inline(always)]
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}

#[inline(always)]
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}

#[inline(always)]
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}

#[inline(always)]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
self.0.try_fill_bytes(dest)
}
}

impl SeedableRng for Prg {
type Seed = Block;

#[inline(always)]
fn from_seed(seed: Self::Seed) -> Self {
Prg(BlockRng::<PrgCore>::from_seed(seed))
}

#[inline(always)]
fn from_rng<R: RngCore>(rng: R) -> Result<Self, rand_core::Error> {
BlockRng::<PrgCore>::from_rng(rng).map(Prg)
}
}

impl CryptoRng for Prg {}

impl Prg {
/// New Prg with random seed.
#[inline(always)]
pub fn new() -> Self {
let seed = rand::random::<Block>();
Prg::from_seed(seed)
}

/// Generate a random bool value.
#[inline(always)]
pub fn random_bool(&mut self) -> bool {
self.gen()
}

/// Fill a bool slice with random bool values.
#[inline(always)]
pub fn random_bools(&mut self, buf: &mut [bool]) {
self.fill(buf);
}

/// Generate a random byte value.
#[inline(always)]
pub fn random_byte(&mut self) -> u8 {
self.gen()
}

/// Fill a byte slice with random values.
#[inline(always)]
pub fn random_bytes(&mut self, buf: &mut [u8]) {
self.fill_bytes(buf);
}

/// Generate a random block.
#[inline(always)]
pub fn random_block(&mut self) -> Block {
self.gen()
}

/// Fill a block slice with random block values.
#[inline(always)]
pub fn random_blocks(&mut self, buf: &mut [Block]) {
let bytes: &mut [u8] = bytemuck::cast_slice_mut(buf);
self.fill_bytes(bytes);
}
}

impl Default for Prg {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}

#[test]
fn prg_test() {
let mut prg = Prg::new();
let mut x = vec![Block::ZERO; 2];
prg.random_blocks(&mut x);
assert_ne!(x[0], x[1]);
}
88 changes: 88 additions & 0 deletions mpz-core/src/tkprp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! Implement the two-key PRG as G(k) = PRF_seed0(k)\xor k || PRF_seed1(k)\xor k
//! Refer to (<https://www.usenix.org/system/files/conference/nsdi17/nsdi17-wang-frank.pdf>, Page 8)
use crate::{aes::AesEncryptor, Block};

/// Struct of two-key prp.
/// This implementation is adapted from EMP toolkit.
pub struct TwoKeyPrp([AesEncryptor; 2]);

impl TwoKeyPrp {
/// New an instance of TwoKeyPrp
#[inline(always)]
pub fn new(seeds: [Block; 2]) -> Self {
Self([AesEncryptor::new(seeds[0]), AesEncryptor::new(seeds[1])])
}

/// expand 1 to 2
#[inline(always)]
pub fn expand_1to2(&self, children: &mut [Block], parent: Block) {
children[0] = parent;
children[1] = parent;
AesEncryptor::para_encrypt::<2, 1>(&self.0, children);
children[0] ^= parent;
children[1] ^= parent;
}

/// expand 2 to 4
// p[0] p[1]
// 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]) {
let mut tmp = [Block::ZERO; 4];
children[3] = parent[1];
children[2] = parent[1];
children[1] = parent[0];
children[0] = parent[0];

tmp[3] = parent[1];
tmp[1] = parent[1];
tmp[2] = parent[0];
tmp[0] = parent[0];

AesEncryptor::para_encrypt::<2, 2>(&self.0, &mut tmp);

children[3] ^= tmp[3];
children[2] ^= tmp[1];
children[1] ^= tmp[2];
children[0] ^= tmp[0];
}

/// expand 4 to 8
// p[0] p[1] p[2] p[3]
// 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]) {
let mut tmp = [Block::ZERO; 8];
children[7] = parent[3];
children[6] = parent[3];
children[5] = parent[2];
children[4] = parent[2];
children[3] = parent[1];
children[2] = parent[1];
children[1] = parent[0];
children[0] = parent[0];

tmp[7] = parent[3];
tmp[3] = parent[3];
tmp[6] = parent[2];
tmp[2] = parent[2];
tmp[5] = parent[1];
tmp[1] = parent[1];
tmp[4] = parent[0];
tmp[0] = parent[0];

AesEncryptor::para_encrypt::<2, 4>(&self.0, &mut tmp);

children[7] ^= tmp[7];
children[6] ^= tmp[3];
children[5] ^= tmp[6];
children[4] ^= tmp[2];
children[3] ^= tmp[5];
children[2] ^= tmp[1];
children[1] ^= tmp[4];
children[0] ^= tmp[0];
}
}
3 changes: 2 additions & 1 deletion ot/mpz-ot/src/chou_orlandi/receiver.rs
Original file line number Diff line number Diff line change
@@ -213,7 +213,8 @@ impl CommittedOTReceiver<bool, Block> for Receiver {
let Some(cointoss_payload) = self.cointoss_payload.take() else {
return Err(ReceiverError::InvalidConfig(
"receiver not configured to commit".to_string(),
).into())
)
.into());
};

let reveal = receiver.reveal_choices().map_err(ReceiverError::from)?;

0 comments on commit a036064

Please sign in to comment.