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 (const auto& limb : _x.binary_basis_limbs) {
51 limb_sum += limb.element;
52 }
53 for (const auto& limb : _y.binary_basis_limbs) {
54 limb_sum += limb.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 if (assert_on_curve) {
81 }
82}
83
84template <typename C, class Fq, class Fr, class G>
86 : _x(other._x)
87 , _y(other._y)
88 , _is_infinity(other.is_point_at_infinity())
89{}
90
91template <typename C, class Fq, class Fr, class G>
93 : _x(other._x)
94 , _y(other._y)
95 , _is_infinity(other.is_point_at_infinity())
96{}
97
98template <typename C, class Fq, class Fr, class G>
100{
101 if (&other == this) {
102 return *this;
103 }
104 _x = other._x;
105 _y = other._y;
106 _is_infinity = other.is_point_at_infinity();
107 return *this;
108}
109
110template <typename C, class Fq, class Fr, class G>
112{
113 if (&other == this) {
114 return *this;
115 }
116 _x = other._x;
117 _y = other._y;
118 _is_infinity = other.is_point_at_infinity();
119 return *this;
120}
121
129template <typename C, class Fq, class Fr, class G>
131{
132 // Adding in `x_coordinates_match` ensures that lambda will always be well-formed
133 // Our curve has the form y² = x³ + ax + b (or y² = x³ + b when a = 0).
134 // If (x₁, y₁), (x₂, y₂) have x₁ == x₂, the generic formula for lambda has a division by 0.
135 // Then y₁ == y₂ (i.e. we are doubling) or y₂ == -y₁ (the sum is infinity).
136 // These cases have special addition formulae. The following booleans allow us to handle these cases uniformly.
137 const bool_ct x_coordinates_match = other._x == _x;
138 const bool_ct y_coordinates_match = (_y == other._y);
139 const bool_ct infinity_predicate = (x_coordinates_match && !y_coordinates_match);
140 const bool_ct double_predicate = (x_coordinates_match && y_coordinates_match);
141 const bool_ct lhs_infinity = is_point_at_infinity();
142 const bool_ct rhs_infinity = other.is_point_at_infinity();
143 const bool_ct has_infinity_input = lhs_infinity || rhs_infinity;
144
145 // NOTE: For valid points on the curve, specifically for bn254 or secp256k1 or secp256r1, y = 0 cannot occur.
146 // For points not on the curve, having y = 0 will lead to a failure while performing the division below.
147 // We could enforce in circuit that y = 0 results in point at infinity, or that y != 0 always.
148 // However, this would be an unnecessary constraint for valid points on the curve.
149 // So we perform a native check here to catch any accidental misuse of this function.
150 // Exception: Points at infinity use the canonical (0, 0) representation, so skip the check for them.
151 if (!lhs_infinity.get_value()) {
152 const typename G::Fq y_value = uint256_t(_y.get_value());
153 BB_ASSERT_EQ((y_value == 0), false, "Attempting to add a point with y = 0, not allowed.");
154 }
155 if (!rhs_infinity.get_value()) {
156 const typename G::Fq other_y_value = uint256_t(other._y.get_value());
157 BB_ASSERT_EQ((other_y_value == 0), false, "Attempting to add a point with y = 0, not allowed.");
158 }
159
160 // Compute the gradient λ. If we add, λ = (y₂ - y₁)/(x₂ - x₁)
161 // For doubling: λ = (3x₁² + a)/(2y₁) if curve has 'a', else λ = 3x₁²/(2y₁)
162 const Fq add_lambda_numerator = other._y - _y;
163 const Fq xx = _x * _x;
164 Fq dbl_lambda_numerator = xx + xx + xx; // 3x²
165 if constexpr (G::has_a) {
166 // Curve equation: y² = x³ + ax + b
167 // Doubling formula numerator: 3x² + a
168 const Fq a(get_context(), uint256_t(G::curve_a));
169 dbl_lambda_numerator = dbl_lambda_numerator + a;
170 }
171 const Fq lambda_numerator = Fq::conditional_assign(double_predicate, dbl_lambda_numerator, add_lambda_numerator);
172
173 const Fq add_lambda_denominator = other._x - _x;
174 const Fq dbl_lambda_denominator = _y + _y;
175 Fq lambda_denominator = Fq::conditional_assign(double_predicate, dbl_lambda_denominator, add_lambda_denominator);
176
177 // If either input is a point at infinity, or if the result would be infinity, set lambda_denominator to 1
178 // to prevent division by zero. Cases where result is infinity: x₁ == x₂ but y₁ != y₂ (points are inverses)
179 const bool_ct safe_denominator_needed = has_infinity_input || infinity_predicate;
180 lambda_denominator = Fq::conditional_assign(safe_denominator_needed, Fq(1), lambda_denominator);
181
182 // Compute λ = numerator / denominator
183 // We enforce that denominator is not zero to protect against soundness issues.
184 const Fq lambda = lambda_numerator / lambda_denominator;
185
186 // Compute resulting point coordinates: x₃ = λ² - x₁ - x₂, y₃ = λ(x₁ - x₃) - y₁
187 Fq x3 = lambda.sqradd({ -other._x, -_x });
188 Fq y3 = lambda.madd(_x - x3, { -_y });
189
190 // if lhs infinity, return rhs
191 x3 = Fq::conditional_assign(lhs_infinity, other._x, x3);
192 y3 = Fq::conditional_assign(lhs_infinity, other._y, y3);
193 // if rhs infinity, return lhs
194 x3 = Fq::conditional_assign(rhs_infinity, _x, x3);
195 y3 = Fq::conditional_assign(rhs_infinity, _y, y3);
196
197 // Determine if result is point at infinity:
198 // - If x₁ == x₂ and y₁ == -y₂ (i.e., points are inverses), result is ∞
199 // - If both inputs are ∞, result is ∞
200 bool_ct result_is_infinity = (infinity_predicate && !has_infinity_input) || (lhs_infinity && rhs_infinity);
201 mark_witness_as_used(field_t<C>(result_is_infinity));
202
203 element result(x3, y3, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
204 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
205 return result;
206}
207
214template <typename C, class Fq, class Fr, class G>
216{
217 element result = add_internal(other);
218 return result.get_standard_form();
219}
220
228template <typename C, class Fq, class Fr, class G>
230{
231
232 const bool_ct is_infinity = is_point_at_infinity();
233
234 element result(*this);
235 const Fq zero = Fq(get_context(), 0);
236 result._x = Fq::conditional_assign(is_infinity, zero, this->_x);
237 result._y = Fq::conditional_assign(is_infinity, zero, this->_y);
238 return result;
239}
240
248template <typename C, class Fq, class Fr, class G>
250{
251 // Adding in `x_coordinates_match` ensures that lambda will always be well-formed
252 // Our curve has the form y² = x³ + ax + b (or y² = x³ + b when a = 0).
253 // If (x₁, y₁), (x₂, y₂) have x₁ == x₂, the generic formula for lambda has a division by 0.
254 // For subtraction P₁ - P₂ = P₁ + (-P₂), where -P₂ = (x₂, -y₂):
255 // - If y₁ == -y₂ (i.e., x₁ == x₂ and y₁ == -y₂), this becomes doubling: P₁ + P₁
256 // - If y₁ == y₂ (i.e., x₁ == x₂ and y₁ == y₂), result is infinity: P₁ - P₁ = ∞
257 // These cases have special addition formulae. The following booleans allow us to handle these cases uniformly.
258 const bool_ct x_coordinates_match = other._x == _x;
259 const bool_ct y_coordinates_match = (_y == other._y);
260 const bool_ct infinity_predicate = (x_coordinates_match && y_coordinates_match);
261 const bool_ct double_predicate = (x_coordinates_match && !y_coordinates_match);
262 const bool_ct lhs_infinity = is_point_at_infinity();
263 const bool_ct rhs_infinity = other.is_point_at_infinity();
264 const bool_ct has_infinity_input = lhs_infinity || rhs_infinity;
265
266 // Compute the gradient λ. For subtraction, λ = (-y₂ - y₁)/(x₂ - x₁)
267 // For doubling: λ = (3x₁² + a)/(2y₁) if curve has 'a', else λ = 3x₁²/(2y₁)
268 const Fq add_lambda_numerator = -other._y - _y;
269 const Fq xx = _x * _x;
270 Fq dbl_lambda_numerator = xx + xx + xx; // 3x²
271 if constexpr (G::has_a) {
272 // Curve equation: y² = x³ + ax + b
273 // Doubling formula numerator: 3x² + a
274 const Fq a(get_context(), uint256_t(G::curve_a));
275 dbl_lambda_numerator = dbl_lambda_numerator + a;
276 }
277 const Fq lambda_numerator = Fq::conditional_assign(double_predicate, dbl_lambda_numerator, add_lambda_numerator);
278
279 const Fq add_lambda_denominator = other._x - _x;
280 const Fq dbl_lambda_denominator = _y + _y;
281 Fq lambda_denominator = Fq::conditional_assign(double_predicate, dbl_lambda_denominator, add_lambda_denominator);
282
283 // If either input is a point at infinity, or if the result would be infinity (x₁ == x₂ and y₁ == y₂),
284 // set lambda_denominator to 1 to prevent division by zero. The lambda value won't be used in these cases.
285 // Defense in depth: also guard when doubling with y = 0 (denominator 2y would be zero).
286 const bool_ct safe_denominator_needed = has_infinity_input || infinity_predicate;
287 lambda_denominator = Fq::conditional_assign(safe_denominator_needed, Fq(1), lambda_denominator);
288
289 // Now compute lambda = numerator / denominator
290 // We enforce that the denominator is not zero (in division operator), so this division is safe.
291 const Fq lambda = lambda_numerator / lambda_denominator;
292
293 // Compute resulting point coordinates: x₃ = λ² - x₁ - x₂, y₃ = λ(x₁ - x₃) - y₁
294 Fq x3 = lambda.sqradd({ -other._x, -_x });
295 Fq y3 = lambda.madd(_x - x3, { -_y });
296
297 // if lhs infinity, return -rhs (negated rhs point)
298 x3 = Fq::conditional_assign(lhs_infinity, other._x, x3);
299 y3 = Fq::conditional_assign(lhs_infinity, -other._y, y3);
300 // if rhs infinity, return lhs
301 x3 = Fq::conditional_assign(rhs_infinity, _x, x3);
302 y3 = Fq::conditional_assign(rhs_infinity, _y, y3);
303
304 // Determine if result is point at infinity:
305 // - If x₁ == x₂ and y₁ == y₂ (i.e., P₁ - P₁), result is ∞
306 // - If both inputs are ∞, result is ∞
307 bool_ct result_is_infinity = (infinity_predicate && !has_infinity_input) || (lhs_infinity && rhs_infinity);
308 mark_witness_as_used(field_t<C>(result_is_infinity));
309
310 element result(x3, y3, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
311 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
312 return result;
313}
314
321template <typename C, class Fq, class Fr, class G>
323{
324 element result = subtract_internal(other);
325 return result.get_standard_form();
326}
327template <typename C, class Fq, class Fr, class G>
329{
330 other._x.assert_is_not_equal(_x);
331 const Fq lambda = Fq::div_without_denominator_check({ other._y, -_y }, (other._x - _x));
332 const Fq x3 = lambda.sqradd({ -other._x, -_x });
333 const Fq y3 = lambda.madd(_x - x3, { -_y });
334 // Use 4-arg constructor with is_infinity=false (checked operations assume valid, non-infinity points).
335 // This avoids the expensive bigfield equality checks in the 2-arg constructor's infinity auto-detection.
336 return element(x3, y3, bool_ct(x3.get_context(), false), /*assert_on_curve=*/false);
337}
338
339template <typename C, class Fq, class Fr, class G>
341{
342
343 other._x.assert_is_not_equal(_x);
344 const Fq lambda = Fq::div_without_denominator_check({ other._y, _y }, (other._x - _x));
345 const Fq x_3 = lambda.sqradd({ -other._x, -_x });
346 const Fq y_3 = lambda.madd(x_3 - _x, { -_y });
347
348 // Use 4-arg constructor with is_infinity=false (checked operations assume valid, non-infinity points).
349 return element(x_3, y_3, bool_ct(x_3.get_context(), false), /*assert_on_curve=*/false);
350}
351
366template <typename C, class Fq, class Fr, class G>
368{
369 // validate we can use incomplete addition formulae
370 other._x.assert_is_not_equal(_x);
371
372 const Fq denominator = other._x - _x;
373 const Fq x2x1 = -(other._x + _x);
374
375 const Fq lambda1 = Fq::div_without_denominator_check({ other._y, -_y }, denominator);
376 const Fq x_3 = lambda1.sqradd({ x2x1 });
377 const Fq y_3 = lambda1.madd(_x - x_3, { -_y });
378 const Fq lambda2 = Fq::div_without_denominator_check({ -other._y, -_y }, denominator);
379 const Fq x_4 = lambda2.sqradd({ x2x1 });
380 const Fq y_4 = lambda2.madd(_x - x_4, { -_y });
381
382 // Use 4-arg constructor with is_infinity=false (checked operations assume valid, non-infinity points).
383 bool_ct not_infinity(x_3.get_context(), false);
384 return { element(x_3, y_3, not_infinity, /*assert_on_curve=*/false),
385 element(x_4, y_4, not_infinity, /*assert_on_curve=*/false) };
386}
387
394template <typename C, class Fq, class Fr, class G> element<C, Fq, Fr, G> element<C, Fq, Fr, G>::dbl_internal() const
395{
396 const bool_ct is_infinity = is_point_at_infinity();
397
398 // NOTE: For valid points on the curve, specifically for bn254 or secp256k1 or secp256r1, y = 0 cannot occur.
399 // For points not on the curve, having y = 0 will lead to a failure while performing the division below.
400 // We could enforce in circuit that y = 0 results in point at infinity, or that y != 0 always.
401 // However, this would be an unnecessary constraint for valid points on the curve.
402 // So we perform a native check here to catch any accidental misuse of this function.
403 // Exception: Points at infinity use the canonical (0, 0) representation, so skip the check for them.
404 if (!is_infinity.get_value()) {
405 const typename G::Fq y_value = uint256_t(_y.get_value());
406 BB_ASSERT_EQ((y_value == 0), false, "Attempting to dbl a point with y = 0, not allowed.");
407 }
408
409 // If the input is a point at infinity, use a safe denominator (1) to prevent division by zero.
410 // The result will be infinity anyway, so the computed coordinates don't matter.
411 const Fq two_y = _y + _y;
412 Fq denominator = Fq::conditional_assign(is_infinity, Fq(get_context(), 1), two_y);
413
414 Fq two_x = _x + _x;
415 if constexpr (G::has_a) {
416 // Curve equation: y² = x³ + ax + b
417 Fq a(get_context(), uint256_t(G::curve_a));
418
419 // Compute neg_lambda = -λ = -(3x² + a) / (2y)
420 // msub_div computes: -(Σᵢ aᵢ·bᵢ + Σⱼ cⱼ) / d = -(x·(3x) + a) / (2y) = -(3x² + a) / (2y)
421 // We enforce the denominator is not zero to protect against soundness issues.
422 Fq neg_lambda = Fq::msub_div({ _x }, { (two_x + _x) }, denominator, { a }, /*enable_divisor_nz_check*/ true);
423
424 // Compute x₃ = λ² - 2x
425 // Since neg_lambda = -λ, we have: (-λ)² - 2x = λ² - 2x
426 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
427
428 // Compute y₃ = λ(x - x₃) - y
429 // Using neg_lambda = -λ: (-λ)(x₃ - x) + (-y) = -λ(x₃ - x) - y = λ(x - x₃) - y
430 Fq y_3 = neg_lambda.madd(x_3 - _x, { -_y });
431
432 mark_witness_as_used(field_t<C>(is_infinity));
433 return element(x_3, y_3, /*is_infinity=*/is_infinity, /*assert_on_curve=*/false);
434 }
435
436 // Curve equation when a = 0: y² = x³ + b
437 // Compute neg_lambda = -λ = -3x² / (2y)
438 // msub_div computes: -(Σᵢ aᵢ·bᵢ) / d = -(x·(3x)) / (2y) = -3x² / (2y)
439 // We enforce the denominator is not zero to protect against soundness issues.
440 Fq neg_lambda = Fq::msub_div({ _x }, { (two_x + _x) }, denominator, {}, /*enable_divisor_nz_check*/ true);
441
442 // Compute x₃ = λ² - 2x
443 // Since neg_lambda = -λ, we have: (-λ)² - 2x = λ² - 2x
444 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
445
446 // Compute y₃ = λ(x - x₃) - y
447 // Using neg_lambda = -λ: (-λ)(x₃ - x) + (-y) = -λ(x₃ - x) - y = λ(x - x₃) - y
448 Fq y_3 = neg_lambda.madd(x_3 - _x, { -_y });
449
450 mark_witness_as_used(field_t<C>(is_infinity));
451 return element(x_3, y_3, /*is_infinity=*/is_infinity, /*assert_on_curve=*/false);
452}
453
459template <typename C, class Fq, class Fr, class G> element<C, Fq, Fr, G> element<C, Fq, Fr, G>::dbl() const
460{
461 element result = dbl_internal();
462 return result.get_standard_form();
463}
464
471template <typename C, class Fq, class Fr, class G>
473 const element& p2)
474{
476 output.x1_prev = p1._x;
477 output.y1_prev = p1._y;
478
479 // Require x₁ ≠ x₂ for incomplete addition formula
480 p1._x.assert_is_not_equal(p2._x);
481
482 // Compute λ = (y₂ - y₁)/(x₂ - x₁)
483 const Fq lambda = Fq::div_without_denominator_check({ p2._y, -p1._y }, (p2._x - p1._x));
484
485 // Compute x₃ = λ² - x₁ - x₂
486 const Fq x3 = lambda.sqradd({ -p2._x, -p1._x });
487 output.x3_prev = x3;
488 output.lambda_prev = lambda;
489 return output;
490}
491
509template <typename C, class Fq, class Fr, class G>
511 const chain_add_accumulator& acc)
512{
513 // If accumulator has a full y-coordinate, use chain_add_start instead
514 if (acc.is_full_element) {
515 // Use 4-arg constructor with is_infinity=false (chain operations assume valid, non-infinity points).
516 return chain_add_start(
517 p1,
518 element(acc.x3_prev, acc.y3_prev, bool_ct(acc.x3_prev.get_context(), false), /*assert_on_curve=*/false));
519 }
520
521 // Require x₁ ≠ x₂ for incomplete addition formula
522 p1._x.assert_is_not_equal(acc.x3_prev);
523
524 // Compute λ = (y₂ - y₁)/(x₂ - x₁), but we don't have y₂!
525 // We know that y₂ = lambda_prev * (x1_prev - x₂) - y1_prev from the previous addition.
526 //
527 // Derivation:
528 // λ(x₂ - x₁) = y₂ - y₁
529 // λ(x₂ - x₁) = lambda_prev * (x1_prev - x₂) - y1_prev - y₁
530 // λ(x₂ - x₁) = -lambda_prev * (x₂ - x1_prev) - y1_prev - y₁
531 // λ = -(lambda_prev * (x₂ - x1_prev) + y1_prev + y₁) / (x₂ - x₁)
532 //
533 auto& x2 = acc.x3_prev;
534 const auto lambda = Fq::msub_div({ acc.lambda_prev },
535 { (x2 - acc.x1_prev) },
536 (x2 - p1._x),
537 { acc.y1_prev, p1._y },
538 /*enable_divisor_nz_check*/ false); // Divisor is non-zero as x₂ ≠ x₁ is enforced
539
540 // Compute x₃ = λ² - x₂ - x₁
541 const auto x3 = lambda.sqradd({ -x2, -p1._x });
542
543 // Update the accumulator
545 output.x3_prev = x3;
546 output.x1_prev = p1._x;
547 output.y1_prev = p1._y;
548 output.lambda_prev = lambda;
549
550 return output;
551}
552
558template <typename C, class Fq, class Fr, class G>
560{
561 // If accumulator already has a full y-coordinate, return it directly
562 if (acc.is_full_element) {
563 // Use 4-arg constructor with is_infinity=false (chain operations assume valid, non-infinity points).
564 return element(acc.x3_prev, acc.y3_prev, bool_ct(acc.x3_prev.get_context(), false), /*assert_on_curve=*/false);
565 }
566
567 // Compute y₃ = λ(x₁ - x₃) - y₁
568 // where λ, x₁, y₁ are from the previous addition stored in the accumulator
569 auto& x3 = acc.x3_prev;
570 auto& lambda = acc.lambda_prev;
571
572 Fq y3 = lambda.madd((acc.x1_prev - x3), { -acc.y1_prev });
573 // Use 4-arg constructor with is_infinity=false (chain operations assume valid, non-infinity points).
574 return element(x3, y3, bool_ct(x3.get_context(), false), /*assert_on_curve=*/false);
575}
576
596template <typename C, class Fq, class Fr, class G>
598 const std::vector<chain_add_accumulator>& add) const
599{
600 struct composite_y {
601 std::vector<Fq> mul_left;
602 std::vector<Fq> mul_right;
603 std::vector<Fq> add;
604 bool is_negative = false;
605 };
606
607 // Handle edge case of empty input
608 if (add.empty()) {
609 return *this;
610 }
611
612 // Let A = (x, y) and P = (x₁, y₁)
613 // For the first point P, we want to compute: (2A + P) = (A + P) + A
614 // We first need to check if x ≠ x₁.
615 x().assert_is_not_equal(add[0].x3_prev, "biggroup::multiple_montgomery_ladder: x-coordinates must be distinct.");
616
617 // Compute λ₁ for computing the first addition: (A + P)
618 Fq lambda1;
619 if (!add[0].is_full_element) {
620 // Case 1: P is an accumulator (i.e., it lacks a y-coordinate)
621 // λ₁ = (y - y₁) / (x - x₁)
622 // = -(y₁ - y) / (x - x₁)
623 // = -(λ₁_ₚᵣₑᵥ * (x₁_ₚᵣₑᵥ - x₁) - y₁_ₚᵣₑᵥ - y) / (x - x₁)
624 //
625 // NOTE: msub_div computes -(∑ᵢ aᵢ * bᵢ + ∑ⱼcⱼ) / d
626 lambda1 = Fq::msub_div({ add[0].lambda_prev }, // numerator left multiplicands: λ₁_ₚᵣₑᵥ
627 { add[0].x1_prev - add[0].x3_prev }, // numerator right multiplicands: (x₁_ₚᵣₑᵥ - x₁)
628 (x() - add[0].x3_prev), // denominator: (x - x₁)
629 { -add[0].y1_prev, -y() }, // numerator additions: -y₁_ₚᵣₑᵥ - y
630 /*enable_divisor_nz_check*/ false); // divisor check is not needed as x ≠ x₁ is enforced
631 } else {
632 // Case 2: P is a full element (i.e., it has a y-coordinate)
633 // λ₁ = (y - y₁) / (x - x₁)
634 //
635 lambda1 = Fq::div_without_denominator_check({ y() - add[0].y3_prev }, (x() - add[0].x3_prev));
636 }
637
638 // Using λ₁, compute x₃ for (A + P):
639 // x₃ = λ₁.λ₁ - x₁ - x
640 Fq x_3 = lambda1.madd(lambda1, { -add[0].x3_prev, -x() });
641
642 // Compute λ₂ for the addition (A + P) + A:
643 // λ₂ = (y - y₃) / (x - x₃)
644 // = (y - (λ₁ * (x - x₃) - y)) / (x - x₃) (substituting y₃)
645 // = (2y) / (x - x₃) - λ₁
646 //
647 x().assert_is_not_equal(x_3, "biggroup::multiple_montgomery_ladder: x-coordinates must be distinct.");
648 Fq lambda2 = Fq::div_without_denominator_check({ y() + y() }, (x() - x_3)) - lambda1;
649
650 // Using λ₂, compute x₄ for the final result:
651 // x₄ = λ₂.λ₂ - x₃ - x
652 Fq x_4 = lambda2.sqradd({ -x_3, -x() });
653
654 // Compute y₄ for the final result:
655 // y₄ = λ₂ * (x - x₄) - y
656 //
657 // However, we don't actually compute y₄ here. Instead, we build a "composite" y value that contains
658 // the components needed to compute y₄ later. This is done to avoid the explicit multiplication here.
659 //
660 // We store the result as either y₄ or -y₄, depending on whether the number of points added
661 // is even or odd. This sign adjustment simplifies the handling of subsequent additions in the loop below.
662 // +y₄ = λ₂ * (x - x₄) - y
663 // -y₄ = λ₂ * (x₄ - x) + y
664 const bool num_points_even = ((add.size() & 1ULL) == 0);
665 composite_y previous_y;
666 previous_y.add.emplace_back(num_points_even ? y() : -y());
667 previous_y.mul_left.emplace_back(lambda2);
668 previous_y.mul_right.emplace_back(num_points_even ? x_4 - x() : x() - x_4);
669 previous_y.is_negative = num_points_even;
670
671 // Handle remaining iterations (i > 0) in a loop
672 Fq previous_x = x_4;
673 for (size_t i = 1; i < add.size(); ++i) {
674 // Let x = previous_x, y = previous_y
675 // Let P = (xᵢ, yᵢ) be the next point to add (represented by add[i])
676 // Ensure x-coordinates are distinct: x ≠ xᵢ
677 previous_x.assert_is_not_equal(add[i].x3_prev,
678 "biggroup::multiple_montgomery_ladder: x-coordinates must be distinct.");
679
680 // Determine sign adjustment based on previous y's sign
681 // If the previous y was positive, we need to negate the y-component from add[i]
682 const bool negate_add_y = !previous_y.is_negative;
683
684 // Build λ₁ numerator components from previous composite y and current accumulator
685 std::vector<Fq> lambda1_left = previous_y.mul_left;
686 std::vector<Fq> lambda1_right = previous_y.mul_right;
687 std::vector<Fq> lambda1_add = previous_y.add;
688
689 if (!add[i].is_full_element) {
690 // Case 1: add[i] is an accumulator (lacks y-coordinate)
691 // λ₁ = (y - yᵢ) / (x - xᵢ)
692 // = -(yᵢ - y) / (x - xᵢ)
693 // = -(λᵢ_ₚᵣₑᵥ * (xᵢ_ₚᵣₑᵥ - xᵢ) - yᵢ_ₚᵣₑᵥ - y) / (x - xᵢ)
694 //
695 // If (previous) y is stored as positive, we compute λ₁ as:
696 // λ₁ = -(λᵢ_ₚᵣₑᵥ * (xᵢ - xᵢ_ₚᵣₑᵥ) + yᵢ_ₚᵣₑᵥ + y) / (xᵢ - x)
697 //
698 lambda1_left.emplace_back(add[i].lambda_prev);
699 lambda1_right.emplace_back(negate_add_y ? add[i].x3_prev - add[i].x1_prev
700 : add[i].x1_prev - add[i].x3_prev);
701 lambda1_add.emplace_back(negate_add_y ? add[i].y1_prev : -add[i].y1_prev);
702 } else {
703 // Case 2: add[i] is a full element (has y-coordinate)
704 // λ₁ = (yᵢ - y) / (xᵢ - x)
705 //
706 // If previous y is positive, we compute λ₁ as:
707 // λ₁ = -(y - yᵢ) / (xᵢ - x)
708 //
709 lambda1_add.emplace_back(negate_add_y ? -add[i].y3_prev : add[i].y3_prev);
710 }
711
712 // Compute λ₁
713 Fq denominator = negate_add_y ? add[i].x3_prev - previous_x : previous_x - add[i].x3_prev;
714 Fq lambda1 =
715 Fq::msub_div(lambda1_left, lambda1_right, denominator, lambda1_add, /*enable_divisor_nz_check*/ false);
716
717 // Using λ₁, compute x₃ for (previous + P):
718 // x₃ = λ₁.λ₁ - xᵢ - x
719 // y₃ = λ₁ * (x - x₃) - y (we don't compute this explicitly)
720 Fq x_3 = lambda1.madd(lambda1, { -add[i].x3_prev, -previous_x });
721
722 // Compute λ₂ using previous composite y
723 // λ₂ = (y - y₃) / (x - x₃)
724 // = (y - (λ₁ * (x - x₃) - y)) / (x - x₃) (substituting y₃)
725 // = (2y) / (x - x₃) - λ₁
726 // = -2(y / (x₃ - x)) - λ₁
727 //
728 previous_x.assert_is_not_equal(x_3, "biggroup::multiple_montgomery_ladder: x-coordinates must be distinct.");
729 Fq l2_denominator = previous_y.is_negative ? previous_x - x_3 : x_3 - previous_x;
730 Fq partial_lambda2 = Fq::msub_div(previous_y.mul_left,
731 previous_y.mul_right,
732 l2_denominator,
733 previous_y.add,
734 /*enable_divisor_nz_check*/ false);
735 partial_lambda2 = partial_lambda2 + partial_lambda2;
736 lambda2 = partial_lambda2 - lambda1;
737
738 // Using λ₂, compute x₄ for the final result of this iteration:
739 // x₄ = λ₂.λ₂ - x₃ - x
740 x_4 = lambda2.sqradd({ -x_3, -previous_x });
741
742 // Build composite y for this iteration
743 // y₄ = λ₂ * (x - x₄) - y
744 // However, we don't actually compute y₄ explicitly, we rather store components to compute it later.
745 // We store the result as either y₄ or -y₄, depending on the sign of previous_y.
746 // +y₄ = λ₂ * (x - x₄) - y
747 // -y₄ = λ₂ * (x₄ - x) + y
748 composite_y y_4;
749 y_4.is_negative = !previous_y.is_negative;
750 y_4.mul_left.emplace_back(lambda2);
751 y_4.mul_right.emplace_back(previous_y.is_negative ? previous_x - x_4 : x_4 - previous_x);
752
753 // Append terms from previous_y to y_4. We want to make sure the terms above are added into the start
754 // of y_4. This is to ensure they are cached correctly when
755 // `builder::evaluate_partial_non_native_field_multiplication` is called. (the 1st mul_left, mul_right elements
756 // will trigger builder::evaluate_non_native_field_multiplication
757 // when Fq::mult_madd is called - this term cannot be cached so we want to make sure it is unique)
758 std::copy(previous_y.mul_left.begin(), previous_y.mul_left.end(), std::back_inserter(y_4.mul_left));
759 std::copy(previous_y.mul_right.begin(), previous_y.mul_right.end(), std::back_inserter(y_4.mul_right));
760 std::copy(previous_y.add.begin(), previous_y.add.end(), std::back_inserter(y_4.add));
761
762 previous_x = x_4;
763 previous_y = y_4;
764 }
765
766 Fq x_out = previous_x;
767
768 BB_ASSERT(!previous_y.is_negative);
769
770 Fq y_out = Fq::mult_madd(previous_y.mul_left, previous_y.mul_right, previous_y.add);
771 // Use 4-arg constructor with is_infinity=false (montgomery ladder assumes valid, non-infinity points).
772 return element(x_out, y_out, bool_ct(x_out.get_context(), false), /*assert_on_curve=*/false);
773}
774
812template <typename C, class Fq, class Fr, class G>
814 const size_t num_rounds)
815{
816 constexpr typename G::affine_element offset_generator =
817 get_precomputed_generators<G, "biggroup offset generator", 1>()[0];
818
819 const uint256_t offset_multiplier = uint256_t(1) << uint256_t(num_rounds - 1);
820
821 const typename G::affine_element offset_generator_end = typename G::element(offset_generator) * offset_multiplier;
822
823 return std::make_pair<element, element>(element(offset_generator.x, offset_generator.y),
824 element(offset_generator_end.x, offset_generator_end.y));
825}
826
827template <typename C, class Fq, class Fr, class G>
829 const std::vector<Fr>& scalars,
830 const size_t max_num_bits)
831{
832 // Sanity checks
833 BB_ASSERT_GT(points.size(), 0ULL, "process_strauss_msm: points cannot be empty");
834 BB_ASSERT_EQ(points.size(), scalars.size(), "process_strauss_msm: points and scalars size mismatch");
835
836 // Check that all scalars are in range
837 for (const auto& scalar : scalars) {
838 const size_t num_scalar_bits = static_cast<size_t>(uint512_t(scalar.get_value()).get_msb()) + 1ULL;
839 BB_ASSERT_LTE(num_scalar_bits, max_num_bits, "process_strauss_msm: scalar out of range");
840 }
841
842 // Constant parameters
843 const size_t num_rounds = max_num_bits;
844 const size_t msm_size = scalars.size();
845
846 // Compute ROM lookup table for points. Example if we have 3 points G1, G2, G3:
847 // ┌───────┬─────────────────┐
848 // │ Index │ Point │
849 // ├───────┼─────────────────┤
850 // │ 0 │ G1 + G2 + G3 │
851 // │ 1 │ G1 + G2 - G3 │
852 // │ 2 │ G1 - G2 + G3 │
853 // │ 3 │ G1 - G2 - G3 │
854 // │ 4 │ -G1 + G2 + G3 │
855 // │ 5 │ -G1 + G2 - G3 │
856 // │ 6 │ -G1 - G2 + G3 │
857 // │ 7 │ -G1 - G2 - G3 │
858 // └───────┴─────────────────┘
859 batch_lookup_table point_table(points);
860
861 // Compute NAF representations of scalars
863 for (size_t i = 0; i < msm_size; ++i) {
864 naf_entries.emplace_back(compute_naf(scalars[i], num_rounds));
865 }
866
867 // We choose a deterministic offset generator based on the number of rounds.
868 // We compute both the initial and final offset generators: G_offset, 2ⁿ⁻¹ * G_offset.
869 const auto [offset_generator_start, offset_generator_end] = compute_offset_generators(num_rounds);
870
871 // Initialize accumulator with offset generator + first NAF column.
872 element accumulator =
873 element::chain_add_end(element::chain_add(offset_generator_start, point_table.get_chain_initial_entry()));
874
875 // Process 4 NAF entries per iteration (for the remaining (num_rounds - 1) rounds)
876 constexpr size_t num_rounds_per_iteration = 4;
877 const size_t num_iterations = numeric::ceil_div((num_rounds - 1), num_rounds_per_iteration);
878 const size_t num_rounds_per_final_iteration = (num_rounds - 1) - ((num_iterations - 1) * num_rounds_per_iteration);
879
880 for (size_t i = 0; i < num_iterations; ++i) {
882 const size_t inner_num_rounds =
883 (i != num_iterations - 1) ? num_rounds_per_iteration : num_rounds_per_final_iteration;
884 for (size_t j = 0; j < inner_num_rounds; ++j) {
885 // Gather the NAF columns for this iteration
886 std::vector<bool_ct> nafs(msm_size);
887 for (size_t k = 0; k < msm_size; ++k) {
888 nafs[k] = (naf_entries[k][(i * num_rounds_per_iteration) + j + 1]);
889 }
890 to_add.emplace_back(point_table.get_chain_add_accumulator(nafs));
891 }
892
893 // Once we have looked-up all points from the four NAF columns, we update the accumulator as:
894 // accumulator = 2.(2.(2.(2.accumulator + to_add[0]) + to_add[1]) + to_add[2]) + to_add[3]
895 // = 2⁴.accumulator + 2³.to_add[0] + 2².to_add[1] + 2¹.to_add[2] + to_add[3]
896 accumulator = accumulator.multiple_montgomery_ladder(to_add);
897 }
898
899 // Subtract the skew factors (if any)
900 for (size_t i = 0; i < msm_size; ++i) {
901 element skew = accumulator.subtract_internal(points[i]);
902 accumulator = accumulator.conditional_select(skew, naf_entries[i][num_rounds]);
903 }
904
905 // Subtract the scaled offset generator
906 accumulator = accumulator.subtract_internal(offset_generator_end);
907
908 return accumulator;
909}
910
947template <typename C, class Fq, class Fr, class G>
949 const std::vector<Fr>& _scalars,
950 const size_t max_num_bits,
951 const bool with_edgecases)
952{
953 // Sanity check input sizes
954 BB_ASSERT_GT(_points.size(), 0ULL, "biggroup batch_mul: no points provided for batch multiplication");
955 BB_ASSERT_EQ(_points.size(), _scalars.size(), "biggroup batch_mul: points and scalars size mismatch");
956
957 // Get builder context from input points (needed for creating infinity results)
958 C* builder = _points[0].get_context();
959
960 // Replace (∞, scalar) pairs by the pair (G, 0).
961 auto [points, scalars] = handle_points_at_infinity(_points, _scalars);
962
963 BB_ASSERT_LTE(points.size(), _points.size());
964 BB_ASSERT_EQ(points.size(),
965 scalars.size(),
966 "biggroup batch_mul: points and scalars size mismatch after handling points at infinity");
967
968 // If batch_mul actually performs batch multiplication on the points and scalars, subprocedures can do
969 // operations like addition or subtraction of points, which can trigger OriginTag security mechanisms
970 // even though the final result satisfies the security logic. For example
971 // result = submitted_in_round_0 * challenge_from_round_0 + submitted_in_round_1 * challenge_in_round_1
972 // will trigger it, because the addition of submitted_in_round_0 to submitted_in_round_1 is dangerous by itself.
973 // To avoid this, we remove the tags, merge them separately and set the result appropriately
974 OriginTag tag = OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
975 auto empty_tag = OriginTag::constant(); // Disable origin checking during intermediate operations
976 for (size_t i = 0; i < _points.size(); i++) {
977 tag = OriginTag(tag, OriginTag(_points[i].get_origin_tag(), _scalars[i].get_origin_tag()));
978 }
979 for (size_t i = 0; i < scalars.size(); i++) {
980 points[i].set_origin_tag(empty_tag);
981 scalars[i].set_origin_tag(empty_tag);
982 }
983
984 // Accumulate constant-constant pairs out of circuit
985 bool has_constant_terms = false;
986 typename G::element constant_accumulator = G::element::infinity();
987 std::vector<element> new_points;
988 std::vector<Fr> new_scalars;
989 for (size_t i = 0; i < points.size(); ++i) {
990 if (points[i].is_constant() && scalars[i].is_constant()) {
991 const auto& point_value = typename G::element(points[i].get_value());
992 const auto& scalar_value = typename G::Fr(scalars[i].get_value());
993 constant_accumulator += (point_value * scalar_value);
994 has_constant_terms = true;
995 } else {
996 new_points.emplace_back(points[i]);
997 new_scalars.emplace_back(scalars[i]);
998 }
999 }
1000 points = new_points;
1001 scalars = new_scalars;
1002
1003 if (with_edgecases && !points.empty()) {
1004 // If points are linearly dependent, we randomise them using a free-witness offset generator.
1005 // We do this to ensure that the x-coordinates of the points are all distinct. This is required
1006 // while creating the ROM lookup table with the points.
1007 std::tie(points, scalars) = mask_points(points, scalars);
1008 }
1009
1011 points.size(), scalars.size(), "biggroup batch_mul: points and scalars size mismatch after handling edgecases");
1012
1013 // Separate out zero scalars and corresponding points (because NAF(0) = NAF(modulus) which is 254 bits long)
1014 // Also add the last point and scalar to big_points and big_scalars (because its a 254-bit scalar)
1015 // We do this only if max_num_bits != 0 (i.e. we are not forced to use 254 bits anyway)
1016 const size_t original_size = scalars.size();
1017 std::vector<Fr> big_scalars;
1018 std::vector<element> big_points;
1019 std::vector<Fr> small_scalars;
1020 std::vector<element> small_points;
1021 for (size_t i = 0; i < original_size; ++i) {
1022 if (max_num_bits == 0) {
1023 big_points.emplace_back(points[i]);
1024 big_scalars.emplace_back(scalars[i]);
1025 } else {
1026 const bool is_last_scalar_big = ((i == original_size - 1) && with_edgecases);
1027 if (scalars[i].get_value() == 0 || is_last_scalar_big) {
1028 big_points.emplace_back(points[i]);
1029 big_scalars.emplace_back(scalars[i]);
1030 } else {
1031 small_points.emplace_back(points[i]);
1032 small_scalars.emplace_back(scalars[i]);
1033 }
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
element add_internal(const element &other) const
Internal implementation of ECC point addition.
element multiple_montgomery_ladder(const std::vector< chain_add_accumulator > &to_add) const
Perform repeated iterations of the montgomery ladder algorithm.
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
void set_origin_tag(OriginTag tag) const
Definition biggroup.hpp:408
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
element subtract_internal(const element &other) const
Internal implementation of ECC point subtraction.
element get_standard_form() const
Enforce x and y coordinates of a point to be (0, 0) in the case of point at infinity.
bool_t< Builder > is_zero() const
Validate whether a field_t element is zero.
Definition field.cpp:783
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:306
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...
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:821
curve::BN254::BaseField Fq