libdpf/include/dpf/random.hpp

234 lines
5.9 KiB
C++
Raw Normal View History

/// @file dpf/random.hpp
/// @brief
/// @details
/// @author Ryan Henry <ryan.henry@ucalgary.ca>
/// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors)
/// @license Released under a GNU General Public v2.0 (GPLv2) license;
/// see [LICENSE.md](@ref license) for details.
#ifndef LIBDPF_INCLUDE_DPF_RANDOM_HPP__
#define LIBDPF_INCLUDE_DPF_RANDOM_HPP__
#include <bsd/stdlib.h>
#include <cerrno>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <exception>
#include <fcntl.h>
#include <mutex>
#include <type_traits>
#include <unistd.h>
#include <utility>
#include "hedley/hedley.h"
#include "dpf/secret_share.hpp"
namespace dpf
{
namespace detail
{
/// When set, `uniform_fill` copies from this hook and does not read the
/// system RNG. Used to feed the same beaver coins to dealer `make_dpf` and
/// Doerner–Shelat gen. Null in normal use.
inline thread_local void (*uniform_bytes_hook)(void *, std::size_t) = nullptr;
template <typename T>
HEDLEY_ALWAYS_INLINE
bool fill_from_hook(T & buf) noexcept
{
if (uniform_bytes_hook == nullptr)
{
return false;
}
uniform_bytes_hook(&buf, sizeof(buf));
return true;
}
/// `bool` and `enum : bool` (including `dpf::bit`) have only two valid
/// representations. Filling them with a raw entropy byte is undefined.
template <typename T>
constexpr bool is_boolean_representation() noexcept
{
using U = std::remove_cv_t<T>;
if constexpr (std::is_same_v<U, bool>)
{
return true;
}
else if constexpr (std::is_enum_v<U>)
{
return std::is_same_v<std::underlying_type_t<U>, bool>;
}
else
{
return false;
}
}
#if !defined(LIBDPF_USE_ARC4RANDOM)
/// One unbuffered, exclusively locked read of the entropy device.
/// Buffering would copy unread bytes into a `fork()` child, so parent and
/// child would repeat the same key material. The lock keeps concurrent
/// `fread` calls off the shared `FILE`.
struct entropy_source
{
#if defined(LIBDPF_USE_DEV_RANDOM)
static constexpr const char * path = "/dev/random";
static constexpr const char * open_error = "dpf: cannot open /dev/random\n";
#else
static constexpr const char * path = "/dev/urandom";
static constexpr const char * open_error = "dpf: cannot open /dev/urandom\n";
#endif
FILE * fp = nullptr;
std::mutex mu;
entropy_source() = default;
entropy_source(const entropy_source &) = delete;
entropy_source & operator=(const entropy_source &) = delete;
entropy_source(entropy_source &&) = delete;
entropy_source & operator=(entropy_source &&) = delete;
~entropy_source()
{
if (fp != nullptr)
{
std::fclose(fp);
}
}
void open_unlocked()
{
if (fp != nullptr)
{
return;
}
fp = std::fopen(path, "rb");
if (fp == nullptr)
{
std::fputs(open_error, stderr);
std::terminate();
}
// Before any read. A buffered FILE duplicates entropy across fork().
if (std::setvbuf(fp, nullptr, _IONBF, 0) != 0)
{
std::fclose(fp);
fp = nullptr;
std::fputs("dpf: cannot disable entropy buffering\n", stderr);
std::terminate();
}
int fd = ::fileno(fp);
if (fd >= 0)
{
::fcntl(fd, F_SETFD, FD_CLOEXEC);
}
}
void read(void * dst, std::size_t n)
{
std::lock_guard<std::mutex> lock(mu);
if (fp == nullptr)
{
open_unlocked();
}
auto * p = static_cast<unsigned char *>(dst);
while (n > 0)
{
std::size_t got = std::fread(p, 1, n, fp);
if (got == 0)
{
if (std::ferror(fp) && errno == EINTR)
{
std::clearerr(fp);
continue;
}
std::fputs("dpf: entropy read failed\n", stderr);
std::terminate();
}
p += got;
n -= got;
}
}
};
inline entropy_source & entropy()
{
static entropy_source source;
return source;
}
#endif // !LIBDPF_USE_ARC4RANDOM
} // namespace detail
template <typename T>
HEDLEY_NO_THROW
auto & uniform_fill(T & buf) noexcept // NOLINT(runtime/references)
{
static_assert(std::is_trivially_copyable_v<std::remove_cv_t<T>>,
"uniform_fill requires a trivially copyable type");
if constexpr (detail::is_boolean_representation<T>())
{
unsigned char raw = 0;
uniform_fill(raw);
buf = static_cast<T>(static_cast<bool>(raw & 1u));
return buf;
}
else
{
if (detail::fill_from_hook(buf)) return buf;
#if defined(LIBDPF_USE_ARC4RANDOM)
arc4random_buf(&buf, sizeof(buf));
#else
detail::entropy().read(&buf, sizeof(buf));
#endif
return buf;
}
}
template <typename T>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
auto uniform_sample() noexcept
{
using U = std::remove_cv_t<T>;
U buf;
uniform_fill(buf);
return buf;
}
template <typename T>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
auto additively_share(T secret) noexcept
{
using T_ = std::remove_cv_t<std::remove_reference_t<T>>;
T_ tmp = uniform_sample<T_>();
// Signed subtraction overflows for extreme shares. Subtract in the
// unsigned width and copy the bits back so the group is mod 2^n.
T_ other;
if constexpr (std::is_integral_v<T_> && std::is_signed_v<T_>)
{
using U = std::make_unsigned_t<T_>;
U diff = static_cast<U>(static_cast<T_>(secret)) - static_cast<U>(tmp);
std::memcpy(&other, &diff, sizeof(other));
}
else
{
other = static_cast<T_>(static_cast<T_>(secret) - tmp);
}
return std::make_pair(
additive_share<T_, 0>::from_raw(tmp),
additive_share<T_, 1>::from_raw(other));
}
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_RANDOM_HPP__