Homomorphic Encryption With CKKS and BFV For The Inner Product of Two Vectors

--

Encryption is one of the weakest implementations within cybersecurity, and where data which is not encrypted can be exposed to data breaches. And, so, data over the air and data at rest are fairly well covered with encryption, what happens to data in-process? Let’s say a central bank needs to determine the total income and expenditure from all its banks and without any of the banks giving away their sensitive data. Well, one way is to get each bank (Bob, Alice and Peggy) to encrypt their data in a homomorphic public key and then compute the totals for income and expenditure using a homomorphic computing method for adding. Then the result can be decrypted by the central bank (Wendy) with the associated private key:

So, let’s take an example of a maths function that we use when we have vectors: the inner product, and implement it with the two main homomorphic encryption methods: CKKS and BFV.

CCKS

The CCKS method allows us to encrypt floating point values, and the operate on these in a homomorphic way. For our example, the inner product of two vectors of a and b is represented by ⟨a,b⟩. It is the dot product of two vectors, and represented as ⟨a,b⟩=a.y.cos(θ), and where θ is the angle between the two vectors. If we have a vector of a=(10,20,15), then the magniture will be:

If we have the same vector of b=(10,20,15), we will have the same magnitude. The inner product will then be:

We initially need to compile the OpenFHE code into a library. The Windows build is [here]. Then we create our cpp file, and build with:

g++.exe *.cpp lib.a -o openfhe_01  -I../../openfhe_main/src/pke/include -I../../openfhe_main/src/binfhe/include -I../../openfhe_main/src/core/include -I../../openfhe_main/src/core -I../../openfhe_main/build/src/core -I../../openfhe_main/src/binfhe -I../../openfhe_main/third-party/cereal/include

With this, we will encrypt the vectors with a public key and then perform an inner production operation:

KeyPair keys = cc->KeyGen();
cc->EvalMultKeyGen(keys.secretKey);
cc->EvalSumKeyGen(keys.secretKey);

Plaintext plaintext1 = cc->MakeCKKSPackedPlaintext(v1);
auto ct1 = cc->Encrypt(keys.publicKey, plaintext1);
Plaintext plaintext2 = cc->MakeCKKSPackedPlaintext(v2);
auto ct2 = cc->Encrypt(keys.publicKey, plaintext2);

and decrypt with the secret key:

lbcrypto::Plaintext res;
cc->Decrypt(keys.secretKey, finalResult, &res);

The full coding is [here]:

#include <openfhe.h>
using namespace lbcrypto;
using namespace std;
#include <iostream>
#include <vector>
#include <algorithm>

std::string ReadAndRemoveFirstTokenFromString (const char &separator, std::string& line) // faster than stringstream, at least for reading first element
{
auto found=line.find(separator);
if (found==std::string::npos)
{ string hold=line;
line.clear();
return hold;
}
else
{
std::string out=line.substr(0,found);
line=line.substr(found+1,line.size());
while (line[0]==' ') line=line.substr(1,line.size());
if (out=="") out="-999999.0";
return out;
}
}
vector<double> split(string a)
{
std::vector <double> number;
while(a.size()>0)
{
string num=ReadAndRemoveFirstTokenFromString(' ', a);
if ((num=="\0") || (num.empty())) number.emplace_back(-999999.0);
else number.emplace_back(stod(num));
}
return number;
}
int main(int argc, char *argv[]) {

lbcrypto::SecurityLevel securityLevel = lbcrypto::HEStd_NotSet;
uint32_t dcrtBits = 59;
uint32_t ringDim = 1 << 8;
uint32_t batchSize = ringDim / 2;
lbcrypto::CCParams<lbcrypto::CryptoContextCKKSRNS> parameters;
uint32_t multDepth = 10;
string s1="10 20 30";
string s2="10 20 30";
if (argc>1) {
s1= (argv[1]);
}
if (argc>2) {
s2=(argv[2]);
}
auto v1= split(s1);
auto v2= split(s2);

parameters.SetMultiplicativeDepth(multDepth);
parameters.SetScalingModSize(dcrtBits);
parameters.SetBatchSize(batchSize);
parameters.SetSecurityLevel(securityLevel);
parameters.SetRingDim(ringDim);
lbcrypto::CryptoContext<lbcrypto::DCRTPoly> cc;
cc = GenCryptoContext(parameters);
cc->Enable(PKE);
cc->Enable(LEVELEDSHE);
cc->Enable(ADVANCEDSHE);
KeyPair keys = cc->KeyGen();
cc->EvalMultKeyGen(keys.secretKey);
cc->EvalSumKeyGen(keys.secretKey);
Plaintext plaintext1 = cc->MakeCKKSPackedPlaintext(v1);
auto ct1 = cc->Encrypt(keys.publicKey, plaintext1);
Plaintext plaintext2 = cc->MakeCKKSPackedPlaintext(v2);
auto ct2 = cc->Encrypt(keys.publicKey, plaintext2);
auto finalResult = cc->EvalInnerProduct(ct1, ct2, batchSize);
lbcrypto::Plaintext res;
cc->Decrypt(keys.secretKey, finalResult, &res);
res->SetLength(v1.size());
auto final = res->GetCKKSPackedValue()[0].real();
std::cout << "v1=" << s1 << std::endl;
std::cout << "v2=" << s2 << std::endl;
std::cout << "Inner Product Result: " << final << std::endl;
std::cout << "Expected value: " << inner_product(v1.begin(), v1.end(), v2.begin(), 0) << std::endl;

return 0;
}

A sample run is [here]:

v1=10 20 15
v2=10 20 15
Inner Product Result: 725
Expected value: 725

As there is no angle between the vectors, we can easily check with:

>>> math.sqrt(10**2+20**2+15**2)*math.sqrt(10**2+20**2+15**2)
725.0

The angle between the vectors of x=(3,4,5) and y=(3,3,3) is 11.537∘. We can then compute the inner product with:

and:

Thus:

We can compute in Python with:

math.sqrt(3**2+4**2+5**2)*math.sqrt(3**2+3**2+3**2)*math.cos(11.537/360*2*math.pi)
35.99999474574525

If we check the homomorphic program, we get [here]:

v1=3 4 5
v2=3 3 3
Inner Product Result: 36
Expected value: 36

BFV

The BFV method operates on integer values, while CKKS deals with floating point values. For this, we can simple change our program to support the BFV method [here]:

#include <openfhe.h>

using namespace lbcrypto;
using namespace std;

#include <iostream>
#include <vector>
#include <algorithm>


std::string ReadAndRemoveFirstTokenFromString (const char &separator, std::string& line) // faster than stringstream, at least for reading first element
{
auto found=line.find(separator);
if (found==std::string::npos)
{ string hold=line;
line.clear();
return hold;
}
else
{
std::string out=line.substr(0,found);
line=line.substr(found+1,line.size());
while (line[0]==' ') line=line.substr(1,line.size());
if (out=="") out="-999999";
return out;
}
}
vector<int64_t> split(string a)
{

std::vector <int64_t> number;
while(a.size()>0)
{
string num=ReadAndRemoveFirstTokenFromString(' ', a);
if ((num=="\0") || (num.empty())) number.emplace_back(-999999.0);
else number.emplace_back(stoi(num));
}
return number;
}
int main(int argc, char *argv[]) {





string s1="10 20 30";
string s2="10 20 30";

if (argc>1) {
s1= (argv[1]);

}

if (argc>2) {
s2=(argv[2]);

}

auto v1= split(s1);
auto v2= split(s2);

cout << v1 << endl;
cout << v2 << endl;

CCParams<CryptoContextBFVRNS> parameters;
parameters.SetPlaintextModulus(65537);
parameters.SetMultiplicativeDepth(20);
parameters.SetSecurityLevel(lbcrypto::HEStd_NotSet);
parameters.SetRingDim(1 << 7);
uint32_t batchSize = parameters.GetRingDim() / 2;

lbcrypto::CryptoContext<lbcrypto::DCRTPoly> cc;
cc = GenCryptoContext(parameters);

cc->Enable(PKE);
cc->Enable(LEVELEDSHE);
cc->Enable(ADVANCEDSHE);

KeyPair keys = cc->KeyGen();
cc->EvalMultKeyGen(keys.secretKey);
cc->EvalSumKeyGen(keys.secretKey);

Plaintext plaintext1 = cc->MakePackedPlaintext(v1);
auto ct1 = cc->Encrypt(keys.publicKey, plaintext1);

Plaintext plaintext2 = cc->MakePackedPlaintext(v2);
auto ct2 = cc->Encrypt(keys.publicKey, plaintext2);

auto finalResult = cc->EvalInnerProduct(ct1, ct2, batchSize);
lbcrypto::Plaintext res;
cc->Decrypt(keys.secretKey, finalResult, &res);
res->SetLength(v1.size());
auto final = res-> GetPackedValue()[0];

std::cout << "v1=" << s1 << std::endl;
std::cout << "v2=" << s2 << std::endl;
std::cout << "Inner Product Result: " << final << std::endl;
std::cout << "Expected value: " << inner_product(v1.begin(), v1.end(), v2.begin(), 0) << std::endl;



return 0;
}

A sample run shows that we get the right answer [here]:

v1=10 20 15
v2=10 20 15
Inner Product Result: 725
Expected value: 725

Conclusions

Learn more about the wonderful world of homomorphic encryption here:

References

[1] Cheon, J. H., Kim, A., Kim, M., & Song, Y. (2017). Homomorphic encryption for arithmetic of approximate numbers. In Advances in Cryptology–ASIACRYPT 2017: 23rd International Conference on the Theory and Applications of Cryptology and Information Security, Hong Kong, China, December 3–7, 2017, Proceedings, Part I 23 (pp. 409–437). Springer International Publishing. [paper]

--

--

Prof Bill Buchanan OBE FRSE
ASecuritySite: When Bob Met Alice

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.