mirror of
https://github.com/arnaucube/fhe-study.git
synced 2026-01-24 04:33:52 +01:00
add discretized torus & 𝕋_<N,q>[X]; organize a bit arith crate
This commit is contained in:
@@ -6,19 +6,29 @@
|
|||||||
|
|
||||||
pub mod complex;
|
pub mod complex;
|
||||||
pub mod matrix;
|
pub mod matrix;
|
||||||
|
pub mod torus;
|
||||||
|
pub mod zq;
|
||||||
|
|
||||||
|
pub mod ring;
|
||||||
|
pub mod ring_n;
|
||||||
|
pub mod ring_nq;
|
||||||
|
pub mod ring_torus;
|
||||||
|
pub mod tuple_ring;
|
||||||
|
|
||||||
mod naive_ntt; // note: for dev only
|
mod naive_ntt; // note: for dev only
|
||||||
pub mod ntt;
|
pub mod ntt;
|
||||||
pub mod ring;
|
|
||||||
pub mod ringq;
|
// expose objects
|
||||||
pub mod traits;
|
|
||||||
pub mod tuple_ring;
|
|
||||||
pub mod zq;
|
|
||||||
|
|
||||||
pub use complex::C;
|
pub use complex::C;
|
||||||
pub use matrix::Matrix;
|
pub use matrix::Matrix;
|
||||||
pub use ntt::NTT;
|
pub use torus::T64;
|
||||||
pub use ring::R;
|
|
||||||
pub use ringq::Rq;
|
|
||||||
pub use traits::Ring;
|
|
||||||
pub use tuple_ring::TR;
|
|
||||||
pub use zq::Zq;
|
pub use zq::Zq;
|
||||||
|
|
||||||
|
pub use ring::Ring;
|
||||||
|
pub use ring_n::R;
|
||||||
|
pub use ring_nq::Rq;
|
||||||
|
pub use ring_torus::Tn;
|
||||||
|
pub use tuple_ring::TR;
|
||||||
|
|
||||||
|
pub use ntt::NTT;
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use rand_distr::Uniform;
|
use rand_distr::Uniform;
|
||||||
|
|
||||||
use crate::ringq::matrix_vec_product;
|
use crate::ring_nq::matrix_vec_product;
|
||||||
use crate::ringq::Rq;
|
use crate::ring_nq::Rq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn roots_of_unity() -> Result<()> {
|
fn roots_of_unity() -> Result<()> {
|
||||||
|
|||||||
@@ -1,433 +1,33 @@
|
|||||||
//! Polynomial ring Z[X]/(X^N+1)
|
|
||||||
//!
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use rand::{distributions::Distribution, Rng};
|
use rand::{distributions::Distribution, Rng};
|
||||||
use std::array;
|
use std::fmt::Debug;
|
||||||
use std::fmt;
|
|
||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
|
||||||
|
|
||||||
use crate::Ring;
|
/// Represents a ring element. Currently implemented by ring_n.rs#R and ring_nq.rs#Rq.
|
||||||
|
pub trait Ring:
|
||||||
// TODO rename to not have name conflicts with the Ring trait (R: Ring)
|
Sized
|
||||||
// PolynomialRing element, where the PolynomialRing is R = Z[X]/(X^n +1)
|
+ Add<Output = Self>
|
||||||
#[derive(Clone, Copy)]
|
+ AddAssign
|
||||||
pub struct R<const N: usize>(pub [i64; N]);
|
+ Sum
|
||||||
|
+ Sub<Output = Self>
|
||||||
impl<const N: usize> Ring for R<N> {
|
+ SubAssign
|
||||||
type C = i64;
|
+ Mul<Output = Self>
|
||||||
fn coeffs(&self) -> Vec<Self::C> {
|
+ Mul<u64, Output = Self> // scalar mul
|
||||||
self.0.to_vec()
|
+ PartialEq
|
||||||
}
|
+ Debug
|
||||||
fn zero() -> Self {
|
+ Clone
|
||||||
let coeffs: [i64; N] = array::from_fn(|_| 0i64);
|
+ Sum<<Self as Add>::Output>
|
||||||
Self(coeffs)
|
+ Sum<<Self as Mul>::Output>
|
||||||
}
|
|
||||||
fn rand(mut rng: impl Rng, dist: impl Distribution<f64>) -> Self {
|
|
||||||
// let coeffs: [i64; N] = array::from_fn(|_| Self::C::rand(&mut rng, &dist));
|
|
||||||
let coeffs: [i64; N] = array::from_fn(|_| dist.sample(&mut rng).round() as i64);
|
|
||||||
Self(coeffs)
|
|
||||||
// let coeffs: [C; N] = array::from_fn(|_| Zq::from_u64(dist.sample(&mut rng)));
|
|
||||||
// Self(coeffs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the decomposition of each polynomial coefficient
|
|
||||||
fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
|
|
||||||
unimplemented!();
|
|
||||||
// array::from_fn(|i| self.coeffs[i].decompose(beta, l))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const Q: u64, const N: usize> From<crate::ringq::Rq<Q, N>> for R<N> {
|
|
||||||
fn from(rq: crate::ringq::Rq<Q, N>) -> Self {
|
|
||||||
Self::from_vec_u64(rq.coeffs().to_vec().iter().map(|e| e.0).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> R<N> {
|
|
||||||
pub fn coeffs(&self) -> [i64; N] {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
pub fn to_rq<const Q: u64>(self) -> crate::Rq<Q, N> {
|
|
||||||
crate::Rq::<Q, N>::from(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_vec(coeffs: Vec<i64>) -> Self {
|
|
||||||
let mut p = coeffs;
|
|
||||||
modulus::<N>(&mut p);
|
|
||||||
Self(array::from_fn(|i| p[i]))
|
|
||||||
}
|
|
||||||
// this method is mostly for tests
|
|
||||||
pub fn from_vec_u64(coeffs: Vec<u64>) -> Self {
|
|
||||||
let coeffs_i64 = coeffs.iter().map(|c| *c as i64).collect();
|
|
||||||
Self::from_vec(coeffs_i64)
|
|
||||||
}
|
|
||||||
pub fn from_vec_f64(coeffs: Vec<f64>) -> Self {
|
|
||||||
let coeffs_i64 = coeffs.iter().map(|c| c.round() as i64).collect();
|
|
||||||
Self::from_vec(coeffs_i64)
|
|
||||||
}
|
|
||||||
pub fn new(coeffs: [i64; N]) -> Self {
|
|
||||||
Self(coeffs)
|
|
||||||
}
|
|
||||||
pub fn mul_by_i64(&self, s: i64) -> Self {
|
|
||||||
Self(array::from_fn(|i| self.0[i] * s))
|
|
||||||
}
|
|
||||||
// performs the multiplication and division over f64, and then it rounds the
|
|
||||||
// result, only applying the mod Q at the end
|
|
||||||
pub fn mul_div_round<const Q: u64>(&self, num: u64, den: u64) -> crate::Rq<Q, N> {
|
|
||||||
let r: Vec<f64> = self
|
|
||||||
.coeffs()
|
|
||||||
.iter()
|
|
||||||
.map(|e| ((num as f64 * *e as f64) / den as f64).round())
|
|
||||||
.collect();
|
|
||||||
crate::Rq::<Q, N>::from_vec_f64(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn infinity_norm(&self) -> u64 {
|
|
||||||
self.coeffs()
|
|
||||||
.iter()
|
|
||||||
// .map(|x| if x.0 > (Q / 2) { Q - x.0 } else { x.0 })
|
|
||||||
.map(|x| x.abs() as u64)
|
|
||||||
.fold(0, |a, b| a.max(b))
|
|
||||||
}
|
|
||||||
pub fn mod_centered_q<const Q: u64>(&self) -> R<N> {
|
|
||||||
let q = Q as i64;
|
|
||||||
let r = self
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.map(|v| {
|
|
||||||
let mut res = v % q;
|
|
||||||
if res > q / 2 {
|
|
||||||
res = res - q;
|
|
||||||
}
|
|
||||||
res
|
|
||||||
})
|
|
||||||
.collect::<Vec<i64>>();
|
|
||||||
R::<N>::from_vec(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mul_div_round<const Q: u64, const N: usize>(
|
|
||||||
v: Vec<i64>,
|
|
||||||
num: u64,
|
|
||||||
den: u64,
|
|
||||||
) -> crate::Rq<Q, N> {
|
|
||||||
// dbg!(&v);
|
|
||||||
let r: Vec<f64> = v
|
|
||||||
.iter()
|
|
||||||
.map(|e| ((num as f64 * *e as f64) / den as f64).round())
|
|
||||||
.collect();
|
|
||||||
// dbg!(&r);
|
|
||||||
crate::Rq::<Q, N>::from_vec_f64(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO rename to make it clear that is not mod q, but mod X^N+1
|
|
||||||
// apply mod (X^N+1)
|
|
||||||
pub fn modulus<const N: usize>(p: &mut Vec<i64>) {
|
|
||||||
if p.len() < N {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for i in N..p.len() {
|
|
||||||
p[i - N] = p[i - N].clone() - p[i].clone();
|
|
||||||
p[i] = 0;
|
|
||||||
}
|
|
||||||
p.truncate(N);
|
|
||||||
}
|
|
||||||
pub fn modulus_i128<const N: usize>(p: &mut Vec<i128>) {
|
|
||||||
if p.len() < N {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for i in N..p.len() {
|
|
||||||
p[i - N] = p[i - N].clone() - p[i].clone();
|
|
||||||
p[i] = 0;
|
|
||||||
}
|
|
||||||
p.truncate(N);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> PartialEq for R<N> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0 == other.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N: usize> Add<R<N>> for R<N> {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self {
|
|
||||||
Self(array::from_fn(|i| self.0[i] + rhs.0[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N: usize> Add<&R<N>> for &R<N> {
|
|
||||||
type Output = R<N>;
|
|
||||||
|
|
||||||
fn add(self, rhs: &R<N>) -> Self::Output {
|
|
||||||
R(array::from_fn(|i| self.0[i] + rhs.0[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N: usize> AddAssign for R<N> {
|
|
||||||
fn add_assign(&mut self, rhs: Self) {
|
|
||||||
for i in 0..N {
|
|
||||||
self.0[i] += rhs.0[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Sum<R<N>> for R<N> {
|
|
||||||
fn sum<I>(iter: I) -> Self
|
|
||||||
where
|
|
||||||
I: Iterator<Item = Self>,
|
|
||||||
{
|
{
|
||||||
let mut acc = R::<N>::zero();
|
/// C defines the coefficient type
|
||||||
for e in iter {
|
type C: Debug + Clone;
|
||||||
acc += e;
|
|
||||||
}
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Sub<R<N>> for R<N> {
|
fn coeffs(&self) -> Vec<Self::C>;
|
||||||
type Output = Self;
|
fn zero() -> Self;
|
||||||
|
// note/wip/warning: dist (0,q) with f64, will output more '0=q' elements than other values
|
||||||
|
fn rand(rng: impl Rng, dist: impl Distribution<f64>) -> Self;
|
||||||
|
|
||||||
fn sub(self, rhs: Self) -> Self {
|
fn from_vec(coeffs: Vec<Self::C>) -> Self;
|
||||||
Self(array::from_fn(|i| self.0[i] - rhs.0[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N: usize> Sub<&R<N>> for &R<N> {
|
|
||||||
type Output = R<N>;
|
|
||||||
|
|
||||||
fn sub(self, rhs: &R<N>) -> Self::Output {
|
fn decompose(&self, beta: u32, l: u32) -> Vec<Self>;
|
||||||
R(array::from_fn(|i| self.0[i] - rhs.0[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> SubAssign for R<N> {
|
|
||||||
fn sub_assign(&mut self, rhs: Self) {
|
|
||||||
for i in 0..N {
|
|
||||||
self.0[i] -= rhs.0[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Mul<R<N>> for R<N> {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn mul(self, rhs: Self) -> Self {
|
|
||||||
naive_poly_mul(&self, &rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N: usize> Mul<&R<N>> for &R<N> {
|
|
||||||
type Output = R<N>;
|
|
||||||
|
|
||||||
fn mul(self, rhs: &R<N>) -> Self::Output {
|
|
||||||
naive_poly_mul(self, rhs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO WIP
|
|
||||||
pub fn naive_poly_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> R<N> {
|
|
||||||
let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
|
|
||||||
let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
|
|
||||||
let mut result: Vec<i128> = vec![0; (N * 2) - 1];
|
|
||||||
for i in 0..N {
|
|
||||||
for j in 0..N {
|
|
||||||
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply mod (X^N + 1))
|
|
||||||
// R::<N>::from_vec(result.iter().map(|c| *c as i64).collect())
|
|
||||||
modulus_i128::<N>(&mut result);
|
|
||||||
// dbg!(&result);
|
|
||||||
// dbg!(R::<N>(array::from_fn(|i| result[i] as i64)).coeffs());
|
|
||||||
|
|
||||||
// sanity check: check that there are no coeffs > i64_max
|
|
||||||
assert_eq!(
|
|
||||||
result,
|
|
||||||
R::<N>(array::from_fn(|i| result[i] as i64))
|
|
||||||
.coeffs()
|
|
||||||
.iter()
|
|
||||||
.map(|c| *c as i128)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
R(array::from_fn(|i| result[i] as i64))
|
|
||||||
}
|
|
||||||
pub fn naive_mul_2<const N: usize>(poly1: &Vec<i128>, poly2: &Vec<i128>) -> Vec<i128> {
|
|
||||||
let mut result: Vec<i128> = vec![0; (N * 2) - 1];
|
|
||||||
for i in 0..N {
|
|
||||||
for j in 0..N {
|
|
||||||
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply mod (X^N + 1))
|
|
||||||
// R::<N>::from_vec(result.iter().map(|c| *c as i64).collect())
|
|
||||||
modulus_i128::<N>(&mut result);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn naive_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> Vec<i64> {
|
|
||||||
let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
|
|
||||||
let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
|
|
||||||
let mut result = vec![0; (N * 2) - 1];
|
|
||||||
for i in 0..N {
|
|
||||||
for j in 0..N {
|
|
||||||
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.iter().map(|c| *c as i64).collect()
|
|
||||||
}
|
|
||||||
pub fn naive_mul_TMP<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> Vec<i64> {
|
|
||||||
let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
|
|
||||||
let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
|
|
||||||
let mut result: Vec<i128> = vec![0; (N * 2) - 1];
|
|
||||||
for i in 0..N {
|
|
||||||
for j in 0..N {
|
|
||||||
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dbg!(&result);
|
|
||||||
modulus_i128::<N>(&mut result);
|
|
||||||
// for c_i in result.iter() {
|
|
||||||
// println!("---");
|
|
||||||
// println!("{:?}", &c_i);
|
|
||||||
// println!("{:?}", *c_i as i64);
|
|
||||||
// println!("{:?}", (*c_i as i64) as i128);
|
|
||||||
// assert_eq!(*c_i, (*c_i as i64) as i128, "{:?}", c_i);
|
|
||||||
// }
|
|
||||||
result.iter().map(|c| *c as i64).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// wip
|
|
||||||
pub fn mod_centered_q<const Q: u64, const N: usize>(p: Vec<i128>) -> R<N> {
|
|
||||||
let q: i128 = Q as i128;
|
|
||||||
let r = p
|
|
||||||
.iter()
|
|
||||||
.map(|v| {
|
|
||||||
let mut res = v % q;
|
|
||||||
if res > q / 2 {
|
|
||||||
res = res - q;
|
|
||||||
}
|
|
||||||
res
|
|
||||||
})
|
|
||||||
.collect::<Vec<i128>>();
|
|
||||||
R::<N>::from_vec(r.iter().map(|v| *v as i64).collect::<Vec<i64>>())
|
|
||||||
}
|
|
||||||
|
|
||||||
// mul by u64
|
|
||||||
impl<const N: usize> Mul<u64> for R<N> {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn mul(self, s: u64) -> Self {
|
|
||||||
self.mul_by_i64(s as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N: usize> Mul<&u64> for &R<N> {
|
|
||||||
type Output = R<N>;
|
|
||||||
|
|
||||||
fn mul(self, s: &u64) -> Self::Output {
|
|
||||||
self.mul_by_i64(*s as i64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Neg for R<N> {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn neg(self) -> Self::Output {
|
|
||||||
Self(array::from_fn(|i| -self.0[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> R<N> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let mut str = "";
|
|
||||||
let mut zero = true;
|
|
||||||
for (i, coeff) in self.0.iter().enumerate().rev() {
|
|
||||||
if *coeff == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
zero = false;
|
|
||||||
f.write_str(str)?;
|
|
||||||
if *coeff != 1 {
|
|
||||||
f.write_str(coeff.to_string().as_str())?;
|
|
||||||
if i > 0 {
|
|
||||||
f.write_str("*")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if *coeff == 1 && i == 0 {
|
|
||||||
f.write_str(coeff.to_string().as_str())?;
|
|
||||||
}
|
|
||||||
if i == 1 {
|
|
||||||
f.write_str("x")?;
|
|
||||||
} else if i > 1 {
|
|
||||||
f.write_str("x^")?;
|
|
||||||
f.write_str(i.to_string().as_str())?;
|
|
||||||
}
|
|
||||||
str = " + ";
|
|
||||||
}
|
|
||||||
if zero {
|
|
||||||
f.write_str("0")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_str(" mod Z")?;
|
|
||||||
f.write_str("/(X^")?;
|
|
||||||
f.write_str(N.to_string().as_str())?;
|
|
||||||
f.write_str("+1)")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N: usize> fmt::Display for R<N> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
self.fmt(f)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<const N: usize> fmt::Debug for R<N> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
self.fmt(f)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_mul() -> Result<()> {
|
|
||||||
const Q: u64 = 2u64.pow(16) + 1;
|
|
||||||
const N: usize = 2;
|
|
||||||
let q: i64 = Q as i64;
|
|
||||||
|
|
||||||
// test vectors generated with SageMath
|
|
||||||
let a: [i64; N] = [q - 1, q - 1];
|
|
||||||
let b: [i64; N] = [q - 1, q - 1];
|
|
||||||
let c: [i64; N] = [0, 8589934592];
|
|
||||||
test_mul_opt::<Q, N>(a, b, c)?;
|
|
||||||
|
|
||||||
let a: [i64; N] = [1, q - 1];
|
|
||||||
let b: [i64; N] = [1, q - 1];
|
|
||||||
let c: [i64; N] = [-4294967295, 131072];
|
|
||||||
test_mul_opt::<Q, N>(a, b, c)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn test_mul_opt<const Q: u64, const N: usize>(
|
|
||||||
a: [i64; N],
|
|
||||||
b: [i64; N],
|
|
||||||
expected_c: [i64; N],
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut a = R::new(a);
|
|
||||||
let mut b = R::new(b);
|
|
||||||
dbg!(&a);
|
|
||||||
dbg!(&b);
|
|
||||||
let expected_c = R::new(expected_c);
|
|
||||||
|
|
||||||
let mut c = naive_mul(&mut a, &mut b);
|
|
||||||
modulus::<N>(&mut c);
|
|
||||||
dbg!(R::<N>::from_vec(c.clone()));
|
|
||||||
assert_eq!(c, expected_c.0.to_vec());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
434
arith/src/ring_n.rs
Normal file
434
arith/src/ring_n.rs
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
//! Polynomial ring Z[X]/(X^N+1)
|
||||||
|
//!
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use rand::{distributions::Distribution, Rng};
|
||||||
|
use std::array;
|
||||||
|
use std::fmt;
|
||||||
|
use std::iter::Sum;
|
||||||
|
use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||||
|
|
||||||
|
use crate::Ring;
|
||||||
|
|
||||||
|
// TODO rename to not have name conflicts with the Ring trait (R: Ring)
|
||||||
|
// PolynomialRing element, where the PolynomialRing is R = Z[X]/(X^n +1)
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct R<const N: usize>(pub [i64; N]);
|
||||||
|
|
||||||
|
impl<const N: usize> Ring for R<N> {
|
||||||
|
type C = i64;
|
||||||
|
fn coeffs(&self) -> Vec<Self::C> {
|
||||||
|
self.0.to_vec()
|
||||||
|
}
|
||||||
|
fn zero() -> Self {
|
||||||
|
let coeffs: [i64; N] = array::from_fn(|_| 0i64);
|
||||||
|
Self(coeffs)
|
||||||
|
}
|
||||||
|
fn rand(mut rng: impl Rng, dist: impl Distribution<f64>) -> Self {
|
||||||
|
// let coeffs: [i64; N] = array::from_fn(|_| Self::C::rand(&mut rng, &dist));
|
||||||
|
let coeffs: [i64; N] = array::from_fn(|_| dist.sample(&mut rng).round() as i64);
|
||||||
|
Self(coeffs)
|
||||||
|
// let coeffs: [C; N] = array::from_fn(|_| Zq::from_u64(dist.sample(&mut rng)));
|
||||||
|
// Self(coeffs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_vec(coeffs: Vec<Self::C>) -> Self {
|
||||||
|
let mut p = coeffs;
|
||||||
|
modulus::<N>(&mut p);
|
||||||
|
Self(array::from_fn(|i| p[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the decomposition of each polynomial coefficient
|
||||||
|
fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
|
||||||
|
unimplemented!();
|
||||||
|
// array::from_fn(|i| self.coeffs[i].decompose(beta, l))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const Q: u64, const N: usize> From<crate::ring_nq::Rq<Q, N>> for R<N> {
|
||||||
|
fn from(rq: crate::ring_nq::Rq<Q, N>) -> Self {
|
||||||
|
Self::from_vec_u64(rq.coeffs().to_vec().iter().map(|e| e.0).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> R<N> {
|
||||||
|
pub fn coeffs(&self) -> [i64; N] {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
pub fn to_rq<const Q: u64>(self) -> crate::Rq<Q, N> {
|
||||||
|
crate::Rq::<Q, N>::from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method is mostly for tests
|
||||||
|
pub fn from_vec_u64(coeffs: Vec<u64>) -> Self {
|
||||||
|
let coeffs_i64 = coeffs.iter().map(|c| *c as i64).collect();
|
||||||
|
Self::from_vec(coeffs_i64)
|
||||||
|
}
|
||||||
|
pub fn from_vec_f64(coeffs: Vec<f64>) -> Self {
|
||||||
|
let coeffs_i64 = coeffs.iter().map(|c| c.round() as i64).collect();
|
||||||
|
Self::from_vec(coeffs_i64)
|
||||||
|
}
|
||||||
|
pub fn new(coeffs: [i64; N]) -> Self {
|
||||||
|
Self(coeffs)
|
||||||
|
}
|
||||||
|
pub fn mul_by_i64(&self, s: i64) -> Self {
|
||||||
|
Self(array::from_fn(|i| self.0[i] * s))
|
||||||
|
}
|
||||||
|
// performs the multiplication and division over f64, and then it rounds the
|
||||||
|
// result, only applying the mod Q at the end
|
||||||
|
pub fn mul_div_round<const Q: u64>(&self, num: u64, den: u64) -> crate::Rq<Q, N> {
|
||||||
|
let r: Vec<f64> = self
|
||||||
|
.coeffs()
|
||||||
|
.iter()
|
||||||
|
.map(|e| ((num as f64 * *e as f64) / den as f64).round())
|
||||||
|
.collect();
|
||||||
|
crate::Rq::<Q, N>::from_vec_f64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn infinity_norm(&self) -> u64 {
|
||||||
|
self.coeffs()
|
||||||
|
.iter()
|
||||||
|
// .map(|x| if x.0 > (Q / 2) { Q - x.0 } else { x.0 })
|
||||||
|
.map(|x| x.abs() as u64)
|
||||||
|
.fold(0, |a, b| a.max(b))
|
||||||
|
}
|
||||||
|
pub fn mod_centered_q<const Q: u64>(&self) -> R<N> {
|
||||||
|
let q = Q as i64;
|
||||||
|
let r = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
let mut res = v % q;
|
||||||
|
if res > q / 2 {
|
||||||
|
res = res - q;
|
||||||
|
}
|
||||||
|
res
|
||||||
|
})
|
||||||
|
.collect::<Vec<i64>>();
|
||||||
|
R::<N>::from_vec(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mul_div_round<const Q: u64, const N: usize>(
|
||||||
|
v: Vec<i64>,
|
||||||
|
num: u64,
|
||||||
|
den: u64,
|
||||||
|
) -> crate::Rq<Q, N> {
|
||||||
|
// dbg!(&v);
|
||||||
|
let r: Vec<f64> = v
|
||||||
|
.iter()
|
||||||
|
.map(|e| ((num as f64 * *e as f64) / den as f64).round())
|
||||||
|
.collect();
|
||||||
|
// dbg!(&r);
|
||||||
|
crate::Rq::<Q, N>::from_vec_f64(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO rename to make it clear that is not mod q, but mod X^N+1
|
||||||
|
// apply mod (X^N+1)
|
||||||
|
pub fn modulus<const N: usize>(p: &mut Vec<i64>) {
|
||||||
|
if p.len() < N {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for i in N..p.len() {
|
||||||
|
p[i - N] = p[i - N].clone() - p[i].clone();
|
||||||
|
p[i] = 0;
|
||||||
|
}
|
||||||
|
p.truncate(N);
|
||||||
|
}
|
||||||
|
pub fn modulus_i128<const N: usize>(p: &mut Vec<i128>) {
|
||||||
|
if p.len() < N {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for i in N..p.len() {
|
||||||
|
p[i - N] = p[i - N].clone() - p[i].clone();
|
||||||
|
p[i] = 0;
|
||||||
|
}
|
||||||
|
p.truncate(N);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> PartialEq for R<N> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0 == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Add<R<N>> for R<N> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self {
|
||||||
|
Self(array::from_fn(|i| self.0[i] + rhs.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Add<&R<N>> for &R<N> {
|
||||||
|
type Output = R<N>;
|
||||||
|
|
||||||
|
fn add(self, rhs: &R<N>) -> Self::Output {
|
||||||
|
R(array::from_fn(|i| self.0[i] + rhs.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> AddAssign for R<N> {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
for i in 0..N {
|
||||||
|
self.0[i] += rhs.0[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Sum<R<N>> for R<N> {
|
||||||
|
fn sum<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: Iterator<Item = Self>,
|
||||||
|
{
|
||||||
|
let mut acc = R::<N>::zero();
|
||||||
|
for e in iter {
|
||||||
|
acc += e;
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Sub<R<N>> for R<N> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self {
|
||||||
|
Self(array::from_fn(|i| self.0[i] - rhs.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Sub<&R<N>> for &R<N> {
|
||||||
|
type Output = R<N>;
|
||||||
|
|
||||||
|
fn sub(self, rhs: &R<N>) -> Self::Output {
|
||||||
|
R(array::from_fn(|i| self.0[i] - rhs.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> SubAssign for R<N> {
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
for i in 0..N {
|
||||||
|
self.0[i] -= rhs.0[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Mul<R<N>> for R<N> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Self) -> Self {
|
||||||
|
naive_poly_mul(&self, &rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Mul<&R<N>> for &R<N> {
|
||||||
|
type Output = R<N>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: &R<N>) -> Self::Output {
|
||||||
|
naive_poly_mul(self, rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO WIP
|
||||||
|
pub fn naive_poly_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> R<N> {
|
||||||
|
let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
|
||||||
|
let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
|
||||||
|
let mut result: Vec<i128> = vec![0; (N * 2) - 1];
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply mod (X^N + 1))
|
||||||
|
// R::<N>::from_vec(result.iter().map(|c| *c as i64).collect())
|
||||||
|
modulus_i128::<N>(&mut result);
|
||||||
|
// dbg!(&result);
|
||||||
|
// dbg!(R::<N>(array::from_fn(|i| result[i] as i64)).coeffs());
|
||||||
|
|
||||||
|
// sanity check: check that there are no coeffs > i64_max
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
R::<N>(array::from_fn(|i| result[i] as i64))
|
||||||
|
.coeffs()
|
||||||
|
.iter()
|
||||||
|
.map(|c| *c as i128)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
R(array::from_fn(|i| result[i] as i64))
|
||||||
|
}
|
||||||
|
pub fn naive_mul_2<const N: usize>(poly1: &Vec<i128>, poly2: &Vec<i128>) -> Vec<i128> {
|
||||||
|
let mut result: Vec<i128> = vec![0; (N * 2) - 1];
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply mod (X^N + 1))
|
||||||
|
// R::<N>::from_vec(result.iter().map(|c| *c as i64).collect())
|
||||||
|
modulus_i128::<N>(&mut result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn naive_mul<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> Vec<i64> {
|
||||||
|
let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
|
||||||
|
let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
|
||||||
|
let mut result = vec![0; (N * 2) - 1];
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.iter().map(|c| *c as i64).collect()
|
||||||
|
}
|
||||||
|
pub fn naive_mul_TMP<const N: usize>(poly1: &R<N>, poly2: &R<N>) -> Vec<i64> {
|
||||||
|
let poly1: Vec<i128> = poly1.0.iter().map(|c| *c as i128).collect();
|
||||||
|
let poly2: Vec<i128> = poly2.0.iter().map(|c| *c as i128).collect();
|
||||||
|
let mut result: Vec<i128> = vec![0; (N * 2) - 1];
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbg!(&result);
|
||||||
|
modulus_i128::<N>(&mut result);
|
||||||
|
// for c_i in result.iter() {
|
||||||
|
// println!("---");
|
||||||
|
// println!("{:?}", &c_i);
|
||||||
|
// println!("{:?}", *c_i as i64);
|
||||||
|
// println!("{:?}", (*c_i as i64) as i128);
|
||||||
|
// assert_eq!(*c_i, (*c_i as i64) as i128, "{:?}", c_i);
|
||||||
|
// }
|
||||||
|
result.iter().map(|c| *c as i64).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// wip
|
||||||
|
pub fn mod_centered_q<const Q: u64, const N: usize>(p: Vec<i128>) -> R<N> {
|
||||||
|
let q: i128 = Q as i128;
|
||||||
|
let r = p
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
let mut res = v % q;
|
||||||
|
if res > q / 2 {
|
||||||
|
res = res - q;
|
||||||
|
}
|
||||||
|
res
|
||||||
|
})
|
||||||
|
.collect::<Vec<i128>>();
|
||||||
|
R::<N>::from_vec(r.iter().map(|v| *v as i64).collect::<Vec<i64>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul by u64
|
||||||
|
impl<const N: usize> Mul<u64> for R<N> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, s: u64) -> Self {
|
||||||
|
self.mul_by_i64(s as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Mul<&u64> for &R<N> {
|
||||||
|
type Output = R<N>;
|
||||||
|
|
||||||
|
fn mul(self, s: &u64) -> Self::Output {
|
||||||
|
self.mul_by_i64(*s as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Neg for R<N> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Self(array::from_fn(|i| -self.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> R<N> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let mut str = "";
|
||||||
|
let mut zero = true;
|
||||||
|
for (i, coeff) in self.0.iter().enumerate().rev() {
|
||||||
|
if *coeff == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
zero = false;
|
||||||
|
f.write_str(str)?;
|
||||||
|
if *coeff != 1 {
|
||||||
|
f.write_str(coeff.to_string().as_str())?;
|
||||||
|
if i > 0 {
|
||||||
|
f.write_str("*")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *coeff == 1 && i == 0 {
|
||||||
|
f.write_str(coeff.to_string().as_str())?;
|
||||||
|
}
|
||||||
|
if i == 1 {
|
||||||
|
f.write_str("x")?;
|
||||||
|
} else if i > 1 {
|
||||||
|
f.write_str("x^")?;
|
||||||
|
f.write_str(i.to_string().as_str())?;
|
||||||
|
}
|
||||||
|
str = " + ";
|
||||||
|
}
|
||||||
|
if zero {
|
||||||
|
f.write_str("0")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.write_str(" mod Z")?;
|
||||||
|
f.write_str("/(X^")?;
|
||||||
|
f.write_str(N.to_string().as_str())?;
|
||||||
|
f.write_str("+1)")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> fmt::Display for R<N> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.fmt(f)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> fmt::Debug for R<N> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.fmt(f)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mul() -> Result<()> {
|
||||||
|
const Q: u64 = 2u64.pow(16) + 1;
|
||||||
|
const N: usize = 2;
|
||||||
|
let q: i64 = Q as i64;
|
||||||
|
|
||||||
|
// test vectors generated with SageMath
|
||||||
|
let a: [i64; N] = [q - 1, q - 1];
|
||||||
|
let b: [i64; N] = [q - 1, q - 1];
|
||||||
|
let c: [i64; N] = [0, 8589934592];
|
||||||
|
test_mul_opt::<Q, N>(a, b, c)?;
|
||||||
|
|
||||||
|
let a: [i64; N] = [1, q - 1];
|
||||||
|
let b: [i64; N] = [1, q - 1];
|
||||||
|
let c: [i64; N] = [-4294967295, 131072];
|
||||||
|
test_mul_opt::<Q, N>(a, b, c)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn test_mul_opt<const Q: u64, const N: usize>(
|
||||||
|
a: [i64; N],
|
||||||
|
b: [i64; N],
|
||||||
|
expected_c: [i64; N],
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut a = R::new(a);
|
||||||
|
let mut b = R::new(b);
|
||||||
|
dbg!(&a);
|
||||||
|
dbg!(&b);
|
||||||
|
let expected_c = R::new(expected_c);
|
||||||
|
|
||||||
|
let mut c = naive_mul(&mut a, &mut b);
|
||||||
|
modulus::<N>(&mut c);
|
||||||
|
dbg!(R::<N>::from_vec(c.clone()));
|
||||||
|
assert_eq!(c, expected_c.0.to_vec());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Polynomial ring Z_q[X]/(X^N+1)
|
//! Polynomial ring Z_q[X]/(X^N+1)
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
use rand::{distributions::Distribution, Rng};
|
use rand::{distributions::Distribution, Rng};
|
||||||
use std::array;
|
use std::array;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -9,7 +10,6 @@ use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
|
|||||||
|
|
||||||
use crate::ntt::NTT;
|
use crate::ntt::NTT;
|
||||||
use crate::zq::{modulus_u64, Zq};
|
use crate::zq::{modulus_u64, Zq};
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
|
|
||||||
use crate::Ring;
|
use crate::Ring;
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ pub struct Rq<const Q: u64, const N: usize> {
|
|||||||
|
|
||||||
impl<const Q: u64, const N: usize> Ring for Rq<Q, N> {
|
impl<const Q: u64, const N: usize> Ring for Rq<Q, N> {
|
||||||
type C = Zq<Q>;
|
type C = Zq<Q>;
|
||||||
|
|
||||||
fn coeffs(&self) -> Vec<Self::C> {
|
fn coeffs(&self) -> Vec<Self::C> {
|
||||||
self.coeffs.to_vec()
|
self.coeffs.to_vec()
|
||||||
}
|
}
|
||||||
@@ -48,6 +49,16 @@ impl<const Q: u64, const N: usize> Ring for Rq<Q, N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_vec(coeffs: Vec<Zq<Q>>) -> Self {
|
||||||
|
let mut p = coeffs;
|
||||||
|
modulus::<Q, N>(&mut p);
|
||||||
|
let coeffs = array::from_fn(|i| p[i]);
|
||||||
|
Self {
|
||||||
|
coeffs,
|
||||||
|
evals: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// returns the decomposition of each polynomial coefficient, such
|
// returns the decomposition of each polynomial coefficient, such
|
||||||
// decomposition will be a vecotor of length N, containint N vectors of Zq
|
// decomposition will be a vecotor of length N, containint N vectors of Zq
|
||||||
fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
|
fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
|
||||||
@@ -61,8 +72,8 @@ impl<const Q: u64, const N: usize> Ring for Rq<Q, N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const Q: u64, const N: usize> From<crate::ring::R<N>> for Rq<Q, N> {
|
impl<const Q: u64, const N: usize> From<crate::ring_n::R<N>> for Rq<Q, N> {
|
||||||
fn from(r: crate::ring::R<N>) -> Self {
|
fn from(r: crate::ring_n::R<N>) -> Self {
|
||||||
Self::from_vec(
|
Self::from_vec(
|
||||||
r.coeffs()
|
r.coeffs()
|
||||||
.iter()
|
.iter()
|
||||||
@@ -104,15 +115,6 @@ impl<const Q: u64, const N: usize> Rq<Q, N> {
|
|||||||
// evals: None,
|
// evals: None,
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
pub fn from_vec(coeffs: Vec<Zq<Q>>) -> Self {
|
|
||||||
let mut p = coeffs;
|
|
||||||
modulus::<Q, N>(&mut p);
|
|
||||||
let coeffs = array::from_fn(|i| p[i]);
|
|
||||||
Self {
|
|
||||||
coeffs,
|
|
||||||
evals: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// this method is mostly for tests
|
// this method is mostly for tests
|
||||||
pub fn from_vec_u64(coeffs: Vec<u64>) -> Self {
|
pub fn from_vec_u64(coeffs: Vec<u64>) -> Self {
|
||||||
let coeffs_mod_q = coeffs.iter().map(|c| Zq::from_u64(*c)).collect();
|
let coeffs_mod_q = coeffs.iter().map(|c| Zq::from_u64(*c)).collect();
|
||||||
@@ -286,7 +288,7 @@ impl<const Q: u64, const N: usize> Rq<Q, N> {
|
|||||||
.map(|x| if x.0 > (Q / 2) { Q - x.0 } else { x.0 })
|
.map(|x| if x.0 > (Q / 2) { Q - x.0 } else { x.0 })
|
||||||
.fold(0, |a, b| a.max(b))
|
.fold(0, |a, b| a.max(b))
|
||||||
}
|
}
|
||||||
pub fn mod_centered_q(&self) -> crate::ring::R<N> {
|
pub fn mod_centered_q(&self) -> crate::ring_n::R<N> {
|
||||||
self.to_r().mod_centered_q::<Q>()
|
self.to_r().mod_centered_q::<Q>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -535,7 +537,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn poly_ring() {
|
fn test_polynomial_ring() {
|
||||||
// the test values used are generated with SageMath
|
// the test values used are generated with SageMath
|
||||||
const Q: u64 = 7;
|
const Q: u64 = 7;
|
||||||
const N: usize = 3;
|
const N: usize = 3;
|
||||||
@@ -623,14 +625,14 @@ mod tests {
|
|||||||
let d = a.decompose(beta, l);
|
let d = a.decompose(beta, l);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
d[0],
|
d[0].coeffs().to_vec(),
|
||||||
vec![1u64, 3, 0, 1]
|
vec![1u64, 3, 0, 1]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| Zq::<Q>::from_u64(*e))
|
.map(|e| Zq::<Q>::from_u64(*e))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
d[1],
|
d[1].coeffs().to_vec(),
|
||||||
vec![3u64, 2, 3, 2]
|
vec![3u64, 2, 3, 2]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| Zq::<Q>::from_u64(*e))
|
.map(|e| Zq::<Q>::from_u64(*e))
|
||||||
192
arith/src/ring_torus.rs
Normal file
192
arith/src/ring_torus.rs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
//! 𝕋_<N,q>[X] = ℝ_<N,q>[X] / ℤ_<N,q>[X], polynomials modulo X^N+1 with
|
||||||
|
//! coefficients in 𝕋_Q.
|
||||||
|
//!
|
||||||
|
//! Note: this is not an algebraic ring, since internal-product is not well
|
||||||
|
//! defined. But since we work over the discrete torus 𝕋_q, which we identify as
|
||||||
|
//! 𝕋q = ℤ/qℤ ≈ ℤq, whith q=64. Since we allow product between 𝕋q elements and
|
||||||
|
//! u64, we fit it into the `Ring` trait (from ring.rs) so that we can compose
|
||||||
|
//! the 𝕋_<N,q> implementation with the other objects from the code.
|
||||||
|
|
||||||
|
use rand::{distributions::Distribution, Rng};
|
||||||
|
use std::array;
|
||||||
|
use std::iter::Sum;
|
||||||
|
use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
|
||||||
|
|
||||||
|
use crate::{ring::Ring, torus::T64};
|
||||||
|
|
||||||
|
/// 𝕋_<N,Q>[X] = 𝕋<Q>[X]/(X^N +1), polynomials modulo X^N+1 with coefficients in
|
||||||
|
/// 𝕋, where Q=2^64.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Tn<const N: usize>(pub [T64; N]);
|
||||||
|
|
||||||
|
impl<const N: usize> Ring for Tn<N> {
|
||||||
|
type C = T64;
|
||||||
|
|
||||||
|
fn coeffs(&self) -> Vec<T64> {
|
||||||
|
self.0.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zero() -> Self {
|
||||||
|
Self(array::from_fn(|_| T64::zero()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rand(mut rng: impl Rng, dist: impl Distribution<f64>) -> Self {
|
||||||
|
Self(array::from_fn(|_| T64::rand_f64(&mut rng, &dist)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_vec(coeffs: Vec<Self::C>) -> Self {
|
||||||
|
let mut p = coeffs;
|
||||||
|
modulus::<N>(&mut p);
|
||||||
|
Self(array::from_fn(|i| p[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
|
||||||
|
let elems: Vec<Vec<T64>> = self.0.iter().map(|r| r.decompose(beta, l)).collect();
|
||||||
|
// transpose it
|
||||||
|
let r: Vec<Vec<T64>> = (0..elems[0].len())
|
||||||
|
.map(|i| (0..elems.len()).map(|j| elems[j][i]).collect())
|
||||||
|
.collect();
|
||||||
|
// convert it to Tn<N>
|
||||||
|
r.iter().map(|a_i| Self::from_vec(a_i.clone())).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply mod (X^N+1)
|
||||||
|
pub fn modulus<const N: usize>(p: &mut Vec<T64>) {
|
||||||
|
if p.len() < N {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for i in N..p.len() {
|
||||||
|
p[i - N] = p[i - N].clone() - p[i].clone();
|
||||||
|
p[i] = T64::zero();
|
||||||
|
}
|
||||||
|
p.truncate(N);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Add<Tn<N>> for Tn<N> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self {
|
||||||
|
Self(array::from_fn(|i| self.0[i] + rhs.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Add<&Tn<N>> for &Tn<N> {
|
||||||
|
type Output = Tn<N>;
|
||||||
|
|
||||||
|
fn add(self, rhs: &Tn<N>) -> Self::Output {
|
||||||
|
Tn(array::from_fn(|i| self.0[i] + rhs.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> AddAssign for Tn<N> {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
for i in 0..N {
|
||||||
|
self.0[i] += rhs.0[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Sum<Tn<N>> for Tn<N> {
|
||||||
|
fn sum<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: Iterator<Item = Self>,
|
||||||
|
{
|
||||||
|
let mut acc = Tn::<N>::zero();
|
||||||
|
for e in iter {
|
||||||
|
acc += e;
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Sub<Tn<N>> for Tn<N> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self {
|
||||||
|
Self(array::from_fn(|i| self.0[i] - rhs.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Sub<&Tn<N>> for &Tn<N> {
|
||||||
|
type Output = Tn<N>;
|
||||||
|
|
||||||
|
fn sub(self, rhs: &Tn<N>) -> Self::Output {
|
||||||
|
Tn(array::from_fn(|i| self.0[i] - rhs.0[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> SubAssign for Tn<N> {
|
||||||
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
for i in 0..N {
|
||||||
|
self.0[i] -= rhs.0[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> PartialEq for Tn<N> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0 == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Mul<Tn<N>> for Tn<N> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, rhs: Self) -> Self {
|
||||||
|
naive_poly_mul(&self, &rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Mul<&Tn<N>> for &Tn<N> {
|
||||||
|
type Output = Tn<N>;
|
||||||
|
|
||||||
|
fn mul(self, rhs: &Tn<N>) -> Self::Output {
|
||||||
|
naive_poly_mul(self, rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn naive_poly_mul<const N: usize>(poly1: &Tn<N>, poly2: &Tn<N>) -> Tn<N> {
|
||||||
|
let poly1: Vec<u128> = poly1.0.iter().map(|c| c.0 as u128).collect();
|
||||||
|
let poly2: Vec<u128> = poly2.0.iter().map(|c| c.0 as u128).collect();
|
||||||
|
let mut result: Vec<u128> = vec![0; (N * 2) - 1];
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
result[i + j] = result[i + j] + poly1[i] * poly2[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply mod (X^N + 1))
|
||||||
|
modulus_u128::<N>(&mut result);
|
||||||
|
|
||||||
|
// sanity check: check that there are no coeffs > i64_max
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Tn::<N>(array::from_fn(|i| T64(result[i] as u64)))
|
||||||
|
.coeffs()
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.0 as u128)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
Tn(array::from_fn(|i| T64(result[i] as u64)))
|
||||||
|
}
|
||||||
|
fn modulus_u128<const N: usize>(p: &mut Vec<u128>) {
|
||||||
|
if p.len() < N {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for i in N..p.len() {
|
||||||
|
p[i - N] = p[i - N].clone() - p[i].clone();
|
||||||
|
p[i] = 0;
|
||||||
|
}
|
||||||
|
p.truncate(N);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul by u64
|
||||||
|
impl<const N: usize> Mul<u64> for Tn<N> {
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, s: u64) -> Self {
|
||||||
|
Self(array::from_fn(|i| self.0[i] * s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N: usize> Mul<&u64> for &Tn<N> {
|
||||||
|
type Output = Tn<N>;
|
||||||
|
fn mul(self, s: &u64) -> Self::Output {
|
||||||
|
Tn::<N>(array::from_fn(|i| self.0[i] * *s))
|
||||||
|
}
|
||||||
|
}
|
||||||
142
arith/src/torus.rs
Normal file
142
arith/src/torus.rs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
use rand::{distributions::Distribution, Rng};
|
||||||
|
use std::{
|
||||||
|
iter::Sum,
|
||||||
|
ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
impl T64 {
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self(0u64)
|
||||||
|
}
|
||||||
|
pub fn rand(mut rng: impl Rng, dist: impl Distribution<u64>) -> Self {
|
||||||
|
let r: u64 = dist.sample(&mut rng);
|
||||||
|
Self(r)
|
||||||
|
}
|
||||||
|
pub fn rand_f64(mut rng: impl Rng, dist: impl Distribution<f64>) -> Self {
|
||||||
|
let r: f64 = dist.sample(&mut rng);
|
||||||
|
Self(r.round() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Note: only beta=2 and l=64 is supported.
|
||||||
|
pub fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
|
||||||
|
assert_eq!(beta, 2u32); // only beta=2 supported
|
||||||
|
assert_eq!(l, 64u32); // only l=64 supported
|
||||||
|
|
||||||
|
(0..64)
|
||||||
|
.rev()
|
||||||
|
.map(|i| T64(((self.0 >> i) & 1) as u64))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<T64> 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 += rhs.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<T64> 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 -= 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<I>(iter: I) -> Self
|
||||||
|
where
|
||||||
|
I: Iterator<Item = Self>,
|
||||||
|
{
|
||||||
|
iter.fold(Self(0), |acc, x| acc + x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<T64> 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 *= rhs.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mul by u64
|
||||||
|
impl Mul<u64> for T64 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, s: u64) -> Self {
|
||||||
|
Self(self.0 * s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Mul<&u64> for &T64 {
|
||||||
|
type Output = T64;
|
||||||
|
|
||||||
|
fn mul(self, s: &u64) -> Self::Output {
|
||||||
|
T64(self.0 * s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rand::distributions::Standard;
|
||||||
|
|
||||||
|
fn recompose(d: Vec<T64>) -> 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 mut rng = rand::thread_rng();
|
||||||
|
for _ in 0..1000 {
|
||||||
|
let x = T64::rand(&mut rng, Standard);
|
||||||
|
let d = x.decompose(beta, l);
|
||||||
|
assert_eq!(recompose(d), x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use rand::{distributions::Distribution, Rng};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::iter::Sum;
|
|
||||||
use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
|
|
||||||
|
|
||||||
/// Represents a ring element. Currently implemented by ring.rs#R and ringq.rs#Rq.
|
|
||||||
pub trait Ring:
|
|
||||||
Sized
|
|
||||||
+ Add<Output = Self>
|
|
||||||
+ AddAssign
|
|
||||||
+ Sum
|
|
||||||
+ Sub<Output = Self>
|
|
||||||
+ SubAssign
|
|
||||||
+ Mul<Output = Self>
|
|
||||||
+ Mul<u64, Output = Self> // scalar mul
|
|
||||||
+ PartialEq
|
|
||||||
+ Debug
|
|
||||||
+ Clone
|
|
||||||
+ Sum<<Self as Add>::Output>
|
|
||||||
+ Sum<<Self as Mul>::Output>
|
|
||||||
{
|
|
||||||
/// C defines the coefficient type
|
|
||||||
type C: Debug + Clone;
|
|
||||||
|
|
||||||
fn coeffs(&self) -> Vec<Self::C>;
|
|
||||||
fn zero() -> Self;
|
|
||||||
// note/wip/warning: dist (0,q) with f64, will output more '0=q' elements than other values
|
|
||||||
fn rand(rng: impl Rng, dist: impl Distribution<f64>) -> Self;
|
|
||||||
|
|
||||||
fn decompose(&self, beta: u32, l: u32) -> Vec<Self>;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
use anyhow::{anyhow, Result};
|
|
||||||
use rand::{distributions::Distribution, Rng};
|
use rand::{distributions::Distribution, Rng};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops;
|
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
|
||||||
|
|
||||||
/// Z_q, integers modulus q, not necessarily prime
|
/// Z_q, integers modulus q, not necessarily prime
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
@@ -59,7 +58,7 @@ impl<const Q: u64> Zq<Q> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Zq(0u64)
|
Self(0u64)
|
||||||
}
|
}
|
||||||
pub fn square(self) -> Self {
|
pub fn square(self) -> Self {
|
||||||
self * self
|
self * self
|
||||||
@@ -131,8 +130,14 @@ impl<const Q: u64> Zq<Q> {
|
|||||||
Zq::<Q2>::from_u64(((self.0 as f64 * Q2 as f64) / Q as f64).round() as u64)
|
Zq::<Q2>::from_u64(((self.0 as f64 * Q2 as f64) / Q as f64).round() as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO more efficient method for when decomposing with base 2 (beta=2)
|
|
||||||
pub fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
|
pub fn decompose(&self, beta: u32, l: u32) -> Vec<Self> {
|
||||||
|
if beta == 2 {
|
||||||
|
self.decompose_base2(l)
|
||||||
|
} else {
|
||||||
|
self.decompose_base_beta(beta, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn decompose_base_beta(&self, beta: u32, l: u32) -> Vec<Self> {
|
||||||
let mut rem: u64 = self.0;
|
let mut rem: u64 = self.0;
|
||||||
// next if is for cases in which beta does not divide Q (concretely
|
// next if is for cases in which beta does not divide Q (concretely
|
||||||
// beta^l!=Q). round to the nearest multiple of q/beta^l
|
// beta^l!=Q). round to the nearest multiple of q/beta^l
|
||||||
@@ -152,6 +157,41 @@ impl<const Q: u64> Zq<Q> {
|
|||||||
}
|
}
|
||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
/// decompose when beta=2
|
||||||
|
pub fn decompose_base2(&self, l: u32) -> Vec<Self> {
|
||||||
|
// next if is for cases in which beta does not divide Q (concretely
|
||||||
|
// beta^l!=Q). round to the nearest multiple of q/beta^l
|
||||||
|
if self.0 >= 1 << l as u64 {
|
||||||
|
// rem = Q - 1 - (Q / beta as u64); // floor
|
||||||
|
// (where beta=2)
|
||||||
|
return vec![Zq(1); l as usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
(0..l)
|
||||||
|
.rev()
|
||||||
|
.map(|i| Self(((self.0 >> i) & 1) as u64))
|
||||||
|
.collect()
|
||||||
|
|
||||||
|
// naive ver:
|
||||||
|
// let mut rem: u64 = self.0;
|
||||||
|
// // next if is for cases in which beta does not divide Q (concretely
|
||||||
|
// // beta^l!=Q). round to the nearest multiple of q/beta^l
|
||||||
|
// if rem >= 1 << l as u64 {
|
||||||
|
// // rem = Q - 1 - (Q / beta as u64); // floor
|
||||||
|
// return vec![Zq(1); l as usize];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// let mut x: Vec<Self> = vec![];
|
||||||
|
// for i in 1..l + 1 {
|
||||||
|
// let den = Q / (1 << i as u64);
|
||||||
|
// let x_i = rem / den; // division between u64 already does floor
|
||||||
|
// x.push(Self::from_u64(x_i));
|
||||||
|
// if x_i != 0 {
|
||||||
|
// rem = rem % den;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// x
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const Q: u64> Zq<Q> {
|
impl<const Q: u64> Zq<Q> {
|
||||||
@@ -163,7 +203,7 @@ impl<const Q: u64> Zq<Q> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const Q: u64> ops::Add<Zq<Q>> for Zq<Q> {
|
impl<const Q: u64> Add<Zq<Q>> for Zq<Q> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
@@ -174,7 +214,7 @@ impl<const Q: u64> ops::Add<Zq<Q>> for Zq<Q> {
|
|||||||
Zq(r)
|
Zq(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const Q: u64> ops::Add<&Zq<Q>> for &Zq<Q> {
|
impl<const Q: u64> Add<&Zq<Q>> for &Zq<Q> {
|
||||||
type Output = Zq<Q>;
|
type Output = Zq<Q>;
|
||||||
|
|
||||||
fn add(self, rhs: &Zq<Q>) -> Self::Output {
|
fn add(self, rhs: &Zq<Q>) -> Self::Output {
|
||||||
@@ -185,7 +225,7 @@ impl<const Q: u64> ops::Add<&Zq<Q>> for &Zq<Q> {
|
|||||||
Zq(r)
|
Zq(r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const Q: u64> ops::AddAssign<Zq<Q>> for Zq<Q> {
|
impl<const Q: u64> AddAssign<Zq<Q>> for Zq<Q> {
|
||||||
fn add_assign(&mut self, rhs: Self) {
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
*self = *self + rhs
|
*self = *self + rhs
|
||||||
}
|
}
|
||||||
@@ -198,7 +238,7 @@ impl<const Q: u64> std::iter::Sum for Zq<Q> {
|
|||||||
iter.fold(Zq(0), |acc, x| acc + x)
|
iter.fold(Zq(0), |acc, x| acc + x)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const Q: u64> ops::Sub<Zq<Q>> for Zq<Q> {
|
impl<const Q: u64> Sub<Zq<Q>> for Zq<Q> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn sub(self, rhs: Self) -> Zq<Q> {
|
fn sub(self, rhs: Self) -> Zq<Q> {
|
||||||
@@ -209,7 +249,7 @@ impl<const Q: u64> ops::Sub<Zq<Q>> for Zq<Q> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const Q: u64> ops::Sub<&Zq<Q>> for &Zq<Q> {
|
impl<const Q: u64> Sub<&Zq<Q>> for &Zq<Q> {
|
||||||
type Output = Zq<Q>;
|
type Output = Zq<Q>;
|
||||||
|
|
||||||
fn sub(self, rhs: &Zq<Q>) -> Self::Output {
|
fn sub(self, rhs: &Zq<Q>) -> Self::Output {
|
||||||
@@ -220,19 +260,19 @@ impl<const Q: u64> ops::Sub<&Zq<Q>> for &Zq<Q> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const Q: u64> ops::SubAssign<Zq<Q>> for Zq<Q> {
|
impl<const Q: u64> SubAssign<Zq<Q>> for Zq<Q> {
|
||||||
fn sub_assign(&mut self, rhs: Self) {
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
*self = *self - rhs
|
*self = *self - rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const Q: u64> ops::Neg for Zq<Q> {
|
impl<const Q: u64> Neg for Zq<Q> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn neg(self) -> Self::Output {
|
fn neg(self) -> Self::Output {
|
||||||
Zq(Q - self.0)
|
Zq(Q - self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const Q: u64> ops::Mul<Zq<Q>> for Zq<Q> {
|
impl<const Q: u64> Mul<Zq<Q>> for Zq<Q> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn mul(self, rhs: Self) -> Zq<Q> {
|
fn mul(self, rhs: Self) -> Zq<Q> {
|
||||||
@@ -241,7 +281,7 @@ impl<const Q: u64> ops::Mul<Zq<Q>> for Zq<Q> {
|
|||||||
// Zq((self.0 * rhs.0) % Q)
|
// Zq((self.0 * rhs.0) % Q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<const Q: u64> ops::Div<Zq<Q>> for Zq<Q> {
|
impl<const Q: u64> Div<Zq<Q>> for Zq<Q> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn div(self, rhs: Self) -> Zq<Q> {
|
fn div(self, rhs: Self) -> Zq<Q> {
|
||||||
@@ -313,9 +353,8 @@ mod tests {
|
|||||||
|
|
||||||
for _ in 0..1000 {
|
for _ in 0..1000 {
|
||||||
let x = Zq::<Q>::from_u64(dist.sample(&mut rng));
|
let x = Zq::<Q>::from_u64(dist.sample(&mut rng));
|
||||||
|
|
||||||
let d = x.decompose(beta, l);
|
let d = x.decompose(beta, l);
|
||||||
|
assert_eq!(d.len(), l as usize);
|
||||||
assert_eq!(recompose::<Q>(beta, l, d), x);
|
assert_eq!(recompose::<Q>(beta, l, d), x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,6 +366,7 @@ mod tests {
|
|||||||
let l: u32 = 4;
|
let l: u32 = 4;
|
||||||
let x = Zq::<Q>::from_u64(16); // in q, but bigger than beta^l
|
let x = Zq::<Q>::from_u64(16); // in q, but bigger than beta^l
|
||||||
let d = x.decompose(beta, l);
|
let d = x.decompose(beta, l);
|
||||||
|
assert_eq!(d.len(), l as usize);
|
||||||
assert_eq!(recompose::<Q>(beta, l, d), Zq(15));
|
assert_eq!(recompose::<Q>(beta, l, d), Zq(15));
|
||||||
|
|
||||||
const Q2: u64 = 5u64.pow(3) + 1;
|
const Q2: u64 = 5u64.pow(3) + 1;
|
||||||
@@ -334,6 +374,7 @@ mod tests {
|
|||||||
let l: u32 = 3;
|
let l: u32 = 3;
|
||||||
let x = Zq::<Q2>::from_u64(125); // in q, but bigger than beta^l
|
let x = Zq::<Q2>::from_u64(125); // in q, but bigger than beta^l
|
||||||
let d = x.decompose(beta, l);
|
let d = x.decompose(beta, l);
|
||||||
|
assert_eq!(d.len(), l as usize);
|
||||||
assert_eq!(recompose::<Q2>(beta, l, d), Zq(124));
|
assert_eq!(recompose::<Q2>(beta, l, d), Zq(124));
|
||||||
|
|
||||||
const Q3: u64 = 2u64.pow(16) + 1;
|
const Q3: u64 = 2u64.pow(16) + 1;
|
||||||
@@ -341,6 +382,7 @@ mod tests {
|
|||||||
let l: u32 = 16;
|
let l: u32 = 16;
|
||||||
let x = Zq::<Q3>::from_u64(Q3 - 1); // in q, but bigger than beta^l
|
let x = Zq::<Q3>::from_u64(Q3 - 1); // in q, but bigger than beta^l
|
||||||
let d = x.decompose(beta, l);
|
let d = x.decompose(beta, l);
|
||||||
|
assert_eq!(d.len(), l as usize);
|
||||||
assert_eq!(recompose::<Q3>(beta, l, d), Zq(beta.pow(l) as u64 - 1));
|
assert_eq!(recompose::<Q3>(beta, l, d), Zq(beta.pow(l) as u64 - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ impl<const Q: u64, const N: usize> RLWE<Q, N> {
|
|||||||
// tensor (\in R) (2021-204 p.9)
|
// tensor (\in R) (2021-204 p.9)
|
||||||
// NOTE: here can use *, but at first versions want to make it explicit
|
// NOTE: here can use *, but at first versions want to make it explicit
|
||||||
// that we're using the naive mul. TODO use *.
|
// that we're using the naive mul. TODO use *.
|
||||||
use arith::ring::naive_mul;
|
use arith::ring_n::naive_mul;
|
||||||
let c0: Vec<i64> = naive_mul(&a0, &b0);
|
let c0: Vec<i64> = naive_mul(&a0, &b0);
|
||||||
let c1_l: Vec<i64> = naive_mul(&a0, &b1);
|
let c1_l: Vec<i64> = naive_mul(&a0, &b1);
|
||||||
let c1_r = naive_mul(&a1, &b0);
|
let c1_r = naive_mul(&a1, &b0);
|
||||||
@@ -60,9 +60,9 @@ impl<const Q: u64, const N: usize> RLWE<Q, N> {
|
|||||||
let c2: Vec<i64> = naive_mul(&a1, &b1);
|
let c2: Vec<i64> = naive_mul(&a1, &b1);
|
||||||
|
|
||||||
// scale down, then reduce module Q, so result is \in R_q
|
// scale down, then reduce module Q, so result is \in R_q
|
||||||
let c0: Rq<Q, N> = arith::ring::mul_div_round::<Q, N>(c0, T, Q);
|
let c0: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c0, T, Q);
|
||||||
let c1: Rq<Q, N> = arith::ring::mul_div_round::<Q, N>(c1, T, Q);
|
let c1: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c1, T, Q);
|
||||||
let c2: Rq<Q, N> = arith::ring::mul_div_round::<Q, N>(c2, T, Q);
|
let c2: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c2, T, Q);
|
||||||
|
|
||||||
(c0, c1, c2)
|
(c0, c1, c2)
|
||||||
}
|
}
|
||||||
@@ -72,9 +72,9 @@ impl<const Q: u64, const N: usize> RLWE<Q, N> {
|
|||||||
BFV::<Q, N, T>::relinearize_204::<PQ>(&rlk, &c0, &c1, &c2)
|
BFV::<Q, N, T>::relinearize_204::<PQ>(&rlk, &c0, &c1, &c2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// naive mul in the ring Rq, reusing the ring::naive_mul and then applying mod(X^N +1)
|
// naive mul in the ring Rq, reusing the ring_n::naive_mul and then applying mod(X^N +1)
|
||||||
fn tmp_naive_mul<const Q: u64, const N: usize>(a: Rq<Q, N>, b: Rq<Q, N>) -> Rq<Q, N> {
|
fn tmp_naive_mul<const Q: u64, const N: usize>(a: Rq<Q, N>, b: Rq<Q, N>) -> Rq<Q, N> {
|
||||||
Rq::<Q, N>::from_vec_i64(arith::ring::naive_mul(&a.to_r(), &b.to_r()))
|
Rq::<Q, N>::from_vec_i64(arith::ring_n::naive_mul(&a.to_r(), &b.to_r()))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const Q: u64, const N: usize> ops::Add<RLWE<Q, N>> for RLWE<Q, N> {
|
impl<const Q: u64, const N: usize> ops::Add<RLWE<Q, N>> for RLWE<Q, N> {
|
||||||
@@ -139,7 +139,7 @@ impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
|
|||||||
let cs = c.0 + c.1 * sk.0; // done in mod q
|
let cs = c.0 + c.1 * sk.0; // done in mod q
|
||||||
|
|
||||||
// same but with naive_mul:
|
// same but with naive_mul:
|
||||||
// let c1s = arith::ring::naive_mul(&c.1.to_r(), &sk.0.to_r());
|
// let c1s = arith::ring_n::naive_mul(&c.1.to_r(), &sk.0.to_r());
|
||||||
// let c1s = Rq::<Q, N>::from_vec_i64(c1s);
|
// let c1s = Rq::<Q, N>::from_vec_i64(c1s);
|
||||||
// let cs = c.0 + c1s;
|
// let cs = c.0 + c1s;
|
||||||
|
|
||||||
@@ -219,11 +219,11 @@ impl<const Q: u64, const N: usize, const T: u64> BFV<Q, N, T> {
|
|||||||
// let r0: Rq<Q, N> = c2rlk0.mul_div_round(1, P).remodule::<Q>();
|
// let r0: Rq<Q, N> = c2rlk0.mul_div_round(1, P).remodule::<Q>();
|
||||||
// let r1: Rq<Q, N> = c2rlk1.mul_div_round(1, P).remodule::<Q>();
|
// let r1: Rq<Q, N> = c2rlk1.mul_div_round(1, P).remodule::<Q>();
|
||||||
|
|
||||||
use arith::ring::naive_mul;
|
use arith::ring_n::naive_mul;
|
||||||
let c2rlk0: Vec<i64> = naive_mul(&c2.to_r(), &rlk.0.to_r());
|
let c2rlk0: Vec<i64> = naive_mul(&c2.to_r(), &rlk.0.to_r());
|
||||||
let c2rlk1: Vec<i64> = naive_mul(&c2.to_r(), &rlk.1.to_r());
|
let c2rlk1: Vec<i64> = naive_mul(&c2.to_r(), &rlk.1.to_r());
|
||||||
let r0: Rq<Q, N> = arith::ring::mul_div_round::<Q, N>(c2rlk0, 1, P);
|
let r0: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c2rlk0, 1, P);
|
||||||
let r1: Rq<Q, N> = arith::ring::mul_div_round::<Q, N>(c2rlk1, 1, P);
|
let r1: Rq<Q, N> = arith::ring_n::mul_div_round::<Q, N>(c2rlk1, 1, P);
|
||||||
|
|
||||||
let res = RLWE::<Q, N>(c0 + &r0, c1 + &r1);
|
let res = RLWE::<Q, N>(c0 + &r0, c1 + &r1);
|
||||||
res
|
res
|
||||||
@@ -294,7 +294,7 @@ mod tests {
|
|||||||
fn test_constant_add_mul() -> Result<()> {
|
fn test_constant_add_mul() -> Result<()> {
|
||||||
const Q: u64 = 2u64.pow(16) + 1;
|
const Q: u64 = 2u64.pow(16) + 1;
|
||||||
const N: usize = 16;
|
const N: usize = 16;
|
||||||
const T: u64 = 16; // plaintext modulus
|
const T: u64 = 8; // plaintext modulus
|
||||||
type S = BFV<Q, N, T>;
|
type S = BFV<Q, N, T>;
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
@@ -488,10 +488,10 @@ mod tests {
|
|||||||
// decrypt non-relinearized mul result
|
// decrypt non-relinearized mul result
|
||||||
let m3: Rq<Q, N> = c_a + c_b * sk.0 + c_c * sk.0 * sk.0;
|
let m3: Rq<Q, N> = c_a + c_b * sk.0 + c_c * sk.0 * sk.0;
|
||||||
// let m3: Rq<Q, N> = c_a
|
// let m3: Rq<Q, N> = c_a
|
||||||
// + Rq::<Q, N>::from_vec_i64(arith::ring::naive_mul(&c_b.to_r(), &sk.0.to_r()))
|
// + Rq::<Q, N>::from_vec_i64(arith::ring_n::naive_mul(&c_b.to_r(), &sk.0.to_r()))
|
||||||
// + Rq::<Q, N>::from_vec_i64(arith::ring::naive_mul(
|
// + Rq::<Q, N>::from_vec_i64(arith::ring_n::naive_mul(
|
||||||
// &c_c.to_r(),
|
// &c_c.to_r(),
|
||||||
// &R::<N>::from_vec(arith::ring::naive_mul(&sk.0.to_r(), &sk.0.to_r())),
|
// &R::<N>::from_vec(arith::ring_n::naive_mul(&sk.0.to_r(), &sk.0.to_r())),
|
||||||
// ));
|
// ));
|
||||||
let m3: Rq<Q, N> = m3.mul_div_round(T, Q); // descale
|
let m3: Rq<Q, N> = m3.mul_div_round(T, Q); // descale
|
||||||
let m3 = m3.remodule::<T>();
|
let m3 = m3.remodule::<T>();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use arith::{Matrix, Rq, C, R};
|
use arith::{Matrix, Ring, Rq, C, R};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SecretKey<const Q: u64, const N: usize>(Rq<Q, N>);
|
pub struct SecretKey<const Q: u64, const N: usize>(Rq<Q, N>);
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ mod tests {
|
|||||||
fn test_encode_encrypt_decrypt_decode() -> Result<()> {
|
fn test_encode_encrypt_decrypt_decode() -> Result<()> {
|
||||||
const Q: u64 = 2u64.pow(16) + 1;
|
const Q: u64 = 2u64.pow(16) + 1;
|
||||||
const N: usize = 16;
|
const N: usize = 16;
|
||||||
const T: u64 = 16;
|
const T: u64 = 8;
|
||||||
let scale_factor = C::<f64>::new(512.0, 0.0); // delta
|
let scale_factor = C::<f64>::new(512.0, 0.0); // delta
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
@@ -202,7 +202,7 @@ mod tests {
|
|||||||
fn test_add() -> Result<()> {
|
fn test_add() -> Result<()> {
|
||||||
const Q: u64 = 2u64.pow(16) + 1;
|
const Q: u64 = 2u64.pow(16) + 1;
|
||||||
const N: usize = 16;
|
const N: usize = 16;
|
||||||
const T: u64 = 10;
|
const T: u64 = 8;
|
||||||
let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
|
let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
@@ -245,7 +245,7 @@ mod tests {
|
|||||||
fn test_sub() -> Result<()> {
|
fn test_sub() -> Result<()> {
|
||||||
const Q: u64 = 2u64.pow(16) + 1;
|
const Q: u64 = 2u64.pow(16) + 1;
|
||||||
const N: usize = 16;
|
const N: usize = 16;
|
||||||
const T: u64 = 10;
|
const T: u64 = 8;
|
||||||
let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
|
let scale_factor = C::<f64>::new(1024.0, 0.0); // delta
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|||||||
Reference in New Issue
Block a user