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: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
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>
25constexpr element<Fq, Fr, T>::element(const element& other) noexcept
26 : x(other.x)
27 , y(other.y)
28 , z(other.z)
29{}
30
31template <class Fq, class Fr, class T>
32constexpr element<Fq, Fr, T>::element(element&& other) noexcept
33 : x(other.x)
34 , y(other.y)
35 , z(other.z)
36{}
37
38template <class Fq, class Fr, class T>
39constexpr element<Fq, Fr, T>::element(const affine_element<Fq, Fr, T>& other) noexcept
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] >= 0x4000000000000000ULL) {
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] >= 0x4000000000000000ULL) {
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] >= 0x4000000000000000ULL) {
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] >= 0x4000000000000000ULL) {
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] >= 0x4000000000000000ULL) {
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] >= 0x4000000000000000ULL) {
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
721template <class Fq, class Fr, class T>
723 const std::span<affine_element<Fq, Fr, T>>& second_group,
724 const std::span<affine_element<Fq, Fr, T>>& results) noexcept
725{
727 const size_t num_points = first_group.size();
728 BB_ASSERT_EQ(second_group.size(), first_group.size());
729
730 // Space for temporary values
731 std::vector<Fq> scratch_space(num_points);
732
734 num_points, [&](size_t i) { results[i] = first_group[i]; }, thread_heuristics::FF_COPY_COST * 2);
735
736 // TODO(#826): Same code as in batch mul
737 // we can mutate rhs but NOT lhs!
738 // output is stored in rhs
743 const auto batch_affine_add_chunked =
744 [](const affine_element* lhs, affine_element* rhs, const size_t point_count, Fq* personal_scratch_space) {
745 Fq batch_inversion_accumulator = Fq::one();
746
747 for (size_t i = 0; i < point_count; i += 1) {
748 personal_scratch_space[i] = lhs[i].x + rhs[i].x; // x2 + x1
749 rhs[i].x -= lhs[i].x; // x2 - x1
750 rhs[i].y -= lhs[i].y; // y2 - y1
751 rhs[i].y *= batch_inversion_accumulator; // (y2 - y1)*accumulator_old
752 batch_inversion_accumulator *= (rhs[i].x);
753 }
754 batch_inversion_accumulator = batch_inversion_accumulator.invert();
755
756 for (size_t i = (point_count)-1; i < point_count; i -= 1) {
757 rhs[i].y *= batch_inversion_accumulator; // update accumulator
758 batch_inversion_accumulator *= rhs[i].x;
759 rhs[i].x = rhs[i].y.sqr();
760 rhs[i].x = rhs[i].x - (personal_scratch_space[i]); // x3 = lambda_squared - x2
761 // - x1
762 personal_scratch_space[i] = lhs[i].x - rhs[i].x;
763 personal_scratch_space[i] *= rhs[i].y;
764 rhs[i].y = personal_scratch_space[i] - lhs[i].y;
765 }
766 };
767
772 const auto batch_affine_add_internal = [&](const affine_element* lhs, affine_element* rhs) {
774 num_points,
775 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
776 batch_affine_add_chunked(lhs + start, rhs + start, end - start, &scratch_space[0] + start);
777 },
779 };
780 batch_affine_add_internal(&second_group[0], &results[0]);
781}
782
793template <class Fq, class Fr, class T>
795 const std::span<const affine_element<Fq, Fr, T>>& points, const Fr& scalar) noexcept
796{
797 PROFILE_THIS();
799 const size_t num_points = points.size();
800
801 // Space for temporary values
802 std::vector<Fq> scratch_space(num_points);
803
804 // TODO(#826): Same code as in batch add
805 // we can mutate rhs but NOT lhs!
806 // output is stored in rhs
811 const auto batch_affine_add_chunked =
812 [](const affine_element* lhs, affine_element* rhs, const size_t point_count, Fq* personal_scratch_space) {
813 Fq batch_inversion_accumulator = Fq::one();
814
815 for (size_t i = 0; i < point_count; i += 1) {
816 personal_scratch_space[i] = lhs[i].x + rhs[i].x; // x2 + x1
817 rhs[i].x -= lhs[i].x; // x2 - x1
818 rhs[i].y -= lhs[i].y; // y2 - y1
819 rhs[i].y *= batch_inversion_accumulator; // (y2 - y1)*accumulator_old
820 batch_inversion_accumulator *= (rhs[i].x);
821 }
822 batch_inversion_accumulator = batch_inversion_accumulator.invert();
823
824 for (size_t i = (point_count)-1; i < point_count; i -= 1) {
825 rhs[i].y *= batch_inversion_accumulator; // update accumulator
826 batch_inversion_accumulator *= rhs[i].x;
827 rhs[i].x = rhs[i].y.sqr();
828 rhs[i].x = rhs[i].x - (personal_scratch_space[i]); // x3 = lambda_squared - x2
829 // - x1
830 personal_scratch_space[i] = lhs[i].x - rhs[i].x;
831 personal_scratch_space[i] *= rhs[i].y;
832 rhs[i].y = personal_scratch_space[i] - lhs[i].y;
833 }
834 };
835
840 const auto batch_affine_add_internal =
841 [num_points, &scratch_space, &batch_affine_add_chunked](const affine_element* lhs, affine_element* rhs) {
843 num_points,
844 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
845 batch_affine_add_chunked(lhs + start, rhs + start, end - start, &scratch_space[0] + start);
846 },
848 };
849
854 const auto batch_affine_double_chunked =
855 [](affine_element* lhs, const size_t point_count, Fq* personal_scratch_space) {
856 Fq batch_inversion_accumulator = Fq::one();
857
858 for (size_t i = 0; i < point_count; i += 1) {
859
860 personal_scratch_space[i] = lhs[i].x.sqr();
861 personal_scratch_space[i] =
862 personal_scratch_space[i] + personal_scratch_space[i] + personal_scratch_space[i];
863
864 personal_scratch_space[i] *= batch_inversion_accumulator;
865
866 batch_inversion_accumulator *= (lhs[i].y + lhs[i].y);
867 }
868 batch_inversion_accumulator = batch_inversion_accumulator.invert();
869
870 Fq temp;
871 for (size_t i = (point_count)-1; i < point_count; i -= 1) {
872
873 personal_scratch_space[i] *= batch_inversion_accumulator;
874 batch_inversion_accumulator *= (lhs[i].y + lhs[i].y);
875
876 temp = lhs[i].x;
877 lhs[i].x = personal_scratch_space[i].sqr() - (lhs[i].x + lhs[i].x);
878 lhs[i].y = personal_scratch_space[i] * (temp - lhs[i].x) - lhs[i].y;
879 }
880 };
885 const auto batch_affine_double = [num_points, &scratch_space, &batch_affine_double_chunked](affine_element* lhs) {
887 num_points,
888 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
889 batch_affine_double_chunked(lhs + start, end - start, &scratch_space[0] + start);
890 },
892 };
893
894 // We compute the resulting point through WNAF by evaluating (the (\sum_i (16ⁱ⋅
895 // (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
896 // always odd and skew is used to reconstruct an even scalar. This means that to construct scalar p-1, where p is
897 // the order of the scalar field, we first compute p through the sums and then subtract -1. Howver, since we are
898 // computing p⋅Point, we get a point at infinity, which is an edgecase, and we don't want to handle edgecases in the
899 // hot loop since the slow the computation down. So it's better to just handle it here.
900 if (scalar == -Fr::one()) {
901 std::vector<affine_element> results(num_points);
902 parallel_for_heuristic(num_points, [&](size_t i) { results[i] = -points[i]; }, thread_heuristics::FF_COPY_COST);
903 return results;
904 }
905 // Compute wnaf for scalar
906 const Fr converted_scalar = scalar.from_montgomery_form();
907
908 // If the scalar is zero, just set results to the point at infinity
909 if (converted_scalar.is_zero()) {
910 affine_element result{ Fq::zero(), Fq::zero() };
911 result.self_set_infinity();
912 std::vector<affine_element> results(num_points);
913 parallel_for_heuristic(num_points, [&](size_t i) { results[i] = result; }, thread_heuristics::FF_COPY_COST);
914 return results;
915 }
916
917 constexpr size_t LOOKUP_SIZE = 8;
918 constexpr size_t NUM_ROUNDS = 32;
919 std::array<std::vector<affine_element>, LOOKUP_SIZE> lookup_table;
920 for (auto& table : lookup_table) {
921 table.resize(num_points);
922 }
923 // Initialize first etnries in lookup table
924 std::vector<affine_element> temp_point_vector(num_points);
926 num_points,
927 [&](size_t i) {
928 // If the point is at infinity we fix-up the result later
929 // To avoid 'trying to invert zero in the field' we set the point to 'one' here
930 temp_point_vector[i] = points[i].is_point_at_infinity() ? affine_element::one() : points[i];
931 lookup_table[0][i] = points[i].is_point_at_infinity() ? affine_element::one() : points[i];
932 },
934
935 // Construct lookup table
936 batch_affine_double(&temp_point_vector[0]);
937 for (size_t j = 1; j < LOOKUP_SIZE; ++j) {
939 num_points,
940 [&](size_t i) { lookup_table[j][i] = lookup_table[j - 1][i]; },
942 batch_affine_add_internal(&temp_point_vector[0], &lookup_table[j][0]);
943 }
944
945 detail::EndoScalars endo_scalars = Fr::split_into_endomorphism_scalars(converted_scalar);
947
948 std::vector<affine_element> work_elements(num_points);
949
950 constexpr Fq beta = Fq::cube_root_of_unity();
951 uint64_t wnaf_entry = 0;
952 uint64_t index = 0;
953 bool sign = 0;
954 // Prepare elements for the first batch addition
955 for (size_t j = 0; j < 2; ++j) {
956 wnaf_entry = wnaf.table[j];
957 index = wnaf_entry & 0x0fffffffU;
958 sign = static_cast<bool>((wnaf_entry >> 31) & 1);
959 const bool is_odd = ((j & 1) == 1);
961 num_points,
962 [&](size_t i) {
963 auto to_add = lookup_table[static_cast<size_t>(index)][i];
964 to_add.y.self_conditional_negate(sign ^ is_odd);
965 if (is_odd) {
966 to_add.x *= beta;
967 }
968 if (j == 0) {
969 work_elements[i] = to_add;
970 } else {
971 temp_point_vector[i] = to_add;
972 }
973 },
976 }
977 // First cycle of addition
978 batch_affine_add_internal(&temp_point_vector[0], &work_elements[0]);
979 // Run through SM logic in wnaf form (excluding the skew)
980 for (size_t j = 2; j < NUM_ROUNDS * 2; ++j) {
981 wnaf_entry = wnaf.table[j];
982 index = wnaf_entry & 0x0fffffffU;
983 sign = static_cast<bool>((wnaf_entry >> 31) & 1);
984 const bool is_odd = ((j & 1) == 1);
985 if (!is_odd) {
986 for (size_t k = 0; k < 4; ++k) {
987 batch_affine_double(&work_elements[0]);
988 }
989 }
991 num_points,
992 [&](size_t i) {
993 auto to_add = lookup_table[static_cast<size_t>(index)][i];
994 to_add.y.self_conditional_negate(sign ^ is_odd);
995 if (is_odd) {
996 to_add.x *= beta;
997 }
998 temp_point_vector[i] = to_add;
999 },
1002 batch_affine_add_internal(&temp_point_vector[0], &work_elements[0]);
1003 }
1004
1005 // Apply skew for the first endo scalar
1006 if (wnaf.skew) {
1008 num_points,
1009 [&](size_t i) { temp_point_vector[i] = -lookup_table[0][i]; },
1011 batch_affine_add_internal(&temp_point_vector[0], &work_elements[0]);
1012 }
1013 // Apply skew for the second endo scalar
1014 if (wnaf.endo_skew) {
1016 num_points,
1017 [&](size_t i) {
1018 temp_point_vector[i] = lookup_table[0][i];
1019 temp_point_vector[i].x *= beta;
1020 },
1022 batch_affine_add_internal(&temp_point_vector[0], &work_elements[0]);
1023 }
1024 // handle points at infinity explicitly
1026 num_points,
1027 [&](size_t i) {
1028 work_elements[i] = points[i].is_point_at_infinity() ? work_elements[i].set_infinity() : work_elements[i];
1029 },
1031
1032 return work_elements;
1033}
1034
1035template <typename Fq, typename Fr, typename T>
1038 const uint64_t predicate) noexcept
1039{
1040 out = { in.x, predicate ? -in.y : in.y };
1041}
1042
1043template <typename Fq, typename Fr, typename T>
1044void element<Fq, Fr, T>::batch_normalize(element* elements, const size_t num_elements) noexcept
1045{
1046 std::vector<Fq> temporaries;
1047 temporaries.reserve(num_elements * 2);
1048 Fq accumulator = Fq::one();
1049
1050 // Iterate over the points, computing the product of their z-coordinates.
1051 // At each iteration, store the currently-accumulated z-coordinate in `temporaries`
1052 for (size_t i = 0; i < num_elements; ++i) {
1053 temporaries.emplace_back(accumulator);
1054 if (!elements[i].is_point_at_infinity()) {
1055 accumulator *= elements[i].z;
1056 }
1057 }
1058 // For the rest of this method we refer to the product of all z-coordinates as the 'global' z-coordinate
1059 // Invert the global z-coordinate and store in `accumulator`
1060 accumulator = accumulator.invert();
1061
1084 for (size_t i = num_elements - 1; i < num_elements; --i) {
1085 if (!elements[i].is_point_at_infinity()) {
1086 Fq z_inv = accumulator * temporaries[i];
1087 Fq zz_inv = z_inv.sqr();
1088 elements[i].x *= zz_inv;
1089 elements[i].y *= (zz_inv * z_inv);
1090 accumulator *= elements[i].z;
1091 }
1092 elements[i].z = Fq::one();
1093 }
1094}
1095
1096template <typename Fq, typename Fr, typename T>
1097template <typename>
1099{
1100 bool found_one = false;
1101 Fq yy;
1102 Fq x;
1103 Fq y;
1104 while (!found_one) {
1106 yy = x.sqr() * x + T::b;
1107 if constexpr (T::has_a) {
1108 yy += (x * T::a);
1109 }
1110 auto [found_root, y1] = yy.sqrt();
1111 y = y1;
1112 found_one = found_root;
1113 }
1114 return { x, y, Fq::one() };
1115}
1116
1117} // namespace bb::group_elements
1118// NOLINTEND(readability-implicit-bool-conversion, cppcoreguidelines-avoid-c-arrays)
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:59
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
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:146
constexpr size_t FF_ADDITION_COST
Definition thread.hpp:134
constexpr size_t FF_MULTIPLICATION_COST
Definition thread.hpp:136
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, domain_start, skip_count > operator*(const Fr &ff, const Univariate< Fr, domain_end, domain_start, skip_count > &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:132
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
#define PROFILE_THIS()
Definition op_count.hpp:15
static constexpr field cube_root_of_unity()
static constexpr field one()
static constexpr uint256_t modulus
BB_INLINE constexpr void self_conditional_negate(uint64_t predicate) &noexcept
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
constexpr std::pair< bool, field > sqrt() const noexcept
Compute square root of the field element.
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
bb::fq Fq