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"
16
18
19template <typename C, class Fq, class Fr, class G>
21 : _x()
22 , _y()
23 , _is_infinity()
24{}
25
26template <typename C, class Fq, class Fr, class G>
27element<C, Fq, Fr, G>::element(const typename G::affine_element& input)
28 : _x(nullptr, input.x)
29 , _y(nullptr, input.y)
30 , _is_infinity(nullptr, input.is_point_at_infinity())
31{}
32
33template <typename C, class Fq, class Fr, class G>
34element<C, Fq, Fr, G>::element(const Fq& x_in, const Fq& y_in, const bool assert_on_curve)
35 : _x(x_in)
36 , _y(y_in)
37 , _is_infinity(_x.get_context() ? _x.get_context() : _y.get_context(), false)
38{
39 if (assert_on_curve) {
41 }
42}
43
44template <typename C, class Fq, class Fr, class G>
45element<C, Fq, Fr, G>::element(const Fq& x_in, const Fq& y_in, const bool_ct& is_infinity, const bool assert_on_curve)
46 : _x(x_in)
47 , _y(y_in)
48 , _is_infinity(is_infinity)
49{
50 if (assert_on_curve) {
52 }
53}
54
55template <typename C, class Fq, class Fr, class G>
57 : _x(other._x)
58 , _y(other._y)
59 , _is_infinity(other.is_point_at_infinity())
60{}
61
62template <typename C, class Fq, class Fr, class G>
64 : _x(other._x)
65 , _y(other._y)
66 , _is_infinity(other.is_point_at_infinity())
67{}
68
69template <typename C, class Fq, class Fr, class G>
71{
72 if (&other == this) {
73 return *this;
74 }
75 _x = other._x;
76 _y = other._y;
77 _is_infinity = other.is_point_at_infinity();
78 return *this;
79}
80
81template <typename C, class Fq, class Fr, class G>
83{
84 if (&other == this) {
85 return *this;
86 }
87 _x = other._x;
88 _y = other._y;
89 _is_infinity = other.is_point_at_infinity();
90 return *this;
91}
92
93template <typename C, class Fq, class Fr, class G>
95{
96 // Adding in `x_coordinates_match` ensures that lambda will always be well-formed
97 // Our curve has the form y² = x³ + ax + b (or y² = x³ + b when a = 0).
98 // If (x₁, y₁), (x₂, y₂) have x₁ == x₂, the generic formula for lambda has a division by 0.
99 // Then y₁ == y₂ (i.e. we are doubling) or y₂ == -y₁ (the sum is infinity).
100 // These cases have special addition formulae. The following booleans allow us to handle these cases uniformly.
101 const bool_ct x_coordinates_match = other._x == _x;
102 const bool_ct y_coordinates_match = (_y == other._y);
103 const bool_ct infinity_predicate = (x_coordinates_match && !y_coordinates_match);
104 const bool_ct double_predicate = (x_coordinates_match && y_coordinates_match);
105 const bool_ct lhs_infinity = is_point_at_infinity();
106 const bool_ct rhs_infinity = other.is_point_at_infinity();
107 const bool_ct has_infinity_input = lhs_infinity || rhs_infinity;
108
109 // NOTE: For valid points on the curve, specifically for bn254 or secp256k1 or secp256r1, y = 0 cannot occur.
110 // For points not on the curve, having y = 0 will lead to a failure while performing the division below.
111 // We could enforce in circuit that y = 0 results in point at infinity, or that y != 0 always.
112 // However, this would be an unnecessary constraint for valid points on the curve.
113 // So we perform a native check here to catch any accidental misuse of this function.
114 const typename G::Fq y_value = uint256_t(_y.get_value());
115 BB_ASSERT_EQ((y_value == 0), false, "Attempting to add a point with y = 0, not allowed.");
116
117 // Compute the gradient λ. If we add, λ = (y₂ - y₁)/(x₂ - x₁)
118 // For doubling: λ = (3x₁² + a)/(2y₁) if curve has 'a', else λ = 3x₁²/(2y₁)
119 const Fq add_lambda_numerator = other._y - _y;
120 const Fq xx = _x * _x;
121 Fq dbl_lambda_numerator = xx + xx + xx; // 3x²
122 if constexpr (G::has_a) {
123 // Curve equation: y² = x³ + ax + b
124 // Doubling formula numerator: 3x² + a
125 const Fq a(get_context(), uint256_t(G::curve_a));
126 dbl_lambda_numerator = dbl_lambda_numerator + a;
127 }
128 const Fq lambda_numerator = Fq::conditional_assign(double_predicate, dbl_lambda_numerator, add_lambda_numerator);
129
130 const Fq add_lambda_denominator = other._x - _x;
131 const Fq dbl_lambda_denominator = _y + _y;
132 Fq lambda_denominator = Fq::conditional_assign(double_predicate, dbl_lambda_denominator, add_lambda_denominator);
133
134 // If either input is a point at infinity, or if the result would be infinity, set lambda_denominator to 1
135 // to prevent division by zero. Cases where result is infinity: x₁ == x₂ but y₁ != y₂ (points are inverses)
136 const bool_ct safe_denominator_needed = has_infinity_input || infinity_predicate;
137 lambda_denominator = Fq::conditional_assign(safe_denominator_needed, Fq(1), lambda_denominator);
138 const Fq lambda = Fq::div_without_denominator_check({ lambda_numerator }, lambda_denominator);
139
140 // Compute resulting point coordinates: x₃ = λ² - x₁ - x₂, y₃ = λ(x₁ - x₃) - y₁
141 const Fq x3 = lambda.sqradd({ -other._x, -_x });
142 const Fq y3 = lambda.madd(_x - x3, { -_y });
143 element result(x3, y3, /*assert_on_curve=*/false);
144
145 // if lhs infinity, return rhs
146 result._x = Fq::conditional_assign(lhs_infinity, other._x, result._x);
147 result._y = Fq::conditional_assign(lhs_infinity, other._y, result._y);
148 // if rhs infinity, return lhs
149 result._x = Fq::conditional_assign(rhs_infinity, _x, result._x);
150 result._y = Fq::conditional_assign(rhs_infinity, _y, result._y);
151
152 // Determine if result is point at infinity:
153 // - If x₁ == x₂ and y₁ == -y₂ (i.e., points are inverses), result is ∞
154 // - If both inputs are ∞, result is ∞
155 bool_ct result_is_infinity = (infinity_predicate && !has_infinity_input) || (lhs_infinity && rhs_infinity);
156 result.set_point_at_infinity(result_is_infinity, /* add_to_used_witnesses */ true);
157
158 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
159 return result;
160}
161
169template <typename C, class Fq, class Fr, class G>
171{
172
173 const bool_ct is_infinity = is_point_at_infinity();
174 element result(*this);
175 const Fq zero = Fq::zero();
176 result._x = Fq::conditional_assign(is_infinity, zero, this->_x);
177 result._y = Fq::conditional_assign(is_infinity, zero, this->_y);
178 return result;
179}
180
181template <typename C, class Fq, class Fr, class G>
183{
184 // Adding in `x_coordinates_match` ensures that lambda will always be well-formed
185 // Our curve has the form y² = x³ + ax + b (or y² = x³ + b when a = 0).
186 // If (x₁, y₁), (x₂, y₂) have x₁ == x₂, the generic formula for lambda has a division by 0.
187 // For subtraction P₁ - P₂ = P₁ + (-P₂), where -P₂ = (x₂, -y₂):
188 // - If y₁ == -y₂ (i.e., x₁ == x₂ and y₁ == -y₂), this becomes doubling: P₁ + P₁
189 // - If y₁ == y₂ (i.e., x₁ == x₂ and y₁ == y₂), result is infinity: P₁ - P₁ = ∞
190 // These cases have special addition formulae. The following booleans allow us to handle these cases uniformly.
191 const bool_ct x_coordinates_match = other._x == _x;
192 const bool_ct y_coordinates_match = (_y == other._y);
193 const bool_ct infinity_predicate = (x_coordinates_match && y_coordinates_match);
194 const bool_ct double_predicate = (x_coordinates_match && !y_coordinates_match);
195 const bool_ct lhs_infinity = is_point_at_infinity();
196 const bool_ct rhs_infinity = other.is_point_at_infinity();
197 const bool_ct has_infinity_input = lhs_infinity || rhs_infinity;
198
199 // Compute the gradient λ. For subtraction, λ = (-y₂ - y₁)/(x₂ - x₁)
200 // For doubling: λ = (3x₁² + a)/(2y₁) if curve has 'a', else λ = 3x₁²/(2y₁)
201 const Fq add_lambda_numerator = -other._y - _y;
202 const Fq xx = _x * _x;
203 Fq dbl_lambda_numerator = xx + xx + xx; // 3x²
204 if constexpr (G::has_a) {
205 // Curve equation: y² = x³ + ax + b
206 // Doubling formula numerator: 3x² + a
207 const Fq a(get_context(), uint256_t(G::curve_a));
208 dbl_lambda_numerator = dbl_lambda_numerator + a;
209 }
210 const Fq lambda_numerator = Fq::conditional_assign(double_predicate, dbl_lambda_numerator, add_lambda_numerator);
211
212 const Fq add_lambda_denominator = other._x - _x;
213 const Fq dbl_lambda_denominator = _y + _y;
214 Fq lambda_denominator = Fq::conditional_assign(double_predicate, dbl_lambda_denominator, add_lambda_denominator);
215
216 // If either input is a point at infinity, or if the result would be infinity (x₁ == x₂ and y₁ == y₂),
217 // set lambda_denominator to 1 to prevent division by zero. The lambda value won't be used in these cases.
218 lambda_denominator = Fq::conditional_assign(has_infinity_input || infinity_predicate, Fq(1), lambda_denominator);
219 const Fq lambda = Fq::div_without_denominator_check({ lambda_numerator }, lambda_denominator);
220
221 // Compute resulting point coordinates: x₃ = λ² - x₁ - x₂, y₃ = λ(x₁ - x₃) - y₁
222 const Fq x3 = lambda.sqradd({ -other._x, -_x });
223 const Fq y3 = lambda.madd(_x - x3, { -_y });
224 element result(x3, y3, /*assert_on_curve=*/false);
225
226 // if lhs infinity, return -rhs (negated rhs point)
227 result._x = Fq::conditional_assign(lhs_infinity, other._x, result._x);
228 result._y = Fq::conditional_assign(lhs_infinity, -other._y, result._y);
229 // if rhs infinity, return lhs
230 result._x = Fq::conditional_assign(rhs_infinity, _x, result._x);
231 result._y = Fq::conditional_assign(rhs_infinity, _y, result._y);
232
233 // Determine if result is point at infinity:
234 // - If x₁ == x₂ and y₁ == y₂ (i.e., P₁ - P₁), result is ∞
235 // - If both inputs are ∞, result is ∞
236 bool_ct result_is_infinity = (infinity_predicate && !has_infinity_input) || (lhs_infinity && rhs_infinity);
237
238 result.set_point_at_infinity(result_is_infinity, /* add_to_used_witnesses */ true);
239 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
240 return result;
241}
242
243template <typename C, class Fq, class Fr, class G>
245{
246 other._x.assert_is_not_equal(_x);
247 const Fq lambda = Fq::div_without_denominator_check({ other._y, -_y }, (other._x - _x));
248 const Fq x3 = lambda.sqradd({ -other._x, -_x });
249 const Fq y3 = lambda.madd(_x - x3, { -_y });
250 return element(x3, y3, /*assert_on_curve=*/false);
251}
252
253template <typename C, class Fq, class Fr, class G>
255{
256
257 other._x.assert_is_not_equal(_x);
258 const Fq lambda = Fq::div_without_denominator_check({ other._y, _y }, (other._x - _x));
259 const Fq x_3 = lambda.sqradd({ -other._x, -_x });
260 const Fq y_3 = lambda.madd(x_3 - _x, { -_y });
261
262 return element(x_3, y_3, /*assert_on_curve=*/false);
263}
264
279template <typename C, class Fq, class Fr, class G>
281{
282 // validate we can use incomplete addition formulae
283 other._x.assert_is_not_equal(_x);
284
285 const Fq denominator = other._x - _x;
286 const Fq x2x1 = -(other._x + _x);
287
288 const Fq lambda1 = Fq::div_without_denominator_check({ other._y, -_y }, denominator);
289 const Fq x_3 = lambda1.sqradd({ x2x1 });
290 const Fq y_3 = lambda1.madd(_x - x_3, { -_y });
291 const Fq lambda2 = Fq::div_without_denominator_check({ -other._y, -_y }, denominator);
292 const Fq x_4 = lambda2.sqradd({ x2x1 });
293 const Fq y_4 = lambda2.madd(_x - x_4, { -_y });
294
295 return { element(x_3, y_3, /*assert_on_curve=*/false), element(x_4, y_4, /*assert_on_curve=*/false) };
296}
297
298template <typename C, class Fq, class Fr, class G> element<C, Fq, Fr, G> element<C, Fq, Fr, G>::dbl() const
299{
300 // NOTE: For valid points on the curve, specifically for bn254 or secp256k1 or secp256r1, y = 0 cannot occur.
301 // For points not on the curve, having y = 0 will lead to a failure while performing the division below.
302 // We could enforce in circuit that y = 0 results in point at infinity, or that y != 0 always.
303 // However, this would be an unnecessary constraint for valid points on the curve.
304 // So we perform a native check here to catch any accidental misuse of this function.
305 const typename G::Fq y_value = uint256_t(_y.get_value());
306 BB_ASSERT_EQ((y_value == 0), false, "Attempting to dbl a point with y = 0, not allowed.");
307
308 Fq two_x = _x + _x;
309 if constexpr (G::has_a) {
310 // Curve equation: y² = x³ + ax + b
311 Fq a(get_context(), uint256_t(G::curve_a));
312
313 // Compute neg_lambda = -λ = -(3x² + a) / (2y)
314 // msub_div computes: -(Σᵢ aᵢ·bᵢ + Σⱼ cⱼ) / d = -(x·(3x) + a) / (2y) = -(3x² + a) / (2y)
315 Fq neg_lambda = Fq::msub_div({ _x }, { (two_x + _x) }, (_y + _y), { a }, /*enable_divisor_nz_check*/ false);
316
317 // Compute x₃ = λ² - 2x
318 // Since neg_lambda = -λ, we have: (-λ)² - 2x = λ² - 2x
319 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
320
321 // Compute y₃ = λ(x - x₃) - y
322 // Using neg_lambda = -λ: (-λ)(x₃ - x) + (-y) = -λ(x₃ - x) - y = λ(x - x₃) - y
323 Fq y_3 = neg_lambda.madd(x_3 - _x, { -_y });
324
325 element result(x_3, y_3, /*assert_on_curve=*/false);
326 result.set_point_at_infinity(is_point_at_infinity(), /* add_to_used_witnesses */ true);
327 return result;
328 }
329
330 // Curve equation when a = 0: y² = x³ + b
331 // Compute neg_lambda = -λ = -3x² / (2y)
332 // msub_div computes: -(Σᵢ aᵢ·bᵢ) / d = -(x·(3x)) / (2y) = -3x² / (2y)
333 Fq neg_lambda = Fq::msub_div({ _x }, { (two_x + _x) }, (_y + _y), {}, /*enable_divisor_nz_check*/ false);
334
335 // Compute x₃ = λ² - 2x
336 // Since neg_lambda = -λ, we have: (-λ)² - 2x = λ² - 2x
337 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
338
339 // Compute y₃ = λ(x - x₃) - y
340 // Using neg_lambda = -λ: (-λ)(x₃ - x) + (-y) = -λ(x₃ - x) - y = λ(x - x₃) - y
341 Fq y_3 = neg_lambda.madd(x_3 - _x, { -_y });
342
343 element result = element(x_3, y_3, /*assert_on_curve=*/false);
344 result.set_point_at_infinity(is_point_at_infinity(), /* add_to_used_witnesses */ true);
345 return result;
346}
347
354template <typename C, class Fq, class Fr, class G>
356 const element& p2)
357{
359 output.x1_prev = p1._x;
360 output.y1_prev = p1._y;
361
362 // Require x₁ ≠ x₂ for incomplete addition formula
363 p1._x.assert_is_not_equal(p2._x);
364
365 // Compute λ = (y₂ - y₁)/(x₂ - x₁)
366 const Fq lambda = Fq::div_without_denominator_check({ p2._y, -p1._y }, (p2._x - p1._x));
367
368 // Compute x₃ = λ² - x₁ - x₂
369 const Fq x3 = lambda.sqradd({ -p2._x, -p1._x });
370 output.x3_prev = x3;
371 output.lambda_prev = lambda;
372 return output;
373}
374
392template <typename C, class Fq, class Fr, class G>
394 const chain_add_accumulator& acc)
395{
396 // If accumulator has a full y-coordinate, use chain_add_start instead
397 if (acc.is_full_element) {
398 return chain_add_start(p1, element(acc.x3_prev, acc.y3_prev, /*assert_on_curve=*/false));
399 }
400
401 // Require x₁ ≠ x₂ for incomplete addition formula
402 p1._x.assert_is_not_equal(acc.x3_prev);
403
404 // Compute λ = (y₂ - y₁)/(x₂ - x₁), but we don't have y₂!
405 // We know that y₂ = lambda_prev * (x1_prev - x₂) - y1_prev from the previous addition.
406 //
407 // Derivation:
408 // λ(x₂ - x₁) = y₂ - y₁
409 // λ(x₂ - x₁) = lambda_prev * (x1_prev - x₂) - y1_prev - y₁
410 // λ(x₂ - x₁) = -lambda_prev * (x₂ - x1_prev) - y1_prev - y₁
411 // λ = -(lambda_prev * (x₂ - x1_prev) + y1_prev + y₁) / (x₂ - x₁)
412 //
413 auto& x2 = acc.x3_prev;
414 const auto lambda = Fq::msub_div({ acc.lambda_prev },
415 { (x2 - acc.x1_prev) },
416 (x2 - p1._x),
417 { acc.y1_prev, p1._y },
418 /*enable_divisor_nz_check*/ false); // Divisor is non-zero as x₂ ≠ x₁ is enforced
419
420 // Compute x₃ = λ² - x₂ - x₁
421 const auto x3 = lambda.sqradd({ -x2, -p1._x });
422
423 // Update the accumulator
425 output.x3_prev = x3;
426 output.x1_prev = p1._x;
427 output.y1_prev = p1._y;
428 output.lambda_prev = lambda;
429
430 return output;
431}
432
438template <typename C, class Fq, class Fr, class G>
440{
441 // If accumulator already has a full y-coordinate, return it directly
442 if (acc.is_full_element) {
443 return element(acc.x3_prev, acc.y3_prev, /*assert_on_curve=*/false);
444 }
445
446 // Compute y₃ = λ(x₁ - x₃) - y₁
447 // where λ, x₁, y₁ are from the previous addition stored in the accumulator
448 auto& x3 = acc.x3_prev;
449 auto& lambda = acc.lambda_prev;
450
451 Fq y3 = lambda.madd((acc.x1_prev - x3), { -acc.y1_prev });
452 return element(x3, y3, /*assert_on_curve=*/false);
453}
454
474template <typename C, class Fq, class Fr, class G>
476 const std::vector<chain_add_accumulator>& add) const
477{
478 struct composite_y {
479 std::vector<Fq> mul_left;
480 std::vector<Fq> mul_right;
481 std::vector<Fq> add;
482 bool is_negative = false;
483 };
484
485 // Handle edge case of empty input
486 if (add.empty()) {
487 return *this;
488 }
489
490 // Let A = (x, y) and P = (x₁, y₁)
491 // For the first point P, we want to compute: (2A + P) = (A + P) + A
492 // We first need to check if x ≠ x₁.
493 x().assert_is_not_equal(add[0].x3_prev);
494
495 // Compute λ₁ for computing the first addition: (A + P)
496 Fq lambda1;
497 if (!add[0].is_full_element) {
498 // Case 1: P is an accumulator (i.e., it lacks a y-coordinate)
499 // λ₁ = (y - y₁) / (x - x₁)
500 // = -(y₁ - y) / (x - x₁)
501 // = -(λ₁_ₚᵣₑᵥ * (x₁_ₚᵣₑᵥ - x₁) - y₁_ₚᵣₑᵥ - y) / (x - x₁)
502 //
503 // NOTE: msub_div computes -(∑ᵢ aᵢ * bᵢ + ∑ⱼcⱼ) / d
504 lambda1 = Fq::msub_div({ add[0].lambda_prev }, // numerator left multiplicands: λ₁_ₚᵣₑᵥ
505 { add[0].x1_prev - add[0].x3_prev }, // numerator right multiplicands: (x₁_ₚᵣₑᵥ - x₁)
506 (x() - add[0].x3_prev), // denominator: (x - x₁)
507 { -add[0].y1_prev, -y() }, // numerator additions: -y₁_ₚᵣₑᵥ - y
508 /*enable_divisor_nz_check*/ false); // divisor check is not needed as x ≠ x₁ is enforced
509 } else {
510 // Case 2: P is a full element (i.e., it has a y-coordinate)
511 // λ₁ = (y - y₁) / (x - x₁)
512 //
513 lambda1 = Fq::div_without_denominator_check({ y() - add[0].y3_prev }, (x() - add[0].x3_prev));
514 }
515
516 // Using λ₁, compute x₃ for (A + P):
517 // x₃ = λ₁.λ₁ - x₁ - x
518 Fq x_3 = lambda1.madd(lambda1, { -add[0].x3_prev, -x() });
519
520 // Compute λ₂ for the addition (A + P) + A:
521 // λ₂ = (y - y₃) / (x - x₃)
522 // = (y - (λ₁ * (x - x₃) - y)) / (x - x₃) (substituting y₃)
523 // = (2y) / (x - x₃) - λ₁
524 //
525 x().assert_is_not_equal(x_3);
526 Fq lambda2 = Fq::div_without_denominator_check({ y() + y() }, (x() - x_3)) - lambda1;
527
528 // Using λ₂, compute x₄ for the final result:
529 // x₄ = λ₂.λ₂ - x₃ - x
530 Fq x_4 = lambda2.sqradd({ -x_3, -x() });
531
532 // Compute y₄ for the final result:
533 // y₄ = λ₂ * (x - x₄) - y
534 //
535 // However, we don't actually compute y₄ here. Instead, we build a "composite" y value that contains
536 // the components needed to compute y₄ later. This is done to avoid the explicit multiplication here.
537 //
538 // We store the result as either y₄ or -y₄, depending on whether the number of points added
539 // is even or odd. This sign adjustment simplifies the handling of subsequent additions in the loop below.
540 // +y₄ = λ₂ * (x - x₄) - y
541 // -y₄ = λ₂ * (x₄ - x) + y
542 const bool num_points_even = ((add.size() & 1ULL) == 0);
543 composite_y previous_y;
544 previous_y.add.emplace_back(num_points_even ? y() : -y());
545 previous_y.mul_left.emplace_back(lambda2);
546 previous_y.mul_right.emplace_back(num_points_even ? x_4 - x() : x() - x_4);
547 previous_y.is_negative = num_points_even;
548
549 // Handle remaining iterations (i > 0) in a loop
550 Fq previous_x = x_4;
551 for (size_t i = 1; i < add.size(); ++i) {
552 // Let x = previous_x, y = previous_y
553 // Let P = (xᵢ, yᵢ) be the next point to add (represented by add[i])
554 // Ensure x-coordinates are distinct: x ≠ xᵢ
555 previous_x.assert_is_not_equal(add[i].x3_prev);
556
557 // Determine sign adjustment based on previous y's sign
558 // If the previous y was positive, we need to negate the y-component from add[i]
559 const bool negate_add_y = !previous_y.is_negative;
560
561 // Build λ₁ numerator components from previous composite y and current accumulator
562 std::vector<Fq> lambda1_left = previous_y.mul_left;
563 std::vector<Fq> lambda1_right = previous_y.mul_right;
564 std::vector<Fq> lambda1_add = previous_y.add;
565
566 if (!add[i].is_full_element) {
567 // Case 1: add[i] is an accumulator (lacks y-coordinate)
568 // λ₁ = (y - yᵢ) / (x - xᵢ)
569 // = -(yᵢ - y) / (x - xᵢ)
570 // = -(λᵢ_ₚᵣₑᵥ * (xᵢ_ₚᵣₑᵥ - xᵢ) - yᵢ_ₚᵣₑᵥ - y) / (x - xᵢ)
571 //
572 // If (previous) y is stored as positive, we compute λ₁ as:
573 // λ₁ = -(λᵢ_ₚᵣₑᵥ * (xᵢ - xᵢ_ₚᵣₑᵥ) + yᵢ_ₚᵣₑᵥ + y) / (xᵢ - x)
574 //
575 lambda1_left.emplace_back(add[i].lambda_prev);
576 lambda1_right.emplace_back(negate_add_y ? add[i].x3_prev - add[i].x1_prev
577 : add[i].x1_prev - add[i].x3_prev);
578 lambda1_add.emplace_back(negate_add_y ? add[i].y1_prev : -add[i].y1_prev);
579 } else {
580 // Case 2: add[i] is a full element (has y-coordinate)
581 // λ₁ = (yᵢ - y) / (xᵢ - x)
582 //
583 // If previous y is positive, we compute λ₁ as:
584 // λ₁ = -(y - yᵢ) / (xᵢ - x)
585 //
586 lambda1_add.emplace_back(negate_add_y ? -add[i].y3_prev : add[i].y3_prev);
587 }
588
589 // Compute λ₁
590 Fq denominator = negate_add_y ? add[i].x3_prev - previous_x : previous_x - add[i].x3_prev;
591 Fq lambda1 =
592 Fq::msub_div(lambda1_left, lambda1_right, denominator, lambda1_add, /*enable_divisor_nz_check*/ false);
593
594 // Using λ₁, compute x₃ for (previous + P):
595 // x₃ = λ₁.λ₁ - xᵢ - x
596 // y₃ = λ₁ * (x - x₃) - y (we don't compute this explicitly)
597 Fq x_3 = lambda1.madd(lambda1, { -add[i].x3_prev, -previous_x });
598
599 // Compute λ₂ using previous composite y
600 // λ₂ = (y - y₃) / (x - x₃)
601 // = (y - (λ₁ * (x - x₃) - y)) / (x - x₃) (substituting y₃)
602 // = (2y) / (x - x₃) - λ₁
603 // = -2(y / (x₃ - x)) - λ₁
604 //
605 previous_x.assert_is_not_equal(x_3);
606 Fq l2_denominator = previous_y.is_negative ? previous_x - x_3 : x_3 - previous_x;
607 Fq partial_lambda2 = Fq::msub_div(previous_y.mul_left,
608 previous_y.mul_right,
609 l2_denominator,
610 previous_y.add,
611 /*enable_divisor_nz_check*/ false);
612 partial_lambda2 = partial_lambda2 + partial_lambda2;
613 lambda2 = partial_lambda2 - lambda1;
614
615 // Using λ₂, compute x₄ for the final result of this iteration:
616 // x₄ = λ₂.λ₂ - x₃ - x
617 x_4 = lambda2.sqradd({ -x_3, -previous_x });
618
619 // Build composite y for this iteration
620 // y₄ = λ₂ * (x - x₄) - y
621 // However, we don't actually compute y₄ explicitly, we rather store components to compute it later.
622 // We store the result as either y₄ or -y₄, depending on the sign of previous_y.
623 // +y₄ = λ₂ * (x - x₄) - y
624 // -y₄ = λ₂ * (x₄ - x) + y
625 composite_y y_4;
626 y_4.is_negative = !previous_y.is_negative;
627 y_4.mul_left.emplace_back(lambda2);
628 y_4.mul_right.emplace_back(previous_y.is_negative ? previous_x - x_4 : x_4 - previous_x);
629
630 // Append terms from previous_y to y_4. We want to make sure the terms above are added into the start
631 // of y_4. This is to ensure they are cached correctly when
632 // `builder::evaluate_partial_non_native_field_multiplication` is called. (the 1st mul_left, mul_right elements
633 // will trigger builder::evaluate_non_native_field_multiplication
634 // when Fq::mult_madd is called - this term cannot be cached so we want to make sure it is unique)
635 std::copy(previous_y.mul_left.begin(), previous_y.mul_left.end(), std::back_inserter(y_4.mul_left));
636 std::copy(previous_y.mul_right.begin(), previous_y.mul_right.end(), std::back_inserter(y_4.mul_right));
637 std::copy(previous_y.add.begin(), previous_y.add.end(), std::back_inserter(y_4.add));
638
639 previous_x = x_4;
640 previous_y = y_4;
641 }
642
643 Fq x_out = previous_x;
644
645 BB_ASSERT(!previous_y.is_negative);
646
647 Fq y_out = Fq::mult_madd(previous_y.mul_left, previous_y.mul_right, previous_y.add);
648 return element(x_out, y_out, /*assert_on_curve=*/false);
649}
650
688template <typename C, class Fq, class Fr, class G>
690 const size_t num_rounds)
691{
692 constexpr typename G::affine_element offset_generator =
693 get_precomputed_generators<G, "biggroup offset generator", 1>()[0];
694
695 const uint256_t offset_multiplier = uint256_t(1) << uint256_t(num_rounds - 1);
696
697 const typename G::affine_element offset_generator_end = typename G::element(offset_generator) * offset_multiplier;
698 return std::make_pair<element, element>(offset_generator, offset_generator_end);
699}
700
701template <typename C, class Fq, class Fr, class G>
703 const std::vector<Fr>& scalars,
704 const size_t max_num_bits)
705{
706 // Sanity checks
707 BB_ASSERT_GT(points.size(), 0ULL, "process_strauss_msm: points cannot be empty");
708 BB_ASSERT_EQ(points.size(), scalars.size(), "process_strauss_msm: points and scalars size mismatch");
709
710 // Check that all scalars are in range
711 for (const auto& scalar : scalars) {
712 const size_t num_scalar_bits = static_cast<size_t>(uint512_t(scalar.get_value()).get_msb()) + 1ULL;
713 BB_ASSERT_LTE(num_scalar_bits, max_num_bits, "process_strauss_msm: scalar out of range");
714 }
715
716 // Constant parameters
717 const size_t num_rounds = max_num_bits;
718 const size_t msm_size = scalars.size();
719
720 // Compute ROM lookup table for points. Example if we have 3 points G1, G2, G3:
721 // ┌───────┬─────────────────┐
722 // │ Index │ Point │
723 // ├───────┼─────────────────┤
724 // │ 0 │ G1 + G2 + G3 │
725 // │ 1 │ G1 + G2 - G3 │
726 // │ 2 │ G1 - G2 + G3 │
727 // │ 3 │ G1 - G2 - G3 │
728 // │ 4 │ -G1 + G2 + G3 │
729 // │ 5 │ -G1 + G2 - G3 │
730 // │ 6 │ -G1 - G2 + G3 │
731 // │ 7 │ -G1 - G2 - G3 │
732 // └───────┴─────────────────┘
733 batch_lookup_table point_table(points);
734
735 // Compute NAF representations of scalars
737 for (size_t i = 0; i < msm_size; ++i) {
738 naf_entries.emplace_back(compute_naf(scalars[i], num_rounds));
739 }
740
741 // We choose a deterministic offset generator based on the number of rounds.
742 // We compute both the initial and final offset generators: G_offset, 2ⁿ⁻¹ * G_offset.
743 const auto [offset_generator_start, offset_generator_end] = compute_offset_generators(num_rounds);
744
745 // Initialize accumulator with offset generator + first NAF column.
746 element accumulator =
747 element::chain_add_end(element::chain_add(offset_generator_start, point_table.get_chain_initial_entry()));
748
749 // Process 4 NAF entries per iteration (for the remaining (num_rounds - 1) rounds)
750 constexpr size_t num_rounds_per_iteration = 4;
751 const size_t num_iterations = numeric::ceil_div((num_rounds - 1), num_rounds_per_iteration);
752 const size_t num_rounds_per_final_iteration = (num_rounds - 1) - ((num_iterations - 1) * num_rounds_per_iteration);
753
754 for (size_t i = 0; i < num_iterations; ++i) {
756 const size_t inner_num_rounds =
757 (i != num_iterations - 1) ? num_rounds_per_iteration : num_rounds_per_final_iteration;
758 for (size_t j = 0; j < inner_num_rounds; ++j) {
759 // Gather the NAF columns for this iteration
760 std::vector<bool_ct> nafs(msm_size);
761 for (size_t k = 0; k < msm_size; ++k) {
762 nafs[k] = (naf_entries[k][(i * num_rounds_per_iteration) + j + 1]);
763 }
764 to_add.emplace_back(point_table.get_chain_add_accumulator(nafs));
765 }
766
767 // Once we have looked-up all points from the four NAF columns, we update the accumulator as:
768 // accumulator = 2.(2.(2.(2.accumulator + to_add[0]) + to_add[1]) + to_add[2]) + to_add[3]
769 // = 2⁴.accumulator + 2³.to_add[0] + 2².to_add[1] + 2¹.to_add[2] + to_add[3]
770 accumulator = accumulator.multiple_montgomery_ladder(to_add);
771 }
772
773 // Subtract the skew factors (if any)
774 for (size_t i = 0; i < msm_size; ++i) {
775 element skew = accumulator - points[i];
776 accumulator = accumulator.conditional_select(skew, naf_entries[i][num_rounds]);
777 }
778
779 // Subtract the scaled offset generator
780 accumulator = accumulator - offset_generator_end;
781
782 return accumulator;
783}
784
818template <typename C, class Fq, class Fr, class G>
820 const std::vector<Fr>& _scalars,
821 const size_t max_num_bits,
822 const bool with_edgecases,
823 const Fr& masking_scalar)
824{
825 // Sanity check input sizes
826 BB_ASSERT_GT(_points.size(), 0ULL, "biggroup batch_mul: no points provided for batch multiplication");
827 BB_ASSERT_EQ(_points.size(), _scalars.size(), "biggroup batch_mul: points and scalars size mismatch");
828
829 // Replace (∞, scalar) pairs by the pair (G, 0).
830 auto [points, scalars] = handle_points_at_infinity(_points, _scalars);
831
832 BB_ASSERT_LTE(points.size(), _points.size());
833 BB_ASSERT_EQ(points.size(),
834 scalars.size(),
835 "biggroup batch_mul: points and scalars size mismatch after handling points at infinity");
836
837 // If batch_mul actually performs batch multiplication on the points and scalars, subprocedures can do
838 // operations like addition or subtraction of points, which can trigger OriginTag security mechanisms
839 // even though the final result satisfies the security logic. For example
840 // result = submitted_in_round_0 * challenge_from_round_0 + submitted_in_round_1 * challenge_in_round_1
841 // will trigger it, because the addition of submitted_in_round_0 to submitted_in_round_1 is dangerous by itself.
842 // To avoid this, we remove the tags, merge them separately and set the result appropriately
843 OriginTag tag{};
844 const auto empty_tag = OriginTag();
845 for (size_t i = 0; i < _points.size(); i++) {
846 tag = OriginTag(tag, OriginTag(_points[i].get_origin_tag(), _scalars[i].get_origin_tag()));
847 }
848 for (size_t i = 0; i < scalars.size(); i++) {
849 points[i].set_origin_tag(empty_tag);
850 scalars[i].set_origin_tag(empty_tag);
851 }
852
853 // Accumulate constant-constant pairs out of circuit
854 bool has_constant_terms = false;
855 typename G::element constant_accumulator = G::element::infinity();
856 std::vector<element> new_points;
857 std::vector<Fr> new_scalars;
858 for (size_t i = 0; i < points.size(); ++i) {
859 if (points[i].is_constant() && scalars[i].is_constant()) {
860 const auto& point_value = typename G::element(points[i].get_value());
861 const auto& scalar_value = typename G::Fr(scalars[i].get_value());
862 constant_accumulator += (point_value * scalar_value);
863 has_constant_terms = true;
864 } else {
865 new_points.emplace_back(points[i]);
866 new_scalars.emplace_back(scalars[i]);
867 }
868 }
869 points = new_points;
870 scalars = new_scalars;
871
872 // If with_edgecases is false, masking_scalar must be constant and equal to 1 (as it is unused).
873 if (!with_edgecases) {
875 masking_scalar.is_constant() && masking_scalar.get_value() == 1,
876 true,
877 "biggroup batch_mul: masking_scalar must be constant (and equal to 1) when with_edgecases is false");
878 }
879
880 if (with_edgecases) {
881 // If points are linearly dependent, we randomise them using a masking scalar.
882 // We do this to ensure that the x-coordinates of the points are all distinct. This is required
883 // while creating the ROM lookup table with the points.
884 std::tie(points, scalars) = mask_points(points, scalars, masking_scalar);
885 }
886
888 points.size(), scalars.size(), "biggroup batch_mul: points and scalars size mismatch after handling edgecases");
889
890 // Separate out zero scalars and corresponding points (because NAF(0) = NAF(modulus) which is 254 bits long)
891 // Also add the last point and scalar to big_points and big_scalars (because its a 254-bit scalar)
892 // We do this only if max_num_bits != 0 (i.e. we are not forced to use 254 bits anyway)
893 const size_t original_size = scalars.size();
894 std::vector<Fr> big_scalars;
895 std::vector<element> big_points;
896 std::vector<Fr> small_scalars;
897 std::vector<element> small_points;
898 for (size_t i = 0; i < original_size; ++i) {
899 if (max_num_bits == 0) {
900 big_points.emplace_back(points[i]);
901 big_scalars.emplace_back(scalars[i]);
902 } else {
903 const bool is_last_scalar_big = ((i == original_size - 1) && with_edgecases);
904 if (scalars[i].get_value() == 0 || is_last_scalar_big) {
905 big_points.emplace_back(points[i]);
906 big_scalars.emplace_back(scalars[i]);
907 } else {
908 small_points.emplace_back(points[i]);
909 small_scalars.emplace_back(scalars[i]);
910 }
911 }
912 }
913
914 BB_ASSERT_EQ(original_size,
915 small_points.size() + big_points.size(),
916 "biggroup batch_mul: points size mismatch after separating big scalars");
917 BB_ASSERT_EQ(big_points.size(),
918 big_scalars.size(),
919 "biggroup batch_mul: big points and scalars size mismatch after separating big scalars");
920 BB_ASSERT_EQ(small_points.size(),
921 small_scalars.size(),
922 "biggroup batch_mul: small points and scalars size mismatch after separating big scalars");
923
924 const size_t max_num_bits_in_field = Fr::modulus.get_msb() + 1;
925
926 element accumulator;
927 bool accumulator_initialized = false;
928
929 // Check if we'll need to process any witness points
930
931 // Initialize accumulator with constant terms if they exist, OR if there are no remaining points
932 // (to handle the case where all points were filtered out by handle_points_at_infinity)
933 const bool has_no_points = big_points.empty() && small_points.empty();
934 if (has_constant_terms || has_no_points) {
935 accumulator = element(constant_accumulator);
936 accumulator_initialized = true;
937 }
938
939 if (!big_points.empty()) {
940 // Process big scalars separately
941 element big_result = element::process_strauss_msm_rounds(big_points, big_scalars, max_num_bits_in_field);
942 accumulator = accumulator_initialized ? accumulator + big_result : big_result;
943 accumulator_initialized = true;
944 }
945
946 if (!small_points.empty()) {
947 // Process small scalars
948 const size_t effective_max_num_bits = (max_num_bits == 0) ? max_num_bits_in_field : max_num_bits;
949 element small_result = element::process_strauss_msm_rounds(small_points, small_scalars, effective_max_num_bits);
950 accumulator = accumulator_initialized ? accumulator + small_result : small_result;
951 accumulator_initialized = true;
952 }
953
954 accumulator.set_origin_tag(tag);
955 return accumulator;
956}
960template <typename C, class Fq, class Fr, class G>
962{
963 // Use `scalar_mul` method without specifying the length of `scalar`.
964 return scalar_mul(scalar);
965}
966
967template <typename C, class Fq, class Fr, class G>
976element<C, Fq, Fr, G> element<C, Fq, Fr, G>::scalar_mul(const Fr& scalar, const size_t max_num_bits) const
977{
1001 return element::batch_mul({ *this }, { scalar }, max_num_bits, /*with_edgecases=*/false);
1002}
1003} // namespace bb::stdlib::element_default
#define BB_ASSERT(expression,...)
Definition assert.hpp:67
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:107
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:77
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:152
constexpr uint64_t get_msb() const
constexpr uint64_t get_msb() const
Definition uintx.hpp:69
Implements boolean logic in-circuit.
Definition bool.hpp:59
element multiple_montgomery_ladder(const std::vector< chain_add_accumulator > &to_add) const
Perform repeated iterations of the montgomery ladder algorithm.
void validate_on_curve(std::string const &msg="biggroup::validate_on_curve") const
Check that the point is on the curve.
Definition biggroup.hpp:114
void set_origin_tag(OriginTag tag) const
Definition biggroup.hpp:405
void set_point_at_infinity(const bool_ct &is_infinity, const bool &add_to_used_witnesses=false)
Definition biggroup.hpp:396
element conditional_select(const element &other, const bool_ct &predicate) const
Selects this if predicate is false, other if predicate is true.
Definition biggroup.hpp:237
FF a
#define G(r, i, a, b, c, d)
Definition blake2s.cpp:116
constexpr T ceil_div(const T &numerator, const T &denominator)
Computes the ceiling of the division of two integral types.
Definition general.hpp:23
uintx< uint256_t > uint512_t
Definition uintx.hpp:307
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
static constexpr field zero()
element::chain_add_accumulator get_chain_add_accumulator(std::vector< bool_ct > &naf_entries) const
Definition biggroup.hpp:807
curve::BN254::BaseField Fq