/// @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` /// (`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 #include #include #include #include #include #include #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 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 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 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 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 inline constexpr bool is_share_v = params>::value; namespace detail { template HEDLEY_ALWAYS_INLINE HEDLEY_CONST HEDLEY_NO_THROW constexpr T horner(T secret, const std::array & 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(static_cast(high * tx) + coeff[i]); } return static_cast(static_cast(high * tx) + secret); } template HEDLEY_CONST HEDLEY_NO_THROW constexpr bool distinct_points(const std::array & 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 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(num * static_cast(at - xj)); den = static_cast(den * static_cast(xi - xj)); } if (den == T{}) throw std::invalid_argument( "shamir: evaluation points collide in the field"); const T weight = static_cast( num * ::dpf::detail::shamir_field::inv(den)); secret = static_cast(secret + static_cast(ys[i] * weight)); } return secret; } template T open_points(const std::uint64_t * xs, const T * ys, std::size_t count) { static_assert(::dpf::detail::shamir_field::value, "shamir reconstruct: specialize detail::shamir_field"); 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 T open_runtime(const point_share * shares, std::size_t count) { std::array xs{}; std::array 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(shares[i].point) > N) throw std::invalid_argument( "shamir: evaluation point is outside 1..N"); xs[i] = static_cast(shares[i].point); ys[i] = shares[i].value; } return open_points(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`. /// @tparam T field element /// @tparam Party 0-based party index, in `0 .. N-1` /// @tparam K reconstruction threshold /// @tparam N shareholder count template struct basic_share { static_assert(!(K == 2 && N == 3), "(2,3) Shamir is shamir::share (dpf::shamir_share)"); static_assert(Party < N, "shamir party must be in 0 .. N-1"); using value_type = T; using access_type = access; 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(-value)); } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr basic_share & operator+=(const basic_share & rhs) noexcept { value = static_cast(value + rhs.value); return *this; } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr basic_share & operator-=(const basic_share & rhs) noexcept { value = static_cast(value - rhs.value); return *this; } template && std::is_convertible_v, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr basic_share & operator*=(const Scalar & c) noexcept { value = static_cast(value * static_cast(c)); return *this; } /// @brief Absorb a public plaintext. Every point of `p` grows by `c`. template && std::is_convertible_v, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr basic_share & operator+=(const Plain & c) noexcept { value = static_cast(value + static_cast(c)); return *this; } template && std::is_convertible_v, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW constexpr basic_share & operator-=(const Plain & c) noexcept { value = static_cast(value - static_cast(c)); return *this; } }; template struct params> : 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 HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr basic_share operator+( basic_share lhs, const basic_share & rhs) noexcept { lhs += rhs; return lhs; } template HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr basic_share operator-( basic_share lhs, const basic_share & rhs) noexcept { lhs -= rhs; return lhs; } template && std::is_convertible_v, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr basic_share operator*( basic_share lhs, const Scalar & c) noexcept { lhs *= c; return lhs; } template && std::is_convertible_v, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr basic_share operator*( const Scalar & c, basic_share rhs) noexcept { rhs *= c; return rhs; } template && std::is_convertible_v, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr basic_share operator+( basic_share lhs, const Plain & c) noexcept { lhs += c; return lhs; } template && std::is_convertible_v, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr basic_share operator+( const Plain & c, basic_share rhs) noexcept { rhs += c; return rhs; } template && std::is_convertible_v, int> = 0> HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr basic_share operator-( basic_share lhs, const Plain & c) noexcept { lhs -= c; return lhs; } template HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr bool operator==(const basic_share & lhs, const basic_share & rhs) noexcept { return lhs.raw() == rhs.raw(); } template HEDLEY_ALWAYS_INLINE HEDLEY_PURE HEDLEY_NO_THROW constexpr bool operator!=(const basic_share & lhs, const basic_share & 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 struct share_of { using type = basic_share; }; /// @brief Share of a `(K,N)` Shamir secret at party `Party`. /// @details `share` is `shamir_share`. template using share = typename share_of::type; namespace detail { template HEDLEY_ALWAYS_INLINE HEDLEY_CONST HEDLEY_NO_THROW constexpr auto deal_at(T secret, const std::array::degree> & coeff, std::index_sequence) noexcept { return std::make_tuple(share::from_raw( horner(secret, coeff, static_cast(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` 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` /// @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 HEDLEY_ALWAYS_INLINE HEDLEY_CONST HEDLEY_NO_THROW constexpr auto deal(T secret, const std::array::degree> & coeff) noexcept { return detail::deal_at( secret, coeff, std::make_index_sequence{}); } /// @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 , int> = 0> HEDLEY_WARN_UNUSED_RESULT auto reconstruct(const Share0 & first, const Rest &... rest) { using info = params>; 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 && params>::threshold == info::threshold && params>::parties == info::parties && std::is_same_v< typename params>::value_type, value_type>) && ...), "shamir reconstruct: shares must be one (K,N) scheme"); constexpr std::array xs{{ info::point, params>::point...}}; static_assert(detail::distinct_points(xs, info::parties), "shamir reconstruct: need distinct parties in 0 .. N-1"); const std::array ys{{first.raw(), rest.raw()...}}; return detail::open_points( xs.data(), ys.data(), M); } /// @brief Open runtime point shares. `shares` has length `count`. /// @tparam T field type. Requires `detail::shamir_field` /// @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 HEDLEY_WARN_UNUSED_RESULT T reconstruct(const point_share * shares, std::size_t count) { return detail::open_runtime(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 HEDLEY_WARN_UNUSED_RESULT T reconstruct(const std::array, M> & shares) { return detail::open_runtime(shares.data(), shares.size()); } } // namespace shamir } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_SHAMIR_HPP__