1

Currently experimenting on homomorphic encryption using the PALISADE library.

I want to apply simple he operations like additions and multiplications on large encrypted inputs. For example input A[3200] and input B[4096] both vectors/arrays of int values get encrypted. With those two inputs Enc(A) and Enc(B) I want to apply an multiplication:

EvalMult(Enc(A[0]), Enc(B[42])) 

*0 and 42 denoting the indexes of the corresponding inputs
** no SIMD needed

As far as I am concerned the he implementation of the above described requirenments could be solved in two different ways:

  1. Pack the inputs in a single ciphertext (SIMD like) and for the he operations I could use EvalIndexAt() to get the right value out of the encrypted input.

  2. Encrypt each value from A and B separately.

I am not quite sure what of the described solutions would be the best in terms of efficiency. The first approach has this major advantage that only one encryption process for the whole input is needed but this comes with the disadvantage that I always have to access the correct element using the EvalAtIndex() method and the bigger the inputs get the slower the computation of the EvalAtIndexKeyGen() gets. (At least on my machine)

The second approach seems the fit better because EvalAtIndex() is not needed but it comes with the cost of encrypting each value separately which takes quite some time.

Any thoughts recommendations?

John Paul
  • 12,196
  • 6
  • 55
  • 75
Cowas
  • 328
  • 2
  • 12

1 Answers1

2

Thank you for the question.

The main benefit of approach #1 (SIMD) is that you can perform addition and multiplication of vectors (of 4096 or more integers/real numbers) using a single homomorphic addition or multiplication (very efficient). Rotation (called EvalAtIndex in PALISADE) is an extra operation that allows one access individual indices or do efficient summation (as in inner product), matrix multiplication, etc. This approach also has a much a smaller ciphertext expansion factor (by 4096x or more) than approach #2. Generally option #1 is preferred in practice (and I cannot think of any real use case where I would want to go with option #2).

To minimize the cost of multiplication, maybe you could pack the vector in contiguous blocks so that you need a single rotation for one block. For example,

EvalMult(Enc(A[0:5]),Enc(B[42:47))

Another technique you can use is EvalFastRotation (available only for CKKS and BGVrns in PALISADE v1.10.x). If you need multiple rotations of the same ciphertext, you can precompute something for the ciphertext, and then use cheaper rotations (the most benefit is achieved for BV key switching) - see https://gitlab.com/palisade/palisade-development/-/blob/master/src/pke/examples/advanced-real-numbers.cpp for an example.

There are also ways to minimize the number of keys to be generated if you need multiple rotations (only compute roughly a square root of the number of rotations needed), e.g., using the baby-step-giant-step technique described in https://eprint.iacr.org/2018/244 (these techniques can be implemented in your PALISADE-based application).

You can also use a special order of packing a vector if the pattern for performing multiplication is known (this way your rotation will prepare several blocks across the vector using a single rotation operation). Rotations are cyclical (wrap around) in both CKKS and BGVrns when the # plaintext slots (batch size) is equal to ring dimension / 2. If you have a smaller vector than that, you can always clone/replicate the small vector as many times as needed to fill ring dimension / 2.

In summary, the biggest efficiency improvement can be achieved if you think of your problem in terms of SIMD-like vectors. Then you can reformulate your problem / model to take full advantage of the toolset HE provides. In a way, this is similar to programming using vectorized instructions, e.g., AVX, or matrix-oriented programming (like in MATLAB).