diff --git a/README.md b/README.md index d73a955..bb5e159 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,11 @@ use rand::prelude::*; // Matyas-Meyer-Oseas (via AES128) provides 128-bit security and should be enough. // Hirose (via AES256) still only provides 128-bit security because the output is not chained. // But Hirose can be helpful is you are forced to choose AES256. -use fss_rs::dcf::prg::Aes128MatyasMeyerOseasPrg; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dcf::{Dcf, DcfImpl}; let keys: [[u8; 32]; 2] = thread_rng().gen(); -let prg = Aes128MatyasMeyerOseasPrg::<16, 2>::new(std::array::from_fn(|i| &keys[i])); +let prg = Aes128MatyasMeyerOseasPrg::<16, 2, 2>::new(std::array::from_fn(|i| &keys[i])); // DCF for example let dcf = DcfImpl::<16, 16, _>::new(prg); ``` diff --git a/benches/dcf_eval.rs b/benches/dcf_eval.rs index 55cf8b8..524e14f 100644 --- a/benches/dcf_eval.rs +++ b/benches/dcf_eval.rs @@ -4,10 +4,10 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use rand::prelude::*; -use fss_rs::dcf::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dcf::{BoundState, CmpFn, Dcf, DcfImpl}; use fss_rs::group::byte::ByteGroup; use fss_rs::group::Group; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; fn from_domain_range_size( c: &mut Criterion, @@ -16,7 +16,7 @@ fn from_domain_range_size::new(keys_iter); + let prg = Aes128MatyasMeyerOseasPrg::::new(keys_iter); let dcf = DcfImpl::::new(prg); let mut s0s = [[0; OUT_BLEN]; 2]; diff --git a/benches/dcf_eval_batch.rs b/benches/dcf_eval_batch.rs index 1955245..5e6f0af 100644 --- a/benches/dcf_eval_batch.rs +++ b/benches/dcf_eval_batch.rs @@ -4,10 +4,10 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use rand::prelude::*; -use fss_rs::dcf::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dcf::{BoundState, CmpFn, Dcf, DcfImpl}; use fss_rs::group::byte::ByteGroup; use fss_rs::group::Group; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; const POINT_NUM: usize = 10000; @@ -18,7 +18,7 @@ fn from_domain_range_size::new(keys_iter); + let prg = Aes128MatyasMeyerOseasPrg::::new(keys_iter); let dcf = DcfImpl::::new(prg); let mut s0s = [[0; OUT_BLEN]; 2]; diff --git a/benches/dcf_full_eval.rs b/benches/dcf_full_eval.rs index f58c3cb..a4210b5 100644 --- a/benches/dcf_full_eval.rs +++ b/benches/dcf_full_eval.rs @@ -4,10 +4,10 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use rand::prelude::*; -use fss_rs::dcf::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dcf::{BoundState, CmpFn, Dcf, DcfImpl}; use fss_rs::group::byte::ByteGroup; use fss_rs::group::Group; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; fn from_domain_range_size( c: &mut Criterion, @@ -17,7 +17,7 @@ fn from_domain_range_size::new(keys_iter); + let prg = Aes128MatyasMeyerOseasPrg::::new(keys_iter); let dcf = DcfImpl::::new_with_filter(prg, filter_bitn); let mut s0s = [[0; OUT_BLEN]; 2]; diff --git a/benches/dcf_gen.rs b/benches/dcf_gen.rs index 9278f75..1b6f192 100644 --- a/benches/dcf_gen.rs +++ b/benches/dcf_gen.rs @@ -4,9 +4,9 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use rand::prelude::*; -use fss_rs::dcf::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dcf::{BoundState, CmpFn, Dcf, DcfImpl}; use fss_rs::group::byte::ByteGroup; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; fn from_domain_range_size( c: &mut Criterion, @@ -15,7 +15,7 @@ fn from_domain_range_size::new(keys_iter); + let prg = Aes128MatyasMeyerOseasPrg::::new(keys_iter); let dcf = DcfImpl::::new(prg); let mut s0s = [[0; OUT_BLEN]; 2]; diff --git a/benches/dpf_eval.rs b/benches/dpf_eval.rs index 77b28ea..73bbdd9 100644 --- a/benches/dpf_eval.rs +++ b/benches/dpf_eval.rs @@ -4,10 +4,10 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use rand::prelude::*; -use fss_rs::dpf::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dpf::{Dpf, DpfImpl, PointFn}; use fss_rs::group::byte::ByteGroup; use fss_rs::group::Group; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; fn from_domain_range_size( c: &mut Criterion, @@ -16,7 +16,7 @@ fn from_domain_range_size::new(keys_iter); + let prg = Aes128MatyasMeyerOseasPrg::::new(keys_iter); let dpf = DpfImpl::::new(prg); let mut s0s = [[0; OUT_BLEN]; 2]; diff --git a/benches/dpf_eval_batch.rs b/benches/dpf_eval_batch.rs index 934de0e..6d0e1cc 100644 --- a/benches/dpf_eval_batch.rs +++ b/benches/dpf_eval_batch.rs @@ -4,10 +4,10 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use rand::prelude::*; -use fss_rs::dpf::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dpf::{Dpf, DpfImpl, PointFn}; use fss_rs::group::byte::ByteGroup; use fss_rs::group::Group; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; const POINT_NUM: usize = 10000; @@ -18,7 +18,7 @@ fn from_domain_range_size::new(keys_iter); + let prg = Aes128MatyasMeyerOseasPrg::::new(keys_iter); let dpf = DpfImpl::::new(prg); let mut s0s = [[0; OUT_BLEN]; 2]; diff --git a/benches/dpf_full_eval.rs b/benches/dpf_full_eval.rs index 3a76735..ade7236 100644 --- a/benches/dpf_full_eval.rs +++ b/benches/dpf_full_eval.rs @@ -4,10 +4,10 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use rand::prelude::*; -use fss_rs::dpf::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dpf::{Dpf, DpfImpl, PointFn}; use fss_rs::group::byte::ByteGroup; use fss_rs::group::Group; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; fn from_domain_range_size( c: &mut Criterion, @@ -17,7 +17,7 @@ fn from_domain_range_size::new(keys_iter); + let prg = Aes128MatyasMeyerOseasPrg::::new(keys_iter); let dpf = DpfImpl::::new_with_filter(prg, filter_bitn); let mut s0s = [[0; OUT_BLEN]; 2]; diff --git a/benches/dpf_gen.rs b/benches/dpf_gen.rs index 56870f0..a9c8234 100644 --- a/benches/dpf_gen.rs +++ b/benches/dpf_gen.rs @@ -4,9 +4,9 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use rand::prelude::*; -use fss_rs::dpf::prg::Aes128MatyasMeyerOseasPrg; use fss_rs::dpf::{Dpf, DpfImpl, PointFn}; use fss_rs::group::byte::ByteGroup; +use fss_rs::prg::Aes128MatyasMeyerOseasPrg; fn from_domain_range_size( c: &mut Criterion, @@ -15,7 +15,7 @@ fn from_domain_range_size::new(keys_iter); + let prg = Aes128MatyasMeyerOseasPrg::::new(keys_iter); let dpf = DpfImpl::::new(prg); let mut s0s = [[0; OUT_BLEN]; 2]; diff --git a/src/dcf/mod.rs b/src/dcf/mod.rs index 0818367..334372d 100644 --- a/src/dcf/mod.rs +++ b/src/dcf/mod.rs @@ -9,10 +9,7 @@ use rayon::prelude::*; use crate::group::Group; use crate::utils::{xor, xor_inplace}; -use crate::{decl_prg_trait, Cw, PointFn, Share}; - -#[cfg(feature = "prg")] -pub mod prg; +use crate::{Cw, PointFn, Prg, Share}; /// Distributed comparison function API. /// @@ -64,14 +61,12 @@ where } } -decl_prg_trait!(([u8; OUT_BLEN], [u8; OUT_BLEN], bool)); - /// [`Dcf`] impl. /// /// `$\alpha$` itself is not included (or say exclusive endpoint), which means `$f(\alpha)$ = 0`. pub struct DcfImpl where - P: Prg, + P: Prg, { prg: P, filter_bitn: usize, @@ -79,7 +74,7 @@ where impl DcfImpl where - P: Prg, + P: Prg, { pub fn new(prg: P) -> Self { Self { @@ -100,7 +95,7 @@ const IDX_R: usize = 1; impl Dcf for DcfImpl where - P: Prg, + P: Prg, G: Group, { fn gen( @@ -119,8 +114,8 @@ where for i in 0..n { // MSB is required since we index from high to low in arrays. let alpha_i = f.alpha.view_bits::()[i]; - let [(s0l, v0l, t0l), (s0r, v0r, t0r)] = self.prg.gen(&ss_prev[0]); - let [(s1l, v1l, t1l), (s1r, v1r, t1r)] = self.prg.gen(&ss_prev[1]); + let [([s0l, v0l], t0l), ([s0r, v0r], t0r)] = self.prg.gen(&ss_prev[0]); + let [([s1l, v1l], t1l), ([s1r, v1r], t1r)] = self.prg.gen(&ss_prev[1]); // MSB is required since we index from high to low in arrays. let (keep, lose) = if alpha_i { (IDX_R, IDX_L) @@ -201,7 +196,7 @@ where impl DcfImpl where - P: Prg, + P: Prg, { /// Eval with single-threading. /// See [`Dcf::eval`]. @@ -255,7 +250,7 @@ where let cw = &k.cws[layer_i]; // `*_hat` before in-place XOR. - let [(mut sl, vl_hat, mut tl), (mut sr, vr_hat, mut tr)] = self.prg.gen(&s); + let [([mut sl, vl_hat], mut tl), ([mut sr, vr_hat], mut tr)] = self.prg.gen(&s); xor_inplace(&mut sl, &[if t { &cw.s } else { &[0; OUT_BLEN] }]); xor_inplace(&mut sr, &[if t { &cw.s } else { &[0; OUT_BLEN] }]); tl ^= t & cw.tl; @@ -291,7 +286,7 @@ where for i in 0..n { let cw = &k.cws[i]; // `*_hat` before in-place XOR. - let [(mut sl, vl_hat, mut tl), (mut sr, vr_hat, mut tr)] = self.prg.gen(&s_prev); + let [([mut sl, vl_hat], mut tl), ([mut sr, vr_hat], mut tr)] = self.prg.gen(&s_prev); xor_inplace(&mut sl, &[if t_prev { &cw.s } else { &[0; OUT_BLEN] }]); xor_inplace(&mut sr, &[if t_prev { &cw.s } else { &[0; OUT_BLEN] }]); tl ^= t_prev & cw.tl; @@ -326,9 +321,9 @@ pub enum BoundState { mod tests { use rand::prelude::*; - use super::prg::Aes256HirosePrg; use super::*; use crate::group::byte::ByteGroup; + use crate::prg::Aes256HirosePrg; const KEYS: &[&[u8; 32]] = &[ b"j9\x1b_\xb3X\xf33\xacW\x15\x1b\x0812K\xb3I\xb9\x90r\x1cN\xb5\xee9W\xd3\xbb@\xc6d", @@ -345,7 +340,7 @@ mod tests { #[test] fn test_dcf_gen_then_eval() { - let prg = Aes256HirosePrg::<16, 2>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 2, 2>::new(std::array::from_fn(|i| KEYS[i])); let dcf = DcfImpl::<16, 16, _>::new(prg); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = CmpFn { @@ -377,7 +372,7 @@ mod tests { #[test] fn test_dcf_gen_gt_beta_then_eval() { - let prg = Aes256HirosePrg::<16, 2>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 2, 2>::new(std::array::from_fn(|i| KEYS[i])); let dcf = DcfImpl::<16, 16, _>::new(prg); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = CmpFn { @@ -409,7 +404,7 @@ mod tests { #[test] fn test_dcf_gen_then_eval_with_filter() { - let prg = Aes256HirosePrg::<16, 2>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 2, 2>::new(std::array::from_fn(|i| KEYS[i])); let dcf = DcfImpl::<16, 16, _>::new_with_filter(prg, 127); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = CmpFn { @@ -441,7 +436,7 @@ mod tests { #[test] fn test_dcf_gen_then_eval_not_zeros() { - let prg = Aes256HirosePrg::<16, 2>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 2, 2>::new(std::array::from_fn(|i| KEYS[i])); let dcf = DcfImpl::<16, 16, _>::new(prg); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = CmpFn { @@ -465,7 +460,7 @@ mod tests { #[test] fn test_dcf_full_eval() { let x: [u8; 2] = ALPHAS[2][..2].try_into().unwrap(); - let prg = Aes256HirosePrg::<16, 2>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 2, 2>::new(std::array::from_fn(|i| KEYS[i])); let dcf = DcfImpl::<2, 16, _>::new(prg); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = CmpFn { @@ -491,7 +486,7 @@ mod tests { #[test] fn test_dcf_full_eval_with_filter() { let x: [u8; 2] = ALPHAS[2][..2].try_into().unwrap(); - let prg = Aes256HirosePrg::<16, 2>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 2, 2>::new(std::array::from_fn(|i| KEYS[i])); let dcf = DcfImpl::<2, 16, _>::new_with_filter(prg, 15); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = CmpFn { diff --git a/src/dcf/prg.rs b/src/dcf/prg.rs deleted file mode 100644 index eb7fb25..0000000 --- a/src/dcf/prg.rs +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2023 Yulong Ming (myl7) - -//! [`super::Prg`] impl. - -use aes::cipher::generic_array::GenericArray; -use aes::cipher::{BlockEncrypt, KeyInit}; -use aes::{Aes128, Aes256}; -use bitvec::prelude::*; - -use super::Prg; -use crate::utils::{xor, xor_inplace}; - -/// Hirose double-block-length one-way compression function with AES256 and precreated keys. -/// -/// To avoid `#![feature(generic_const_exprs)]`, you MUST ensure `OUT_BLEN % 16 = 0` and `CIPHER_N = 2 * (OUT_BLEN / 16)`. -/// -/// It actually works for OUT_BLEN * 8 - 1 bits other than OUT_BLEN bytes. -/// The last bit of the output `[u8; OUT_BLEN]` is always set to 0. -pub struct Aes256HirosePrg { - ciphers: [Aes256; CIPHER_N], -} - -impl Aes256HirosePrg { - pub fn new(keys: [&[u8; 32]; CIPHER_N]) -> Self { - let ciphers = std::array::from_fn(|i| { - let key_block = GenericArray::from_slice(keys[i]); - Aes256::new(key_block) - }); - Self { ciphers } - } - - /// The arbitrary non-zero constant `c` for the arbitrary fixed-point-free permutation, - /// which is typically just XOR `c`. - const fn c() -> [u8; OUT_BLEN] { - [0xff; OUT_BLEN] - } -} - -impl Prg - for Aes256HirosePrg -{ - fn gen(&self, seed: &[u8; OUT_BLEN]) -> [([u8; OUT_BLEN], [u8; OUT_BLEN], bool); 2] { - // `$p(G_{i - 1})$`. - let seed_p = xor(&[seed, &Self::c()]); - let mut result_buf0 = [[0; OUT_BLEN]; 2]; - let mut result_buf1 = [[0; OUT_BLEN]; 2]; - let mut out_blocks = [GenericArray::default(); 2]; - (0..2usize).for_each(|i| { - (0..OUT_BLEN / 16).for_each(|j| { - let in_block0 = GenericArray::from_slice(&seed[j * 16..(j + 1) * 16]); - let in_block1 = GenericArray::from_slice(&seed_p[j * 16..(j + 1) * 16]); - self.ciphers[i * (OUT_BLEN / 16) + j] - .encrypt_blocks_b2b(&[*in_block0, *in_block1], &mut out_blocks) - .unwrap(); - result_buf0[i][j * 16..(j + 1) * 16].copy_from_slice(out_blocks[0].as_ref()); - result_buf1[i][j * 16..(j + 1) * 16].copy_from_slice(out_blocks[1].as_ref()); - }); - }); - result_buf0 - .iter_mut() - .for_each(|buf| xor_inplace(buf, &[seed])); - result_buf1 - .iter_mut() - .for_each(|buf| xor_inplace(buf, &[&seed_p])); - let bit0 = result_buf0[0].view_bits::()[0]; - let bit1 = result_buf1[0].view_bits::()[0]; - result_buf0 - .iter_mut() - .chain(result_buf1.iter_mut()) - .for_each(|buf| buf[OUT_BLEN - 1].view_bits_mut::().set(0, false)); - [ - (result_buf0[0], result_buf1[0], bit0), - (result_buf0[1], result_buf1[1], bit1), - ] - } -} - -/// Matyas-Meyer-Oseas single-block-length one-way compression function with AES128 and precreated keys. -/// -/// To avoid `#![feature(generic_const_exprs)]`, you MUST ensure `OUT_BLEN % 16 = 0` and `CIPHER_N = 4 * (OUT_BLEN / 16)`. -/// -/// It actually works for OUT_BLEN * 8 - 1 bits other than OUT_BLEN bytes. -/// The last bit of the output `[u8; OUT_BLEN]` is always set to 0. -pub struct Aes128MatyasMeyerOseasPrg { - ciphers: [Aes128; CIPHER_N], -} - -impl Aes128MatyasMeyerOseasPrg { - pub fn new(keys: [&[u8; 16]; CIPHER_N]) -> Self { - let ciphers = std::array::from_fn(|i| { - let key_block = GenericArray::from_slice(keys[i]); - Aes128::new(key_block) - }); - Self { ciphers } - } -} - -impl Prg - for Aes128MatyasMeyerOseasPrg -{ - fn gen(&self, seed: &[u8; OUT_BLEN]) -> [([u8; OUT_BLEN], [u8; OUT_BLEN], bool); 2] { - let mut result_buf0 = [[0; OUT_BLEN]; 2]; - let mut result_buf1 = [[0; OUT_BLEN]; 2]; - let mut out_block = GenericArray::default(); - (0..OUT_BLEN / 16).for_each(|j| { - let in_block = GenericArray::from_slice(&seed[j * 16..(j + 1) * 16]); - self.ciphers[j].encrypt_block_b2b(in_block, &mut out_block); - result_buf0[0][j * 16..(j + 1) * 16].copy_from_slice(out_block.as_ref()); - self.ciphers[j + OUT_BLEN / 16].encrypt_block_b2b(in_block, &mut out_block); - result_buf0[1][j * 16..(j + 1) * 16].copy_from_slice(out_block.as_ref()); - self.ciphers[j + OUT_BLEN / 16 * 2].encrypt_block_b2b(in_block, &mut out_block); - result_buf1[0][j * 16..(j + 1) * 16].copy_from_slice(out_block.as_ref()); - self.ciphers[j + OUT_BLEN / 16 * 3].encrypt_block_b2b(in_block, &mut out_block); - result_buf1[1][j * 16..(j + 1) * 16].copy_from_slice(out_block.as_ref()); - }); - xor_inplace(&mut result_buf0[0], &[seed]); - xor_inplace(&mut result_buf0[1], &[seed]); - xor_inplace(&mut result_buf1[0], &[seed]); - xor_inplace(&mut result_buf1[1], &[seed]); - let bit0 = result_buf0[0].view_bits::()[0]; - let bit1 = result_buf0[1].view_bits::()[0]; - result_buf0 - .iter_mut() - .for_each(|buf| buf[OUT_BLEN - 1].view_bits_mut::().set(0, false)); - [ - (result_buf0[0], result_buf1[0], bit0), - (result_buf0[1], result_buf1[1], bit1), - ] - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const KEYS: [&[u8; 32]; 2] = [ - b"j9\x1b_\xb3X\xf33\xacW\x15\x1b\x0812K\xb3I\xb9\x90r\x1cN\xb5\xee9W\xd3\xbb@\xc6d", - b"\x9b\x15\xc8\x0f\xb7\xbc!q\x9e\x89\xb8\xf7\x0e\xa0S\x9dN\xfa\x0c;\x16\xe4\x98\x82b\xfcdy\xb5\x8c{\xc2", - ]; - const SEED: &[u8; 16] = b"*L\x8f%y\x12Z\x94*E\x8f$+NH\x19"; - - #[test] - fn test_prg_gen_not_zeros() { - let prg = Aes256HirosePrg::<16, 2>::new(KEYS); - let out = prg.gen(SEED); - (0..2).for_each(|i| { - assert_ne!(out[i].0, [0; 16]); - assert_ne!(out[i].1, [0; 16]); - assert_ne!(xor(&[&out[i].0, SEED]), [0; 16]); - assert_ne!(xor(&[&out[i].1, SEED]), [0; 16]); - assert_ne!(xor(&[&out[i].0, SEED]), [0xff; 16]); - assert_ne!(xor(&[&out[i].1, SEED]), [0xff; 16]); - }); - } -} diff --git a/src/dpf/mod.rs b/src/dpf/mod.rs index 5fef173..44296ea 100644 --- a/src/dpf/mod.rs +++ b/src/dpf/mod.rs @@ -10,10 +10,7 @@ use rayon::prelude::*; use crate::group::Group; use crate::utils::{xor, xor_inplace}; pub use crate::PointFn; -use crate::{decl_prg_trait, Cw, Share}; - -#[cfg(feature = "prg")] -pub mod prg; +use crate::{Cw, Prg, Share}; /// Distributed point function API. /// @@ -41,12 +38,10 @@ where fn full_eval(&self, b: bool, k: &Share, ys: &mut [&mut G]); } -decl_prg_trait!(([u8; OUT_BLEN], bool)); - /// [`Dpf`] impl. pub struct DpfImpl where - P: Prg, + P: Prg, { prg: P, filter_bitn: usize, @@ -54,7 +49,7 @@ where impl DpfImpl where - P: Prg, + P: Prg, { pub fn new(prg: P) -> Self { Self { @@ -75,7 +70,7 @@ const IDX_R: usize = 1; impl Dpf for DpfImpl where - P: Prg, + P: Prg, G: Group, { fn gen( @@ -93,8 +88,8 @@ where for i in 0..n { // MSB is required since we index from high to low in arrays. let alpha_i = f.alpha.view_bits::()[i]; - let [(s0l, t0l), (s0r, t0r)] = self.prg.gen(&ss_prev[0]); - let [(s1l, t1l), (s1r, t1r)] = self.prg.gen(&ss_prev[1]); + let [([s0l], t0l), ([s0r], t0r)] = self.prg.gen(&ss_prev[0]); + let [([s1l], t1l), ([s1r], t1r)] = self.prg.gen(&ss_prev[1]); let (keep, lose) = if alpha_i { (IDX_R, IDX_L) } else { @@ -154,7 +149,7 @@ where impl DpfImpl where - P: Prg, + P: Prg, { /// Eval with single-threading. /// See [`Dpf::eval`]. @@ -207,7 +202,7 @@ where } let cw = &k.cws[layer_i]; - let [(mut sl, mut tl), (mut sr, mut tr)] = self.prg.gen(&s); + let [([mut sl], mut tl), ([mut sr], mut tr)] = self.prg.gen(&s); xor_inplace(&mut sl, &[if t { &cw.s } else { &[0; OUT_BLEN] }]); xor_inplace(&mut sr, &[if t { &cw.s } else { &[0; OUT_BLEN] }]); tl ^= t & cw.tl; @@ -238,7 +233,7 @@ where let mut t_prev = b; for i in 0..n { let cw = &k.cws[i]; - let [(mut sl, mut tl), (mut sr, mut tr)] = self.prg.gen(&s_prev); + let [([mut sl], mut tl), ([mut sr], mut tr)] = self.prg.gen(&s_prev); xor_inplace(&mut sl, &[if t_prev { &cw.s } else { &[0; OUT_BLEN] }]); xor_inplace(&mut sr, &[if t_prev { &cw.s } else { &[0; OUT_BLEN] }]); tl ^= t_prev & cw.tl; @@ -260,9 +255,9 @@ where mod tests { use rand::prelude::*; - use super::prg::Aes256HirosePrg; use super::*; use crate::group::byte::ByteGroup; + use crate::prg::Aes256HirosePrg; const KEYS: &[&[u8; 32]] = &[b"j9\x1b_\xb3X\xf33\xacW\x15\x1b\x0812K\xb3I\xb9\x90r\x1cN\xb5\xee9W\xd3\xbb@\xc6d"]; @@ -277,7 +272,7 @@ mod tests { #[test] fn test_dpf_gen_then_eval() { - let prg = Aes256HirosePrg::<16, 1>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 1, 1>::new(std::array::from_fn(|i| KEYS[i])); let dpf = DpfImpl::<16, 16, _>::new(prg); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = PointFn { @@ -308,7 +303,7 @@ mod tests { #[test] fn test_dpf_gen_then_eval_with_filter() { - let prg = Aes256HirosePrg::<16, 1>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 1, 1>::new(std::array::from_fn(|i| KEYS[i])); let dpf = DpfImpl::<16, 16, _>::new_with_filter(prg, 127); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = PointFn { @@ -339,7 +334,7 @@ mod tests { #[test] fn test_dpf_gen_then_eval_not_zeros() { - let prg = Aes256HirosePrg::<16, 1>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 1, 1>::new(std::array::from_fn(|i| KEYS[i])); let dpf = DpfImpl::<16, 16, _>::new(prg); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = PointFn { @@ -362,7 +357,7 @@ mod tests { #[test] fn test_dpf_full_eval() { let x: [u8; 2] = ALPHAS[2][..2].try_into().unwrap(); - let prg = Aes256HirosePrg::<16, 1>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 1, 1>::new(std::array::from_fn(|i| KEYS[i])); let dpf = DpfImpl::<2, 16, _>::new(prg); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = PointFn { @@ -387,7 +382,7 @@ mod tests { #[test] fn test_dpf_full_eval_with_filter() { let x: [u8; 2] = ALPHAS[2][..2].try_into().unwrap(); - let prg = Aes256HirosePrg::<16, 1>::new(std::array::from_fn(|i| KEYS[i])); + let prg = Aes256HirosePrg::<16, 1, 1>::new(std::array::from_fn(|i| KEYS[i])); let dpf = DpfImpl::<2, 16, _>::new_with_filter(prg, 15); let s0s: [[u8; 16]; 2] = thread_rng().gen(); let f = PointFn { diff --git a/src/dpf/prg.rs b/src/dpf/prg.rs deleted file mode 100644 index 6452f65..0000000 --- a/src/dpf/prg.rs +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2023 Yulong Ming (myl7) - -//! [`super::Prg`] impl. - -use aes::cipher::generic_array::GenericArray; -use aes::cipher::{BlockEncrypt, KeyInit}; -use aes::{Aes128, Aes256}; -use bitvec::prelude::*; - -use super::Prg; -use crate::utils::{xor, xor_inplace}; - -/// Hirose double-block-length one-way compression function with AES256 and precreated keys. -/// -/// To avoid `#![feature(generic_const_exprs)]`, you MUST ensure `OUT_BLEN % 16 = 0` and `CIPHER_N = OUT_BLEN / 16`. -/// -/// It actually works for OUT_BLEN * 8 - 1 bits other than OUT_BLEN bytes. -/// The last bit of the output `[u8; OUT_BLEN]` is always set to 0. -pub struct Aes256HirosePrg { - ciphers: [Aes256; CIPHER_N], -} - -impl Aes256HirosePrg { - pub fn new(keys: [&[u8; 32]; CIPHER_N]) -> Self { - let ciphers = std::array::from_fn(|i| { - let key_block = GenericArray::from_slice(keys[i]); - Aes256::new(key_block) - }); - Self { ciphers } - } - - /// The arbitrary non-zero constant `c` for the arbitrary fixed-point-free permutation, - /// which is typically just XOR `c`. - const fn c() -> [u8; OUT_BLEN] { - [0xff; OUT_BLEN] - } -} - -impl Prg - for Aes256HirosePrg -{ - fn gen(&self, seed: &[u8; OUT_BLEN]) -> [([u8; OUT_BLEN], bool); 2] { - // `$p(G_{i - 1})$`. - let seed_p = xor(&[seed, &Self::c()]); - let mut result_buf0 = [[0; OUT_BLEN]; 2]; - let mut out_blocks = [GenericArray::default(); 2]; - (0..OUT_BLEN / 16).for_each(|j| { - let in_block0 = GenericArray::from_slice(&seed[j * 16..(j + 1) * 16]); - let in_block1 = GenericArray::from_slice(&seed_p[j * 16..(j + 1) * 16]); - self.ciphers[j] - .encrypt_blocks_b2b(&[*in_block0, *in_block1], &mut out_blocks) - .unwrap(); - result_buf0[0][j * 16..(j + 1) * 16].copy_from_slice(out_blocks[0].as_ref()); - result_buf0[1][j * 16..(j + 1) * 16].copy_from_slice(out_blocks[1].as_ref()); - }); - xor_inplace(&mut result_buf0[0], &[seed]); - xor_inplace(&mut result_buf0[1], &[&seed_p]); - let bit0 = result_buf0[0].view_bits::()[0]; - let bit1 = result_buf0[1].view_bits::()[0]; - result_buf0 - .iter_mut() - .for_each(|buf| buf[OUT_BLEN - 1].view_bits_mut::().set(0, false)); - [(result_buf0[0], bit0), (result_buf0[1], bit1)] - } -} - -/// Matyas-Meyer-Oseas single-block-length one-way compression function with AES128 and precreated keys. -/// -/// To avoid `#![feature(generic_const_exprs)]`, you MUST ensure `OUT_BLEN % 16 = 0` and `CIPHER_N = 2 * (OUT_BLEN / 16)`. -/// -/// It actually works for OUT_BLEN * 8 - 1 bits other than OUT_BLEN bytes. -/// The last bit of the output `[u8; OUT_BLEN]` is always set to 0. -pub struct Aes128MatyasMeyerOseasPrg { - ciphers: [Aes128; CIPHER_N], -} - -impl Aes128MatyasMeyerOseasPrg { - pub fn new(keys: [&[u8; 16]; CIPHER_N]) -> Self { - let ciphers = std::array::from_fn(|i| { - let key_block = GenericArray::from_slice(keys[i]); - Aes128::new(key_block) - }); - Self { ciphers } - } -} - -impl Prg - for Aes128MatyasMeyerOseasPrg -{ - fn gen(&self, seed: &[u8; OUT_BLEN]) -> [([u8; OUT_BLEN], bool); 2] { - let mut result_buf = [[0; OUT_BLEN]; 2]; - let mut out_block = GenericArray::default(); - (0..OUT_BLEN / 16).for_each(|j| { - let in_block = GenericArray::from_slice(&seed[j * 16..(j + 1) * 16]); - self.ciphers[j].encrypt_block_b2b(in_block, &mut out_block); - result_buf[0][j * 16..(j + 1) * 16].copy_from_slice(out_block.as_ref()); - self.ciphers[j + OUT_BLEN / 16].encrypt_block_b2b(in_block, &mut out_block); - result_buf[1][j * 16..(j + 1) * 16].copy_from_slice(out_block.as_ref()); - }); - xor_inplace(&mut result_buf[0], &[seed]); - xor_inplace(&mut result_buf[1], &[seed]); - let bit0 = result_buf[0].view_bits::()[0]; - let bit1 = result_buf[1].view_bits::()[0]; - result_buf - .iter_mut() - .for_each(|buf| buf[OUT_BLEN - 1].view_bits_mut::().set(0, false)); - [(result_buf[0], bit0), (result_buf[1], bit1)] - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const KEYS: [&[u8; 32]; 1] = - [b"j9\x1b_\xb3X\xf33\xacW\x15\x1b\x0812K\xb3I\xb9\x90r\x1cN\xb5\xee9W\xd3\xbb@\xc6d"]; - const SEED: &[u8; 16] = b"*L\x8f%y\x12Z\x94*E\x8f$+NH\x19"; - - #[test] - fn test_prg_gen_not_zeros() { - let prg = Aes256HirosePrg::<16, 1>::new(KEYS); - let out = prg.gen(SEED); - (0..2).for_each(|i| { - assert_ne!(out[i].0, [0; 16]); - assert_ne!(xor(&[&out[i].0, SEED]), [0; 16]); - }); - } -} diff --git a/src/lib.rs b/src/lib.rs index 004948c..f71f444 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,8 @@ use group::Group; pub mod dcf; pub mod dpf; pub mod group; +#[cfg(feature = "prg")] +pub mod prg; pub mod utils; /// Point function. @@ -30,18 +32,13 @@ where pub beta: G, } -macro_rules! decl_prg_trait { - ($ret_elem:ty) => { - /// Pseudorandom generator. - /// - /// Requires `Sync` for multi-threading. - /// We still require it for single-threading since it should be still easy to be included. - pub trait Prg: Sync { - fn gen(&self, seed: &[u8; OUT_BLEN]) -> [$ret_elem; 2]; - } - }; +/// Pseudorandom generator (PRG). +/// +/// Requires `Sync` for multi-threading. +/// We still require it for single-threading since it should be still easy to be included. +pub trait Prg: Sync { + fn gen(&self, seed: &[u8; BLEN]) -> [([[u8; BLEN]; BLEN_N], bool); 2]; } -pub(crate) use decl_prg_trait; /// `Cw`. Correclation word. #[derive(Clone)] diff --git a/src/prg.rs b/src/prg.rs new file mode 100644 index 0000000..635d156 --- /dev/null +++ b/src/prg.rs @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 Yulong Ming (myl7) + +//! Fast PRG implementations based on one-way compression functions, AES, and precreated keys. + +use aes::cipher::generic_array::GenericArray; +use aes::cipher::{BlockEncrypt, KeyInit}; +use aes::{Aes128, Aes256}; +use bitvec::prelude::*; + +use crate::utils::{xor, xor_inplace}; +use crate::Prg; + +// TODO: BLEN_N +/// Hirose double-block-length one-way compression function with AES256 and precreated keys. +/// +/// To avoid `#![feature(generic_const_exprs)]`, you MUST ensure `OUT_BLEN % 16 = 0` and `CIPHER_N = (OUT_BLEN / 16) * OUT_BLEN_N`. +/// We append a prefix `OUT_` to the generic constants to emphasize the PRG output is used for the output domain. +/// +/// It actually works for OUT_BLEN * 8 - 1 bits other than OUT_BLEN bytes. +/// The last bit of EVERY `[u8; OUT_BLEN]` of the output is always set to 0. +pub struct Aes256HirosePrg { + ciphers: [Aes256; CIPHER_N], +} + +impl + Aes256HirosePrg +{ + pub fn new(keys: [&[u8; 32]; CIPHER_N]) -> Self { + let ciphers = std::array::from_fn(|i| { + let key_block = GenericArray::from_slice(keys[i]); + Aes256::new(key_block) + }); + Self { ciphers } + } + + /// The arbitrary non-zero constant `c` for the arbitrary fixed-point-free permutation, + /// which is typically just XOR `c`. + const fn c() -> [u8; OUT_BLEN] { + [0xff; OUT_BLEN] + } +} + +impl + Prg for Aes256HirosePrg +{ + fn gen(&self, seed: &[u8; OUT_BLEN]) -> [([[u8; OUT_BLEN]; OUT_BLEN_N], bool); 2] { + // `$p(G_{i - 1})$`. + let seed_p = xor(&[seed, &Self::c()]); + + let mut res_bufs = [[[0; OUT_BLEN]; OUT_BLEN_N]; 2]; + let mut out_blocks = [GenericArray::default(); 2]; + (0..OUT_BLEN_N).for_each(|blen_i| { + (0..OUT_BLEN / 16).for_each(|block_i| { + // Same as j = 0..CIHPER_N. + let cipher_i = blen_i * (OUT_BLEN / 16) + block_i; + let in_block0 = GenericArray::from_slice(&seed[block_i * 16..(block_i + 1) * 16]); + let in_block1 = GenericArray::from_slice(&seed_p[block_i * 16..(block_i + 1) * 16]); + self.ciphers[cipher_i] + .encrypt_blocks_b2b(&[*in_block0, *in_block1], &mut out_blocks) + .unwrap(); + res_bufs[0][blen_i][block_i * 16..(block_i + 1) * 16] + .copy_from_slice(out_blocks[0].as_ref()); + res_bufs[1][blen_i][block_i * 16..(block_i + 1) * 16] + .copy_from_slice(out_blocks[1].as_ref()); + }); + }); + (0..OUT_BLEN_N).for_each(|k| { + xor_inplace(&mut res_bufs[0][k], &[seed]); + xor_inplace(&mut res_bufs[1][k], &[&seed_p]); + }); + let bit0 = res_bufs[0][0].view_bits::()[0]; + let bit1 = res_bufs[1][0].view_bits::()[0]; + res_bufs.iter_mut().for_each(|bufs| { + bufs.iter_mut() + .for_each(|buf| buf[OUT_BLEN - 1].view_bits_mut::().set(0, false)) + }); + [(res_bufs[0], bit0), (res_bufs[1], bit1)] + } +} + +/// Matyas-Meyer-Oseas single-block-length one-way compression function with AES128 and precreated keys. +/// +/// To avoid `#![feature(generic_const_exprs)]`, you MUST ensure `OUT_BLEN % 16 = 0` and `CIPHER_N = (OUT_BLEN / 16) * OUT_BLEN_N * 2`. +/// We append a prefix `OUT_` to the generic constants to emphasize the PRG output is used for the output domain. +/// +/// It actually works for OUT_BLEN * 8 - 1 bits other than OUT_BLEN bytes. +/// The last bit of EVERY `[u8; OUT_BLEN]` of the output is always set to 0. +pub struct Aes128MatyasMeyerOseasPrg< + const OUT_BLEN: usize, + const OUT_BLEN_N: usize, + const CIPHER_N: usize, +> { + ciphers: [Aes128; CIPHER_N], +} + +impl + Aes128MatyasMeyerOseasPrg +{ + pub fn new(keys: [&[u8; 16]; CIPHER_N]) -> Self { + let ciphers = std::array::from_fn(|i| { + let key_block = GenericArray::from_slice(keys[i]); + Aes128::new(key_block) + }); + Self { ciphers } + } +} + +impl + Prg for Aes128MatyasMeyerOseasPrg +{ + fn gen(&self, seed: &[u8; OUT_BLEN]) -> [([[u8; OUT_BLEN]; OUT_BLEN_N], bool); 2] { + let ciphers0 = &self.ciphers[0..CIPHER_N / 2]; + let ciphers1 = &self.ciphers[CIPHER_N / 2..]; + + let mut res_bufs = [[[0; OUT_BLEN]; OUT_BLEN_N]; 2]; + let mut out_blocks = [GenericArray::default(); 2]; + (0..OUT_BLEN_N).for_each(|blen_i| { + (0..OUT_BLEN / 16).for_each(|block_i| { + // Same as j = 0..CIHPER_N / 2. + let cipher_i = blen_i * (OUT_BLEN / 16) + block_i; + let in_block0 = GenericArray::from_slice(&seed[block_i * 16..(block_i + 1) * 16]); + let in_block1 = GenericArray::from_slice(&seed[block_i * 16..(block_i + 1) * 16]); + // AES instruction-level parallelism does not give noticeable performance improvement according to our trials. + ciphers0[cipher_i].encrypt_block_b2b(in_block0, &mut out_blocks[0]); + ciphers1[cipher_i].encrypt_block_b2b(in_block1, &mut out_blocks[1]); + res_bufs[0][blen_i][block_i * 16..(block_i + 1) * 16] + .copy_from_slice(out_blocks[0].as_ref()); + res_bufs[1][blen_i][block_i * 16..(block_i + 1) * 16] + .copy_from_slice(out_blocks[1].as_ref()); + }); + }); + (0..OUT_BLEN_N).for_each(|k| { + xor_inplace(&mut res_bufs[0][k], &[seed]); + xor_inplace(&mut res_bufs[1][k], &[seed]); + }); + let bit0 = res_bufs[0][0].view_bits::()[0]; + let bit1 = res_bufs[1][0].view_bits::()[0]; + res_bufs.iter_mut().for_each(|bufs| { + bufs.iter_mut() + .for_each(|buf| buf[OUT_BLEN - 1].view_bits_mut::().set(0, false)) + }); + [(res_bufs[0], bit0), (res_bufs[1], bit1)] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const KEYS: [&[u8; 32]; 2] = [ + b"j9\x1b_\xb3X\xf33\xacW\x15\x1b\x0812K\xb3I\xb9\x90r\x1cN\xb5\xee9W\xd3\xbb@\xc6d", + b"\x9b\x15\xc8\x0f\xb7\xbc!q\x9e\x89\xb8\xf7\x0e\xa0S\x9dN\xfa\x0c;\x16\xe4\x98\x82b\xfcdy\xb5\x8c{\xc2", + ]; + const SEED: &[u8; 16] = b"*L\x8f%y\x12Z\x94*E\x8f$+NH\x19"; + + #[test] + fn test_aes128_prg_gen_not_zeros() { + let prg = Aes128MatyasMeyerOseasPrg::<16, 2, 4>::new( + [ + &KEYS[0][0..16], + &KEYS[0][16..32], + &KEYS[1][0..16], + &KEYS[1][16..32], + ] + .map(|a| a.try_into().unwrap()), + ); + let out = prg.gen(SEED); + (0..2).for_each(|i| { + assert_ne!(out[i].0[0], [0; 16]); + assert_ne!(out[i].0[1], [0; 16]); + assert_ne!(xor(&[&out[i].0[0], SEED]), [0; 16]); + assert_ne!(xor(&[&out[i].0[1], SEED]), [0; 16]); + assert_ne!(xor(&[&out[i].0[0], SEED]), [0xff; 16]); + assert_ne!(xor(&[&out[i].0[1], SEED]), [0xff; 16]); + }); + } + + #[test] + fn test_aes256_prg_gen_not_zeros() { + let prg = Aes256HirosePrg::<16, 2, 2>::new(KEYS); + let out = prg.gen(SEED); + (0..2).for_each(|i| { + assert_ne!(out[i].0[0], [0; 16]); + assert_ne!(out[i].0[1], [0; 16]); + assert_ne!(xor(&[&out[i].0[0], SEED]), [0; 16]); + assert_ne!(xor(&[&out[i].0[1], SEED]), [0; 16]); + assert_ne!(xor(&[&out[i].0[0], SEED]), [0xff; 16]); + assert_ne!(xor(&[&out[i].0[1], SEED]), [0xff; 16]); + }); + } +}