/// @file grotto/residue.hpp /// @brief Compile-time modular residues as comparison payload groups. /// @details `zn64` and `zn128` supply `from_seed`, `+`, and unary /// `-`, which selects the payload-group comparison path. The modulus /// is a template argument so keygen needs no runtime side channel. /// @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_RESIDUE_HPP__ #define LIBDPF_INCLUDE_GROTTO_RESIDUE_HPP__ #include #include #include #include #include "hedley/hedley.h" namespace grotto { /// @brief Residue class modulo a 64-bit compile-time modulus. /// @tparam Mod compile-time modulus, greater than 1 /// @see grotto::make_ring_switch_keys /// @see grotto::ring_switch_factor /// \complexity Construction, `from_seed`, addition, and negation are one remainder each. `Θ(1)` time and extra space. template class zn64 { static_assert(Mod > 1, "zn64 modulus must be greater than 1"); public: /// \complexity One remainder modulo `Mod`, with a sign branch for negative `T`. `Θ(1)`. /// @see grotto::ring_switch_eval static constexpr std::uint64_t modulus = Mod; static constexpr bool dpf_point_group = true; HEDLEY_ALWAYS_INLINE constexpr zn64() noexcept = default; template >> HEDLEY_ALWAYS_INLINE constexpr zn64(T v) noexcept { assign_integer(v); } HEDLEY_ALWAYS_INLINE constexpr zn64(unsigned __int128 v) noexcept { val_ = static_cast(v % Mod); } /// \complexity Copies at most 16 seed bytes and reduces the 128-bit word modulo `Mod`. `Θ(1)`. /// @see grotto::make_ring_switch_keys HEDLEY_ALWAYS_INLINE static zn64 from_seed(const void * bytes, std::size_t n) noexcept { unsigned char buf[16]{}; if (n > sizeof(buf)) n = sizeof(buf); std::memcpy(buf, bytes, n); std::uint64_t w[2]{}; std::memcpy(w, buf, sizeof(w)); const unsigned __int128 wide = static_cast(w[0]) | (static_cast(w[1]) << 64); return from_reduced(static_cast(wide % Mod)); } HEDLEY_ALWAYS_INLINE HEDLEY_PURE constexpr std::uint64_t raw() const noexcept { return val_; } HEDLEY_ALWAYS_INLINE friend constexpr zn64 operator+(zn64 a, zn64 b) noexcept { const unsigned __int128 s = static_cast(a.val_) + b.val_; return from_reduced(static_cast(s % Mod)); } HEDLEY_ALWAYS_INLINE friend constexpr zn64 operator-(zn64 a) noexcept { if (a.val_ == 0) return a; return from_reduced(Mod - a.val_); } HEDLEY_ALWAYS_INLINE friend constexpr zn64 operator-(zn64 a, zn64 b) noexcept { return a + (-b); } HEDLEY_ALWAYS_INLINE friend constexpr bool operator==(zn64 a, zn64 b) noexcept { return a.val_ == b.val_; } HEDLEY_ALWAYS_INLINE friend constexpr bool operator!=(zn64 a, zn64 b) noexcept { return !(a == b); } private: std::uint64_t val_{}; HEDLEY_ALWAYS_INLINE static constexpr zn64 from_reduced(std::uint64_t v) noexcept { zn64 out; out.val_ = v; return out; } template HEDLEY_ALWAYS_INLINE constexpr void assign_integer(T v) noexcept { if constexpr (std::is_signed_v) { if (v < 0) { using U = std::make_unsigned_t; const auto mag = static_cast(0) - static_cast(v); val_ = static_cast( Mod - (static_cast(mag) % Mod)); if (val_ == Mod) val_ = 0; return; } val_ = static_cast( static_cast>(v) % Mod); } else { val_ = static_cast( static_cast(v) % Mod); } } }; /// @brief Residue class modulo a 128-bit compile-time modulus `(Hi << 64) | Lo`. /// @tparam Lo low 64 bits of the modulus /// @tparam Hi high 64 bits of the modulus /// @see grotto::make_ring_switch_keys /// @see grotto::zn64 /// \complexity Constructors reduce one integer modulo that modulus. `from_seed` folds at most 16 bytes. Addition is one 128-bit sum and a remainder. `Θ(1)` time and extra space. template class zn128 { static_assert(Lo != 0 || Hi != 0, "zn128 modulus must be nonzero"); static_assert(Hi != 0 || Lo > 1, "zn128 modulus must be greater than 1"); public: static constexpr std::uint64_t modulus_lo = Lo; static constexpr std::uint64_t modulus_hi = Hi; static constexpr bool dpf_point_group = true; HEDLEY_ALWAYS_INLINE constexpr zn128() noexcept = default; template >> HEDLEY_ALWAYS_INLINE constexpr zn128(T v) noexcept { assign_integer(v); } HEDLEY_ALWAYS_INLINE static zn128 from_u128(unsigned __int128 v) noexcept { return reduce(static_cast(v), static_cast(v >> 64)); } HEDLEY_ALWAYS_INLINE static zn128 from_seed(const void * bytes, std::size_t n) noexcept { unsigned char buf[16]{}; if (n > sizeof(buf)) n = sizeof(buf); std::memcpy(buf, bytes, n); std::uint64_t w[2]{}; std::memcpy(w, buf, sizeof(w)); return reduce(w[0], w[1]); } HEDLEY_ALWAYS_INLINE HEDLEY_PURE constexpr std::uint64_t lo() const noexcept { return lo_; } HEDLEY_ALWAYS_INLINE HEDLEY_PURE constexpr std::uint64_t hi() const noexcept { return hi_; } HEDLEY_ALWAYS_INLINE friend constexpr zn128 operator+(zn128 a, zn128 b) noexcept { const unsigned __int128 low = static_cast(a.lo_) + b.lo_; const unsigned __int128 high = static_cast(a.hi_) + b.hi_ + (low >> 64); std::uint64_t lo = static_cast(low); std::uint64_t hi = static_cast(high); const std::uint64_t top = static_cast(high >> 64); // Two reduced residues sum to less than 2M < 2^129. if (top != 0) sub_words(lo, hi, Lo, Hi, lo, hi); if (hi > Hi || (hi == Hi && lo >= Lo)) sub_words(lo, hi, Lo, Hi, lo, hi); return from_reduced(lo, hi); } HEDLEY_ALWAYS_INLINE friend constexpr zn128 operator-(zn128 a) noexcept { if (a.lo_ == 0 && a.hi_ == 0) return a; std::uint64_t ol = 0, oh = 0; sub_words(Lo, Hi, a.lo_, a.hi_, ol, oh); return from_reduced(ol, oh); } HEDLEY_ALWAYS_INLINE friend constexpr zn128 operator-(zn128 a, zn128 b) noexcept { return a + (-b); } HEDLEY_ALWAYS_INLINE friend constexpr bool operator==(zn128 a, zn128 b) noexcept { return a.lo_ == b.lo_ && a.hi_ == b.hi_; } HEDLEY_ALWAYS_INLINE friend constexpr bool operator!=(zn128 a, zn128 b) noexcept { return !(a == b); } private: std::uint64_t lo_{}; std::uint64_t hi_{}; HEDLEY_ALWAYS_INLINE static constexpr zn128 from_reduced(std::uint64_t lo, std::uint64_t hi) noexcept { zn128 out; out.lo_ = lo; out.hi_ = hi; return out; } HEDLEY_ALWAYS_INLINE static constexpr bool less(std::uint64_t ah, std::uint64_t al, std::uint64_t bh, std::uint64_t bl) noexcept { return ah < bh || (ah == bh && al < bl); } HEDLEY_ALWAYS_INLINE static constexpr void sub_words(std::uint64_t al, std::uint64_t ah, std::uint64_t bl, std::uint64_t bh, std::uint64_t & ol, std::uint64_t & oh) noexcept { const unsigned borrow = al < bl ? 1u : 0u; ol = static_cast(al - bl); oh = static_cast(ah - bh - borrow); } HEDLEY_ALWAYS_INLINE static constexpr zn128 reduce(std::uint64_t lo, std::uint64_t hi) noexcept { const unsigned __int128 mod = static_cast(Lo) | (static_cast(Hi) << 64); const unsigned __int128 v = static_cast(lo) | (static_cast(hi) << 64); const unsigned __int128 r = v % mod; return from_reduced(static_cast(r), static_cast(r >> 64)); } template HEDLEY_ALWAYS_INLINE constexpr void assign_integer(T v) noexcept { bool neg = false; unsigned __int128 mag = 0; if constexpr (std::is_signed_v) { if (v < 0) { neg = true; using U = std::make_unsigned_t; mag = static_cast(0) - static_cast(v); } else { mag = static_cast>(v); } } else { mag = static_cast(v); } *this = from_u128(mag); if (neg) *this = -*this; } }; /// @brief Reduce a `zn64` share into `zn64` when `Factor | Mod`. /// \complexity One `raw()` read and a `zn64` construction (one remainder). `Θ(1)`. /// The `static_assert` requires `Factor` divides `Mod` (and, for `zn128`, that the modulus is a 64-bit multiple of `Factor`). /// @see grotto::ring_switch_eval /// @see grotto::zn64 /// @param share a residue already reduced modulo `Mod` /// @return the same integer modulo `Factor` template HEDLEY_ALWAYS_INLINE constexpr zn64 ring_switch_factor(zn64 share) noexcept { static_assert(Mod % Factor == 0, "ring_switch_factor: Factor must divide Mod"); return zn64{share.raw()}; } /// @brief Reduce a `zn128` share into a 64-bit factor when the factor divides. /// \complexity One `raw()` read and a `zn64` construction (one remainder). `Θ(1)`. /// The `static_assert` requires `Factor` divides `Mod` (and, for `zn128`, that the modulus is a 64-bit multiple of `Factor`). /// @see grotto::ring_switch_eval /// @see grotto::zn64 /// @param share a residue already reduced modulo `Mod` /// @return the same integer modulo `Factor` template HEDLEY_ALWAYS_INLINE constexpr zn64 ring_switch_factor(zn128 share) noexcept { static_assert(Hi == 0 && Lo % Factor == 0, "ring_switch_factor: Factor must divide the 128-bit modulus"); return zn64{share.lo()}; } } // namespace grotto #endif // LIBDPF_INCLUDE_GROTTO_RESIDUE_HPP__