clean rgsw/runtime
@@ -25,8 +25,8 @@ DefaultSecureRng, NewWithSeed, RandomFill, RandomFillGaussianInModulus,
RandomFillUniformInModulus, RandomGaussianElementInModulus, }, rgsw::{ - decrypt_rlwe, galois_auto, generate_auto_map, public_key_encrypt_rgsw, - rgsw_by_rgsw_inplace, secret_key_encrypt_rgsw, seeded_auto_key_gen, + decrypt_rlwe, generate_auto_map, public_key_encrypt_rgsw, rgsw_by_rgsw_inplace, rlwe_auto, + secret_key_encrypt_rgsw, seeded_auto_key_gen, }, utils::{ encode_x_pow_si_with_emebedding_factor, fill_random_ternary_secret_with_hamming_weight,
@@ -178,7 +178,7 @@ use crate::{
bool::{evaluator::BoolEncoding, keys::NonInteractiveMultiPartyClientKey}, pbs::{sample_extract, PbsInfo, WithShoupRepr}, random::{NewWithSeed, RandomFillUniformInModulus}, - rgsw::{key_switch, seeded_secret_key_encrypt_rlwe}, + rgsw::{rlwe_key_switch, seeded_secret_key_encrypt_rlwe}, utils::TryConvertFrom1, Encryptor, KeySwitchWithId, Matrix, MatrixEntity, MatrixMut, RowEntity, RowMut, };@@ -324,7 +324,7 @@ let ksk = server_key.ui_to_s_ksk(user_id);
let decomposer = e.ni_ui_to_s_ks_decomposer().as_ref().unwrap(); // perform key switch - key_switch( + rlwe_key_switch( self, ksk.as_ref(), ksk.shoup_repr(),
@@ -10,7 +10,7 @@ decomposer::RlweDecomposer,
lwe::{decrypt_lwe, lwe_key_switch}, parameters::{BoolParameters, CiphertextModulus}, random::{DefaultSecureRng, RandomFillUniformInModulus}, - rgsw::{decrypt_rlwe, galois_auto, IsTrivial, RlweCiphertext}, + rgsw::{decrypt_rlwe, rlwe_auto, IsTrivial, RlweCiphertext}, utils::{encode_x_pow_si_with_emebedding_factor, tests::Stats, TryConvertFrom1}, ArithmeticOps, ClientKey, Decomposer, MatrixEntity, MatrixMut, ModInit, Ntt, NttInit, RowEntity, RowMut, VectorOps,@@ -283,7 +283,7 @@ let mut rlwe = RlweCiphertext::<_, DefaultSecureRng>::new_trivial(M::zeros(2, rlwe_n));
rlwe.data.get_row_mut(0).copy_from_slice(m.as_ref()); rlwe.set_not_trivial(); - galois_auto( + rlwe_auto( &mut rlwe, server_key.galois_key_for_auto(*k), &mut scratch_matrix,
@@ -9,14 +9,11 @@ ops::{Div, Neg, Sub},
}; use crate::{ - backend::{ArithmeticOps, GetModulus, Modulus, VectorOps}, - decomposer::{self, Decomposer, RlweDecomposer}, - ntt::{self, Ntt, NttInit}, - random::{ - DefaultSecureRng, NewWithSeed, RandomElementInModulus, RandomFill, - RandomFillGaussianInModulus, RandomFillUniformInModulus, - }, - utils::{fill_random_ternary_secret_with_hamming_weight, ToShoup, TryConvertFrom1, WithLocal}, + backend::Modulus, + decomposer::{Decomposer, RlweDecomposer}, + ntt::{Ntt, NttInit}, + random::{DefaultSecureRng, NewWithSeed, RandomFillUniformInModulus}, + utils::{fill_random_ternary_secret_with_hamming_weight, ToShoup, WithLocal}, Matrix, MatrixEntity, MatrixMut, Row, RowEntity, RowMut, Secret, };@@ -210,8 +207,6 @@ to_ri.as_mut().copy_from_slice(from_ri.as_ref());
}); // sample A polynomials of RLWE'(m) - RLWE'A(m) - // TODO(Jay): Do we want to be generic over RandomGenerator used here? I think - // not. let mut p_rng = R::new_with_seed(value.seed.clone()); izip!(data.iter_rows_mut().skip(value.d_a * 2).take(value.d_b * 1)) .for_each(|ri| p_rng.random_fill(&value.modulus, ri.as_mut()));@@ -528,7 +523,7 @@ decrypt_rlwe, generate_auto_map, measure_noise, public_key_encrypt_rgsw,
rlwe_public_key, secret_key_encrypt_rgsw, seeded_auto_key_gen, seeded_secret_key_encrypt_rlwe, }, - runtime::{galois_auto, rgsw_by_rgsw_inplace, rlwe_by_rgsw}, + runtime::{rgsw_by_rgsw_inplace, rlwe_auto, rlwe_by_rgsw}, AutoKeyEvaluationDomain, RgswCiphertext, RgswCiphertextEvaluationDomain, RlweCiphertext, RlwePublicKey, RlweSecret, SeededAutoKey, SeededRgswCiphertext, SeededRlweCiphertext, SeededRlwePublicKey,@@ -636,69 +631,6 @@ );
seeded_rgsw_ct } - /// Prints noise in RGSW ciphertext RGSW(m). - /// - /// - rgsw_ct: RGSW ciphertext in coefficient domain - pub(crate) fn _measure_noise_rgsw<T: Modulus<Element = u64> + Clone>( - rgsw_ct: &[Vec<u64>], - m: &[u64], - s: &[i32], - decomposer: &(DefaultDecomposer<u64>, DefaultDecomposer<u64>), - q: &T, - ) { - let gadget_vector_a = decomposer.a().gadget_vector(); - let gadget_vector_b = decomposer.b().gadget_vector(); - let d_a = gadget_vector_a.len(); - let d_b = gadget_vector_b.len(); - let ring_size = s.len(); - assert!(Matrix::dimension(&rgsw_ct) == (d_a * 2 + d_b * 2, ring_size)); - assert!(m.len() == ring_size); - - let mod_op = ModularOpsU64::new(q.clone()); - let ntt_op = NttBackendU64::new(q, ring_size); - - let mul_mod = - |a: &u64, b: &u64| ((*a as u128 * *b as u128) % q.q().unwrap() as u128) as u64; - let s_poly = Vec::<u64>::try_convert_from(s, q); - let mut neg_s = s_poly.clone(); - mod_op.elwise_neg_mut(neg_s.as_mut()); - let neg_sm0m1 = negacyclic_mul(&neg_s, &m, mul_mod, q.q().unwrap()); - - // RLWE(\beta^j -s * m) - for j in 0..d_a { - let ideal_m = { - // RLWE(\beta^j -s * m) - let mut beta_neg_sm0m1 = vec![0u64; ring_size as usize]; - mod_op.elwise_scalar_mul(beta_neg_sm0m1.as_mut(), &neg_sm0m1, &gadget_vector_a[j]); - beta_neg_sm0m1 - }; - - let mut rlwe = vec![vec![0u64; ring_size as usize]; 2]; - rlwe[0].copy_from_slice(rgsw_ct.get_row_slice(j)); - rlwe[1].copy_from_slice(rgsw_ct.get_row_slice(d_a + j)); - let noise = measure_noise(&rlwe, &ideal_m, &ntt_op, &mod_op, s); - - println!(r"Noise RLWE(\beta^{j} -sm0m1): {noise}"); - } - - // RLWE(\beta^j m) - for j in 0..d_b { - let ideal_m = { - // RLWE(\beta^j m) - let mut beta_m0m1 = vec![0u64; ring_size as usize]; - mod_op.elwise_scalar_mul(beta_m0m1.as_mut(), &m, &gadget_vector_b[j]); - beta_m0m1 - }; - - let mut rlwe = vec![vec![0u64; ring_size as usize]; 2]; - rlwe[0].copy_from_slice(rgsw_ct.get_row_slice(d_a * 2 + j)); - rlwe[1].copy_from_slice(rgsw_ct.get_row_slice(d_a * 2 + d_b + j)); - let noise = measure_noise(&rlwe, &ideal_m, &ntt_op, &mod_op, s); - - println!(r"Noise RLWE(\beta^{j} m0m1): {noise}"); - } - } - #[test] fn rlwe_encrypt_decryption() { let logq = 50;@@ -742,9 +674,6 @@ .iter()
.map(|v| (((*v as f64 * p as f64) / q as f64).round() as u64) % p) .collect_vec(); assert_eq!(m0, m_back); - - // let noise = measure_noise(&rlwe_in_ct, &encoded_m, &ntt_op, &mod_op, - // s.values()); println!("Noise: {noise}"); } #[test]@@ -999,7 +928,7 @@ };
// normal galois auto { - galois_auto( + rlwe_auto( &mut rlwe_m, &auto_key.data, &mut scratch_space,@@ -1104,7 +1033,7 @@ decomposer.b().decomposition_count()
) ]; - _measure_noise_rgsw(&rgsw_carrym, &carry_m, s.values(), &decomposer, &q); + // _measure_noise_rgsw(&rgsw_carrym, &carry_m, s.values(), &decomposer, &q); for i in 0..2 { let mut m = vec![0u64; ring_size as usize];@@ -1127,7 +1056,8 @@
// measure noise carry_m = negacyclic_mul(&carry_m, &m, mul_mod, q); println!("########### Noise RGSW(carrym) in {i}^th loop ###########"); - _measure_noise_rgsw(&rgsw_carrym, &carry_m, s.values(), &decomposer, &q); + // _measure_noise_rgsw(&rgsw_carrym, &carry_m, s.values(), + // &decomposer, &q); } { // RLWE(m) x RGSW(carry_m)
@@ -10,7 +10,7 @@ };
use super::IsTrivial; -pub(crate) fn routine<R: RowMut, ModOp: VectorOps<Element = R::Element>>( +pub(crate) fn poly_fma_routine<R: RowMut, ModOp: VectorOps<Element = R::Element>>( write_to_row: &mut [R::Element], matrix_a: &[R], matrix_b: &[R],@@ -48,11 +48,18 @@ });
} } -/// Sends RLWE_{s}(X) -> RLWE_{s}(X^k) where k is some galois element +/// Sends RLWE_{s(X)}(m(X)) -> RLWE_{s(X)}(m{X^k}) where k is some galois +/// element /// -/// - scratch_matrix: must have dimension at-least d+2 x ring_size. d rows to -/// store decomposed polynomials and 2 for rlwe -pub(crate) fn galois_auto< +/// - rlwe_in: Input ciphertext RLWE_{s(X)}(m(X)). +/// - ksk: Auto key switching key with polynomials in evaluation domain +/// - auto_map_index: If automorphism sends i^th coefficient of m(X) to j^th +/// coefficient of m(X^k) then auto_map_index[i] = j +/// - auto_sign_index: With a = m(X)[i], if m(X^k)[auto_map_index[i]] = -a, then +/// auto_sign_index[i] = false, else auto_sign_index[i] = true +/// - scratch_matrix: must have dimension at-least d+2 x ring_size. `d` rows to +/// store decomposed polynomials nad 2 rows to store out RLWE temporarily. +pub(crate) fn rlwe_auto< MT: Matrix + IsTrivial + MatrixMut, Mmut: MatrixMut<MatElement = MT::MatElement>, ModOp: ArithmeticOps<Element = MT::MatElement> + VectorOps<Element = MT::MatElement>,@@ -119,7 +126,7 @@ // RLWE(m^k) = a', b'; RLWE(m) = a, b
// key switch: (a * RLWE'(s(X^k))) let (ksk_a, ksk_b) = ksk.split_at_row(d); // a' = decomp<a> * RLWE'_A(s(X^k)) - routine( + poly_fma_routine( tmp_rlwe_out[0].as_mut(), scratch_matrix_d_ring, ksk_a,@@ -127,7 +134,7 @@ mod_op,
); // b' += decomp<a(X^k)> * RLWE'_B(s(X^k)) - routine( + poly_fma_routine( tmp_rlwe_out[1].as_mut(), scratch_matrix_d_ring, ksk_b,@@ -181,6 +188,12 @@ .get_row_mut(1)
.copy_from_slice(tmp_rlwe_out[1].as_ref()); } +/// Sends RLWE_{s(X)}(m(X)) -> RLWE_{s(X)}(m{X^k}) where k is some galois +/// element +/// +/// This is same as `galois_auto` with the difference that alongside `ksk` with +/// key switching polynomials in evaluation domain, shoup representation, +/// `ksk_shoup`, of the polynomials in evaluation domain is also supplied. pub(crate) fn galois_auto_shoup< MT: Matrix + IsTrivial + MatrixMut, Mmut: MatrixMut<MatElement = MT::MatElement>,@@ -309,14 +322,12 @@ .get_row_mut(1)
.copy_from_slice(tmp_rlwe_out[1].as_ref()); } -/// Returns RLWE(m0m1) = RLWE(m0) x RGSW(m1). Mutates rlwe_in inplace to equal -/// RLWE(m0m1) +/// Inplace mutates RLWE(m0) to equal RLWE(m0m1) = RLWE(m0) x RGSW(m1). /// /// - rlwe_in: is RLWE(m0) with polynomials in coefficient domain /// - rgsw_in: is RGSW(m1) with polynomials in evaluation domain -/// - scratch_matrix_d_ring: is a matrix with atleast max(d_a, d_b) rows and -/// ring_size columns. It's used to store decomposed polynomials and out RLWE -/// temoporarily +/// - scratch_matrix: with dimension (max(d_a, d_b) + 2) x ring_size columns. +/// It's used to store decomposed polynomials and out RLWE temporarily pub(crate) fn rlwe_by_rgsw< Mmut: MatrixMut, MT: Matrix<MatElement = Mmut::MatElement> + MatrixMut<MatElement = Mmut::MatElement> + IsTrivial,@@ -365,14 +376,14 @@ .iter_mut()
.take(d_a) .for_each(|r| ntt_op.forward(r.as_mut())); // a_out += decomp<a_in> \cdot RLWE_A'(-sm) - routine( + poly_fma_routine( scratch_rlwe_out[0].as_mut(), &scratch_matrix_d_ring[..d_a], &rlwe_dash_nsm[..d_a], mod_op, ); // b_out += decomp<a_in> \cdot RLWE_B'(-sm) - routine( + poly_fma_routine( scratch_rlwe_out[1].as_mut(), &scratch_matrix_d_ring[..d_a], &rlwe_dash_nsm[d_a..],@@ -390,14 +401,14 @@ .iter_mut()
.take(d_b) .for_each(|r| ntt_op.forward(r.as_mut())); // a_out += decomp<b_in> \cdot RLWE_A'(m) - routine( + poly_fma_routine( scratch_rlwe_out[0].as_mut(), &scratch_matrix_d_ring[..d_b], &rlwe_dash_m[..d_b], mod_op, ); // b_out += decomp<b_in> \cdot RLWE_B'(m) - routine( + poly_fma_routine( scratch_rlwe_out[1].as_mut(), &scratch_matrix_d_ring[..d_b], &rlwe_dash_m[d_b..],@@ -418,6 +429,11 @@ .copy_from_slice(scratch_rlwe_out[1].as_mut());
rlwe_in.set_not_trivial(); } +/// Inplace mutates RLWE(m0) to equal RLWE(m0m1) = RLWE(m0) x RGSW(m1). +/// +/// Same as `rlwe_by_rgsw` with the difference that alongside `rgsw_in` with +/// polynomials in evaluation domain, shoup representation of polynomials in +/// evaluation domain, `rgsw_in_shoup`, is also supplied. pub(crate) fn rlwe_by_rgsw_shoup< Mmut: MatrixMut, MT: Matrix<MatElement = Mmut::MatElement> + MatrixMut<MatElement = Mmut::MatElement> + IsTrivial,@@ -528,27 +544,32 @@ .copy_from_slice(scratch_rlwe_out[1].as_mut());
rlwe_in.set_not_trivial(); } -/// Inplace mutates rlwe_0 to equal RGSW(m0m1) = RGSW(m0)xRGSW(m1) -/// in evaluation domain +/// Inplace mutates RGSW(m0) to equal RGSW(m0m1) = RGSW(m0)xRGSW(m1) +/// +/// RGSW x RGSW product requires multiple RLWE x RGSW products. For example, +/// Define +/// +/// RGSW(m0) = [RLWE(-sm), RLWE(\beta -sm), ..., RLWE(\beta^{d-1} -sm) +/// RLWE(m), RLWE(\beta m), ..., RLWE(\beta^{d-1} m)] +/// And RGSW(m1) /// -/// Warning - -/// Pass a fresh RGSW ciphertext as the second operand, i.e. as `rgsw_1`. -/// This is to assure minimal error growth in the resulting RGSW ciphertext. -/// RGSWxRGSW boils down to d_rgsw*2 RLWExRGSW multiplications. Hence, the noise -/// growth in resulting ciphertext depends on the norm of second RGSW -/// ciphertext, not the first. This is useful in cases where one is accumulating -/// multiple RGSW ciphertexts into 1. In which case, pass the accumulating RGSW -/// ciphertext as rlwe_0 (the one with higher noise) and subsequent RGSW -/// ciphertexts, with lower noise, to be accumulated as second -/// operand. +/// Then RGSW(m0) x RGSW(m1) equals: +/// RGSW(m0m1) = [ +/// rlwe_x_rgsw(RLWE(-sm), RGSW(m1)), +/// ..., +/// rlwe_x_rgsw(RLWE(\beta^{d-1} -sm), RGSW(m1)), +/// rlwe_x_rgsw(RLWE(m), RGSW(m1)), +/// ..., +/// rlwe_x_rgsw(RLWE(\beta^{d-1} m), RGSW(m1)), +/// ] /// -/// - rgsw_0: RGSW(m0) -/// - rgsw_1_eval: RGSW(m1) in Evaluation domain -/// - scratch_matrix_d_plus_rgsw_by_ring: scratch space matrix with rows -/// (max(d_a, d_b) + d_a*2+d_b*2) and columns ring_size +/// Since noise growth in RLWE x RGSW depends on noise in RGSW ciphertext, it is +/// clear to observe from above that noise in resulting RGSW(m0m1) equals noise +/// accumulated in a single RLWE x RGSW and depends on noise in RGSW(m1) (i.e. +/// rgsw_1_eval) /// -/// ## Note: -/// - We treat RGSW x RGSW as multiple RLWE x RGSW multiplications. . +/// - rgsw_0: RGSW(m0) in coefficient domain +/// - rgsw_1_eval: RGSW(m1) in evaluation domain pub(crate) fn rgsw_by_rgsw_inplace< Mmut: MatrixMut, D: RlweDecomposer<Element = Mmut::MatElement>,@@ -620,13 +641,13 @@ decomp_r_space
.iter_mut() .take(d_a) .for_each(|ri| ntt_op.forward(ri.as_mut())); - routine( + poly_fma_routine( rlwe_out_a.as_mut(), &decomp_r_space[..d_a], &rgsw1_nsm[..d_a], mod_op, ); - routine( + poly_fma_routine( rlwe_out_b.as_mut(), &decomp_r_space[..d_a], &rgsw1_nsm[d_a..],@@ -639,13 +660,13 @@ decomp_r_space
.iter_mut() .take(d_b) .for_each(|ri| ntt_op.forward(ri.as_mut())); - routine( + poly_fma_routine( rlwe_out_a.as_mut(), &decomp_r_space[..d_b], &rgsw1_m[..d_b], mod_op, ); - routine( + poly_fma_routine( rlwe_out_b.as_mut(), &decomp_r_space[..d_b], &rgsw1_m[d_b..],@@ -663,7 +684,21 @@ .iter_rows_mut()
.for_each(|ri| ntt_op.backward(ri.as_mut())); } -pub(crate) fn key_switch< +/// Key switches input RLWE_{s'}(m) -> RLWE_{s}(m) +/// +/// Let RLWE_{s'}(m) = [a, b] s.t. m+e = b - as' +/// +/// Given key switchin key Ksk(s' -> s) = RLWE'_{s}(s') = [RLWE_{s}(beta^i s')] +/// = [a, a*s + e + beta^i s'] for i \in [0,d), key switching computes: +/// 1. RLWE_{s}(-s'a) = \sum signed_decompose(-a)[i] RLWE_{s}(beta^i s') +/// 2. RLWE_{s}(m) = (b, 0) + RLWE_{s}(-s'a) +/// +/// - rlwe_in: Input rlwe ciphertext +/// - ksk: Key switching key Ksk(s' -> s) with polynomials in evaluation domain +/// - ksk_shoup: Key switching key Ksk(s' -> s) with polynomials in evaluation +/// domain in shoup representation +/// - decomposer: Decomposer used for key switching +pub(crate) fn rlwe_key_switch< M: MatrixMut + MatrixEntity, ModOp: GetModulus<Element = M::MatElement> + ShoupMatrixFMA<M::R> + VectorOps<Element = M::MatElement>, NttOp: Ntt<Element = M::MatElement>,