Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
element_impl.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Planned, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
12#include "element.hpp"
13#include <cstdint>
14
15// NOLINTBEGIN(readability-implicit-bool-conversion, cppcoreguidelines-avoid-c-arrays)
16namespace bb::group_elements {
17template <class Fq, class Fr, class T>
18constexpr element<Fq, Fr, T>::element(const Fq& a, const Fq& b, const Fq& c) noexcept
19 : x(a)
20 , y(b)
21 , z(c)
22{}
23
24template <class Fq, class Fr, class T>
26 : x(other.x)
27 , y(other.y)
28 , z(other.z)
29{}
30
31template <class Fq, class Fr, class T>
33 : x(other.x)
34 , y(other.y)
35 , z(other.z)
36{}
37
38template <class Fq, class Fr, class T>
40 : x(other.x)
41 , y(other.y)
42 , z(Fq::one())
43{}
44
45template <class Fq, class Fr, class T>
47{
48 if (this == &other) {
49 return *this;
50 }
51 x = other.x;
52 y = other.y;
53 z = other.z;
54 return *this;
55}
56
57template <class Fq, class Fr, class T>
59{
60 x = other.x;
61 y = other.y;
62 z = other.z;
63 return *this;
64}
65
66template <class Fq, class Fr, class T> constexpr element<Fq, Fr, T>::operator affine_element<Fq, Fr, T>() const noexcept
67{
68 if (is_point_at_infinity()) {
70 result.x = Fq(0);
71 result.y = Fq(0);
72 result.self_set_infinity();
73 return result;
74 }
75 Fq z_inv = z.invert();
76 Fq zz_inv = z_inv.sqr();
77 Fq zzz_inv = zz_inv * z_inv;
78 affine_element<Fq, Fr, T> result(x * zz_inv, y * zzz_inv);
79 return result;
80}
81
82template <class Fq, class Fr, class T> constexpr void element<Fq, Fr, T>::self_dbl() noexcept
83{
84 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
85 if (is_point_at_infinity()) {
86 return;
87 }
88 } else {
89 if (x.is_msb_set_word()) {
90 return;
91 }
92 }
93
94 // T0 = x*x
95 Fq T0 = x.sqr();
96
97 // T1 = y*y
98 Fq T1 = y.sqr();
99
100 // T2 = T2*T1 = y*y*y*y
101 Fq T2 = T1.sqr();
102
103 // T1 = T1 + x = x + y*y
104 T1 += x;
105
106 // T1 = T1 * T1
107 T1.self_sqr();
108
109 // T3 = T0 + T2 = xx + y*y*y*y
110 Fq T3 = T0 + T2;
111
112 // T1 = T1 - T3 = x*x + y*y*y*y + 2*x*x*y*y*y*y - x*x - y*y*y*y = 2*x*x*y*y*y*y = 2*S
113 T1 -= T3;
114
115 // T1 = 2T1 = 4*S
116 T1 += T1;
117
118 // T3 = 3T0
119 T3 = T0 + T0;
120 T3 += T0;
121 if constexpr (T::has_a) {
122 T3 += (T::a * z.sqr().sqr());
123 }
124
125 // z2 = 2*y*z
126 z += z;
127 z *= y;
128
129 // T0 = 2T1
130 T0 = T1 + T1;
131
132 // x2 = T3*T3
133 x = T3.sqr();
134
135 // x2 = x2 - 2T1
136 x -= T0;
137
138 // T2 = 8T2
139 T2 += T2;
140 T2 += T2;
141 T2 += T2;
142
143 // y2 = T1 - x2
144 y = T1 - x;
145
146 // y2 = y2 * T3 - T2
147 y *= T3;
148 y -= T2;
149}
150
151template <class Fq, class Fr, class T> constexpr element<Fq, Fr, T> element<Fq, Fr, T>::dbl() const noexcept
152{
153 element result(*this);
154 result.self_dbl();
155 return result;
156}
157
158template <class Fq, class Fr, class T>
160{
161 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
162 if (is_point_at_infinity()) {
163 *this = { other.x, other.y, Fq::one() };
164 return *this;
165 }
166 } else {
167 const bool edge_case_trigger = x.is_msb_set() || other.x.is_msb_set();
168 if (edge_case_trigger) {
169 if (x.is_msb_set()) {
170 *this = { other.x, other.y, Fq::one() };
171 }
172 return *this;
173 }
174 }
175
176 // T0 = z1.z1
177 Fq T0 = z.sqr();
178
179 // T1 = x2.t0 - x1 = x2.z1.z1 - x1
180 Fq T1 = other.x * T0;
181 T1 -= x;
182
183 // T2 = T0.z1 = z1.z1.z1
184 // T2 = T2.y2 - y1 = y2.z1.z1.z1 - y1
185 Fq T2 = z * T0;
186 T2 *= other.y;
187 T2 -= y;
188
189 if (__builtin_expect(T1.is_zero(), 0)) {
190 if (T2.is_zero()) {
191 self_dbl();
192 return *this;
193 }
194 self_set_infinity();
195 return *this;
196 }
197
198 // T2 = 2T2 = 2(y2.z1.z1.z1 - y1) = R
199 // z3 = z1 + H
200 T2 += T2;
201 z += T1;
202
203 // T3 = T1*T1 = HH
204 Fq T3 = T1.sqr();
205
206 // z3 = z3 - z1z1 - HH
207 T0 += T3;
208
209 // z3 = (z1 + H)*(z1 + H)
210 z.self_sqr();
211 z -= T0;
212
213 // T3 = 4HH
214 T3 += T3;
215 T3 += T3;
216
217 // T1 = T1*T3 = 4HHH
218 T1 *= T3;
219
220 // T3 = T3 * x1 = 4HH*x1
221 T3 *= x;
222
223 // T0 = 2T3
224 T0 = T3 + T3;
225
226 // T0 = T0 + T1 = 2(4HH*x1) + 4HHH
227 T0 += T1;
228 x = T2.sqr();
229
230 // x3 = x3 - T0 = R*R - 8HH*x1 -4HHH
231 x -= T0;
232
233 // T3 = T3 - x3 = 4HH*x1 - x3
234 T3 -= x;
235
236 T1 *= y;
237 T1 += T1;
238
239 // T3 = T2 * T3 = R*(4HH*x1 - x3)
240 T3 *= T2;
241
242 // y3 = T3 - T1
243 y = T3 - T1;
244 return *this;
245}
246
247template <class Fq, class Fr, class T>
248constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator+(const affine_element<Fq, Fr, T>& other) const noexcept
249{
250 element result(*this);
251 return (result += other);
252}
253
254template <class Fq, class Fr, class T>
255constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator-=(const affine_element<Fq, Fr, T>& other) noexcept
256{
257 const affine_element<Fq, Fr, T> to_add{ other.x, -other.y };
258 return operator+=(to_add);
259}
260
261template <class Fq, class Fr, class T>
262constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator-(const affine_element<Fq, Fr, T>& other) const noexcept
263{
264 element result(*this);
265 return (result -= other);
266}
267
268template <class Fq, class Fr, class T>
270{
271 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
272 bool p1_zero = is_point_at_infinity();
273 bool p2_zero = other.is_point_at_infinity();
274 if (__builtin_expect((p1_zero || p2_zero), 0)) {
275 if (p1_zero && !p2_zero) {
276 *this = other;
277 return *this;
278 }
279 if (p2_zero && !p1_zero) {
280 return *this;
281 }
282 self_set_infinity();
283 return *this;
284 }
285 } else {
286 bool p1_zero = x.is_msb_set();
287 bool p2_zero = other.x.is_msb_set();
288 if (__builtin_expect((p1_zero || p2_zero), 0)) {
289 if (p1_zero && !p2_zero) {
290 *this = other;
291 return *this;
292 }
293 if (p2_zero && !p1_zero) {
294 return *this;
295 }
296 self_set_infinity();
297 return *this;
298 }
299 }
300 Fq Z1Z1(z.sqr());
301 Fq Z2Z2(other.z.sqr());
302 Fq S2(Z1Z1 * z);
303 Fq U2(Z1Z1 * other.x);
304 S2 *= other.y;
305 Fq U1(Z2Z2 * x);
306 Fq S1(Z2Z2 * other.z);
307 S1 *= y;
308
309 Fq F(S2 - S1);
310
311 Fq H(U2 - U1);
312
313 if (__builtin_expect(H.is_zero(), 0)) {
314 if (F.is_zero()) {
315 self_dbl();
316 return *this;
317 }
318 self_set_infinity();
319 return *this;
320 }
321
322 F += F;
323
324 Fq I(H + H);
325 I.self_sqr();
326
327 Fq J(H * I);
328
329 U1 *= I;
330
331 U2 = U1 + U1;
332 U2 += J;
333
334 x = F.sqr();
335
336 x -= U2;
337
338 J *= S1;
339 J += J;
340
341 y = U1 - x;
342
343 y *= F;
344
345 y -= J;
346
347 z += other.z;
348
349 Z1Z1 += Z2Z2;
350
351 z.self_sqr();
352 z -= Z1Z1;
353 z *= H;
354 return *this;
355}
356
357template <class Fq, class Fr, class T>
358constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator+(const element& other) const noexcept
359{
360 element result(*this);
361 return (result += other);
362}
363
364template <class Fq, class Fr, class T>
366{
367 const element to_add{ other.x, -other.y, other.z };
368 return operator+=(to_add);
369}
370
371template <class Fq, class Fr, class T>
372constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator-(const element& other) const noexcept
373{
374 element result(*this);
375 return (result -= other);
376}
377
378template <class Fq, class Fr, class T> constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator-() const noexcept
379{
380 return { x, -y, z };
381}
382
383template <class Fq, class Fr, class T>
385{
386 if constexpr (T::USE_ENDOMORPHISM) {
387 return mul_with_endomorphism(exponent);
388 }
389 return mul_without_endomorphism(exponent);
390}
391
392template <class Fq, class Fr, class T> element<Fq, Fr, T> element<Fq, Fr, T>::operator*=(const Fr& exponent) noexcept
393{
394 *this = operator*(exponent);
395 return *this;
396}
397
398template <class Fq, class Fr, class T> constexpr element<Fq, Fr, T> element<Fq, Fr, T>::normalize() const noexcept
399{
400 const affine_element<Fq, Fr, T> converted = *this;
401 return element(converted);
402}
403
404template <class Fq, class Fr, class T> element<Fq, Fr, T> element<Fq, Fr, T>::infinity()
405{
408 return e;
409}
410
411template <class Fq, class Fr, class T> constexpr element<Fq, Fr, T> element<Fq, Fr, T>::set_infinity() const noexcept
412{
413 element result(*this);
414 result.self_set_infinity();
415 return result;
416}
417
418template <class Fq, class Fr, class T> constexpr void element<Fq, Fr, T>::self_set_infinity() noexcept
419{
420 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
421 // We set the value of x equal to modulus to represent inifinty
422 x.data[0] = Fq::modulus.data[0];
423 x.data[1] = Fq::modulus.data[1];
424 x.data[2] = Fq::modulus.data[2];
425 x.data[3] = Fq::modulus.data[3];
426 } else {
427 (*this).x = Fq::zero();
428 (*this).y = Fq::zero();
429 (*this).z = Fq::zero();
430 x.self_set_msb();
431 }
432}
433
434template <class Fq, class Fr, class T> constexpr bool element<Fq, Fr, T>::is_point_at_infinity() const noexcept
435{
436 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
437 // We check if the value of x is equal to modulus to represent inifinty
438 return ((x.data[0] ^ Fq::modulus.data[0]) | (x.data[1] ^ Fq::modulus.data[1]) |
439 (x.data[2] ^ Fq::modulus.data[2]) | (x.data[3] ^ Fq::modulus.data[3])) == 0;
440 } else {
441 return (x.is_msb_set());
442 }
443}
444
445template <class Fq, class Fr, class T> constexpr bool element<Fq, Fr, T>::on_curve() const noexcept
446{
447 if (is_point_at_infinity()) {
448 return true;
449 }
450 // We specify the point at inifinity not by (0 \lambda 0), so z should not be 0
451 if (z.is_zero()) {
452 return false;
453 }
454 Fq zz = z.sqr();
455 Fq zzzz = zz.sqr();
456 Fq bz_6 = zzzz * zz * T::b;
457 if constexpr (T::has_a) {
458 bz_6 += (x * T::a) * zzzz;
459 }
460 Fq xxx = x.sqr() * x + bz_6;
461 Fq yy = y.sqr();
462 return (xxx == yy);
463}
464
465template <class Fq, class Fr, class T>
466constexpr bool element<Fq, Fr, T>::operator==(const element& other) const noexcept
467{
468 // If one of points is not on curve, we have no business comparing them.
469 if ((!on_curve()) || (!other.on_curve())) {
470 return false;
471 }
472 bool am_infinity = is_point_at_infinity();
473 bool is_infinity = other.is_point_at_infinity();
474 bool both_infinity = am_infinity && is_infinity;
475 // If just one is infinity, then they are obviously not equal.
476 if ((!both_infinity) && (am_infinity || is_infinity)) {
477 return false;
478 }
479 const Fq lhs_zz = z.sqr();
480 const Fq lhs_zzz = lhs_zz * z;
481 const Fq rhs_zz = other.z.sqr();
482 const Fq rhs_zzz = rhs_zz * other.z;
483
484 const Fq lhs_x = x * rhs_zz;
485 const Fq lhs_y = y * rhs_zzz;
486
487 const Fq rhs_x = other.x * lhs_zz;
488 const Fq rhs_y = other.y * lhs_zzz;
489 return both_infinity || ((lhs_x == rhs_x) && (lhs_y == rhs_y));
490}
491
492template <class Fq, class Fr, class T>
494{
495 if constexpr (T::can_hash_to_curve) {
496 element result = random_coordinates_on_curve(engine);
497 result.z = Fq::random_element(engine);
498 Fq zz = result.z.sqr();
499 Fq zzz = zz * result.z;
500 result.x *= zz;
501 result.y *= zzz;
502 return result;
503 } else {
504 Fr scalar = Fr::random_element(engine);
505 return (element{ T::one_x, T::one_y, Fq::one() } * scalar);
506 }
507}
508
509template <class Fq, class Fr, class T>
511{
512 const uint256_t converted_scalar(scalar);
513
514 if (converted_scalar == 0) {
515 return element::infinity();
516 }
517
518 element accumulator(*this);
519 const uint64_t maximum_set_bit = converted_scalar.get_msb();
520 // This is simpler and doublings of infinity should be fast. We should think if we want to defend against the
521 // timing leak here (if used with ECDSA it can sometimes lead to private key compromise)
522 for (uint64_t i = maximum_set_bit - 1; i < maximum_set_bit; --i) {
523 accumulator.self_dbl();
524 if (converted_scalar.get_bit(i)) {
525 accumulator += *this;
526 }
527 }
528 return accumulator;
529}
530
531namespace detail {
532// Represents the result of
534
543template <typename Element, std::size_t NUM_ROUNDS> struct EndomorphismWnaf {
544 // NUM_WNAF_BITS: Number of bits per window in the WNAF representation.
545 static constexpr size_t NUM_WNAF_BITS = 4;
546 // table: Stores the WNAF representation of the scalars.
547 std::array<uint64_t, NUM_ROUNDS * 2> table;
548 // skew and endo_skew: Indicate if our original scalar is even or odd.
549 bool skew = false;
550 bool endo_skew = false;
551
556 {
557 wnaf::fixed_wnaf(&scalars.first[0], &table[0], skew, 0, 2, NUM_WNAF_BITS);
558 wnaf::fixed_wnaf(&scalars.second[0], &table[1], endo_skew, 0, 2, NUM_WNAF_BITS);
559 }
560};
561
562} // namespace detail
563
564template <class Fq, class Fr, class T>
566{
567 // Consider the infinity flag, return infinity if set
568 if (is_point_at_infinity()) {
569 return element::infinity();
570 }
571 constexpr size_t NUM_ROUNDS = 32;
572 const Fr converted_scalar = scalar.from_montgomery_form();
573
574 if (converted_scalar.is_zero()) {
575 return element::infinity();
576 }
577 static constexpr size_t LOOKUP_SIZE = 8;
579
580 element d2 = dbl();
581 lookup_table[0] = element(*this);
582 for (size_t i = 1; i < LOOKUP_SIZE; ++i) {
583 lookup_table[i] = lookup_table[i - 1] + d2;
584 }
585
586 detail::EndoScalars endo_scalars = Fr::split_into_endomorphism_scalars(converted_scalar);
588 element accumulator{ T::one_x, T::one_y, Fq::one() };
589 accumulator.self_set_infinity();
590 Fq beta = Fq::cube_root_of_unity();
591
592 for (size_t i = 0; i < NUM_ROUNDS * 2; ++i) {
593 uint64_t wnaf_entry = wnaf.table[i];
594 uint64_t index = wnaf_entry & 0x0fffffffU;
595 bool sign = static_cast<bool>((wnaf_entry >> 31) & 1);
596 const bool is_odd = ((i & 1) == 1);
597 auto to_add = lookup_table[static_cast<size_t>(index)];
598 to_add.y.self_conditional_negate(sign ^ is_odd);
599 if (is_odd) {
600 to_add.x *= beta;
601 }
602 accumulator += to_add;
603
604 if (i != ((2 * NUM_ROUNDS) - 1) && is_odd) {
605 for (size_t j = 0; j < 4; ++j) {
606 accumulator.self_dbl();
607 }
608 }
609 }
610
611 if (wnaf.skew) {
612 accumulator += -lookup_table[0];
613 }
614 if (wnaf.endo_skew) {
615 accumulator += element{ lookup_table[0].x * beta, lookup_table[0].y, lookup_table[0].z };
616 }
617
618 return accumulator;
619}
620
635template <typename AffineElement, typename Fq>
636__attribute__((always_inline)) inline void batch_affine_add_impl(const AffineElement* lhs,
637 AffineElement* rhs,
638 const size_t num_pairs,
639 Fq* scratch_space) noexcept
640{
642
643 // Forward pass: prepare batch inversion
644 for (size_t i = 0; i < num_pairs; ++i) {
645 scratch_space[i] = lhs[i].x + rhs[i].x;
646 rhs[i].x -= lhs[i].x;
647 rhs[i].y -= lhs[i].y;
650 }
651
653 throw_or_abort("attempted to invert zero in batch_affine_add_impl");
654 }
656
657 // Backward pass: compute additions
658 for (size_t i = num_pairs - 1; i < num_pairs; --i) {
659 // lambda = (y2 - y1) / (x2 - x1)
662 rhs[i].x = rhs[i].y.sqr();
663 rhs[i].x -= scratch_space[i]; // x3 = lambda^2 - (x1 + x2)
664
665 // y3 = lambda * (x1 - x3) - y1
666 Fq temp = lhs[i].x - rhs[i].x;
667 temp *= rhs[i].y;
668 rhs[i].y = temp - lhs[i].y;
669 }
670}
671
683template <typename AffineElement, typename Fq>
684__attribute__((always_inline)) inline void batch_affine_add_interleaved(AffineElement* points,
685 const size_t num_points,
686 Fq* scratch_space) noexcept
687{
689
690 // Forward pass: accumulate (x2 - x1) products for batch inversion
691 for (size_t i = 0; i < num_points; i += 2) {
692 scratch_space[i >> 1] = points[i].x + points[i + 1].x; // x1 + x2 (saved for later)
693 points[i + 1].x -= points[i].x; // x2 - x1
694 points[i + 1].y -= points[i].y; // y2 - y1
695 points[i + 1].y *= batch_inversion_accumulator;
696 batch_inversion_accumulator *= points[i + 1].x;
697 }
698
700 throw_or_abort("attempted to invert zero in batch_affine_add_interleaved");
701 }
703
704 // Backward pass: complete inversions and compute additions
705 for (size_t i = num_points - 2; i < num_points; i -= 2) {
706 // lambda = (y2 - y1) / (x2 - x1)
707 points[i + 1].y *= batch_inversion_accumulator;
708 batch_inversion_accumulator *= points[i + 1].x;
709 points[i + 1].x = points[i + 1].y.sqr();
710 // x3 = lambda^2 - (x1 + x2)
711 points[(i + num_points) >> 1].x = points[i + 1].x - scratch_space[i >> 1];
712
713 if (i >= 2) {
714 __builtin_prefetch(points + i - 2);
715 __builtin_prefetch(points + i - 1);
716 __builtin_prefetch(points + ((i + num_points - 2) >> 1));
717 __builtin_prefetch(scratch_space + ((i - 2) >> 1));
718 }
719
720 // y3 = lambda * (x1 - x3) - y1
721 points[i].x -= points[(i + num_points) >> 1].x;
722 points[i].x *= points[i + 1].y;
723 points[(i + num_points) >> 1].y = points[i].x - points[i].y;
724 }
725}
726
740template <typename AffineElement, typename Fq>
741__attribute__((always_inline)) inline void batch_affine_double_impl(AffineElement* points,
742 const size_t num_points,
743 Fq* scratch_space) noexcept
744{
746
747 // Forward pass: prepare batch inversion
748 for (size_t i = 0; i < num_points; ++i) {
749 scratch_space[i] = points[i].x.sqr();
750 scratch_space[i] = scratch_space[i] + scratch_space[i] + scratch_space[i];
751 scratch_space[i] *= batch_inversion_accumulator;
752 batch_inversion_accumulator *= (points[i].y + points[i].y);
753 }
754
756 throw_or_abort("attempted to invert zero in batch_affine_double_impl");
757 }
759
760 // Backward pass: compute doublings
762 for (size_t i_plus_1 = num_points; i_plus_1 > 0; --i_plus_1) {
763 size_t i = i_plus_1 - 1;
764
765 scratch_space[i] *= batch_inversion_accumulator;
766 batch_inversion_accumulator *= (points[i].y + points[i].y);
767
768 temp_x = points[i].x;
769 points[i].x = scratch_space[i].sqr() - (points[i].x + points[i].x);
770 points[i].y = scratch_space[i] * (temp_x - points[i].x) - points[i].y;
771 }
772}
773
785template <class Fq, class Fr, class T>
787 const std::span<affine_element<Fq, Fr, T>>& second_group,
788 const std::span<affine_element<Fq, Fr, T>>& results) noexcept
789{
791 const size_t num_points = first_group.size();
792 BB_ASSERT_EQ(second_group.size(), first_group.size());
793
794 // Space for temporary values
795 std::vector<Fq> scratch_space(num_points);
796
798 num_points, [&](size_t i) { results[i] = first_group[i]; }, thread_heuristics::FF_COPY_COST * 2);
799
800 // Perform batch affine addition: (lhs[i], rhs[i]) -> rhs[i]
803 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
804 batch_affine_add_impl<affine_element, Fq>(
805 &second_group[start], &results[start], end - start, &scratch_space[start]);
806 },
808}
809
820template <class Fq, class Fr, class T>
822 const std::span<const affine_element<Fq, Fr, T>>& points, const Fr& scalar) noexcept
823{
824 BB_BENCH();
826 const size_t num_points = points.size();
827
828 // Space for temporary values
829 std::vector<Fq> scratch_space(num_points);
830
831 // We compute the resulting point through WNAF by evaluating (the (\sum_i (16ⁱ⋅
832 // (a_i ∈ {-15,-13,-11,-9,-7,-5,-3,-1,1,3,5,7,9,11,13,15}))) - skew), where skew is 0 or 1. The result of the sum is
833 // always odd and skew is used to reconstruct an even scalar. This means that to construct scalar p-1, where p is
834 // the order of the scalar field, we first compute p through the sums and then subtract -1. Howver, since we are
835 // computing p⋅Point, we get a point at infinity, which is an edgecase, and we don't want to handle edgecases in the
836 // hot loop since the slow the computation down. So it's better to just handle it here.
837 if (scalar == -Fr::one()) {
839 parallel_for_heuristic(num_points, [&](size_t i) { results[i] = -points[i]; }, thread_heuristics::FF_COPY_COST);
840 return results;
841 }
842 // Compute wnaf for scalar
843 const Fr converted_scalar = scalar.from_montgomery_form();
844
845 // If the scalar is zero, just set results to the point at infinity
846 if (converted_scalar.is_zero()) {
847 affine_element result{ Fq::zero(), Fq::zero() };
848 result.self_set_infinity();
850 parallel_for_heuristic(num_points, [&](size_t i) { results[i] = result; }, thread_heuristics::FF_COPY_COST);
851 return results;
852 }
853
854 constexpr size_t LOOKUP_SIZE = 8;
855 constexpr size_t NUM_ROUNDS = 32;
856
857 detail::EndoScalars endo_scalars = Fr::split_into_endomorphism_scalars(converted_scalar);
859
861 std::array<std::vector<affine_element>, LOOKUP_SIZE> lookup_table;
862 for (auto& table : lookup_table) {
863 table.resize(num_points);
864 }
865 std::vector<affine_element> temp_point_vector(num_points);
866
867 auto execute_range = [&](size_t start, size_t end) {
868 // Perform batch affine addition in parallel
869 const auto add_chunked = [&](const affine_element* lhs, affine_element* rhs) {
870 batch_affine_add_impl<affine_element, Fq>(&lhs[start], &rhs[start], end - start, &scratch_space[start]);
871 };
872
873 // Perform point doubling in parallel
874 const auto double_chunked = [&](affine_element* lhs) {
875 batch_affine_double_impl<affine_element, Fq>(&lhs[start], end - start, &scratch_space[start]);
876 };
877
878 // Initialize first entries in lookup table
879 for (size_t i = start; i < end; ++i) {
880 if (points[i].is_point_at_infinity()) {
881 temp_point_vector[i] = affine_element::one();
882 lookup_table[0][i] = affine_element::one();
883 } else {
884 temp_point_vector[i] = points[i];
885 lookup_table[0][i] = points[i];
886 }
887 }
888 // Costruct lookup table
889 double_chunked(&temp_point_vector[0]);
890 for (size_t j = 1; j < LOOKUP_SIZE; ++j) {
891 for (size_t i = start; i < end; ++i) {
892 lookup_table[j][i] = lookup_table[j - 1][i];
893 }
894 add_chunked(&temp_point_vector[0], &lookup_table[j][0]);
895 }
896
897 constexpr Fq beta = Fq::cube_root_of_unity();
898 uint64_t wnaf_entry = 0;
899 uint64_t index = 0;
900 bool sign = 0;
901 // Prepare elements for the first batch addition
902 for (size_t j = 0; j < 2; ++j) {
903 wnaf_entry = wnaf.table[j];
904 index = wnaf_entry & 0x0fffffffU;
905 sign = static_cast<bool>((wnaf_entry >> 31) & 1);
906 const bool is_odd = ((j & 1) == 1);
907 for (size_t i = start; i < end; ++i) {
908 auto to_add = lookup_table[static_cast<size_t>(index)][i];
909 to_add.y.self_conditional_negate(sign ^ is_odd);
910 if (is_odd) {
911 to_add.x *= beta;
912 }
913 if (j == 0) {
914 work_elements[i] = to_add;
915 } else {
916 temp_point_vector[i] = to_add;
917 }
918 }
919 }
920 add_chunked(&temp_point_vector[0], &work_elements[0]);
921 // Run through SM logic in wnaf form (excluding the skew)
922 for (size_t j = 2; j < NUM_ROUNDS * 2; ++j) {
923 wnaf_entry = wnaf.table[j];
924 index = wnaf_entry & 0x0fffffffU;
925 sign = static_cast<bool>((wnaf_entry >> 31) & 1);
926 const bool is_odd = ((j & 1) == 1);
927 if (!is_odd) {
928 for (size_t k = 0; k < 4; ++k) {
929 double_chunked(&work_elements[0]);
930 }
931 }
932 for (size_t i = start; i < end; ++i) {
933 auto to_add = lookup_table[static_cast<size_t>(index)][i];
934 to_add.y.self_conditional_negate(sign ^ is_odd);
935 if (is_odd) {
936 to_add.x *= beta;
937 }
938 temp_point_vector[i] = to_add;
939 }
940 add_chunked(&temp_point_vector[0], &work_elements[0]);
941 }
942 // Apply skew for the first endo scalar
943 if (wnaf.skew) {
944 for (size_t i = start; i < end; ++i) {
945 temp_point_vector[i] = -lookup_table[0][i];
946 }
947 add_chunked(&temp_point_vector[0], &work_elements[0]);
948 }
949 // Apply skew for the second endo scalar
950 if (wnaf.endo_skew) {
951 for (size_t i = start; i < end; ++i) {
952 temp_point_vector[i] = lookup_table[0][i];
953 temp_point_vector[i].x *= beta;
954 }
955 add_chunked(&temp_point_vector[0], &work_elements[0]);
956 }
957 // handle points at infinity explicitly
958 for (size_t i = start; i < end; ++i) {
959 work_elements[i] = points[i].is_point_at_infinity() ? work_elements[i].set_infinity() : work_elements[i];
960 }
961 };
962 parallel_for_range(num_points, execute_range);
963
964 return work_elements;
965}
966
967template <typename Fq, typename Fr, typename T>
968void element<Fq, Fr, T>::batch_normalize(element* elements, const size_t num_elements) noexcept
969{
970 std::vector<Fq> temporaries;
971 temporaries.reserve(num_elements * 2);
972 Fq accumulator = Fq::one();
973
974 // Iterate over the points, computing the product of their z-coordinates.
975 // At each iteration, store the currently-accumulated z-coordinate in `temporaries`
976 for (size_t i = 0; i < num_elements; ++i) {
977 temporaries.emplace_back(accumulator);
978 if (!elements[i].is_point_at_infinity()) {
979 accumulator *= elements[i].z;
980 }
981 }
982 // For the rest of this method we refer to the product of all z-coordinates as the 'global' z-coordinate
983 // Invert the global z-coordinate and store in `accumulator`
984 accumulator = accumulator.invert();
985
1008 for (size_t i = num_elements - 1; i < num_elements; --i) {
1009 if (!elements[i].is_point_at_infinity()) {
1010 Fq z_inv = accumulator * temporaries[i];
1011 Fq zz_inv = z_inv.sqr();
1012 elements[i].x *= zz_inv;
1013 elements[i].y *= (zz_inv * z_inv);
1014 accumulator *= elements[i].z;
1015 }
1016 elements[i].z = Fq::one();
1017 }
1018}
1019
1020template <typename Fq, typename Fr, typename T>
1021template <typename>
1023{
1024 bool found_one = false;
1025 Fq yy;
1026 Fq x;
1027 Fq y;
1028 while (!found_one) {
1030 yy = x.sqr() * x + T::b;
1031 if constexpr (T::has_a) {
1032 yy += (x * T::a);
1033 }
1034 auto [found_root, y1] = yy.sqrt();
1035 y = y1;
1036 found_one = found_root;
1037 }
1038 return { x, y, Fq::one() };
1039}
1040
1041} // namespace bb::group_elements
1042// NOLINTEND(readability-implicit-bool-conversion, cppcoreguidelines-avoid-c-arrays)
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_BENCH()
Definition bb_bench.hpp:229
constexpr void self_set_infinity() noexcept
static constexpr affine_element one() noexcept
element class. Implements ecc group arithmetic using Jacobian coordinates See https://hyperelliptic....
Definition element.hpp:33
element operator*=(const Fr &exponent) noexcept
BB_INLINE constexpr element set_infinity() const noexcept
element mul_with_endomorphism(const Fr &scalar) const noexcept
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
friend constexpr element operator+(const affine_element< Fq, Fr, Params > &left, const element &right) noexcept
Definition element.hpp:74
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.
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
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
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
#define BB_UNUSED
FF a
FF b
numeric::RNG & engine
std::pair< std::array< uint64_t, 2 >, std::array< uint64_t, 2 > > EndoScalars
__attribute__((always_inline)) inline void batch_affine_add_impl(const AffineElement *lhs
Batch affine addition for parallel arrays: (lhs[i], rhs[i]) → rhs[i].
AffineElement const size_t num_pairs
const size_t num_points
AffineElement * rhs
AffineElement const size_t Fq *scratch_space noexcept
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
Definition thread.hpp:144
constexpr size_t FF_ADDITION_COST
Definition thread.hpp:132
constexpr size_t FF_MULTIPLICATION_COST
Definition thread.hpp:134
void fixed_wnaf(const uint64_t *scalar, uint64_t *wnaf, bool &skew_map, const uint64_t point_index, const uint64_t num_points, const size_t wnaf_bits) noexcept
Performs fixed-window non-adjacent form (WNAF) computation for scalar multiplication.
Definition wnaf.hpp:117
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.
Definition thread.cpp:171
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.
Definition thread.cpp:141
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr field cube_root_of_unity()
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()
Handles the WNAF computation for scalars that are split using an endomorphism, achieved through split...
EndomorphismWnaf(const EndoScalars &scalars)
std::array< uint64_t, NUM_ROUNDS *2 > table
void throw_or_abort(std::string const &err)
curve::BN254::BaseField Fq