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 // This method should not be called on known points at infinity
374 BB_ASSERT(!this->is_constant_point_at_infinity(),
375 "cycle_group::_unconditional_add_or_subtract called on constant point at infinity");
377 "cycle_group::_unconditional_add_or_subtract called on constant point at infinity");
378
379 auto context = get_context(other);
380
381 // If one point is a witness and the other is a constant, convert the constant to a fixed witness then call this
382 // method again so we can use the ecc_add gate
383 const bool lhs_constant = is_constant();
384 const bool rhs_constant = other.is_constant();
385
386 if (lhs_constant && !rhs_constant) {
387 auto lhs = cycle_group::from_constant_witness(context, get_value());
388 lhs.set_origin_tag(get_origin_tag()); // Maintain the origin tag
389 return lhs._unconditional_add_or_subtract(other, is_addition, hint);
390 }
391 if (!lhs_constant && rhs_constant) {
393 rhs.set_origin_tag(other.get_origin_tag()); // Maintain the origin tag
394 return _unconditional_add_or_subtract(rhs, is_addition, hint);
395 }
396
397 // Compute the result coordinates (either from hint or by native calculation)
398 bb::fr x3;
399 bb::fr y3;
400 if (hint.has_value()) {
401 x3 = hint.value().x;
402 y3 = hint.value().y;
403 } else {
404 const AffineElement p1 = get_value();
405 const AffineElement p2 = other.get_value();
406 AffineElement p3 = is_addition ? (Element(p1) + Element(p2)) : (Element(p1) - Element(p2));
407 x3 = p3.x;
408 y3 = p3.y;
409 }
410
411 // Construct the result based on whether inputs are constant or witness
412 // Note: this code path is for non-infinity points, so result cannot be at infinity
413 // We use the private 4-arg constructor to explicitly set is_infinity=false, avoiding the
414 // auto-detection gates that the 2-arg constructor would add.
415 cycle_group result;
416 if (lhs_constant && rhs_constant) {
417 result = cycle_group(x3, y3, /*is_infinity=*/bool_t(false), /*assert_on_curve=*/false);
418 } else {
419 // Both points are witnesses - create result witness and construct ECC add constraint
420 result = cycle_group(
421 witness_t(context, x3), witness_t(context, y3), /*is_infinity=*/false, /*assert_on_curve=*/false);
422
423 context->create_ecc_add_gate(bb::ecc_add_gate_{
424 .x1 = _x.get_witness_index(),
425 .y1 = _y.get_witness_index(),
426 .x2 = other._x.get_witness_index(),
427 .y2 = other._y.get_witness_index(),
428 .x3 = result._x.get_witness_index(),
429 .y3 = result._y.get_witness_index(),
430 .is_addition = is_addition,
431 });
432 }
433
434 // Merge the origin tags from both inputs
435 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
436
437 return result;
438}
439
446template <typename Builder>
448 const std::optional<AffineElement> hint) const
449{
450 return _unconditional_add_or_subtract(other, /*is_addition=*/true, hint);
451}
452
459template <typename Builder>
461 const std::optional<AffineElement> hint) const
462{
463 return _unconditional_add_or_subtract(other, /*is_addition=*/false, hint);
464}
465
477template <typename Builder>
479 const std::optional<AffineElement> hint) const
480{
481 const field_t x_delta = this->_x - other._x;
482 if (x_delta.is_constant()) {
483 BB_ASSERT(x_delta.get_value() != 0);
484 } else {
485 x_delta.assert_is_not_zero("cycle_group::checked_unconditional_add, x-coordinate collision");
486 }
487 return unconditional_add(other, hint);
488}
489
502template <typename Builder>
504 const std::optional<AffineElement> hint) const
505{
506 const field_t x_delta = this->_x - other._x;
507 if (x_delta.is_constant()) {
508 BB_ASSERT(x_delta.get_value() != 0);
509 } else {
510 x_delta.assert_is_not_zero("cycle_group::checked_unconditional_subtract, x-coordinate collision");
511 }
512 return unconditional_subtract(other, hint);
513}
514
530template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator+(const cycle_group& other) const
531{
532 // If lhs is constant point at infinity, return the rhs and vice versa
533 if (this->is_constant_point_at_infinity()) {
534 return other;
535 }
536 if (other.is_constant_point_at_infinity()) {
537 return *this;
538 }
539
540 const bool_t x_coordinates_match = (_x == other._x);
541 const bool_t y_coordinates_match = (_y == other._y);
542
543 const field_t x1 = _x;
544 const field_t y1 = _y;
545 const field_t x2 = other._x;
546 const field_t y2 = other._y;
547
548 // Execute point addition with modified lambda = (y2 - y1)/(x2 - x1 + x_coordinates_match) to avoid the possibility
549 // of division by zero.
550 const field_t x_diff = x2.add_two(-x1, x_coordinates_match);
551 // Compute lambda in one of two ways depending on whether either numerator or denominator is constant or not
552 field_t lambda;
553 if ((y1.is_constant() && y2.is_constant()) || x_diff.is_constant()) {
554 lambda = (y2 - y1).divide_no_zero_check(x_diff);
555 } else {
556 // Note: branch saves one gate vs just using divide_no_zero_check because we avoid computing y2 - y1 in circuit
557 Builder* context = get_context(other);
558 lambda = field_t::from_witness(context, (y2.get_value() - y1.get_value()) / x_diff.get_value());
559 // We need to manually propagate the origin tag
561 // Constrain x_diff * lambda = y2 - y1
562 field_t::evaluate_polynomial_identity(x_diff, lambda, -y2, y1);
563 }
564 const field_t add_result_x = lambda.madd(lambda, -(x2 + x1)); // x3 = lambda^2 - x1 - x2
565 const field_t add_result_y = lambda.madd(x1 - add_result_x, -y1); // y3 = lambda * (x1 - x3) - y1
566
567 // Compute the doubling result
568 const cycle_group dbl_result = dbl();
569
570 // If the addition amounts to a doubling then the result is the doubling result, else the addition result.
571 const bool_t double_predicate = (x_coordinates_match && y_coordinates_match);
572 auto result_x = field_t::conditional_assign(double_predicate, dbl_result._x, add_result_x);
573 auto result_y = field_t::conditional_assign(double_predicate, dbl_result._y, add_result_y);
574
575 // If the lhs is the point at infinity, return rhs
576 const bool_t lhs_infinity = is_point_at_infinity();
577 result_x = field_t::conditional_assign(lhs_infinity, other._x, result_x);
578 result_y = field_t::conditional_assign(lhs_infinity, other._y, result_y);
579
580 // If the rhs is the point at infinity, return lhs
581 const bool_t rhs_infinity = other.is_point_at_infinity();
582 result_x = field_t::conditional_assign(rhs_infinity, _x, result_x);
583 result_y = field_t::conditional_assign(rhs_infinity, _y, result_y);
584
585 // The result is the point at infinity if:
586 // (lhs._x, lhs._y) == (rhs._x, -rhs._y) and neither are infinity, OR both are the point at infinity
587 const bool_t infinity_predicate = (x_coordinates_match && !y_coordinates_match);
588 bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
589 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
590
591 return cycle_group(result_x, result_y, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
592}
593
609template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator-(const cycle_group& other) const
610{
611 // If lhs is constant point at infinity, return -rhs
612 if (this->is_constant_point_at_infinity()) {
613 return -other;
614 }
615 // If rhs is constant point at infinity, return the lhs
616 if (other.is_constant_point_at_infinity()) {
617 return *this;
618 }
619
620 Builder* context = get_context(other);
621
622 const bool_t x_coordinates_match = (_x == other._x);
623 const bool_t y_coordinates_match = (_y == other._y);
624
625 const field_t x1 = _x;
626 const field_t y1 = _y;
627 const field_t x2 = other._x;
628 const field_t y2 = other._y;
629
630 // Execute point addition with modified lambda = (-y2 - y1)/(x2 - x1 + x_coordinates_match) to avoid the possibility
631 // of division by zero.
632 const field_t x_diff = x2.add_two(-x1, x_coordinates_match);
633 // Compute lambda in one of two ways depending on whether either numerator or denominator is constant or not
634 field_t lambda;
635 if ((y1.is_constant() && y2.is_constant()) || x_diff.is_constant()) {
636 lambda = (-y2 - y1).divide_no_zero_check(x_diff);
637 } else {
638 // Note: branch saves one gate vs using divide_no_zero_check because we avoid computing (-y2 - y1) in circuit
639 lambda = field_t::from_witness(context, (-y2.get_value() - y1.get_value()) / x_diff.get_value());
640 // We need to manually propagate the origin tag
642 // Constrain x_diff * lambda = -y2 - y1
643 field_t::evaluate_polynomial_identity(x_diff, lambda, y2, y1);
644 }
645 const field_t sub_result_x = lambda.madd(lambda, -(x2 + x1)); // x3 = lambda^2 - x1 - x2
646 const field_t sub_result_y = lambda.madd(x1 - sub_result_x, -y1); // y3 = lambda * (x1 - x3) - y1
647
648 // Compute the doubling result
649 const cycle_group dbl_result = dbl();
650
651 // If the subtraction amounts to a doubling then the result is the doubling result, else the subtraction result.
652 // Note: The assumption here is that x1 == x2 && y1 != y2 implies y1 == -y2 which is true assuming that the points
653 // are both on the curve.
654 const bool_t double_predicate = (x_coordinates_match && !y_coordinates_match);
655 auto result_x = field_t::conditional_assign(double_predicate, dbl_result._x, sub_result_x);
656 auto result_y = field_t::conditional_assign(double_predicate, dbl_result._y, sub_result_y);
657
658 mark_witness_as_used(result_x);
659 mark_witness_as_used(result_y);
660
661 // If the lhs is the point at infinity, return -rhs
662 const bool_t lhs_infinity = is_point_at_infinity();
663 result_x = field_t::conditional_assign(lhs_infinity, other._x, result_x);
664 result_y = field_t::conditional_assign(lhs_infinity, (-other._y), result_y);
665
666 // If the rhs is the point at infinity, return lhs
667 const bool_t rhs_infinity = other.is_point_at_infinity();
668 result_x = field_t::conditional_assign(rhs_infinity, _x, result_x);
669 result_y = field_t::conditional_assign(rhs_infinity, _y, result_y);
670
671 // The result is the point at infinity if:
672 // (lhs.x, lhs.y) == (rhs.x, rhs.y) and neither are infinity, OR both are the point at infinity
673 const bool_t infinity_predicate = (x_coordinates_match && y_coordinates_match);
674 mark_witness_as_used(field_t(infinity_predicate));
675 bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
676 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
677
678 return cycle_group(result_x, result_y, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
679}
680
688template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator-() const
689{
690 cycle_group result(*this);
691 // We have to normalize immediately. All the methods, related to
692 // elliptic curve operations, assume that the coordinates are in normalized form and
693 // don't perform any extra normalizations
694 result._y = (-_y).normalize();
695 return result;
696}
697
698template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator+=(const cycle_group& other)
699{
700 *this = *this + other;
701 return *this;
702}
703
704template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator-=(const cycle_group& other)
705{
706 *this = *this - other;
707 return *this;
708}
709
730template <typename Builder>
732 const std::span<cycle_scalar> scalars,
733 const std::span<cycle_group> base_points,
734 const std::span<AffineElement const> offset_generators,
735 const bool unconditional_add)
736{
737 BB_ASSERT_EQ(!scalars.empty(), true, "Empty scalars provided to variable base batch mul!");
738 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in variable base batch mul!");
739 BB_ASSERT_EQ(offset_generators.size(), base_points.size() + 1, "Too few offset generators provided!");
740 const size_t num_points = scalars.size();
741
742 // Extract the circuit context from any scalar or point
743 Builder* context = nullptr;
744 for (auto [scalar, point] : zip_view(scalars, base_points)) {
745 if (context = scalar.get_context(); context != nullptr) {
746 break;
747 }
748 if (context = point.get_context(); context != nullptr) {
749 break;
750 }
751 }
752
753 // All cycle_scalars are guaranteed to be 254 bits
754 constexpr size_t num_rounds = numeric::ceil_div(cycle_scalar::NUM_BITS, ROM_TABLE_BITS);
755
756 // Decompose each scalar into 4-bit slices. Note: This operation enforces range constraints on the lo/hi limbs of
757 // each scalar (LO_BITS and (num_bits - LO_BITS) respectively).
759 scalar_slices.reserve(num_points);
760 for (const auto& scalar : scalars) {
761 scalar_slices.emplace_back(context, scalar, ROM_TABLE_BITS);
762 }
763
764 // Compute the result of each point addition involved in the Straus MSM algorithm natively so they can be used as
765 // "hints" in the in-circuit Straus algorithm. This includes the additions needed to construct the point tables and
766 // those needed to compute the MSM via Straus. Points are computed as Element types with a Z-coordinate then
767 // batch-converted to AffineElement types. This avoids the need to compute modular inversions for every group
768 // operation, which dramatically reduces witness generation times.
769 std::vector<Element> operation_transcript;
770 Element offset_generator_accumulator = offset_generators[0];
771 {
772 // For each point, construct native straus lookup table of the form {G , G + [1]P, G + [2]P, ... , G + [15]P}
773 std::vector<std::vector<Element>> native_straus_tables;
774 for (size_t i = 0; i < num_points; ++i) {
775 AffineElement point = base_points[i].get_value();
776 AffineElement offset = offset_generators[i + 1];
777 std::vector<Element> table = straus_lookup_table::compute_native_table(point, offset, ROM_TABLE_BITS);
778 // Copy all but the first entry (the offset generator) into the operation transcript for use as hints
779 std::copy(table.begin() + 1, table.end(), std::back_inserter(operation_transcript));
780 native_straus_tables.emplace_back(std::move(table));
781 }
782
783 // Perform the Straus algorithm natively to generate the witness values (hints) for all intermediate points
784 Element accumulator = offset_generators[0];
785 for (size_t i = 0; i < num_rounds; ++i) {
786 if (i != 0) {
787 // Perform doublings of the accumulator and offset generator accumulator
788 for (size_t j = 0; j < ROM_TABLE_BITS; ++j) {
789 accumulator = accumulator.dbl();
790 operation_transcript.push_back(accumulator);
791 offset_generator_accumulator = offset_generator_accumulator.dbl();
792 }
793 }
794 for (size_t j = 0; j < num_points; ++j) {
795 // Look up and accumulate the appropriate point for this scalar slice
796 auto slice_value = static_cast<size_t>(scalar_slices[j].slices_native[num_rounds - i - 1]);
797 const Element point = native_straus_tables[j][slice_value];
798 accumulator += point;
799
800 // Populate hint and update offset generator accumulator
801 operation_transcript.push_back(accumulator);
802 offset_generator_accumulator += Element(offset_generators[j + 1]);
803 }
804 }
805 }
806
807 // Normalize the computed witness points and convert them into AffineElements
808 Element::batch_normalize(&operation_transcript[0], operation_transcript.size());
809 std::vector<AffineElement> operation_hints;
810 operation_hints.reserve(operation_transcript.size());
811 for (const Element& element : operation_transcript) {
812 operation_hints.emplace_back(element.x, element.y);
813 }
814
815 // Construct an in-circuit Straus lookup table for each point
817 point_tables.reserve(num_points);
818 const size_t hints_per_table = (1ULL << ROM_TABLE_BITS) - 1;
819 for (size_t i = 0; i < num_points; ++i) {
820 // Construct Straus table
821 std::span<AffineElement> table_hints(&operation_hints[i * hints_per_table], hints_per_table);
822 straus_lookup_table table(context, base_points[i], offset_generators[i + 1], ROM_TABLE_BITS, table_hints);
823 point_tables.push_back(std::move(table));
824 }
825
826 // Initialize pointer to the precomputed Straus algorithm hints (stored just after the table construction hints)
827 AffineElement* hint_ptr = &operation_hints[num_points * hints_per_table];
828 cycle_group accumulator = offset_generators[0];
829
830 // Execute Straus algorithm in-circuit using the precomputed hints.
831 // If unconditional_add == false, accumulate x-coordinate differences to batch-validate no collisions.
832 field_t coordinate_check_product = 1;
833 for (size_t i = 0; i < num_rounds; ++i) {
834 // Double the accumulator ROM_TABLE_BITS times (except in first round)
835 if (i != 0) {
836 for (size_t j = 0; j < ROM_TABLE_BITS; ++j) {
837 accumulator = accumulator.dbl(*hint_ptr);
838 hint_ptr++;
839 }
840 }
841 // Add the contribution from each point's scalar slice for this round
842 for (size_t j = 0; j < num_points; ++j) {
843 const field_t scalar_slice = scalar_slices[j][num_rounds - i - 1];
844 BB_ASSERT_EQ(scalar_slice.get_value(), scalar_slices[j].slices_native[num_rounds - i - 1]); // Sanity check
845 const cycle_group point = point_tables[j].read(scalar_slice);
846 if (!unconditional_add) {
847 coordinate_check_product *= point._x - accumulator._x;
848 }
849 accumulator = accumulator.unconditional_add(point, *hint_ptr);
850 hint_ptr++;
851 }
852 }
853
854 // Batch-validate no x-coordinate collisions occurred. We batch because each assert_is_not_zero
855 // requires an expensive modular inversion during witness generation.
856 if (!unconditional_add) {
857 coordinate_check_product.assert_is_not_zero("_variable_base_batch_mul_internal x-coordinate collision");
858 }
859
860 // Set CONSTANT tag on accumulator to avoid tag conflicts during intermediate operations
861 // The final tag will be set in batch_mul from result_tag
862 accumulator.set_origin_tag(OriginTag::constant());
863
864 // Note: offset_generator_accumulator represents the sum of all the offset generator terms present in `accumulator`.
865 // We don't subtract it off yet as we may be able to combine it with other constant terms in `batch_mul` before
866 // performing the subtraction.
867 return { accumulator, AffineElement(offset_generator_accumulator) };
868}
869
893template <typename Builder>
895 const std::span<cycle_scalar> scalars, const std::span<AffineElement> base_points)
896{
897 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in fixed-base batch mul");
898
902
903 std::vector<MultiTableId> multitable_ids;
904 std::vector<field_t> scalar_limbs;
905
906 for (const auto [point, scalar] : zip_view(base_points, scalars)) {
907 std::array<MultiTableId, 2> table_id = table::get_lookup_table_ids_for_point(point);
908 multitable_ids.push_back(table_id[0]);
909 multitable_ids.push_back(table_id[1]);
910 scalar_limbs.push_back(scalar.lo());
911 scalar_limbs.push_back(scalar.hi());
912 }
913
914 // Look up the multiples of each slice of each lo/hi scalar limb in the corresponding plookup table.
915 std::vector<cycle_group> lookup_points;
916 Element offset_generator_accumulator = Group::point_at_infinity;
917 for (const auto [table_id, scalar] : zip_view(multitable_ids, scalar_limbs)) {
918 // Each lookup returns multiple EC points corresponding to different bit slices of the scalar.
919 // For a scalar slice s_i at bit position (table_bits*i), the table stores the point:
920 // P_i = [offset_generator_i] + (s_i * 2^(table_bits*i)) * [base_point]
922 for (size_t j = 0; j < lookup_data[ColumnIdx::C2].size(); ++j) {
923 const field_t x = lookup_data[ColumnIdx::C2][j];
924 const field_t y = lookup_data[ColumnIdx::C3][j];
925 // Lookup table points are never at infinity (they are precomputed points on the curve).
926 // Use private constructor to avoid auto-detection gates.
927 auto is_infinity = bool_t(x.get_context(), false);
928 lookup_points.push_back(cycle_group(x, y, is_infinity, /*assert_on_curve=*/false));
929 }
930 // Update offset accumulator with the total offset for the corresponding multitable
931 offset_generator_accumulator += table::get_generator_offset_for_table_id(table_id);
932 }
933
934 // Compute the witness values of the batch_mul algorithm natively, as Element types with a Z-coordinate.
935 std::vector<Element> operation_transcript;
936 {
937 Element accumulator = lookup_points[0].get_value();
938 for (size_t i = 1; i < lookup_points.size(); ++i) {
939 accumulator += (lookup_points[i].get_value());
940 operation_transcript.push_back(accumulator);
941 }
942 }
943 // Batch-convert to AffineElement types, and feed these points as "hints" into the in-circuit addition. This avoids
944 // the need to compute modular inversions for every group operation, which dramatically reduces witness generation
945 // times.
946 Element::batch_normalize(&operation_transcript[0], operation_transcript.size());
947 std::vector<AffineElement> operation_hints;
948 operation_hints.reserve(operation_transcript.size());
949 for (const Element& element : operation_transcript) {
950 operation_hints.emplace_back(element.x, element.y);
951 }
952
953 // Perform the in-circuit point additions sequentially. Each addition costs 1 gate iff additions are chained such
954 // that the output of each addition is the input to the next. Otherwise, each addition costs 2 gates.
955 // Set all lookup_points to have CONSTANT tags to avoid tag conflicts during intermediate operations
956 // The final tag will be set in batch_mul from result_tag
957 auto const_tag = OriginTag::constant();
958 for (auto& point : lookup_points) {
959 point.set_origin_tag(const_tag);
960 }
961 cycle_group accumulator = lookup_points[0];
962 for (size_t i = 1; i < lookup_points.size(); ++i) {
963 accumulator = accumulator.unconditional_add(lookup_points[i], operation_hints[i - 1]);
964 }
965
966 // The offset_generator_accumulator represents the sum of all the offset generator terms present in `accumulator`.
967 // We don't subtract off yet, as we may be able to combine `offset_generator_accumulator` with other constant
968 // terms in `batch_mul` before performing the subtraction.
969 return { accumulator, offset_generator_accumulator };
970}
971
985template <typename Builder>
987 const std::span<cycle_scalar> scalars,
988 const std::span<AffineElement const> base_points,
989 const std::span<AffineElement const> offset_generators,
990 const size_t table_bits)
991{
992 BB_ASSERT_EQ(!scalars.empty(), true, "Empty scalars provided to fixed base plookup batch mul!");
993 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in fixed base plookup batch mul!");
994 BB_ASSERT_EQ(offset_generators.size(), base_points.size() + 1, "Too few offset generators provided!");
995 const size_t num_points = scalars.size();
996
997 Builder* context = nullptr;
998 for (const auto& scalar : scalars) {
999 if (context = scalar.get_context(); context != nullptr) {
1000 break;
1001 }
1002 }
1003 BB_ASSERT(context != nullptr);
1005 0UL,
1006 "table_bits must evenly divide cycle_scalar::LO_BITS. The Straus algorithm splits the scalar "
1007 "into lo/hi limbs and decomposes each separately; if LO_BITS is not a multiple of table_bits, "
1008 "the hi-limb slices start at the wrong bit-offset and the MSM result is incorrect. "
1009 "Valid values for table_bits (given LO_BITS=128) are: 1, 2, 4, 8, 16, 32, 64, 128.");
1010
1011 const size_t num_rounds = numeric::ceil_div(cycle_scalar::NUM_BITS, table_bits);
1012
1013 // Decompose each scalar into table_bits-bit slices (also enforces range constraints)
1015 scalar_slices.reserve(num_points);
1016 for (const auto& scalar : scalars) {
1017 scalar_slices.emplace_back(context, scalar, table_bits);
1018 }
1019
1020 // Create plookup tables for each constant base point (zero gate cost).
1021 // Phase 1 (parallel): compute native table entries and BasicTable column data — no builder access.
1022 // Phase 2 (serial): register each BasicTable with the builder (builder is not thread-safe).
1024 parallel_for(num_points, [&](size_t i) {
1025 precomputed_tables[i] =
1026 straus_plookup_table::build_precomputed_data(base_points[i], offset_generators[i + 1], table_bits);
1027 });
1029 point_tables.reserve(num_points);
1030 for (size_t i = 0; i < num_points; ++i) {
1031 point_tables.emplace_back(context, std::move(precomputed_tables[i]));
1032 }
1033
1034 // Compute all intermediate points natively for use as hints in the in-circuit Straus algorithm.
1035 // Using projective coordinates + batch normalize to avoid per-operation modular inversions.
1036 // The per-point lookup tables (point_tables) already hold the precomputed affine entries; we reuse
1037 // them directly rather than rebuilding projective copies.
1038 std::vector<Element> operation_transcript;
1039 Element offset_generator_accumulator = offset_generators[0];
1040 {
1041 // Perform Straus algorithm natively
1042 Element accumulator = offset_generators[0];
1043 for (size_t i = 0; i < num_rounds; ++i) {
1044 if (i != 0) {
1045 for (size_t j = 0; j < table_bits; ++j) {
1046 accumulator = accumulator.dbl();
1047 operation_transcript.push_back(accumulator);
1048 offset_generator_accumulator = offset_generator_accumulator.dbl();
1049 }
1050 }
1051 for (size_t j = 0; j < num_points; ++j) {
1052 auto slice_value = static_cast<size_t>(scalar_slices[j].slices_native[num_rounds - i - 1]);
1053 const Element point(point_tables[j].get_native_table()[slice_value]);
1054 accumulator += point;
1055 operation_transcript.push_back(accumulator);
1056 offset_generator_accumulator += Element(offset_generators[j + 1]);
1057 }
1058 }
1059 }
1060
1061 // Batch-normalize all hint points
1062 Element::batch_normalize(operation_transcript.data(), operation_transcript.size());
1063 std::vector<AffineElement> operation_hints;
1064 operation_hints.reserve(operation_transcript.size());
1065 for (const Element& element : operation_transcript) {
1066 operation_hints.emplace_back(element.x, element.y);
1067 }
1068
1069 // Execute Straus algorithm in-circuit using plookup reads and precomputed hints
1070 AffineElement* hint_ptr = operation_hints.data();
1071 cycle_group accumulator = offset_generators[0];
1072
1073 for (size_t i = 0; i < num_rounds; ++i) {
1074 if (i != 0) {
1075 for (size_t j = 0; j < table_bits; ++j) {
1076 accumulator = accumulator.dbl(*hint_ptr);
1077 hint_ptr++;
1078 }
1079 }
1080 for (size_t j = 0; j < num_points; ++j) {
1081 const field_t scalar_slice = scalar_slices[j][num_rounds - i - 1];
1082 const cycle_group point = point_tables[j].read(scalar_slice);
1083 // Safe to use unconditional_add: all base points are constants hence linearly independent of offset
1084 // generators
1085 accumulator = accumulator.unconditional_add(point, *hint_ptr);
1086 hint_ptr++;
1087 }
1088 }
1089
1090 accumulator.set_origin_tag(OriginTag::constant());
1091 return { accumulator, AffineElement(offset_generator_accumulator) };
1092}
1093
1106template <typename Builder>
1108 const std::vector<cycle_scalar>& scalars,
1110 const size_t table_bits)
1111{
1112 BB_ASSERT_EQ(scalars.size(), constant_points.size(), "Points/scalars size mismatch in fixed_batch_mul!");
1113
1114 if (scalars.empty()) {
1115 return cycle_group{ Group::point_at_infinity };
1116 }
1117
1118 // Merge all tags
1119 OriginTag result_tag = OriginTag::constant();
1120 for (auto [point, scalar] : zip_view(constant_points, scalars)) {
1121 result_tag = OriginTag(result_tag, OriginTag(point.get_origin_tag(), scalar.get_origin_tag()));
1122 }
1123
1124 std::vector<cycle_scalar> plookup_scalars;
1125 std::vector<AffineElement> plookup_points;
1126 bool has_non_constant_component = false;
1127 Element constant_acc = Group::point_at_infinity;
1128
1129 for (const auto [point, scalar] : zip_view(constant_points, scalars)) {
1130 BB_ASSERT(point.is_constant());
1131 if (scalar.is_constant()) {
1132 // Both constant: compute natively
1133 constant_acc += point.get_value() * scalar.get_value();
1134 } else {
1135 if (point.get_value().is_point_at_infinity()) {
1136 // Constant infinity contributes nothing, but still need range constraints on scalar
1137 auto* ctx = scalar.get_context();
1138 ctx->create_limbed_range_constraint(scalar.lo().get_witness_index(),
1140 table_bits,
1141 "fixed_batch_mul: lo range constraint for scalar with constant "
1142 "infinity");
1143 ctx->create_limbed_range_constraint(scalar.hi().get_witness_index(),
1145 table_bits,
1146 "fixed_batch_mul: hi range constraint for scalar with constant "
1147 "infinity");
1148 continue;
1149 }
1150 plookup_scalars.push_back(scalar);
1151 plookup_points.push_back(point.get_value());
1152 has_non_constant_component = true;
1153 }
1154 }
1155
1156 if (!has_non_constant_component) {
1157 auto result = cycle_group(constant_acc);
1158 result.set_origin_tag(result_tag);
1159 return result;
1160 }
1161
1162 // Compute offset generators
1163 const size_t num_offset_generators = plookup_points.size() + 1;
1164 const std::span<AffineElement const> offset_generators =
1165 context.generators->get(num_offset_generators, 0, OFFSET_GENERATOR_DOMAIN_SEPARATOR);
1166
1167 // Run the plookup-based Straus algorithm
1168 Element offset_accumulator = -constant_acc;
1169 const auto [accumulator, offset_generator_delta] =
1170 _fixed_base_plookup_batch_mul_internal(plookup_scalars, plookup_points, offset_generators, table_bits);
1171 offset_accumulator += offset_generator_delta;
1172
1173 // Subtract offset. Since all points are constants and linearly independent of offset generators,
1174 // we can safely use unconditional_add when constant_acc is non-trivial.
1175 cycle_group result;
1176 if (!constant_acc.is_point_at_infinity()) {
1177 result = accumulator.unconditional_add(AffineElement(-offset_accumulator));
1178 } else {
1179 result = accumulator - cycle_group(AffineElement(offset_accumulator));
1180 }
1181
1182 result.set_origin_tag(result_tag);
1183 return result;
1184}
1185
1222template <typename Builder>
1224 const std::vector<cycle_scalar>& scalars,
1226{
1227 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in batch mul!");
1228
1229 if (scalars.empty()) {
1230 cycle_group result{ Group::point_at_infinity };
1231 return result;
1232 }
1233
1234 std::vector<cycle_scalar> variable_base_scalars;
1235 std::vector<cycle_group> variable_base_points;
1236 std::vector<cycle_scalar> fixed_base_scalars;
1237 std::vector<AffineElement> fixed_base_points;
1238
1239 // Merge all tags
1240 OriginTag result_tag = OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1241 for (auto [point, scalar] : zip_view(base_points, scalars)) {
1242 result_tag = OriginTag(result_tag, OriginTag(point.get_origin_tag(), scalar.get_origin_tag()));
1243 }
1244
1245 // We can unconditionally add in the variable-base algorithm iff all of the input points are fixed-base points (i.e.
1246 // we are doing fixed-base mul over points not present in our plookup tables)
1247 bool can_unconditional_add = true;
1248 bool has_non_constant_component = false;
1249 Element constant_acc = Group::point_at_infinity;
1250 for (const auto [point, scalar] : zip_view(base_points, scalars)) {
1251 if (scalar.is_constant() && point.is_constant()) {
1252 // Case 1: both point and scalar are constant; update constant accumulator without adding gates
1253 constant_acc += (point.get_value()) * (scalar.get_value());
1254 } else if (!scalar.is_constant() && point.is_constant()) {
1255 if (point.get_value().is_point_at_infinity()) {
1256 // oi mate, why are you creating a circuit that multiplies a known point at infinity?
1257#ifndef FUZZING_DISABLE_WARNINGS
1258 info("Warning: Performing batch mul with constant point at infinity!");
1259#endif
1260 // Constant infinity * witness scalar contributes nothing to the result, however, we must still apply
1261 // the range constraints that the cycle_scalar constructor defers to this method.
1262 auto* ctx = scalar.get_context();
1263 ctx->create_limbed_range_constraint(scalar.lo().get_witness_index(),
1265 ROM_TABLE_BITS,
1266 "batch_mul: lo range constraint for scalar with constant "
1267 "infinity");
1268 ctx->create_limbed_range_constraint(scalar.hi().get_witness_index(),
1270 ROM_TABLE_BITS,
1271 "batch_mul: hi range constraint for scalar with constant "
1272 "infinity");
1273 continue;
1274 }
1276 // Case 2A: constant point is one of two for which we have plookup tables; use fixed-base Straus
1277 fixed_base_scalars.push_back(scalar);
1278 fixed_base_points.push_back(point.get_value());
1279 } else {
1280 // Case 2B: constant point but no precomputed lookup tables; use variable-base Straus with ROM tables
1281 variable_base_scalars.push_back(scalar);
1282 variable_base_points.push_back(point);
1283 }
1284 has_non_constant_component = true;
1285 } else {
1286 // Case 3: point is a witness; use variable-base Straus with ROM tables
1287 variable_base_scalars.push_back(scalar);
1288 variable_base_points.push_back(point);
1289 can_unconditional_add = false;
1290 has_non_constant_component = true;
1291 }
1292 }
1293
1294 // If all inputs are constant, return the computed constant component and call it a day.
1295 if (!has_non_constant_component) {
1296 auto result = cycle_group(constant_acc);
1297 result.set_origin_tag(result_tag);
1298 return result;
1299 }
1300
1301 // Add the constant component into our offset accumulator. (Note: we'll subtract `offset_accumulator` from the MSM
1302 // output later on so we negate here to counter that future negation).
1303 Element offset_accumulator = -constant_acc;
1304 const bool has_variable_points = !variable_base_points.empty();
1305 const bool has_fixed_points = !fixed_base_points.empty();
1306
1307 cycle_group result;
1308 if (has_fixed_points) {
1309 // Compute the result of the fixed-base portion of the MSM and update the offset accumulator
1310 const auto [fixed_accumulator, offset_generator_delta] =
1311 _fixed_base_batch_mul_internal(fixed_base_scalars, fixed_base_points);
1312 offset_accumulator += offset_generator_delta;
1313 result = fixed_accumulator;
1314 }
1315
1316 if (has_variable_points) {
1317 // Compute required offset generators; one per point plus one extra for the initial accumulator
1318 const size_t num_offset_generators = variable_base_points.size() + 1;
1319 const std::span<AffineElement const> offset_generators =
1320 context.generators->get(num_offset_generators, 0, OFFSET_GENERATOR_DOMAIN_SEPARATOR);
1321
1322 // Compute the result of the variable-base portion of the MSM and update the offset accumulator
1323 const auto [variable_accumulator, offset_generator_delta] = _variable_base_batch_mul_internal(
1324 variable_base_scalars, variable_base_points, offset_generators, can_unconditional_add);
1325 offset_accumulator += offset_generator_delta;
1326
1327 // Combine the variable-base result with the fixed-base result (if present)
1328 if (has_fixed_points) {
1329 result = can_unconditional_add ? result.unconditional_add(variable_accumulator)
1330 : result.checked_unconditional_add(variable_accumulator);
1331 } else {
1332 result = variable_accumulator;
1333 }
1334 }
1335
1336 // Update `result` to remove the offset generator terms, and add in any constant terms from `constant_acc`.
1337 // We have two potential modes here:
1338 // 1. All inputs are fixed-base and constant_acc is not the point at infinity
1339 // 2. Everything else.
1340 // Case 1 is a special case, as we *know* we cannot hit incomplete addition edge cases, under the assumption that
1341 // all input points are linearly independent of one another. Because constant_acc is not the point at infinity we
1342 // know that at least 1 input scalar was not zero, i.e. the output will not be the point at infinity. We also know
1343 // that, under case 1, we won't trigger the doubling formula either, as every point is linearly independent of every
1344 // other point (including offset generators).
1345 if (!constant_acc.is_point_at_infinity() && can_unconditional_add) {
1346 result = result.unconditional_add(AffineElement(-offset_accumulator));
1347 } else {
1348 // For case 2, we must use a full subtraction operation that handles all possible edge cases, as the output
1349 // point may be the point at infinity.
1350 // Note about optimizations for posterity: An honest prover might hit the point at infinity, but won't
1351 // trigger the doubling edge case (since doubling edge case implies input points are also the offset generator
1352 // points). We could do the following which would be slightly cheaper than operator-:
1353 // 1. If x-coords match, assert y-coords do not match
1354 // 2. If x-coords match, return point at infinity, else unconditionally compute result - offset_accumulator.
1355 result = result - cycle_group(AffineElement(offset_accumulator));
1356 }
1357 // Ensure the tag of the result is a union of all inputs
1358 result.set_origin_tag(result_tag);
1359 return result;
1360}
1361
1362template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator*(const cycle_scalar& scalar) const
1363{
1364 return batch_mul({ *this }, { scalar });
1365}
1366
1367template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator*=(const cycle_scalar& scalar)
1368{
1369 *this = operator*(scalar);
1370 return *this;
1371}
1372
1373template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator*(const BigScalarField& scalar) const
1374{
1375 return batch_mul({ *this }, { scalar });
1376}
1377
1379{
1380 *this = operator*(scalar);
1381 return *this;
1382}
1383
1385{
1386 this->standardize();
1387 other.standardize();
1388 const auto equal = (_x == other._x) && (_y == other._y) && (this->_is_infinity == other._is_infinity);
1389 return equal;
1390}
1391
1392template <typename Builder> void cycle_group<Builder>::assert_equal(cycle_group& other, std::string const& msg)
1393{
1394 this->standardize();
1395 other.standardize();
1396 _x.assert_equal(other._x, msg);
1397 _y.assert_equal(other._y, msg);
1398 this->_is_infinity.assert_equal(other._is_infinity);
1399}
1400
1401template <typename Builder>
1403 const cycle_group& lhs,
1404 const cycle_group& rhs)
1405{
1406 auto x_res = field_t::conditional_assign(predicate, lhs._x, rhs._x);
1407 auto y_res = field_t::conditional_assign(predicate, lhs._y, rhs._y);
1408 auto _is_infinity_res =
1409 bool_t::conditional_assign(predicate, lhs.is_point_at_infinity(), rhs.is_point_at_infinity());
1410
1411 cycle_group<Builder> result(x_res, y_res, _is_infinity_res, /*assert_on_curve=*/false);
1412 return result;
1413};
1414
1417
1418} // 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
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.