/// @file dpf/field128.hpp /// @brief Prime field of libprio's `Field128`, as a DPF output. /// @details The modulus is 340282366920938462946865773367900766209. Pass /// `dpf::field128{n}` as a `make_dpf` payload. Leaf addition and /// scaling are the field operations. A raw PRG block is reduced into /// the field on the first leaf operation. This is a point-function /// output, not a comparison payload. /// @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_FIELD128_HPP__ #define LIBDPF_INCLUDE_DPF_FIELD128_HPP__ #include #include #include #include #include #include #include "hedley/hedley.h" #include "simde/simde/x86/avx2.h" #include "dpf/leaf_arithmetic.hpp" #include "dpf/random.hpp" #include "dpf/utils.hpp" namespace dpf { /// @brief Element of the 128-bit libprio field. class field128 { public: /// @brief Low limb of the modulus `2^128 - 0x1bffffffffffffffff`. static constexpr std::uint64_t mod_lo = 0x0000000000000001ull; /// @brief High limb of the modulus. static constexpr std::uint64_t mod_hi = 0xffffffffffffffe4ull; static constexpr bool dpf_point_group = true; /// @brief The zero element. HEDLEY_ALWAYS_INLINE constexpr field128() noexcept = default; /// @brief Reduce `v` into the field. A negative value is negated in the field. /// @tparam T integral type, at most 128 bits /// @param v the integer to reduce template >> HEDLEY_ALWAYS_INLINE constexpr field128(T v) noexcept { assign_integer(v); } /// @brief Reduce a PRG block into the field. Uses up to 16 bytes. /// @param bytes the PRG output /// @param n the number of bytes available /// @return the field element HEDLEY_ALWAYS_INLINE static field128 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 from_lane(w[0], w[1]); } /// @brief Reduce an unreduced 128-bit lane `(hi << 64) + lo`. /// @param lo the low limb, not necessarily reduced /// @param hi the high limb, not necessarily reduced /// @return the field element HEDLEY_ALWAYS_INLINE static constexpr field128 from_lane(std::uint64_t lo, std::uint64_t hi) noexcept { const std::uint64_t words[4] = {lo, hi, 0, 0}; return reduce_words(words); } /// @brief Canonical representative in `[0, mod)`. HEDLEY_ALWAYS_INLINE static constexpr field128 canonicalize(field128 a) noexcept { return from_lane(a.lo_, a.hi_); } /// @brief Low 64 bits of the reduced representative. /// @return the low limb HEDLEY_ALWAYS_INLINE HEDLEY_PURE constexpr std::uint64_t lo() const noexcept { return lo_; } /// @brief High 64 bits of the reduced representative. /// @return the high limb HEDLEY_ALWAYS_INLINE HEDLEY_PURE constexpr std::uint64_t hi() const noexcept { return hi_; } /// @brief Field addition. /// @param a left addend /// @param b right addend /// @return `a + b` in the field HEDLEY_ALWAYS_INLINE friend constexpr field128 operator+(field128 a, field128 b) noexcept { const unsigned __int128 low = static_cast(a.lo_) + b.lo_; const unsigned __int128 high = static_cast(a.hi_) + b.hi_ + (low >> 64); const std::uint64_t words[4] = { static_cast(low), static_cast(high), static_cast(high >> 64), 0}; return reduce_words(words); } /// @brief Field subtraction. /// @param a minuend /// @param b subtrahend /// @return `a - b` in the field HEDLEY_ALWAYS_INLINE friend constexpr field128 operator-(field128 a, field128 b) noexcept { a = canonicalize(a); b = canonicalize(b); if (!less(a.hi_, a.lo_, b.hi_, b.lo_)) { std::uint64_t lo = 0, hi = 0; sub_words(a.lo_, a.hi_, b.lo_, b.hi_, lo, hi); return from_reduced(lo, hi); } std::uint64_t lo = 0, hi = 0; sub_words(mod_lo, mod_hi, b.lo_, b.hi_, lo, hi); return from_reduced(lo, hi) + a; } /// @brief Field negation. /// @param a the element to negate /// @return `-a`, with `-0 = 0` HEDLEY_ALWAYS_INLINE friend constexpr field128 operator-(field128 a) noexcept { a = canonicalize(a); if (a.lo_ == 0 && a.hi_ == 0) return a; std::uint64_t lo = 0, hi = 0; sub_words(mod_lo, mod_hi, a.lo_, a.hi_, lo, hi); return from_reduced(lo, hi); } /// @brief Field multiplication. /// @param a left factor /// @param b right factor /// @return `a * b` in the field HEDLEY_ALWAYS_INLINE friend constexpr field128 operator*(field128 a, field128 b) noexcept { std::uint64_t words[4]{}; mul128(a.lo_, a.hi_, b.lo_, b.hi_, words); return reduce_words(words); } /// @brief Field equality. /// @param a left element /// @param b right element /// @return `true` when the reduced values match HEDLEY_ALWAYS_INLINE friend constexpr bool operator==(field128 a, field128 b) noexcept { a = canonicalize(a); b = canonicalize(b); return a.lo_ == b.lo_ && a.hi_ == b.hi_; } /// @brief Field inequality. /// @param a left element /// @param b right element /// @return `true` when the reduced values differ HEDLEY_ALWAYS_INLINE friend constexpr bool operator!=(field128 a, field128 b) noexcept { return !(a == b); } /// @brief Write the reduced representative in hexadecimal. /// @param os the output stream /// @param a the element to write /// @return `os` friend std::ostream & operator<<(std::ostream & os, field128 a) { const auto flags = os.flags(); os << "0x" << std::hex << a.hi_ << std::setfill('0') << std::setw(16) << a.lo_; os.flags(flags); return os; } private: std::uint64_t lo_{}; std::uint64_t hi_{}; HEDLEY_ALWAYS_INLINE static constexpr field128 from_reduced(std::uint64_t lo, std::uint64_t hi) noexcept { field128 out; out.lo_ = lo; out.hi_ = hi; return out; } HEDLEY_ALWAYS_INLINE static constexpr bool less(std::uint64_t hi, std::uint64_t lo, std::uint64_t ohi, std::uint64_t olo) noexcept { return hi < ohi || (hi == ohi && lo < olo); } HEDLEY_ALWAYS_INLINE static constexpr bool ge_mod(std::uint64_t hi, std::uint64_t lo) noexcept { return hi > mod_hi || (hi == mod_hi && lo >= mod_lo); } 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 void add_c(std::uint64_t &lo, std::uint64_t &hi, std::uint64_t &top) noexcept { const unsigned __int128 low = static_cast(lo) + 0xffffffffffffffffull; lo = static_cast(low); const unsigned __int128 high = static_cast(hi) + 0x1bull + static_cast(low >> 64); hi = static_cast(high); top = static_cast(high >> 64); } /// @brief 128×128 → 256 multiply, little-endian limbs. HEDLEY_ALWAYS_INLINE static constexpr void mul128(std::uint64_t a0, std::uint64_t a1, std::uint64_t b0, std::uint64_t b1, std::uint64_t out[4]) noexcept { const unsigned __int128 p00 = static_cast(a0) * b0; const unsigned __int128 p01 = static_cast(a0) * b1; const unsigned __int128 p10 = static_cast(a1) * b0; const unsigned __int128 p11 = static_cast(a1) * b1; out[0] = static_cast(p00); const unsigned __int128 mid = (p00 >> 64) + static_cast(p01) + static_cast(p10); out[1] = static_cast(mid); const unsigned __int128 high = (mid >> 64) + (p01 >> 64) + (p10 >> 64) + static_cast(p11); out[2] = static_cast(high); out[3] = static_cast((high >> 64) + (p11 >> 64)); } /// @brief Reduce a little-endian 256-bit integer. `2^128 ≡ C`. HEDLEY_ALWAYS_INLINE static constexpr field128 reduce_words(const std::uint64_t z[4]) noexcept { std::uint64_t lo = 0, hi = 0, top = 0; for (int bit = 255; bit >= 0; --bit) { top = hi >> 63; hi = (hi << 1) | (lo >> 63); lo <<= 1; const unsigned limb = static_cast(bit >> 6); const unsigned off = static_cast(bit & 63); if ((z[limb] >> off) & 1u) { const unsigned __int128 low = static_cast(lo) + 1; lo = static_cast(low); const unsigned __int128 high = static_cast(hi) + static_cast(low >> 64); hi = static_cast(high); top += static_cast(high >> 64); } while (top) add_c(lo, hi, top); if (ge_mod(hi, lo)) sub_words(lo, hi, mod_lo, mod_hi, lo, hi); } return from_reduced(lo, hi); } 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); } const std::uint64_t words[4] = { static_cast(mag), static_cast(mag >> 64), 0, 0}; *this = reduce_words(words); if (neg) *this = -*this; } }; namespace utils { template <> struct bitlength_of : std::integral_constant { }; template <> struct has_characteristic_two : std::false_type { }; } // namespace utils namespace leaf_arithmetic { namespace detail { HEDLEY_ALWAYS_INLINE field128 load_field128(const void *lane) noexcept { std::uint64_t w[2]{}; std::memcpy(w, lane, sizeof(w)); return field128::from_lane(w[0], w[1]); } HEDLEY_ALWAYS_INLINE void store_field128(void *lane, field128 v) noexcept { const std::uint64_t w[2] = {v.lo(), v.hi()}; std::memcpy(lane, w, sizeof(w)); } template HEDLEY_ALWAYS_INLINE void field128_lanes(const void *a, const void *b, void *out, field128 (*op)(field128, field128)) noexcept { auto *aa = static_cast(a); auto *bb = static_cast(b); auto *cc = static_cast(out); for (std::size_t i = 0; i < Lanes; ++i) { const field128 y = op(load_field128(aa + i * 16), load_field128(bb + i * 16)); store_field128(cc + i * 16, y); } } template HEDLEY_ALWAYS_INLINE void field128_scale(const void *a, field128 b, void *out) noexcept { auto *aa = static_cast(a); auto *cc = static_cast(out); for (std::size_t i = 0; i < Lanes; ++i) store_field128(cc + i * 16, load_field128(aa + i * 16) * b); } } // namespace detail HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") template <> struct add_t { auto operator()(const simde__m128i &a, const simde__m128i &b) const { simde__m128i out; detail::field128_lanes<1>(&a, &b, &out, [](field128 x, field128 y) { return x + y; }); return out; } }; template <> struct subtract_t { auto operator()(const simde__m128i &a, const simde__m128i &b) const { simde__m128i out; detail::field128_lanes<1>(&a, &b, &out, [](field128 x, field128 y) { return x - y; }); return out; } }; template <> struct multiply_t { auto operator()(const simde__m128i &a, field128 b) const { simde__m128i out; detail::field128_scale<1>(&a, b, &out); return out; } }; template <> struct add_t { auto operator()(const simde__m256i &a, const simde__m256i &b) const { simde__m256i out; detail::field128_lanes<2>(&a, &b, &out, [](field128 x, field128 y) { return x + y; }); return out; } }; template <> struct subtract_t { auto operator()(const simde__m256i &a, const simde__m256i &b) const { simde__m256i out; detail::field128_lanes<2>(&a, &b, &out, [](field128 x, field128 y) { return x - y; }); return out; } }; template <> struct multiply_t { auto operator()(const simde__m256i &a, field128 b) const { simde__m256i out; detail::field128_scale<2>(&a, b, &out); return out; } }; HEDLEY_PRAGMA(GCC diagnostic pop) } // namespace leaf_arithmetic /// @brief Sample a uniform field element by rejection. /// @return an element of the field template <> HEDLEY_NO_THROW inline auto uniform_sample() noexcept { for (;;) { const auto lo = uniform_sample(); const auto hi = uniform_sample(); if (hi < field128::mod_hi || (hi == field128::mod_hi && lo < field128::mod_lo)) return field128::from_lane(lo, hi); } } } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_FIELD128_HPP__