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: Complete, auditors: [Suyash], commit: 553c5eb82901955c638b943065acd3e47fc918c0}
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
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
40template <typename C, class Fq, class Fr, class G>
41element<C, Fq, Fr, G>::element(const Fq& x_in, const Fq& y_in, const bool assert_on_curve)
42 : _x(x_in)
43 , _y(y_in)
44{
45 // Detect infinity: point is at infinity iff both coordinates are zero.
46 // We sum all 8 binary basis limbs (4 from x, 4 from y) and check if the sum is zero.
47 // This works because: (1) after reduction, each limb is non-negative and range-constrained,
48 // so sum=0 iff all limbs=0; (2) max sum is 8 * 2^68 ≈ 2^71 << n ≈ 2^254, so no native wraparound.
49 field_ct limb_sum = 0;
50 for (size_t i = 0; i < Fq::NUM_LIMBS; ++i) {
51 limb_sum += _x.get_limb(i).element;
52 }
53 for (size_t i = 0; i < Fq::NUM_LIMBS; ++i) {
54 limb_sum += _y.get_limb(i).element;
55 }
56 _is_infinity = limb_sum.is_zero();
57
58 // Validate on-curve if requested
59 if (assert_on_curve) {
60 [[maybe_unused]] Fq _ = validate_on_curve();
61 }
62}
63
70template <typename C, class Fq, class Fr, class G>
72 const Fq& y_in,
73 const stdlib::bool_t<C>& is_infinity,
74 const bool assert_on_curve)
75 : _x(x_in)
76 , _y(y_in)
77 , _is_infinity(is_infinity.normalize())
78{
79 BB_ASSERT(!(_x.is_constant() && _y.is_constant() && !_is_infinity.is_constant()),
80 "biggroup: constant coordinates with non-constant infinity flag");
81 if (assert_on_curve) {
83 }
84}
85
86template <typename C, class Fq, class Fr, class G>
88 : _x(other._x)
89 , _y(other._y)
90 , _is_infinity(other.is_point_at_infinity())
91{}
92
93template <typename C, class Fq, class Fr, class G>
95 : _x(other._x)
96 , _y(other._y)
97 , _is_infinity(other.is_point_at_infinity())
98{}
99
100template <typename C, class Fq, class Fr, class G>
102{
103 if (&other == this) {
104 return *this;
105 }
106 _x = other._x;
107 _y = other._y;
108 _is_infinity = other.is_point_at_infinity();
109 return *this;
110}
111
112template <typename C, class Fq, class Fr, class G>
114{
115 if (&other == this) {
116 return *this;
117 }
118 _x = other._x;
119 _y = other._y;
120 _is_infinity = other.is_point_at_infinity();
121 return *this;
122}
123
131template <typename C, class Fq, class Fr, class G>
133{
134 // Adding in `x_coordinates_match` ensures that lambda will always be well-formed
135 // Our curve has the form y² = x³ + ax + b (or y² = x³ + b when a = 0).
136 // If (x₁, y₁), (x₂, y₂) have x₁ == x₂, the generic formula for lambda has a division by 0.
137 // Then y₁ == y₂ (i.e. we are doubling) or y₂ == -y₁ (the sum is infinity).
138 // These cases have special addition formulae. The following booleans allow us to handle these cases uniformly.
139 const bool_ct x_coordinates_match = other._x == _x;
140 const bool_ct y_coordinates_match = (_y == other._y);
141 const bool_ct infinity_predicate = (x_coordinates_match && !y_coordinates_match);
142 const bool_ct double_predicate = (x_coordinates_match && y_coordinates_match);
143 const bool_ct lhs_infinity = is_point_at_infinity();
144 const bool_ct rhs_infinity = other.is_point_at_infinity();
145 const bool_ct has_infinity_input = lhs_infinity || rhs_infinity;
146
147 // NOTE: For valid points on the curve, specifically for bn254 or secp256k1 or secp256r1, y = 0 cannot occur.
148 // For points not on the curve, having y = 0 will lead to a failure while performing the division below.
149 // We could enforce in circuit that y = 0 results in point at infinity, or that y != 0 always.
150 // However, this would be an unnecessary constraint for valid points on the curve.
151 // So we perform a native check here to catch any accidental misuse of this function.
152 // Exception: Points at infinity use the canonical (0, 0) representation, so skip the check for them.
153 if (!lhs_infinity.get_value()) {
154 const typename G::Fq y_value = uint256_t(_y.get_value());
155 BB_ASSERT_EQ((y_value == 0), false, "Attempting to add a point with y = 0, not allowed.");
156 }
157 if (!rhs_infinity.get_value()) {
158 const typename G::Fq other_y_value = uint256_t(other._y.get_value());
159 BB_ASSERT_EQ((other_y_value == 0), false, "Attempting to add a point with y = 0, not allowed.");
160 }
161
162 // Compute the gradient λ. If we add, λ = (y₂ - y₁)/(x₂ - x₁)
163 // For doubling: λ = (3x₁² + a)/(2y₁) if curve has 'a', else λ = 3x₁²/(2y₁)
164 const Fq add_lambda_numerator = other._y - _y;
165 const Fq xx = _x * _x;
166 Fq dbl_lambda_numerator = xx + xx + xx; // 3x²
167 if constexpr (G::has_a) {
168 // Curve equation: y² = x³ + ax + b
169 // Doubling formula numerator: 3x² + a
170 const Fq a(get_context(), uint256_t(G::curve_a));
171 dbl_lambda_numerator = dbl_lambda_numerator + a;
172 }
173 const Fq lambda_numerator = Fq::conditional_assign(double_predicate, dbl_lambda_numerator, add_lambda_numerator);
174
175 const Fq add_lambda_denominator = other._x - _x;
176 const Fq dbl_lambda_denominator = _y + _y;
177 Fq lambda_denominator = Fq::conditional_assign(double_predicate, dbl_lambda_denominator, add_lambda_denominator);
178
179 // If either input is a point at infinity, or if the result would be infinity, set lambda_denominator to 1
180 // to prevent division by zero. Cases where result is infinity: x₁ == x₂ but y₁ != y₂ (points are inverses)
181 const bool_ct safe_denominator_needed = has_infinity_input || infinity_predicate;
182 lambda_denominator = Fq::conditional_assign(safe_denominator_needed, Fq(1), lambda_denominator);
183
184 // Compute λ = numerator / denominator
185 // We enforce that denominator is not zero to protect against soundness issues.
186 const Fq lambda = lambda_numerator / lambda_denominator;
187
188 // Compute resulting point coordinates: x₃ = λ² - x₁ - x₂, y₃ = λ(x₁ - x₃) - y₁
189 Fq x3 = lambda.sqradd({ -other._x, -_x });
190 Fq y3 = lambda.madd(_x - x3, { -_y });
191
192 // if lhs infinity, return rhs
193 x3 = Fq::conditional_assign(lhs_infinity, other._x, x3);
194 y3 = Fq::conditional_assign(lhs_infinity, other._y, y3);
195 // if rhs infinity, return lhs
196 x3 = Fq::conditional_assign(rhs_infinity, _x, x3);
197 y3 = Fq::conditional_assign(rhs_infinity, _y, y3);
198
199 // Determine if result is point at infinity:
200 // - If x₁ == x₂ and y₁ == -y₂ (i.e., points are inverses), result is ∞
201 // - If both inputs are ∞, result is ∞
202 bool_ct result_is_infinity = (infinity_predicate && !has_infinity_input) || (lhs_infinity && rhs_infinity);
203 mark_witness_as_used(field_t<C>(result_is_infinity));
204
205 element result(x3, y3, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
206 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
207 return result;
208}
209
216template <typename C, class Fq, class Fr, class G>
218{
219 element result = add_internal(other);
220 return result.get_standard_form();
221}
222
230template <typename C, class Fq, class Fr, class G>
232{
233
234 const bool_ct is_infinity = is_point_at_infinity();
235
236 element result(*this);
237 const Fq zero = Fq(get_context(), 0);
238 result._x = Fq::conditional_assign(is_infinity, zero, this->_x);
239 result._y = Fq::conditional_assign(is_infinity, zero, this->_y);
240 return result;
241}
242
250template <typename C, class Fq, class Fr, class G>
252{
253 // Adding in `x_coordinates_match` ensures that lambda will always be well-formed
254 // Our curve has the form y² = x³ + ax + b (or y² = x³ + b when a = 0).
255 // If (x₁, y₁), (x₂, y₂) have x₁ == x₂, the generic formula for lambda has a division by 0.
256 // For subtraction P₁ - P₂ = P₁ + (-P₂), where -P₂ = (x₂, -y₂):
257 // - If y₁ == -y₂ (i.e., x₁ == x₂ and y₁ == -y₂), this becomes doubling: P₁ + P₁
258 // - If y₁ == y₂ (i.e., x₁ == x₂ and y₁ == y₂), result is infinity: P₁ - P₁ = ∞
259 // These cases have special addition formulae. The following booleans allow us to handle these cases uniformly.
260 const bool_ct x_coordinates_match = other._x == _x;
261 const bool_ct y_coordinates_match = (_y == other._y);
262 const bool_ct infinity_predicate = (x_coordinates_match && y_coordinates_match);
263 const bool_ct double_predicate = (x_coordinates_match && !y_coordinates_match);
264 const bool_ct lhs_infinity = is_point_at_infinity();
265 const bool_ct rhs_infinity = other.is_point_at_infinity();
266 const bool_ct has_infinity_input = lhs_infinity || rhs_infinity;
267
268 // Compute the gradient λ. For subtraction, λ = (-y₂ - y₁)/(x₂ - x₁)
269 // For doubling: λ = (3x₁² + a)/(2y₁) if curve has 'a', else λ = 3x₁²/(2y₁)
270 const Fq add_lambda_numerator = -other._y - _y;
271 const Fq xx = _x * _x;
272 Fq dbl_lambda_numerator = xx + xx + xx; // 3x²
273 if constexpr (G::has_a) {
274 // Curve equation: y² = x³ + ax + b
275 // Doubling formula numerator: 3x² + a
276 const Fq a(get_context(), uint256_t(G::curve_a));
277 dbl_lambda_numerator = dbl_lambda_numerator + a;
278 }
279 const Fq lambda_numerator = Fq::conditional_assign(double_predicate, dbl_lambda_numerator, add_lambda_numerator);
280
281 const Fq add_lambda_denominator = other._x - _x;
282 const Fq dbl_lambda_denominator = _y + _y;
283 Fq lambda_denominator = Fq::conditional_assign(double_predicate, dbl_lambda_denominator, add_lambda_denominator);
284
285 // If either input is a point at infinity, or if the result would be infinity (x₁ == x₂ and y₁ == y₂),
286 // set lambda_denominator to 1 to prevent division by zero. The lambda value won't be used in these cases.
287 // Defense in depth: also guard when doubling with y = 0 (denominator 2y would be zero).
288 const bool_ct safe_denominator_needed = has_infinity_input || infinity_predicate;
289 lambda_denominator = Fq::conditional_assign(safe_denominator_needed, Fq(1), lambda_denominator);
290
291 // Now compute lambda = numerator / denominator
292 // We enforce that the denominator is not zero (in division operator), so this division is safe.
293 const Fq lambda = lambda_numerator / lambda_denominator;
294
295 // Compute resulting point coordinates: x₃ = λ² - x₁ - x₂, y₃ = λ(x₁ - x₃) - y₁
296 Fq x3 = lambda.sqradd({ -other._x, -_x });
297 Fq y3 = lambda.madd(_x - x3, { -_y });
298
299 // if lhs infinity, return -rhs (negated rhs point)
300 x3 = Fq::conditional_assign(lhs_infinity, other._x, x3);
301 y3 = Fq::conditional_assign(lhs_infinity, -other._y, y3);
302 // if rhs infinity, return lhs
303 x3 = Fq::conditional_assign(rhs_infinity, _x, x3);
304 y3 = Fq::conditional_assign(rhs_infinity, _y, y3);
305
306 // Determine if result is point at infinity:
307 // - If x₁ == x₂ and y₁ == y₂ (i.e., P₁ - P₁), result is ∞
308 // - If both inputs are ∞, result is ∞
309 bool_ct result_is_infinity = (infinity_predicate && !has_infinity_input) || (lhs_infinity && rhs_infinity);
310 mark_witness_as_used(field_t<C>(result_is_infinity));
311
312 element result(x3, y3, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
313 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
314 return result;
315}
316
323template <typename C, class Fq, class Fr, class G>
325{
326 element result = subtract_internal(other);
327 return result.get_standard_form();
328}
329template <typename C, class Fq, class Fr, class G>
331{
332 other._x.assert_is_not_equal(_x);
333 const Fq lambda = Fq::div_without_denominator_check({ other._y, -_y }, (other._x - _x));
334 const Fq x3 = lambda.sqradd({ -other._x, -_x });
335 const Fq y3 = lambda.madd(_x - x3, { -_y });
336 // Use 4-arg constructor with is_infinity=false (checked operations assume valid, non-infinity points).
337 // This avoids the expensive bigfield equality checks in the 2-arg constructor's infinity auto-detection.
338 return element(x3, y3, bool_ct(x3.get_context(), false), /*assert_on_curve=*/false);
339}
340
341template <typename C, class Fq, class Fr, class G>
343{
344
345 other._x.assert_is_not_equal(_x);
346 const Fq lambda = Fq::div_without_denominator_check({ other._y, _y }, (other._x - _x));
347 const Fq x_3 = lambda.sqradd({ -other._x, -_x });
348 const Fq y_3 = lambda.madd(x_3 - _x, { -_y });
349
350 // Use 4-arg constructor with is_infinity=false (checked operations assume valid, non-infinity points).
351 return element(x_3, y_3, bool_ct(x_3.get_context(), false), /*assert_on_curve=*/false);
352}
353
368template <typename C, class Fq, class Fr, class G>
370{
371 // validate we can use incomplete addition formulae
372 other._x.assert_is_not_equal(_x);
373
374 const Fq denominator = other._x - _x;
375 const Fq x2x1 = -(other._x + _x);
376
377 const Fq lambda1 = Fq::div_without_denominator_check({ other._y, -_y }, denominator);
378 const Fq x_3 = lambda1.sqradd({ x2x1 });
379 const Fq y_3 = lambda1.madd(_x - x_3, { -_y });
380 const Fq lambda2 = Fq::div_without_denominator_check({ -other._y, -_y }, denominator);
381 const Fq x_4 = lambda2.sqradd({ x2x1 });
382 const Fq y_4 = lambda2.madd(_x - x_4, { -_y });
383
384 // Use 4-arg constructor with is_infinity=false (checked operations assume valid, non-infinity points).
385 bool_ct not_infinity(x_3.get_context(), false);
386 return { element(x_3, y_3, not_infinity, /*assert_on_curve=*/false),
387 element(x_4, y_4, not_infinity, /*assert_on_curve=*/false) };
388}
389
396template <typename C, class Fq, class Fr, class G> element<C, Fq, Fr, G> element<C, Fq, Fr, G>::dbl_internal() const
397{
398 const bool_ct is_infinity = is_point_at_infinity();
399
400 // NOTE: For valid points on the curve, specifically for bn254 or secp256k1 or secp256r1, y = 0 cannot occur.
401 // For points not on the curve, having y = 0 will lead to a failure while performing the division below.
402 // We could enforce in circuit that y = 0 results in point at infinity, or that y != 0 always.
403 // However, this would be an unnecessary constraint for valid points on the curve.
404 // So we perform a native check here to catch any accidental misuse of this function.
405 // Exception: Points at infinity use the canonical (0, 0) representation, so skip the check for them.
406 if (!is_infinity.get_value()) {
407 const typename G::Fq y_value = uint256_t(_y.get_value());
408 BB_ASSERT_EQ((y_value == 0), false, "Attempting to dbl a point with y = 0, not allowed.");
409 }
410
411 // If the input is a point at infinity, use a safe denominator (1) to prevent division by zero.
412 // The result will be infinity anyway, so the computed coordinates don't matter.
413 const Fq two_y = _y + _y;
414 Fq denominator = Fq::conditional_assign(is_infinity, Fq(get_context(), 1), two_y);
415
416 Fq two_x = _x + _x;
417 if constexpr (G::has_a) {
418 // Curve equation: y² = x³ + ax + b
419 Fq a(get_context(), uint256_t(G::curve_a));
420
421 // Compute neg_lambda = -λ = -(3x² + a) / (2y)
422 // msub_div computes: -(Σᵢ aᵢ·bᵢ + Σⱼ cⱼ) / d = -(x·(3x) + a) / (2y) = -(3x² + a) / (2y)
423 // We enforce the denominator is not zero to protect against soundness issues.
424 Fq neg_lambda = Fq::msub_div({ _x }, { (two_x + _x) }, denominator, { a }, /*enable_divisor_nz_check*/ true);
425
426 // Compute x₃ = λ² - 2x
427 // Since neg_lambda = -λ, we have: (-λ)² - 2x = λ² - 2x
428 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
429
430 // Compute y₃ = λ(x - x₃) - y
431 // Using neg_lambda = -λ: (-λ)(x₃ - x) + (-y) = -λ(x₃ - x) - y = λ(x - x₃) - y
432 Fq y_3 = neg_lambda.madd(x_3 - _x, { -_y });
433
434 mark_witness_as_used(field_t<C>(is_infinity));
435 return element(x_3, y_3, /*is_infinity=*/is_infinity, /*assert_on_curve=*/false);
436 }
437
438 // Curve equation when a = 0: y² = x³ + b
439 // Compute neg_lambda = -λ = -3x² / (2y)
440 // msub_div computes: -(Σᵢ aᵢ·bᵢ) / d = -(x·(3x)) / (2y) = -3x² / (2y)
441 // We enforce the denominator is not zero to protect against soundness issues.
442 Fq neg_lambda = Fq::msub_div({ _x }, { (two_x + _x) }, denominator, {}, /*enable_divisor_nz_check*/ true);
443
444 // Compute x₃ = λ² - 2x
445 // Since neg_lambda = -λ, we have: (-λ)² - 2x = λ² - 2x
446 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
447
448 // Compute y₃ = λ(x - x₃) - y
449 // Using neg_lambda = -λ: (-λ)(x₃ - x) + (-y) = -λ(x₃ - x) - y = λ(x - x₃) - y
450 Fq y_3 = neg_lambda.madd(x_3 - _x, { -_y });
451
452 mark_witness_as_used(field_t<C>(is_infinity));
453 return element(x_3, y_3, /*is_infinity=*/is_infinity, /*assert_on_curve=*/false);
454}
455
461template <typename C, class Fq, class Fr, class G> element<C, Fq, Fr, G> element<C, Fq, Fr, G>::dbl() const
462{
463 element result = dbl_internal();
464 return result.get_standard_form();
465}
466
473template <typename C, class Fq, class Fr, class G>
475 const element& p2)
476{
478 output.x1_prev = p1._x;
479 output.y1_prev = p1._y;
480
481 // Require x₁ ≠ x₂ for incomplete addition formula
482 p1._x.assert_is_not_equal(p2._x);
483
484 // Compute λ = (y₂ - y₁)/(x₂ - x₁)
485 const Fq lambda = Fq::div_without_denominator_check({ p2._y, -p1._y }, (p2._x - p1._x));
486
487 // Compute x₃ = λ² - x₁ - x₂
488 const Fq x3 = lambda.sqradd({ -p2._x, -p1._x });
489 output.x3_prev = x3;
490 output.lambda_prev = lambda;
491 return output;
492}
493
511template <typename C, class Fq, class Fr, class G>
513 const chain_add_accumulator& acc)
514{
515 // If accumulator has a full y-coordinate, use chain_add_start instead
516 if (acc.is_full_element) {
517 // Use 4-arg constructor with is_infinity=false (chain operations assume valid, non-infinity points).
518 return chain_add_start(
519 p1,
520 element(acc.x3_prev, acc.y3_prev, bool_ct(acc.x3_prev.get_context(), false), /*assert_on_curve=*/false));
521 }
522
523 // Require x₁ ≠ x₂ for incomplete addition formula
524 p1._x.assert_is_not_equal(acc.x3_prev);
525
526 // Compute λ = (y₂ - y₁)/(x₂ - x₁), but we don't have y₂!
527 // We know that y₂ = lambda_prev * (x1_prev - x₂) - y1_prev from the previous addition.
528 //
529 // Derivation:
530 // λ(x₂ - x₁) = y₂ - y₁
531 // λ(x₂ - x₁) = lambda_prev * (x1_prev - x₂) - y1_prev - y₁
532 // λ(x₂ - x₁) = -lambda_prev * (x₂ - x1_prev) - y1_prev - y₁
533 // λ = -(lambda_prev * (x₂ - x1_prev) + y1_prev + y₁) / (x₂ - x₁)
534 //
535 auto& x2 = acc.x3_prev;
536 const auto lambda = Fq::msub_div({ acc.lambda_prev },
537 { (x2 - acc.x1_prev) },
538 (x2 - p1._x),
539 { acc.y1_prev, p1._y },
540 /*enable_divisor_nz_check*/ false); // Divisor is non-zero as x₂ ≠ x₁ is enforced
541
542 // Compute x₃ = λ² - x₂ - x₁
543 const auto x3 = lambda.sqradd({ -x2, -p1._x });
544
545 // Update the accumulator
547 output.x3_prev = x3;
548 output.x1_prev = p1._x;
549 output.y1_prev = p1._y;
550 output.lambda_prev = lambda;
551
552 return output;
553}
554
560template <typename C, class Fq, class Fr, class G>
562{
563 // If accumulator already has a full y-coordinate, return it directly
564 if (acc.is_full_element) {
565 // Use 4-arg constructor with is_infinity=false (chain operations assume valid, non-infinity points).
566 return element(acc.x3_prev, acc.y3_prev, bool_ct(acc.x3_prev.get_context(), false), /*assert_on_curve=*/false);
567 }
568
569 // Compute y₃ = λ(x₁ - x₃) - y₁
570 // where λ, x₁, y₁ are from the previous addition stored in the accumulator
571 auto& x3 = acc.x3_prev;
572 auto& lambda = acc.lambda_prev;
573
574 Fq y3 = lambda.madd((acc.x1_prev - x3), { -acc.y1_prev });
575 // Use 4-arg constructor with is_infinity=false (chain operations assume valid, non-infinity points).
576 return element(x3, y3, bool_ct(x3.get_context(), false), /*assert_on_curve=*/false);
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 // Handle edge case of empty input
610 if (add.empty()) {
611 return *this;
612 }
613
614 // Let A = (x, y) and P = (x₁, y₁)
615 // For the first point P, we want to compute: (2A + P) = (A + P) + A
616 // We first need to check if x ≠ x₁.
617 x().assert_is_not_equal(add[0].x3_prev, "biggroup::multiple_montgomery_ladder: x-coordinates must be distinct.");
618
619 // Compute λ₁ for computing the first addition: (A + P)
620 Fq lambda1;
621 if (!add[0].is_full_element) {
622 // Case 1: P is an accumulator (i.e., it lacks a y-coordinate)
623 // λ₁ = (y - y₁) / (x - x₁)
624 // = -(y₁ - y) / (x - x₁)
625 // = -(λ₁_ₚᵣₑᵥ * (x₁_ₚᵣₑᵥ - x₁) - y₁_ₚᵣₑᵥ - y) / (x - x₁)
626 //
627 // NOTE: msub_div computes -(∑ᵢ aᵢ * bᵢ + ∑ⱼcⱼ) / d
628 lambda1 = Fq::msub_div({ add[0].lambda_prev }, // numerator left multiplicands: λ₁_ₚᵣₑᵥ
629 { add[0].x1_prev - add[0].x3_prev }, // numerator right multiplicands: (x₁_ₚᵣₑᵥ - x₁)
630 (x() - add[0].x3_prev), // denominator: (x - x₁)
631 { -add[0].y1_prev, -y() }, // numerator additions: -y₁_ₚᵣₑᵥ - y
632 /*enable_divisor_nz_check*/ false); // divisor check is not needed as x ≠ x₁ is enforced
633 } else {
634 // Case 2: P is a full element (i.e., it has a y-coordinate)
635 // λ₁ = (y - y₁) / (x - x₁)
636 //
637 lambda1 = Fq::div_without_denominator_check({ y() - add[0].y3_prev }, (x() - add[0].x3_prev));
638 }
639
640 // Using λ₁, compute x₃ for (A + P):
641 // x₃ = λ₁.λ₁ - x₁ - x
642 Fq x_3 = lambda1.madd(lambda1, { -add[0].x3_prev, -x() });
643
644 // Compute λ₂ for the addition (A + P) + A:
645 // λ₂ = (y - y₃) / (x - x₃)
646 // = (y - (λ₁ * (x - x₃) - y)) / (x - x₃) (substituting y₃)
647 // = (2y) / (x - x₃) - λ₁
648 //
649 x().assert_is_not_equal(x_3, "biggroup::multiple_montgomery_ladder: x-coordinates must be distinct.");
650 Fq lambda2 = Fq::div_without_denominator_check({ y() + y() }, (x() - x_3)) - lambda1;
651
652 // Using λ₂, compute x₄ for the final result:
653 // x₄ = λ₂.λ₂ - x₃ - x
654 Fq x_4 = lambda2.sqradd({ -x_3, -x() });
655
656 // Compute y₄ for the final result:
657 // y₄ = λ₂ * (x - x₄) - y
658 //
659 // However, we don't actually compute y₄ here. Instead, we build a "composite" y value that contains
660 // the components needed to compute y₄ later. This is done to avoid the explicit multiplication here.
661 //
662 // We store the result as either y₄ or -y₄, depending on whether the number of points added
663 // is even or odd. This sign adjustment simplifies the handling of subsequent additions in the loop below.
664 // +y₄ = λ₂ * (x - x₄) - y
665 // -y₄ = λ₂ * (x₄ - x) + y
666 const bool num_points_even = ((add.size() & 1ULL) == 0);
667 composite_y previous_y;
668 previous_y.add.emplace_back(num_points_even ? y() : -y());
669 previous_y.mul_left.emplace_back(lambda2);
670 previous_y.mul_right.emplace_back(num_points_even ? x_4 - x() : x() - x_4);
671 previous_y.is_negative = num_points_even;
672
673 // Handle remaining iterations (i > 0) in a loop
674 Fq previous_x = x_4;
675 for (size_t i = 1; i < add.size(); ++i) {
676 // Let x = previous_x, y = previous_y
677 // Let P = (xᵢ, yᵢ) be the next point to add (represented by add[i])
678 // Ensure x-coordinates are distinct: x ≠ xᵢ
679 previous_x.assert_is_not_equal(add[i].x3_prev,
680 "biggroup::multiple_montgomery_ladder: x-coordinates must be distinct.");
681
682 // Determine sign adjustment based on previous y's sign
683 // If the previous y was positive, we need to negate the y-component from add[i]
684 const bool negate_add_y = !previous_y.is_negative;
685
686 // Build λ₁ numerator components from previous composite y and current accumulator
687 std::vector<Fq> lambda1_left = previous_y.mul_left;
688 std::vector<Fq> lambda1_right = previous_y.mul_right;
689 std::vector<Fq> lambda1_add = previous_y.add;
690
691 if (!add[i].is_full_element) {
692 // Case 1: add[i] is an accumulator (lacks y-coordinate)
693 // λ₁ = (y - yᵢ) / (x - xᵢ)
694 // = -(yᵢ - y) / (x - xᵢ)
695 // = -(λᵢ_ₚᵣₑᵥ * (xᵢ_ₚᵣₑᵥ - xᵢ) - yᵢ_ₚᵣₑᵥ - y) / (x - xᵢ)
696 //
697 // If (previous) y is stored as positive, we compute λ₁ as:
698 // λ₁ = -(λᵢ_ₚᵣₑᵥ * (xᵢ - xᵢ_ₚᵣₑᵥ) + yᵢ_ₚᵣₑᵥ + y) / (xᵢ - x)
699 //
700 lambda1_left.emplace_back(add[i].lambda_prev);
701 lambda1_right.emplace_back(negate_add_y ? add[i].x3_prev - add[i].x1_prev
702 : add[i].x1_prev - add[i].x3_prev);
703 lambda1_add.emplace_back(negate_add_y ? add[i].y1_prev : -add[i].y1_prev);
704 } else {
705 // Case 2: add[i] is a full element (has y-coordinate)
706 // λ₁ = (yᵢ - y) / (xᵢ - x)
707 //
708 // If previous y is positive, we compute λ₁ as:
709 // λ₁ = -(y - yᵢ) / (xᵢ - x)
710 //
711 lambda1_add.emplace_back(negate_add_y ? -add[i].y3_prev : add[i].y3_prev);
712 }
713
714 // Compute λ₁
715 Fq denominator = negate_add_y ? add[i].x3_prev - previous_x : previous_x - add[i].x3_prev;
716 Fq lambda1 =
717 Fq::msub_div(lambda1_left, lambda1_right, denominator, lambda1_add, /*enable_divisor_nz_check*/ false);
718
719 // Using λ₁, compute x₃ for (previous + P):
720 // x₃ = λ₁.λ₁ - xᵢ - x
721 // y₃ = λ₁ * (x - x₃) - y (we don't compute this explicitly)
722 Fq x_3 = lambda1.madd(lambda1, { -add[i].x3_prev, -previous_x });
723
724 // Compute λ₂ using previous composite y
725 // λ₂ = (y - y₃) / (x - x₃)
726 // = (y - (λ₁ * (x - x₃) - y)) / (x - x₃) (substituting y₃)
727 // = (2y) / (x - x₃) - λ₁
728 // = -2(y / (x₃ - x)) - λ₁
729 //
730 previous_x.assert_is_not_equal(x_3, "biggroup::multiple_montgomery_ladder: x-coordinates must be distinct.");
731 Fq l2_denominator = previous_y.is_negative ? previous_x - x_3 : x_3 - previous_x;
732 Fq partial_lambda2 = Fq::msub_div(previous_y.mul_left,
733 previous_y.mul_right,
734 l2_denominator,
735 previous_y.add,
736 /*enable_divisor_nz_check*/ false);
737 partial_lambda2 = partial_lambda2 + partial_lambda2;
738 lambda2 = partial_lambda2 - lambda1;
739
740 // Using λ₂, compute x₄ for the final result of this iteration:
741 // x₄ = λ₂.λ₂ - x₃ - x
742 x_4 = lambda2.sqradd({ -x_3, -previous_x });
743
744 // Build composite y for this iteration
745 // y₄ = λ₂ * (x - x₄) - y
746 // However, we don't actually compute y₄ explicitly, we rather store components to compute it later.
747 // We store the result as either y₄ or -y₄, depending on the sign of previous_y.
748 // +y₄ = λ₂ * (x - x₄) - y
749 // -y₄ = λ₂ * (x₄ - x) + y
750 composite_y y_4;
751 y_4.is_negative = !previous_y.is_negative;
752 y_4.mul_left.emplace_back(lambda2);
753 y_4.mul_right.emplace_back(previous_y.is_negative ? previous_x - x_4 : x_4 - previous_x);
754
755 // Append terms from previous_y to y_4. We want to make sure the terms above are added into the start
756 // of y_4. This is to ensure they are cached correctly when
757 // `builder::evaluate_partial_non_native_field_multiplication` is called. (the 1st mul_left, mul_right elements
758 // will trigger builder::evaluate_non_native_field_multiplication
759 // when Fq::mult_madd is called - this term cannot be cached so we want to make sure it is unique)
760 y_4.mul_left.insert(y_4.mul_left.end(), previous_y.mul_left.begin(), previous_y.mul_left.end());
761 y_4.mul_right.insert(y_4.mul_right.end(), previous_y.mul_right.begin(), previous_y.mul_right.end());
762 y_4.add.insert(y_4.add.end(), previous_y.add.begin(), previous_y.add.end());
763
764 previous_x = x_4;
765 previous_y = y_4;
766 }
767
768 Fq x_out = previous_x;
769
770 BB_ASSERT(!previous_y.is_negative);
771
772 Fq y_out = Fq::mult_madd(previous_y.mul_left, previous_y.mul_right, previous_y.add);
773 // Use 4-arg constructor with is_infinity=false (montgomery ladder assumes valid, non-infinity points).
774 return element(x_out, y_out, bool_ct(x_out.get_context(), false), /*assert_on_curve=*/false);
775}
776
814template <typename C, class Fq, class Fr, class G>
816 const size_t num_rounds)
817{
818 constexpr typename G::affine_element offset_generator =
819 get_precomputed_generators<G, "biggroup offset generator", 1>()[0];
820
821 const uint256_t offset_multiplier = uint256_t(1) << uint256_t(num_rounds - 1);
822
823 const typename G::affine_element offset_generator_end = typename G::element(offset_generator) * offset_multiplier;
824
825 return std::make_pair<element, element>(element(offset_generator.x, offset_generator.y),
826 element(offset_generator_end.x, offset_generator_end.y));
827}
828
829template <typename C, class Fq, class Fr, class G>
831 const std::vector<Fr>& scalars,
832 const size_t max_num_bits)
833{
834 // Sanity checks
835 BB_ASSERT_GT(points.size(), 0ULL, "process_strauss_msm: points cannot be empty");
836 BB_ASSERT_EQ(points.size(), scalars.size(), "process_strauss_msm: points and scalars size mismatch");
837
838 // Check that all scalars are in range
839 for (const auto& scalar : scalars) {
840 const size_t num_scalar_bits = static_cast<size_t>(uint512_t(scalar.get_value()).get_msb()) + 1ULL;
841 BB_ASSERT_LTE(num_scalar_bits, max_num_bits, "process_strauss_msm: scalar out of range");
842 }
843
844 // Constant parameters
845 const size_t num_rounds = max_num_bits;
846 const size_t msm_size = scalars.size();
847
848 // Compute ROM lookup table for points. Example if we have 3 points G1, G2, G3:
849 // ┌───────┬─────────────────┐
850 // │ Index │ Point │
851 // ├───────┼─────────────────┤
852 // │ 0 │ G1 + G2 + G3 │
853 // │ 1 │ G1 + G2 - G3 │
854 // │ 2 │ G1 - G2 + G3 │
855 // │ 3 │ G1 - G2 - G3 │
856 // │ 4 │ -G1 + G2 + G3 │
857 // │ 5 │ -G1 + G2 - G3 │
858 // │ 6 │ -G1 - G2 + G3 │
859 // │ 7 │ -G1 - G2 - G3 │
860 // └───────┴─────────────────┘
861 batch_lookup_table point_table(points);
862
863 // Compute NAF representations of scalars
865 for (size_t i = 0; i < msm_size; ++i) {
866 naf_entries.emplace_back(compute_naf(scalars[i], num_rounds));
867 }
868
869 // We choose a deterministic offset generator based on the number of rounds.
870 // We compute both the initial and final offset generators: G_offset, 2ⁿ⁻¹ * G_offset.
871 const auto [offset_generator_start, offset_generator_end] = compute_offset_generators(num_rounds);
872
873 // Initialize accumulator with offset generator + first NAF column.
874 element accumulator =
875 element::chain_add_end(element::chain_add(offset_generator_start, point_table.get_chain_initial_entry()));
876
877 // Process 4 NAF entries per iteration (for the remaining (num_rounds - 1) rounds)
878 constexpr size_t num_rounds_per_iteration = 4;
879 const size_t num_iterations = numeric::ceil_div((num_rounds - 1), num_rounds_per_iteration);
880 const size_t num_rounds_per_final_iteration = (num_rounds - 1) - ((num_iterations - 1) * num_rounds_per_iteration);
881
882 for (size_t i = 0; i < num_iterations; ++i) {
884 const size_t inner_num_rounds =
885 (i != num_iterations - 1) ? num_rounds_per_iteration : num_rounds_per_final_iteration;
886 for (size_t j = 0; j < inner_num_rounds; ++j) {
887 // Gather the NAF columns for this iteration
888 std::vector<bool_ct> nafs(msm_size);
889 for (size_t k = 0; k < msm_size; ++k) {
890 nafs[k] = (naf_entries[k][(i * num_rounds_per_iteration) + j + 1]);
891 }
892 to_add.emplace_back(point_table.get_chain_add_accumulator(nafs));
893 }
894
895 // Once we have looked-up all points from the four NAF columns, we update the accumulator as:
896 // accumulator = 2.(2.(2.(2.accumulator + to_add[0]) + to_add[1]) + to_add[2]) + to_add[3]
897 // = 2⁴.accumulator + 2³.to_add[0] + 2².to_add[1] + 2¹.to_add[2] + to_add[3]
898 accumulator = accumulator.multiple_montgomery_ladder(to_add);
899 }
900
901 // Subtract the skew factors (if any)
902 for (size_t i = 0; i < msm_size; ++i) {
903 element skew = accumulator.subtract_internal(points[i]);
904 accumulator = accumulator.conditional_select(skew, naf_entries[i][num_rounds]);
905 }
906
907 // Subtract the scaled offset generator
908 accumulator = accumulator.subtract_internal(offset_generator_end);
909
910 return accumulator;
911}
912
949template <typename C, class Fq, class Fr, class G>
951 const std::vector<Fr>& _scalars,
952 const size_t max_num_bits,
953 const bool with_edgecases)
954{
955 // Sanity check input sizes
956 BB_ASSERT_GT(_points.size(), 0ULL, "biggroup batch_mul: no points provided for batch multiplication");
957 BB_ASSERT_EQ(_points.size(), _scalars.size(), "biggroup batch_mul: points and scalars size mismatch");
958
959 // Get builder context from input points (needed for creating infinity results)
960 C* builder = _points[0].get_context();
961
962 // Replace (∞, scalar) pairs by the pair (G, 0).
963 auto [points, scalars] = handle_points_at_infinity(_points, _scalars);
964
965 BB_ASSERT_LTE(points.size(), _points.size());
966 BB_ASSERT_EQ(points.size(),
967 scalars.size(),
968 "biggroup batch_mul: points and scalars size mismatch after handling points at infinity");
969
970 // If batch_mul actually performs batch multiplication on the points and scalars, subprocedures can do
971 // operations like addition or subtraction of points, which can trigger OriginTag security mechanisms
972 // even though the final result satisfies the security logic. For example
973 // result = submitted_in_round_0 * challenge_from_round_0 + submitted_in_round_1 * challenge_in_round_1
974 // will trigger it, because the addition of submitted_in_round_0 to submitted_in_round_1 is dangerous by itself.
975 // To avoid this, we remove the tags, merge them separately and set the result appropriately
976 OriginTag tag = OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
977 auto empty_tag = OriginTag::constant(); // Disable origin checking during intermediate operations
978 for (size_t i = 0; i < _points.size(); i++) {
979 tag = OriginTag(tag, OriginTag(_points[i].get_origin_tag(), _scalars[i].get_origin_tag()));
980 }
981 for (size_t i = 0; i < scalars.size(); i++) {
982 points[i].set_origin_tag(empty_tag);
983 scalars[i].set_origin_tag(empty_tag);
984 }
985
986 // Accumulate constant-constant pairs out of circuit
987 bool has_constant_terms = false;
988 typename G::element constant_accumulator = G::element::infinity();
989 std::vector<element> new_points;
990 std::vector<Fr> new_scalars;
991 for (size_t i = 0; i < points.size(); ++i) {
992 if (points[i].is_constant() && scalars[i].is_constant()) {
993 const auto& point_value = typename G::element(points[i].get_value());
994 const auto& scalar_value = typename G::Fr(scalars[i].get_value());
995 constant_accumulator += (point_value * scalar_value);
996 has_constant_terms = true;
997 } else {
998 new_points.emplace_back(points[i]);
999 new_scalars.emplace_back(scalars[i]);
1000 }
1001 }
1002 points = new_points;
1003 scalars = new_scalars;
1004
1005 if (with_edgecases && !points.empty()) {
1006 // If points are linearly dependent, we randomise them using a free-witness offset generator.
1007 // We do this to ensure that the x-coordinates of the points are all distinct. This is required
1008 // while creating the ROM lookup table with the points.
1009 auto [masked_points, masked_scalars, _offset_generator] = mask_points(points, scalars);
1010 points = std::move(masked_points);
1011 scalars = std::move(masked_scalars);
1012 }
1013
1015 points.size(), scalars.size(), "biggroup batch_mul: points and scalars size mismatch after handling edgecases");
1016
1017 // Partition scalars into big and small paths using compile-time information.
1018 // Since `with_edgecases` appends a 254-bit randomization scalar at the end (via
1019 // `mask_points`), the last scalar must go to big. Everything else routes by
1020 // `max_num_bits`: `0` forces the full-width path, anything else uses the small path.
1021 const size_t original_size = scalars.size();
1022 std::vector<Fr> big_scalars;
1023 std::vector<element> big_points;
1024 std::vector<Fr> small_scalars;
1025 std::vector<element> small_points;
1026 for (size_t i = 0; i < original_size; ++i) {
1027 const bool is_last_scalar_big = (i == original_size - 1) && with_edgecases;
1028 if (max_num_bits == 0 || is_last_scalar_big) {
1029 big_points.emplace_back(points[i]);
1030 big_scalars.emplace_back(scalars[i]);
1031 } else {
1032 small_points.emplace_back(points[i]);
1033 small_scalars.emplace_back(scalars[i]);
1034 }
1035 }
1036
1037 BB_ASSERT_EQ(original_size,
1038 small_points.size() + big_points.size(),
1039 "biggroup batch_mul: points size mismatch after separating big scalars");
1040 BB_ASSERT_EQ(big_points.size(),
1041 big_scalars.size(),
1042 "biggroup batch_mul: big points and scalars size mismatch after separating big scalars");
1043 BB_ASSERT_EQ(small_points.size(),
1044 small_scalars.size(),
1045 "biggroup batch_mul: small points and scalars size mismatch after separating big scalars");
1046
1047 const size_t max_num_bits_in_field = Fr::modulus.get_msb() + 1;
1048
1049 element accumulator;
1050 bool accumulator_initialized = false;
1051
1052 // Check if we'll need to process any witness points
1053
1054 // Initialize accumulator with constant terms if they exist, OR if there are no remaining points
1055 // (to handle the case where all points were filtered out by handle_points_at_infinity)
1056 const bool has_no_points = big_points.empty() && small_points.empty();
1057 if (has_constant_terms || has_no_points) {
1058 // Convert from projective to affine to get correct (x, y) coordinates
1059 typename G::affine_element constant_accumulator_affine(constant_accumulator);
1060 if (constant_accumulator_affine.is_point_at_infinity()) {
1061 // For infinity, create element with canonical (0, 0) coordinates
1062 // Using constant zero Fq to create a constant infinity element
1063 Fq zero_fq = Fq(builder, 0);
1064 accumulator = element(zero_fq, zero_fq);
1065 } else {
1066 accumulator = element(constant_accumulator_affine.x, constant_accumulator_affine.y);
1067 }
1068 accumulator_initialized = true;
1069 }
1070
1071 if (!big_points.empty()) {
1072 // Process big scalars separately
1073 element big_result = element::process_strauss_msm_rounds(big_points, big_scalars, max_num_bits_in_field);
1074 accumulator = accumulator_initialized ? accumulator.add_internal(big_result) : big_result;
1075 accumulator_initialized = true;
1076 }
1077
1078 if (!small_points.empty()) {
1079 // Process small scalars
1080 const size_t effective_max_num_bits = (max_num_bits == 0) ? max_num_bits_in_field : max_num_bits;
1081 element small_result = element::process_strauss_msm_rounds(small_points, small_scalars, effective_max_num_bits);
1082 accumulator = accumulator_initialized ? accumulator.add_internal(small_result) : small_result;
1083 accumulator_initialized = true;
1084 }
1085
1086 accumulator.set_origin_tag(tag);
1087 return accumulator;
1088}
1089
1105template <typename C, class Fq, class Fr, class G>
1107 const std::vector<Fr>& scalars,
1108 const size_t max_num_bits,
1109 const bool with_edgecases)
1110{
1111 element result = batch_mul_internal(points, scalars, max_num_bits, with_edgecases);
1112 return result.get_standard_form();
1113}
1114
1120template <typename C, class Fq, class Fr, class G>
1122{
1123 // Use `scalar_mul` method without specifying the length of `scalar`.
1124 return scalar_mul(scalar);
1125}
1126
1127template <typename C, class Fq, class Fr, class G>
1139element<C, Fq, Fr, G> element<C, Fq, Fr, G>::scalar_mul(const Fr& scalar, const size_t max_num_bits) const
1140{
1164 return element::batch_mul({ *this }, { scalar }, max_num_bits, /*with_edgecases=*/false);
1165}
1166} // namespace bb::stdlib::element_default
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_GT(left, right,...)
Definition assert.hpp:113
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
constexpr uint64_t get_msb() const
constexpr uint64_t get_msb() const
Definition uintx.hpp:68
Implements boolean logic in-circuit.
Definition bool.hpp:60
bool get_value() const
Definition bool.hpp:125
bool is_constant() const
Definition bool.hpp:127
Fq validate_on_curve(std::string const &msg="biggroup::validate_on_curve", bool assert_is_on_curve=true) const
Check that the point is on the curve.
Definition biggroup.hpp:102
bool_t< Builder > is_zero() const
Validate whether a field_t element is zero.
Definition field.cpp:785
AluTraceBuilder builder
Definition alu.test.cpp:124
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:309
void mark_witness_as_used(const field_t< Builder > &field)
Mark a field_t witness as used (for UltraBuilder only).
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...
grumpkin::fq Fq
static OriginTag constant()
static constexpr uint256_t modulus
element::chain_add_accumulator get_chain_add_accumulator(std::vector< bool_ct > &naf_entries) const
Definition biggroup.hpp:844
VectorField result