Homomorphic additions, multiplications, and rotations for vectors of real numbers via CKKS
Overview
Homomorphic encryption allows computations on encrypted data without decrypting it. The CKKS (Cheon-Kim-Kim-Song) scheme supports approximate arithmetic operations on encrypted data, making it suitable for real number calculations.
The example walks through the setup of cryptographic parameters, key generation, encryption of plaintext vectors, performing homomorphic operations, and decrypting the results. The example for this code is located in :code:`examples/simple_real_numbers.rs <https://github.com/fairmath/openfhe-rs/blob/master/examples/simple_real_numbers.rs>`_.
Code breakdown
Importing libraries
We start by importing the necessary libraries and modules:
use openfhe::cxx::{CxxVector};
use openfhe::ffi as ffi;
The code example
The main function contains the entire workflow for setting up the CKKS scheme, performing encryption, executing homomorphic operations, and decrypting the results.
fn main()
{
// Define cryptographic parameters for CKKS
let _mult_depth: u32 = 1;
let _scale_mod_size: u32 = 50;
let _batch_size: u32 = 8;
Generating Parameters
We define the cryptographic parameters for the CKKS scheme, including multiplicative depth, scaling modulus size, and batch size.
// Generate parameters for CKKS scheme
let mut _cc_params_ckksrns = ffi::GenParamsCKKSRNS();
_cc_params_ckksrns.pin_mut().SetMultiplicativeDepth(_mult_depth);
_cc_params_ckksrns.pin_mut().SetScalingModSize(_scale_mod_size);
_cc_params_ckksrns.pin_mut().SetBatchSize(_batch_size);
Creating Crypto Context
We create a crypto context based on the defined parameters and enable necessary features.
// Create crypto context based on parameters
let _cc = ffi::DCRTPolyGenCryptoContextByParamsCKKSRNS(&_cc_params_ckksrns);
_cc.EnableByFeature(ffi::PKESchemeFeature::PKE);
_cc.EnableByFeature(ffi::PKESchemeFeature::KEYSWITCH);
_cc.EnableByFeature(ffi::PKESchemeFeature::LEVELEDSHE);
// Outputing the ring dimension for clarity
println!("CKKS scheme is using ring dimension {}\n", _cc.GetRingDimension());
Key Generation
We generate the necessary keys for encryption, including evaluation keys for multiplication and rotation.
// Key generation
let _key_pair = _cc.KeyGen();
_cc.EvalMultKeyGen(&_key_pair.GetPrivateKey());
// Generate rotation keys
let mut _index_list = CxxVector::<i32>::new();
_index_list.pin_mut().push(1);
_index_list.pin_mut().push(-2);
_cc.EvalRotateKeyGen(&_key_pair.GetPrivateKey(), &_index_list, &ffi::DCRTPolyGenNullPublicKey());
Creating Input Vectors
We create two input vectors for the demonstration.
// Create input vectors
let mut _x_1 = CxxVector::<f64>::new();
_x_1.pin_mut().push(0.25);
...
_x_1.pin_mut().push(5.0);
let mut _x_2 = CxxVector::<f64>::new();
_x_2.pin_mut().push(5.0);
...
_x_2.pin_mut().push(0.25);
Creating Plaintext Objects
We convert the input vectors into plaintext objects.
// Create plaintext objects from vectors
let _dcrt_poly_params = ffi::DCRTPolyGenNullParams();
let _p_txt_1 = _cc.MakeCKKSPackedPlaintextByVectorOfDouble(&_x_1, 1, 0, &_dcrt_poly_params, 0);
let _p_txt_2 = _cc.MakeCKKSPackedPlaintextByVectorOfDouble(&_x_2, 1, 0, &_dcrt_poly_params, 0);
// Outputing the vectors for clarity
println!("Input x1: {}", _p_txt_1.GetString());
println!("Input x2: {}", _p_txt_2.GetString());
Encrypting Plaintext Vectors
We encrypt the plaintext vectors using the generated public key.
// Encrypt plaintext vectors
let _c1 = _cc.EncryptByPublicKey(&_key_pair.GetPublicKey(), &_p_txt_1);
let _c2 = _cc.EncryptByPublicKey(&_key_pair.GetPublicKey(), &_p_txt_2);
Performing Homomorphic Operations
We perform various homomorphic operations on the encrypted data, including addition, subtraction, multiplication by a constant, multiplication of ciphertexts, and rotations.
// Perform homomorphic operations
let _c_add = _cc.EvalAddByCiphertexts(&_c1, &_c2);
let _c_sub = _cc.EvalSubByCiphertexts(&_c1, &_c2);
let _c_scalar = _cc.EvalMultByCiphertextAndConst(&_c1, 4.0);
let _c_mul = _cc.EvalMultByCiphertexts(&_c1, &_c2);
let _c_rot_1 = _cc.EvalRotate(&_c1, 1);
let _c_rot_2 = _cc.EvalRotate(&_c1, -2);
Decrypting and Printing Results
Finally, we decrypt the results of the homomorphic computations and print them.
// Prepare for decryption
let mut _result = ffi::GenNullPlainText();
println!("\nResults of homomorphic computations:");
// Decrypt and print results
_cc.DecryptByPrivateKeyAndCiphertext(&_key_pair.GetPrivateKey(), &_c1, _result.pin_mut());
_result.SetLength(_batch_size.try_into().unwrap());
println!("x1 = {}Estimated precision in bits: {}", _result.GetString(), _result.GetLogPrecision());
_cc.DecryptByPrivateKeyAndCiphertext(&_key_pair.GetPrivateKey(), &_c_add, _result.pin_mut());
_result.SetLength(_batch_size.try_into().unwrap());
println!("x1 + x2 = {}Estimated precision in bits: {}",_result.GetString(), _result.GetLogPrecision());
_cc.DecryptByPrivateKeyAndCiphertext(&_key_pair.GetPrivateKey(), &_c_sub, _result.pin_mut());
_result.SetLength(_batch_size.try_into().unwrap());
println!("x1 - x2 = {}", _result.GetString());
_cc.DecryptByPrivateKeyAndCiphertext(&_key_pair.GetPrivateKey(), &_c_scalar, _result.pin_mut());
_result.SetLength(_batch_size.try_into().unwrap());
println!("4 * x1 = {}", _result.GetString());
_cc.DecryptByPrivateKeyAndCiphertext(&_key_pair.GetPrivateKey(), &_c_mul, _result.pin_mut());
_result.SetLength(_batch_size.try_into().unwrap());
println!("x1 * x2 = {}", _result.GetString());
_cc.DecryptByPrivateKeyAndCiphertext(&_key_pair.GetPrivateKey(), &_c_rot_1, _result.pin_mut());
_result.SetLength(_batch_size.try_into().unwrap());
println!("\nIn rotations, very small outputs (~10^-10 here) correspond to 0's:");
println!("x1 rotate by 1 = {}", _result.GetString());
_cc.DecryptByPrivateKeyAndCiphertext(&_key_pair.GetPrivateKey(), &_c_rot_2, _result.pin_mut());
_result.SetLength(_batch_size.try_into().unwrap());
println!("x1 rotate by -2 = {}", _result.GetString());
Running the example
Ensure the openfhe-rs library is installed and properly configured, see the About OpenFHE-rs section.
Go to the openfhe-rs directory.
Compile and run the simple_real_numbers.rs example:
cargo run --example simple_real_numbers
This should output the results of the homomorphic computations to the console.