18template <
class Fq,
class Fr,
class T>
25template <
class Fq,
class Fr,
class T>
32template <
class Fq,
class Fr,
class T>
39template <
class Fq,
class Fr,
class T>
46template <
class Fq,
class Fr,
class T>
58template <
class Fq,
class Fr,
class T>
73 if (is_point_at_infinity()) {
77 result.self_set_infinity();
81 Fq zz_inv = z_inv.
sqr();
82 Fq zzz_inv = zz_inv * z_inv;
83 affine_element<Fq, Fr, T>
result(x * zz_inv, y * zzz_inv);
87template <
class Fq,
class Fr,
class T>
90 if (is_point_at_infinity()) {
94 result.self_set_infinity();
98 Fq zz_inv = z_inv.
sqr();
99 Fq zzz_inv = zz_inv * z_inv;
106 if constexpr (
Fq::modulus.
data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
107 if (is_point_at_infinity()) {
111 if (x.is_msb_set_word()) {
143 if constexpr (T::has_a) {
144 T3 += (T::a * z.sqr().sqr());
180template <
class Fq,
class Fr,
class T>
183 if constexpr (
Fq::modulus.
data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
185 if (other.is_point_at_infinity()) {
188 if (is_point_at_infinity()) {
189 *
this = { other.
x, other.y,
Fq::one() };
193 const bool edge_case_trigger = x.
is_msb_set() || other.x.is_msb_set();
194 if (edge_case_trigger) {
195 if (x.is_msb_set()) {
196 *
this = { other.x, other.y,
Fq::one() };
206 Fq T1 = other.x * T0;
215 if (__builtin_expect(T1.
is_zero(), 0)) {
273template <
class Fq,
class Fr,
class T>
280template <
class Fq,
class Fr,
class T>
283 const affine_element<Fq, Fr, T> to_add{ other.
x, -other.y };
284 return operator+=(to_add);
287template <
class Fq,
class Fr,
class T>
294template <
class Fq,
class Fr,
class T>
297 if constexpr (
Fq::modulus.
data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
298 bool p1_zero = is_point_at_infinity();
299 bool p2_zero = other.is_point_at_infinity();
300 if (__builtin_expect((p1_zero || p2_zero), 0)) {
301 if (p1_zero && !p2_zero) {
305 if (p2_zero && !p1_zero) {
312 bool p1_zero = x.is_msb_set();
313 bool p2_zero = other.x.is_msb_set();
314 if (__builtin_expect((p1_zero || p2_zero), 0)) {
315 if (p1_zero && !p2_zero) {
319 if (p2_zero && !p1_zero) {
327 Fq Z2Z2(other.z.sqr());
329 Fq U2(Z1Z1 * other.x);
332 Fq S1(Z2Z2 * other.z);
339 if (__builtin_expect(H.is_zero(), 0)) {
383template <
class Fq,
class Fr,
class T>
390template <
class Fq,
class Fr,
class T>
393 const element to_add{ other.
x, -other.y, other.z };
394 return operator+=(to_add);
397template <
class Fq,
class Fr,
class T>
409template <
class Fq,
class Fr,
class T>
412 if constexpr (T::USE_ENDOMORPHISM) {
413 return mul_with_endomorphism(exponent);
415 return mul_without_endomorphism(exponent);
424template <
class Fq,
class Fr,
class T>
444 const uint64_t r =
engine->get_random_uint64() | (UINT64_C(1) << 63);
456 auto cs_fq = [](
Fq&
a,
Fq&
b, uint64_t mask) {
457 constexpr size_t NUM_LIMBS =
sizeof(
Fq) /
sizeof(uint64_t);
458 for (
size_t i = 0; i < NUM_LIMBS; ++i) {
465 cs_fq(
a.x,
b.x, mask);
466 cs_fq(
a.y,
b.y, mask);
467 cs_fq(
a.z,
b.z, mask);
476 for (
size_t i = NUM_BITS; i-- > 0;) {
477 const uint64_t mask = 0ULL -
static_cast<uint64_t
>(k_blinded.
get_bit(i));
494template <
class Fq,
class Fr,
class T>
497 return element(to_affine_const_time());
510 result.self_set_infinity();
516 if constexpr (
Fq::modulus.
data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
536 if constexpr (
Fq::modulus.
data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
541 return (x.is_msb_set());
547 if (is_point_at_infinity()) {
556 Fq bz_6 = zzzz * zz * T::b;
557 if constexpr (T::has_a) {
558 bz_6 += (x * T::a) * zzzz;
560 Fq xxx = x.
sqr() * x + bz_6;
565template <
class Fq,
class Fr,
class T>
569 if ((!on_curve()) || (!other.on_curve())) {
572 bool am_infinity = is_point_at_infinity();
573 bool is_infinity = other.is_point_at_infinity();
574 bool both_infinity = am_infinity && is_infinity;
576 if ((!both_infinity) && (am_infinity || is_infinity)) {
579 const Fq lhs_zz = z.sqr();
580 const Fq lhs_zzz = lhs_zz * z;
581 const Fq rhs_zz = other.z.sqr();
582 const Fq rhs_zzz = rhs_zz * other.z;
584 const Fq lhs_x = x * rhs_zz;
585 const Fq lhs_y = y * rhs_zzz;
587 const Fq rhs_x = other.x * lhs_zz;
588 const Fq rhs_y = other.y * lhs_zzz;
589 return both_infinity || ((lhs_x == rhs_x) && (lhs_y == rhs_y));
592template <
class Fq,
class Fr,
class T>
595 if constexpr (T::can_hash_to_curve) {
609template <
class Fq,
class Fr,
class T>
612 const uint256_t converted_scalar(scalar);
614 if (converted_scalar == 0) {
619 const uint64_t maximum_set_bit = converted_scalar.
get_msb();
623 for (uint64_t i = maximum_set_bit - 1; i < maximum_set_bit; --i) {
625 if (converted_scalar.
get_bit(i)) {
692template <
class Fq,
class Fr,
class T>
695 if (is_point_at_infinity()) {
699 if (converted_scalar.
is_zero()) {
707 lookup_table[0] =
element(*
this);
708 for (
size_t i = 1; i < LOOKUP_SIZE; ++i) {
709 lookup_table[i] = lookup_table[i - 1] + *
this;
716 const uint64_t* k1 = endo_scalars.first.data();
717 const uint64_t* k2 = endo_scalars.second.data();
729 for (
size_t h = 0; h < 2; ++h) {
730 const uint64_t* s = (h == 0) ? k1 : k2;
732 const uint32_t magnitude = digit & 0x7FFFFFFFU;
733 if (magnitude == 0) {
736 const bool sign = (digit >> 31) != 0;
737 element to_add = lookup_table[magnitude - 1];
738 to_add.
y.self_conditional_negate(sign ^ (h == 1));
753template <
class Fq,
class Fr,
class T>
758 const size_t n = std::min(points.size(), scalars.size());
763 if constexpr (T::USE_ENDOMORPHISM) {
774 struct ActiveScalar {
782 for (
size_t i = 0; i < n; ++i) {
783 if (points[i].is_point_at_infinity()) {
793 for (
size_t k = 1; k < LOOKUP_SIZE; ++k) {
794 e.lookup[k] = e.lookup[k - 1] + pt;
801 if (active.empty()) {
809 for (
size_t w = NUM_WINDOWS; w-- > 0;) {
810 for (
size_t h = 0; h < 2; ++h) {
811 for (
auto&
a : active) {
812 const uint64_t* s = (h == 0) ?
a.k1.
data() :
a.k2.
data();
813 const uint32_t digit = detail::booth_packed_digit(s, slice_params[w], WINDOW_BITS);
814 const uint32_t magnitude = digit & 0x7FFFFFFFU;
815 if (magnitude == 0) {
818 const bool sign = (digit >> 31) != 0;
819 element to_add =
a.lookup[magnitude - 1];
820 to_add.
y.self_conditional_negate(sign ^ (h == 1));
828 for (
size_t d = 0; d < WINDOW_BITS; ++d) {
838 active_points.reserve(n);
839 active_scalars.reserve(n);
840 uint64_t max_set_bit = 0;
841 for (
size_t i = 0; i < n; ++i) {
842 if (points[i].is_point_at_infinity()) {
850 active_points.push_back(points[i]);
851 active_scalars.push_back(s);
853 if (active_points.empty()) {
858 for (uint64_t bit = max_set_bit + 1; bit-- > 0;) {
860 for (
size_t i = 0; i < active_points.size(); ++i) {
861 if (active_scalars[i].get_bit(bit)) {
884template <
typename AffineElement,
typename Fq>
888 Fq* scratch_space)
noexcept
894 scratch_space[i] =
lhs[i].x +
rhs[i].x;
902 throw_or_abort(
"attempted to invert zero in batch_affine_add_impl");
911 rhs[i].x =
rhs[i].y.sqr();
912 rhs[i].x -= scratch_space[i];
917 rhs[i].y = temp -
lhs[i].y;
930template <
typename AffineElement,
typename Fq>
931__attribute__((always_inline))
inline void batch_affine_add_interleaved(AffineElement* points,
933 Fq* scratch_space)
noexcept
939 scratch_space[i >> 1] = points[i].x + points[i + 1].x;
940 points[i + 1].x -= points[i].x;
941 points[i + 1].y -= points[i].y;
947 throw_or_abort(
"attempted to invert zero in batch_affine_add_interleaved");
956 points[i + 1].x = points[i + 1].y.sqr();
958 points[(i +
num_points) >> 1].x = points[i + 1].x - scratch_space[i >> 1];
961 __builtin_prefetch(points + i - 2);
962 __builtin_prefetch(points + i - 1);
963 __builtin_prefetch(points + ((i +
num_points - 2) >> 1));
964 __builtin_prefetch(scratch_space + ((i - 2) >> 1));
968 points[i].x -= points[(i +
num_points) >> 1].x;
969 points[i].x *= points[i + 1].y;
970 points[(i +
num_points) >> 1].y = points[i].x - points[i].y;
988template <
typename AffineElement,
typename Fq,
typename T>
989__attribute__((always_inline))
inline void batch_affine_double_impl(AffineElement* points,
991 Fq* scratch_space)
noexcept
997 scratch_space[i] = points[i].x.sqr();
998 if constexpr (T::has_a) {
999 scratch_space[i] += T::a;
1001 scratch_space[i] = scratch_space[i] + scratch_space[i] + scratch_space[i];
1007 throw_or_abort(
"attempted to invert zero in batch_affine_double_impl");
1014 size_t i = i_plus_1 - 1;
1020 points[i].x = scratch_space[i].
sqr() - (points[i].x + points[i].x);
1021 points[i].y = scratch_space[i] * (
temp_x - points[i].x) - points[i].y;
1053template <
typename AffineElement,
typename Fq>
1054__attribute__((always_inline))
inline void batch_affine_combined_double_add_impl(
const AffineElement* to_add,
1059 Fq* scratch_c)
noexcept
1063 for (
size_t i = 0; i <
num_pairs; ++i) {
1074 throw_or_abort(
"attempted to invert zero in batch_affine_combined_double_add_impl phase 1");
1082 Fq x3 = scratch_c[k].
sqr();
1090 for (
size_t i = 0; i <
num_pairs; ++i) {
1097 throw_or_abort(
"attempted to invert zero in batch_affine_combined_double_add_impl phase 2");
1105 Fq lambda2 = -scratch_c[k];
1108 Fq x4 = lambda2.
sqr();
1145template <
typename AffineElement,
typename Fq>
1146__attribute__((always_inline))
inline void batch_affine_add_indexed_impl(AffineElement* buckets,
1149 Fq* scratch_space)
noexcept
1157 constexpr size_t PREFETCH_AHEAD = 4;
1163 for (
size_t i = 0; i <
num_pairs; ++i) {
1165 __builtin_prefetch(buckets +
pairs[i + PREFETCH_AHEAD].first, 1, 3);
1166 __builtin_prefetch(buckets +
pairs[i + PREFETCH_AHEAD].second, 0, 3);
1168 AffineElement& dst = buckets[
pairs[i].first];
1169 const AffineElement& src = buckets[
pairs[i].second];
1170 scratch_space[i] = src.x + dst.x;
1178 throw_or_abort(
"attempted to invert zero in batch_affine_add_indexed_impl");
1183 for (
size_t j =
num_pairs; j > 0; --j) {
1184 const size_t i = j - 1;
1185 if (i >= PREFETCH_AHEAD) {
1186 __builtin_prefetch(buckets +
pairs[i - PREFETCH_AHEAD].first, 1, 3);
1187 __builtin_prefetch(buckets +
pairs[i - PREFETCH_AHEAD].second, 0, 3);
1188 __builtin_prefetch(scratch_space + (i - PREFETCH_AHEAD), 0, 3);
1190 AffineElement& dst = buckets[
pairs[i].first];
1191 const AffineElement& src = buckets[
pairs[i].second];
1196 dst.x = dst.y.sqr();
1197 dst.x -= scratch_space[i];
1200 Fq temp = src.x - dst.x;
1202 dst.y = temp - src.y;
1222template <
typename AffineElement,
typename Fq>
1223__attribute__((always_inline))
inline void batch_affine_double_indexed_impl(AffineElement* buckets,
1226 Fq* scratch_space)
noexcept
1232 constexpr size_t PREFETCH_AHEAD = 4;
1239 __builtin_prefetch(buckets +
indices[i + PREFETCH_AHEAD], 1, 3);
1241 AffineElement& p = buckets[
indices[i]];
1242 scratch_space[i] = p.x.sqr();
1243 scratch_space[i] = scratch_space[i] + scratch_space[i] + scratch_space[i];
1249 throw_or_abort(
"attempted to invert zero in batch_affine_double_indexed_impl");
1256 const size_t i = j - 1;
1257 if (i >= PREFETCH_AHEAD) {
1258 __builtin_prefetch(buckets +
indices[i - PREFETCH_AHEAD], 1, 3);
1259 __builtin_prefetch(scratch_space + (i - PREFETCH_AHEAD), 0, 3);
1261 AffineElement& p = buckets[
indices[i]];
1267 p.x = scratch_space[i].
sqr() - (p.x + p.x);
1268 p.y = scratch_space[i] * (
temp_x - p.x) - p.y;
1283template <
class Fq,
class Fr,
class T>
1289 const size_t num_points = first_group.size();
1301 [&](
size_t start,
size_t end,
BB_UNUSED size_t chunk_index) {
1302 batch_affine_add_impl<affine_element, Fq>(
1303 &second_group[start], &results[start], end - start, &scratch_space[start]);
1318template <
class Fq,
class Fr,
class T>
1347 if (converted_scalar.
is_zero()) {
1349 result.self_set_infinity();
1380 const bool is_short_scalar =
1381 ((converted_scalar.
data[2] | converted_scalar.
data[3]) == 0) && ((converted_scalar.
data[1] >> 63) == 0);
1385 const uint64_t* k1 = endo_scalars.first.data();
1386 const uint64_t* k2 = endo_scalars.second.data();
1387 BB_ASSERT((k2[1] >> 63) == 0,
"GLV K2 split must fit below 2^127 for the offset Booth window schedule");
1391 for (
size_t w = 0; w < NUM_WINDOWS; ++w) {
1392 k1_digits[w] = detail::booth_packed_digit(k1, slice_params[w], WINDOW_BITS);
1394 k2_digits[0] = detail::booth_packed_digit(k2, k2_slice_params[0], K2_LOW_WINDOW_BITS);
1395 for (
size_t w = 1; w < K2_NUM_WINDOWS; ++w) {
1396 k2_digits[w] = detail::booth_packed_digit(k2, k2_slice_params[w], WINDOW_BITS);
1413 auto compute_safe_mask = [&]() -> uint64_t {
1417 bool initialised =
false;
1419 const auto signed_digit = [](uint32_t packed) -> int64_t {
1420 const int64_t mag =
static_cast<int64_t
>(packed & 0x7FFFFFFFU);
1421 return ((packed >> 31) != 0) ? -mag : mag;
1429 const auto edge_for_combined = [&](int64_t d,
bool is_k1) ->
bool {
1434 if ((d % 2 == 0) && (2 *
a == d || 2 *
a == -d)) {
1437 return (d % 4 == 0) && (4 *
a == -d);
1442 if ((d % 2 == 0) && (2 *
b == d || 2 *
b == -d)) {
1445 return (d % 4 == 0) && (4 *
b == -d);
1451 const auto edge_for_add = [&](int64_t d,
bool is_k1) ->
bool {
1453 return (
b == 0) && (
a == d ||
a == -d);
1455 return (
a == 0) && (
b == d ||
b == -d);
1460 const uint32_t d126 = k2_digits[K2_NUM_WINDOWS - 1];
1461 if ((d126 & 0x7FFFFFFFU) != 0) {
1462 b = signed_digit(d126);
1468 for (
size_t step = 0; step < 62; ++step) {
1475 const size_t pos = 124 - 2 * step;
1476 const bool is_k1 = (pos % 4 == 0);
1477 const uint32_t digit = is_k1 ? k1_digits[pos / 4] : k2_digits[(pos + 2) / 4];
1478 const uint32_t m = digit & 0x7FFFFFFFU;
1479 const int64_t d = signed_digit(digit);
1497 if (edge_for_combined(d, is_k1)) {
1498 mask |= (uint64_t{ 1 } << step);
1512 const uint32_t d0 = k1_digits[0];
1513 const uint32_t d1 = k2_digits[0];
1514 const uint32_t m0 = d0 & 0x7FFFFFFFU;
1515 const uint32_t m1 = d1 & 0x7FFFFFFFU;
1516 const int64_t s0 = signed_digit(d0);
1517 const int64_t s1 = signed_digit(d1);
1526 }
else if (m1 != 0) {
1530 }
else if (m0 == 0 && m1 == 0) {
1534 const bool fuse_with_h1 = (m0 == 0);
1535 const int64_t fused_d = fuse_with_h1 ? s1 : s0;
1536 if (edge_for_combined(fused_d, !fuse_with_h1)) {
1537 mask |= (uint64_t{ 1 } << 62);
1546 if (m0 != 0 && m1 != 0) {
1549 if (edge_for_add(s1,
false)) {
1550 mask |= (uint64_t{ 1 } << 63);
1559 const uint64_t safe_mask = compute_safe_mask();
1563 for (
auto& table : lookup_table) {
1568 auto execute_range = [&](
size_t start,
size_t end) {
1571 batch_affine_add_impl<affine_element, Fq>(&
lhs[start], &
rhs[start], end - start, &
scratch_a[start]);
1574 for (
size_t i = start; i < end; ++i) {
1581 batch_affine_double_impl<affine_element, Fq, T>(&
lhs[start], end - start, &
scratch_a[start]);
1585 batch_affine_combined_double_add_impl<affine_element, Fq>(
1586 &to_add[start], &accum[start], end - start, &
scratch_a[start], &
scratch_b[start], &scratch_c[start]);
1589 for (
size_t i = start; i < end; ++i) {
1599 for (
size_t i = start; i < end; ++i) {
1600 if (points[i].is_point_at_infinity()) {
1604 lookup_table[0][i] = points[i];
1605 temp_point_vector[i] = points[i];
1610 for (
size_t i = start; i < end; ++i) {
1611 lookup_table[1][i] = lookup_table[0][i];
1613 double_chunked(&lookup_table[1][0]);
1615 for (
size_t j = 2; j < LOOKUP_SIZE; ++j) {
1616 for (
size_t i = start; i < end; ++i) {
1617 lookup_table[j][i] = lookup_table[j - 1][i];
1619 add_chunked(&temp_point_vector[0], &lookup_table[j][0]);
1627 auto fill_to_add = [&](uint32_t digit,
bool half_idx,
affine_element* dst) {
1628 const uint32_t magnitude = digit & 0x7FFFFFFFU;
1629 const bool sign = (digit >> 31) != 0;
1630 const bool flip_y = sign ^ half_idx;
1631 for (
size_t i = start; i < end; ++i) {
1633 pt.
y.self_conditional_negate(flip_y);
1649 bool initialised =
false;
1650 const auto update_initialised_from_work = [&]() { initialised = !work_elements[start].
is_point_at_infinity(); };
1651 auto seed_or_skip = [&](uint32_t digit,
bool half_idx) {
1653 if ((digit & 0x7FFFFFFFU) != 0) {
1654 fill_to_add(digit, half_idx, &work_elements[0]);
1660 seed_or_skip(k2_digits[K2_NUM_WINDOWS - 1],
true);
1663 for (
size_t step = 0; step < 62; ++step) {
1664 const size_t pos = 124 - 2 * step;
1665 const bool is_k1 = (pos % 4 == 0);
1666 const uint32_t digit = is_k1 ? k1_digits[pos / 4] : k2_digits[(pos + 2) / 4];
1667 const bool half_idx = !is_k1;
1668 const uint32_t m = digit & 0x7FFFFFFFU;
1672 fill_to_add(digit, half_idx, &work_elements[0]);
1680 double_chunked(&work_elements[0]);
1681 double_chunked(&work_elements[0]);
1686 double_chunked(&work_elements[0]);
1687 fill_to_add(digit, half_idx, &temp_point_vector[0]);
1688 if ((safe_mask >> step) & uint64_t{ 1 }) {
1689 combined_safe_chunked(&temp_point_vector[0], &work_elements[0]);
1690 update_initialised_from_work();
1692 combined_chunked(&temp_point_vector[0], &work_elements[0]);
1700 const uint32_t d0 = k1_digits[0];
1701 const uint32_t d1 = k2_digits[0];
1702 const uint32_t m0 = d0 & 0x7FFFFFFFU;
1703 const uint32_t m1 = d1 & 0x7FFFFFFFU;
1707 fill_to_add(d0,
false, &work_elements[0]);
1713 fill_to_add(d1,
true, &temp_point_vector[0]);
1714 add_chunked(&temp_point_vector[0], &work_elements[0]);
1716 }
else if (m1 != 0) {
1717 fill_to_add(d1,
true, &work_elements[0]);
1720 }
else if (m0 == 0 && m1 == 0) {
1721 double_chunked(&work_elements[0]);
1722 double_chunked(&work_elements[0]);
1724 double_chunked(&work_elements[0]);
1725 const bool fuse_with_h1 = (m0 == 0);
1726 const uint32_t fused_digit = fuse_with_h1 ? d1 : d0;
1727 fill_to_add(fused_digit, fuse_with_h1, &temp_point_vector[0]);
1728 if ((safe_mask >> 62) & uint64_t{ 1 }) {
1729 combined_safe_chunked(&temp_point_vector[0], &work_elements[0]);
1730 update_initialised_from_work();
1732 combined_chunked(&temp_point_vector[0], &work_elements[0]);
1734 if (m0 != 0 && m1 != 0) {
1736 fill_to_add(d1,
true, &work_elements[0]);
1739 fill_to_add(d1,
true, &temp_point_vector[0]);
1740 if ((safe_mask >> 63) & uint64_t{ 1 }) {
1741 add_safe_chunked(&temp_point_vector[0], &work_elements[0]);
1742 update_initialised_from_work();
1744 add_chunked(&temp_point_vector[0], &work_elements[0]);
1751 BB_ASSERT(initialised,
"non-zero scalar must produce at least one non-zero Booth digit");
1754 for (
size_t i = start; i < end; ++i) {
1755 if (points[i].is_point_at_infinity()) {
1756 work_elements[i].self_set_infinity();
1762 return work_elements;
1765template <
class Fq,
class Fr,
class T>
1774 constexpr size_t NUM_TERMS = 4;
1775 constexpr size_t NUM_MULTIPLIED_BASES = 3;
1777 BB_ASSERT_EQ(points.size() % NUM_TERMS, 0U,
"batch_two_round_fold input must split into four equal quarters");
1778 const size_t t = points.size() / NUM_TERMS;
1788 constexpr size_t TOP_BIT = (WINDOW_BITS * NUM_WINDOWS) - 1;
1797 "batch_two_round_fold challenges must be below 2^127");
1799 "batch_two_round_fold challenges must be below 2^127");
1800 const Fr u12_conv = (u1 * u2).from_montgomery_form_reduced();
1802 BB_ASSERT_EQ((endo_scalars.first[1] >> 63), 0U,
"GLV K1 split must fit below 2^127 for the Booth grid");
1803 BB_ASSERT_EQ((endo_scalars.second[1] >> 63), 0U,
"GLV K2 split must fit below 2^127 for the offset Booth grid");
1810 "the offset-grid interleaving (term j at pos ≡ j mod 4) requires a 2-bit bottom window for term 2");
1811 constexpr auto grid0 =
1812 detail::make_booth_slice_params<NUM_WINDOWS, WINDOW_BITS, detail::BOOTH_ENDO_NUM_LIMBS_U64>();
1813 constexpr auto grid1 =
1814 detail::make_offset_booth_slice_params<OFFSET_NUM_WINDOWS, WINDOW_BITS, 1, detail::BOOTH_ENDO_NUM_LIMBS_U64>();
1815 constexpr auto grid2 = detail::make_offset_booth_slice_params<OFFSET_NUM_WINDOWS,
1819 constexpr auto grid3 =
1820 detail::make_offset_booth_slice_params<OFFSET_NUM_WINDOWS, WINDOW_BITS, 3, detail::BOOTH_ENDO_NUM_LIMBS_U64>();
1826 for (
size_t w = 0; w < NUM_WINDOWS; ++w) {
1827 digits0[w] = detail::booth_packed_digit(endo_scalars.first.data(), grid0[w], WINDOW_BITS);
1829 digits1[0] = detail::booth_packed_digit(u1_conv.
data, grid1[0], 1);
1832 digits3[0] = detail::booth_packed_digit(u2_conv.
data, grid3[0], 3);
1833 for (
size_t w = 1; w < OFFSET_NUM_WINDOWS; ++w) {
1834 digits1[w] = detail::booth_packed_digit(u1_conv.
data, grid1[w], WINDOW_BITS);
1835 digits2[w] = detail::booth_packed_digit(endo_scalars.second.data(), grid2[w], WINDOW_BITS);
1836 digits3[w] = detail::booth_packed_digit(u2_conv.
data, grid3[w], WINDOW_BITS);
1840 const auto digit_at = [&](
size_t term,
size_t window) -> uint32_t {
1843 return digits0[window];
1845 return digits1[window];
1847 return digits2[window];
1849 return digits3[window];
1857 ops.reserve(TOP_BIT + NUM_TERMS + 1);
1858 bool final_initialised =
false;
1863 bool initialised =
false;
1868 constexpr int64_t CAP = int64_t{ 1 } << 40;
1869 const auto clamp_coeff = [&]() {
1870 for (
auto& c : coeff) {
1878 const auto signed_delta = [](uint32_t digit,
size_t term) -> int64_t {
1879 const auto mag =
static_cast<int64_t
>(digit & 0x7FFFFFFFU);
1880 const bool neg = ((digit >> 31) != 0) ^ (term == 2);
1881 return neg ? -mag : mag;
1884 const auto others_zero = [&](
size_t j) {
1885 for (
size_t i = 0; i < NUM_TERMS; ++i) {
1886 if (i != j && coeff[i] != 0) {
1893 const auto all_zero = [&]() {
return coeff[0] == 0 && coeff[1] == 0 && coeff[2] == 0 && coeff[3] == 0; };
1897 const auto accumulate_digit = [&](
size_t term, uint32_t digit,
bool transition) {
1898 const int64_t delta = signed_delta(digit, term);
1899 const int64_t mag =
std::abs(delta);
1903 coeff[term] = delta;
1910 const bool safe = others_zero(term) && (
std::abs(coeff[term]) == mag || 2 * coeff[term] == -delta);
1912 for (
auto& c : coeff) {
1917 const bool safe = others_zero(term) &&
std::abs(coeff[term]) == mag;
1920 coeff[term] += delta;
1924 initialised =
false;
1931 for (
size_t pos = TOP_BIT; pos >= 1; --pos) {
1932 const size_t term = pos % NUM_TERMS;
1933 const size_t window = (term == 0) ? pos / WINDOW_BITS : (pos - term) / WINDOW_BITS + 1;
1934 const uint32_t digit = digit_at(term, window);
1935 if ((digit & 0x7FFFFFFFU) == 0) {
1940 for (
auto& c : coeff) {
1947 accumulate_digit(term, digit,
true);
1952 bool transition_pending = initialised;
1953 for (
size_t term = 0; term < NUM_TERMS; ++term) {
1954 const uint32_t digit = digit_at(term, 0);
1955 if ((digit & 0x7FFFFFFFU) == 0) {
1958 accumulate_digit(term, digit, transition_pending);
1959 transition_pending =
false;
1963 if (transition_pending) {
1966 final_initialised = initialised;
1972 if (!final_initialised) {
1977 return work_elements;
1983 Fq*
const scratch_c = &scratch_space[2 * t];
1988 for (
auto& base_table : lookup_tables) {
1989 for (
auto& table : base_table) {
1999 auto fold_point_range = [&](
size_t start,
size_t end) {
2001 batch_affine_add_impl<affine_element, Fq>(&
lhs[start], &
rhs[start], end - start, &
scratch_a[start]);
2004 batch_affine_double_impl<affine_element, Fq, T>(&pts[start], end - start, &
scratch_a[start]);
2007 batch_affine_combined_double_add_impl<affine_element, Fq>(
2008 &to_add[start], &accum[start], end - start, &
scratch_a[start], &
scratch_b[start], &scratch_c[start]);
2011 for (
size_t i = start; i < end; ++i) {
2018 for (
size_t i = start; i < end; ++i) {
2027 for (
size_t base = 0; base < NUM_MULTIPLIED_BASES; ++base) {
2029 auto& table = lookup_tables[base];
2030 for (
size_t i = start; i < end; ++i) {
2031 table[0][i] = base_points[i];
2032 table[1][i] = base_points[i];
2033 temp_point_vector[i] = base_points[i];
2035 double_chunked(&table[1][0]);
2036 for (
size_t j = 2; j < LOOKUP_SIZE; ++j) {
2037 for (
size_t i = start; i < end; ++i) {
2038 table[j][i] = table[j - 1][i];
2040 add_chunked(&temp_point_vector[0], &table[j][0]);
2045 const auto fill_to_add = [&](
size_t term, uint32_t digit,
affine_element* dst) {
2046 const uint32_t magnitude = digit & 0x7FFFFFFFU;
2047 const bool flip_y = ((digit >> 31) != 0) ^ (term == 2);
2051 const auto& table = lookup_tables[term_to_base[term]][magnitude - 1];
2052 for (
size_t i = start; i < end; ++i) {
2054 pt.
y.self_conditional_negate(flip_y);
2065 double_chunked(&work_elements[0]);
2068 fill_to_add(op.term, op.digit, &work_elements[0]);
2071 fill_to_add(op.term, op.digit, &temp_point_vector[0]);
2073 combined_safe_chunked(&temp_point_vector[0], &work_elements[0]);
2075 combined_chunked(&temp_point_vector[0], &work_elements[0]);
2079 fill_to_add(op.term, op.digit, &temp_point_vector[0]);
2081 add_safe_chunked(&temp_point_vector[0], &work_elements[0]);
2083 add_chunked(&temp_point_vector[0], &work_elements[0]);
2091 add_chunked(&points[3 * t], &work_elements[0]);
2095 return work_elements;
2098template <
typename Fq,
typename Fr,
typename T>
2102 temporaries.reserve(num_elements * 2);
2107 for (
size_t i = 0; i < num_elements; ++i) {
2109 if (!elements[i].is_point_at_infinity()) {
2139 for (
size_t i = num_elements - 1; i < num_elements; --i) {
2140 if (!elements[i].is_point_at_infinity()) {
2142 Fq zz_inv = z_inv.
sqr();
2143 elements[i].x *= zz_inv;
2144 elements[i].y *= (zz_inv * z_inv);
2151template <
typename Fq,
typename Fr,
typename T>
2155 bool found_one =
false;
2159 while (!found_one) {
2161 yy = x.sqr() * x + T::b;
2162 if constexpr (T::has_a) {
2165 auto [found_root, y1] = yy.sqrt();
2167 found_one = found_root;
#define BB_ASSERT(expression,...)
#define BB_ASSERT_EQ(actual, expected,...)
#define BB_BENCH_NAME(name)
#define BB_BENCH_TRACY_NAME(name)
constexpr bool is_point_at_infinity() const noexcept
static constexpr affine_element one() noexcept
element class. Implements ecc group arithmetic using Jacobian coordinates See https://hyperelliptic....
element operator*=(const Fr &exponent) noexcept
BB_INLINE constexpr element set_infinity() const noexcept
element mul_with_endomorphism(const Fr &scalar) const noexcept
static element infinity()
static std::vector< affine_element< Fq, Fr, Params > > batch_mul_with_endomorphism(const std::span< const affine_element< Fq, Fr, Params > > &points, const Fr &scalar) noexcept
Multiply each point by the same scalar.
constexpr element operator-=(const element &other) noexcept
constexpr element operator-() const noexcept
constexpr affine_element< Fq, Fr, Params > to_affine_const_time() const noexcept
friend constexpr element operator+(const affine_element< Fq, Fr, Params > &left, const element &right) noexcept
static std::vector< affine_element< Fq, Fr, Params > > batch_two_round_fold(const std::span< const affine_element< Fq, Fr, Params > > &points, const Fr &u1, const Fr &u2) noexcept
Fused two-round IPA SRS fold: out[i] = (u1·u2)·P[i] + u1·P[i+t] + u2·P[i+2t] + P[i+3t].
constexpr element dbl() const noexcept
constexpr element normalize() const noexcept
constexpr void self_dbl() noexcept
static element random_element(numeric::RNG *engine=nullptr) noexcept
static void batch_normalize(element *elements, size_t num_elements) noexcept
constexpr element operator+=(const element &other) noexcept
static void batch_affine_add(const std::span< affine_element< Fq, Fr, Params > > &first_group, const std::span< affine_element< Fq, Fr, Params > > &second_group, const std::span< affine_element< Fq, Fr, Params > > &results) noexcept
Pairwise affine add points in first and second group.
element mul_const_time(const Fr &scalar, numeric::RNG *engine=nullptr) const noexcept
Constant-time scalar multiplication intended for secret scalars (e.g. ECDSA / Schnorr nonces).
BB_INLINE constexpr bool on_curve() const noexcept
BB_INLINE constexpr bool operator==(const element &other) const noexcept
element operator*(const Fr &exponent) const noexcept
static element straus_msm(std::span< const affine_element< Fq, Fr, Params > > points, std::span< const Fr > scalars) noexcept
Straus-style multi-scalar multiplication.
element() noexcept=default
static element random_coordinates_on_curve(numeric::RNG *engine=nullptr) noexcept
element mul_without_endomorphism(const Fr &scalar) const noexcept
constexpr element & operator=(const element &other) noexcept
BB_INLINE constexpr void self_set_infinity() noexcept
constexpr element normalize_const_time() const noexcept
BB_INLINE constexpr bool is_point_at_infinity() const noexcept
constexpr bool get_bit(uint64_t bit_index) const
constexpr uint64_t get_msb() const
bool get_bit(uint64_t bit_index) const
constexpr std::array< BoothSliceParams, NUM_WINDOWS > make_offset_booth_slice_params() noexcept
constexpr std::array< BoothSliceParams, NUM_WINDOWS > make_booth_slice_params() noexcept
uint32_t booth_packed_digit(const uint64_t *s, const BoothSliceParams &sp, size_t window_bits) noexcept
Read a (window_bits+1)-bit window from s[] (uint64 limbs) and apply Constantine's signedWindowEncodin...
constexpr size_t BOOTH_ENDO_K2_NUM_WINDOWS
std::pair< std::array< uint64_t, 2 >, std::array< uint64_t, 2 > > EndoScalars
constexpr size_t BOOTH_ENDO_K2_LOW_WINDOW_BITS
constexpr size_t BOOTH_ENDO_WINDOW_BITS
constexpr size_t BOOTH_ENDO_LOOKUP_SIZE
constexpr size_t BOOTH_ENDO_NUM_WINDOWS
constexpr size_t BOOTH_ENDO_NUM_LIMBS_U64
AffineElement const size_t Fq *scratch_space noexcept
AffineElement const size_t num_pairs
__attribute__((always_inline)) inline void batch_affine_add_impl(const AffineElement *lhs
Batch affine addition for parallel arrays: (lhs[i], rhs[i]) → rhs[i].
batch_inversion_accumulator
AffineElement const size_t Fq Fq * scratch_b
AffineElement * accumulator
AffineElement const size_t Fq * scratch_a
const std::pair< uint32_t, uint32_t > * pairs
uintx< uint256_t > uint512_t
std::conditional_t< IsGoblinBigGroup< C, Fq, Fr, G >, element_goblin::goblin_element< C, goblin_field< C >, Fr, G >, element_default::element< C, Fq, Fr, G > > element
element wraps either element_default::element or element_goblin::goblin_element depending on parametr...
constexpr size_t FF_COPY_COST
constexpr size_t FF_ADDITION_COST
constexpr size_t FF_MULTIPLICATION_COST
Univariate< Fr, domain_end > operator*(const Fr &ff, const Univariate< Fr, domain_end > &uv)
void parallel_for_heuristic(size_t num_points, const std::function< void(size_t, size_t, size_t)> &func, size_t heuristic_cost)
Split a loop into several loops running in parallel based on operations in 1 iteration.
void parallel_for_range(size_t num_points, const std::function< void(size_t, size_t)> &func, size_t no_multhreading_if_less_or_equal)
Split a loop into several loops running in parallel.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
bb::VectorAffineElementPushSpan< BaseParams > lhs
static constexpr field cube_root_of_unity()
BB_INLINE constexpr field from_montgomery_form_reduced() const noexcept
static constexpr field one()
static constexpr uint256_t modulus
static void split_into_endomorphism_scalars(const field &k, field &k1, field &k2)
Full-width endomorphism decomposition: k ≡ k1 - k2·λ (mod r). Modifies the field elements k1 and k2.
BB_INLINE constexpr void self_sqr() &noexcept
constexpr field invert() const noexcept
BB_INLINE constexpr bool is_msb_set() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field sqr() const noexcept
BB_INLINE constexpr bool is_zero() const noexcept
BB_INLINE constexpr field from_montgomery_form() const noexcept
static constexpr field zero()
constexpr field invert_const_time() const noexcept
void throw_or_abort(std::string const &err)