@ -10,12 +10,13 @@ use rand::Rng;
use rand_distr ::{ Normal , Uniform } ;
use std ::ops ;
use arithmetic ::{ Rq , Zq , R } ;
use arithmetic ::{ Rq , R } ;
// error deviation for the Gaussian(Normal) distribution
// sigma=3.2 from: https://eprint.iacr.org/2022/162.pdf page 5
const ERR_SIGMA : f64 = 3.2 ;
// const ERR_SIGMA: f64 = 0.0; // TODO WIP
// const ERR_SIGMA: f64 = 1.0; // TODO WIP
#[ derive(Clone, Debug) ]
pub struct SecretKey < const Q : u64 , const N : usize > ( Rq < Q , N > ) ;
@ -48,21 +49,23 @@ impl RLWE {
}
fn tensor < const PQ : u64 , const T : u64 > ( a : & Self , b : & Self ) -> ( Rq < Q , N > , Rq < Q , N > , Rq < Q , N > ) {
// expand Q->PQ // TODO rm
// get the coefficients in Z, ie. interpret a,b \in R (instead of R_q)
let a0 : R < N > = a . 0. to_r ( ) ;
let a1 : R < N > = a . 1. to_r ( ) ;
let b0 : R < N > = b . 0. to_r ( ) ;
let b1 : R < N > = b . 1. to_r ( ) ;
// tensor (\in R)
// tensor (\in R) (2021-204 p.9)
use arithmetic ::ring ::naive_mul ;
// (here can use *, but want to make it explicit that we're using the naive mul)
let c0 : Vec < i64 > = naive_mul ( & a0 , & b0 ) ;
let c1_l : Vec < i64 > = naive_mul ( & a0 , & b1 ) ;
let c1_r = naive_mul ( & a1 , & b0 ) ;
let c1 : Vec < i64 > = itertools ::zip_eq ( c1_l , c1_r ) . map ( | ( l , r ) | l + r ) . collect ( ) ;
let c2 : Vec < i64 > = naive_mul ( & a1 , & b1 ) ;
// scale down, 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 > = arithmetic ::ring ::mul_div_round ::< Q , N > ( c0 , T , Q ) ;
let c1 : Rq < Q , N > = arithmetic ::ring ::mul_div_round ::< Q , N > ( c1 , T , Q ) ;
let c2 : Rq < Q , N > = arithmetic ::ring ::mul_div_round ::< Q , N > ( c2 , T , Q ) ;
@ -119,76 +122,73 @@ impl ops::Add<&Rq> for &RLWE
BFV ::< Q , N , T > ::add_const ( self , rhs )
}
}
impl < const Q : u64 , const N : usize , const T : u64 > ops ::Mul < & Rq < T , N > > for & RLWE < Q , N > {
type Output = RLWE < Q , N > ;
fn mul ( self , rhs : & Rq < T , N > ) -> Self ::Output {
BFV ::< Q , N , T > ::mul_const ( & self , rhs )
}
}
// impl<const Q: u64, const N: usize, const T: u64> ops::Mul<&Rq<T, N>> for &RLWE<Q, N> {
// type Output = RLWE<Q, N>;
// fn mul(self, rhs: &Rq<T, N>) -> Self::Output {
// BFV::<Q, N, T>::mul_const(&self, rhs)
// }
// }
pub struct BFV < const Q : u64 , const N : usize , const T : u64 > { }
impl < const Q : u64 , const N : usize , const T : u64 > BFV < Q , N , T > {
const DELTA : u64 = Q / T ;
const DELTA : u64 = Q / T ; // floor
/// generate a new key pair (privK, pubK)
pub fn new_key ( mut rng : impl Rng ) -> Result < ( SecretKey < Q , N > , PublicKey < Q , N > ) > {
// WIP: review probabilities
// let Xi_key = Uniform::new(-1_f64, 1_f64);
let Xi_key = Uniform ::new ( 0_ u64 , 2_ u64 ) ;
let Xi_key = Uniform ::new ( - 1_ f64 , 1_ f64 ) ;
// let Xi_key = Uniform::new(0_u64, 2_u64);
// use rand::distributions::Bernoulli;
// let Xi_key = Bernoulli::new(0.5)?;
let Xi_err = Normal ::new ( 0_ f64 , ERR_SIGMA ) ? ;
// let Xi_err = Normal::new(0_f64, 0.0)?;
// secret key
let s = Rq ::< Q , N > ::rand_u64 ( & mut rng , Xi_key ) ? ;
let s = Rq ::< Q , N > ::rand_f64 ( & mut rng , Xi_key ) ? ;
// let s = Rq::<Q, N>::rand_u64(&mut rng, Xi_key)?;
#[ cfg(test) ] // sanity check
assert ! ( s . infinity_norm ( ) < = 1 , "{:?}" , s . coeffs ( ) ) ;
// #[cfg(test)] // sanity check
// assert!(
// s.infinity_norm() <= 1,
// "s.infinity_norm check failed {:?}",
// s.coeffs()
// );
// pk = (-a * s + e, a)
let a = Rq ::< Q , N > ::rand_u64 ( & mut rng , Uniform ::new ( 0_ u64 , Q ) ) ? ;
let e = Rq ::< Q , N > ::rand_f64 ( & mut rng , Xi_err ) ? ;
// println!("e{:?}", e.coeffs());
let pk : PublicKey < Q , N > = PublicKey ( ( & ( - a ) * & s ) + e , a . clone ( ) ) ;
Ok ( ( SecretKey ( s ) , pk ) )
}
pub fn encrypt ( mut rng : impl Rng , pk : & PublicKey < Q , N > , m : & Rq < T , N > ) -> Result < RLWE < Q , N > > {
// let Xi_key = Uniform::new(-1_f64, 1_f64);
// let Xi_key = Uniform::new(0_f64, 2_f64);
let Xi_key = Uniform ::new ( 0_ u64 , 2_ u64 ) ;
let Xi_key = Uniform ::new ( - 1_ f64 , 1_ f64 ) ;
// let Xi_key = Uniform::new(0_u64, 2_u64);
let Xi_err = Normal ::new ( 0_ f64 , ERR_SIGMA ) ? ;
let u = Rq ::< Q , N > ::rand_u64 ( & mut rng , Xi_key ) ? ;
let e_1 = Rq ::< Q , N > ::rand_f64_abs ( & mut rng , Xi_err ) ? ;
let e_2 = Rq ::< Q , N > ::rand_f64_abs ( & mut rng , Xi_err ) ? ;
// println!("{:?}", &e_1.coeffs());
// println!("{:?}", &e_2.coeffs());
#[ cfg(test) ] // sanity check
assert ! ( u . infinity_norm ( ) < = 1 , "{:?}" , u . coeffs ( ) ) ;
// let Xi_err = Normal::new(0_f64, 0.0)?;
let u = Rq ::< Q , N > ::rand_f64 ( & mut rng , Xi_key ) ? ;
// let u = Rq::<Q, N>::rand_u64(&mut rng, Xi_key)?;
let e_1 = Rq ::< Q , N > ::rand_f64 ( & mut rng , Xi_err ) ? ;
let e_2 = Rq ::< Q , N > ::rand_f64 ( & mut rng , Xi_err ) ? ;
// println!("e_1{:?}", e_1.coeffs());
// println!("e_2{:?}", e_2.coeffs());
// #[cfg(test)] // sanity check
// assert!(
// u.infinity_norm() <= 1,
// "u.infinity_norm check failed {:?}",
// u.coeffs()
// );
// migrate m's coeffs to the bigger modulus Q (from T)
let m = m . remodule ::< Q > ( ) ;
#[ cfg(test) ]
{
// sanity check // TODO rm
let m_remod_naive =
Rq ::< Q , N > ::from_vec_u64 ( m . coeffs ( ) . iter ( ) . map ( | m_i | m_i . 0 ) . collect ( ) ) ;
assert_eq ! ( m_remod_naive , m ) ;
}
// let c0 = &pk.0 * &u + e_1 + m * Self::DELTA;
let c0 = & pk . 0 * & u + e_1 + m . mul_div_round ( Q , T ) ; // TODO use DELTA?
// let c0 = &pk.0 * &u + e_1 + m.mul_div_round(Q, T);
let c0 = & pk . 0 * & u + e_1 + m * Self ::DELTA ;
// let D: u64 = (Q as f64 / T as f64).floor() as u64;
// let c0 = &pk.0 * &u + e_1 + m * D; // TODO use DELTA?
let c1 = & pk . 1 * & u + e_2 ;
// let c0 = tmp_naive_mul(pk.0, u) + e_1 + m * Self::DELTA;
// let c0 = tmp_naive_mul(pk.0, u) + e_1 + m.mul_div_round(Q, T);
// let c1 = tmp_naive_mul(pk.1, u)
// // &pk.1 * &u
// + e_2;
Ok ( RLWE ::< Q , N > ( c0 , c1 ) )
}
@ -216,10 +216,13 @@ impl BFV {
let m = m . remodule ::< Q > ( ) ;
RLWE ::< Q , N > ( c . 0 + m * Self ::DELTA , c . 1 )
}
fn mul_const ( c : & RLWE < Q , N > , m : & Rq < T , N > ) -> RLWE < Q , N > {
fn mul_const < const PQ : u64 > ( rlk : & RLK < PQ , N > , c : & RLWE < Q , N > , m : & Rq < T , N > ) -> RLWE < Q , N > {
// assuming T<Q, move m from Zq<T> to Zq<Q>
let m = m . remodule ::< Q > ( ) ;
RLWE ::< Q , N > ( c . 0 * m * Self ::DELTA , c . 1 )
// encrypt m*Delta without noise, and then perform normal ciphertext multiplication
let md = RLWE ::< Q , N > ( m * Self ::DELTA , Rq ::zero ( ) ) ;
RLWE ::< Q , N > ::mul ::< PQ , T > ( & rlk , & c , & md )
}
fn rlk_key < const PQ : u64 > ( mut rng : impl Rng , s : & SecretKey < Q , N > ) -> Result < RLK < PQ , N > > {
@ -232,11 +235,11 @@ impl BFV {
// let rlk_1: Rq<PQ, N> = (&(-a) * &s) - e + (s * s) * P;
let P = PQ / Q ;
// let rlk: RLK<PQ, N> = RLK::<PQ, N>((&(-a) * &s) - e + (s * s) * P, a.clone());
let rlk : RLK < PQ , N > = RLK ::< PQ , N > (
- ( tmp_naive_mul ( a , s ) + e ) + tmp_naive_mul ( s , s ) * P ,
a . clone ( ) ,
) ;
let rlk : RLK < PQ , N > = RLK ::< PQ , N > ( ( & ( - a ) * & s ) - e + ( s * s ) * P , a . clone ( ) ) ;
// let rlk: RLK<PQ, N> = RLK::<PQ, N>(
// -(tmp_naive_mul(a, s) + e) + tmp_naive_mul(s, s) * P,
// a.clone(),
// );
// let rlk: RLK<PQ, N> = RLK::<PQ, N>(-(&a * &s + e) + (s * s) * P, a.clone());
// let pq = P * Q;
@ -292,17 +295,8 @@ impl BFV {
// let c2rlk0: Vec<f64> = (c2.remodule::<PQ>() * rlk.0)
use arithmetic ::ring ::naive_mul ;
let c2rlk0 : Vec < i64 > = naive_mul ( & c2 . to_r ( ) , & rlk . 0. to_r ( ) ) ;
// .coeffs()
// .iter()
// .map(|e| (*e as f64 / P as f64).round())
// .collect();
let c2rlk1 : Vec < i64 > = naive_mul ( & c2 . to_r ( ) , & rlk . 1. to_r ( ) ) ;
// .coeffs()
// .iter()
// .map(|e| (*e as f64 / P as f64).round())
// .collect();
//
let r0 : Rq < Q , N > = arithmetic ::ring ::mul_div_round ::< Q , N > ( c2rlk0 , 1 , P ) ;
let r1 : Rq < Q , N > = arithmetic ::ring ::mul_div_round ::< Q , N > ( c2rlk1 , 1 , P ) ;
@ -348,36 +342,37 @@ mod tests {
#[ test ]
fn test_addition ( ) -> Result < ( ) > {
const Q : u64 = 2 u64 . pow ( 16 ) + 1 ;
// const N: usize = 32;
const N : usize = 4 ;
const N : usize = 32 ;
const T : u64 = 4 ; // plaintext modulus
type S = BFV < Q , N , T > ;
let mut rng = rand ::thread_rng ( ) ;
let ( sk , pk ) = S ::new_key ( & mut rng ) ? ;
for _ in 0 . . 1_000 {
let ( sk , pk ) = S ::new_key ( & mut rng ) ? ;
let msg_dist = Uniform ::new ( 0_ u64 , T ) ;
let m1 = Rq ::< T , N > ::rand_u64 ( & mut rng , msg_dist ) ? ;
let m2 = Rq ::< T , N > ::rand_u64 ( & mut rng , msg_dist ) ? ;
let msg_dist = Uniform ::new ( 0_ u64 , T ) ;
let m1 = Rq ::< T , N > ::rand_u64 ( & mut rng , msg_dist ) ? ;
let m2 = Rq ::< T , N > ::rand_u64 ( & mut rng , msg_dist ) ? ;
let c1 = S ::encrypt ( & mut rng , & pk , & m1 ) ? ;
let c2 = S ::encrypt ( & mut rng , & pk , & m2 ) ? ;
let c1 = S ::encrypt ( & mut rng , & pk , & m1 ) ? ;
let c2 = S ::encrypt ( & mut rng , & pk , & m2 ) ? ;
let c3 = c1 + c2 ;
let c3 = c1 + c2 ;
let m3_recovered = S ::decrypt ( & sk , & c3 ) ;
let m3_recovered = S ::decrypt ( & sk , & c3 ) ;
assert_eq ! ( m1 + m2 , m3_recovered ) ;
assert_eq ! ( m1 + m2 , m3_recovered ) ;
}
Ok ( ( ) )
}
#[ test ]
fn test_constant_add ( ) -> Result < ( ) > {
fn test_constant_add_mul ( ) -> Result < ( ) > {
const Q : u64 = 2 u64 . pow ( 16 ) + 1 ;
const N : usize = 32 ;
const T : u64 = 4 ; // plaintext modulus
const T : u64 = 8 ; // plaintext modulus
type S = BFV < Q , N , T > ;
let mut rng = rand ::thread_rng ( ) ;
@ -387,25 +382,25 @@ mod tests {
let msg_dist = Uniform ::new ( 0_ u64 , T ) ;
let m1 = Rq ::< T , N > ::rand_u64 ( & mut rng , msg_dist ) ? ;
let m2_const = Rq ::< T , N > ::rand_u64 ( & mut rng , msg_dist ) ? ;
let c1 = S ::encrypt ( & mut rng , & pk , & m1 ) ? ;
let c3_add = & c1 + & m2_const ;
// let c3_mul = &c1 * &m2_const;
let m3_add_recovered = S ::decrypt ( & sk , & c3_add ) ;
// let m3_mul_recovered = S::decrypt(&sk, &c3_mul);
assert_eq ! ( m1 + m2_const , m3_add_recovered ) ;
//
// let mut mul_res = naive_poly_mul::<T>(&m1.coeffs().to_vec(), &m2_const.coeffs().to_vec());
// arithmetic::ring::modulus::<T, N>(&mut mul_res);
// dbg!(&mul_res);
// let mul_res_2 =
// naive_poly_mul_2::<T, N>(&m1.coeffs().to_vec(), &m2_const.coeffs().to_vec());
// assert_eq!(mul_res, mul_res_2);
// let mul_res = PR::<T, N>::from_vec(mul_res);
// assert_eq!(mul_res.coeffs(), m3_mul_recovered.coeffs());
// test multiplication of a ciphertext by a constant
const P : u64 = Q * Q ;
const PQ : u64 = P * Q ;
let rlk = BFV ::< Q , N , T > ::rlk_key ::< PQ > ( & mut rng , & sk ) ? ;
let c3_mul = S ::mul_const ( & rlk , & c1 , & m2_const ) ;
let m3_mul_recovered = S ::decrypt ( & sk , & c3_mul ) ;
assert_eq ! (
( m1 . to_r ( ) * m2_const . to_r ( ) ) . to_rq ::< T > ( ) . coeffs ( ) ,
m3_mul_recovered . coeffs ( )
) ;
Ok ( ( ) )
}
@ -413,12 +408,11 @@ mod tests {
#[ test ]
fn test_tensor ( ) -> Result < ( ) > {
const Q : u64 = 2 u64 . pow ( 16 ) + 1 ; // q prime, and 2^q + 1 shape
const N : usize = 8 ;
const T : u64 = 4 ; // plaintext modulus
const N : usize = 32 ;
const T : u64 = 8 ; // plaintext modulus
// const P: u64 = Q;
const P : u64 = Q * Q ;
// const P: u64 = 2_u64.pow(13) * Q + 1;
const PQ : u64 = P * Q ;
let mut rng = rand ::thread_rng ( ) ;
@ -447,7 +441,13 @@ mod tests {
// 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 > = m3 . mul_div_round ( T , Q ) ;
// let m3: Rq<Q, N> = c_a
// + Rq::<Q, N>::from_vec_i64(arithmetic::ring::naive_mul(&c_b.to_r(), &sk.0.to_r()))
// + Rq::<Q, N>::from_vec_i64(arithmetic::ring::naive_mul(
// &c_c.to_r(),
// &R::<N>::from_vec(arithmetic::ring::naive_mul(&sk.0.to_r(), &sk.0.to_r())),
// ));
let m3 : Rq < Q , N > = m3 . mul_div_round ( T , Q ) ; // descale
let m3 = m3 . remodule ::< T > ( ) ;
let naive = ( m1 . to_r ( ) * m2 . to_r ( ) ) . to_rq ::< T > ( ) ;
@ -538,7 +538,7 @@ mod tests {
let c1 = BFV ::< Q , N , T > ::encrypt ( & mut rng , & pk , & m1 ) ? ;
let c2 = BFV ::< Q , N , T > ::encrypt ( & mut rng , & pk , & m2 ) ? ;
let c3 = RLWE ::< Q , N > ::mul ::< PQ , T > ( & rlk , & c1 , & c2 ) ;
let c3 = RLWE ::< Q , N > ::mul ::< PQ , T > ( & rlk , & c1 , & c2 ) ; // uses relinearize internally
let m3 = BFV ::< Q , N , T > ::decrypt ( & sk , & c3 ) ;
@ -551,105 +551,4 @@ mod tests {
Ok ( ( ) )
}
#[ test ]
fn test_naive_mul ( ) -> Result < ( ) > {
const Q : u64 = 2 u64 . pow ( 16 ) + 1 ; // prime, and 2^q + 1 shape
const N : usize = 4 ;
const T : u64 = 4 ; // plaintext modulus
let mut rng = rand ::thread_rng ( ) ;
let msg_dist = Uniform ::new ( 0_ u64 , T ) ;
// for _ in 0..10_000 {
for _ in 0 . . 2 {
println ! ( "---" ) ;
// let a = Rq::<Q, N>::rand_u64(&mut rng, msg_dist)?;
// let b = Rq::<Q, N>::rand_u64(&mut rng, msg_dist)?;
// let a = Rq::<Q, N>::from_vec_u64(vec![Q - 1, Q - 2, Q - 3, Q - 3]);
// let b = Rq::<Q, N>::from_vec_u64(vec![Q - 3, Q - 3, Q - 2, Q - 1]);
let a = Rq ::< T , N > ::rand_u64 ( & mut rng , msg_dist ) ? ;
let b = Rq ::< T , N > ::rand_u64 ( & mut rng , msg_dist ) ? ;
dbg ! ( & a ) ;
dbg ! ( & b ) ;
let ( _ , pk ) = BFV ::< Q , N , T > ::new_key ( & mut rng ) ? ;
let ciph1 = BFV ::< Q , N , T > ::encrypt ( & mut rng , & pk , & a ) ? ;
let ciph2 = BFV ::< Q , N , T > ::encrypt ( & mut rng , & pk , & b ) ? ;
let a = ciph1 . 0 ;
let b = ciph2 . 0 ;
dbg ! ( & a ) ;
dbg ! ( & b ) ;
let c0 : Vec < i64 > = arithmetic ::ring ::naive_mul ( & a . to_r ( ) , & b . to_r ( ) ) ;
let c0 = Rq ::< Q , N > ::from_vec_i64 ( c0 ) ;
let c1 = tmp_naive_mul ( a , b ) ; // naive mul
let c2 : Rq < Q , N > = a * b ; // NTT mul
println ! ( "{:?}" , c0 . coeffs ( ) ) ;
println ! ( "{:?}" , c1 . coeffs ( ) ) ;
println ! ( "{:?}" , c2 . coeffs ( ) ) ;
assert_eq ! ( c0 , c2 ) ;
assert_eq ! ( c1 , c2 ) ;
// scale by Delta=Q/T
let a = a . mul_div_round ( Q , T ) ;
let b = b . mul_div_round ( Q , T ) ;
dbg ! ( & a ) ;
dbg ! ( & b ) ;
let c0 : Vec < i64 > = arithmetic ::ring ::naive_mul ( & a . to_r ( ) , & b . to_r ( ) ) ;
let c0 = Rq ::< Q , N > ::from_vec_i64 ( c0 ) ;
let c1 = tmp_naive_mul ( a , b ) ; // naive mul
let c2 : Rq < Q , N > = a * b ; // NTT mul
println ! ( "{:?}" , c0 . coeffs ( ) ) ;
println ! ( "{:?}" , c1 . coeffs ( ) ) ;
println ! ( "{:?}" , c2 . coeffs ( ) ) ;
assert_eq ! ( c0 , c2 ) ;
assert_eq ! ( c1 , c2 ) ;
let c0 = c0 . mul_div_round ( T , Q ) ;
let c1 = c1 . mul_div_round ( T , Q ) ;
let c2 = c2 . mul_div_round ( T , Q ) ;
println ! ( "{:?}" , c0 . coeffs ( ) ) ;
println ! ( "{:?}" , c1 . coeffs ( ) ) ;
println ! ( "{:?}" , c2 . coeffs ( ) ) ;
assert_eq ! ( c0 , c2 ) ;
assert_eq ! ( c1 , c2 ) ;
/*
// now same as before, but multiplying by T/Q
let c0 : Vec < i64 > = arithmetic ::ring ::naive_mul ( & a . to_r ( ) , & b . to_r ( ) ) ;
let c0 : Vec < f64 > = c0
. iter ( )
. map ( | e | ( ( T as f64 * * e as f64 ) / Q as f64 ) . round ( ) )
. collect ( ) ;
let c0 = Rq ::< Q , N > ::from_vec_f64 ( c0 ) ;
dbg ! ( & c0 . coeffs ( ) ) ;
let a = a . mul_div_round ( T , Q ) ;
let b = b . mul_div_round ( T , Q ) ;
println ! ( "a{:?}" , a . coeffs ( ) ) ;
println ! ( "b{:?}" , b . coeffs ( ) ) ;
let c4 : Vec < i64 > = arithmetic ::ring ::naive_mul ( & a . to_r ( ) , & b . to_r ( ) ) ;
let c4 = Rq ::< Q , N > ::from_vec_i64 ( c4 ) ;
let c4 = c4 . mul_div_round ( T , Q ) ;
let c1 = tmp_naive_mul ( a , b ) ; // naive mul
let c1 = c1 . mul_div_round ( T , Q ) ;
let c2 = a * b ; // NTT mul
let c2 = c2 . mul_div_round ( T , Q ) ;
println ! ( "{:?}" , c0 . coeffs ( ) ) ;
println ! ( "{:?}" , c1 . coeffs ( ) ) ;
println ! ( "{:?}" , c2 . coeffs ( ) ) ;
println ! ( "{:?}" , c4 . coeffs ( ) ) ;
assert_eq ! ( c0 , c2 ) ;
assert_eq ! ( c4 , c2 ) ;
assert_eq ! ( c1 , c2 ) ;
* /
}
Ok ( ( ) )
}
}