AI Can Be Private: Homomorphic Encryption and Artificial Neural Networks

--

You may have seen that companies such as Apple and Google are starting to use homomorphic encryption for data gathering. With this, they aim to collect analytics which do not reveal any sensitive information. For example, could Apple determine the average battery level for their devices from all their users? With this, Apple can encrypt the battery level with a homomorphic public key, and then feed into a processing network with other collected values. Then, a private key can be used to decrypt the result:

One of the core problems that we have is with AI, in that it often can be trained on data that should be private. This might relate to the Web sites that a user accesses or for their personal data, such as their age, weight and height. So can we apply encrypted data onto an artifical neural network (ANN)? Well, with homomorphic encryption, we can.

2-layer feed-forward network

With a 2-layer feed-forward network, we can have a input of x1, x2 and x3, and an output of y1, y2 and y3:

The weightings of the values are then w11, w21 … w33. For this we get:

We can represent this in the form of a matrix as:

We can now encrypt the input values with homomorphic encryption, and use a plaintext version of our weightings (as these do not have to be secret). If we have a key pair of pk, sk, we can encrypt the input data vector (v1) with:

We can then use the private key to determine:

Coding

With this, we will encrypt the vectors with a public key and then perform an inner product operation [here]:

#include "openfhe.h"
using namespace lbcrypto;
#include <iostream>
int64_t innerProduct(
std::vector<int64_t> vector1,
std::vector<int64_t> vector2
);
std::vector<int64_t> genRandVect(
size_t length,
int64_t maxValue, int64_t seconds
);
Ciphertext<DCRTPoly> innerProductCC(
CryptoContext<DCRTPoly> cryptoContext,
PublicKey<DCRTPoly> publicKey,
Ciphertext<DCRTPoly> vector1C,
Ciphertext<DCRTPoly> vector2C,
size_t vectorLength,
bool masking = false
);
std::vector<std::vector<int64_t>> genRandMatrix(
size_t rows,
size_t cols,
int64_t maxValue
);
std::vector<int64_t> vectorMatrixMult(
std::vector<int64_t> vector,
std::vector<std::vector<int64_t>> matrix
);
Ciphertext<DCRTPoly> vectorMatrixMultByInnProdCP(
CryptoContext<DCRTPoly> cryptoContext,
PublicKey<DCRTPoly> publicKey,
Ciphertext<DCRTPoly> vectorC,
std::vector<std::vector<int64_t>> matrix
);

int main(int argc, char* argv[]) {

int64_t max = 100;
int mod=65537;
int ROWS=3;
int COLS=3;
if (argc>1) {
std::istringstream iss(argv[1]);
iss >> ROWS;
}
if (argc>2) {
std::istringstream iss(argv[2]);
iss >> COLS;
}
if (argc>3) {
std::istringstream iss(argv[3]);
iss >> max;
}
if (argc>4) {
std::istringstream iss(argv[4]);
iss >> mod;
}

CCParams<CryptoContextBFVRNS> parameters;
parameters.SetPlaintextModulus(mod);
parameters.SetMultiplicativeDepth(4);
parameters.SetMaxRelinSkDeg(3);
CryptoContext<DCRTPoly> cryptoContext = GenCryptoContext(parameters);
cryptoContext->Enable(PKE);
cryptoContext->Enable(KEYSWITCH);
cryptoContext->Enable(LEVELEDSHE);
cryptoContext->Enable(ADVANCEDSHE);

PlaintextModulus p = cryptoContext->GetCryptoParameters()->GetPlaintextModulus();
int n = cryptoContext->GetCryptoParameters()->GetElementParams()->GetCyclotomicOrder() / 2;
double q = cryptoContext->GetCryptoParameters()->GetElementParams()->GetModulus().ConvertToDouble();

std::cout << "Plaintext modulus (p) = " << p << std::endl;
std::cout << "Polynomial degree (n) = " << n << std::endl;
std::cout << "Ciphertext modulus bitsize (log2 q) = " << log2(q) << std::endl;

KeyPair<DCRTPoly> keyPair = cryptoContext->KeyGen();

cryptoContext->EvalMultKeysGen(keyPair.secretKey);

std::cout << "Generating rotation keys... ";
std::vector<int32_t> rotationKeys = {};
for (int i = -ROWS*COLS; i <= ROWS*COLS; i++) rotationKeys.push_back(i);
cryptoContext->EvalRotateKeyGen(keyPair.secretKey, rotationKeys);
std::cout << "Done"<< std::endl << std::endl;

std::vector<int64_t> vector = genRandVect(ROWS, max,0);
Plaintext vectorP = cryptoContext->MakePackedPlaintext(vector);
std::vector<std::vector<int64_t>> matrix = genRandMatrix(ROWS, COLS, max);

std::cout << "Vector (V1) = " << vector << std::endl;
std::cout << "Matrix (M1) = " << matrix << std::endl;

Ciphertext<DCRTPoly> vectorC = cryptoContext->Encrypt(keyPair.publicKey, vectorP);
Ciphertext<DCRTPoly> resC;
Plaintext res;
std::vector<int64_t> resOutput, resOutputtmp;

resOutput = vectorMatrixMult(vector, matrix);
std::cout << "V1*M1 (non encrypted) = " << resOutput << std::endl;
resC = vectorMatrixMultByInnProdCP(cryptoContext, keyPair.publicKey, vectorC, matrix);
cryptoContext->Decrypt(keyPair.secretKey, resC, &res);
res->SetLength(COLS);
resOutput = res->GetPackedValue();
std::cout << "VectorC * Matrix (by inner product) = " << resOutput << std::endl;

}
}

A sample run is [here]:

Plaintext modulus (p) = 65537
Polynomial degree (n) = 16384
Ciphertext modulus bitsize (log2 q) = 240
Generating rotation keys... vector = [ -55 -38 17 ]
matrix = [ [ -52 -90 -87 ] [ 38 -79 71 ] [ -58 -69 -54 ] ]
vector * matrix = [ 430 6779 1169 ]

With this, we have an input vector of:

and a matrix of:

We now get:

and:

and which gives:

The method we have used for homomorphic encryption is BFV, and which processes integer values. If we use floating point values, we can use the CKKS method. The following is the equivalent program using CKKS:

Conclusions

And, there you go. We are often poor at encrypting sensitive data, and especially within AI applications. The use of homomorphic encryption gives us the opportunity to encrypt data at source and then use it within machine learning applications.

Here are other applications of homomorphic encryption:

https://asecuritysite.com/openfhe

--

--

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.