Program Listing for File distributiongenerator.h

Return to documentation for file (core/include/math/distributiongenerator.h)

//==================================================================================
// BSD 2-Clause License
//
// Copyright (c) 2014-2023, NJIT, Duality Technologies Inc. and other contributors
//
// All rights reserved.
//
// Author TPOC: contact@openfhe.org
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//==================================================================================

/*
  This code provides basic structure for distribution generators. This should be inherited by
  all other distribution generators
 */

#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 <chrono>
#include <memory>
// #include <mutex>
#include <random>
#include <thread>

// #define FIXED_SEED // if defined, then uses a fixed seed number for
// reproducible results during debug. Use only one OMP thread to ensure
// reproducibility

namespace lbcrypto {

// Defines the PRNG implementation used by OpenFHE.
// 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;

class PseudoRandomNumberGenerator {
public:
    // TODO: there may be an issue here
    static void InitPRNG() {
        int threads = OpenFHEParallelControls.GetNumThreads();
        if (threads == 0) {
            threads = 1;
        }
#pragma omp parallel for num_threads(threads)
        for (int i = 0; i < threads; ++i) {
            GetPRNG();
        }
    }

    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<uint32_t, 16> seed{};
                seed[0] = 1;
                m_prng  = std::make_shared<PRNG>(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<uint32_t, 16> 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::thread::id>{}(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::thread::id>{}(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<long long>(mem);  // NOLINT
                free(mem);

                PRNG gen(initKey, counter);

                std::uniform_int_distribution<uint32_t> distribution(0);
                std::array<uint32_t, 16> seed{};
                for (uint32_t i = 0; i < 16; i++) {
                    seed[i] = distribution(gen);
                }

                std::array<uint32_t, 16> 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<PRNG>(seed);
#endif
            }
        }
        return *m_prng;
    }

private:
    // shared pointer to a thread-specific PRNG engine
    static std::shared_ptr<PRNG> 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
};

}  // namespace lbcrypto

#endif  // LBCRYPTO_INC_MATH_DISTRIBUTIONGENERATOR_H_