libdpf/include/dpf/field128.hpp
Ryan Henry 0d22946a0e Checkpoint the party/runtime stack before share-program and malicious-mode work.
Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-28 05:59:19 -06:00

492 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// @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 <cstddef>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <ostream>
#include <type_traits>
#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 <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
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<unsigned __int128>(a.lo_) + b.lo_;
const unsigned __int128 high =
static_cast<unsigned __int128>(a.hi_) + b.hi_ + (low >> 64);
const std::uint64_t words[4] = {
static_cast<std::uint64_t>(low),
static_cast<std::uint64_t>(high),
static_cast<std::uint64_t>(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<std::uint64_t>(al - bl);
oh = static_cast<std::uint64_t>(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<unsigned __int128>(lo) + 0xffffffffffffffffull;
lo = static_cast<std::uint64_t>(low);
const unsigned __int128 high =
static_cast<unsigned __int128>(hi) + 0x1bull + static_cast<std::uint64_t>(low >> 64);
hi = static_cast<std::uint64_t>(high);
top = static_cast<std::uint64_t>(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<unsigned __int128>(a0) * b0;
const unsigned __int128 p01 = static_cast<unsigned __int128>(a0) * b1;
const unsigned __int128 p10 = static_cast<unsigned __int128>(a1) * b0;
const unsigned __int128 p11 = static_cast<unsigned __int128>(a1) * b1;
out[0] = static_cast<std::uint64_t>(p00);
const unsigned __int128 mid = (p00 >> 64)
+ static_cast<std::uint64_t>(p01) + static_cast<std::uint64_t>(p10);
out[1] = static_cast<std::uint64_t>(mid);
const unsigned __int128 high = (mid >> 64) + (p01 >> 64) + (p10 >> 64)
+ static_cast<std::uint64_t>(p11);
out[2] = static_cast<std::uint64_t>(high);
out[3] = static_cast<std::uint64_t>((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<unsigned>(bit >> 6);
const unsigned off = static_cast<unsigned>(bit & 63);
if ((z[limb] >> off) & 1u)
{
const unsigned __int128 low =
static_cast<unsigned __int128>(lo) + 1;
lo = static_cast<std::uint64_t>(low);
const unsigned __int128 high =
static_cast<unsigned __int128>(hi) + static_cast<std::uint64_t>(low >> 64);
hi = static_cast<std::uint64_t>(high);
top += static_cast<std::uint64_t>(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 <typename T>
HEDLEY_ALWAYS_INLINE
constexpr void assign_integer(T v) noexcept
{
bool neg = false;
unsigned __int128 mag = 0;
if constexpr (std::is_signed_v<T>)
{
if (v < 0)
{
neg = true;
using U = std::make_unsigned_t<T>;
mag = static_cast<U>(0) - static_cast<U>(v);
}
else
{
mag = static_cast<std::make_unsigned_t<T>>(v);
}
}
else
{
mag = static_cast<unsigned __int128>(v);
}
const std::uint64_t words[4] = {
static_cast<std::uint64_t>(mag),
static_cast<std::uint64_t>(mag >> 64),
0, 0};
*this = reduce_words(words);
if (neg)
*this = -*this;
}
};
namespace utils
{
template <>
struct bitlength_of<field128>
: std::integral_constant<std::size_t, 128>
{ };
template <>
struct has_characteristic_two<field128> : 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 <std::size_t Lanes>
HEDLEY_ALWAYS_INLINE
void field128_lanes(const void *a, const void *b, void *out,
field128 (*op)(field128, field128)) noexcept
{
auto *aa = static_cast<const unsigned char *>(a);
auto *bb = static_cast<const unsigned char *>(b);
auto *cc = static_cast<unsigned char *>(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 <std::size_t Lanes>
HEDLEY_ALWAYS_INLINE
void field128_scale(const void *a, field128 b, void *out) noexcept
{
auto *aa = static_cast<const unsigned char *>(a);
auto *cc = static_cast<unsigned char *>(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<field128, simde__m128i>
{
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<field128, simde__m128i>
{
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<field128, simde__m128i>
{
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<field128, simde__m256i>
{
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<field128, simde__m256i>
{
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<field128, simde__m256i>
{
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<field128>() noexcept
{
for (;;)
{
const auto lo = uniform_sample<std::uint64_t>();
const auto hi = uniform_sample<std::uint64_t>();
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__