adapt gfhe to work with Ring trait, so that it can work with Rq & Tn (for TFHE)

This commit is contained in:
2025-07-24 14:26:48 +00:00
parent 4790fdbb3b
commit 0bee7582db
10 changed files with 428 additions and 185 deletions

View File

@@ -3,7 +3,10 @@ use std::fmt::Debug;
use std::iter::Sum;
use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
/// Represents a ring element. Currently implemented by ring_n.rs#R and ring_nq.rs#Rq.
/// Represents a ring element. Currently implemented by ring_n.rs#R and
/// ring_nq.rs#Rq. Is not a 'pure algebraic ring', but more a custom trait
/// definition which includes methods like `mod_switch`.
// assumed to be mod (X^N +1)
pub trait Ring:
Sized
+ Add<Output = Self>
@@ -11,17 +14,22 @@ pub trait Ring:
+ Sum
+ Sub<Output = Self>
+ SubAssign
+ Mul<Output = Self>
+ Mul<u64, Output = Self> // scalar mul
+ Mul<Output = Self> // internal product
+ Mul<u64, Output = Self> // scalar mul, external product
+ Mul<Self::C, Output = Self>
+ PartialEq
+ Debug
+ Clone
+ Copy
+ Sum<<Self as Add>::Output>
+ Sum<<Self as Mul>::Output>
{
/// C defines the coefficient type
type C: Debug + Clone;
const Q: u64;
const N: usize;
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
@@ -31,6 +39,9 @@ pub trait Ring:
fn decompose(&self, beta: u32, l: u32) -> Vec<Self>;
fn remodule<const P: u64>(&self) -> impl Ring;
fn mod_switch<const P: u64>(&self) -> impl Ring;
/// returns [ [(num/den) * self].round() ] mod q
/// ie. performs the multiplication and division over f64, and then it
/// rounds the result, only applying the mod Q (if the ring is mod Q) at the

View File

@@ -15,9 +15,13 @@ use crate::Ring;
#[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> {
// impl<const N: usize> Ring for R<N> {
impl<const N: usize> R<N> {
// type C = i64;
// const Q: u64 = i64::MAX as u64; // WIP
// const N: usize = N;
pub fn coeffs(&self) -> Vec<i64> {
self.0.to_vec()
}
fn zero() -> Self {
@@ -32,30 +36,39 @@ impl<const N: usize> Ring for R<N> {
// Self(coeffs)
}
fn from_vec(coeffs: Vec<Self::C>) -> Self {
pub fn from_vec(coeffs: Vec<i64>) -> 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))
}
// performs the multiplication and division over f64, and then it rounds the
// result, only applying the mod Q at the end
fn remodule<const P: u64>(&self) -> impl Ring {
unimplemented!()
}
fn mod_switch<const P: u64, const M: usize>(&self) -> R<N> {
unimplemented!()
}
/// performs the multiplication and division over f64, and then it rounds the
/// result, only applying the mod Q at the end
fn mul_div_round(&self, num: u64, den: u64) -> Self {
unimplemented!()
// 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)
// 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)
}
*/
}
impl<const Q: u64, const N: usize> From<crate::ring_nq::Rq<Q, N>> for R<N> {
@@ -65,9 +78,9 @@ impl<const Q: u64, const N: usize> From<crate::ring_nq::Rq<Q, N>> for R<N> {
}
impl<const N: usize> R<N> {
pub fn coeffs(&self) -> [i64; N] {
self.0
}
// 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)
}
@@ -318,6 +331,13 @@ pub fn mod_centered_q<const Q: u64, const N: usize>(p: Vec<i128>) -> R<N> {
R::<N>::from_vec(r.iter().map(|v| *v as i64).collect::<Vec<i64>>())
}
impl<const N: usize> Mul<i64> for R<N> {
type Output = Self;
fn mul(self, s: i64) -> Self {
self.mul_by_i64(s)
}
}
// mul by u64
impl<const N: usize> Mul<u64> for R<N> {
type Output = Self;

View File

@@ -30,6 +30,9 @@ pub struct Rq<const Q: u64, const N: usize> {
impl<const Q: u64, const N: usize> Ring for Rq<Q, N> {
type C = Zq<Q>;
const Q: u64 = Q;
const N: usize = N;
fn coeffs(&self) -> Vec<Self::C> {
self.coeffs.to_vec()
}
@@ -71,9 +74,26 @@ impl<const Q: u64, const N: usize> Ring for Rq<Q, N> {
r.iter().map(|a_i| Self::from_vec(a_i.clone())).collect()
}
// returns [ [(num/den) * self].round() ] mod q
// ie. performs the multiplication and division over f64, and then it rounds the
// result, only applying the mod Q at the end
// Warning: this method will behave differently depending on the values P and Q:
// if Q<P, it just 'renames' the modulus parameter to P
// if Q>=P, it crops to mod P
fn remodule<const P: u64>(&self) -> Rq<P, N> {
Rq::<P, N>::from_vec_u64(self.coeffs().iter().map(|m_i| m_i.0).collect())
}
/// perform the mod switch operation from Q to Q', where Q2=Q'
// fn mod_switch<const P: u64, const M: usize>(&self) -> impl Ring {
fn mod_switch<const P: u64>(&self) -> Rq<P, N> {
// assert_eq!(N, M); // sanity check
Rq::<P, N> {
coeffs: array::from_fn(|i| self.coeffs[i].mod_switch::<P>()),
evals: None,
}
}
/// returns [ [(num/den) * self].round() ] mod q
/// ie. performs the multiplication and division over f64, and then it rounds the
/// result, only applying the mod Q at the end
fn mul_div_round(&self, num: u64, den: u64) -> Self {
let r: Vec<f64> = self
.coeffs()
@@ -183,17 +203,9 @@ impl<const Q: u64, const N: usize> Rq<Q, N> {
// Warning: this method will behave differently depending on the values P and Q:
// if Q<P, it just 'renames' the modulus parameter to P
// if Q>=P, it crops to mod P
pub fn remodule<const P: u64>(&self) -> Rq<P, N> {
Rq::<P, N>::from_vec_u64(self.coeffs().iter().map(|m_i| m_i.0).collect())
}
/// perform the mod switch operation from Q to Q', where Q2=Q'
pub fn mod_switch<const Q2: u64>(&self) -> Rq<Q2, N> {
Rq::<Q2, N> {
coeffs: array::from_fn(|i| self.coeffs[i].mod_switch::<Q2>()),
evals: None,
}
}
// pub fn remodule<const P: u64>(&self) -> Rq<P, N> {
// Rq::<P, N>::from_vec_u64(self.coeffs().iter().map(|m_i| m_i.0).collect())
// }
// applies mod(T) to all coefficients of self
pub fn coeffs_mod<const T: u64>(&self) -> Self {

View File

@@ -12,7 +12,7 @@ use std::array;
use std::iter::Sum;
use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
use crate::{ring::Ring, torus::T64};
use crate::{ring::Ring, torus::T64, Rq, Zq};
/// 𝕋_<N,Q>[X] = 𝕋<Q>[X]/(X^N +1), polynomials modulo X^N+1 with coefficients in
/// 𝕋, where Q=2^64.
@@ -22,6 +22,9 @@ pub struct Tn<const N: usize>(pub [T64; N]);
impl<const N: usize> Ring for Tn<N> {
type C = T64;
const Q: u64 = u64::MAX; // WIP
const N: usize = N;
fn coeffs(&self) -> Vec<T64> {
self.0.to_vec()
}
@@ -50,6 +53,22 @@ impl<const N: usize> Ring for Tn<N> {
r.iter().map(|a_i| Self::from_vec(a_i.clone())).collect()
}
fn remodule<const P: u64>(&self) -> Tn<N> {
todo!()
// Rq::<P, N>::from_vec_u64(self.coeffs().iter().map(|m_i| m_i.0).collect())
}
// fn mod_switch<const P: u64>(&self) -> impl Ring {
fn mod_switch<const P: u64>(&self) -> Rq<P, N> {
// unimplemented!()
// TODO WIP
let coeffs = array::from_fn(|i| Zq::<P>::from_u64(self.0[i].mod_switch::<P>().0));
Rq::<P, N> {
coeffs,
evals: None,
}
}
/// returns [ [(num/den) * self].round() ] mod q
/// ie. performs the multiplication and division over f64, and then it rounds the
/// result, only applying the mod Q at the end
@@ -174,12 +193,19 @@ fn modulus_u128<const N: usize>(p: &mut Vec<u128>) {
return;
}
for i in N..p.len() {
p[i - N] = p[i - N].clone() - p[i].clone();
// p[i - N] = p[i - N].clone() - p[i].clone();
p[i - N] = p[i - N].wrapping_sub(p[i]);
p[i] = 0;
}
p.truncate(N);
}
impl<const N: usize> Mul<T64> for Tn<N> {
type Output = Self;
fn mul(self, s: T64) -> Self {
Self(array::from_fn(|i| self.0[i] * s))
}
}
// mul by u64
impl<const N: usize> Mul<u64> for Tn<N> {
type Output = Self;

View File

@@ -33,6 +33,9 @@ impl T64 {
.map(|i| T64(((self.0 >> i) & 1) as u64))
.collect()
}
pub fn mod_switch<const Q2: u64>(&self) -> T64 {
todo!()
}
}
impl Add<T64> for T64 {