diff --git a/CMakeLists.txt b/CMakeLists.txt index bc178b9e1..040f3b3ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -539,6 +539,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/third-party/google-test/googlet include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/third-party/google-test/googletest/include ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src/core/include ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src/binfhe/include ) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/prng_libs ) include_directories( ${CMAKE_CURRENT_BINARY_DIR}/src/core ) diff --git a/src/core/include/utils/prng/CPPLINT.cfg b/prng_libs/blake2_lib/include/CPPLINT.cfg similarity index 100% rename from src/core/include/utils/prng/CPPLINT.cfg rename to prng_libs/blake2_lib/include/CPPLINT.cfg diff --git a/src/core/include/utils/prng/README.md b/prng_libs/blake2_lib/include/README.md similarity index 100% rename from src/core/include/utils/prng/README.md rename to prng_libs/blake2_lib/include/README.md diff --git a/src/core/include/utils/prng/blake2-impl.h b/prng_libs/blake2_lib/include/blake2-impl.h similarity index 100% rename from src/core/include/utils/prng/blake2-impl.h rename to prng_libs/blake2_lib/include/blake2-impl.h diff --git a/src/core/include/utils/prng/blake2.h b/prng_libs/blake2_lib/include/blake2.h similarity index 100% rename from src/core/include/utils/prng/blake2.h rename to prng_libs/blake2_lib/include/blake2.h diff --git a/src/core/include/utils/prng/blake2engine.h b/prng_libs/blake2_lib/include/blake2engine.h similarity index 53% rename from src/core/include/utils/prng/blake2engine.h rename to prng_libs/blake2_lib/include/blake2engine.h index 48ff40133..ae0d41386 100644 --- a/src/core/include/utils/prng/blake2engine.h +++ b/prng_libs/blake2_lib/include/blake2engine.h @@ -37,17 +37,18 @@ #ifndef _SRC_LIB_UTILS_BLAKE2ENGINE_H #define _SRC_LIB_UTILS_BLAKE2ENGINE_H -#include -#include -#include -#include -#include - +#include "prng.h" #include "blake2.h" -#include "utils/exception.h" +#include +#include +#include +#include +#include +#include +#include +#include -namespace lbcrypto { // the buffer stores 1024 samples of 32-bit integers const uint32_t PRNG_BUFFER_SIZE = 1024; @@ -56,56 +57,38 @@ const uint32_t PRNG_BUFFER_SIZE = 1024; * @brief Defines the PRNG engine used by OpenFHE. It is based on BLAKE2. Use * this as a template for adding other PRNG engines to OpenFHE. */ -class Blake2Engine { +class Blake2Engine : public PRNG { public: - // all C++11 distributions used in OpenFHE work by default with uint32_t - // a different data type can be specified if needed for a particular - // architecture - using result_type = uint32_t; + enum {MAX_SEED_GENS = 16}; /** * @brief Constructor using a small seed - used for generating a large seed */ - explicit Blake2Engine(result_type seed) - : m_counter(0), m_buffer({}), m_bufferIndex(0) { + explicit Blake2Engine(PRNG::result_type seed) + : PRNG(seed), m_counter(0), m_buffer({}), m_bufferIndex(0) { m_seed[0] = seed; } - /** - * @brief Main constructor taking a vector of 16 integers as a seed - */ - explicit Blake2Engine(const std::array& seed) - : m_counter(0), m_seed(seed), m_buffer({}), m_bufferIndex(0) {} + // TODO (dsuponit): commented the constructor below and added a default paramter value to the next contructor + // /** + // * @brief Main constructor taking a vector of MAX_SEED_GENS integers as a seed + // */ + // explicit Blake2Engine(const std::array& seed) + // : m_counter(0), m_seed(seed), m_buffer({}), m_bufferIndex(0) {} /** - * @brief Main constructor taking a vector of 16 integers as a seed and a + * @brief Main constructor taking a vector of MAX_SEED_GENS integers as a seed and a * counter */ - explicit Blake2Engine(const std::array& seed, - result_type counter) + explicit Blake2Engine(const std::array& seed, + PRNG::result_type counter = 0) : m_counter(counter), m_seed(seed), m_buffer({}), m_bufferIndex(0) {} - /** - * @brief minimum value used by C+11 distribution generators when no lower - * bound is explicitly specified by the user - */ - static constexpr result_type min() { - return std::numeric_limits::min(); - } - - /** - * @brief maximum value used by C+11 distribution generators when no upper - * bound is explicitly specified by the user - */ - static constexpr result_type max() { - return std::numeric_limits::max(); - } - /** * @brief main call to the PRNG */ - result_type operator()() { - result_type result; + PRNG::result_type operator()() override { + PRNG::result_type result; if (m_bufferIndex == PRNG_BUFFER_SIZE) m_bufferIndex = 0; @@ -142,10 +125,10 @@ class Blake2Engine { void Generate() { // m_counter is the input to the hash function // m_buffer is the output - if (blake2xb(m_buffer.begin(), m_buffer.size() * sizeof(result_type), + if (blake2xb(m_buffer.begin(), m_buffer.size() * sizeof(PRNG::result_type), &m_counter, sizeof(m_counter), m_seed.cbegin(), - m_seed.size() * sizeof(result_type)) != 0) { - OPENFHE_THROW("PRNG: blake2xb failed"); + m_seed.size() * sizeof(PRNG::result_type)) != 0) { + throw std::runtime_error("PRNG: blake2xb failed"); } m_counter++; return; @@ -156,16 +139,64 @@ class Blake2Engine { uint64_t m_counter = 0; // the seed for the BLAKE2 hash function - std::array m_seed{}; + std::array m_seed{}; // The vector that stores random samples generated using the hash function - std::array m_buffer{}; + std::array m_buffer{}; // Index in m_buffer corresponding to the current PRNG sample uint16_t m_bufferIndex = 0; }; -} // namespace lbcrypto +// the code calling createEngineInstance() should clean the memory allocated by createEngineInstance() +// TODO (dsuponit): check with Jack if createEngineInstance() can return an object instead of a pointer. We can do it +// for Blake2Engine +extern "C" { + Blake2Engine* createEngineInstance(); +} +// extern "C" Blake2Engine* createEngineInstance() { +// // initialization of PRNGs +// constexpr size_t maxGens = Blake2Engine::MAX_SEED_GENS; +// #pragma omp critical +// std::array initKey{}; +// initKey[0] = std::chrono::high_resolution_clock::now().time_since_epoch().count(); +// initKey[1] = std::hash{}(std::this_thread::get_id()); +// #if !defined(__arm__) && !defined(__EMSCRIPTEN__) +// if (sizeof(size_t) == 8) +// initKey[2] = (std::hash{}(std::this_thread::get_id()) >> 32); +// #endif +// void* mem = malloc(1); +// uint32_t counter = reinterpret_cast(mem); // NOLINT +// free(mem); + +// Blake2Engine gen(initKey, counter); + +// std::uniform_int_distribution distribution(0); +// std::array seed{}; +// for (uint32_t i = 0; i < maxGens; i++) { +// seed[i] = distribution(gen); +// } + +// std::array rdseed{}; +// size_t attempts = 3; +// bool rdGenPassed = false; +// for(size_t i = 0; i < attempts && !rdGenPassed; ++i) { +// try { +// std::random_device genR; +// for (uint32_t i = 0; i < maxGens; i++) { +// rdseed[i] = distribution(genR); +// } +// rdGenPassed = true; +// } +// catch (std::exception& e) { +// } +// } +// for (uint32_t i = 0; i < maxGens; i++) { +// seed[i] += rdseed[i]; +// } + +// return new Blake2Engine(seed); +// } #endif // clang-format on diff --git a/src/core/lib/utils/prng/CPPLINT.cfg b/prng_libs/blake2_lib/lib/CPPLINT.cfg similarity index 100% rename from src/core/lib/utils/prng/CPPLINT.cfg rename to prng_libs/blake2_lib/lib/CPPLINT.cfg diff --git a/src/core/lib/utils/prng/blake2b-ref.c b/prng_libs/blake2_lib/lib/blake2b-ref.c similarity index 99% rename from src/core/lib/utils/prng/blake2b-ref.c rename to prng_libs/blake2_lib/lib/blake2b-ref.c index 0e802c1bd..2f0787207 100644 --- a/src/core/lib/utils/prng/blake2b-ref.c +++ b/prng_libs/blake2_lib/lib/blake2b-ref.c @@ -18,8 +18,8 @@ #include #include -#include "utils/prng/blake2.h" -#include "utils/prng/blake2-impl.h" +#include "blake2.h" +#include "blake2-impl.h" static const uint64_t blake2b_IV[8] = { 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, diff --git a/prng_libs/blake2_lib/lib/blake2engine.cpp b/prng_libs/blake2_lib/lib/blake2engine.cpp new file mode 100644 index 000000000..b434be62b --- /dev/null +++ b/prng_libs/blake2_lib/lib/blake2engine.cpp @@ -0,0 +1,57 @@ +//================================================================================== +// © 2024 Duality Technologies, Inc. All rights reserved. +// This is a proprietary software product of Duality Technologies, Inc. +// protected under copyright laws and international copyright treaties, patent +// law, trade secret law and other intellectual property rights of general +// applicability. Any use of this software is strictly prohibited absent a +// written agreement executed by Duality Technologies, Inc., which provides +// certain limited rights to use this software. You may not copy, distribute, +// make publicly available, publicly perform, disassemble, de-compile or reverse +// engineer any part of this software, breach its security, or circumvent, +// manipulate, impair or disrupt its operation. +//================================================================================== +#include "blake2engine.h" + +Blake2Engine* createEngineInstance() { +// initialization of PRNGs +constexpr size_t maxGens = Blake2Engine::MAX_SEED_GENS; +#pragma omp critical + std::array initKey{}; + initKey[0] = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + initKey[1] = std::hash{}(std::this_thread::get_id()); +#if !defined(__arm__) && !defined(__EMSCRIPTEN__) + if (sizeof(size_t) == 8) + initKey[2] = (std::hash{}(std::this_thread::get_id()) >> 32); +#endif + void* mem = malloc(1); + uint32_t counter = reinterpret_cast(mem); // NOLINT + free(mem); + + Blake2Engine gen(initKey, counter); + + std::uniform_int_distribution distribution(0); + std::array seed{}; + for (uint32_t i = 0; i < maxGens; i++) { + seed[i] = distribution(gen); + } + + std::array rdseed{}; + size_t attempts = 3; + bool rdGenPassed = false; + for(size_t i = 0; i < attempts && !rdGenPassed; ++i) { + try { + std::random_device genR; + for (uint32_t i = 0; i < maxGens; i++) { + rdseed[i] = distribution(genR); + } + rdGenPassed = true; + } + catch (std::exception& e) { + } + } + for (uint32_t i = 0; i < maxGens; i++) { + seed[i] += rdseed[i]; + } + + return new Blake2Engine(seed); +} diff --git a/src/core/lib/utils/prng/blake2xb-ref.c b/prng_libs/blake2_lib/lib/blake2xb-ref.c similarity index 98% rename from src/core/lib/utils/prng/blake2xb-ref.c rename to prng_libs/blake2_lib/lib/blake2xb-ref.c index 16d476a28..761add3a0 100644 --- a/src/core/lib/utils/prng/blake2xb-ref.c +++ b/prng_libs/blake2_lib/lib/blake2xb-ref.c @@ -21,8 +21,8 @@ #include #include -#include "utils/prng/blake2.h" -#include "utils/prng/blake2-impl.h" +#include "blake2.h" +#include "blake2-impl.h" int blake2xb_init(blake2xb_state *S, const size_t outlen) { return blake2xb_init_key(S, outlen, NULL, 0); diff --git a/prng_libs/blake2_lib/link.sh b/prng_libs/blake2_lib/link.sh new file mode 100755 index 000000000..94c2066eb --- /dev/null +++ b/prng_libs/blake2_lib/link.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# set -x + +g++ -fPIC -shared -o libblake2.so -I./include -I../ ./lib/*.c ./lib/*.cpp + diff --git a/prng_libs/prng.h b/prng_libs/prng.h new file mode 100644 index 000000000..778d62f37 --- /dev/null +++ b/prng_libs/prng.h @@ -0,0 +1,49 @@ +//================================================================================== +// © 2024 Duality Technologies, Inc. All rights reserved. +// This is a proprietary software product of Duality Technologies, Inc. +// protected under copyright laws and international copyright treaties, patent +// law, trade secret law and other intellectual property rights of general +// applicability. Any use of this software is strictly prohibited absent a +// written agreement executed by Duality Technologies, Inc., which provides +// certain limited rights to use this software. You may not copy, distribute, +// make publicly available, publicly perform, disassemble, de-compile or reverse +// engineer any part of this software, breach its security, or circumvent, +// manipulate, impair or disrupt its operation. +//================================================================================== +#ifndef __PRNG_H__ +#define __PRNG_H__ + +#include +#include + + +class PRNG { +public: + // all C++11 distributions used in OpenFHE work by default with uint32_t + // a different data type can be specified if needed for a particular + // architecture + using result_type = uint32_t; + + PRNG(result_type seed) {} + /** + * @brief minimum value used by C+11 distribution generators when no lower + * bound is explicitly specified by the user + */ + static constexpr result_type min() { + return std::numeric_limits::min(); + } + + /** + * @brief maximum value used by C+11 distribution generators when no upper + * bound is explicitly specified by the user + */ + static constexpr result_type max() { + return std::numeric_limits::max(); + } + + virtual result_type operator()() = 0; + virtual ~PRNG() = default; +}; + +#endif // __PRNG_H__ + diff --git a/src/core/include/math/distributiongenerator.h b/src/core/include/math/distributiongenerator.h index 960457021..7fb41e5b7 100644 --- a/src/core/include/math/distributiongenerator.h +++ b/src/core/include/math/distributiongenerator.h @@ -37,20 +37,18 @@ #ifndef LBCRYPTO_INC_MATH_DISTRIBUTIONGENERATOR_H_ #define LBCRYPTO_INC_MATH_DISTRIBUTIONGENERATOR_H_ -// #include "math/math-hal.h" -#include "utils/parallel.h" -#include "utils/prng/blake2engine.h" +// #include "utils/parallel.h" +#include "utils/exception.h" + +#include "prng.h" -#include #include // #include -#include -#include - -// #define FIXED_SEED // if defined, then uses a fixed seed number for -// reproducible results during debug. Use only one OMP thread to ensure -// reproducibility +// #include +// #include +// #include +#include namespace lbcrypto { @@ -58,136 +56,139 @@ namespace lbcrypto { // The cryptographically secure PRNG used by OpenFHE is based on BLAKE2 hash // functions. A user can replace it with a different PRNG if desired by defining // the same methods as for the Blake2Engine class. -typedef Blake2Engine PRNG; - -/** - * @brief The class providing the PRNG capability to all random distribution - * generators in OpenFHE. THe security of Ring Learning With Errors (used for - * all crypto capabilities in OpenFHE) depends on the randomness of uniform, - * ternary, and Gaussian distributions, which derive their randomness from the - * PRNG. - */ +// typedef Blake2Engine PRNG; + + class PseudoRandomNumberGenerator { public: - /** - * @brief Returns a reference to the PRNG engine - */ - - // TODO: there may be an issue here - static void InitPRNG() { - int threads = OpenFHEParallelControls.GetNumThreads(); - if (threads == 0) { - threads = 1; + // static PseudoRandomNumberGenerator& getInstance() { + // std::call_once(initInstanceFlag, &PseudoRandomNumberGenerator::createInstance); + // return *instance; + // } + + static PRNG& GetPRNG() { + std::cerr << __FILE__ << ":l." << __LINE__ << "In PseudoRandomNumberGenerator:: GetPRNG()" << std::endl; + // I commented getInstance() as we use GetPRNG() instead of getInstance() and initialized this singleton class + // in GetPRNG(). TODO (dsuponit): i am curious if we may be able to get rid of + // std::unique_ptr PseudoRandomNumberGenerator::instance :))) + std::call_once(initInstanceFlag, &PseudoRandomNumberGenerator::createInstance); + return *engine; + } + +private: + PseudoRandomNumberGenerator() = default; + ~PseudoRandomNumberGenerator() { + if (engine) { + delete engine; + engine = nullptr; } -#pragma omp parallel for num_threads(threads) - for (int i = 0; i < threads; ++i) { - GetPRNG(); + if (singletonHandle) { + dlclose(singletonHandle); + singletonHandle = nullptr; } } + + static void createInstance() { + instance.reset(new PseudoRandomNumberGenerator); + + const std::string engineLibName = "libengine.so"; + void* handle = dlopen(engineLibName.c_str(), RTLD_LAZY); + if (!handle) { + OPENFHE_THROW("Cannot open " + engineLibName); + } - static PRNG& GetPRNG() { - // initialization of PRNGs - if (m_prng == nullptr) { -#pragma omp critical - { -#if defined(FIXED_SEED) - // Only used for debugging in the single-threaded mode. - std::cerr << "**FOR DEBUGGING ONLY!!!! Using fixed initializer for " - "PRNG. Use a single thread only, e.g., OMP_NUM_THREADS=1!" - << std::endl; - - std::array seed{}; - seed[0] = 1; - m_prng = std::make_shared(seed); -#else - // A 512-bit seed is generated for each thread (this roughly corresponds - // to 256 bits of security). The seed is the sum of a random sample - // generated using std::random_device (typically works correctly in - // Linux, MacOS X, and MinGW starting with GCC 9.2) and a BLAKE2 sample - // seeded from current time stamp, a hash of the current thread, and a - // memory location of a heap variable. The BLAKE2 sample is added in - // case random_device is deterministic (happens on MinGW with GCC - // below 9.2). All future calls to PRNG use the seed generated here. - - // The code below derives randomness from time, thread id, and a memory - // location of a heap variable. This seed is relevant only if the - // implementation of random_device is deterministic (as in older - // versions of GCC in MinGW) - std::array initKey{}; - // high-resolution clock typically has a nanosecond tick period - // Arguably this may give up to 32 bits of entropy as the clock gets - // recycled every 4.3 seconds - initKey[0] = std::chrono::high_resolution_clock::now().time_since_epoch().count(); - // A thread id is often close to being random (on most systems) - initKey[1] = std::hash{}(std::this_thread::get_id()); - // On a 64-bit machine, the thread id is 64 bits long - // skip on 32-bit arm architectures - #if !defined(__arm__) && !defined(__EMSCRIPTEN__) - if (sizeof(size_t) == 8) - initKey[2] = (std::hash{}(std::this_thread::get_id()) >> 32); - #endif - - // heap variable; we are going to use the least 32 bits of its memory - // location as the counter for BLAKE2 This will increase the entropy of - // the BLAKE2 sample - void* mem = malloc(1); - uint32_t counter = reinterpret_cast(mem); // NOLINT - free(mem); - - PRNG gen(initKey, counter); - - std::uniform_int_distribution distribution(0); - std::array seed{}; - for (uint32_t i = 0; i < 16; i++) { - seed[i] = distribution(gen); - } - - std::array rdseed{}; - size_t attempts = 3; - bool rdGenPassed = false; - size_t idx = 0; - while (!rdGenPassed && idx < attempts) { - try { - std::random_device genR; - for (uint32_t i = 0; i < 16; i++) { - // we use the fact that there is no overflow for unsigned integers - // (from C++ standard) i.e., arithmetic mod 2^32 is performed. For - // the seed to be random, it is sufficient for one of the two - // samples below to be random. In almost all practical cases, - // distribution(genR) is random. We add distribution(gen) just in - // case there is an implementation issue with random_device (as in - // older MinGW systems). - rdseed[i] = distribution(genR); - } - rdGenPassed = true; - } - catch (std::exception& e) { - } - idx++; - } - - for (uint32_t i = 0; i < 16; i++) { - seed[i] += rdseed[i]; - } - - m_prng = std::make_shared(seed); -#endif + // get the factory function + using CreateInstanceFunc = PRNG*(*)(); + const std::string engineLoadFuncName = "createEngineInstance"; + CreateInstanceFunc func = (CreateInstanceFunc)dlsym(handle, engineLoadFuncName.c_str()); + if (!func) { + std::string errMsg{"Cannot load symbol " + engineLoadFuncName}; + const char* dlsym_error = dlerror(); + if (dlsym_error) { + errMsg += ": "; + errMsg += dlsym_error; } + // dlclose(handle); - destructor will call it + OPENFHE_THROW(errMsg); + } + // create the engine instance + engine = func(); + if (!engine) { + // dlclose(handle); - destructor will call it + OPENFHE_THROW("Cannot create a PRNG engine"); } - return *m_prng; + singletonHandle = handle; // store handle for dlclose } -private: - // shared pointer to a thread-specific PRNG engine - static std::shared_ptr m_prng; - -#if !defined(FIXED_SEED) - // avoid contention on m_prng - // local copies of m_prng are created for each thread - #pragma omp threadprivate(m_prng) -#endif + static std::unique_ptr instance; // Pointer to the instance + static std::once_flag initInstanceFlag; // Flag for thread-safe initialization + static PRNG* engine; // Pointer to the engine + static void* singletonHandle; // Handle for the shared library }; +//================================================================== +// class PseudoRandomNumberGenerator { +// public: +// static PRNG& GetPRNG() { +// // initialization of PRNGs +// if (m_prng == nullptr) { +// #pragma omp critical +// { +// std::array initKey{}; +// initKey[0] = std::chrono::high_resolution_clock::now().time_since_epoch().count(); +// initKey[1] = std::hash{}(std::this_thread::get_id()); +// #if !defined(__arm__) && !defined(__EMSCRIPTEN__) +// if (sizeof(size_t) == 8) +// initKey[2] = (std::hash{}(std::this_thread::get_id()) >> 32); +// #endif +// void* mem = malloc(1); +// uint32_t counter = reinterpret_cast(mem); // NOLINT +// free(mem); + +// PRNG gen(initKey, counter); + +// std::uniform_int_distribution distribution(0); +// std::array seed{}; +// for (uint32_t i = 0; i < 16; i++) { +// seed[i] = distribution(gen); +// } + +// std::array rdseed{}; +// size_t attempts = 3; +// bool rdGenPassed = false; +// size_t idx = 0; +// while (!rdGenPassed && idx < attempts) { +// try { +// std::random_device genR; +// for (uint32_t i = 0; i < 16; i++) { +// rdseed[i] = distribution(genR); +// } +// rdGenPassed = true; +// } +// catch (std::exception& e) { +// } +// idx++; +// } + +// for (uint32_t i = 0; i < 16; i++) { +// seed[i] += rdseed[i]; +// } + +// m_prng = std::make_shared(seed); +// } +// } +// return *m_prng; +// } + +// private: +// // shared pointer to a thread-specific PRNG engine +// static std::shared_ptr m_prng; + +// // avoid contention on m_prng +// // local copies of m_prng are created for each thread +// #pragma omp threadprivate(m_prng) +// }; + } // namespace lbcrypto #endif // LBCRYPTO_INC_MATH_DISTRIBUTIONGENERATOR_H_ diff --git a/src/core/lib/math/distributiongenerator.cpp b/src/core/lib/math/distributiongenerator.cpp index 995a412a7..edbac17fe 100644 --- a/src/core/lib/math/distributiongenerator.cpp +++ b/src/core/lib/math/distributiongenerator.cpp @@ -41,6 +41,11 @@ namespace lbcrypto { -std::shared_ptr PseudoRandomNumberGenerator::m_prng = nullptr; + +// Static members initialization +std::unique_ptr PseudoRandomNumberGenerator::instance = nullptr; +std::once_flag PseudoRandomNumberGenerator::initInstanceFlag; +PRNG* PseudoRandomNumberGenerator::engine = nullptr; +void* PseudoRandomNumberGenerator::singletonHandle = nullptr; } // namespace lbcrypto