2026-09-24 14:08:32 -06:00
|
|
|
|
/// @file dpf/random.hpp
|
2026-09-24 23:18:10 -06:00
|
|
|
|
/// @brief Entropy source and uniform sampling.
|
2026-09-24 14:08:32 -06:00
|
|
|
|
/// @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>
|
|
|
|
|
|
|
2026-09-28 05:59:19 -06:00
|
|
|
|
#include <array>
|
2026-09-24 14:08:32 -06:00
|
|
|
|
#include <cerrno>
|
|
|
|
|
|
#include <cstddef>
|
2026-09-28 05:59:19 -06:00
|
|
|
|
#include <cstdint>
|
2026-09-24 14:08:32 -06:00
|
|
|
|
#include <cstdio>
|
|
|
|
|
|
#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
|
|
|
|
|
|
{
|
|
|
|
|
|
|
2026-09-28 05:59:19 -06:00
|
|
|
|
/// @brief Thread-local count of bytes delivered by `uniform_fill`.
|
|
|
|
|
|
inline thread_local std::uint64_t random_bytes_tls = 0;
|
|
|
|
|
|
|
2026-09-24 23:18:10 -06:00
|
|
|
|
/// @brief When set, `uniform_fill` copies from this hook and does not read the
|
2026-09-24 14:08:32 -06:00
|
|
|
|
/// 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;
|
|
|
|
|
|
|
2026-09-28 05:59:19 -06:00
|
|
|
|
/// @brief State for `uniform_bytes_hook`, set and read by the hook's owner
|
|
|
|
|
|
/// (`experiment` keeps itself here). Travels with the hook when another thread
|
|
|
|
|
|
/// adopts this one's draws (`dpf/thread_work.hpp`).
|
|
|
|
|
|
inline thread_local void * uniform_bytes_ctx = nullptr;
|
|
|
|
|
|
|
2026-09-24 14:08:32 -06:00
|
|
|
|
template <typename T>
|
|
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
bool fill_from_hook(T & buf) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
if (uniform_bytes_hook == nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
uniform_bytes_hook(&buf, sizeof(buf));
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 23:18:10 -06:00
|
|
|
|
/// @brief `bool` and `enum : bool` (including `dpf::bit`) have only two valid
|
2026-09-24 14:08:32 -06:00
|
|
|
|
/// representations. Filling them with a raw entropy byte is undefined.
|
2026-09-24 23:18:10 -06:00
|
|
|
|
/// @tparam T value type
|
|
|
|
|
|
/// @return `bool` and `enum : bool` (including `dpf::bit`) have only two valid representations
|
2026-09-24 14:08:32 -06:00
|
|
|
|
template <typename T>
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
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)
|
|
|
|
|
|
|
2026-09-24 23:18:10 -06:00
|
|
|
|
/// @brief One unbuffered, exclusively locked read of the entropy device.
|
|
|
|
|
|
/// @details Buffering would copy unread bytes into a `fork()` child, so parent and
|
2026-09-24 14:08:32 -06:00
|
|
|
|
/// 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;
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
~entropy_source() noexcept
|
2026-09-24 14:08:32 -06:00
|
|
|
|
{
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2026-09-28 05:59:19 -06:00
|
|
|
|
/// @brief Zero the thread-local random-byte counter.
|
|
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
|
|
|
|
|
void reset_random_bytes_count() noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
detail::random_bytes_tls = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// @brief Bytes filled by `uniform_fill` since the last reset on this thread.
|
|
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
|
|
|
|
|
std::uint64_t random_bytes_count() noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
return detail::random_bytes_tls;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 14:08:32 -06:00
|
|
|
|
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
|
|
|
|
|
|
{
|
2026-09-28 05:59:19 -06:00
|
|
|
|
if (detail::fill_from_hook(buf))
|
|
|
|
|
|
{
|
|
|
|
|
|
detail::random_bytes_tls += sizeof(buf);
|
|
|
|
|
|
return buf;
|
|
|
|
|
|
}
|
2026-09-24 14:08:32 -06:00
|
|
|
|
#if defined(LIBDPF_USE_ARC4RANDOM)
|
|
|
|
|
|
arc4random_buf(&buf, sizeof(buf));
|
|
|
|
|
|
#else
|
|
|
|
|
|
detail::entropy().read(&buf, sizeof(buf));
|
|
|
|
|
|
#endif
|
2026-09-28 05:59:19 -06:00
|
|
|
|
detail::random_bytes_tls += sizeof(buf);
|
2026-09-24 14:08:32 -06:00
|
|
|
|
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_>();
|
2026-09-28 05:59:19 -06:00
|
|
|
|
T_ other = detail::group_sub(static_cast<T_>(secret), tmp);
|
2026-09-24 14:08:32 -06:00
|
|
|
|
return std::make_pair(
|
|
|
|
|
|
additive_share<T_, 0>::from_raw(tmp),
|
|
|
|
|
|
additive_share<T_, 1>::from_raw(other));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-28 05:59:19 -06:00
|
|
|
|
/// @brief Uniform (3,3)-additive sharing of `secret`.
|
|
|
|
|
|
/// @details Two components are uniform. The third is `secret` minus those
|
|
|
|
|
|
/// two in the share group, so the three shares sum to `secret`.
|
|
|
|
|
|
/// @tparam T value type
|
|
|
|
|
|
/// @param secret the cleartext secret
|
|
|
|
|
|
/// @return shares for parties 0, 1, and 2
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
auto additively_share3(T secret) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
using T_ = std::remove_cv_t<std::remove_reference_t<T>>;
|
|
|
|
|
|
const T_ a = uniform_sample<T_>();
|
|
|
|
|
|
const T_ b = uniform_sample<T_>();
|
|
|
|
|
|
const T_ c = detail::group_sub(
|
|
|
|
|
|
detail::group_sub(static_cast<T_>(secret), a), b);
|
|
|
|
|
|
return std::make_tuple(
|
|
|
|
|
|
additive3_share<T_, 0>::from_raw(a),
|
|
|
|
|
|
additive3_share<T_, 1>::from_raw(b),
|
|
|
|
|
|
additive3_share<T_, 2>::from_raw(c));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// @brief Uniform (2,3)-replicated sharing of `secret`.
|
|
|
|
|
|
/// @details The underlying (3,3) components are a uniform additive split.
|
|
|
|
|
|
/// Each party receives its component and the next party's.
|
|
|
|
|
|
/// @tparam T value type
|
|
|
|
|
|
/// @param secret the cleartext secret
|
|
|
|
|
|
/// @return shares for parties 0, 1, and 2
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
auto share_replicated(T secret) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
using T_ = std::remove_cv_t<std::remove_reference_t<T>>;
|
|
|
|
|
|
const T_ x0 = uniform_sample<T_>();
|
|
|
|
|
|
const T_ x1 = uniform_sample<T_>();
|
|
|
|
|
|
const T_ x2 = detail::group_sub(
|
|
|
|
|
|
detail::group_sub(static_cast<T_>(secret), x0), x1);
|
|
|
|
|
|
return make_replicated_shares(x0, x1, x2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
namespace shamir
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
/// @brief Uniform `(K,N)` Shamir sharing of `secret`.
|
|
|
|
|
|
/// @details Coefficients of `x, ..., x^{K-1}` are `uniform_sample<T>`. The
|
|
|
|
|
|
/// shares are `deal<T, K, N>`. Threshold 1 draws nothing: every share
|
|
|
|
|
|
/// equals `secret`. `shamir3::share_secret` is the `(2,3)` case on
|
|
|
|
|
|
/// `fp61`, reindexed to points `1`, `2`, and `3`.
|
|
|
|
|
|
/// @tparam K shares required to reconstruct
|
|
|
|
|
|
/// @tparam N shareholders
|
|
|
|
|
|
/// @tparam T field type. Opening needs `detail::shamir_field<T>`
|
|
|
|
|
|
/// @param secret the cleartext secret
|
|
|
|
|
|
/// @return one share per party `0 .. N-1`
|
|
|
|
|
|
/// \complexity O(NK) field operations, plus `K-1` field samples. No messages.
|
|
|
|
|
|
template <typename T, std::size_t K, std::size_t N>
|
|
|
|
|
|
HEDLEY_WARN_UNUSED_RESULT
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
auto share_secret(T secret) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
using T_ = std::remove_cv_t<std::remove_reference_t<T>>;
|
|
|
|
|
|
constexpr std::size_t degree = access<K, N>::degree;
|
|
|
|
|
|
std::array<T_, degree> coeff{};
|
|
|
|
|
|
for (std::size_t i = 0; i < degree; ++i)
|
|
|
|
|
|
coeff[i] = uniform_sample<T_>();
|
|
|
|
|
|
return deal<T_, K, N>(static_cast<T_>(secret), coeff);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace shamir
|
|
|
|
|
|
|
2026-09-24 14:08:32 -06:00
|
|
|
|
} // namespace dpf
|
|
|
|
|
|
|
|
|
|
|
|
#endif // LIBDPF_INCLUDE_DPF_RANDOM_HPP__
|