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 const uint64_t predicate) noexcept
161{
162 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
163 if (is_point_at_infinity()) {
164 conditional_negate_affine(other, *(affine_element<Fq, Fr, T>*)this, predicate); // NOLINT
165 z = Fq::one();
166 return;
167 }
168 } else {
169 const bool edge_case_trigger = x.is_msb_set() || other.x.is_msb_set();
170 if (edge_case_trigger) {
171 if (x.is_msb_set()) {
172 conditional_negate_affine(other, *(affine_element<Fq, Fr, T>*)this, predicate); // NOLINT
173 z = Fq::one();
174 }
175 return;
176 }
177 }
178
179 // T0 = z1.z1
180 Fq T0 = z.sqr();
181
182 // T1 = x2.t0 - x1 = x2.z1.z1 - x1
183 Fq T1 = other.x * T0;
184 T1 -= x;
185
186 // T2 = T0.z1 = z1.z1.z1
187 // T2 = T2.y2 - y1 = y2.z1.z1.z1 - y1
188 Fq T2 = z * T0;
189 T2 *= other.y;
190 T2.self_conditional_negate(predicate);
191 T2 -= y;
192
193 if (__builtin_expect(T1.is_zero(), 0)) {
194 if (T2.is_zero()) {
195 // y2 equals y1, x2 equals x1, double x1
196 self_dbl();
197 return;
198 }
199 self_set_infinity();
200 return;
201 }
202
203 // T2 = 2T2 = 2(y2.z1.z1.z1 - y1) = R
204 // z3 = z1 + H
205 T2 += T2;
206 z += T1;
207
208 // T3 = T1*T1 = HH
209 Fq T3 = T1.sqr();
210
211 // z3 = z3 - z1z1 - HH
212 T0 += T3;
213
214 // z3 = (z1 + H)*(z1 + H)
215 z.self_sqr();
216 z -= T0;
217
218 // T3 = 4HH
219 T3 += T3;
220 T3 += T3;
221
222 // T1 = T1*T3 = 4HHH
223 T1 *= T3;
224
225 // T3 = T3 * x1 = 4HH*x1
226 T3 *= x;
227
228 // T0 = 2T3
229 T0 = T3 + T3;
230
231 // T0 = T0 + T1 = 2(4HH*x1) + 4HHH
232 T0 += T1;
233 x = T2.sqr();
234
235 // x3 = x3 - T0 = R*R - 8HH*x1 -4HHH
236 x -= T0;
237
238 // T3 = T3 - x3 = 4HH*x1 - x3
239 T3 -= x;
240
241 T1 *= y;
242 T1 += T1;
243
244 // T3 = T2 * T3 = R*(4HH*x1 - x3)
245 T3 *= T2;
246
247 // y3 = T3 - T1
248 y = T3 - T1;
249}
250
251template <class Fq, class Fr, class T>
253{
254 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
255 if (is_point_at_infinity()) {
256 *this = { other.x, other.y, Fq::one() };
257 return *this;
258 }
259 } else {
260 const bool edge_case_trigger = x.is_msb_set() || other.x.is_msb_set();
261 if (edge_case_trigger) {
262 if (x.is_msb_set()) {
263 *this = { other.x, other.y, Fq::one() };
264 }
265 return *this;
266 }
267 }
268
269 // T0 = z1.z1
270 Fq T0 = z.sqr();
271
272 // T1 = x2.t0 - x1 = x2.z1.z1 - x1
273 Fq T1 = other.x * T0;
274 T1 -= x;
275
276 // T2 = T0.z1 = z1.z1.z1
277 // T2 = T2.y2 - y1 = y2.z1.z1.z1 - y1
278 Fq T2 = z * T0;
279 T2 *= other.y;
280 T2 -= y;
281
282 if (__builtin_expect(T1.is_zero(), 0)) {
283 if (T2.is_zero()) {
284 self_dbl();
285 return *this;
286 }
287 self_set_infinity();
288 return *this;
289 }
290
291 // T2 = 2T2 = 2(y2.z1.z1.z1 - y1) = R
292 // z3 = z1 + H
293 T2 += T2;
294 z += T1;
295
296 // T3 = T1*T1 = HH
297 Fq T3 = T1.sqr();
298
299 // z3 = z3 - z1z1 - HH
300 T0 += T3;
301
302 // z3 = (z1 + H)*(z1 + H)
303 z.self_sqr();
304 z -= T0;
305
306 // T3 = 4HH
307 T3 += T3;
308 T3 += T3;
309
310 // T1 = T1*T3 = 4HHH
311 T1 *= T3;
312
313 // T3 = T3 * x1 = 4HH*x1
314 T3 *= x;
315
316 // T0 = 2T3
317 T0 = T3 + T3;
318
319 // T0 = T0 + T1 = 2(4HH*x1) + 4HHH
320 T0 += T1;
321 x = T2.sqr();
322
323 // x3 = x3 - T0 = R*R - 8HH*x1 -4HHH
324 x -= T0;
325
326 // T3 = T3 - x3 = 4HH*x1 - x3
327 T3 -= x;
328
329 T1 *= y;
330 T1 += T1;
331
332 // T3 = T2 * T3 = R*(4HH*x1 - x3)
333 T3 *= T2;
334
335 // y3 = T3 - T1
336 y = T3 - T1;
337 return *this;
338}
339
340template <class Fq, class Fr, class T>
341constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator+(const affine_element<Fq, Fr, T>& other) const noexcept
342{
343 element result(*this);
344 return (result += other);
345}
346
347template <class Fq, class Fr, class T>
348constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator-=(const affine_element<Fq, Fr, T>& other) noexcept
349{
350 const affine_element<Fq, Fr, T> to_add{ other.x, -other.y };
351 return operator+=(to_add);
352}
353
354template <class Fq, class Fr, class T>
355constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator-(const affine_element<Fq, Fr, T>& other) const noexcept
356{
357 element result(*this);
358 return (result -= other);
359}
360
361template <class Fq, class Fr, class T>
363{
364 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
365 bool p1_zero = is_point_at_infinity();
366 bool p2_zero = other.is_point_at_infinity();
367 if (__builtin_expect((p1_zero || p2_zero), 0)) {
368 if (p1_zero && !p2_zero) {
369 *this = other;
370 return *this;
371 }
372 if (p2_zero && !p1_zero) {
373 return *this;
374 }
375 self_set_infinity();
376 return *this;
377 }
378 } else {
379 bool p1_zero = x.is_msb_set();
380 bool p2_zero = other.x.is_msb_set();
381 if (__builtin_expect((p1_zero || p2_zero), 0)) {
382 if (p1_zero && !p2_zero) {
383 *this = other;
384 return *this;
385 }
386 if (p2_zero && !p1_zero) {
387 return *this;
388 }
389 self_set_infinity();
390 return *this;
391 }
392 }
393 Fq Z1Z1(z.sqr());
394 Fq Z2Z2(other.z.sqr());
395 Fq S2(Z1Z1 * z);
396 Fq U2(Z1Z1 * other.x);
397 S2 *= other.y;
398 Fq U1(Z2Z2 * x);
399 Fq S1(Z2Z2 * other.z);
400 S1 *= y;
401
402 Fq F(S2 - S1);
403
404 Fq H(U2 - U1);
405
406 if (__builtin_expect(H.is_zero(), 0)) {
407 if (F.is_zero()) {
408 self_dbl();
409 return *this;
410 }
411 self_set_infinity();
412 return *this;
413 }
414
415 F += F;
416
417 Fq I(H + H);
418 I.self_sqr();
419
420 Fq J(H * I);
421
422 U1 *= I;
423
424 U2 = U1 + U1;
425 U2 += J;
426
427 x = F.sqr();
428
429 x -= U2;
430
431 J *= S1;
432 J += J;
433
434 y = U1 - x;
435
436 y *= F;
437
438 y -= J;
439
440 z += other.z;
441
442 Z1Z1 += Z2Z2;
443
444 z.self_sqr();
445 z -= Z1Z1;
446 z *= H;
447 return *this;
448}
449
450template <class Fq, class Fr, class T>
451constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator+(const element& other) const noexcept
452{
453 element result(*this);
454 return (result += other);
455}
456
457template <class Fq, class Fr, class T>
459{
460 const element to_add{ other.x, -other.y, other.z };
461 return operator+=(to_add);
462}
463
464template <class Fq, class Fr, class T>
465constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator-(const element& other) const noexcept
466{
467 element result(*this);
468 return (result -= other);
469}
470
471template <class Fq, class Fr, class T> constexpr element<Fq, Fr, T> element<Fq, Fr, T>::operator-() const noexcept
472{
473 return { x, -y, z };
474}
475
476template <class Fq, class Fr, class T>
478{
479 if constexpr (T::USE_ENDOMORPHISM) {
480 return mul_with_endomorphism(exponent);
481 }
482 return mul_without_endomorphism(exponent);
483}
484
485template <class Fq, class Fr, class T> element<Fq, Fr, T> element<Fq, Fr, T>::operator*=(const Fr& exponent) noexcept
486{
487 *this = operator*(exponent);
488 return *this;
489}
490
491template <class Fq, class Fr, class T> constexpr element<Fq, Fr, T> element<Fq, Fr, T>::normalize() const noexcept
492{
493 const affine_element<Fq, Fr, T> converted = *this;
494 return element(converted);
495}
496
497template <class Fq, class Fr, class T> element<Fq, Fr, T> element<Fq, Fr, T>::infinity()
498{
501 return e;
502}
503
504template <class Fq, class Fr, class T> constexpr element<Fq, Fr, T> element<Fq, Fr, T>::set_infinity() const noexcept
505{
506 element result(*this);
507 result.self_set_infinity();
508 return result;
509}
510
511template <class Fq, class Fr, class T> constexpr void element<Fq, Fr, T>::self_set_infinity() noexcept
512{
513 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
514 // We set the value of x equal to modulus to represent inifinty
515 x.data[0] = Fq::modulus.data[0];
516 x.data[1] = Fq::modulus.data[1];
517 x.data[2] = Fq::modulus.data[2];
518 x.data[3] = Fq::modulus.data[3];
519 } else {
520 (*this).x = Fq::zero();
521 (*this).y = Fq::zero();
522 (*this).z = Fq::zero();
523 x.self_set_msb();
524 }
525}
526
527template <class Fq, class Fr, class T> constexpr bool element<Fq, Fr, T>::is_point_at_infinity() const noexcept
528{
529 if constexpr (Fq::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
530 // We check if the value of x is equal to modulus to represent inifinty
531 return ((x.data[0] ^ Fq::modulus.data[0]) | (x.data[1] ^ Fq::modulus.data[1]) |
532 (x.data[2] ^ Fq::modulus.data[2]) | (x.data[3] ^ Fq::modulus.data[3])) == 0;
533 } else {
534 return (x.is_msb_set());
535 }
536}
537
538template <class Fq, class Fr, class T> constexpr bool element<Fq, Fr, T>::on_curve() const noexcept
539{
540 if (is_point_at_infinity()) {
541 return true;
542 }
543 // We specify the point at inifinity not by (0 \lambda 0), so z should not be 0
544 if (z.is_zero()) {
545 return false;
546 }
547 Fq zz = z.sqr();
548 Fq zzzz = zz.sqr();
549 Fq bz_6 = zzzz * zz * T::b;
550 if constexpr (T::has_a) {
551 bz_6 += (x * T::a) * zzzz;
552 }
553 Fq xxx = x.sqr() * x + bz_6;
554 Fq yy = y.sqr();
555 return (xxx == yy);
556}
557
558template <class Fq, class Fr, class T>
559constexpr bool element<Fq, Fr, T>::operator==(const element& other) const noexcept
560{
561 // If one of points is not on curve, we have no business comparing them.
562 if ((!on_curve()) || (!other.on_curve())) {
563 return false;
564 }
565 bool am_infinity = is_point_at_infinity();
566 bool is_infinity = other.is_point_at_infinity();
567 bool both_infinity = am_infinity && is_infinity;
568 // If just one is infinity, then they are obviously not equal.
569 if ((!both_infinity) && (am_infinity || is_infinity)) {
570 return false;
571 }
572 const Fq lhs_zz = z.sqr();
573 const Fq lhs_zzz = lhs_zz * z;
574 const Fq rhs_zz = other.z.sqr();
575 const Fq rhs_zzz = rhs_zz * other.z;
576
577 const Fq lhs_x = x * rhs_zz;
578 const Fq lhs_y = y * rhs_zzz;
579
580 const Fq rhs_x = other.x * lhs_zz;
581 const Fq rhs_y = other.y * lhs_zzz;
582 return both_infinity || ((lhs_x == rhs_x) && (lhs_y == rhs_y));
583}
584
585template <class Fq, class Fr, class T>
587{
588 if constexpr (T::can_hash_to_curve) {
589 element result = random_coordinates_on_curve(engine);
590 result.z = Fq::random_element(engine);
591 Fq zz = result.z.sqr();
592 Fq zzz = zz * result.z;
593 result.x *= zz;
594 result.y *= zzz;
595 return result;
596 } else {
597 Fr scalar = Fr::random_element(engine);
598 return (element{ T::one_x, T::one_y, Fq::one() } * scalar);
599 }
600}
601
602template <class Fq, class Fr, class T>
604{
605 const uint256_t converted_scalar(scalar);
606
607 if (converted_scalar == 0) {
608 return element::infinity();
609 }
610
611 element accumulator(*this);
612 const uint64_t maximum_set_bit = converted_scalar.get_msb();
613 // This is simpler and doublings of infinity should be fast. We should think if we want to defend against the
614 // timing leak here (if used with ECDSA it can sometimes lead to private key compromise)
615 for (uint64_t i = maximum_set_bit - 1; i < maximum_set_bit; --i) {
616 accumulator.self_dbl();
617 if (converted_scalar.get_bit(i)) {
618 accumulator += *this;
619 }
620 }
621 return accumulator;
622}
623
624namespace detail {
625// Represents the result of
627
636template <typename Element, std::size_t NUM_ROUNDS> struct EndomorphismWnaf {
637 // NUM_WNAF_BITS: Number of bits per window in the WNAF representation.
638 static constexpr size_t NUM_WNAF_BITS = 4;
639 // table: Stores the WNAF representation of the scalars.
640 std::array<uint64_t, NUM_ROUNDS * 2> table;
641 // skew and endo_skew: Indicate if our original scalar is even or odd.
642 bool skew = false;
643 bool endo_skew = false;
644
649 {
650 wnaf::fixed_wnaf(&scalars.first[0], &table[0], skew, 0, 2, NUM_WNAF_BITS);
651 wnaf::fixed_wnaf(&scalars.second[0], &table[1], endo_skew, 0, 2, NUM_WNAF_BITS);
652 }
653};
654
655} // namespace detail
656
657template <class Fq, class Fr, class T>
659{
660 // Consider the infinity flag, return infinity if set
661 if (is_point_at_infinity()) {
662 return element::infinity();
663 }
664 constexpr size_t NUM_ROUNDS = 32;
665 const Fr converted_scalar = scalar.from_montgomery_form();
666
667 if (converted_scalar.is_zero()) {
668 return element::infinity();
669 }
670 static constexpr size_t LOOKUP_SIZE = 8;
672
673 element d2 = dbl();
674 lookup_table[0] = element(*this);
675 for (size_t i = 1; i < LOOKUP_SIZE; ++i) {
676 lookup_table[i] = lookup_table[i - 1] + d2;
677 }
678
679 detail::EndoScalars endo_scalars = Fr::split_into_endomorphism_scalars(converted_scalar);
681 element accumulator{ T::one_x, T::one_y, Fq::one() };
682 accumulator.self_set_infinity();
683 Fq beta = Fq::cube_root_of_unity();
684
685 for (size_t i = 0; i < NUM_ROUNDS * 2; ++i) {
686 uint64_t wnaf_entry = wnaf.table[i];
687 uint64_t index = wnaf_entry & 0x0fffffffU;
688 bool sign = static_cast<bool>((wnaf_entry >> 31) & 1);
689 const bool is_odd = ((i & 1) == 1);
690 auto to_add = lookup_table[static_cast<size_t>(index)];
691 to_add.y.self_conditional_negate(sign ^ is_odd);
692 if (is_odd) {
693 to_add.x *= beta;
694 }
695 accumulator += to_add;
696
697 if (i != ((2 * NUM_ROUNDS) - 1) && is_odd) {
698 for (size_t j = 0; j < 4; ++j) {
699 accumulator.self_dbl();
700 }
701 }
702 }
703
704 if (wnaf.skew) {
705 accumulator += -lookup_table[0];
706 }
707 if (wnaf.endo_skew) {
708 accumulator += element{ lookup_table[0].x * beta, lookup_table[0].y, lookup_table[0].z };
709 }
710
711 return accumulator;
712}
713
728template <typename AffineElement, typename Fq>
729__attribute__((always_inline)) inline void batch_affine_add_impl(const AffineElement* lhs,
730 AffineElement* rhs,
731 const size_t num_pairs,
732 Fq* scratch_space) noexcept
733{
735
736 // Forward pass: prepare batch inversion
737 for (size_t i = 0; i < num_pairs; ++i) {
738 scratch_space[i] = lhs[i].x + rhs[i].x;
739 rhs[i].x -= lhs[i].x;
740 rhs[i].y -= lhs[i].y;
743 }
744
746 throw_or_abort("attempted to invert zero in batch_affine_add_impl");
747 }
749
750 // Backward pass: compute additions
751 for (size_t i = num_pairs - 1; i < num_pairs; --i) {
752 // lambda = (y2 - y1) / (x2 - x1)
755 rhs[i].x = rhs[i].y.sqr();
756 rhs[i].x -= scratch_space[i]; // x3 = lambda^2 - (x1 + x2)
757
758 // y3 = lambda * (x1 - x3) - y1
759 Fq temp = lhs[i].x - rhs[i].x;
760 temp *= rhs[i].y;
761 rhs[i].y = temp - lhs[i].y;
762 }
763}
764
776template <typename AffineElement, typename Fq>
777__attribute__((always_inline)) inline void batch_affine_add_interleaved(AffineElement* points,
778 const size_t num_points,
779 Fq* scratch_space) noexcept
780{
782
783 // Forward pass: accumulate (x2 - x1) products for batch inversion
784 for (size_t i = 0; i < num_points; i += 2) {
785 scratch_space[i >> 1] = points[i].x + points[i + 1].x; // x1 + x2 (saved for later)
786 points[i + 1].x -= points[i].x; // x2 - x1
787 points[i + 1].y -= points[i].y; // y2 - y1
788 points[i + 1].y *= batch_inversion_accumulator;
789 batch_inversion_accumulator *= points[i + 1].x;
790 }
791
793 throw_or_abort("attempted to invert zero in batch_affine_add_interleaved");
794 }
796
797 // Backward pass: complete inversions and compute additions
798 for (size_t i = num_points - 2; i < num_points; i -= 2) {
799 // lambda = (y2 - y1) / (x2 - x1)
800 points[i + 1].y *= batch_inversion_accumulator;
801 batch_inversion_accumulator *= points[i + 1].x;
802 points[i + 1].x = points[i + 1].y.sqr();
803 // x3 = lambda^2 - (x1 + x2)
804 points[(i + num_points) >> 1].x = points[i + 1].x - scratch_space[i >> 1];
805
806 if (i >= 2) {
807 __builtin_prefetch(points + i - 2);
808 __builtin_prefetch(points + i - 1);
809 __builtin_prefetch(points + ((i + num_points - 2) >> 1));
810 __builtin_prefetch(scratch_space + ((i - 2) >> 1));
811 }
812
813 // y3 = lambda * (x1 - x3) - y1
814 points[i].x -= points[(i + num_points) >> 1].x;
815 points[i].x *= points[i + 1].y;
816 points[(i + num_points) >> 1].y = points[i].x - points[i].y;
817 }
818}
819
833template <typename AffineElement, typename Fq>
834__attribute__((always_inline)) inline void batch_affine_double_impl(AffineElement* points,
835 const size_t num_points,
836 Fq* scratch_space) noexcept
837{
839
840 // Forward pass: prepare batch inversion
841 for (size_t i = 0; i < num_points; ++i) {
842 scratch_space[i] = points[i].x.sqr();
843 scratch_space[i] = scratch_space[i] + scratch_space[i] + scratch_space[i];
844 scratch_space[i] *= batch_inversion_accumulator;
845 batch_inversion_accumulator *= (points[i].y + points[i].y);
846 }
847
849 throw_or_abort("attempted to invert zero in batch_affine_double_impl");
850 }
852
853 // Backward pass: compute doublings
855 for (size_t i_plus_1 = num_points; i_plus_1 > 0; --i_plus_1) {
856 size_t i = i_plus_1 - 1;
857
858 scratch_space[i] *= batch_inversion_accumulator;
859 batch_inversion_accumulator *= (points[i].y + points[i].y);
860
861 temp_x = points[i].x;
862 points[i].x = scratch_space[i].sqr() - (points[i].x + points[i].x);
863 points[i].y = scratch_space[i] * (temp_x - points[i].x) - points[i].y;
864 }
865}
866
878template <class Fq, class Fr, class T>
880 const std::span<affine_element<Fq, Fr, T>>& second_group,
881 const std::span<affine_element<Fq, Fr, T>>& results) noexcept
882{
884 const size_t num_points = first_group.size();
885 BB_ASSERT_EQ(second_group.size(), first_group.size());
886
887 // Space for temporary values
888 std::vector<Fq> scratch_space(num_points);
889
891 num_points, [&](size_t i) { results[i] = first_group[i]; }, thread_heuristics::FF_COPY_COST * 2);
892
893 // Perform batch affine addition: (lhs[i], rhs[i]) -> rhs[i]
896 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
897 batch_affine_add_impl<affine_element, Fq>(
898 &second_group[start], &results[start], end - start, &scratch_space[start]);
899 },
901}
902
913template <class Fq, class Fr, class T>
915 const std::span<const affine_element<Fq, Fr, T>>& points, const Fr& scalar) noexcept
916{
917 BB_BENCH();
919 const size_t num_points = points.size();
920
921 // Space for temporary values
922 std::vector<Fq> scratch_space(num_points);
923
924 // We compute the resulting point through WNAF by evaluating (the (\sum_i (16ⁱ⋅
925 // (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
926 // always odd and skew is used to reconstruct an even scalar. This means that to construct scalar p-1, where p is
927 // the order of the scalar field, we first compute p through the sums and then subtract -1. Howver, since we are
928 // computing p⋅Point, we get a point at infinity, which is an edgecase, and we don't want to handle edgecases in the
929 // hot loop since the slow the computation down. So it's better to just handle it here.
930 if (scalar == -Fr::one()) {
932 parallel_for_heuristic(num_points, [&](size_t i) { results[i] = -points[i]; }, thread_heuristics::FF_COPY_COST);
933 return results;
934 }
935 // Compute wnaf for scalar
936 const Fr converted_scalar = scalar.from_montgomery_form();
937
938 // If the scalar is zero, just set results to the point at infinity
939 if (converted_scalar.is_zero()) {
940 affine_element result{ Fq::zero(), Fq::zero() };
941 result.self_set_infinity();
943 parallel_for_heuristic(num_points, [&](size_t i) { results[i] = result; }, thread_heuristics::FF_COPY_COST);
944 return results;
945 }
946
947 constexpr size_t LOOKUP_SIZE = 8;
948 constexpr size_t NUM_ROUNDS = 32;
949
950 detail::EndoScalars endo_scalars = Fr::split_into_endomorphism_scalars(converted_scalar);
952
954 std::array<std::vector<affine_element>, LOOKUP_SIZE> lookup_table;
955 for (auto& table : lookup_table) {
956 table.resize(num_points);
957 }
958 std::vector<affine_element> temp_point_vector(num_points);
959
960 auto execute_range = [&](size_t start, size_t end) {
961 // Perform batch affine addition in parallel
962 const auto add_chunked = [&](const affine_element* lhs, affine_element* rhs) {
963 batch_affine_add_impl<affine_element, Fq>(&lhs[start], &rhs[start], end - start, &scratch_space[start]);
964 };
965
966 // Perform point doubling in parallel
967 const auto double_chunked = [&](affine_element* lhs) {
968 batch_affine_double_impl<affine_element, Fq>(&lhs[start], end - start, &scratch_space[start]);
969 };
970
971 // Initialize first entries in lookup table
972 for (size_t i = start; i < end; ++i) {
973 if (points[i].is_point_at_infinity()) {
974 temp_point_vector[i] = affine_element::one();
975 lookup_table[0][i] = affine_element::one();
976 } else {
977 temp_point_vector[i] = points[i];
978 lookup_table[0][i] = points[i];
979 }
980 }
981 // Costruct lookup table
982 double_chunked(&temp_point_vector[0]);
983 for (size_t j = 1; j < LOOKUP_SIZE; ++j) {
984 for (size_t i = start; i < end; ++i) {
985 lookup_table[j][i] = lookup_table[j - 1][i];
986 }
987 add_chunked(&temp_point_vector[0], &lookup_table[j][0]);
988 }
989
990 constexpr Fq beta = Fq::cube_root_of_unity();
991 uint64_t wnaf_entry = 0;
992 uint64_t index = 0;
993 bool sign = 0;
994 // Prepare elements for the first batch addition
995 for (size_t j = 0; j < 2; ++j) {
996 wnaf_entry = wnaf.table[j];
997 index = wnaf_entry & 0x0fffffffU;
998 sign = static_cast<bool>((wnaf_entry >> 31) & 1);
999 const bool is_odd = ((j & 1) == 1);
1000 for (size_t i = start; i < end; ++i) {
1001 auto to_add = lookup_table[static_cast<size_t>(index)][i];
1002 to_add.y.self_conditional_negate(sign ^ is_odd);
1003 if (is_odd) {
1004 to_add.x *= beta;
1005 }
1006 if (j == 0) {
1007 work_elements[i] = to_add;
1008 } else {
1009 temp_point_vector[i] = to_add;
1010 }
1011 }
1012 }
1013 add_chunked(&temp_point_vector[0], &work_elements[0]);
1014 // Run through SM logic in wnaf form (excluding the skew)
1015 for (size_t j = 2; j < NUM_ROUNDS * 2; ++j) {
1016 wnaf_entry = wnaf.table[j];
1017 index = wnaf_entry & 0x0fffffffU;
1018 sign = static_cast<bool>((wnaf_entry >> 31) & 1);
1019 const bool is_odd = ((j & 1) == 1);
1020 if (!is_odd) {
1021 for (size_t k = 0; k < 4; ++k) {
1022 double_chunked(&work_elements[0]);
1023 }
1024 }
1025 for (size_t i = start; i < end; ++i) {
1026 auto to_add = lookup_table[static_cast<size_t>(index)][i];
1027 to_add.y.self_conditional_negate(sign ^ is_odd);
1028 if (is_odd) {
1029 to_add.x *= beta;
1030 }
1031 temp_point_vector[i] = to_add;
1032 }
1033 add_chunked(&temp_point_vector[0], &work_elements[0]);
1034 }
1035 // Apply skew for the first endo scalar
1036 if (wnaf.skew) {
1037 for (size_t i = start; i < end; ++i) {
1038 temp_point_vector[i] = -lookup_table[0][i];
1039 }
1040 add_chunked(&temp_point_vector[0], &work_elements[0]);
1041 }
1042 // Apply skew for the second endo scalar
1043 if (wnaf.endo_skew) {
1044 for (size_t i = start; i < end; ++i) {
1045 temp_point_vector[i] = lookup_table[0][i];
1046 temp_point_vector[i].x *= beta;
1047 }
1048 add_chunked(&temp_point_vector[0], &work_elements[0]);
1049 }
1050 // handle points at infinity explicitly
1051 for (size_t i = start; i < end; ++i) {
1052 work_elements[i] = points[i].is_point_at_infinity() ? work_elements[i].set_infinity() : work_elements[i];
1053 }
1054 };
1055 parallel_for_range(num_points, execute_range);
1056
1057 return work_elements;
1058}
1059
1060template <typename Fq, typename Fr, typename T>
1063 const uint64_t predicate) noexcept
1064{
1065 out = { in.x, predicate ? -in.y : in.y };
1066}
1067
1068template <typename Fq, typename Fr, typename T>
1069void element<Fq, Fr, T>::batch_normalize(element* elements, const size_t num_elements) noexcept
1070{
1071 std::vector<Fq> temporaries;
1072 temporaries.reserve(num_elements * 2);
1073 Fq accumulator = Fq::one();
1074
1075 // Iterate over the points, computing the product of their z-coordinates.
1076 // At each iteration, store the currently-accumulated z-coordinate in `temporaries`
1077 for (size_t i = 0; i < num_elements; ++i) {
1078 temporaries.emplace_back(accumulator);
1079 if (!elements[i].is_point_at_infinity()) {
1080 accumulator *= elements[i].z;
1081 }
1082 }
1083 // For the rest of this method we refer to the product of all z-coordinates as the 'global' z-coordinate
1084 // Invert the global z-coordinate and store in `accumulator`
1085 accumulator = accumulator.invert();
1086
1109 for (size_t i = num_elements - 1; i < num_elements; --i) {
1110 if (!elements[i].is_point_at_infinity()) {
1111 Fq z_inv = accumulator * temporaries[i];
1112 Fq zz_inv = z_inv.sqr();
1113 elements[i].x *= zz_inv;
1114 elements[i].y *= (zz_inv * z_inv);
1115 accumulator *= elements[i].z;
1116 }
1117 elements[i].z = Fq::one();
1118 }
1119}
1120
1121template <typename Fq, typename Fr, typename T>
1122template <typename>
1124{
1125 bool found_one = false;
1126 Fq yy;
1127 Fq x;
1128 Fq y;
1129 while (!found_one) {
1131 yy = x.sqr() * x + T::b;
1132 if constexpr (T::has_a) {
1133 yy += (x * T::a);
1134 }
1135 auto [found_root, y1] = yy.sqrt();
1136 y = y1;
1137 found_one = found_root;
1138 }
1139 return { x, y, Fq::one() };
1140}
1141
1142} // namespace bb::group_elements
1143// 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:75
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
constexpr void self_mixed_add_or_sub(const affine_element< Fq, Fr, Params > &other, uint64_t predicate) noexcept
element() noexcept=default
static void conditional_negate_affine(const affine_element< Fq, Fr, Params > &in, affine_element< Fq, Fr, Params > &out, uint64_t predicate) noexcept
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:178
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)
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