Rotations WIP

This commit is contained in:
Rick Weber
2021-11-09 17:31:41 -08:00
parent bcd8f45fed
commit 71242f005c
3 changed files with 217 additions and 47 deletions

View File

@@ -3,7 +3,7 @@ use std::ptr::null_mut;
use crate::bindgen;
use crate::error::*;
use crate::{Ciphertext, Context, Plaintext, RelinearizationKeys};
use crate::{Ciphertext, Context, GaloisKeys, Plaintext, RelinearizationKeys};
/**
* Provides operations on ciphertexts. Due to the properties of the encryption scheme, the arithmetic operations
@@ -428,14 +428,13 @@ impl Evaluator {
self.get_handle(),
a.get_handle(),
b.get_handle(),
c.get_handle()
c.get_handle(),
)
})?;
Ok(c)
}
/**
* Adds a ciphertext and a plaintext.
* * `a` - the ciphertext
@@ -447,13 +446,13 @@ impl Evaluator {
self.get_handle(),
a.get_handle(),
b.get_handle(),
a.get_handle()
a.get_handle(),
)
})?;
Ok(())
}
/**
* Subtract a plaintext from a ciphertext.
* * `a` - the ciphertext
@@ -467,14 +466,13 @@ impl Evaluator {
self.get_handle(),
a.get_handle(),
b.get_handle(),
c.get_handle()
c.get_handle(),
)
})?;
Ok(c)
}
/**
* Subtract a plaintext from a ciphertext and store the result in the ciphertext.
* * `a` - the ciphertext
@@ -486,7 +484,7 @@ impl Evaluator {
self.get_handle(),
a.get_handle(),
b.get_handle(),
a.get_handle()
a.get_handle(),
)
})?;
@@ -507,14 +505,13 @@ impl Evaluator {
a.get_handle(),
b.get_handle(),
c.get_handle(),
null_mut()
null_mut(),
)
})?;
Ok(c)
}
/**
* Multiply a ciphertext by a plaintext and store in the ciphertext.
* * `a` - the ciphertext
@@ -527,7 +524,7 @@ impl Evaluator {
a.get_handle(),
b.get_handle(),
a.get_handle(),
null_mut()
null_mut(),
)
})?;
@@ -604,6 +601,40 @@ impl BFVEvaluator {
Ok(out)
}
/**
* Rotates plaintext matrix rows cyclically.
*
* When batching is used with the BFV scheme, this function rotates the encrypted plaintext matrix rows
* cyclically to the left (steps > 0) or to the right (steps < 0). Since the size of the batched matrix
* is 2-by-(N/2), where N is the degree of the polynomial modulus, the number of steps to rotate must have
* absolute value at most N/2-1.
*
* * `a` - The ciphertext to rotate
* * `steps` - The number of steps to rotate (positive left, negative right)
* * `galois_keys` - The Galois keys
*/
pub fn rotate_rows(
&self,
a: &Ciphertext,
steps: i32,
galois_keys: &GaloisKeys,
) -> Result<Ciphertext> {
let out = Ciphertext::new()?;
convert_seal_error(unsafe {
bindgen::Evaluator_RotateRows(
self.handle,
a.get_handle(),
steps,
galois_keys.get_handle(),
out.get_handle(),
null_mut(),
)
})?;
Ok(out)
}
}
#[cfg(test)]
@@ -612,7 +643,7 @@ mod tests {
fn run_bfv_test<F>(test: F)
where
F: FnOnce(Decryptor, BFVEncoder, Encryptor, BFVEvaluator, RelinearizationKeys),
F: FnOnce(Decryptor, BFVEncoder, Encryptor, BFVEvaluator, RelinearizationKeys, GaloisKeys),
{
let params = BfvEncryptionParametersBuilder::new()
.set_poly_modulus_degree(8192)
@@ -631,6 +662,7 @@ mod tests {
let public_key = gen.create_public_key();
let secret_key = gen.secret_key();
let relinearization_keys = gen.create_relinearization_keys();
let galois_keys = gen.create_galois_keys();
let encryptor =
Encryptor::with_public_and_secret_key(&ctx, &public_key, &secret_key).unwrap();
@@ -643,6 +675,7 @@ mod tests {
encryptor,
evaluator,
relinearization_keys,
galois_keys
);
}
@@ -686,7 +719,7 @@ mod tests {
#[test]
fn can_negate() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let a_c = encryptor.encrypt(&a_p).unwrap();
@@ -706,7 +739,7 @@ mod tests {
#[test]
fn can_negate_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let mut a_c = encryptor.encrypt(&a_p).unwrap();
@@ -726,7 +759,7 @@ mod tests {
#[test]
fn can_add() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -750,7 +783,7 @@ mod tests {
#[test]
fn can_add_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -774,7 +807,7 @@ mod tests {
#[test]
fn can_add_many() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let c = make_vec(&encoder);
@@ -809,7 +842,7 @@ mod tests {
#[test]
fn can_multiply_many() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys, _| {
let a = make_small_vec(&encoder);
let b = make_small_vec(&encoder);
let c = make_small_vec(&encoder);
@@ -844,7 +877,7 @@ mod tests {
#[test]
fn can_sub() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -868,7 +901,7 @@ mod tests {
#[test]
fn can_sub_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -892,7 +925,7 @@ mod tests {
#[test]
fn can_multiply() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -916,7 +949,7 @@ mod tests {
#[test]
fn can_multiply_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -940,7 +973,7 @@ mod tests {
#[test]
fn can_square() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let a_c = encryptor.encrypt(&a_p).unwrap();
@@ -960,7 +993,7 @@ mod tests {
#[test]
fn can_square_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let mut a_c = encryptor.encrypt(&a_p).unwrap();
@@ -980,7 +1013,7 @@ mod tests {
#[test]
fn can_relinearize_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys, _| {
let a = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let mut a_c = encryptor.encrypt(&a_p).unwrap();
@@ -1012,7 +1045,7 @@ mod tests {
#[test]
fn can_relinearize() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys, _| {
let a = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let mut a_c = encryptor.encrypt(&a_p).unwrap();
@@ -1040,7 +1073,7 @@ mod tests {
#[test]
fn can_exponentiate() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys, _galois| {
let a = make_small_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let a_c = encryptor.encrypt(&a_p).unwrap();
@@ -1053,22 +1086,21 @@ mod tests {
assert_eq!(a.len(), c.len());
for i in 0..a.len() {
assert_eq!(
c[i],
a[i] * a[i] * a[i] * a[i]
);
assert_eq!(c[i], a[i] * a[i] * a[i] * a[i]);
}
});
}
#[test]
fn can_exponentiate_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, relin_keys, _galois| {
let a = make_small_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let a_c = encryptor.encrypt(&a_p).unwrap();
evaluator.exponentiate_inplace(&a_c, 4, &relin_keys).unwrap();
evaluator
.exponentiate_inplace(&a_c, 4, &relin_keys)
.unwrap();
let a_p = decryptor.decrypt(&a_c).unwrap();
let c = encoder.decode_signed(&a_p).unwrap();
@@ -1076,17 +1108,14 @@ mod tests {
assert_eq!(a.len(), c.len());
for i in 0..a.len() {
assert_eq!(
c[i],
a[i] * a[i] * a[i] * a[i]
);
assert_eq!(c[i], a[i] * a[i] * a[i] * a[i]);
}
});
}
#[test]
fn can_add_plain() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -1109,7 +1138,7 @@ mod tests {
#[test]
fn can_add_plain_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -1132,7 +1161,7 @@ mod tests {
#[test]
fn can_sub_plain() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -1155,7 +1184,7 @@ mod tests {
#[test]
fn can_sub_plain_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -1178,7 +1207,7 @@ mod tests {
#[test]
fn can_multiply_plain() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -1201,7 +1230,7 @@ mod tests {
#[test]
fn can_multiply_plain_inplace() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _| {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, _galois| {
let a = make_vec(&encoder);
let b = make_vec(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
@@ -1221,4 +1250,37 @@ mod tests {
}
});
}
fn make_matrix(encoder: &BFVEncoder) -> Vec<i64> {
let dim = encoder.get_slot_count();
let dim_2 = dim / 2;
let mut matrix = vec![0i64; dim];
matrix[0] = 1;
matrix[1] = -2;
matrix[dim_2] = -1;
matrix[dim_2 + 1] = 2;
matrix
}
#[test]
fn can_rotate_rows() {
run_bfv_test(|decryptor, encoder, encryptor, evaluator, _, galois_keys| {
let a = make_matrix(&encoder);
let a_p = encoder.encode_signed(&a).unwrap();
let a_c = encryptor.encrypt(&a_p).unwrap();
let c_c = evaluator.rotate_rows(&a_c, -1, &galois_keys).unwrap();
let c_p = decryptor.decrypt(&c_c).unwrap();
let c = encoder.decode_signed(&c_p).unwrap();
assert_eq!(a[0], c[1]);
assert_eq!(a[1], c[2]);
assert_eq!(a[4096], c[4097]);
assert_eq!(a[4097], c[4098]);
});
}
}

View File

@@ -131,7 +131,54 @@ impl KeyGenerator {
RelinearizationKeys { handle }
}
// TODO: Galois keys
/**
* Generates and returns Galois keys as a serializable object.
*
* Generates and returns Galois keys as a serializable object. Every time
* this function is called, new Galois keys will be generated.
*
* Half of the key data is pseudo-randomly generated from a seed to reduce
* the object size. The resulting serializable object cannot be used
* directly and is meant to be serialized for the size reduction to have an
* impact.
*
* This function creates logarithmically many (in degree of the polynomial
* modulus) Galois keys that is sufficient to apply any Galois automorphism
* (e.g. rotations) on encrypted data. Most users will want to use this
* overload of the function.
*/
pub fn create_compact_galois_keys(&self) -> CompactGaloisKeys {
CompactGaloisKeys(self.create_galois_keys_internal(true))
}
/**
* Generates Galois keys and stores the result in destination.
* </summary>
* <remarks>
* <para>
* Generates Galois keys and stores the result in destination. Every time
* this function is called, new Galois keys will be generated.
* </para>
* <para>
* This function creates logarithmically many (in degree of the polynomial
* modulus) Galois keys that is sufficient to apply any Galois automorphism
* (e.g. rotations) on encrypted data. Most users will want to use this
* overload of the function.
*/
pub fn create_galois_keys(&self) -> GaloisKeys {
self.create_galois_keys_internal(false)
}
fn create_galois_keys_internal(&self, save_seed: bool) -> GaloisKeys {
let mut handle = null_mut();
convert_seal_error(unsafe {
bindgen::KeyGenerator_CreateGaloisKeysAll(self.handle, save_seed, &mut handle)
})
.expect("Fatal error in KeyGenerator::create_galois_keys()");
GaloisKeys { handle }
}
}
impl Drop for KeyGenerator {
@@ -275,16 +322,60 @@ impl Drop for RelinearizationKeys {
// destructor.
bindgen::KSwitchKeys_Destroy(self.handle)
})
.expect("Fatal error in PublicKey::drop")
.expect("Fatal error in PublicKey::drop()")
}
}
/**
* A relinearization key that stores a random number seed to generate the rest of the key.
* This form isn't directly usable, but serializes in a very compact representation.
* This form isn't directly usable, but serializes in a compact representation.
*/
pub struct CompactRelinearizationKeys(RelinearizationKeys);
/**
* Class to store Galois keys.
*
* Slot rotations
* Galois keys are certain types of public keys that are needed to perform encrypted
* vector rotation operations on batched ciphertexts. Batched ciphertexts encrypt
* a 2-by-(N/2) matrix of modular integers in the BFV scheme, or an N/2-dimensional
* vector of complex numbers in the CKKS scheme, where N denotes the degree of the
* polynomial modulus. In the BFV scheme Galois keys can enable both cyclic rotations
* of the encrypted matrix rows, as well as row swaps (column rotations). In the CKKS
* scheme Galois keys can enable cyclic vector rotations, as well as a complex
* conjugation operation.
*/
pub struct GaloisKeys {
handle: *mut c_void,
}
impl GaloisKeys {
/**
* Returns the handle to the underlying SEAL object.
*/
pub fn get_handle(&self) -> *mut c_void {
self.handle
}
}
impl Drop for GaloisKeys {
fn drop(&mut self) {
convert_seal_error(unsafe {
// GaloisKeys doesn't have a destructor, but inherits
// from KSwitchKeys, which does. Just call the base class's
// destructor.
bindgen::KSwitchKeys_Destroy(self.handle)
})
.expect("Fatal error in GaloisKeys::drop()")
}
}
/**
* A galois key set that stores a random number seed to generate the rest of the key.
* This form isn't directly usable, but serializes in a compact representation.
*/
pub struct CompactGaloisKeys(GaloisKeys);
#[cfg(test)]
mod tests {
use crate::*;
@@ -350,6 +441,23 @@ mod tests {
gen.create_relinearization_keys();
}
#[test]
fn can_create_galois_key() {
let params = BfvEncryptionParametersBuilder::new()
.set_poly_modulus_degree(8192)
.set_coefficient_modulus(
CoefficientModulus::bfv_default(8192, SecurityLevel::TC128).unwrap(),
)
.set_plain_modulus(PlainModulus::batching(8192, 32).unwrap())
.build()
.unwrap();
let ctx = Context::new(&params, false, SecurityLevel::TC128).unwrap();
let gen = KeyGenerator::new(&ctx).unwrap();
gen.create_galois_keys();
}
#[test]
fn can_init_from_existing_secret_key() {
let params = BfvEncryptionParametersBuilder::new()

View File

@@ -44,6 +44,6 @@ pub use encryption_parameters::*;
pub use encryptor_decryptor::{Decryptor, Encryptor};
pub use error::{Error, Result};
pub use evaluator::{BFVEvaluator, Evaluator};
pub use key_generator::{KeyGenerator, PublicKey, RelinearizationKeys, SecretKey};
pub use key_generator::{GaloisKeys, KeyGenerator, PublicKey, RelinearizationKeys, SecretKey};
pub use modulus::{CoefficientModulus, Modulus, PlainModulus, SecurityLevel};
pub use plaintext_ciphertext::{Ciphertext, Plaintext};