/// @file grotto/ring_switch.hpp /// @brief Exact share conversion from \f$\mathbb{Z}/2^n\f$ into a residue group. /// @details For an unsigned \f$n\f$-bit limb (\f$n\le 64\f$) with representatives /// in \f$[0,2^n)\f$, /// \f$\eta + r = x + w\cdot 2^n,\qquad w=\mathbf{1}[r+\eta\ge 2^n].\f$ /// In any modulus \f$M\f$, /// \f$x \equiv \eta + (r\bmod M) - w\cdot(2^n\bmod M)\pmod M.\f$ /// The wrap bit times \f$2^n\bmod M\f$ must be shared inside the /// destination group: a `uint64` comparison share is not a share /// mod \f$M\f$. The dealer keys `lt(2^n \bmod M)` at the secret \f$r\f$ /// and, after \f$\eta\f$ opens, each party evaluates at the public /// query \f$2^n-1-\eta\f$. That indicator is hot exactly on wrap, /// including the \f$\eta=0\f$ case (no wrap). Party 0 adds public /// \f$\eta\f$; both parties hold an additive split of \f$r\f$ in the /// residue group. /// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license. #ifndef LIBDPF_INCLUDE_GROTTO_RING_SWITCH_HPP__ #define LIBDPF_INCLUDE_GROTTO_RING_SWITCH_HPP__ #include "dpf.hpp" #include "dpf/field128.hpp" #include "dpf/p256_scalar.hpp" #include "grotto/residue.hpp" #include #include #include #include #include #include namespace grotto { namespace ring_switch_detail { template HEDLEY_NO_THROW constexpr unsigned limb_bits() noexcept { static_assert(std::is_unsigned_v, "ring switch limb domain must be an unsigned integer"); constexpr unsigned bits = static_cast(dpf::utils::bitlength_of_v); static_assert(bits >= 1 && bits <= 64, "ring switch supports 1..64 bit limbs"); return bits; } template HEDLEY_NO_THROW constexpr InputT limb_mask() noexcept { constexpr unsigned bits = limb_bits(); if constexpr (bits == 64) return ~InputT{0}; else return static_cast((InputT{1} << bits) - InputT{1}); } /// @brief Public query \f$2^n - 1 - \eta\f$ for the wrap `lt` key. template HEDLEY_NO_THROW constexpr InputT wrap_query(InputT eta) noexcept { return static_cast(limb_mask() - (eta & limb_mask())); } /// @brief \f$2^n\f$ as an integer for constructing the residue payload. template HEDLEY_NO_THROW constexpr unsigned __int128 pow2_n() noexcept { constexpr unsigned bits = limb_bits(); if constexpr (bits == 64) return static_cast(1) << 64; else return static_cast(1) << bits; } template struct has_modulus64 : std::false_type {}; template struct has_modulus64> : std::true_type {}; template struct has_modulus128 : std::false_type {}; template struct has_modulus128> : std::true_type {}; template Residue pow2_payload() { if constexpr (std::is_same_v) { constexpr unsigned bits = limb_bits(); if constexpr (bits == 64) return dpf::field128::from_lane(0, 1); else return Residue{static_cast(InputT{1} << bits)}; } else if constexpr (std::is_same_v) { constexpr unsigned bits = limb_bits(); if constexpr (bits == 64) return Residue{static_cast(1) << 64}; else return Residue{static_cast(InputT{1} << bits)}; } else if constexpr (has_modulus64::value) { return Residue{static_cast( pow2_n() % Residue::modulus)}; } else if constexpr (has_modulus128::value) { return Residue::from_u128(pow2_n()); } else { static_assert(sizeof(Residue) == 0, "unsupported ring switch residue"); return Residue{}; } } template Residue reduce_limb(InputT v) { return Residue{static_cast(v & limb_mask())}; } } // namespace ring_switch_detail /// @brief Dealer material for one exact ring switch into `Residue`. template struct ring_switch_keys { static_assert(std::is_unsigned_v, "ring switch limb domain must be an unsigned integer"); static_assert(dpf::utils::bitlength_of_v <= 64, "ring switch supports at most 64-bit limbs"); using input_type = InputT; using residue_type = Residue; using key_pair = decltype(dpf::make_dpf(std::declval(), dpf::lt(std::declval()))); using key_pair_v = decltype(dpf::make_dpf(std::declval(), dpf::lt(std::declval()), dpf::verifiable{})); InputT r{}; bool verifiable = false; std::optional keys{}; std::optional keys_v{}; /// @brief Additive split of `r` in `Residue`, indexed `[party]`. Residue r_share[2]{}; }; /// \complexity One `dpf::make_dpf` of `lt(2^n mod M)` at the masked limb `r`, plus one `from_seed` to split `r` in `Residue`. /// `n` is `bitlength_of_v` and the header rejects `n` outside `1 .. 64`. /// \rounds No party interaction. /// \communication None inside this function. /// \preprocessing One comparison key and two `Residue` words (`r_share[2]`). /// @note The comparison payload is `2^n mod M` in the destination group. A `uint64_t` comparison share is not a share modulo `M`. /// @see grotto::zn64 /// @see grotto::nmod /// @see [Exact ring switch](@ref ring_switch) template ring_switch_keys make_ring_switch_keys(InputT r) { using namespace ring_switch_detail; ring_switch_keys mat; mat.r = static_cast(r & limb_mask()); mat.verifiable = false; const Residue payload = pow2_payload(); mat.keys = dpf::make_dpf(mat.r, dpf::lt(payload)); const auto seed = dpf::uniform_sample(); mat.r_share[0] = Residue::from_seed(&seed, sizeof(seed)); mat.r_share[1] = reduce_limb(mat.r) - mat.r_share[0]; return mat; } /// \complexity One `dpf::make_dpf` of `lt(2^n mod M)` at the masked limb `r`, plus one `from_seed` to split `r` in `Residue`. /// `n` is `bitlength_of_v` and the header rejects `n` outside `1 .. 64`. /// \rounds No party interaction. /// \communication None inside this function. /// \preprocessing One comparison key and two `Residue` words (`r_share[2]`). /// @note The comparison payload is `2^n mod M` in the destination group. A `uint64_t` comparison share is not a share modulo `M`. /// @see grotto::zn64 /// @see grotto::nmod /// @see [Exact ring switch](@ref ring_switch) template ring_switch_keys make_ring_switch_keys(InputT r, dpf::verifiable) { using namespace ring_switch_detail; ring_switch_keys mat; mat.r = static_cast(r & limb_mask()); mat.verifiable = true; const Residue payload = pow2_payload(); mat.keys_v = dpf::make_dpf(mat.r, dpf::lt(payload), dpf::verifiable{}); const auto seed = dpf::uniform_sample(); mat.r_share[0] = Residue::from_seed(&seed, sizeof(seed)); mat.r_share[1] = reduce_limb(mat.r) - mat.r_share[0]; return mat; } /// @brief Cleartext \f$x \bmod M\f$ from the limb identity. /// \complexity A constant number of limb adds and one comparison of `r + eta` against `2^n`. `Θ(1)`. No keys. /// @see grotto::ring_switch_eval template Residue ring_switch_clear(InputT x, InputT r, InputT eta) { using namespace ring_switch_detail; const InputT mask = limb_mask(); x = static_cast(x & mask); r = static_cast(r & mask); eta = static_cast(eta & mask); const unsigned __int128 sum = static_cast(r) + eta; const unsigned __int128 modn = pow2_n(); const unsigned w = (sum >= modn) ? 1u : 0u; Residue out = reduce_limb(eta) + reduce_limb(r); if (w) out = out - pow2_payload(); return out; } /// @brief One party's share of \f$x\f$ in `Residue` after `eta` is public. /// \complexity One `dpf::eval_point` on the wrap key at the public query `2^n - 1 - eta`, then a constant number of `Residue` additions. /// Party 0 also adds `eta` reduced into `Residue`. `n ≤ 64`. /// Extra space `Θ(1)`. /// \rounds None. `eta` is an argument; the wrap bit is the comparison output, not a separate opening. /// \communication None. /// \preprocessing None created here. Uses the one key and the two `r_share` words from `make_ring_switch_keys`. /// @note The value subtracted on wrap is the keyed payload `2^n mod M`, not a `uint64_t` share. /// @see grotto::zn64 /// @see grotto::nmod /// @see [Exact ring switch](@ref ring_switch) template Residue ring_switch_eval( const ring_switch_keys & mat, InputT eta, dpf::proof_token * pi = nullptr) { static_assert(Party < 2, "ring switch party is 0 or 1"); using namespace ring_switch_detail; eta = static_cast(eta & limb_mask()); const InputT query = wrap_query(eta); Residue wrap{}; if (mat.verifiable) { if (!mat.keys_v) throw std::invalid_argument("ring switch: missing verifiable keys"); auto & key = std::get(*mat.keys_v); if (pi != nullptr) { wrap = dpf::eval_point(dpf::cmp, key, query, dpf::prove(*pi)).raw(); } else { wrap = dpf::eval_point(dpf::cmp, key, query).raw(); } } else { if (!mat.keys) throw std::invalid_argument("ring switch: missing keys"); auto & key = std::get(*mat.keys); wrap = dpf::eval_point(dpf::cmp, key, query).raw(); } Residue out = mat.r_share[Party] - wrap; if constexpr (Party == 0) out = out + reduce_limb(eta); return out; } } // namespace grotto #endif // LIBDPF_INCLUDE_GROTTO_RING_SWITCH_HPP__