use rand::{distributions::Distribution, Rng}; use std::{ iter::Sum, ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; use crate::ring::{Ring, RingParam}; /// Let 𝕋 = ℝ/ℤ, where 𝕋 is a ℤ-module, with homogeneous external product. /// Let 𝕋q /// T64 is 𝕋q with q=2^Ω, with Ω=64. We identify 𝕋q=(1/q)ℤ/ℤ ≈ ℤq. #[derive(Debug, Clone, Copy, PartialEq)] pub struct T64(pub u64); // implement the `Ring` trait for T64, so that it can be used where we would use // `Tn<1>`. impl Ring for T64 { type C = T64; fn param(&self) -> RingParam { RingParam { q: u64::MAX, // WIP n: 1, } } fn coeffs(&self) -> Vec { vec![self.clone()] } fn zero(_: &RingParam) -> Self { Self(0u64) } fn rand(mut rng: impl Rng, dist: impl Distribution, _: &RingParam) -> Self { let r: f64 = dist.sample(&mut rng); Self(r.round() as u64) } fn from_vec(_n: &RingParam, coeffs: Vec) -> Self { assert_eq!(coeffs.len(), 1); coeffs[0] } // TODO rm beta & l from inputs, make it always beta=2,l=64. /// Note: only beta=2 and l=64 is supported. fn decompose(&self, beta: u32, l: u32) -> Vec { assert_eq!(beta, 2u32, "only beta=2 supported"); // assert_eq!(l, 64u32, "only l=64 supported"); // (0..64) (0..l as u64) .rev() .map(|i| T64((self.0 >> i) & 1)) .collect() } fn remodule(&self, p: u64) -> T64 { todo!() } // modulus switch from Q to Q2: self * Q2/Q fn mod_switch(&self, q2: u64) -> T64 { // for the moment we assume Q|Q2, since Q=2^64, check that Q2 is a power // of two: assert!(q2.is_power_of_two()); // since Q=2^64, dividing Q2/Q is equivalent to dividing 2^log2(Q2)/2^64 // which would be like right-shifting 64-log2(Q2). let log2_q2 = 63 - q2.leading_zeros(); T64(self.0 >> (64 - log2_q2)) } fn mul_div_round(&self, num: u64, den: u64) -> Self { T64(((num as f64 * self.0 as f64) / den as f64).round() as u64) } } impl T64 { pub fn rand_u64(mut rng: impl Rng, dist: impl Distribution) -> Self { let r: u64 = dist.sample(&mut rng); Self(r) } } impl Add for T64 { type Output = Self; fn add(self, rhs: Self) -> Self::Output { Self(self.0.wrapping_add(rhs.0)) } } impl AddAssign for T64 { fn add_assign(&mut self, rhs: Self) { self.0 = self.0.wrapping_add(rhs.0) } } impl Sub for T64 { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { Self(self.0.wrapping_sub(rhs.0)) } } impl SubAssign for T64 { fn sub_assign(&mut self, rhs: Self) { self.0 = self.0.wrapping_sub(rhs.0) } } impl Neg for T64 { type Output = Self; fn neg(self) -> Self::Output { Self(self.0.wrapping_neg()) } } impl Sum for T64 { fn sum(iter: I) -> Self where I: Iterator, { iter.fold(Self(0), |acc, x| acc + x) } } impl Mul for T64 { type Output = Self; fn mul(self, rhs: Self) -> Self::Output { Self(self.0.wrapping_mul(rhs.0)) } } impl MulAssign for T64 { fn mul_assign(&mut self, rhs: Self) { self.0 = self.0.wrapping_mul(rhs.0) } } // mul by u64 impl Mul for T64 { type Output = Self; fn mul(self, s: u64) -> Self { if self.0 == 0 { return Self(0); } Self(self.0.wrapping_mul(s)) } } impl Mul<&u64> for &T64 { type Output = T64; fn mul(self, s: &u64) -> Self::Output { T64(self.0.wrapping_mul(*s)) } } #[cfg(test)] mod tests { use super::*; use rand::distributions::Standard; fn recompose(d: Vec) -> T64 { T64(d.iter().fold(0u64, |acc, &b| (acc << 1) | b.0)) } #[test] fn test_decompose() { let beta: u32 = 2; let l: u32 = 64; let x = T64(12345); let d = x.decompose(beta, l); assert_eq!(recompose(d), T64(12345)); let x = T64(0); let d = x.decompose(beta, l); assert_eq!(recompose(d), T64(0)); let x = T64(u64::MAX - 1); let d = x.decompose(beta, l); assert_eq!(recompose(d), T64(u64::MAX - 1)); let param = RingParam { q: u64::MAX, // WIP n: 1, }; let mut rng = rand::thread_rng(); for _ in 0..1000 { let x = T64::rand(&mut rng, Standard, ¶m); let d = x.decompose(beta, l); assert_eq!(recompose(d), x); } } }