Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
cycle_group.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Luke], commit: a48c205d6dcd4338f5b83b4fda18bff6015be07b}
3// external_1: { status: Complete, auditors: [Sherlock], commit: 13c1b927c6c }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#include "../field/field.hpp"
8#include "../field/field_utils.hpp"
14
15#include "./cycle_group.hpp"
21namespace bb::stdlib {
22
28template <typename Builder> cycle_group<Builder>::cycle_group(Builder* _context)
29{
30 *this = constant_infinity(_context);
31}
32
40template <typename Builder>
41cycle_group<Builder>::cycle_group(const field_t& x, const field_t& y, bool assert_on_curve)
42 : _x(x)
43 , _y(y)
44 , context(nullptr)
45{
46 if (_x.get_context() != nullptr) {
48 } else if (_y.get_context() != nullptr) {
50 }
51
52 // Auto-detect infinity: point is at infinity iff both coordinates are zero
53 // we can check this efficiently using the observation that:
54 // (x^2 + 5 * y^2 = 0) has no non-trivial solutions in fr, since fr modulus p == 2 mod 5
55 const field_t x_sqr = _x.sqr();
56 const field_t five_y = _y * bb::fr(5);
57 _is_infinity = (_y.madd(five_y, x_sqr).is_zero());
58
59 // For the simplicity of methods in this class, we ensure that the coordinates of a point always have the same
60 // constancy. If they don't, we convert the non-constant coordinate to a fixed witness. Should be rare.
61 if (_x.is_constant() != _y.is_constant()) {
62 if (_x.is_constant()) {
64 } else {
66 }
67 }
68
69 // Elements are always expected to be on the curve but may or may not be constrained as such.
70 BB_ASSERT(get_value().on_curve(), "cycle_group: Point is not on curve");
71 if (assert_on_curve) {
73 }
74}
75
85template <typename Builder>
86cycle_group<Builder>::cycle_group(const field_t& x, const field_t& y, bool_t is_infinity, bool assert_on_curve)
87 : _x(x)
88 , _y(y)
89 , _is_infinity(is_infinity)
90{
91 if (_x.get_context() != nullptr) {
92 context = _x.get_context();
93 } else if (_y.get_context() != nullptr) {
94 context = _y.get_context();
95 } else {
96 context = is_infinity.get_context();
97 }
98
99 if (is_infinity.is_constant() && is_infinity.get_value()) {
100 *this = constant_infinity(this->context);
101 }
102
103 // For the simplicity of methods in this class, we ensure that the coordinates of a point always have the same
104 // constancy. If they don't, we convert the non-constant coordinate to a fixed witness. Should be rare.
105 if (_x.is_constant() != _y.is_constant()) {
106 if (_x.is_constant()) {
107 _x.convert_constant_to_fixed_witness(context);
108 } else {
109 _y.convert_constant_to_fixed_witness(context);
110 }
112
113 // If both coordinates are constant, is_infinity must also be constant.
114 BB_ASSERT(!(_x.is_constant() && _y.is_constant() && !_is_infinity.is_constant()),
115 "cycle_group: constant coordinates with non-constant infinity flag");
117 // Elements are always expected to be on the curve but may or may not be constrained as such.
118 BB_ASSERT(get_value().on_curve(), "cycle_group: Point is not on curve");
119 if (assert_on_curve) {
120 validate_on_curve();
121 }
122}
123
133template <typename Builder>
135 : _x(_in.is_point_at_infinity() ? 0 : _in.x)
136 , _y(_in.is_point_at_infinity() ? 0 : _in.y)
137 , _is_infinity(_in.is_point_at_infinity())
138 , context(nullptr)
139{}
140
148template <typename Builder> cycle_group<Builder> cycle_group<Builder>::one(Builder* _context)
150 field_t x(_context, Group::one.x);
151 field_t y(_context, Group::one.y);
152 // Generator point is known to be on the curve
153 return cycle_group<Builder>(x, y, /*assert_on_curve=*/false);
161{
162 // Use the AffineElement constructor with an infinity point
163 // This properly sets (0, 0) coordinates and is_infinity = true
164 cycle_group result(AffineElement::infinity());
165
166 // If context provided, create field_t/bool_t with that context
167 if (_context != nullptr) {
168 result._x = field_t(_context, bb::fr(0));
169 result._y = field_t(_context, bb::fr(0));
170 result._is_infinity = bool_t(_context, true);
171 result.context = _context;
172 }
173
174 return result;
175}
176
188template <typename Builder>
190{
191 // By convention we set the coordinates of the point at infinity to (0,0).
193 : field_t::from_witness(_context, _in.x);
195 : field_t::from_witness(_context, _in.y);
196 // The constructor auto-detects infinity from coordinates and validates on curve.
197 cycle_group result(x_val, y_val, /*assert_on_curve=*/true);
198 result.set_free_witness_tag();
199 return result;
200}
201
214template <typename Builder>
216{
217 cycle_group result(_context);
218
219 if (_in.is_point_at_infinity()) {
220 result = constant_infinity(_context);
221 } else {
222 result._x = field_t::from_witness(_context, _in.x);
223 result._y = field_t::from_witness(_context, _in.y);
224 result._x.assert_equal(result._x.get_value());
225 result._y.assert_equal(result._y.get_value());
226 }
227 // point at infinity is circuit constant
228 result._is_infinity = _in.is_point_at_infinity();
229 result.unset_free_witness_tag();
230 return result;
231}
233template <typename Builder> Builder* cycle_group<Builder>::get_context(const cycle_group& other) const
234{
235 if (get_context() != nullptr) {
236 return get_context();
237 }
238 return other.get_context();
240
242{
243 AffineElement result(x().get_value(), y().get_value());
244 if (is_point_at_infinity().get_value()) {
245 result.self_set_infinity();
246 }
247 return result;
248}
249
257template <typename Builder> void cycle_group<Builder>::validate_on_curve() const
258{
259 // This class is for short Weierstrass curves only!
260 static_assert(Group::curve_a == 0);
261 auto xx = _x * _x;
262 auto xxx = xx * _x;
263 auto res = _y.madd(_y, -xxx - Group::curve_b);
264 // If this is the point at infinity, then res is changed to 0, otherwise it remains unchanged
265 res *= !is_point_at_infinity();
266 res.assert_is_zero();
267}
268
274template <typename Builder> void cycle_group<Builder>::standardize()
275{
276 // Set coordinates to (0, 0) if point is at infinity for canonical representation
277 this->_x = field_t::conditional_assign(this->_is_infinity, 0, this->_x);
278 this->_y = field_t::conditional_assign(this->_is_infinity, 0, this->_y);
279 // Mark witnesses as used to prevent boomerang detection false positives when
280 // this is the final operation (e.g., final batch_mul output)
281 mark_witness_as_used(this->_x);
282 mark_witness_as_used(this->_y);
283}
284
297template <typename Builder>
299{
300 // If this is a constant point at infinity, return early
301 if (this->is_constant_point_at_infinity()) {
302 return *this;
303 }
304
305 // To support the point at infinity, we conditionally modify y to be 1 to avoid division by zero in the
306 // doubling formula
307 auto modified_y = field_t::conditional_assign(is_point_at_infinity(), 1, _y);
308
309 // Compute the doubled point coordinates (either from hint or by native calculation)
310 bb::fr x3;
311 bb::fr y3;
312 if (hint.has_value()) {
313 x3 = hint.value().x;
314 y3 = hint.value().y;
315 } else {
316 const bb::fr x1 = _x.get_value();
317 const bb::fr y1 = modified_y.get_value();
318
319 // N.B. the formula to derive the witness value for x3 mirrors the formula in elliptic_relation.hpp
320 // Specifically, we derive x^4 via the Short Weierstrass curve formula y^2 = x^3 + b i.e. x^4 = x * (y^2 - b)
321 // We must follow this pattern exactly to support the edge-case where the input is the point at infinity.
322 const bb::fr y_pow_2 = y1.sqr();
323 const bb::fr x_pow_4 = x1 * (y_pow_2 - Group::curve_b);
324 const bb::fr lambda_squared = (x_pow_4 * 9) / (y_pow_2 * 4);
325 const bb::fr lambda = (x1 * x1 * 3) / (y1 + y1);
326 x3 = lambda_squared - x1 - x1;
327 y3 = lambda * (x1 - x3) - y1;
328 }
329
330 // Construct the doubled point based on whether this is a constant or witness
331 cycle_group result;
332 if (is_constant()) {
333 result = cycle_group(x3, y3, is_point_at_infinity(), /*assert_on_curve=*/false);
334 // Propagate the origin tag as-is
335 result.set_origin_tag(get_origin_tag());
336 } else {
337 // Create result witness and construct ECC double constraint
338 result = cycle_group(
339 witness_t(context, x3), witness_t(context, y3), is_point_at_infinity(), /*assert_on_curve=*/false);
340
341 context->create_ecc_dbl_gate(bb::ecc_dbl_gate_<bb::fr>{
342 .x1 = _x.get_witness_index(),
343 .y1 = modified_y.get_witness_index(),
344 .x3 = result._x.get_witness_index(),
345 .y3 = result._y.get_witness_index(),
346 });
347
348 // Merge the x and y origin tags since the output depends on both
349 result._x.set_origin_tag(OriginTag(_x.get_origin_tag(), _y.get_origin_tag()));
350 result._y.set_origin_tag(OriginTag(_x.get_origin_tag(), _y.get_origin_tag()));
351 }
352
353 return result;
354}
355
368template <typename Builder>
370 bool is_addition,
371 const std::optional<AffineElement> hint) const
372{
373 // Reject point-at-infinity operands: the incomplete ecc_add_gate is degenerate at (0, 0) (the
374 // canonical witness representation of infinity) and admits forged outputs there.
375 this->is_point_at_infinity().assert_equal(
376 false, "cycle_group::_unconditional_add_or_subtract called on point at infinity");
378 false, "cycle_group::_unconditional_add_or_subtract called on point at infinity");
379
380 auto context = get_context(other);
381
382 // If one point is a witness and the other is a constant, convert the constant to a fixed witness then call this
383 // method again so we can use the ecc_add gate
384 const bool lhs_constant = is_constant();
385 const bool rhs_constant = other.is_constant();
386
387 if (lhs_constant && !rhs_constant) {
388 auto lhs = cycle_group::from_constant_witness(context, get_value());
389 lhs.set_origin_tag(get_origin_tag()); // Maintain the origin tag
390 return lhs._unconditional_add_or_subtract(other, is_addition, hint);
391 }
392 if (!lhs_constant && rhs_constant) {
394 rhs.set_origin_tag(other.get_origin_tag()); // Maintain the origin tag
395 return _unconditional_add_or_subtract(rhs, is_addition, hint);
396 }
397
398 // Compute the result coordinates (either from hint or by native calculation)
399 bb::fr x3;
400 bb::fr y3;
401 if (hint.has_value()) {
402 x3 = hint.value().x;
403 y3 = hint.value().y;
404 } else {
405 const AffineElement p1 = get_value();
406 const AffineElement p2 = other.get_value();
407 AffineElement p3 = is_addition ? (Element(p1) + Element(p2)) : (Element(p1) - Element(p2));
408 x3 = p3.x;
409 y3 = p3.y;
410 }
411
412 // Construct the result based on whether inputs are constant or witness
413 // Note: this code path is for non-infinity points, so result cannot be at infinity
414 // We use the private 4-arg constructor to explicitly set is_infinity=false, avoiding the
415 // auto-detection gates that the 2-arg constructor would add.
416 cycle_group result;
417 if (lhs_constant && rhs_constant) {
418 result = cycle_group(x3, y3, /*is_infinity=*/bool_t(false), /*assert_on_curve=*/false);
419 } else {
420 // Both points are witnesses - create result witness and construct ECC add constraint
421 result = cycle_group(
422 witness_t(context, x3), witness_t(context, y3), /*is_infinity=*/false, /*assert_on_curve=*/false);
423
424 context->create_ecc_add_gate(bb::ecc_add_gate_{
425 .x1 = _x.get_witness_index(),
426 .y1 = _y.get_witness_index(),
427 .x2 = other._x.get_witness_index(),
428 .y2 = other._y.get_witness_index(),
429 .x3 = result._x.get_witness_index(),
430 .y3 = result._y.get_witness_index(),
431 .is_addition = is_addition,
432 });
433 }
434
435 // Merge the origin tags from both inputs
436 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
437
438 return result;
439}
440
447template <typename Builder>
449 const std::optional<AffineElement> hint) const
450{
451 return _unconditional_add_or_subtract(other, /*is_addition=*/true, hint);
452}
453
460template <typename Builder>
462 const std::optional<AffineElement> hint) const
463{
464 return _unconditional_add_or_subtract(other, /*is_addition=*/false, hint);
465}
466
478template <typename Builder>
480 const std::optional<AffineElement> hint) const
481{
482 const field_t x_delta = this->_x - other._x;
483 if (x_delta.is_constant()) {
484 BB_ASSERT(x_delta.get_value() != 0);
485 } else {
486 x_delta.assert_is_not_zero("cycle_group::checked_unconditional_add, x-coordinate collision");
487 }
488 return unconditional_add(other, hint);
489}
490
503template <typename Builder>
505 const std::optional<AffineElement> hint) const
506{
507 const field_t x_delta = this->_x - other._x;
508 if (x_delta.is_constant()) {
509 BB_ASSERT(x_delta.get_value() != 0);
510 } else {
511 x_delta.assert_is_not_zero("cycle_group::checked_unconditional_subtract, x-coordinate collision");
512 }
513 return unconditional_subtract(other, hint);
514}
515
531template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator+(const cycle_group& other) const
532{
533 // If lhs is constant point at infinity, return the rhs and vice versa
534 if (this->is_constant_point_at_infinity()) {
535 return other;
536 }
537 if (other.is_constant_point_at_infinity()) {
538 return *this;
539 }
540
541 const bool_t x_coordinates_match = (_x == other._x);
542 const bool_t y_coordinates_match = (_y == other._y);
543
544 const field_t x1 = _x;
545 const field_t y1 = _y;
546 const field_t x2 = other._x;
547 const field_t y2 = other._y;
548
549 // Execute point addition with modified lambda = (y2 - y1)/(x2 - x1 + x_coordinates_match) to avoid the possibility
550 // of division by zero.
551 const field_t x_diff = x2.add_two(-x1, x_coordinates_match);
552 // Compute lambda in one of two ways depending on whether either numerator or denominator is constant or not
553 field_t lambda;
554 if ((y1.is_constant() && y2.is_constant()) || x_diff.is_constant()) {
555 lambda = (y2 - y1).divide_no_zero_check(x_diff);
556 } else {
557 // Note: branch saves one gate vs just using divide_no_zero_check because we avoid computing y2 - y1 in circuit
558 Builder* context = get_context(other);
559 lambda = field_t::from_witness(context, (y2.get_value() - y1.get_value()) / x_diff.get_value());
560 // We need to manually propagate the origin tag
562 // Constrain x_diff * lambda = y2 - y1
563 field_t::evaluate_polynomial_identity(x_diff, lambda, -y2, y1);
564 }
565 const field_t add_result_x = lambda.madd(lambda, -(x2 + x1)); // x3 = lambda^2 - x1 - x2
566 const field_t add_result_y = lambda.madd(x1 - add_result_x, -y1); // y3 = lambda * (x1 - x3) - y1
567
568 // Compute the doubling result
569 const cycle_group dbl_result = dbl();
570
571 // If the addition amounts to a doubling then the result is the doubling result, else the addition result.
572 const bool_t double_predicate = (x_coordinates_match && y_coordinates_match);
573 auto result_x = field_t::conditional_assign(double_predicate, dbl_result._x, add_result_x);
574 auto result_y = field_t::conditional_assign(double_predicate, dbl_result._y, add_result_y);
575
576 // If the lhs is the point at infinity, return rhs
577 const bool_t lhs_infinity = is_point_at_infinity();
578 result_x = field_t::conditional_assign(lhs_infinity, other._x, result_x);
579 result_y = field_t::conditional_assign(lhs_infinity, other._y, result_y);
580
581 // If the rhs is the point at infinity, return lhs
582 const bool_t rhs_infinity = other.is_point_at_infinity();
583 result_x = field_t::conditional_assign(rhs_infinity, _x, result_x);
584 result_y = field_t::conditional_assign(rhs_infinity, _y, result_y);
585
586 // The result is the point at infinity if:
587 // (lhs._x, lhs._y) == (rhs._x, -rhs._y) and neither are infinity, OR both are the point at infinity
588 const bool_t infinity_predicate = (x_coordinates_match && !y_coordinates_match);
589 bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
590 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
591
592 return cycle_group(result_x, result_y, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
593}
594
610template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator-(const cycle_group& other) const
611{
612 // If lhs is constant point at infinity, return -rhs
613 if (this->is_constant_point_at_infinity()) {
614 return -other;
615 }
616 // If rhs is constant point at infinity, return the lhs
617 if (other.is_constant_point_at_infinity()) {
618 return *this;
619 }
620
621 Builder* context = get_context(other);
622
623 const bool_t x_coordinates_match = (_x == other._x);
624 const bool_t y_coordinates_match = (_y == other._y);
625
626 const field_t x1 = _x;
627 const field_t y1 = _y;
628 const field_t x2 = other._x;
629 const field_t y2 = other._y;
630
631 // Execute point addition with modified lambda = (-y2 - y1)/(x2 - x1 + x_coordinates_match) to avoid the possibility
632 // of division by zero.
633 const field_t x_diff = x2.add_two(-x1, x_coordinates_match);
634 // Compute lambda in one of two ways depending on whether either numerator or denominator is constant or not
635 field_t lambda;
636 if ((y1.is_constant() && y2.is_constant()) || x_diff.is_constant()) {
637 lambda = (-y2 - y1).divide_no_zero_check(x_diff);
638 } else {
639 // Note: branch saves one gate vs using divide_no_zero_check because we avoid computing (-y2 - y1) in circuit
640 lambda = field_t::from_witness(context, (-y2.get_value() - y1.get_value()) / x_diff.get_value());
641 // We need to manually propagate the origin tag
643 // Constrain x_diff * lambda = -y2 - y1
644 field_t::evaluate_polynomial_identity(x_diff, lambda, y2, y1);
645 }
646 const field_t sub_result_x = lambda.madd(lambda, -(x2 + x1)); // x3 = lambda^2 - x1 - x2
647 const field_t sub_result_y = lambda.madd(x1 - sub_result_x, -y1); // y3 = lambda * (x1 - x3) - y1
648
649 // Compute the doubling result
650 const cycle_group dbl_result = dbl();
651
652 // If the subtraction amounts to a doubling then the result is the doubling result, else the subtraction result.
653 // Note: The assumption here is that x1 == x2 && y1 != y2 implies y1 == -y2 which is true assuming that the points
654 // are both on the curve.
655 const bool_t double_predicate = (x_coordinates_match && !y_coordinates_match);
656 auto result_x = field_t::conditional_assign(double_predicate, dbl_result._x, sub_result_x);
657 auto result_y = field_t::conditional_assign(double_predicate, dbl_result._y, sub_result_y);
658
659 mark_witness_as_used(result_x);
660 mark_witness_as_used(result_y);
661
662 // If the lhs is the point at infinity, return -rhs
663 const bool_t lhs_infinity = is_point_at_infinity();
664 result_x = field_t::conditional_assign(lhs_infinity, other._x, result_x);
665 result_y = field_t::conditional_assign(lhs_infinity, (-other._y), result_y);
666
667 // If the rhs is the point at infinity, return lhs
668 const bool_t rhs_infinity = other.is_point_at_infinity();
669 result_x = field_t::conditional_assign(rhs_infinity, _x, result_x);
670 result_y = field_t::conditional_assign(rhs_infinity, _y, result_y);
671
672 // The result is the point at infinity if:
673 // (lhs.x, lhs.y) == (rhs.x, rhs.y) and neither are infinity, OR both are the point at infinity
674 const bool_t infinity_predicate = (x_coordinates_match && y_coordinates_match);
675 mark_witness_as_used(field_t(infinity_predicate));
676 bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
677 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
678
679 return cycle_group(result_x, result_y, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
680}
681
689template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator-() const
690{
691 cycle_group result(*this);
692 // We have to normalize immediately. All the methods, related to
693 // elliptic curve operations, assume that the coordinates are in normalized form and
694 // don't perform any extra normalizations
695 result._y = (-_y).normalize();
696 return result;
697}
698
699template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator+=(const cycle_group& other)
700{
701 *this = *this + other;
702 return *this;
703}
704
705template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator-=(const cycle_group& other)
706{
707 *this = *this - other;
708 return *this;
709}
710
731template <typename Builder>
733 const std::span<cycle_scalar> scalars,
734 const std::span<cycle_group> base_points,
735 const std::span<AffineElement const> offset_generators,
736 const bool unconditional_add)
737{
738 BB_ASSERT_EQ(!scalars.empty(), true, "Empty scalars provided to variable base batch mul!");
739 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in variable base batch mul!");
740 BB_ASSERT_EQ(offset_generators.size(), base_points.size() + 1, "Too few offset generators provided!");
741 const size_t num_points = scalars.size();
742
743 // Extract the circuit context from any scalar or point
744 Builder* context = nullptr;
745 for (auto [scalar, point] : zip_view(scalars, base_points)) {
746 if (context = scalar.get_context(); context != nullptr) {
747 break;
748 }
749 if (context = point.get_context(); context != nullptr) {
750 break;
751 }
752 }
753
754 // All cycle_scalars are guaranteed to be 254 bits
755 constexpr size_t num_rounds = numeric::ceil_div(cycle_scalar::NUM_BITS, ROM_TABLE_BITS);
756
757 // Decompose each scalar into 4-bit slices. Note: This operation enforces range constraints on the lo/hi limbs of
758 // each scalar (LO_BITS and (num_bits - LO_BITS) respectively).
760 scalar_slices.reserve(num_points);
761 for (const auto& scalar : scalars) {
762 scalar_slices.emplace_back(context, scalar, ROM_TABLE_BITS);
763 }
764
765 // Compute the result of each point addition involved in the Straus MSM algorithm natively so they can be used as
766 // "hints" in the in-circuit Straus algorithm. This includes the additions needed to construct the point tables and
767 // those needed to compute the MSM via Straus. Points are computed as Element types with a Z-coordinate then
768 // batch-converted to AffineElement types. This avoids the need to compute modular inversions for every group
769 // operation, which dramatically reduces witness generation times.
770 std::vector<Element> operation_transcript;
771 Element offset_generator_accumulator = offset_generators[0];
772 {
773 // For each point, construct native straus lookup table of the form {G , G + [1]P, G + [2]P, ... , G + [15]P}
774 std::vector<std::vector<Element>> native_straus_tables;
775 for (size_t i = 0; i < num_points; ++i) {
776 AffineElement point = base_points[i].get_value();
777 AffineElement offset = offset_generators[i + 1];
778 std::vector<Element> table = straus_lookup_table::compute_native_table(point, offset, ROM_TABLE_BITS);
779 // Copy all but the first entry (the offset generator) into the operation transcript for use as hints
780 std::copy(table.begin() + 1, table.end(), std::back_inserter(operation_transcript));
781 native_straus_tables.emplace_back(std::move(table));
782 }
783
784 // Perform the Straus algorithm natively to generate the witness values (hints) for all intermediate points
785 Element accumulator = offset_generators[0];
786 for (size_t i = 0; i < num_rounds; ++i) {
787 if (i != 0) {
788 // Perform doublings of the accumulator and offset generator accumulator
789 for (size_t j = 0; j < ROM_TABLE_BITS; ++j) {
790 accumulator = accumulator.dbl();
791 operation_transcript.push_back(accumulator);
792 offset_generator_accumulator = offset_generator_accumulator.dbl();
793 }
794 }
795 for (size_t j = 0; j < num_points; ++j) {
796 // Look up and accumulate the appropriate point for this scalar slice
797 auto slice_value = static_cast<size_t>(scalar_slices[j].slices_native[num_rounds - i - 1]);
798 const Element point = native_straus_tables[j][slice_value];
799 accumulator += point;
800
801 // Populate hint and update offset generator accumulator
802 operation_transcript.push_back(accumulator);
803 offset_generator_accumulator += Element(offset_generators[j + 1]);
804 }
805 }
806 }
807
808 // Normalize the computed witness points and convert them into AffineElements
809 Element::batch_normalize(&operation_transcript[0], operation_transcript.size());
810 std::vector<AffineElement> operation_hints;
811 operation_hints.reserve(operation_transcript.size());
812 for (const Element& element : operation_transcript) {
813 operation_hints.emplace_back(element.x, element.y);
814 }
815
816 // Construct an in-circuit Straus lookup table for each point
818 point_tables.reserve(num_points);
819 const size_t hints_per_table = (1ULL << ROM_TABLE_BITS) - 1;
820 for (size_t i = 0; i < num_points; ++i) {
821 // Construct Straus table
822 std::span<AffineElement> table_hints(&operation_hints[i * hints_per_table], hints_per_table);
823 straus_lookup_table table(context, base_points[i], offset_generators[i + 1], ROM_TABLE_BITS, table_hints);
824 point_tables.push_back(std::move(table));
825 }
826
827 // Initialize pointer to the precomputed Straus algorithm hints (stored just after the table construction hints)
828 AffineElement* hint_ptr = &operation_hints[num_points * hints_per_table];
829 cycle_group accumulator = offset_generators[0];
830
831 // Execute Straus algorithm in-circuit using the precomputed hints.
832 // If unconditional_add == false, accumulate x-coordinate differences to batch-validate no collisions.
833 // If unconditional_add == true (constant inputs), no per-slice check is added; soundness relies on the
834 // offset-generator domain separator keeping per-slice operands distinct.
835 field_t coordinate_check_product = 1;
836 for (size_t i = 0; i < num_rounds; ++i) {
837 // Double the accumulator ROM_TABLE_BITS times (except in first round)
838 if (i != 0) {
839 for (size_t j = 0; j < ROM_TABLE_BITS; ++j) {
840 accumulator = accumulator.dbl(*hint_ptr);
841 hint_ptr++;
842 }
843 }
844 // Add the contribution from each point's scalar slice for this round
845 for (size_t j = 0; j < num_points; ++j) {
846 const field_t scalar_slice = scalar_slices[j][num_rounds - i - 1];
847 BB_ASSERT_EQ(scalar_slice.get_value(), scalar_slices[j].slices_native[num_rounds - i - 1]); // Sanity check
848 const cycle_group point = point_tables[j].read(scalar_slice);
849 if (!unconditional_add) {
850 coordinate_check_product *= point._x - accumulator._x;
851 }
852 accumulator = accumulator.unconditional_add(point, *hint_ptr);
853 hint_ptr++;
854 }
855 }
856
857 // Batch-validate no x-coordinate collisions occurred. We batch because each assert_is_not_zero
858 // requires an expensive modular inversion during witness generation.
859 if (!unconditional_add) {
860 coordinate_check_product.assert_is_not_zero("_variable_base_batch_mul_internal x-coordinate collision");
861 }
862
863 // Set CONSTANT tag on accumulator to avoid tag conflicts during intermediate operations
864 // The final tag will be set in batch_mul from result_tag
865 accumulator.set_origin_tag(OriginTag::constant());
866
867 // Note: offset_generator_accumulator represents the sum of all the offset generator terms present in `accumulator`.
868 // We don't subtract it off yet as we may be able to combine it with other constant terms in `batch_mul` before
869 // performing the subtraction.
870 return { accumulator, AffineElement(offset_generator_accumulator) };
871}
872
896template <typename Builder>
898 const std::span<cycle_scalar> scalars, const std::span<AffineElement> base_points)
899{
900 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in fixed-base batch mul");
901
905
906 std::vector<MultiTableId> multitable_ids;
907 std::vector<field_t> scalar_limbs;
908
909 for (const auto [point, scalar] : zip_view(base_points, scalars)) {
910 std::array<MultiTableId, 2> table_id = table::get_lookup_table_ids_for_point(point);
911 multitable_ids.push_back(table_id[0]);
912 multitable_ids.push_back(table_id[1]);
913 scalar_limbs.push_back(scalar.lo());
914 scalar_limbs.push_back(scalar.hi());
915 }
916
917 // Look up the multiples of each slice of each lo/hi scalar limb in the corresponding plookup table.
918 std::vector<cycle_group> lookup_points;
919 Element offset_generator_accumulator = Group::point_at_infinity;
920 for (const auto [table_id, scalar] : zip_view(multitable_ids, scalar_limbs)) {
921 // Each lookup returns multiple EC points corresponding to different bit slices of the scalar.
922 // For a scalar slice s_i at bit position (table_bits*i), the table stores the point:
923 // P_i = [offset_generator_i] + (s_i * 2^(table_bits*i)) * [base_point]
925 for (size_t j = 0; j < lookup_data[ColumnIdx::C2].size(); ++j) {
926 const field_t x = lookup_data[ColumnIdx::C2][j];
927 const field_t y = lookup_data[ColumnIdx::C3][j];
928 // Lookup table points are never at infinity (they are precomputed points on the curve).
929 // Use private constructor to avoid auto-detection gates.
930 auto is_infinity = bool_t(x.get_context(), false);
931 lookup_points.push_back(cycle_group(x, y, is_infinity, /*assert_on_curve=*/false));
932 }
933 // Update offset accumulator with the total offset for the corresponding multitable
934 offset_generator_accumulator += table::get_generator_offset_for_table_id(table_id);
935 }
936
937 // Compute the witness values of the batch_mul algorithm natively, as Element types with a Z-coordinate.
938 std::vector<Element> operation_transcript;
939 {
940 Element accumulator = lookup_points[0].get_value();
941 for (size_t i = 1; i < lookup_points.size(); ++i) {
942 accumulator += (lookup_points[i].get_value());
943 operation_transcript.push_back(accumulator);
944 }
945 }
946 // Batch-convert to AffineElement types, and feed these points as "hints" into the in-circuit addition. This avoids
947 // the need to compute modular inversions for every group operation, which dramatically reduces witness generation
948 // times.
949 Element::batch_normalize(&operation_transcript[0], operation_transcript.size());
950 std::vector<AffineElement> operation_hints;
951 operation_hints.reserve(operation_transcript.size());
952 for (const Element& element : operation_transcript) {
953 operation_hints.emplace_back(element.x, element.y);
954 }
955
956 // Perform the in-circuit point additions sequentially. Each addition costs 1 gate iff additions are chained such
957 // that the output of each addition is the input to the next. Otherwise, each addition costs 2 gates.
958 // Set all lookup_points to have CONSTANT tags to avoid tag conflicts during intermediate operations
959 // The final tag will be set in batch_mul from result_tag
960 auto const_tag = OriginTag::constant();
961 for (auto& point : lookup_points) {
962 point.set_origin_tag(const_tag);
963 }
964 cycle_group accumulator = lookup_points[0];
965 for (size_t i = 1; i < lookup_points.size(); ++i) {
966 accumulator = accumulator.unconditional_add(lookup_points[i], operation_hints[i - 1]);
967 }
968
969 // The offset_generator_accumulator represents the sum of all the offset generator terms present in `accumulator`.
970 // We don't subtract off yet, as we may be able to combine `offset_generator_accumulator` with other constant
971 // terms in `batch_mul` before performing the subtraction.
972 return { accumulator, offset_generator_accumulator };
973}
974
988template <typename Builder>
990 const std::span<cycle_scalar> scalars,
991 const std::span<AffineElement const> base_points,
992 const std::span<AffineElement const> offset_generators,
993 const size_t table_bits)
994{
995 BB_ASSERT_EQ(!scalars.empty(), true, "Empty scalars provided to fixed base plookup batch mul!");
996 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in fixed base plookup batch mul!");
997 BB_ASSERT_EQ(offset_generators.size(), base_points.size() + 1, "Too few offset generators provided!");
998 const size_t num_points = scalars.size();
999
1000 Builder* context = nullptr;
1001 for (const auto& scalar : scalars) {
1002 if (context = scalar.get_context(); context != nullptr) {
1003 break;
1004 }
1005 }
1006 BB_ASSERT(context != nullptr);
1008 0UL,
1009 "table_bits must evenly divide cycle_scalar::LO_BITS. The Straus algorithm splits the scalar "
1010 "into lo/hi limbs and decomposes each separately; if LO_BITS is not a multiple of table_bits, "
1011 "the hi-limb slices start at the wrong bit-offset and the MSM result is incorrect. "
1012 "Valid values for table_bits (given LO_BITS=128) are: 1, 2, 4, 8, 16, 32, 64, 128.");
1013
1014 const size_t num_rounds = numeric::ceil_div(cycle_scalar::NUM_BITS, table_bits);
1015
1016 // Decompose each scalar into table_bits-bit slices (also enforces range constraints)
1018 scalar_slices.reserve(num_points);
1019 for (const auto& scalar : scalars) {
1020 scalar_slices.emplace_back(context, scalar, table_bits);
1021 }
1022
1023 // Create plookup tables for each constant base point (zero gate cost).
1024 // Phase 1 (parallel): compute native table entries and BasicTable column data — no builder access.
1025 // Phase 2 (serial): register each BasicTable with the builder (builder is not thread-safe).
1027 parallel_for(num_points, [&](size_t i) {
1028 precomputed_tables[i] =
1029 straus_plookup_table::build_precomputed_data(base_points[i], offset_generators[i + 1], table_bits);
1030 });
1032 point_tables.reserve(num_points);
1033 for (size_t i = 0; i < num_points; ++i) {
1034 point_tables.emplace_back(context, std::move(precomputed_tables[i]));
1035 }
1036
1037 // Compute all intermediate points natively for use as hints in the in-circuit Straus algorithm.
1038 // Using projective coordinates + batch normalize to avoid per-operation modular inversions.
1039 // The per-point lookup tables (point_tables) already hold the precomputed affine entries; we reuse
1040 // them directly rather than rebuilding projective copies.
1041 std::vector<Element> operation_transcript;
1042 Element offset_generator_accumulator = offset_generators[0];
1043 {
1044 // Perform Straus algorithm natively
1045 Element accumulator = offset_generators[0];
1046 for (size_t i = 0; i < num_rounds; ++i) {
1047 if (i != 0) {
1048 for (size_t j = 0; j < table_bits; ++j) {
1049 accumulator = accumulator.dbl();
1050 operation_transcript.push_back(accumulator);
1051 offset_generator_accumulator = offset_generator_accumulator.dbl();
1052 }
1053 }
1054 for (size_t j = 0; j < num_points; ++j) {
1055 auto slice_value = static_cast<size_t>(scalar_slices[j].slices_native[num_rounds - i - 1]);
1056 const Element point(point_tables[j].get_native_table()[slice_value]);
1057 accumulator += point;
1058 operation_transcript.push_back(accumulator);
1059 offset_generator_accumulator += Element(offset_generators[j + 1]);
1060 }
1061 }
1062 }
1063
1064 // Batch-normalize all hint points
1065 Element::batch_normalize(operation_transcript.data(), operation_transcript.size());
1066 std::vector<AffineElement> operation_hints;
1067 operation_hints.reserve(operation_transcript.size());
1068 for (const Element& element : operation_transcript) {
1069 operation_hints.emplace_back(element.x, element.y);
1070 }
1071
1072 // Execute Straus algorithm in-circuit using plookup reads and precomputed hints
1073 AffineElement* hint_ptr = operation_hints.data();
1074 cycle_group accumulator = offset_generators[0];
1075
1076 for (size_t i = 0; i < num_rounds; ++i) {
1077 if (i != 0) {
1078 for (size_t j = 0; j < table_bits; ++j) {
1079 accumulator = accumulator.dbl(*hint_ptr);
1080 hint_ptr++;
1081 }
1082 }
1083 for (size_t j = 0; j < num_points; ++j) {
1084 const field_t scalar_slice = scalar_slices[j][num_rounds - i - 1];
1085 const cycle_group point = point_tables[j].read(scalar_slice);
1086 // No per-slice x-collision check; soundness relies on the offset-generator domain separator
1087 // keeping `accumulator` distinct from any plookup table entry across the loop.
1088 accumulator = accumulator.unconditional_add(point, *hint_ptr);
1089 hint_ptr++;
1090 }
1091 }
1092
1093 accumulator.set_origin_tag(OriginTag::constant());
1094 return { accumulator, AffineElement(offset_generator_accumulator) };
1095}
1096
1109template <typename Builder>
1111 const std::vector<cycle_scalar>& scalars,
1113 const size_t table_bits)
1114{
1115 BB_ASSERT_EQ(scalars.size(), constant_points.size(), "Points/scalars size mismatch in fixed_batch_mul!");
1116
1117 if (scalars.empty()) {
1118 return cycle_group{ Group::point_at_infinity };
1119 }
1120
1121 // Merge all tags
1122 OriginTag result_tag = OriginTag::constant();
1123 for (auto [point, scalar] : zip_view(constant_points, scalars)) {
1124 result_tag = OriginTag(result_tag, OriginTag(point.get_origin_tag(), scalar.get_origin_tag()));
1125 }
1126
1127 std::vector<cycle_scalar> plookup_scalars;
1128 std::vector<AffineElement> plookup_points;
1129 bool has_non_constant_component = false;
1130 Element constant_acc = Group::point_at_infinity;
1131
1132 for (const auto [point, scalar] : zip_view(constant_points, scalars)) {
1133 BB_ASSERT(point.is_constant());
1134 if (scalar.is_constant()) {
1135 // Both constant: compute natively
1136 constant_acc += point.get_value() * scalar.get_value();
1137 } else {
1138 if (point.get_value().is_point_at_infinity()) {
1139 // Constant infinity contributes nothing, but still need range constraints on scalar
1140 auto* ctx = scalar.get_context();
1141 ctx->create_limbed_range_constraint(scalar.lo().get_witness_index(),
1143 table_bits,
1144 "fixed_batch_mul: lo range constraint for scalar with constant "
1145 "infinity");
1146 ctx->create_limbed_range_constraint(scalar.hi().get_witness_index(),
1148 table_bits,
1149 "fixed_batch_mul: hi range constraint for scalar with constant "
1150 "infinity");
1151 continue;
1152 }
1153 plookup_scalars.push_back(scalar);
1154 plookup_points.push_back(point.get_value());
1155 has_non_constant_component = true;
1156 }
1157 }
1158
1159 if (!has_non_constant_component) {
1160 auto result = cycle_group(constant_acc);
1161 result.set_origin_tag(result_tag);
1162 return result;
1163 }
1164
1165 // Compute offset generators
1166 const size_t num_offset_generators = plookup_points.size() + 1;
1167 const std::span<AffineElement const> offset_generators =
1168 context.generators->get(num_offset_generators, 0, OFFSET_GENERATOR_DOMAIN_SEPARATOR);
1169
1170 // Run the plookup-based Straus algorithm
1171 Element offset_accumulator = -constant_acc;
1172 const auto [accumulator, offset_generator_delta] =
1173 _fixed_base_plookup_batch_mul_internal(plookup_scalars, plookup_points, offset_generators, table_bits);
1174 offset_accumulator += offset_generator_delta;
1175
1176 // Note: No collision check on the offset subtraction; soundness relies on the offset-generator domain
1177 // separator keeping aggregate `offset_accumulator` distinct from `accumulator`.
1178 cycle_group result;
1179 if (!constant_acc.is_point_at_infinity()) {
1180 result = accumulator.unconditional_add(AffineElement(-offset_accumulator));
1181 } else {
1182 result = accumulator - cycle_group(AffineElement(offset_accumulator));
1183 }
1184
1185 result.set_origin_tag(result_tag);
1186 return result;
1187}
1188
1225template <typename Builder>
1227 const std::vector<cycle_scalar>& scalars,
1229{
1230 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in batch mul!");
1231
1232 if (scalars.empty()) {
1233 cycle_group result{ Group::point_at_infinity };
1234 return result;
1235 }
1236
1237 std::vector<cycle_scalar> variable_base_scalars;
1238 std::vector<cycle_group> variable_base_points;
1239 std::vector<cycle_scalar> fixed_base_scalars;
1240 std::vector<AffineElement> fixed_base_points;
1241
1242 // Merge all tags
1243 OriginTag result_tag = OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1244 for (auto [point, scalar] : zip_view(base_points, scalars)) {
1245 result_tag = OriginTag(result_tag, OriginTag(point.get_origin_tag(), scalar.get_origin_tag()));
1246 }
1247
1248 // We can unconditionally add in the variable-base algorithm iff all of the input points are fixed-base points (i.e.
1249 // we are doing fixed-base mul over points not present in our plookup tables)
1250 bool can_unconditional_add = true;
1251 bool has_non_constant_component = false;
1252 Element constant_acc = Group::point_at_infinity;
1253 for (const auto [point, scalar] : zip_view(base_points, scalars)) {
1254 if (scalar.is_constant() && point.is_constant()) {
1255 // Case 1: both point and scalar are constant; update constant accumulator without adding gates
1256 constant_acc += (point.get_value()) * (scalar.get_value());
1257 } else if (!scalar.is_constant() && point.is_constant()) {
1258 if (point.get_value().is_point_at_infinity()) {
1259 // oi mate, why are you creating a circuit that multiplies a known point at infinity?
1260#ifndef FUZZING_DISABLE_WARNINGS
1261 info("Warning: Performing batch mul with constant point at infinity!");
1262#endif
1263 // Constant infinity * witness scalar contributes nothing to the result, however, we must still apply
1264 // the range constraints that the cycle_scalar constructor defers to this method.
1265 auto* ctx = scalar.get_context();
1266 ctx->create_limbed_range_constraint(scalar.lo().get_witness_index(),
1268 ROM_TABLE_BITS,
1269 "batch_mul: lo range constraint for scalar with constant "
1270 "infinity");
1271 ctx->create_limbed_range_constraint(scalar.hi().get_witness_index(),
1273 ROM_TABLE_BITS,
1274 "batch_mul: hi range constraint for scalar with constant "
1275 "infinity");
1276 continue;
1277 }
1279 // Case 2A: constant point is one of two for which we have plookup tables; use fixed-base Straus
1280 fixed_base_scalars.push_back(scalar);
1281 fixed_base_points.push_back(point.get_value());
1282 } else {
1283 // Case 2B: constant point but no precomputed lookup tables; use variable-base Straus with ROM tables
1284 variable_base_scalars.push_back(scalar);
1285 variable_base_points.push_back(point);
1286 }
1287 has_non_constant_component = true;
1288 } else {
1289 // Case 3: point is a witness; use variable-base Straus with ROM tables
1290 variable_base_scalars.push_back(scalar);
1291 variable_base_points.push_back(point);
1292 can_unconditional_add = false;
1293 has_non_constant_component = true;
1294 }
1295 }
1296
1297 // If all inputs are constant, return the computed constant component and call it a day.
1298 if (!has_non_constant_component) {
1299 auto result = cycle_group(constant_acc);
1300 result.set_origin_tag(result_tag);
1301 return result;
1302 }
1303
1304 // Add the constant component into our offset accumulator. (Note: we'll subtract `offset_accumulator` from the MSM
1305 // output later on so we negate here to counter that future negation).
1306 Element offset_accumulator = -constant_acc;
1307 const bool has_variable_points = !variable_base_points.empty();
1308 const bool has_fixed_points = !fixed_base_points.empty();
1309
1310 cycle_group result;
1311 if (has_fixed_points) {
1312 // Compute the result of the fixed-base portion of the MSM and update the offset accumulator
1313 const auto [fixed_accumulator, offset_generator_delta] =
1314 _fixed_base_batch_mul_internal(fixed_base_scalars, fixed_base_points);
1315 offset_accumulator += offset_generator_delta;
1316 result = fixed_accumulator;
1317 }
1318
1319 if (has_variable_points) {
1320 // Compute required offset generators; one per point plus one extra for the initial accumulator
1321 const size_t num_offset_generators = variable_base_points.size() + 1;
1322 const std::span<AffineElement const> offset_generators =
1323 context.generators->get(num_offset_generators, 0, OFFSET_GENERATOR_DOMAIN_SEPARATOR);
1324
1325 // Compute the result of the variable-base portion of the MSM and update the offset accumulator
1326 const auto [variable_accumulator, offset_generator_delta] = _variable_base_batch_mul_internal(
1327 variable_base_scalars, variable_base_points, offset_generators, can_unconditional_add);
1328 offset_accumulator += offset_generator_delta;
1329
1330 // Combine the variable-base result with the fixed-base result (if present)
1331 if (has_fixed_points) {
1332 result = can_unconditional_add ? result.unconditional_add(variable_accumulator)
1333 : result.checked_unconditional_add(variable_accumulator);
1334 } else {
1335 result = variable_accumulator;
1336 }
1337 }
1338
1339 // Update `result` to remove the offset generator terms, and add in any constant terms from `constant_acc`.
1340 // We have two potential modes here:
1341 // 1. All inputs are fixed-base and constant_acc is not the point at infinity
1342 // 2. Everything else.
1343 // Case 1 is a special case, as we *know* we cannot hit incomplete addition edge cases, under the assumption that
1344 // all input points are linearly independent of one another. Because constant_acc is not the point at infinity we
1345 // know that at least 1 input scalar was not zero, i.e. the output will not be the point at infinity. We also know
1346 // that, under case 1, we won't trigger the doubling formula either, as every point is linearly independent of every
1347 // other point (including offset generators).
1348 if (!constant_acc.is_point_at_infinity() && can_unconditional_add) {
1349 result = result.unconditional_add(AffineElement(-offset_accumulator));
1350 } else {
1351 // For case 2, we must use a full subtraction operation that handles all possible edge cases, as the output
1352 // point may be the point at infinity.
1353 // Note about optimizations for posterity: An honest prover might hit the point at infinity, but won't
1354 // trigger the doubling edge case (since doubling edge case implies input points are also the offset generator
1355 // points). We could do the following which would be slightly cheaper than operator-:
1356 // 1. If x-coords match, assert y-coords do not match
1357 // 2. If x-coords match, return point at infinity, else unconditionally compute result - offset_accumulator.
1358 result = result - cycle_group(AffineElement(offset_accumulator));
1359 }
1360 // Ensure the tag of the result is a union of all inputs
1361 result.set_origin_tag(result_tag);
1362 return result;
1363}
1364
1365template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator*(const cycle_scalar& scalar) const
1366{
1367 return batch_mul({ *this }, { scalar });
1368}
1369
1370template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator*=(const cycle_scalar& scalar)
1371{
1372 *this = operator*(scalar);
1373 return *this;
1374}
1375
1376template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator*(const BigScalarField& scalar) const
1377{
1378 return batch_mul({ *this }, { scalar });
1379}
1380
1382{
1383 *this = operator*(scalar);
1384 return *this;
1385}
1386
1388{
1389 this->standardize();
1390 other.standardize();
1391 const auto equal = (_x == other._x) && (_y == other._y) && (this->_is_infinity == other._is_infinity);
1392 return equal;
1393}
1394
1395template <typename Builder> void cycle_group<Builder>::assert_equal(cycle_group& other, std::string const& msg)
1396{
1397 this->standardize();
1398 other.standardize();
1399 _x.assert_equal(other._x, msg);
1400 _y.assert_equal(other._y, msg);
1401 this->_is_infinity.assert_equal(other._is_infinity);
1402}
1403
1404template <typename Builder>
1406 const cycle_group& lhs,
1407 const cycle_group& rhs)
1408{
1409 auto x_res = field_t::conditional_assign(predicate, lhs._x, rhs._x);
1410 auto y_res = field_t::conditional_assign(predicate, lhs._y, rhs._y);
1411 auto _is_infinity_res =
1412 bool_t::conditional_assign(predicate, lhs.is_point_at_infinity(), rhs.is_point_at_infinity());
1413
1414 cycle_group<Builder> result(x_res, y_res, _is_infinity_res, /*assert_on_curve=*/false);
1415 return result;
1416};
1417
1420
1421} // namespace bb::stdlib
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
constexpr bool is_point_at_infinity() const noexcept
constexpr void self_set_infinity() noexcept
element class. Implements ecc group arithmetic using Jacobian coordinates See https://hyperelliptic....
Definition element.hpp:35
constexpr element dbl() const noexcept
BB_INLINE constexpr bool is_point_at_infinity() const noexcept
Container for lookup accumulator values and table reads.
Definition types.hpp:360
Generates plookup tables required to perform fixed-base scalar multiplication over a fixed number of ...
static bool lookup_table_exists_for_point(const affine_element &input)
Returns true iff provided point is one of the two for which we have a precomputed lookup table.
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
static bool_t conditional_assign(const bool_t< Builder > &predicate, const bool_t &lhs, const bool_t &rhs)
Conditionally assign lhs or rhs based on predicate, always returns normalized result.
Definition bool.cpp:478
Builder * get_context() const
Definition bool.hpp:152
void assert_equal(const bool_t &rhs, std::string const &msg="bool_t::assert_equal") const
Implements copy constraint for bool_t elements.
Definition bool.cpp:433
cycle_group represents a group Element of the proving system's embedded curve, i.e....
cycle_group dbl(const std::optional< AffineElement > hint=std::nullopt) const
Evaluates a point doubling using Ultra ECC double gate (if non-constant)
static cycle_group from_constant_witness(Builder *_context, const AffineElement &_in)
Converts a native AffineElement into a witness, but constrains the witness values to be known constan...
cycle_group & operator*=(const cycle_scalar &scalar)
void standardize()
Convert the point to standard form.
cycle_group unconditional_add(const cycle_group &other, const std::optional< AffineElement > hint=std::nullopt) const
Evaluate incomplete ECC point addition over *this and other.
void validate_on_curve() const
On-curve check.
bool_t operator==(cycle_group &other)
Builder * get_context(const cycle_group &other) const
cycle_group & operator-=(const cycle_group &other)
static cycle_group conditional_assign(const bool_t &predicate, const cycle_group &lhs, const cycle_group &rhs)
void unset_free_witness_tag()
Unset the free witness flag for the cycle_group's tags.
cycle_group checked_unconditional_subtract(const cycle_group &other, const std::optional< AffineElement > hint=std::nullopt) const
Evaluate incomplete ECC point subtraction over *this and other, with x-coordinate collision checks.
cycle_group _unconditional_add_or_subtract(const cycle_group &other, bool is_addition, const std::optional< AffineElement > hint) const
Will evaluate ECC point addition or subtraction over *this and other.
static cycle_group from_witness(Builder *_context, const AffineElement &_in)
Converts an AffineElement into a circuit witness.
cycle_group operator-() const
Negates a point.
static cycle_group one(Builder *_context)
Construct a constant cycle_group representation of Group::one.
void set_free_witness_tag()
Set the free witness flag for the cycle_group's tags.
void set_origin_tag(OriginTag tag) const
Set the origin tag for x, y and _is_infinity members of cycle_group.
cycle_group & operator+=(const cycle_group &other)
static batch_mul_internal_output _fixed_base_plookup_batch_mul_internal(std::span< cycle_scalar > scalars, std::span< AffineElement const > base_points, std::span< AffineElement const > offset_generators, size_t table_bits=ROM_TABLE_BITS)
Internal algorithm to perform a fixed-base batch mul using plookup tables.
static cycle_group constant_infinity(Builder *_context=nullptr)
Construct a constant point at infinity.
bool is_constant_point_at_infinity() const
static cycle_group fixed_batch_mul(const std::vector< cycle_group > &constant_points, const std::vector< BigScalarField > &scalars, GeneratorContext context={}, size_t table_bits=ROM_TABLE_BITS)
bool_t is_point_at_infinity() const
static batch_mul_internal_output _variable_base_batch_mul_internal(std::span< cycle_scalar > scalars, std::span< cycle_group > base_points, std::span< AffineElement const > offset_generators, bool unconditional_add)
Internal logic to perform a variable-base batch mul using the Straus MSM algorithm.
cycle_group(Builder *_context=nullptr)
Construct a new constant point at infinity cycle group object.
AffineElement get_value() const
OriginTag get_origin_tag() const
Get the origin tag of cycle_group (a merege of origin tags of x, y and _is_infinity members)
cycle_group operator*(const cycle_scalar &scalar) const
static batch_mul_internal_output _fixed_base_batch_mul_internal(std::span< cycle_scalar > scalars, std::span< AffineElement > base_points)
Internal algorithm to perform a fixed-base batch mul.
void assert_equal(cycle_group &other, std::string const &msg="cycle_group::assert_equal")
cycle_group operator+(const cycle_group &other) const
Evaluate ECC point addition over *this and other.
cycle_group unconditional_subtract(const cycle_group &other, const std::optional< AffineElement > hint=std::nullopt) const
Evaluate incomplete ECC point subtraction over *this and other.
Builder * get_context() const
cycle_group checked_unconditional_add(const cycle_group &other, const std::optional< AffineElement > hint=std::nullopt) const
Evaluate incomplete ECC point addition over *this and other, with x-coordinate collision checks.
static cycle_group batch_mul(const std::vector< cycle_group > &base_points, const std::vector< BigScalarField > &scalars, GeneratorContext context={})
Represents a member of the Grumpkin curve scalar field (i.e. BN254 base field).
static constexpr size_t NUM_BITS
static constexpr size_t LO_BITS
static constexpr size_t HI_BITS
void assert_equal(const field_t &rhs, std::string const &msg="field_t::assert_equal") const
Copy constraint: constrain that *this field is equal to rhs element.
Definition field.cpp:942
field_t madd(const field_t &to_mul, const field_t &to_add) const
Definition field.cpp:520
static void evaluate_polynomial_identity(const field_t &a, const field_t &b, const field_t &c, const field_t &d, const std::string &msg="field_t::evaluate_polynomial_identity")
Given a, b, c, d, constrain a * b + c + d = 0 by creating a big_mul_gate.
Definition field.cpp:1137
Builder * get_context() const
Definition field.hpp:432
field_t sqr() const
Definition field.hpp:283
OriginTag get_origin_tag() const
Definition field.hpp:359
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:838
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:467
bool_t< Builder > is_zero() const
Validate whether a field_t element is zero.
Definition field.cpp:785
void convert_constant_to_fixed_witness(Builder *ctx)
Definition field.hpp:457
bool is_constant() const
Definition field.hpp:442
void set_origin_tag(const OriginTag &new_tag) const
Definition field.hpp:358
field_t add_two(const field_t &add_b, const field_t &add_c) const
Efficiently compute (this + a + b) using big_mul gate.
Definition field.cpp:585
static field_t conditional_assign(const bool_t< Builder > &predicate, const field_t &lhs, const field_t &rhs)
Definition field.hpp:385
void assert_is_not_zero(std::string const &msg="field_t::assert_is_not_zero") const
Constrain *this to be non-zero by establishing that it has an inverse.
Definition field.cpp:720
uint32_t get_witness_index() const
Get the witness index of the current field element.
Definition field.hpp:519
static plookup::ReadData< field_pt > get_lookup_accumulators(const plookup::MultiTableId id, const field_pt &key_a, const field_pt &key_b=0, const bool is_2_to_1_lookup=false)
Definition plookup.cpp:19
straus_lookup_table computes a lookup table of size 1 << table_bits
static std::vector< Element > compute_native_table(const Element &base_point, const Element &offset_generator, size_t table_bits)
Compute the output points generated when computing the Straus lookup table.
static PrecomputedData build_precomputed_data(const AffineElement &base_point, const AffineElement &offset_generator, size_t table_bits)
Compute native table entries and BasicTable column data without touching the circuit builder.
#define info(...)
Definition log.hpp:93
StrictMock< MockContext > context
bb::curve::BN254::Element Element
ssize_t offset
Definition engine.cpp:62
constexpr T ceil_div(const T &numerator, const T &denominator)
Computes the ceiling of the division of two integral types.
Definition general.hpp:23
std::conditional_t< IsGoblinBigGroup< C, Fq, Fr, G >, element_goblin::goblin_element< C, goblin_field< C >, Fr, G >, element_default::element< C, Fq, Fr, G > > element
element wraps either element_default::element or element_goblin::goblin_element depending on parametr...
void mark_witness_as_used(const field_t< Builder > &field)
Mark a field_t witness as used (for UltraBuilder only).
field< Bn254FrParams > fr
Definition fr.hpp:155
Univariate< Fr, domain_end > operator*(const Fr &ff, const Univariate< Fr, domain_end > &uv)
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:111
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()
BB_INLINE constexpr field sqr() const noexcept
static constexpr field zero()
Stores temporary variables produced by internal multiplication algorithms.