homenc / HElib

HElib is an open-source software library that implements homomorphic encryption. It supports the BGV scheme with bootstrapping and the Approximate Number CKKS scheme. HElib also includes optimizations for efficient homomorphic evaluation, focusing on effective use of ciphertext packing techniques and on the Gentry-Halevi-Smart optimizations.
https://homenc.github.io/HElib
Other
3.11k stars 761 forks source link

Difference in encryption time between newly generated keys and keys read in from a file #313

Closed yoshiko305 closed 3 years ago

yoshiko305 commented 5 years ago

OS: Ubuntu 16.04

Hi. I'm currently working on some experimental implementations for a project. During the implementation, I realized that there is a large difference in the encryption time between using newly generated keys and using keys read in from a file.

With the parameters I'm using right now, the time to encrypt a vector of different values using newly generated keys and keys read in from a file is approximately 0.34s and 0.02s, respectively. Also, I have noticed that the bitCapacity() of a newly encrypted ciphertext is different between the two cases (180 and 169). In some cases,

Why is such difference occurring? Thank you.

The following are the current parameters and source code for generating the keys, writing them out to a file, then continue using the newly generated keys for encrypting data.

    long m=11119, p=2, r=18, L=180, c=3, w=64, d=0;

    FHEcontext context(m, p, r);
    buildModChain(context, L, c);
    FHESecKey secretKey(context);
    const FHEPubKey& publicKey = secretKey;
    secretKey.GenSecKey();
    addSome1DMatrices(secretKey);

    string contextFileName = "context.bin";
    string pubkeyFileName = "pk.bin";
    string seckeyFileName = "sk.bin";

    //writing out the context to file
    ofstream contextFile(contextFileName, ios::binary);
    writeContextBaseBinary(contextFile, context);
    writeContextBinary(contextFile, context);
    contextFile.close();

    //writing out pk to file
    ofstream pubkeyFile(pubkeyFileName, ios::binary);
    writePubKeyBinary(pubkeyFile, publicKey);
    pubkeyFile.close();

    //writing out sk to file
    ofstream seckeyFile(seckeyFileName, ios::binary);
    writeSecKeyBinary(seckeyFile, secretKey);
    seckeyFile.close();

    ZZX G = context.alMod.getFactorsOverZZ()[0];
    EncryptedArray ea(context, G);
    long num_slots = context.zMStar.getNSlots();

    vector <long> temp;
    for (int i = 0; i<num_slots; i++){
        temp.push_back(i);
    }

    Ctxt ctData(publicKey);
    ea.encrypt(ctData, publicKey, temp);

Below is the source code for reading in the context, pk, and sk from a file then encrypting the same vector as previous code.

    string contextFileName = "context.bin";
    string pubkeyFileName = "pk.bin";
    string seckeyFileName = "sk.bin";

    ifstream readContextFile(contextFileName, ios::binary);
    unsigned long m1, p1, r1;
    vector<long> gens, ords;
    readContextBaseBinary(readContextFile, m1, p1, r1, gens, ords);
    FHEcontext context(m1,p1,r1,gens,ords);
    readContextBinary(readContextFile, context);
    readContextFile.close();

    ifstream readPubkeyFile(pubkeyFileName, ios::binary);
    FHEPubKey publicKey(context);
    readPubKeyBinary(readPubkeyFile, publicKey);
    readPubkeyFile.close();

    ifstream readSeckeyFile(seckeyFileName, ios::binary);
    FHESecKey secretKey(context);
    readSecKeyBinary(readSeckeyFile, secretKey);
    readSeckeyFile.close();

    ZZX G = context.alMod.getFactorsOverZZ()[0];
    EncryptedArray ea(context, G);
    long num_slots = context.zMStar.getNSlots();

    vector <long> temp;
    for (int i = 0; i<num_slots; i++){
        temp.push_back(i);
    }

    Ctxt ctData(publicKey);
    ea.encrypt(ctData, publicKey, temp);
boev commented 5 years ago

Your code looks right. Having L=180 and getting 180 capacity is not correct, as encryption alone should introduce some noise. In my experience, it actually introduces a great amount of noise. For accurate reading also use capacity() instead of bitCapacity().

yoshiko305 commented 5 years ago

Thank you for telling me about the capacity() function. I tried using it but I still get the same issue of getting different values for capacity() of a newly encrypted ciphertext between the two cases in which I encrypt a vector using newly generated keys vs. using keys that I read in from a file. I got 124 and 118 as the result of calling capacity()

boev commented 5 years ago

I get: noise generated: 117.613279 noise read: 117.614043 Usually these are the same, strange they differ by a bit. I don't know what the problem is. I also have addFrbMatrices(secretKey); in my code, but I don't think it matters in this case. This context takes really long to generate (296.376625 seconds), is there a particular reason you are using it? Asking out of curiosity, because the number of slots is also not that much - 218.

faberga commented 5 years ago

@yoshiko305 @boev Thank you for raising this issue and providing the code that replicates your issue. Let us reproduce the observed behavior and get back to you.

faberga commented 5 years ago

@yoshiko305 we have reproduced the issue of differences in the capacity you raised on our systems and are investigating. Will publish update as we progress.

@yoshiko305 How are you measuring the encryption time?

yoshiko305 commented 5 years ago

@faberga Thank you for investigating the issue. To measure the encryption time, I'm using C++'s std::chrono::system_clock::now() to get the time at before and after the encryption and get the difference. I get the time in milliseconds using msec = std::chrono::duration_cast<std::chrono::milliseconds>(diff).count()

esteffin commented 5 years ago

Hi @yoshiko305, @boev @faberga, I was able to replicate the issue and understand the behaviour.

What I found is that in the first code snippet the encryption is done using the secret key because of the polymorphism from using a reference pointing to the FHESecKey object (const FHEPubKey& publicKey = secretKey).

However, in the second code snippet it is used the public key that has been de-serialised/read in as an actual FHEPubKey object, therefore encryption is done using the public key. This is caused by the fact that the function writePubKeyBinary serialise the key as an FHEPubKey regardless the fact that the argument is a FHESecKey reference.

Using the secret key for encryption gives a bigger capacity (as explained in the documentation [1] page 30: Secret-key encryption).

To obtain the same behaviour in term of capacity in the first code snippet, the public key cannot be a reference to the secret key, but an actual FHEPubKey object. This can be done constructing the key explicitly: FHEPubKey publicKey(secretKey) (using slicing).

[1] https://github.com/homenc/HElib/blob/master/doc/designDocument/he-library.pdf

faberga commented 4 years ago

@yoshiko305 hi there. Can you confirm whether Enrico's answer resolved your query, please? Thank you