use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use spiral_rs::discrete_gaussian::*;
use crate::seed::generate_secure_random_seed;
use super::convolution::negacyclic_matrix_u32;
#[derive(Clone, Debug)]
pub struct LWEParams {
pub n: usize,
pub modulus: u64,
pub pt_modulus: u64,
pub q2_bits: usize,
pub noise_width: f64,
}
impl Default for LWEParams {
fn default() -> Self {
Self {
n: 1024,
modulus: 1u64 << 32,
pt_modulus: 256,
q2_bits: 28,
noise_width: 27.57291103, }
}
}
impl LWEParams {
pub fn scale_k(&self) -> u64 {
self.modulus / self.pt_modulus
}
}
pub struct LWEClient {
lwe_params: LWEParams,
sk: Vec<u32>,
}
pub const LWE_STREAM: u64 = 0x65776c;
impl LWEClient {
pub fn new(lwe_params: LWEParams) -> Self {
let client_seed = generate_secure_random_seed();
Self::from_seed(lwe_params, client_seed)
}
pub fn from_seed(lwe_params: LWEParams, client_seed: [u8; 32]) -> Self {
let mut rng = ChaCha20Rng::from_seed(client_seed);
rng.set_stream(LWE_STREAM);
let dg = DiscreteGaussian::init(lwe_params.noise_width);
let sk = (0..lwe_params.n)
.map(|_| dg.sample(lwe_params.modulus, &mut rng) as u32)
.collect::<Vec<_>>();
Self { lwe_params, sk }
}
pub fn get_sk(&self) -> &[u32] {
&self.sk
}
pub fn encrypt(&self, rng_pub: &mut ChaCha20Rng, pt: u32) -> Vec<u32> {
let dg = DiscreteGaussian::init(self.lwe_params.noise_width);
let mut rng = ChaCha20Rng::from_entropy();
let e = dg.sample(self.lwe_params.modulus, &mut rng) as u32;
let mut ct = Vec::new();
let mut sum = 0u32;
for i in 0..self.lwe_params.n {
let v = rng_pub.sample::<u32, _>(rand::distributions::Standard);
ct.push(v);
sum = sum.wrapping_add(v.wrapping_mul(self.sk[i]));
}
let neg_sum = sum.wrapping_neg();
let b = neg_sum.wrapping_add(pt.wrapping_add(e));
ct.push(b);
ct
}
pub fn encrypt_many(&self, rng_pub: &mut ChaCha20Rng, v_pt: &[u32]) -> Vec<u32> {
assert_eq!(v_pt.len(), self.lwe_params.n);
let mut rng = ChaCha20Rng::from_entropy();
let dg = DiscreteGaussian::init(self.lwe_params.noise_width);
let mut a = Vec::new();
for _ in 0..self.lwe_params.n {
let v = rng_pub.sample::<u32, _>(rand::distributions::Standard);
a.push(v);
}
let nega_a = negacyclic_matrix_u32(&a);
let mut last_row = vec![0u32; self.lwe_params.n];
let n = self.lwe_params.n;
for col in 0..n {
let mut sum = 0u32;
for row in 0..n {
let idx = row * n + col;
sum = sum.wrapping_add(nega_a[idx].wrapping_mul(self.sk[row]));
}
let e = dg.sample(self.lwe_params.modulus, &mut rng) as u32;
let val = sum.wrapping_neg().wrapping_add(v_pt[col].wrapping_add(e));
last_row[col] = val;
}
let ct = [nega_a, last_row].concat();
ct
}
pub fn decrypt(&self, ct: &[u32]) -> u32 {
let mut sum = 0u32;
for i in 0..self.lwe_params.n {
let v1 = ct[i];
let v2 = self.sk[i];
sum = sum.wrapping_add(v1.wrapping_mul(v2));
}
sum = sum.wrapping_add(ct[self.lwe_params.n]);
sum
}
pub fn lwe_params(&self) -> &LWEParams {
&self.lwe_params
}
}