#[cfg(feature = "pqc-parallel")]
use rand::{CryptoRng, RngCore};
#[cfg(feature = "pqc-parallel")]
use rayon::prelude::*;
use crate::kem::ntru_prime::{
self, constants::CT_SIZE, Ciphertext, PrivateKey, PublicKey, SharedSecret,
};
#[cfg(feature = "pqc-parallel")]
pub fn keypairs_parallel<R: RngCore + CryptoRng + Clone + Sized + Send>(
n: usize,
rng: &mut R,
) -> Vec<(PrivateKey, PublicKey)> {
let mut rngs = Vec::with_capacity(n);
for _ in 0..n {
rngs.push(rng.clone());
}
rngs.into_par_iter()
.map(|mut rng| ntru_prime::generate_keypair(&mut rng))
.collect()
}
#[cfg(feature = "pqc-parallel")]
pub fn encapsulate_batch<R: RngCore + CryptoRng + Clone + Sized + Send>(
public_keys: &[PublicKey],
rng: &mut R,
) -> Vec<(Ciphertext, SharedSecret)> {
let mut rngs: Vec<_> = (0..public_keys.len()).map(|_| rng.clone()).collect();
public_keys
.par_iter()
.zip(rngs.into_par_iter())
.map(|(pk, mut rng)| ntru_prime::encapsulate(pk, &mut rng))
.collect()
}
#[cfg(feature = "pqc-parallel")]
pub fn decapsulate_batch(
ciphertexts: &[Ciphertext],
private_keys: &[PrivateKey],
) -> Vec<Result<SharedSecret, String>> {
ciphertexts
.par_iter()
.zip(private_keys.par_iter())
.map(|(ct, sk)| ntru_prime::decapsulate(ct, sk))
.collect()
}
#[cfg(feature = "pqc-parallel")]
pub fn poly_mul_batch_parallel(
results: &mut [Vec<i16>],
polys_a: &[Vec<i16>],
polys_b: &[Vec<i16>],
) {
assert_eq!(polys_a.len(), polys_b.len());
assert_eq!(results.len(), polys_a.len());
results
.par_iter_mut()
.zip(polys_a.par_iter())
.zip(polys_b.par_iter())
.for_each(|((result, poly_a), poly_b)| {
#[cfg(all(feature = "pqc-simd", feature = "pqc-parallel"))]
{
result.clone_from(&crate::ntru_prime::simd_poly::poly_mul(poly_a, poly_b));
}
#[cfg(not(all(feature = "pqc-simd", feature = "pqc-parallel")))]
{
let n = poly_a.len();
let mut computed = vec![0i16; n];
for i in 0..n {
let mut acc = 0i32;
for j in 0..=i {
acc += (poly_a[j] as i32) * (poly_b[i - j] as i32);
}
computed[i] = mod_freeze(acc);
}
result.clone_from(&computed);
}
});
}
#[cfg(all(feature = "pqc-parallel", not(feature = "pqc-simd")))]
#[inline(always)]
fn mod_freeze(a: i32) -> i16 {
const MAGIC: i32 = 934_409;
const Q_I32: i32 = 4_591;
let t = ((a as i64) * (MAGIC as i64)) >> 32;
let b = a - (t as i32) * Q_I32;
let mut c = b - Q_I32;
let mask = (b >> 31) & Q_I32;
c += mask;
c as i16
}
#[inline(always)]
fn freeze_i32(a: i32) -> i32 {
const MAGIC: i32 = 934_409;
const Q_I32: i32 = 4_591;
let t = ((a as i64) * (MAGIC as i64)) >> 32;
let b = a - (t as i32) * Q_I32;
let mut c = b - Q_I32;
let mask = (b >> 31) & Q_I32;
c += mask;
c
}
#[cfg(feature = "pqc-parallel")]
pub fn reduce_batch_parallel(values: &mut [i32]) {
#[cfg(feature = "pqc-simd")]
values.par_iter_mut().for_each(|v| {
*v = crate::ntru_prime::simd_poly::freeze(*v) as i32;
});
#[cfg(not(feature = "pqc-simd"))]
values.par_iter_mut().for_each(|v| {
*v = freeze_i32(*v);
});
}
#[cfg(feature = "pqc-parallel")]
pub fn freeze_r3_batch_parallel(values: &mut [i32]) {
values.par_iter_mut().for_each(|v| {
*v = crate::ntru_prime::r3::mod3::freeze(*v) as i32;
});
}
#[cfg(all(feature = "pqc-parallel", feature = "pqc-simd"))]
pub fn ntt_forward_batch_parallel(polys: &mut [Vec<i32>]) {
polys.par_iter_mut().for_each(|poly| {
crate::ntru_prime::ntt::ntt_forward_inplace(poly.as_mut_slice());
});
}
#[cfg(all(feature = "pqc-parallel", feature = "pqc-simd"))]
pub fn ntt_inverse_batch_parallel(polys: &mut [Vec<i32>]) {
polys.par_iter_mut().for_each(|poly| {
crate::ntru_prime::ntt::ntt_inverse_inplace(poly.as_mut_slice());
});
}
#[cfg(feature = "pqc-parallel")]
pub fn validate_public_keys_batch(public_keys: &[PublicKey]) -> Vec<bool> {
public_keys
.par_iter()
.map(|pk| {
if pk.data.len() != ntru_prime::PK_SIZE {
return false;
}
let _ = crate::ntru_prime::rq::encoding::decode(&pk.data);
true
})
.collect()
}
#[cfg(feature = "pqc-parallel")]
pub fn compute_weights_batch(polys: &[&[i8]]) -> Vec<i32> {
polys
.par_iter()
.map(|poly| poly.iter().map(|&x| x.abs() as i32).sum())
.collect()
}
#[cfg(not(feature = "pqc-parallel"))]
pub fn keypairs_parallel<R: RngCore + CryptoRng>(
n: usize,
rng: &mut R,
) -> Vec<(PrivateKey, PublicKey)> {
let mut results = Vec::with_capacity(n);
for _ in 0..n {
results.push(ntru_prime::generate_keypair(rng));
}
results
}
#[cfg(not(feature = "pqc-parallel"))]
pub fn encapsulate_batch<R: RngCore + CryptoRng>(
public_keys: &[PublicKey],
rng: &mut R,
) -> Vec<(Ciphertext, SharedSecret)> {
public_keys
.iter()
.map(|pk| ntru_prime::encapsulate(pk, rng))
.collect()
}
#[cfg(not(feature = "pqc-parallel"))]
pub fn decapsulate_batch(
ciphertexts: &[Ciphertext],
private_keys: &[PrivateKey],
) -> Vec<Result<SharedSecret, String>> {
ciphertexts
.iter()
.zip(private_keys.iter())
.map(|(ct, sk)| ntru_prime::decapsulate(ct, sk))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
#[test]
fn test_keypairs_parallel() {
let mut rng = rand::rngs::StdRng::from_entropy();
let pairs = keypairs_parallel(4, &mut rng);
assert_eq!(pairs.len(), 4);
for (sk, pk) in pairs {
assert_eq!(pk.data.len(), ntru_prime::PK_SIZE);
assert_eq!(sk.as_bytes().len(), ntru_prime::SK_SIZE);
}
}
#[test]
fn test_encapsulate_batch() {
let mut rng = rand::rngs::StdRng::from_entropy();
let pairs = keypairs_parallel(4, &mut rng);
let public_keys: Vec<_> = pairs.iter().map(|(_, pk)| pk.clone()).collect();
let results = encapsulate_batch(&public_keys, &mut rng);
assert_eq!(results.len(), 4);
for (ct, ss) in results {
assert_eq!(ct.data.len(), CT_SIZE);
assert_eq!(ss.as_bytes().len(), 32);
}
}
#[test]
fn test_decapsulate_batch() {
let mut rng = rand::rngs::StdRng::from_entropy();
let pairs = keypairs_parallel(4, &mut rng);
let private_keys: Vec<_> = pairs.iter().map(|(sk, _)| sk.clone()).collect();
let public_keys: Vec<_> = pairs.iter().map(|(_, pk)| pk.clone()).collect();
let encapsulated = encapsulate_batch(&public_keys, &mut rng);
let ciphertexts: Vec<_> = encapsulated.iter().map(|(ct, _)| ct.clone()).collect();
let results = decapsulate_batch(&ciphertexts, &private_keys);
assert_eq!(results.len(), 4);
for result in results {
assert!(result.is_ok());
}
}
#[test]
fn test_compute_weights_batch() {
let polys: Vec<&[i8]> = vec![
&[1, 0, -1, 1, 1, 0, -1, -1],
&[0, 0, 0, 0, 0, 0, 0, 0],
&[1, 1, 1, 1, 1, 1, 1, 1],
];
let weights = compute_weights_batch(&polys);
assert_eq!(weights[0], 6);
assert_eq!(weights[1], 0);
assert_eq!(weights[2], 8);
}
#[cfg(feature = "pqc-parallel")]
#[test]
fn test_validate_public_keys_batch() {
let mut rng = rand::rngs::StdRng::from_entropy();
let pairs = keypairs_parallel(4, &mut rng);
let public_keys: Vec<_> = pairs.iter().map(|(_, pk)| pk.clone()).collect();
let results = validate_public_keys_batch(&public_keys);
assert_eq!(results.len(), 4);
for result in results {
assert!(result);
}
}
}