arnaucube
arnaucube/fhe-studyPublic Self-hosted repository
tfhe: add bootstrapping impl

add initial version of bootstrapping implementation
arnaucube git@arnaucube.com
Sun, 03 Aug 2025 21:24:17 +0000
commit

13abadf6e108fa63de5167d51a5a619a48341475

parent

2c20a2ed0e60afa5aac5713a26d2f0dbed069f08

4 files changed, 134 insertions(+), 5 deletions(-)

jump to
M README.md → README.md

@@ -52,3 +52,33 @@ let m4 = S::decode::<T>(&p4_recovered);

// m4 is equal to (m1+m2)*m3 ``` + + +## Status of the implementation + +- TFHE + - {TLWE, TGLWE, TLev, TGLev, TGSW, TGGSW} encryption & decryption + - addition of ciphertexts, addition & multiplication of ciphertext by a plaintext + - external products of ciphertexts + - TGSW x TLWE + - TGGSW x TGLWE + - TGSW & TGGSW CMux gate + - blind rotation, key switching, mod switching + - bootstrapping +- CKKS + - encoding & decoding + - encryption & decryption + - addition & substraction of ciphertexts +- BFV + - encryption & decryption + - addition & substraction of ciphertexts + - addition & multiplication of ciphertext by a plaintext + - multiplication of ciphertexts with relinearization +- GFHE (generalized FHE) + - {GLWE & GLev} encryption & decryption + - key switching, mod switching + - addition & substraction of ciphertexts + - addition & multiplication of ciphertext by a plaintext +- arith + - base arithmetic for $\mathbb{Z}_q,~~ R_q=\mathbb{Z}_q[X]/(X^N+1),~~ R=\mathbb{Z}[X]/(X^N+1),~~ \mathbb{T}_q[X]/(X^N +1)$ + - NTT implementation (negacyclic convolution)
M arith/src/ring_torus.rs → arith/src/ring_torus.rs

@@ -85,14 +85,11 @@

impl<const N: usize> Tn<N> { // multiply self by X^-h pub fn left_rotate(&self, h: usize) -> Self { - dbg!(&h); - dbg!(&N); let h = h % N; assert!(h < N); let c = self.0; // c[h], c[h+1], c[h+2], ..., c[n-1], -c[0], -c[1], ..., -c[h-1] // let r: Vec<T64> = vec![c[h..N], c[0..h].iter().map(|&c_i| -c_i).collect()].concat(); - dbg!(&h); let r: Vec<T64> = c[h..N] .iter() .copied()
M tfhe/src/tggsw.rs → tfhe/src/tggsw.rs

@@ -132,3 +132,49 @@ let r: TGLWE<N, K> = zip_eq(v, tlwes).map(|(a_d_i, glwe_i)| glwe_i * a_d_i).sum();

r } } + +#[cfg(test)] +mod tests { + use anyhow::Result; + use rand::distributions::Uniform; + + use super::*; + #[test] + fn test_external_product() -> Result<()> { + const T: u64 = 16; // plaintext modulus + const K: usize = 4; + const N: usize = 64; + const KN: usize = K * N; + + let beta: u32 = 2; + let l: u32 = 64; + + let mut rng = rand::thread_rng(); + let msg_dist = Uniform::new(0_u64, T); + + for _ in 0..50 { + let (sk, _) = TGLWE::<N, K>::new_key::<KN>(&mut rng)?; + + let m1: Rq<T, N> = Rq::rand_u64(&mut rng, msg_dist)?; + let p1: Tn<N> = TGLev::<N, K>::encode::<T>(&m1); + + let m2: Rq<T, N> = Rq::rand_u64(&mut rng, msg_dist)?; + let p2: Tn<N> = TGLWE::<N, K>::encode::<T>(&m2); // scaled by delta + + let tgsw = TGGSW::<N, K>::encrypt_s(&mut rng, beta, l, &sk, &p1)?; + let tlwe = TGLWE::<N, K>::encrypt_s(&mut rng, &sk, &p2)?; + + let res: TGLWE<N, K> = tgsw * tlwe; + + // let p_recovered = res.decrypt(&sk, beta); + let p_recovered = res.decrypt(&sk); + // downscaled by delta^-1 + let res_recovered = TGLWE::<N, K>::decode::<T>(&p_recovered); + + // assert_eq!(m1 * m2, m_recovered); + assert_eq!((m1.to_r() * m2.to_r()).to_rq::<T>(), res_recovered); + } + + Ok(()) + } +}
M tfhe/src/tlwe.rs → tfhe/src/tlwe.rs

@@ -132,21 +132,37 @@ });

c_j } +pub fn bootstrapping<const N: usize, const K: usize, const KN: usize, const KN2: u64>( + btk: BootstrappingKey<N, K, KN>, + table: TGLWE<N, K>, + c: TLWE<KN>, +) -> TLWE<KN> { + let rotated: TGLWE<N, K> = blind_rotation::<N, K, KN, KN2>(c, btk.clone(), table); + let c_h: TLWE<KN> = rotated.sample_extraction(0); + let r = c_h.key_switch(2, 64, &btk.1); + r +} + #[derive(Clone, Debug)] -pub struct BootstrappingKey<const N: usize, const K: usize, const KN: usize>(pub Vec<TGGSW<N, K>>); +pub struct BootstrappingKey<const N: usize, const K: usize, const KN: usize>( + pub Vec<TGGSW<N, K>>, + pub KSK<KN>, +); impl<const N: usize, const K: usize, const KN: usize> BootstrappingKey<N, K, KN> { pub fn from_sk(mut rng: impl Rng, sk: &tglwe::SecretKey<N, K>) -> Result<Self> { let (beta, l) = (2u32, 64u32); // TMP // let s: TR<Tn<N>, K> = sk.0 .0.clone(); + let (sk2, _) = TLWE::<KN>::new_key(&mut rng)?; // TLWE<KN> compatible with TGLWE<N,K> // each btk_j = TGGSW_sk(s_i) let btk: Vec<TGGSW<N, K>> = s .iter() .map(|s_i| TGGSW::<N, K>::encrypt_s(&mut rng, beta, l, sk, s_i)) .collect::<Result<Vec<_>>>()?; + let ksk = TLWE::<KN>::new_ksk(&mut rng, beta, l, &sk.to_tlwe(), &sk2)?; - Ok(Self(btk)) + Ok(Self(btk, ksk)) } }

@@ -396,6 +412,46 @@ let c2 = c.key_switch(beta, l, &ksk);

let p_recovered = c2.decrypt(&sk2); let m_recovered = S::decode::<T>(&p_recovered); assert_eq!(m, m_recovered); + + Ok(()) + } + + #[test] + fn test_bootstrapping() -> Result<()> { + const T: u64 = 128; // plaintext modulus + const K: usize = 1; + const N: usize = 1024; + const KN: usize = K * N; + let mut rng = rand::thread_rng(); + + let start = Instant::now(); + let table: TGLWE<N, K> = compute_lookup_table::<T, K, N>(); + println!("table took: {:?}", start.elapsed()); + + let (sk, _) = TGLWE::<N, K>::new_key::<KN>(&mut rng)?; + let sk_tlwe: SecretKey<KN> = sk.to_tlwe::<KN>(); + + let start = Instant::now(); + let btk = BootstrappingKey::<N, K, KN>::from_sk(&mut rng, &sk)?; + println!("btk took: {:?}", start.elapsed()); + + let msg_dist = Uniform::new(0_u64, T); + let m = Rq::<T, 1>::rand_u64(&mut rng, msg_dist)?; + dbg!(&m); + let p = TLWE::<K>::encode::<T>(&m); // plaintext + + let c = TLWE::<KN>::encrypt_s(&mut rng, &sk_tlwe, &p)?; + + let start = Instant::now(); + // the ugly const generics are temporary + let bootstrapped: TLWE<KN> = + bootstrapping::<N, K, KN, { K as u64 * N as u64 }>(btk, table, c); + println!("bootstrapping took: {:?}", start.elapsed()); + + let p_recovered: T64 = bootstrapped.decrypt(&sk_tlwe); + let m_recovered = TLWE::<KN>::decode::<T>(&p_recovered); + dbg!(&m_recovered); + assert_eq!(m_recovered, m); Ok(()) }