From c851a3b58f35529650ca0b6922c6e93f10d71b1c Mon Sep 17 00:00:00 2001 From: Janmajaya Mall Date: Wed, 1 May 2024 17:22:49 +0530 Subject: [PATCH] test seeded RLWE auto key --- src/rgsw.rs | 340 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 210 insertions(+), 130 deletions(-) diff --git a/src/rgsw.rs b/src/rgsw.rs index e6bff5d..cd6758c 100644 --- a/src/rgsw.rs +++ b/src/rgsw.rs @@ -17,6 +17,77 @@ use crate::{ Matrix, MatrixEntity, MatrixMut, Row, RowEntity, RowMut, Secret, }; +pub struct SeededAutoKey +where + M: Matrix, +{ + data: M, + seed: S, + modulus: M::MatElement, +} + +impl SeededAutoKey { + fn from_raw(data: M, seed: S, modulus: M::MatElement) -> Self { + assert!(data.dimension().0 % 3 == 0); + + SeededAutoKey { + data, + seed, + modulus, + } + } + + fn empty(ring_size: usize, d_rgsw: usize, seed: S, modulus: M::MatElement) -> Self { + SeededAutoKey { + data: M::zeros(d_rgsw, ring_size), + seed, + modulus: modulus, + } + } +} + +pub struct AutoKeyEvaluationDomain { + data: M, + _phantom: PhantomData<(R, N)>, +} + +impl< + M: MatrixMut + MatrixEntity, + R: RandomUniformDist<[M::MatElement], Parameters = M::MatElement> + NewWithSeed, + N: NttInit + Ntt, + > From<&SeededAutoKey> for AutoKeyEvaluationDomain +where + ::R: RowMut, + M::MatElement: Copy, + R::Seed: Clone, +{ + fn from(value: &SeededAutoKey) -> Self { + let (d, ring_size) = value.data.dimension(); + let mut data = M::zeros(2 * d, ring_size); + + // sample RLWE'_A(-s(X^k)) + let mut p_rng = R::new_with_seed(value.seed.clone()); + data.iter_rows_mut().take(d).for_each(|r| { + RandomUniformDist::random_fill(&mut p_rng, &value.modulus, r.as_mut()); + }); + + // copy over RLWE'_B(-s(X^k)) + izip!(data.iter_rows_mut().skip(d), value.data.iter_rows()).for_each(|(to_r, from_r)| { + to_r.as_mut().copy_from_slice(from_r.as_ref()); + }); + + // send RLWE'(-s(X^k)) polynomials to evaluation domain + let ntt_op = N::new(value.modulus, ring_size); + data.iter_rows_mut() + .for_each(|r| ntt_op.forward(r.as_mut())); + + AutoKeyEvaluationDomain { + data, + _phantom: PhantomData, + } + } +} + pub struct SeededRgswCiphertext where M: Matrix, @@ -317,7 +388,7 @@ pub(crate) fn rlwe_ksk_gen< + NewWithSeed, >( ksk_out: &mut Mmut, - mut neg_from_s: Mmut::R, + neg_from_s: Mmut::R, mut to_s: Mmut::R, gadget_vector: &[Mmut::MatElement], mod_op: &ModOp, @@ -843,46 +914,46 @@ pub(crate) fn decrypt_rlwe< // Measures noise in degree 1 RLWE ciphertext against encoded ideal message // encoded_m pub(crate) fn measure_noise< - Mmut: MatrixMut + Matrix + MatrixEntity, + Mmut: MatrixMut + Matrix, ModOp: VectorOps, NttOp: Ntt, - S: Secret, + S, >( rlwe_ct: &Mmut, - encoded_m_ideal: &Mmut, + encoded_m_ideal: &Mmut::R, ntt_op: &NttOp, mod_op: &ModOp, - s: &S, + s: &[S], ) -> f64 where ::R: RowMut, - Mmut: TryConvertFrom<[S::Element], Parameters = Mmut::MatElement>, + Mmut::R: RowEntity + TryConvertFrom<[S], Parameters = Mmut::MatElement>, Mmut::MatElement: PrimInt + ToPrimitive + Debug, { - let ring_size = s.values().len(); + let ring_size = s.len(); assert!(rlwe_ct.dimension() == (2, ring_size)); - assert!(encoded_m_ideal.dimension() == (1, ring_size)); + assert!(encoded_m_ideal.as_ref().len() == ring_size); // -(s * a) let q = VectorOps::modulus(mod_op); - let mut s = Mmut::try_convert_from(s.values(), &q); - ntt_op.forward(s.get_row_mut(0)); - let mut a = Mmut::zeros(1, ring_size); - a.get_row_mut(0).copy_from_slice(rlwe_ct.get_row_slice(0)); - ntt_op.forward(a.get_row_mut(0)); - mod_op.elwise_mul_mut(s.get_row_mut(0), a.get_row_slice(0)); - mod_op.elwise_neg_mut(s.get_row_mut(0)); - ntt_op.backward(s.get_row_mut(0)); + let mut s = Mmut::R::try_convert_from(s, &q); + ntt_op.forward(s.as_mut()); + let mut a = Mmut::R::zeros(ring_size); + a.as_mut().copy_from_slice(rlwe_ct.get_row_slice(0)); + ntt_op.forward(a.as_mut()); + mod_op.elwise_mul_mut(s.as_mut(), a.as_ref()); + mod_op.elwise_neg_mut(s.as_mut()); + ntt_op.backward(s.as_mut()); // m+e = b - s*a let mut m_plus_e = s; - mod_op.elwise_add_mut(m_plus_e.get_row_mut(0), rlwe_ct.get_row_slice(1)); + mod_op.elwise_add_mut(m_plus_e.as_mut(), rlwe_ct.get_row_slice(1)); // difference - mod_op.elwise_sub_mut(m_plus_e.get_row_mut(0), encoded_m_ideal.get_row_slice(0)); + mod_op.elwise_sub_mut(m_plus_e.as_mut(), encoded_m_ideal.as_ref()); let mut max_diff_bits = f64::MIN; - m_plus_e.get_row_slice(0).iter().for_each(|v| { + m_plus_e.as_ref().iter().for_each(|v| { let mut v = *v; if v >= (q >> 1) { @@ -913,8 +984,8 @@ mod tests { ntt::{self, Ntt, NttBackendU64, NttInit}, random::{DefaultSecureRng, RandomUniformDist}, rgsw::{ - measure_noise, RgswCiphertextEvaluationDomain, RlweCiphertext, SeededRgswCiphertext, - SeededRlweCiphertext, + measure_noise, AutoKeyEvaluationDomain, RgswCiphertextEvaluationDomain, RlweCiphertext, + SeededAutoKey, SeededRgswCiphertext, SeededRlweCiphertext, }, utils::{generate_prime, negacyclic_mul}, Matrix, Secret, @@ -1083,113 +1154,122 @@ mod tests { ); } - // #[test] - // fn galois_auto_works() { - // let logq = 50; - // let ring_size = 1 << 5; - // let q = generate_prime(logq, 2 * ring_size, 1u64 << logq).unwrap(); - // let logp = 3; - // let p = 1u64 << logp; - // let d_rgsw = 10; - // let logb = 5; - - // let mut rng = DefaultSecureRng::new(); - // let s = RlweSecret::random((ring_size >> 1) as usize, ring_size as - // usize); - - // let mut m = vec![0u64; ring_size as usize]; - // RandomUniformDist::random_fill(&mut rng, &p, m.as_mut_slice()); - // let encoded_m = m - // .iter() - // .map(|v| (((*v as f64 * q as f64) / (p as f64)).round() as u64)) - // .collect_vec(); - - // let ntt_op = NttBackendU64::new(q, ring_size as usize); - // let mod_op = ModularOpsU64::new(q); - - // // RLWE_{s}(m) - // let mut rlwe_m = vec![vec![0u64; ring_size as usize]; 2]; - // secret_key_encrypt_rlwe( - // &vec![encoded_m.clone()], - // &mut rlwe_m, - // &s, - // &mod_op, - // &ntt_op, - // &mut rng, - // ); - - // let auto_k = -5; - - // // Generate galois key to key switch from s^k to s - // let mut ksk_out = vec![vec![0u64; ring_size as usize]; d_rgsw * 2]; - // let gadget_vector = gadget_vector(logq, logb, d_rgsw); - // galois_key_gen( - // &mut ksk_out, - // &s, - // auto_k, - // &gadget_vector, - // &mod_op, - // &ntt_op, - // &mut rng, - // ); - - // // Send RLWE_{s}(m) -> RLWE_{s}(m^k) - // let mut rlwe_m = RlweCiphertext(rlwe_m, false); - // let mut scratch_space = vec![vec![0u64; ring_size as usize]; d_rgsw + - // 2]; let (auto_map_index, auto_map_sign) = - // generate_auto_map(ring_size as usize, auto_k); let decomposer = - // DefaultDecomposer::new(q, logb, d_rgsw); galois_auto( - // &mut rlwe_m, - // &ksk_out, - // &mut scratch_space, - // &auto_map_index, - // &auto_map_sign, - // &mod_op, - // &ntt_op, - // &decomposer, - // ); - - // let rlwe_m_k = rlwe_m; - - // // Decrypt RLWE_{s}(m^k) and check - // let mut encoded_m_k_back = vec![vec![0u64; ring_size as usize]]; - // decrypt_rlwe( - // &rlwe_m_k, - // s.values(), - // &mut encoded_m_k_back, - // &ntt_op, - // &mod_op, - // ); - // let m_k_back = encoded_m_k_back[0] - // .iter() - // .map(|v| (((*v as f64 * p as f64) / q as f64).round() as u64) % - // p) .collect_vec(); - - // let mut m_k = vec![0u64; ring_size as usize]; - // // Send \delta m -> \delta m^k - // izip!(m.iter(), auto_map_index.iter(), - // auto_map_sign.iter()).for_each( |(v, to_index, sign)| { - // if !*sign { - // m_k[*to_index] = (p - *v) % p; - // } else { - // m_k[*to_index] = *v; - // } - // }, - // ); - - // { - // // let encoded_m_k = m_k - // // .iter() - // // .map(|v| ((*v as f64 * q as f64) / p as f64).round() as - // u64) // .collect_vec(); - - // // let noise = measure_noise(&rlwe_m_k, &vec![encoded_m_k], - // &ntt_op, // &mod_op, &s); println!("Ksk noise: {noise}"); - // } - - // // FIXME(Jay): Galios autormophism will incur high error unless we - // fix in // accurate decomoposition of Decomposer when q is prime - // assert_eq!(m_k_back, m_k); - // // dbg!(m_k_back, m_k, q); - // } + #[test] + fn galois_auto_works() { + let logq = 50; + let ring_size = 1 << 4; + let q = generate_prime(logq, 2 * ring_size, 1u64 << logq).unwrap(); + let logp = 3; + let p = 1u64 << logp; + let d_rgsw = 10; + let logb = 5; + + let mut rng = DefaultSecureRng::new(); + let s = RlweSecret::random((ring_size >> 1) as usize, ring_size as usize); + + let mut m = vec![0u64; ring_size as usize]; + RandomUniformDist::random_fill(&mut rng, &p, m.as_mut_slice()); + let encoded_m = m + .iter() + .map(|v| (((*v as f64 * q as f64) / (p as f64)).round() as u64)) + .collect_vec(); + + let ntt_op = NttBackendU64::new(q, ring_size as usize); + let mod_op = ModularOpsU64::new(q); + + // RLWE_{s}(m) + let mut seed_rlwe = [0u8; 32]; + rng.fill_bytes(&mut seed_rlwe); + let mut seeded_rlwe_m = SeededRlweCiphertext::empty(ring_size as usize, seed_rlwe, q); + secret_key_encrypt_rlwe( + &encoded_m, + &mut seeded_rlwe_m.data, + s.values(), + &mod_op, + &ntt_op, + seeded_rlwe_m.seed, + &mut rng, + ); + let mut rlwe_m = RlweCiphertext::>, DefaultSecureRng>::from(&seeded_rlwe_m); + + let auto_k = -5; + + // Generate galois key to key switch from s^k to s + let mut seed_auto = [0u8; 32]; + rng.fill_bytes(&mut seed_auto); + let mut seeded_auto_key = SeededAutoKey::empty(ring_size as usize, d_rgsw, seed_auto, q); + let gadget_vector = gadget_vector(logq, logb, d_rgsw); + galois_key_gen( + &mut seeded_auto_key.data, + s.values(), + auto_k, + &gadget_vector, + &mod_op, + &ntt_op, + seeded_auto_key.seed, + &mut rng, + ); + let auto_key = + AutoKeyEvaluationDomain::>, DefaultSecureRng, NttBackendU64>::from( + &seeded_auto_key, + ); + + // Send RLWE_{s}(m) -> RLWE_{s}(m^k) + let mut scratch_space = vec![vec![0u64; ring_size as usize]; d_rgsw + 2]; + let (auto_map_index, auto_map_sign) = generate_auto_map(ring_size as usize, auto_k); + let decomposer = DefaultDecomposer::new(q, logb, d_rgsw); + galois_auto( + &mut rlwe_m, + &auto_key.data, + &mut scratch_space, + &auto_map_index, + &auto_map_sign, + &mod_op, + &ntt_op, + &decomposer, + ); + + let rlwe_m_k = rlwe_m; + + // Decrypt RLWE_{s}(m^k) and check + let mut encoded_m_k_back = vec![0u64; ring_size as usize]; + decrypt_rlwe( + &rlwe_m_k, + s.values(), + &mut encoded_m_k_back, + &ntt_op, + &mod_op, + ); + let m_k_back = encoded_m_k_back + .iter() + .map(|v| (((*v as f64 * p as f64) / q as f64).round() as u64) % p) + .collect_vec(); + + let mut m_k = vec![0u64; ring_size as usize]; + // Send \delta m -> \delta m^k + izip!(m.iter(), auto_map_index.iter(), auto_map_sign.iter()).for_each( + |(v, to_index, sign)| { + if !*sign { + m_k[*to_index] = (p - *v) % p; + } else { + m_k[*to_index] = *v; + } + }, + ); + + { + let encoded_m_k = m_k + .iter() + .map(|v| ((*v as f64 * q as f64) / p as f64).round() as u64) + .collect_vec(); + + let noise = measure_noise(&rlwe_m_k, &encoded_m_k, &ntt_op, &mod_op, s.values()); + println!("Ksk noise: {noise}"); + } + + // FIXME(Jay): Galios autormophism will incur high error unless we fix in + // accurate decomoposition of Decomposer when q is prime + assert_eq!(m_k_back, m_k); + // dbg!(m_k_back, m_k, q); + } }