libdpf/include/dpf/shamir.hpp

565 lines
19 KiB
C++
Raw Normal View History

/// @file dpf/shamir.hpp
/// @brief (K,N) Shamir secret sharing over a field.
/// @details The secret is the constant term of a polynomial of degree `K-1`.
/// Party `i` (0-based) holds that polynomial at `x = i+1`. Any `K`
/// shares reconstruct by Lagrange at `0`. Further shares are checked
/// against the polynomial of the first `K`; they are not an error
/// correction. Exactly `K` shares are not checked. When `K = N` that
/// is every share. A full set of shares of a different secret is
/// consistent with itself. `(2,3)`
/// is `two_of_three`. That case is the type `shamir_share<T, Party>`
/// (`sharing::shamir`), and `shamir3` is the same polynomial on
/// `fp61`. Uniform coefficients are drawn by `shamir::share_secret`
/// in `random.hpp`. A complete program is `examples/mwe/shamir.cpp`.
/// @copyright Copyright (c) 2019-2026 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_SHAMIR_HPP__
#define LIBDPF_INCLUDE_DPF_SHAMIR_HPP__
#include <array>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include "hedley/hedley.h"
namespace dpf
{
namespace detail
{
/// @brief Field inverse used by Shamir reconstruction.
/// @details Specialize for a field. `fp61` is specialized in `fp61.hpp`.
/// @tparam T value type
template <typename T>
struct shamir_field : std::false_type
{
};
} // namespace detail
namespace shamir
{
/// @brief Access structure: any `K` of `N` shares open the secret.
/// @tparam K reconstruction threshold, at least 1
/// @tparam N shareholder count, at least `K`
template <std::size_t K, std::size_t N>
struct access
{
static_assert(K >= 1, "shamir threshold is at least 1");
static_assert(N >= K, "shamir threshold cannot exceed the shareholder count");
/// @brief Shares required to open the secret.
static constexpr std::size_t threshold = K;
/// @brief Shareholders who receive a point.
static constexpr std::size_t parties = N;
/// @brief Polynomial degree, `K - 1`.
static constexpr std::size_t degree = K - 1;
};
/// @brief The (2,3) access structure behind `sharing::shamir` and `shamir3`.
using two_of_three = access<2, 3>;
/// @brief One shareholder's value at a runtime evaluation point.
/// @tparam T field element
template <typename T>
struct point_share
{
/// @brief Evaluation point in `1 .. N`. Party `i` uses point `i + 1`.
int point = 1;
/// @brief `p(point)`, where `p(0)` is the secret.
T value{};
};
/// @brief Traits of a typed Shamir share. The primary is not a Shamir share.
template <typename Share, typename Enable = void>
struct params : std::false_type
{
using value_type = void;
static constexpr std::size_t party = 0;
static constexpr std::size_t threshold = 0;
static constexpr std::size_t parties = 0;
static constexpr std::uint64_t point = 0;
};
template <typename Share>
inline constexpr bool is_share_v = params<std::decay_t<Share>>::value;
namespace detail
{
template <typename T, std::size_t Degree>
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr T horner(T secret, const std::array<T, Degree> & coeff,
std::uint64_t x) noexcept
{
// p(x) = secret + c[0] x + c[1] x^2 + ... + c[Degree-1] x^Degree.
T high{};
const T tx{x};
for (std::size_t k = 0; k < Degree; ++k)
{
const std::size_t i = Degree - 1 - k;
high = static_cast<T>(static_cast<T>(high * tx) + coeff[i]);
}
return static_cast<T>(static_cast<T>(high * tx) + secret);
}
template <std::size_t M>
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr bool distinct_points(const std::array<std::uint64_t, M> & xs,
std::size_t parties) noexcept
{
for (std::size_t i = 0; i < M; ++i)
{
if (xs[i] < 1 || xs[i] > parties)
return false;
for (std::size_t j = 0; j < i; ++j)
if (xs[i] == xs[j])
return false;
}
return true;
}
template <typename T>
T lagrange(T at, const std::uint64_t * xs, const T * ys, std::size_t k)
{
T secret{};
const T one{1};
for (std::size_t i = 0; i < k; ++i)
{
T num = one;
T den = one;
const T xi{xs[i]};
for (std::size_t j = 0; j < k; ++j)
{
if (i == j)
continue;
const T xj{xs[j]};
num = static_cast<T>(num * static_cast<T>(at - xj));
den = static_cast<T>(den * static_cast<T>(xi - xj));
}
if (den == T{})
throw std::invalid_argument(
"shamir: evaluation points collide in the field");
const T weight = static_cast<T>(
num * ::dpf::detail::shamir_field<T>::inv(den));
secret = static_cast<T>(secret + static_cast<T>(ys[i] * weight));
}
return secret;
}
template <typename T, std::size_t K, std::size_t N>
T open_points(const std::uint64_t * xs, const T * ys, std::size_t count)
{
static_assert(::dpf::detail::shamir_field<T>::value,
"shamir reconstruct: specialize detail::shamir_field<T>");
if (count < K || count > N)
throw std::invalid_argument(
"shamir: the number of shares must be between K and N");
for (std::size_t i = 0; i < count; ++i)
{
if (xs[i] < 1 || xs[i] > N)
throw std::invalid_argument(
"shamir: evaluation point is outside 1..N");
for (std::size_t j = 0; j < i; ++j)
if (xs[i] == xs[j])
throw std::invalid_argument("shamir: duplicate evaluation point");
// x = 0 is the secret. A field that folds the integer point onto 0
// (gf2 with N > 1) would hand that party the secret.
if (T{xs[i]} == T{})
throw std::invalid_argument(
"shamir: evaluation point is zero in the field");
}
const T secret = lagrange(T{}, xs, ys, K);
for (std::size_t extra = K; extra < count; ++extra)
{
if (lagrange(T{xs[extra]}, xs, ys, K) != ys[extra])
throw std::runtime_error("shamir: inconsistent shares");
}
return secret;
}
template <typename T, std::size_t K, std::size_t N>
T open_runtime(const point_share<T> * shares, std::size_t count)
{
std::array<std::uint64_t, N> xs{};
std::array<T, N> ys{};
if (count < K || count > N)
throw std::invalid_argument(
"shamir: the number of shares must be between K and N");
for (std::size_t i = 0; i < count; ++i)
{
if (shares[i].point < 1
|| static_cast<std::size_t>(shares[i].point) > N)
throw std::invalid_argument(
"shamir: evaluation point is outside 1..N");
xs[i] = static_cast<std::uint64_t>(shares[i].point);
ys[i] = shares[i].value;
}
return open_points<T, K, N>(xs.data(), ys.data(), count);
}
} // namespace detail
/// @brief Share of a `(K,N)` Shamir secret at a compile-time party.
/// @details `(2,3)` is not this type. It is `shamir_share<T, Party>`.
/// @tparam T field element
/// @tparam Party 0-based party index, in `0 .. N-1`
/// @tparam K reconstruction threshold
/// @tparam N shareholder count
template <typename T, std::size_t Party, std::size_t K, std::size_t N>
struct basic_share
{
static_assert(!(K == 2 && N == 3),
"(2,3) Shamir is shamir::share<T, Party, 2, 3> (dpf::shamir_share)");
static_assert(Party < N, "shamir party must be in 0 .. N-1");
using value_type = T;
using access_type = access<K, N>;
static constexpr std::size_t party = Party;
static constexpr std::size_t threshold = K;
static constexpr std::size_t parties = N;
static constexpr std::size_t degree = access_type::degree;
/// @brief Lagrange point. Party 0 is point 1.
static constexpr std::uint64_t point = Party + 1;
T value{};
/// @brief Bit-preserving construction. Does not apply a Lagrange weight.
/// @param v the field element
/// @return the share
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
static constexpr basic_share from_raw(T v) noexcept
{
basic_share s;
s.value = v;
return s;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
constexpr const T & raw() const noexcept { return value; }
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
constexpr T & raw() noexcept { return value; }
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr basic_share operator-() const noexcept
{
return from_raw(static_cast<T>(-value));
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr basic_share & operator+=(const basic_share & rhs) noexcept
{
value = static_cast<T>(value + rhs.value);
return *this;
}
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr basic_share & operator-=(const basic_share & rhs) noexcept
{
value = static_cast<T>(value - rhs.value);
return *this;
}
template <typename Scalar,
std::enable_if_t<!is_share_v<Scalar>
&& std::is_convertible_v<Scalar, T>, int> = 0>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr basic_share & operator*=(const Scalar & c) noexcept
{
value = static_cast<T>(value * static_cast<T>(c));
return *this;
}
/// @brief Absorb a public plaintext. Every point of `p` grows by `c`.
template <typename Plain,
std::enable_if_t<!is_share_v<Plain>
&& std::is_convertible_v<Plain, T>, int> = 0>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr basic_share & operator+=(const Plain & c) noexcept
{
value = static_cast<T>(value + static_cast<T>(c));
return *this;
}
template <typename Plain,
std::enable_if_t<!is_share_v<Plain>
&& std::is_convertible_v<Plain, T>, int> = 0>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
constexpr basic_share & operator-=(const Plain & c) noexcept
{
value = static_cast<T>(value - static_cast<T>(c));
return *this;
}
};
template <typename T, std::size_t Party, std::size_t K, std::size_t N>
struct params<basic_share<T, Party, K, N>> : std::true_type
{
using value_type = T;
static constexpr std::size_t party = Party;
static constexpr std::size_t threshold = K;
static constexpr std::size_t parties = N;
static constexpr std::uint64_t point = Party + 1;
};
template <typename T, std::size_t Party, std::size_t K, std::size_t N>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr basic_share<T, Party, K, N> operator+(
basic_share<T, Party, K, N> lhs,
const basic_share<T, Party, K, N> & rhs) noexcept
{
lhs += rhs;
return lhs;
}
template <typename T, std::size_t Party, std::size_t K, std::size_t N>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr basic_share<T, Party, K, N> operator-(
basic_share<T, Party, K, N> lhs,
const basic_share<T, Party, K, N> & rhs) noexcept
{
lhs -= rhs;
return lhs;
}
template <typename T, std::size_t Party, std::size_t K, std::size_t N, typename Scalar,
std::enable_if_t<!is_share_v<Scalar> && std::is_convertible_v<Scalar, T>, int> = 0>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr basic_share<T, Party, K, N> operator*(
basic_share<T, Party, K, N> lhs, const Scalar & c) noexcept
{
lhs *= c;
return lhs;
}
template <typename T, std::size_t Party, std::size_t K, std::size_t N, typename Scalar,
std::enable_if_t<!is_share_v<Scalar> && std::is_convertible_v<Scalar, T>, int> = 0>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr basic_share<T, Party, K, N> operator*(
const Scalar & c, basic_share<T, Party, K, N> rhs) noexcept
{
rhs *= c;
return rhs;
}
template <typename T, std::size_t Party, std::size_t K, std::size_t N, typename Plain,
std::enable_if_t<!is_share_v<Plain> && std::is_convertible_v<Plain, T>, int> = 0>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr basic_share<T, Party, K, N> operator+(
basic_share<T, Party, K, N> lhs, const Plain & c) noexcept
{
lhs += c;
return lhs;
}
template <typename T, std::size_t Party, std::size_t K, std::size_t N, typename Plain,
std::enable_if_t<!is_share_v<Plain> && std::is_convertible_v<Plain, T>, int> = 0>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr basic_share<T, Party, K, N> operator+(
const Plain & c, basic_share<T, Party, K, N> rhs) noexcept
{
rhs += c;
return rhs;
}
template <typename T, std::size_t Party, std::size_t K, std::size_t N, typename Plain,
std::enable_if_t<!is_share_v<Plain> && std::is_convertible_v<Plain, T>, int> = 0>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr basic_share<T, Party, K, N> operator-(
basic_share<T, Party, K, N> lhs, const Plain & c) noexcept
{
lhs -= c;
return lhs;
}
template <typename T, std::size_t Party, std::size_t K, std::size_t N>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr bool operator==(const basic_share<T, Party, K, N> & lhs,
const basic_share<T, Party, K, N> & rhs) noexcept
{
return lhs.raw() == rhs.raw();
}
template <typename T, std::size_t Party, std::size_t K, std::size_t N>
HEDLEY_ALWAYS_INLINE
HEDLEY_PURE
HEDLEY_NO_THROW
constexpr bool operator!=(const basic_share<T, Party, K, N> & lhs,
const basic_share<T, Party, K, N> & rhs) noexcept
{
return !(lhs == rhs);
}
/// @brief Maps `(T, Party, K, N)` to the share type.
/// @details `(2,3)` is specialized to `shamir_share` in `secret_share.hpp`.
template <typename T, std::size_t Party, std::size_t K, std::size_t N>
struct share_of
{
using type = basic_share<T, Party, K, N>;
};
/// @brief Share of a `(K,N)` Shamir secret at party `Party`.
/// @details `share<T, Party, 2, 3>` is `shamir_share<T, Party>`.
template <typename T, std::size_t Party, std::size_t K, std::size_t N>
using share = typename share_of<T, Party, K, N>::type;
namespace detail
{
template <typename T, std::size_t K, std::size_t N, std::size_t... Party>
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr auto deal_at(T secret,
const std::array<T, access<K, N>::degree> & coeff,
std::index_sequence<Party...>) noexcept
{
return std::make_tuple(share<T, Party, K, N>::from_raw(
horner(secret, coeff, static_cast<std::uint64_t>(Party + 1)))...);
}
} // namespace detail
/// @brief Share `secret` at points `1 .. N`.
/// @details `p(x) = secret + coeff[0] x + ... + coeff[K-2] x^{K-1}`.
/// Party `i` stores `p(i+1)`. A zero coefficient is allowed.
/// Threshold 1 takes an empty coefficient array and copies `secret`.
/// `(2,3)` returns `shamir_share`s. `make_shamir_shares(secret, slope)`
/// is `deal<T, 2, 3>` with that one coefficient.
/// @tparam K reconstruction threshold
/// @tparam N shareholder count
/// @tparam T field type. `+` and `*` are the field operations. Opening also
/// needs `detail::shamir_field<T>`
/// @param secret the constant term
/// @param coeff higher coefficients, low degree first
/// @return shares for parties `0 .. N-1`
/// @see shamir::share_secret
/// \complexity O(NK) field operations. No messages.
template <typename T, std::size_t K, std::size_t N>
HEDLEY_ALWAYS_INLINE
HEDLEY_CONST
HEDLEY_NO_THROW
constexpr auto deal(T secret,
const std::array<T, access<K, N>::degree> & coeff) noexcept
{
return detail::deal_at<T, K, N>(
secret, coeff, std::make_index_sequence<N>{});
}
/// @brief Open typed shares of one `(K,N)` scheme.
/// @details Pass at least `K` and at most `N` shares. The first `K` are the
/// interpolating set and are not checked against each other. Each
/// further share must lie on that polynomial. A lie among the first
/// `K` is reported only when an honest extra share is present. A
/// complete set of shares of some other secret does not throw.
/// @tparam Share0 first share
/// @tparam Rest the other shares
/// @param first first share
/// @param rest the other shares
/// @return the secret
/// @throws std::invalid_argument if a Lagrange denominator is zero
/// @throws std::runtime_error if an extra share misses the polynomial
/// \complexity O(MK^2) field operations for M shares. The shares are already in hand; this function does not exchange them.
template <typename Share0, typename... Rest,
std::enable_if_t<is_share_v<Share0>, int> = 0>
HEDLEY_WARN_UNUSED_RESULT
auto reconstruct(const Share0 & first, const Rest &... rest)
{
using info = params<std::decay_t<Share0>>;
using value_type = typename info::value_type;
constexpr std::size_t M = 1 + sizeof...(Rest);
static_assert(M >= info::threshold && M <= info::parties,
"shamir reconstruct: the number of shares must be between K and N");
static_assert(((is_share_v<Rest>
&& params<std::decay_t<Rest>>::threshold == info::threshold
&& params<std::decay_t<Rest>>::parties == info::parties
&& std::is_same_v<
typename params<std::decay_t<Rest>>::value_type, value_type>) && ...),
"shamir reconstruct: shares must be one (K,N) scheme");
constexpr std::array<std::uint64_t, M> xs{{
info::point, params<std::decay_t<Rest>>::point...}};
static_assert(detail::distinct_points(xs, info::parties),
"shamir reconstruct: need distinct parties in 0 .. N-1");
const std::array<value_type, M> ys{{first.raw(), rest.raw()...}};
return detail::open_points<value_type, info::threshold, info::parties>(
xs.data(), ys.data(), M);
}
/// @brief Open runtime point shares. `shares` has length `count`.
/// @tparam T field type. Requires `detail::shamir_field<T>`
/// @tparam K reconstruction threshold
/// @tparam N shareholder count
/// @param shares evaluation points and values
/// @param count length of `shares`, in `K .. N`
/// @return the secret
/// @throws std::invalid_argument if the count or the points are illegal
/// @throws std::runtime_error if an extra share misses the polynomial of the first K
/// \complexity O(MK^2) field operations for M shares. The shares are already in hand; this function does not exchange them.
template <typename T, std::size_t K, std::size_t N>
HEDLEY_WARN_UNUSED_RESULT
T reconstruct(const point_share<T> * shares, std::size_t count)
{
return detail::open_runtime<T, K, N>(shares, count);
}
/// @brief Open a fixed list of runtime point shares.
/// @tparam T field type
/// @tparam K reconstruction threshold
/// @tparam N shareholder count
/// @tparam M number of shares in the list
/// @param shares evaluation points and values
/// @return the secret
/// @throws std::invalid_argument if the count or the points are illegal
/// @throws std::runtime_error if an extra share misses the polynomial of the first K
template <typename T, std::size_t K, std::size_t N, std::size_t M>
HEDLEY_WARN_UNUSED_RESULT
T reconstruct(const std::array<point_share<T>, M> & shares)
{
return detail::open_runtime<T, K, N>(shares.data(), shares.size());
}
} // namespace shamir
} // namespace dpf
#endif // LIBDPF_INCLUDE_DPF_SHAMIR_HPP__