From 71901378b079c7dcb9ce9d7597dbbbb184be3bd3 Mon Sep 17 00:00:00 2001 From: Janmajaya Mall Date: Fri, 28 Jun 2024 19:55:42 +0530 Subject: [PATCH] clean rgsw/runtime --- src/bool/evaluator.rs | 4 +- src/bool/ni_mp_api.rs | 4 +- src/bool/print_noise.rs | 4 +- src/rgsw/mod.rs | 90 ++++---------------------------- src/rgsw/runtime.rs | 113 ++++++++++++++++++++++++++-------------- 5 files changed, 90 insertions(+), 125 deletions(-) diff --git a/src/bool/evaluator.rs b/src/bool/evaluator.rs index d2b9aee..d09434c 100644 --- a/src/bool/evaluator.rs +++ b/src/bool/evaluator.rs @@ -25,8 +25,8 @@ use crate::{ 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, diff --git a/src/bool/ni_mp_api.rs b/src/bool/ni_mp_api.rs index b5d7353..31891d3 100644 --- a/src/bool/ni_mp_api.rs +++ b/src/bool/ni_mp_api.rs @@ -178,7 +178,7 @@ mod impl_enc_dec { 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 @@ mod impl_enc_dec { 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(), diff --git a/src/bool/print_noise.rs b/src/bool/print_noise.rs index 3522ea1..85aa730 100644 --- a/src/bool/print_noise.rs +++ b/src/bool/print_noise.rs @@ -10,7 +10,7 @@ use crate::{ 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 @@ where 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, diff --git a/src/rgsw/mod.rs b/src/rgsw/mod.rs index d9e4545..26c786c 100644 --- a/src/rgsw/mod.rs +++ b/src/rgsw/mod.rs @@ -9,14 +9,11 @@ use std::{ }; 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 @@ where }); // 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 @@ pub(crate) mod tests { 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 @@ pub(crate) mod tests { seeded_rgsw_ct } - /// Prints noise in RGSW ciphertext RGSW(m). - /// - /// - rgsw_ct: RGSW ciphertext in coefficient domain - pub(crate) fn _measure_noise_rgsw + Clone>( - rgsw_ct: &[Vec], - m: &[u64], - s: &[i32], - decomposer: &(DefaultDecomposer, DefaultDecomposer), - 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::::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 @@ pub(crate) mod tests { .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 @@ pub(crate) mod tests { // normal galois auto { - galois_auto( + rlwe_auto( &mut rlwe_m, &auto_key.data, &mut scratch_space, @@ -1104,7 +1033,7 @@ pub(crate) mod tests { ) ]; - _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 @@ pub(crate) mod tests { // 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) diff --git a/src/rgsw/runtime.rs b/src/rgsw/runtime.rs index d1d0eba..f4ba44b 100644 --- a/src/rgsw/runtime.rs +++ b/src/rgsw/runtime.rs @@ -10,7 +10,7 @@ use crate::{ use super::IsTrivial; -pub(crate) fn routine>( +pub(crate) fn poly_fma_routine>( write_to_row: &mut [R::Element], matrix_a: &[R], matrix_b: &[R], @@ -48,11 +48,18 @@ pub(crate) fn decompose_r>( } } -/// 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, ModOp: ArithmeticOps + VectorOps, @@ -119,7 +126,7 @@ pub(crate) fn galois_auto< // key switch: (a * RLWE'(s(X^k))) let (ksk_a, ksk_b) = ksk.split_at_row(d); // a' = decomp * 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 @@ pub(crate) fn galois_auto< ); // b' += decomp * 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 @@ pub(crate) fn galois_auto< .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, @@ -309,14 +322,12 @@ pub(crate) fn galois_auto_shoup< .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 + MatrixMut + IsTrivial, @@ -365,14 +376,14 @@ pub(crate) fn rlwe_by_rgsw< .take(d_a) .for_each(|r| ntt_op.forward(r.as_mut())); // a_out += decomp \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 \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 @@ pub(crate) fn rlwe_by_rgsw< .take(d_b) .for_each(|r| ntt_op.forward(r.as_mut())); // a_out += decomp \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 \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 @@ pub(crate) fn rlwe_by_rgsw< 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 + MatrixMut + IsTrivial, @@ -528,27 +544,32 @@ pub(crate) fn rlwe_by_rgsw_shoup< 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, @@ -620,13 +641,13 @@ pub(crate) fn rgsw_by_rgsw_inplace< .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 @@ pub(crate) fn rgsw_by_rgsw_inplace< .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 @@ pub(crate) fn rgsw_by_rgsw_inplace< .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 + ShoupMatrixFMA + VectorOps, NttOp: Ntt,