extern crate alloc;
use alloc::vec::Vec;
use alloc::vec;
use alloc::format;
use rand::Rng;
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::error::PrivacyError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FheCiphertext {
pub data: Vec<u8>,
pub noise_budget: u32,
pub ring_degree: u32,
pub modulus_bits: u32,
pub created_at: u64,
pub blake3_hash: [u8; 32],
}
#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
pub struct FheKeyPair {
pub public_key: Vec<u8>,
pub secret_key: Vec<u8>,
pub relin_key: Vec<u8>,
pub galois_keys: Vec<u8>,
pub ring_degree: u32,
pub modulus_bits: u32,
pub created_at: u64,
pub rotation_count: u64,
}
pub struct FheEngine {
pub ring_degree: u32,
pub modulus_bits: u32,
pub max_noise_budget: u32,
}
impl FheEngine {
pub fn new(ring_degree: u32, modulus_bits: u32) -> Self {
Self {
ring_degree,
modulus_bits,
max_noise_budget: 100,
}
}
pub fn with_max_noise_budget(mut self, budget: u32) -> Self {
self.max_noise_budget = budget;
self
}
pub fn keygen(
&mut self,
rng: &mut (impl RngCore + CryptoRng),
) -> Result<FheKeyPair, PrivacyError> {
let n = self.ring_degree as usize;
let q = modulus_from_bits(self.modulus_bits);
let s = random_ternary_poly(rng, n);
let a = random_poly(rng, n, q);
let e = random_error_poly(rng, n, ERROR_BOUND);
let a_times_s = poly_mult_negacyclic(&a, &s, n, q);
let b: Vec<i64> = a_times_s.iter().zip(e.iter())
.map(|(&as_i, &e_i)| mod_reduce(-as_i + e_i, q))
.collect();
let a_relin = random_poly(rng, n, q);
let e_relin = random_error_poly(rng, n, ERROR_BOUND);
let s_sq = poly_mult_negacyclic(&s, &s, n, q);
let a_relin_times_s = poly_mult_negacyclic(&a_relin, &s, n, q);
let b_relin: Vec<i64> = a_relin_times_s.iter()
.zip(e_relin.iter())
.zip(s_sq.iter())
.map(|((&ar_i, &er_i), &ss_i)| mod_reduce(-ar_i + er_i + ss_i, q))
.collect();
let public_key = serialize_two_polys(&a, &b);
let secret_key = serialize_poly(&s);
let relin_key = serialize_two_polys(&a_relin, &b_relin);
let galois_keys = serialize_poly(&a);
Ok(FheKeyPair {
public_key,
secret_key,
relin_key,
galois_keys,
ring_degree: self.ring_degree,
modulus_bits: self.modulus_bits,
created_at: current_time_secs(),
rotation_count: 0,
})
}
pub fn encrypt(
&self,
rng: &mut (impl RngCore + CryptoRng),
keypair: &FheKeyPair,
plaintext: &[u8],
) -> Result<FheCiphertext, PrivacyError> {
let n = self.ring_degree as usize;
let q = modulus_from_bits(self.modulus_bits);
let delta = plaintext_delta(n);
let (a, b) = deserialize_two_polys(&keypair.public_key, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("pk deserialize: {}", e)))?;
let m = encode_bytes_to_poly(plaintext, n);
let m_scaled: Vec<i64> = m.iter().map(|&x| x.saturating_mul(delta)).collect();
let r = random_ternary_poly(rng, n);
let e0 = random_error_poly(rng, n, ERROR_BOUND);
let e1 = random_error_poly(rng, n, ERROR_BOUND);
let b_r = poly_mult_negacyclic(&b, &r, n, q);
let a_r = poly_mult_negacyclic(&a, &r, n, q);
let c0: Vec<i64> = b_r.iter().zip(e0.iter()).zip(m_scaled.iter())
.map(|((&br_i, &e0_i), &m_i)| mod_reduce(br_i + e0_i + m_i, q))
.collect();
let c1: Vec<i64> = a_r.iter().zip(e1.iter())
.map(|(&ar_i, &e1_i)| mod_reduce(ar_i + e1_i, q))
.collect();
let data = serialize_two_polys(&c0, &c1);
let blake3_hash = blake3_hash_bytes(&data);
Ok(FheCiphertext {
data,
noise_budget: self.max_noise_budget,
ring_degree: self.ring_degree,
modulus_bits: self.modulus_bits,
created_at: current_time_secs(),
blake3_hash,
})
}
pub fn decrypt(
&self,
keypair: &FheKeyPair,
ct: &FheCiphertext,
) -> Result<Vec<u8>, PrivacyError> {
let n = self.ring_degree as usize;
let q = modulus_from_bits(self.modulus_bits);
let delta = plaintext_delta(n);
let (c0, c1) = deserialize_two_polys(&ct.data, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("ct deserialize: {}", e)))?;
let s = deserialize_poly(&keypair.secret_key);
if s.len() != n {
return Err(PrivacyError::FheEncryptionFailed(format!(
"secret key length mismatch: expected {}, got {}", n, s.len()
)));
}
let c1_s = poly_mult_negacyclic(&c1, &s, n, q);
let m: Vec<i64> = c0.iter().zip(c1_s.iter())
.map(|(&c0_i, &c1s_i)| mod_reduce(c0_i + c1s_i, q))
.collect();
let m_rescaled: Vec<i64> = m.iter().map(|&x| round_div(x, delta)).collect();
Ok(decode_poly_to_bytes(&m_rescaled))
}
pub fn add(
&self,
ct1: &FheCiphertext,
ct2: &FheCiphertext,
) -> Result<FheCiphertext, PrivacyError> {
let n = self.ring_degree as usize;
let q = modulus_from_bits(self.modulus_bits);
let (c0_1, c1_1) = deserialize_two_polys(&ct1.data, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("ct1: {}", e)))?;
let (c0_2, c1_2) = deserialize_two_polys(&ct2.data, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("ct2: {}", e)))?;
let c0 = poly_add(&c0_1, &c0_2, q);
let c1 = poly_add(&c1_1, &c1_2, q);
let data = serialize_two_polys(&c0, &c1);
let blake3_hash = blake3_hash_bytes(&data);
let noise_budget = ct1.noise_budget.min(ct2.noise_budget).saturating_sub(1);
Ok(FheCiphertext {
data,
noise_budget,
ring_degree: self.ring_degree,
modulus_bits: self.modulus_bits,
created_at: current_time_secs(),
blake3_hash,
})
}
pub fn mult(
&self,
keypair: &FheKeyPair,
ct1: &FheCiphertext,
ct2: &FheCiphertext,
) -> Result<FheCiphertext, PrivacyError> {
let n = self.ring_degree as usize;
let q = modulus_from_bits(self.modulus_bits);
let (c0_1, c1_1) = deserialize_two_polys(&ct1.data, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("ct1: {}", e)))?;
let (c0_2, c1_2) = deserialize_two_polys(&ct2.data, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("ct2: {}", e)))?;
let d0 = poly_mult_negacyclic(&c0_1, &c0_2, n, q);
let d1_a = poly_mult_negacyclic(&c0_1, &c1_2, n, q);
let d1_b = poly_mult_negacyclic(&c1_1, &c0_2, n, q);
let d1 = poly_add(&d1_a, &d1_b, q);
let d2 = poly_mult_negacyclic(&c1_1, &c1_2, n, q);
let (a_relin, b_relin) = deserialize_two_polys(&keypair.relin_key, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("relin key: {}", e)))?;
let d2_b_relin = poly_mult_negacyclic(&d2, &b_relin, n, q);
let d2_a_relin = poly_mult_negacyclic(&d2, &a_relin, n, q);
let c0 = poly_add(&d0, &d2_b_relin, q);
let c1 = poly_add(&d1, &d2_a_relin, q);
let data = serialize_two_polys(&c0, &c1);
let blake3_hash = blake3_hash_bytes(&data);
let noise_budget = ct1.noise_budget.min(ct2.noise_budget).saturating_sub(5);
Ok(FheCiphertext {
data,
noise_budget,
ring_degree: self.ring_degree,
modulus_bits: self.modulus_bits,
created_at: current_time_secs(),
blake3_hash,
})
}
pub fn bootstrap(
&self,
rng: &mut (impl RngCore + CryptoRng),
keypair: &FheKeyPair,
ct: &FheCiphertext,
) -> Result<FheCiphertext, PrivacyError> {
let plaintext = self.decrypt(keypair, ct)?;
let mut refreshed = self.encrypt(rng, keypair, &plaintext)?;
refreshed.noise_budget = self.max_noise_budget;
Ok(refreshed)
}
pub fn negate(&self, ct: &FheCiphertext) -> Result<FheCiphertext, PrivacyError> {
let n = self.ring_degree as usize;
let q = modulus_from_bits(self.modulus_bits);
let (c0, c1) = deserialize_two_polys(&ct.data, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("ct: {}", e)))?;
let c0_neg = poly_negate(&c0, q);
let c1_neg = poly_negate(&c1, q);
let data = serialize_two_polys(&c0_neg, &c1_neg);
let blake3_hash = blake3_hash_bytes(&data);
Ok(FheCiphertext {
data,
noise_budget: ct.noise_budget.saturating_sub(1),
ring_degree: self.ring_degree,
modulus_bits: self.modulus_bits,
created_at: current_time_secs(),
blake3_hash,
})
}
pub fn batch_add(&self, cts: &[FheCiphertext]) -> Result<FheCiphertext, PrivacyError> {
if cts.is_empty() {
return Err(PrivacyError::FheEncryptionFailed("batch_add: empty input".into()));
}
let mut result = cts[0].clone();
for ct in &cts[1..] {
result = self.add(&result, ct)?;
}
Ok(result)
}
pub fn batch_mult(
&self,
keypair: &FheKeyPair,
cts: &[FheCiphertext],
) -> Result<FheCiphertext, PrivacyError> {
if cts.is_empty() {
return Err(PrivacyError::FheEncryptionFailed("batch_mult: empty input".into()));
}
let mut result = cts[0].clone();
for ct in &cts[1..] {
result = self.mult(keypair, &result, ct)?;
}
Ok(result)
}
pub fn relinearize(
&self,
_keypair: &FheKeyPair,
ct: &FheCiphertext,
) -> Result<FheCiphertext, PrivacyError> {
let mut result = ct.clone();
result.blake3_hash = blake3_hash_bytes(&result.data);
Ok(result)
}
pub fn scale_down(&self, ct: &FheCiphertext) -> Result<FheCiphertext, PrivacyError> {
let n = self.ring_degree as usize;
let new_modulus_bits = ct.modulus_bits.saturating_sub(1).max(1);
let new_q = modulus_from_bits(new_modulus_bits);
let (c0, c1) = deserialize_two_polys(&ct.data, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("ct: {}", e)))?;
let c0_scaled: Vec<i64> = c0.iter().map(|&x| mod_reduce(x >> 1, new_q)).collect();
let c1_scaled: Vec<i64> = c1.iter().map(|&x| mod_reduce(x >> 1, new_q)).collect();
let data = serialize_two_polys(&c0_scaled, &c1_scaled);
let blake3_hash = blake3_hash_bytes(&data);
Ok(FheCiphertext {
data,
noise_budget: ct.noise_budget,
ring_degree: self.ring_degree,
modulus_bits: new_modulus_bits,
created_at: current_time_secs(),
blake3_hash,
})
}
pub fn mod_switch(&self, ct: &FheCiphertext) -> Result<FheCiphertext, PrivacyError> {
let n = self.ring_degree as usize;
let new_modulus_bits = ct.modulus_bits.saturating_sub(4).max(1);
let new_q = modulus_from_bits(new_modulus_bits);
let (c0, c1) = deserialize_two_polys(&ct.data, n)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("ct: {}", e)))?;
let c0_switched: Vec<i64> = c0.iter().map(|&x| mod_reduce(x, new_q)).collect();
let c1_switched: Vec<i64> = c1.iter().map(|&x| mod_reduce(x, new_q)).collect();
let data = serialize_two_polys(&c0_switched, &c1_switched);
let blake3_hash = blake3_hash_bytes(&data);
Ok(FheCiphertext {
data,
noise_budget: ct.noise_budget,
ring_degree: self.ring_degree,
modulus_bits: new_modulus_bits,
created_at: current_time_secs(),
blake3_hash,
})
}
pub fn verify(&self, ct: &FheCiphertext) -> Result<bool, PrivacyError> {
let computed = blake3_hash_bytes(&ct.data);
Ok(computed == ct.blake3_hash)
}
pub fn serialize_ciphertext(&self, ct: &FheCiphertext) -> Result<Vec<u8>, PrivacyError> {
serde_json::to_vec(ct)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("serialize: {}", e)))
}
pub fn deserialize_ciphertext(&self, bytes: &[u8]) -> Result<FheCiphertext, PrivacyError> {
serde_json::from_slice(bytes)
.map_err(|e| PrivacyError::FheEncryptionFailed(format!("deserialize: {}", e)))
}
}
pub fn poly_add(a: &[i64], b: &[i64], modulus: i64) -> Vec<i64> {
let n = a.len().max(b.len());
let mut result = vec![0i64; n];
for i in 0..n {
let ai = if i < a.len() { a[i] } else { 0 };
let bi = if i < b.len() { b[i] } else { 0 };
result[i] = mod_reduce(ai + bi, modulus);
}
result
}
#[allow(clippy::needless_range_loop)] pub fn poly_mult_negacyclic(a: &[i64], b: &[i64], n: usize, modulus: i64) -> Vec<i64> {
let mut result = vec![0i64; n];
for i in 0..n {
if a[i] == 0 { continue; }
for j in 0..n {
if b[j] == 0 { continue; }
let k = i + j;
let product = a[i].wrapping_mul(b[j]);
if k < n {
result[k] = mod_reduce(result[k] + product, modulus);
} else {
result[k - n] = mod_reduce(result[k - n] - product, modulus);
}
}
}
result
}
pub fn poly_negate(a: &[i64], modulus: i64) -> Vec<i64> {
a.iter().map(|&x| mod_reduce(-x, modulus)).collect()
}
pub fn encode_bytes_to_poly(data: &[u8], n: usize) -> Vec<i64> {
let mut poly = vec![0i64; n];
for (i, &byte) in data.iter().enumerate().take(n) {
poly[i] = byte as i64;
}
poly
}
pub fn decode_poly_to_bytes(poly: &[i64]) -> Vec<u8> {
poly.iter().map(|&x| (x & 0xFF) as u8).collect()
}
pub fn serialize_poly(poly: &[i64]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(poly.len() * 8);
for &coeff in poly {
bytes.extend_from_slice(&coeff.to_le_bytes());
}
bytes
}
pub fn deserialize_poly(bytes: &[u8]) -> Vec<i64> {
bytes.chunks_exact(8)
.map(|chunk| {
let arr: [u8; 8] = chunk.try_into().unwrap_or([0u8; 8]);
i64::from_le_bytes(arr)
})
.collect()
}
pub fn serialize_two_polys(p0: &[i64], p1: &[i64]) -> Vec<u8> {
let mut bytes = serialize_poly(p0);
bytes.extend_from_slice(&serialize_poly(p1));
bytes
}
pub fn deserialize_two_polys(bytes: &[u8], n: usize) -> Result<(Vec<i64>, Vec<i64>), alloc::string::String> {
let expected = n * 8 * 2;
if bytes.len() < expected {
return Err(format!(
"deserialize_two_polys: expected {} bytes, got {}",
expected, bytes.len()
));
}
let p0 = deserialize_poly(&bytes[..n * 8]);
let p1 = deserialize_poly(&bytes[n * 8..n * 16]);
Ok((p0, p1))
}
const ERROR_BOUND: i64 = 16;
const NOISE_SAFETY_FACTOR: i64 = 64;
#[inline(always)]
fn mod_reduce(x: i64, q: i64) -> i64 {
if q <= 0 { return x; }
let r = x % q;
if r > q / 2 { r - q } else if r < -(q / 2) { r + q } else { r }
}
fn modulus_from_bits(bits: u32) -> i64 {
if bits >= 63 { i64::MAX } else { 1i64 << bits }
}
fn plaintext_delta(n: usize) -> i64 {
let noise_bound = ERROR_BOUND.saturating_mul(2 * n as i64 + 1);
noise_bound.saturating_mul(NOISE_SAFETY_FACTOR)
}
#[inline(always)]
fn round_div(x: i64, delta: i64) -> i64 {
if delta <= 0 { return x; }
let half = delta / 2;
if x >= 0 {
(x + half) / delta
} else {
-(((-x) + half) / delta)
}
}
fn random_poly(rng: &mut impl RngCore, n: usize, q: i64) -> Vec<i64> {
(0..n).map(|_| mod_reduce(rng.gen_range(0..q), q)).collect()
}
fn random_ternary_poly(rng: &mut impl RngCore, n: usize) -> Vec<i64> {
(0..n).map(|_| rng.gen_range(-1i64..=1)).collect()
}
fn random_error_poly(rng: &mut impl RngCore, n: usize, bound: i64) -> Vec<i64> {
(0..n).map(|_| rng.gen_range(-bound..=bound)).collect()
}
fn blake3_hash_bytes(data: &[u8]) -> [u8; 32] {
*blake3::hash(data).as_bytes()
}
fn current_time_secs() -> u64 {
#[cfg(feature = "std")]
{
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
#[cfg(not(feature = "std"))]
{
0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keygen_roundtrip() {
let mut rng = rand::rngs::OsRng;
let mut engine = FheEngine::new(64, 32);
let keypair = engine.keygen(&mut rng).unwrap();
assert_eq!(keypair.ring_degree, 64);
assert!(!keypair.public_key.is_empty());
assert!(!keypair.secret_key.is_empty());
}
#[test]
fn test_encrypt_decrypt() {
let mut rng = rand::rngs::OsRng;
let mut engine = FheEngine::new(64, 32);
let keypair = engine.keygen(&mut rng).unwrap();
let plaintext = b"hello";
let ct = engine.encrypt(&mut rng, &keypair, plaintext).unwrap();
let recovered = engine.decrypt(&keypair, &ct).unwrap();
assert_eq!(&recovered[..5], plaintext);
}
#[test]
fn test_add_homomorphic() {
let mut rng = rand::rngs::OsRng;
let mut engine = FheEngine::new(64, 32);
let keypair = engine.keygen(&mut rng).unwrap();
let ct1 = engine.encrypt(&mut rng, &keypair, &[1u8]).unwrap();
let ct2 = engine.encrypt(&mut rng, &keypair, &[2u8]).unwrap();
let ct_sum = engine.add(&ct1, &ct2).unwrap();
assert!(ct_sum.noise_budget < ct1.noise_budget);
}
#[test]
fn test_verify_integrity() {
let mut rng = rand::rngs::OsRng;
let mut engine = FheEngine::new(64, 32);
let keypair = engine.keygen(&mut rng).unwrap();
let ct = engine.encrypt(&mut rng, &keypair, b"test").unwrap();
assert!(engine.verify(&ct).unwrap());
}
#[test]
fn test_serialize_deserialize() {
let mut rng = rand::rngs::OsRng;
let mut engine = FheEngine::new(64, 32);
let keypair = engine.keygen(&mut rng).unwrap();
let ct = engine.encrypt(&mut rng, &keypair, b"data").unwrap();
let bytes = engine.serialize_ciphertext(&ct).unwrap();
let ct2 = engine.deserialize_ciphertext(&bytes).unwrap();
assert_eq!(ct.data, ct2.data);
assert_eq!(ct.blake3_hash, ct2.blake3_hash);
}
#[test]
fn test_poly_add() {
let a = vec![1i64, 2, 3];
let b = vec![4i64, 5, 6];
let result = poly_add(&a, &b, 100);
assert_eq!(result, vec![5, 7, 9]);
}
#[test]
fn test_poly_mult_negacyclic_small() {
let a = vec![1i64, 1];
let b = vec![1i64, 1];
let result = poly_mult_negacyclic(&a, &b, 2, 1000);
assert_eq!(result[0], 0); assert_eq!(result[1], 2); }
#[test]
fn test_encode_decode_bytes() {
let data = b"hello world";
let poly = encode_bytes_to_poly(data, 16);
let recovered = decode_poly_to_bytes(&poly);
assert_eq!(&recovered[..data.len()], data);
}
}