Deserialization of a public key generated for multiparty

Hello, I am trying to use OpenFHE for a project that requires threshold FHE. In the current state of my prototype I have two programs.

  • the first one generate the cryptocontext, the secret key shares and the joined public key and serialize them
  • the second one simulate different third parties that wants to encrypt something using the joined public key and serialize the ciphertext in order to reuse it later
    I am experimenting problems when I want to use the deserialized joined public key in my second program. The program is crashing with this error
terminate called after throwing an instance of 'lbcrypto::OpenFHEException'                                                                                                                                        
  what():  /usr/local/include/openfhe/pke/cryptocontext.h:l.429:ValidateKey<std::shared_ptr<lbcrypto::PublicKeyImpl<lbcrypto::DCRTPolyImpl<bigintdyn::mubintvec<bigintdyn::ubint<long unsigned int> > > > > >(): Ke
y was not generated with the same crypto context                                                                                                                                                                   
Aborted (core dumped)

I am reusing the same cryptocontext (serialized then deserialized) as when I generated the key.

You will find my two source code below

generator.cpp

#include <openfhe.h>
#include "config.hpp"

using namespace lbcrypto;

int main() {
        // Parse the configuration
        auto config = Config("config.json");
        std::cout << config;

        // Generate and serialize cryptocontext
        std::cout << "Generate cryptocontext" << std::endl;

        CCParams<CryptoContextBFVRNS> parameters;

        parameters.SetPlaintextModulus(65537);
        parameters.SetSecurityLevel(lbcrypto::SecurityLevel::HEStd_128_classic);
        parameters.SetStandardDeviation(3.2);
        parameters.SetSecretKeyDist(UNIFORM_TERNARY);
        parameters.SetMultiplicativeDepth(4);
        parameters.SetBatchSize(16);
        parameters.SetDigitSize(30);
        parameters.SetScalingModSize(60);
        parameters.SetMultiplicationTechnique(HPSPOVERQLEVELED);

        CryptoContext<DCRTPoly> cryptoContext = GenCryptoContext(parameters);
        cryptoContext->Enable(PKE);
        cryptoContext->Enable(KEYSWITCH);
        cryptoContext->Enable(LEVELEDSHE);
        cryptoContext->Enable(ADVANCEDSHE);
        cryptoContext->Enable(MULTIPARTY);

        std::cout << "Generated cryptocontext" << std::endl;
        
        // Perform Key Generation Operation

        std::cout << "Perform Key Generation Operation" << std::endl;
        
        std::vector<KeyPair<DCRTPoly>> keyPairs;
        // generate the kp for all parties
        for ( auto& party: config.parties ) {
                KeyPair<DCRTPoly> kp;
                if ( keyPairs.empty() ) {
                        kp = cryptoContext->KeyGen();
                } else {
                        kp = cryptoContext->MultipartyKeyGen(keyPairs.back().publicKey);
                }

                if (!Serial::SerializeToFile(party.sk, kp.secretKey, SerType::BINARY)) {
                        std::cerr << "Error writing serialization of the secret key to "
                                << party.sk
                                << std::endl;
                        return 1;
                }
                std::cout << "The secret key for party " << party.id << " has been generated and serialized" << std::endl;

                keyPairs.push_back(kp);
        }

        // Generate evalmult key

        // Generate evalsum key

        if (!Serial::SerializeToFile(config.cryptocontext, cryptoContext, SerType::BINARY)) {
                std::cerr << "Error writing serialization of the crypto context to "
                        << config.cryptocontext
                        << std::endl;
                return 1;
        }
        std::cout << "The cryptocontext has been serialized." << std::endl;

        std::ofstream multKeyFile("test/multkey.txt", std::ios::out | std::ios::binary);
        if (multKeyFile.is_open()) {
                if (!cryptoContext->SerializeEvalMultKey(multKeyFile, SerType::BINARY)) {
                        std::cerr << "Error writing eval mult keys" << std::endl;
                        std::exit(1);
                }
                std::cout << "EvalMult/ relinearization keys have been serialized" << std::endl;
                multKeyFile.close();
        }
        else {
                std::cerr << "Error serializing EvalMult keys" << std::endl;
                std::exit(1);
        }

        std::ofstream rotationKeyFile("test/rotkey.txt", std::ios::out | std::ios::binary);
        if (rotationKeyFile.is_open()) {
                if (!cryptoContext->SerializeEvalAutomorphismKey(rotationKeyFile, SerType::BINARY)) {
                        std::cerr << "Error writing rotation keys" << std::endl;
                        std::exit(1);
                }
                std::cout << "Rotation keys have been serialized" << std::endl;
        }
        else {
                std::cerr << "Error serializing Rotation keys" << std::endl;
                std::exit(1);
        }

        if (!Serial::SerializeToFile(config.pk, keyPairs.back().publicKey, SerType::BINARY)) {
                std::cerr << "Error writing serialization of the joined public key to "
                        << config.pk
                        << std::endl;
                return 1;
        }
        std::cout << "The joined public key has been serialized" << std::endl;
}

encryption.cpp

#include <openfhe.h>
#include "config.hpp"

using namespace lbcrypto;

int main() {
        // Parse the configuration
        auto config = Config("config.json");
        std::cout << config;

        // Run each party sequentially
        for ( auto& party: config.parties ) {
                std::cout << "Running party " << party.id << std::endl;

                // Deserialize the cryptocontext
                CryptoContext<DCRTPoly> cryptoContext;
                if (!Serial::DeserializeFromFile(config.cryptocontext, cryptoContext, SerType::BINARY)) {
                        std::cerr << "I cannot read serialization from " << config.cryptocontext << std::endl;
                        return 1;
                }
                std::cout << "The cryptocontext has been deserialized." << std::endl;

                std::ifstream multKeyIStream("test/multkey.txt", std::ios::in | std::ios::binary);
                if (!multKeyIStream.is_open()) {
                        std::cerr << "Cannot read serialization from " << std::endl;
                        std::exit(1);
                }
                if (!cryptoContext->DeserializeEvalMultKey(multKeyIStream, SerType::BINARY)) {
                        std::cerr << "Could not deserialize eval mult key file" << std::endl;
                        std::exit(1);
                }

                std::cout << "Deserialized eval mult keys" << '\n' << std::endl;
                std::ifstream rotKeyIStream("test/rotkey.txt", std::ios::in | std::ios::binary);
                if (!rotKeyIStream.is_open()) {
                        std::cerr << "Cannot read serialization from " << std::endl;
                        std::exit(1);
                }
                if (!cryptoContext->DeserializeEvalAutomorphismKey(rotKeyIStream, SerType::BINARY)) {
                        std::cerr << "Could not deserialize eval rot key file" << std::endl;
                        std::exit(1);
                }

                PublicKey<DCRTPoly> pk;
                if (Serial::DeserializeFromFile(config.pk, pk, SerType::BINARY) == false) {
                        std::cerr << "Could not read public key" << std::endl;
                        return 1;
                }
                std::cout << "The public key has been deserialized." << std::endl;

                Plaintext pt = cryptoContext->MakePackedPlaintext(party.values);
                Ciphertext<DCRTPoly> ct = cryptoContext->Encrypt(pk, pt);

                std::cout << "The plaintexts have been encrypted." << std::endl;

                if (!Serial::SerializeToFile(party.ct, ct, SerType::BINARY)) {
                        std::cerr << "Error writing serialization of ciphertext to " << party.ct << std::endl;
                        return 1;
                }
                std::cout << "The ciphertext has been serialized." << std::endl;
        }
}

I do not encounter the error when I manually disable the check in OpenFHE and install the altered version.

patch

diff --git a/src/pke/include/cryptocontext.h b/src/pke/include/cryptocontext.h
index 4302a6a..bd08c64 100644
--- a/src/pke/include/cryptocontext.h
+++ b/src/pke/include/cryptocontext.h
@@ -424,6 +424,7 @@ protected:
             std::string errorMsg(std::string("Key is nullptr") + CALLER_INFO);
             OPENFHE_THROW(errorMsg);
         }
+       return;
         if (Mismatched(key->GetCryptoContext())) {
             std::string errorMsg(std::string("Key was not generated with the same crypto context") + CALLER_INFO);
             OPENFHE_THROW(errorMsg);

What do I do wrong?

Cheers,
Romain

Could you confirm what version of OpenFHE you are using. Is it v1.2.0?

@rdelaage could you provide ā€œconfig.hppā€ as well?

I compiled openFHE on commit 13bf46f683483da1fe77f591b98035fa455740c6 which corresponds to the tag v1.2.0

Nothing really interesting in this header file, only the layout of my network

#pragma once

#include <jsoncpp/json/json.h>
#include <iostream>
#include <fstream>
#include <optional>

class PartyConfig {
        public:
                int port;
                std::string id;
                std::string sk;
                std::string ct;
                std::vector<long int> values;
                partyConfig(int port, std::string id, std::string sk, std::string ct, std::vector<long int> values) : port(port), id(id), sk(sk), ct(ct), values(values) {}

                friend std::ostream& operator<<(std::ostream& os, const partyConfig& party) {
                        os << "Port: " << party.port << " Id: " << party.id << "SK: " << party.sk << "CT: " << party.ct << " Values:";
                        for ( int value: party.values ) {
                                os << " " << value;
                        }
                        os << std::endl;
                        return os;
                }
};

class Config {
        public:
                std::vector<PartyConfig> parties;
                std::string cryptocontext;
                std::string pk;

                Config(std::string filePath) {
                        std::ifstream ifs;
                        ifs.open(filePath);

                        Json::Value root;
                        ifs >> root;

                        ifs.close();

                        const Json::Value partiesJson = root["parties"];
                        for ( unsigned int i = 0; i < partiesJson.size(); ++i ) {
                                auto port = partiesJson[i]["port"].asInt();
                                auto id = partiesJson[i]["id"].asString();
                                auto sk = partiesJson[i]["sk"].asString();
                                auto ct = partiesJson[i]["ct"].asString();
                                std::vector<long int> values;

                                for ( unsigned int j = 0; j < partiesJson[i]["values"].size(); ++j ) {
                                        values.push_back(partiesJson[i]["values"][j].asInt());
                                }

                                partyConfig party = partyConfig(port, id, sk, ct, values);

                                parties.push_back(party);
                        }

                        cryptocontext = root["cryptocontext"].asString();

                        pk = root["pk"].asString();
                }

                friend std::ostream& operator<<(std::ostream& os, const Config& config) {
                        os << "Parties:" << std::endl;
                        for (auto& party : config.parties) {
                                os << party;
                        }
                        os << std::endl;

                        os << "CryptoContext:" << std::endl;
                        os << config.cryptocontext;
                        os << std::endl;

                        os << "PK:" << std::endl;
                        os << config.pk;
                        os << std::endl;

                        return os;
                }
};

Hi, Do you have any news on this issue? Do you need more information about my experiment?
Regards,

@rdelaage Hi, I will take a look today

@rdelaage Could you verify that the provided code runs without errors for you as I fixed compiler errors in config.hpp? after that I linked both encryption and generator and ran ./generator. That run produced the following error:

terminate called after throwing an instance of ā€˜Json::RuntimeErrorā€™
what(): * Line 1, Column 1
Syntax error: value, object or array expected.
Aborted (core dumped)

Please let me know if I missed anything as I should be able to replicate the same error you get in your environment.

Hello, sorry for the delay. You need a configuration file to run the generator. You can use this repo with everything ready, just run the run.sh script. GitHub - rdelaage/test_openfhe_mult

@rdelaage Hi, if you serialize/deserialize objects, you should additionally include header files (if they exist) for some data types. they all have a suffix -ser. For example, in your case you need to add includes of cryptocontext-ser.h and key/key-ser.h to generator.cpp and to encryption.cpp.

1 Like