Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
biggroup_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
8
9#include "../circuit_builders/circuit_builders.hpp"
10#include "../plookup/plookup.hpp"
15
17
18template <typename C, class Fq, class Fr, class G>
20 : x()
21 , y()
22 , _is_infinity()
23{}
24
25template <typename C, class Fq, class Fr, class G>
26element<C, Fq, Fr, G>::element(const typename G::affine_element& input)
27 : x(nullptr, input.x)
28 , y(nullptr, input.y)
29 , _is_infinity(nullptr, input.is_point_at_infinity())
30{}
31
32template <typename C, class Fq, class Fr, class G>
33element<C, Fq, Fr, G>::element(const Fq& x_in, const Fq& y_in)
34 : x(x_in)
35 , y(y_in)
36 , _is_infinity(x.get_context() ? x.get_context() : y.get_context(), false)
37{}
38
39template <typename C, class Fq, class Fr, class G>
41 : x(other.x)
42 , y(other.y)
43 , _is_infinity(other.is_point_at_infinity())
44{}
45
46template <typename C, class Fq, class Fr, class G>
48 : x(other.x)
49 , y(other.y)
50 , _is_infinity(other.is_point_at_infinity())
51{}
52
53template <typename C, class Fq, class Fr, class G>
55{
56 if (&other == this) {
57 return *this;
58 }
59 x = other.x;
60 y = other.y;
61 _is_infinity = other.is_point_at_infinity();
62 return *this;
63}
64
65template <typename C, class Fq, class Fr, class G>
67{
68 if (&other == this) {
69 return *this;
70 }
71 x = other.x;
72 y = other.y;
73 _is_infinity = other.is_point_at_infinity();
74 return *this;
75}
76
77template <typename C, class Fq, class Fr, class G>
79{
80
81 // Adding in `x_coordinates_match` ensures that lambda will always be well-formed
82 // Our curve has the form y^2 = x^3 + b.
83 // If (x_1, y_1), (x_2, y_2) have x_1 == x_2, and the generic formula for lambda has a division by 0.
84 // Then y_1 == y_2 (i.e. we are doubling) or y_2 == y_1 (the sum is infinity).
85 // The cases have a special addition formula. The following booleans allow us to handle these cases uniformly.
86 const bool_ct x_coordinates_match = other.x == x;
87 const bool_ct y_coordinates_match = (y == other.y);
88 const bool_ct infinity_predicate = (x_coordinates_match && !y_coordinates_match);
89 const bool_ct double_predicate = (x_coordinates_match && y_coordinates_match);
90 const bool_ct lhs_infinity = is_point_at_infinity();
91 const bool_ct rhs_infinity = other.is_point_at_infinity();
92
93 // Compute the gradient `lambda`. If we add, `lambda = (y2 - y1)/(x2 - x1)`, else `lambda = 3x1*x1/2y1
94 const Fq add_lambda_numerator = other.y - y;
95 const Fq xx = x * x;
96 const Fq dbl_lambda_numerator = xx + xx + xx;
97 const Fq lambda_numerator = Fq::conditional_assign(double_predicate, dbl_lambda_numerator, add_lambda_numerator);
98
99 const Fq add_lambda_denominator = other.x - x;
100 const Fq dbl_lambda_denominator = y + y;
101 Fq lambda_denominator = Fq::conditional_assign(double_predicate, dbl_lambda_denominator, add_lambda_denominator);
102 // If either inputs are points at infinity, we set lambda_denominator to be 1. This ensures we never trigger a
103 // divide by zero error.
104 // Note: if either inputs are points at infinity we will not use the result of this computation.
105 Fq safe_edgecase_denominator = Fq(1);
106 lambda_denominator = Fq::conditional_assign(
107 lhs_infinity || rhs_infinity || infinity_predicate, safe_edgecase_denominator, lambda_denominator);
108 const Fq lambda = Fq::div_without_denominator_check({ lambda_numerator }, lambda_denominator);
109
110 const Fq x3 = lambda.sqradd({ -other.x, -x });
111 const Fq y3 = lambda.madd(x - x3, { -y });
112
113 element result(x3, y3);
114 // if lhs infinity, return rhs
115 result.x = Fq::conditional_assign(lhs_infinity, other.x, result.x);
116 result.y = Fq::conditional_assign(lhs_infinity, other.y, result.y);
117 // if rhs infinity, return lhs
118 result.x = Fq::conditional_assign(rhs_infinity, x, result.x);
119 result.y = Fq::conditional_assign(rhs_infinity, y, result.y);
120
121 // is result point at infinity?
122 // yes = infinity_predicate && !lhs_infinity && !rhs_infinity
123 // yes = lhs_infinity && rhs_infinity
124 // n.b. can likely optimize this
125 bool_ct result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
126 if constexpr (IsUltraBuilder<C>) {
127 result_is_infinity.get_context()->update_used_witnesses(result_is_infinity.witness_index);
128 }
129 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
130 if constexpr (IsUltraBuilder<C>) {
131 result_is_infinity.get_context()->update_used_witnesses(result_is_infinity.witness_index);
132 }
133 result.set_point_at_infinity(result_is_infinity);
134
135 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
136 return result;
137}
138
146template <typename C, class Fq, class Fr, class G>
148{
149
150 const bool_ct is_infinity = is_point_at_infinity();
151 element result(*this);
152 const Fq zero = Fq::zero();
153 result.x = Fq::conditional_assign(is_infinity, zero, this->x);
154 result.y = Fq::conditional_assign(is_infinity, zero, this->y);
155 return result;
156}
157
158template <typename C, class Fq, class Fr, class G>
160{
161
162 // if x_coordinates match, lambda triggers a divide by zero error.
163 // Adding in `x_coordinates_match` ensures that lambda will always be well-formed
164 const bool_ct x_coordinates_match = other.x == x;
165 const bool_ct y_coordinates_match = (y == other.y);
166 const bool_ct infinity_predicate = (x_coordinates_match && y_coordinates_match);
167 const bool_ct double_predicate = (x_coordinates_match && !y_coordinates_match);
168 const bool_ct lhs_infinity = is_point_at_infinity();
169 const bool_ct rhs_infinity = other.is_point_at_infinity();
170
171 // Compute the gradient `lambda`. If we add, `lambda = (y2 - y1)/(x2 - x1)`, else `lambda = 3x1*x1/2y1
172 const Fq add_lambda_numerator = -other.y - y;
173 const Fq xx = x * x;
174 const Fq dbl_lambda_numerator = xx + xx + xx;
175 const Fq lambda_numerator = Fq::conditional_assign(double_predicate, dbl_lambda_numerator, add_lambda_numerator);
176
177 const Fq add_lambda_denominator = other.x - x;
178 const Fq dbl_lambda_denominator = y + y;
179 Fq lambda_denominator = Fq::conditional_assign(double_predicate, dbl_lambda_denominator, add_lambda_denominator);
180 // If either inputs are points at infinity, we set lambda_denominator to be 1. This ensures we never trigger a
181 // divide by zero error.
182 // (if either inputs are points at infinity we will not use the result of this computation)
183 Fq safe_edgecase_denominator = Fq(1);
184 lambda_denominator = Fq::conditional_assign(
185 lhs_infinity || rhs_infinity || infinity_predicate, safe_edgecase_denominator, lambda_denominator);
186 const Fq lambda = Fq::div_without_denominator_check({ lambda_numerator }, lambda_denominator);
187
188 const Fq x3 = lambda.sqradd({ -other.x, -x });
189 const Fq y3 = lambda.madd(x - x3, { -y });
190
191 element result(x3, y3);
192 // if lhs infinity, return rhs
193 result.x = Fq::conditional_assign(lhs_infinity, other.x, result.x);
194 result.y = Fq::conditional_assign(lhs_infinity, -other.y, result.y);
195 // if rhs infinity, return lhs
196 result.x = Fq::conditional_assign(rhs_infinity, x, result.x);
197 result.y = Fq::conditional_assign(rhs_infinity, y, result.y);
198
199 // is result point at infinity?
200 // yes = infinity_predicate && !lhs_infinity && !rhs_infinity
201 // yes = lhs_infinity && rhs_infinity
202 // n.b. can likely optimize this
203 bool_ct result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
204 if constexpr (IsUltraBuilder<C>) {
205 result_is_infinity.get_context()->update_used_witnesses(result_is_infinity.witness_index);
206 }
207 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
208 if constexpr (IsUltraBuilder<C>) {
209 result_is_infinity.get_context()->update_used_witnesses(result_is_infinity.witness_index);
210 }
211 result.set_point_at_infinity(result_is_infinity);
212 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
213 return result;
214}
215
216template <typename C, class Fq, class Fr, class G>
218{
219 other.x.assert_is_not_equal(x);
220 const Fq lambda = Fq::div_without_denominator_check({ other.y, -y }, (other.x - x));
221 const Fq x3 = lambda.sqradd({ -other.x, -x });
222 const Fq y3 = lambda.madd(x - x3, { -y });
223 return element(x3, y3);
224}
225
226template <typename C, class Fq, class Fr, class G>
228{
229
230 other.x.assert_is_not_equal(x);
231 const Fq lambda = Fq::div_without_denominator_check({ other.y, y }, (other.x - x));
232 const Fq x_3 = lambda.sqradd({ -other.x, -x });
233 const Fq y_3 = lambda.madd(x_3 - x, { -y });
234
235 return element(x_3, y_3);
236}
237
252// TODO(https://github.com/AztecProtocol/barretenberg/issues/657): This function is untested
253template <typename C, class Fq, class Fr, class G>
255{
256
257 // TODO(https://github.com/AztecProtocol/barretenberg/issues/971): This will fail when the two elements are the
258 // same even in the case of a valid circuit
259 other.x.assert_is_not_equal(x);
260
261 const Fq denominator = other.x - x;
262 const Fq x2x1 = -(other.x + x);
263
264 const Fq lambda1 = Fq::div_without_denominator_check({ other.y, -y }, denominator);
265 const Fq x_3 = lambda1.sqradd({ x2x1 });
266 const Fq y_3 = lambda1.madd(x - x_3, { -y });
267 const Fq lambda2 = Fq::div_without_denominator_check({ -other.y, -y }, denominator);
268 const Fq x_4 = lambda2.sqradd({ x2x1 });
269 const Fq y_4 = lambda2.madd(x - x_4, { -y });
270
271 return { element(x_3, y_3), element(x_4, y_4) };
272}
273
274template <typename C, class Fq, class Fr, class G> element<C, Fq, Fr, G> element<C, Fq, Fr, G>::dbl() const
275{
276
277 Fq two_x = x + x;
278 if constexpr (G::has_a) {
279 Fq a(get_context(), uint256_t(G::curve_a));
280 Fq neg_lambda = Fq::msub_div({ x }, { (two_x + x) }, (y + y), { a });
281 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
282 Fq y_3 = neg_lambda.madd(x_3 - x, { -y });
283 return element(x_3, y_3);
284 }
285 Fq neg_lambda = Fq::msub_div({ x }, { (two_x + x) }, (y + y), {});
286 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
287 Fq y_3 = neg_lambda.madd(x_3 - x, { -y });
288 element result = element(x_3, y_3);
289 result.set_point_at_infinity(is_point_at_infinity());
290 return result;
291}
292
311template <typename C, class Fq, class Fr, class G>
313 const element& p2)
314{
316 output.x1_prev = p1.x;
317 output.y1_prev = p1.y;
318
319 p1.x.assert_is_not_equal(p2.x);
320 const Fq lambda = Fq::div_without_denominator_check({ p2.y, -p1.y }, (p2.x - p1.x));
321
322 const Fq x3 = lambda.sqradd({ -p2.x, -p1.x });
323 output.x3_prev = x3;
324 output.lambda_prev = lambda;
325 return output;
326}
327
328template <typename C, class Fq, class Fr, class G>
330 const chain_add_accumulator& acc)
331{
332 // use `chain_add_start` to start an addition chain (i.e. if acc has a y-coordinate)
333 if (acc.is_element) {
334 return chain_add_start(p1, element(acc.x3_prev, acc.y3_prev));
335 }
336 // validate we can use incomplete addition formulae
337 p1.x.assert_is_not_equal(acc.x3_prev);
338
339 // lambda = (y2 - y1) / (x2 - x1)
340 // but we don't have y2!
341 // however, we do know that y2 = lambda_prev * (x1_prev - x2) - y1_prev
342 // => lambda * (x2 - x1) = lambda_prev * (x1_prev - x2) - y1_prev - y1
343 // => lambda * (x2 - x1) + lambda_prev * (x2 - x1_prev) + y1 + y1_pev = 0
344 // => lambda = lambda_prev * (x1_prev - x2) - y1_prev - y1 / (x2 - x1)
345 // => lambda = - (lambda_prev * (x2 - x1_prev) + y1_prev + y1) / (x2 - x1)
346
356 auto& x2 = acc.x3_prev;
357 const auto lambda = Fq::msub_div({ acc.lambda_prev }, { (x2 - acc.x1_prev) }, (x2 - p1.x), { acc.y1_prev, p1.y });
358 const auto x3 = lambda.sqradd({ -x2, -p1.x });
359
361 output.x3_prev = x3;
362 output.x1_prev = p1.x;
363 output.y1_prev = p1.y;
364 output.lambda_prev = lambda;
365
366 return output;
367}
368
372template <typename C, class Fq, class Fr, class G>
374{
375 if (acc.is_element) {
376 return element(acc.x3_prev, acc.y3_prev);
377 }
378 auto& x3 = acc.x3_prev;
379 auto& lambda = acc.lambda_prev;
380
381 Fq y3 = lambda.madd((acc.x1_prev - x3), { -acc.y1_prev });
382 return element(x3, y3);
383}
405// #################################
406// ### SCALAR MULTIPLICATION METHODS
407// #################################
425template <typename C, class Fq, class Fr, class G>
427{
428 other.x.assert_is_not_equal(x);
429 const Fq lambda_1 = Fq::div_without_denominator_check({ other.y - y }, (other.x - x));
430
431 const Fq x_3 = lambda_1.sqradd({ -other.x, -x });
432
433 const Fq minus_lambda_2 = lambda_1 + Fq::div_without_denominator_check({ y + y }, (x_3 - x));
434
435 const Fq x_4 = minus_lambda_2.sqradd({ -x, -x_3 });
436
437 const Fq y_4 = minus_lambda_2.madd(x_4 - x, { -y });
438 return element(x_4, y_4);
439}
440
461template <typename C, class Fq, class Fr, class G>
463{
464 if (to_add.is_element) {
465 throw_or_abort("An accumulator expected");
466 }
467 x.assert_is_not_equal(to_add.x3_prev);
468
469 // lambda = (y2 - y1) / (x2 - x1)
470 // but we don't have y2!
471 // however, we do know that y2 = lambda_prev * (x1_prev - x2) - y1_prev
472 // => lambda * (x2 - x1) = lambda_prev * (x1_prev - x2) - y1_prev - y1
473 // => lambda * (x2 - x1) + lambda_prev * (x2 - x1_prev) + y1 + y1_pev = 0
474 // => lambda = lambda_prev * (x1_prev - x2) - y1_prev - y1 / (x2 - x1)
475 // => lambda = - (lambda_prev * (x2 - x1_prev) + y1_prev + y1) / (x2 - x1)
476
477 auto& x2 = to_add.x3_prev;
478 const auto lambda =
479 Fq::msub_div({ to_add.lambda_prev }, { (x2 - to_add.x1_prev) }, (x2 - x), { to_add.y1_prev, y });
480 const auto x3 = lambda.sqradd({ -x2, -x });
481
482 const Fq minus_lambda_2 = lambda + Fq::div_without_denominator_check({ y + y }, (x3 - x));
483
484 const Fq x4 = minus_lambda_2.sqradd({ -x, -x3 });
485
486 const Fq y4 = minus_lambda_2.madd(x4 - x, { -y });
487 return element(x4, y4);
488}
489
504template <typename C, class Fq, class Fr, class G>
506{
507 const Fq two_x = x + x;
508 Fq x_1;
509 Fq minus_lambda_dbl;
510 if constexpr (G::has_a) {
511 Fq a(get_context(), uint256_t(G::curve_a));
512 minus_lambda_dbl = Fq::msub_div({ x }, { (two_x + x) }, (y + y), { a });
513 x_1 = minus_lambda_dbl.sqradd({ -(two_x) });
514 } else {
515 minus_lambda_dbl = Fq::msub_div({ x }, { (two_x + x) }, (y + y), {});
516 x_1 = minus_lambda_dbl.sqradd({ -(two_x) });
517 }
518
519 BB_ASSERT_GT(to_add.size(), 0);
520 to_add[0].x.assert_is_not_equal(x_1);
521
522 const Fq x_minus_x_1 = x - x_1;
523
524 const Fq lambda_1 = Fq::msub_div({ minus_lambda_dbl }, { x_minus_x_1 }, (x_1 - to_add[0].x), { to_add[0].y, y });
525
526 const Fq x_3 = lambda_1.sqradd({ -to_add[0].x, -x_1 });
527
528 const Fq half_minus_lambda_2_minus_lambda_1 =
529 Fq::msub_div({ minus_lambda_dbl }, { x_minus_x_1 }, (x_3 - x_1), { y });
530
531 const Fq minus_lambda_2_minus_lambda_1 = half_minus_lambda_2_minus_lambda_1 + half_minus_lambda_2_minus_lambda_1;
532 const Fq minus_lambda_2 = minus_lambda_2_minus_lambda_1 + lambda_1;
533
534 const Fq x_4 = minus_lambda_2.sqradd({ -x_1, -x_3 });
535
536 const Fq x_4_sub_x_1 = x_4 - x_1;
537
538 if (to_add.size() == 1) {
539 const Fq y_4 = Fq::dual_madd(minus_lambda_2, x_4_sub_x_1, minus_lambda_dbl, x_minus_x_1, { y });
540 return element(x_4, y_4);
541 }
542 to_add[1].x.assert_is_not_equal(to_add[0].x);
543
544 Fq minus_lambda_3 = Fq::msub_div(
545 { minus_lambda_dbl, minus_lambda_2 }, { x_minus_x_1, x_4_sub_x_1 }, (x_4 - to_add[1].x), { y, -(to_add[1].y) });
546
547 // X5 = L3.L3 - X4 - XB
548 const Fq x_5 = minus_lambda_3.sqradd({ -x_4, -to_add[1].x });
549
550 if (to_add.size() == 2) {
551 // Y5 = L3.(XB - X5) - YB
552 const Fq y_5 = minus_lambda_3.madd(x_5 - to_add[1].x, { -to_add[1].y });
553 return element(x_5, y_5);
554 }
555
556 Fq x_prev = x_5;
557 Fq minus_lambda_prev = minus_lambda_3;
558
559 for (size_t i = 2; i < to_add.size(); ++i) {
560
561 to_add[i].x.assert_is_not_equal(to_add[i - 1].x);
562 // Lambda = Yprev - Yadd[i] / Xprev - Xadd[i]
563 // = -Lprev.(Xprev - Xadd[i-1]) - Yadd[i - 1] - Yadd[i] / Xprev - Xadd[i]
564 const Fq minus_lambda = Fq::msub_div({ minus_lambda_prev },
565 { to_add[i - 1].x - x_prev },
566 (to_add[i].x - x_prev),
567 { to_add[i - 1].y, to_add[i].y });
568 // X = Lambda * Lambda - Xprev - Xadd[i]
569 const Fq x_out = minus_lambda.sqradd({ -x_prev, -to_add[i].x });
570
571 x_prev = x_out;
572 minus_lambda_prev = minus_lambda;
573 }
574 const Fq y_out = minus_lambda_prev.madd(x_prev - to_add[to_add.size() - 1].x, { -to_add[to_add.size() - 1].y });
575
576 return element(x_prev, y_out);
577}
578
598template <typename C, class Fq, class Fr, class G>
600 const std::vector<chain_add_accumulator>& add) const
601{
602 struct composite_y {
603 std::vector<Fq> mul_left;
604 std::vector<Fq> mul_right;
605 std::vector<Fq> add;
606 bool is_negative = false;
607 };
608
609 Fq previous_x = x;
610 composite_y previous_y{ std::vector<Fq>(), std::vector<Fq>(), std::vector<Fq>(), false };
611 for (size_t i = 0; i < add.size(); ++i) {
612 previous_x.assert_is_not_equal(add[i].x3_prev);
613
614 // composite_y add_y;
615 bool negate_add_y = (i > 0) && !previous_y.is_negative;
616 std::vector<Fq> lambda1_left;
617 std::vector<Fq> lambda1_right;
618 std::vector<Fq> lambda1_add;
619
620 if (i == 0) {
621 lambda1_add.emplace_back(-y);
622 } else {
623 lambda1_left = previous_y.mul_left;
624 lambda1_right = previous_y.mul_right;
625 lambda1_add = previous_y.add;
626 }
627
628 if (!add[i].is_element) {
629 lambda1_left.emplace_back(add[i].lambda_prev);
630 lambda1_right.emplace_back(negate_add_y ? add[i].x3_prev - add[i].x1_prev
631 : add[i].x1_prev - add[i].x3_prev);
632 lambda1_add.emplace_back(negate_add_y ? add[i].y1_prev : -add[i].y1_prev);
633 } else if (i > 0) {
634 lambda1_add.emplace_back(negate_add_y ? -add[i].y3_prev : add[i].y3_prev);
635 }
636 // if previous_y is negated then add stays positive
637 // if previous_y is positive then add stays negated
638 // | add.y is negated | previous_y is negated | output of msub_div is -lambda |
639 // | --- | --- | --- |
640 // | no | yes | yes |
641 // | yes | no | no |
642
643 Fq lambda1;
644 if (!add[i].is_element || i > 0) {
645 bool flip_lambda1_denominator = !negate_add_y;
646 Fq denominator = flip_lambda1_denominator ? previous_x - add[i].x3_prev : add[i].x3_prev - previous_x;
647 lambda1 = Fq::msub_div(lambda1_left, lambda1_right, denominator, lambda1_add);
648 } else {
649 lambda1 = Fq::div_without_denominator_check({ add[i].y3_prev - y }, (add[i].x3_prev - x));
650 }
651
652 Fq x_3 = lambda1.madd(lambda1, { -add[i].x3_prev, -previous_x });
653
654 // We can avoid computing y_4, instead substituting the expression `minus_lambda_2 * (x_4 - x) - y` where
655 // needed. This is cheaper, because we can evaluate two field multiplications (or a field multiplication + a
656 // field division) with only one non-native field reduction. E.g. evaluating (a * b) + (c * d) = e mod p only
657 // requires 1 quotient and remainder, which is the major cost of a non-native field multiplication
658 Fq lambda2;
659 if (i == 0) {
660 lambda2 = Fq::div_without_denominator_check({ y + y }, (previous_x - x_3)) - lambda1;
661 } else {
662 Fq l2_denominator = previous_y.is_negative ? previous_x - x_3 : x_3 - previous_x;
663 Fq partial_lambda2 =
664 Fq::msub_div(previous_y.mul_left, previous_y.mul_right, l2_denominator, previous_y.add);
665 partial_lambda2 = partial_lambda2 + partial_lambda2;
666 lambda2 = partial_lambda2 - lambda1;
667 }
668
669 Fq x_4 = lambda2.sqradd({ -x_3, -previous_x });
670 composite_y y_4;
671 if (i == 0) {
672 // We want to make sure that at the final iteration, `y_previous.is_negative = false`
673 // Each iteration flips the sign of y_previous.is_negative.
674 // i.e. whether we store y_4 or -y_4 depends on the number of points we have
675 bool num_points_even = ((add.size() & 0x01UL) == 0);
676 y_4.add.emplace_back(num_points_even ? y : -y);
677 y_4.mul_left.emplace_back(lambda2);
678 y_4.mul_right.emplace_back(num_points_even ? x_4 - previous_x : previous_x - x_4);
679 y_4.is_negative = num_points_even;
680 } else {
681 y_4.is_negative = !previous_y.is_negative;
682 y_4.mul_left.emplace_back(lambda2);
683 y_4.mul_right.emplace_back(previous_y.is_negative ? previous_x - x_4 : x_4 - previous_x);
684 // append terms in previous_y to y_4. We want to make sure the terms above are added into the start of y_4.
685 // This is to ensure they are cached correctly when
686 // `builder::evaluate_partial_non_native_field_multiplication` is called.
687 // (the 1st mul_left, mul_right elements will trigger builder::evaluate_non_native_field_multiplication
688 // when Fq::mult_madd is called - this term cannot be cached so we want to make sure it is unique)
689 std::copy(previous_y.mul_left.begin(), previous_y.mul_left.end(), std::back_inserter(y_4.mul_left));
690 std::copy(previous_y.mul_right.begin(), previous_y.mul_right.end(), std::back_inserter(y_4.mul_right));
691 std::copy(previous_y.add.begin(), previous_y.add.end(), std::back_inserter(y_4.add));
692 }
693 previous_x = x_4;
694 previous_y = y_4;
695 }
696 Fq x_out = previous_x;
697
698 ASSERT(!previous_y.is_negative);
699
700 Fq y_out = Fq::mult_madd(previous_y.mul_left, previous_y.mul_right, previous_y.add);
701 return element(x_out, y_out);
702}
703
741template <typename C, class Fq, class Fr, class G>
743 const size_t num_rounds)
744{
745 constexpr typename G::affine_element offset_generator =
746 get_precomputed_generators<G, "biggroup offset generator", 1>()[0];
747
748 const uint256_t offset_multiplier = uint256_t(1) << uint256_t(num_rounds - 1);
749
750 const typename G::affine_element offset_generator_end = typename G::element(offset_generator) * offset_multiplier;
751 return std::make_pair<element, element>(offset_generator, offset_generator_end);
752}
753
770template <typename C, class Fq, class Fr, class G>
772 const std::vector<Fr>& _scalars,
773 const size_t max_num_bits,
774 const bool with_edgecases)
775{
776 auto [points, scalars] = handle_points_at_infinity(_points, _scalars);
777 OriginTag tag{};
778 const auto empty_tag = OriginTag();
779
780 for (size_t i = 0; i < _points.size(); i++) {
781 tag = OriginTag(tag, OriginTag(_points[i].get_origin_tag(), _scalars[i].get_origin_tag()));
782 }
783 for (size_t i = 0; i < scalars.size(); i++) {
784 // If batch_mul actually performs batch multiplication on the points and scalars, subprocedures can do
785 // operations like addition or subtraction of points, which can trigger OriginTag security mechanisms even
786 // though the final result satisfies the security logic
787 // For example result = submitted_in_round_0 *challenge_from_round_0 +submitted_in_round_1 *
788 // challenge_in_round_1 will trigger it, because the addition of submitted_in_round_0 to submitted_in_round_1 is
789 // dangerous by itself. To avoid this, we remove the tags, merge them separately and set the result
790 // appropriately
791 points[i].set_origin_tag(empty_tag);
792 scalars[i].set_origin_tag(empty_tag);
793 }
794
795 // Perform goblinized batched mul if available; supported only for BN254
796 if (with_edgecases) {
797 std::tie(points, scalars) = mask_points(points, scalars);
798 }
799 const size_t num_points = points.size();
800 BB_ASSERT_EQ(scalars.size(), num_points);
801
802 batch_lookup_table point_table(points);
803 const size_t num_rounds = (max_num_bits == 0) ? Fr::modulus.get_msb() + 1 : max_num_bits;
804
806 for (size_t i = 0; i < num_points; ++i) {
807 naf_entries.emplace_back(compute_naf(scalars[i], max_num_bits));
808 }
809 const auto offset_generators = compute_offset_generators(num_rounds);
810 element accumulator =
811 element::chain_add_end(element::chain_add(offset_generators.first, point_table.get_chain_initial_entry()));
812
813 constexpr size_t num_rounds_per_iteration = 4;
814 size_t num_iterations = num_rounds / num_rounds_per_iteration;
815 num_iterations += ((num_iterations * num_rounds_per_iteration) == num_rounds) ? 0 : 1;
816 const size_t num_rounds_per_final_iteration = (num_rounds - 1) - ((num_iterations - 1) * num_rounds_per_iteration);
817 for (size_t i = 0; i < num_iterations; ++i) {
818
819 std::vector<bool_ct> nafs(num_points);
821 const size_t inner_num_rounds =
822 (i != num_iterations - 1) ? num_rounds_per_iteration : num_rounds_per_final_iteration;
823 for (size_t j = 0; j < inner_num_rounds; ++j) {
824 for (size_t k = 0; k < num_points; ++k) {
825 nafs[k] = (naf_entries[k][i * num_rounds_per_iteration + j + 1]);
826 }
827 to_add.emplace_back(point_table.get_chain_add_accumulator(nafs));
828 }
829 accumulator = accumulator.multiple_montgomery_ladder(to_add);
830 }
831 for (size_t i = 0; i < num_points; ++i) {
832 element skew = accumulator - points[i];
833 Fq out_x = accumulator.x.conditional_select(skew.x, naf_entries[i][num_rounds]);
834 Fq out_y = accumulator.y.conditional_select(skew.y, naf_entries[i][num_rounds]);
835 accumulator = element(out_x, out_y);
836 }
837 accumulator = accumulator - offset_generators.second;
838
839 accumulator.set_origin_tag(tag);
840 return accumulator;
841}
845template <typename C, class Fq, class Fr, class G>
847{
848 // Use `scalar_mul` method without specifying the length of `scalar`.
849 return scalar_mul(scalar);
850}
851
852template <typename C, class Fq, class Fr, class G>
861element<C, Fq, Fr, G> element<C, Fq, Fr, G>::scalar_mul(const Fr& scalar, const size_t max_num_bits) const
862{
863 BB_ASSERT_EQ(max_num_bits % 2, 0U);
887 OriginTag tag{};
888 tag = OriginTag(tag, OriginTag(this->get_origin_tag(), scalar.get_origin_tag()));
889
890 bool_ct is_point_at_infinity = this->is_point_at_infinity();
891
892 const size_t num_rounds = (max_num_bits == 0) ? Fr::modulus.get_msb() + 1 : max_num_bits;
893
894 element result;
895 if (max_num_bits != 0) {
896 // The case of short scalars
897 result = element::bn254_endo_batch_mul({}, {}, { *this }, { scalar }, num_rounds);
898 } else {
899 // The case of arbitrary length scalars
900 result = element::bn254_endo_batch_mul({ *this }, { scalar }, {}, {}, num_rounds);
901 };
902
903 // Handle point at infinity
904 result.x = Fq::conditional_assign(is_point_at_infinity, x, result.x);
905 result.y = Fq::conditional_assign(is_point_at_infinity, y, result.y);
906
907 result.set_point_at_infinity(is_point_at_infinity);
908
909 // Propagate the origin tag
910 result.set_origin_tag(tag);
911
912 return result;
913}
914} // namespace bb::stdlib::element_default
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:87
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:59
#define ASSERT(expression,...)
Definition assert.hpp:49
Implements boolean logic in-circuit.
Definition bool.hpp:59
Builder * get_context() const
Definition bool.hpp:117
uint32_t witness_index
Definition bool.hpp:133
element multiple_montgomery_ladder(const std::vector< chain_add_accumulator > &to_add) const
Perform repeated iterations of the montgomery ladder algorithm.
void set_origin_tag(OriginTag tag) const
Definition biggroup.hpp:376
void set_point_at_infinity(const bool_ct &is_infinity)
Definition biggroup.hpp:373
FF a
#define G(r, i, a, b, c, d)
Definition blake2s.cpp:116
constexpr T get_msb(const T in)
Definition get_msb.hpp:47
constexpr std::span< const typename Group::affine_element > get_precomputed_generators()
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit p...
static constexpr uint256_t modulus
BB_INLINE constexpr field add(const field &other) const noexcept
static constexpr field zero()
element::chain_add_accumulator get_chain_add_accumulator(std::vector< bool_ct > &naf_entries) const
Definition biggroup.hpp:741
void throw_or_abort(std::string const &err)
bb::fq Fq