MPC FHE: Key-Switching

In this blogpost, we describe the key-switching operation, which is a key primitive (no pun intended) within Multiparty Computation Fully Homomorphic Encryption (MPC-FHE).

The jump from FHE to MPC-FHE

In previous blogposts, we introduced the notion of Fully Homomorphic Encryption (FHE), which allows parties to perform arbitrary computations on encrypted data, without decrypting it.

On a higher application-based level, our current notion of FHE looks like the following:

Note that this application only supports computation on a single user’s data. What happens if we instead we have parties and , with secret data and respectively, and they wish to compute without revealing other information about or ? This general problem is called Multiparty Computation Fully Homomorphic Encryption, or MPC-FHE for short.

The main issue we run into with trying to plug in our existing primitive (“server computes arbitrary function on private data”) is that now each user’s data is encrypted under different secrets (say we have and ), while evaluating OR/AND gates homomorphically require that inputs are encrypted under the same private key.

One operation that solves this is called “key-switching”, which can be stated somewhat formally as follows:

With this, we can switch all parties’ encrypted inputs to the same key, and then proceed with our homomorphic evaluation of

How key-switching works

So now that we’ve described the problem that we’re trying to solve and why we need it in MPC-FHE, what does the solution look like?

Current MPC-FHE schemes generally use Learning With Error (LWE) FHE schemes, which is what we will use as our basis to discuss key switching. We briefly explain the mechanics of LWE below.

Learning with Errors (LWE)

For simplicity, we’ll look at the symmetric-key version of LWE, where we just have a secret key that is used both to encrypt and to decrypt. We talked about LWE in a previous post, so we’ll just briefly remind ourselves how it works.

Our secret key is a tuple . Suppose we have a message , which is a single bit. Usually a bit is 0 or 1, but this time we’re going to encode it as either or . (If is odd, just use instead.)

What are the parameters and ? There are lots of possible values. You should imagine that : in other words, could be as small as a few thousand, or it could be as big as a cryptographically-sized prime. And might lie in the range .

To encrypt , we will give any tuple

such that

where is “small”.

We haven’t said exactly what “small” means, but you should imagine that is much smaller than .

This encryption scheme is nondeterministic: Given , there are lots of different ways to encrypt it. In fact, you can choose however you like, pick a random small error , and then compute .

In the other direction, decryption is easy if you know the secret key . Given the encryption

simply compute

Now you know is either or , and so you can just round off the error to recover . This works as long as you know .

Before we get to key switching, let’s explore some simple operations on ciphertexts.

Addition

To get an encryption of under , we can simply add (component-wise) the encryptions of and under . If

then

In other words,

is a perfectly good encryption of .

The only important point is that the errors add: if the original two encryptions have errors and , the new encryption will have error .

Scalar multiplication:

To get an encryption of under , where is some scalar, we can just multiply (component-wise) our ciphertext by . This works, except that it multiplies the error by . In other words, if the orginal ciphertext has error , the new ciphertext will have error .

If is a small constant, this might not be a big problem: we just have to keep track of the error and make sure it stays below the threshold . But if is big, or if the error is already getting close to the limit, we can’t do scalar multiplication.

Key switching

Now that we have our addition and scalar multiplication tools above, we can finally tackle key switching. Suppose we start with a message encrypted under secret , which looks like

and we want to end up with encrypted under a new secret , which looks like

We notice there’s an annoying extra masking term in that we need to get rid of. We can get rid of this with the addition tools we presented above, but how do we get access to if it’s the secret key? It turns out we don’t need direct access.

Remember, to recover , we just need to be able to compute

But we don’t actually need to recover , only its encryption under . So what if we have the encryptions of under our new secret key :

Then we can try to compute the linear combination

(We’re not worried about that error on the left, as long as it is much smaller than .
When we finally decrypt, will just get added to the final decryption error.)

The right-hand side involves a sum of terms. Now we can pull the addition out of the encryption, at the cost of adding errors. We can choose to be much, much bigger than , so if we start with a small enough error, this won’t be an issue:

Now we come to the tricky part. We know , and we want to learn , but we can’t just do a scalar multiplication by . If is big, the error will grow out of control.

The solution is to give the evaluator more than just encryptions of . Instead of just , we give them the full array where is the largest power of less than .

How does this help us?

Binary.

Write in binary: This formula looks complicated, but it really just says that is a sum of a bunch of powers of . (Remember, all the coefficients are either or .)

So now and

So we’ve expressed each as a sum – and because it’s a sum, the error stays under control.

To recap: In order to allow key switching, the encryptor (who chooses the secret keys) has to send to the evaluator encryptions for each . (This amounts to a total of ciphertexts – if and are large, this might be a lot of data!)

If the evaluator has some ciphertext under the old key ,

the evaluator knows that

The evaluator simply has to expand the ’s in binary as and then compute the new encryption

Easy!