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: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#include "../field/field.hpp"
8#include "../field/field_utils.hpp"
13
14#include "./cycle_group.hpp"
20namespace bb::stdlib {
21
27template <typename Builder> cycle_group<Builder>::cycle_group(Builder* _context)
28{
29 *this = constant_infinity(_context);
30}
31
42template <typename Builder>
43cycle_group<Builder>::cycle_group(const field_t& x, const field_t& y, bool_t is_infinity, bool assert_on_curve)
44 : _x(x)
45 , _y(y)
46 , _is_infinity(is_infinity)
47{
48 if (_x.get_context() != nullptr) {
50 } else if (_y.get_context() != nullptr) {
52 } else {
53 context = is_infinity.get_context();
54 }
55
56 if (is_infinity.is_constant() && is_infinity.get_value()) {
57 *this = constant_infinity(this->context);
58 }
59
60 // For the simplicity of methods in this class, we ensure that the coordinates of a point always have the same
61 // constancy. If they don't, we convert the non-constant coordinate to a fixed witness. Should be rare.
62 if (_x.is_constant() != _y.is_constant()) {
63 if (_x.is_constant()) {
65 } else {
67 }
68 }
69
70 // If both coordinates are constant, enforce that is_infinity is also constant.
71 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1584): make this an assertion when possible
74 }
76 // Elements are always expected to be on the curve but may or may not be constrained as such.
77 BB_ASSERT(get_value().on_curve(), "cycle_group: Point is not on curve");
78 if (assert_on_curve) {
80 }
95template <typename Builder>
96cycle_group<Builder>::cycle_group(const bb::fr& x, const bb::fr& y, bool is_infinity)
97 : _x(is_infinity ? 0 : x)
98 , _y(is_infinity ? 0 : y)
99 , _is_infinity(is_infinity)
100 , context(nullptr)
102 BB_ASSERT(get_value().on_curve());
103}
114template <typename Builder>
116 : _x(_in.is_point_at_infinity() ? 0 : _in.x)
117 , _y(_in.is_point_at_infinity() ? 0 : _in.y)
118 , _is_infinity(_in.is_point_at_infinity())
119 , context(nullptr)
120{}
121
129template <typename Builder> cycle_group<Builder> cycle_group<Builder>::one(Builder* _context)
131 field_t x(_context, Group::one.x);
132 field_t y(_context, Group::one.y);
133 bool_t is_infinity(_context, false);
135 return cycle_group<Builder>(x, y, is_infinity, /*assert_on_curve=*/false);
136}
137
143{
144 cycle_group result(bb::fr(0), bb::fr(0), /*is_infinity=*/true);
145
146 // If context provided, create field_t/bool_t with that context
147 if (_context != nullptr) {
148 result._x = field_t(_context, bb::fr(0));
149 result._y = field_t(_context, bb::fr(0));
150 result._is_infinity = bool_t(_context, true);
151 result.context = _context;
152 }
153
154 return result;
155}
156
169template <typename Builder>
171{
172 cycle_group result(_context);
173
174 // By convention we set the coordinates of the point at infinity to (0,0).
175 if (_in.is_point_at_infinity()) {
176 result._x = field_t::from_witness(_context, bb::fr::zero());
177 result._y = field_t::from_witness(_context, bb::fr::zero());
178 } else {
179 result._x = field_t::from_witness(_context, _in.x);
180 result._y = field_t::from_witness(_context, _in.y);
181 }
182 result._is_infinity = bool_t(witness_t(_context, _in.is_point_at_infinity()));
183 result.validate_on_curve();
184 result.set_free_witness_tag();
185 return result;
186}
187
200template <typename Builder>
202{
203 cycle_group result(_context);
204
205 if (_in.is_point_at_infinity()) {
206 result = constant_infinity(_context);
207 } else {
208 result._x = field_t::from_witness(_context, _in.x);
209 result._y = field_t::from_witness(_context, _in.y);
210 result._x.assert_equal(result._x.get_value());
211 result._y.assert_equal(result._y.get_value());
212 }
213 // point at infinity is circuit constant
214 result._is_infinity = _in.is_point_at_infinity();
215 result.unset_free_witness_tag();
216 return result;
217}
218
219template <typename Builder> Builder* cycle_group<Builder>::get_context(const cycle_group& other) const
220{
221 if (get_context() != nullptr) {
222 return get_context();
224 return other.get_context();
225}
226
229 AffineElement result(x().get_value(), y().get_value());
230 if (is_point_at_infinity().get_value()) {
231 result.self_set_infinity();
233 return result;
234}
235
242template <typename Builder> void cycle_group<Builder>::validate_on_curve() const
243{
244 // This class is for short Weierstrass curves only!
245 static_assert(Group::curve_a == 0);
246 auto xx = _x * _x;
247 auto xxx = xx * _x;
248 auto res = _y.madd(_y, -xxx - Group::curve_b);
249 // If this is the point at infinity, then res is changed to 0, otherwise it remains unchanged
250 res *= !is_point_at_infinity();
251 res.assert_is_zero();
252}
253
259template <typename Builder> void cycle_group<Builder>::standardize()
260{
261 this->_x = field_t::conditional_assign(this->_is_infinity, 0, this->_x);
262 this->_y = field_t::conditional_assign(this->_is_infinity, 0, this->_y);
263}
264
272template <typename Builder>
274{
275 // If this is a constant point at infinity, return early
276 if (this->is_constant_point_at_infinity()) {
277 return *this;
278 }
279
280 // To support the point at infinity, we conditionally modify y to be 1 to avoid division by zero in the
281 // doubling formula
282 auto modified_y = field_t::conditional_assign(is_point_at_infinity(), 1, _y);
283
284 // Compute the doubled point coordinates (either from hint or by native calculation)
285 bb::fr x3;
286 bb::fr y3;
287 if (hint.has_value()) {
288 x3 = hint.value().x;
289 y3 = hint.value().y;
290 } else {
291 const bb::fr x1 = _x.get_value();
292 const bb::fr y1 = modified_y.get_value();
293
294 // N.B. the formula to derive the witness value for x3 mirrors the formula in elliptic_relation.hpp
295 // Specifically, we derive x^4 via the Short Weierstrass curve formula y^2 = x^3 + b i.e. x^4 = x * (y^2 - b)
296 // We must follow this pattern exactly to support the edge-case where the input is the point at infinity.
297 const bb::fr y_pow_2 = y1.sqr();
298 const bb::fr x_pow_4 = x1 * (y_pow_2 - Group::curve_b);
299 const bb::fr lambda_squared = (x_pow_4 * 9) / (y_pow_2 * 4);
300 const bb::fr lambda = (x1 * x1 * 3) / (y1 + y1);
301 x3 = lambda_squared - x1 - x1;
302 y3 = lambda * (x1 - x3) - y1;
303 }
304
305 // Construct the doubled point based on whether this is a constant or witness
306 cycle_group result;
307 if (is_constant()) {
308 result = cycle_group(x3, y3, is_point_at_infinity(), /*assert_on_curve=*/false);
309 // Propagate the origin tag as-is
310 result.set_origin_tag(get_origin_tag());
311 } else {
312 // Create result witness and construct ECC double constraint
313 result = cycle_group(
314 witness_t(context, x3), witness_t(context, y3), is_point_at_infinity(), /*assert_on_curve=*/false);
315
316 context->create_ecc_dbl_gate(bb::ecc_dbl_gate_<bb::fr>{
317 .x1 = _x.get_witness_index(),
318 .y1 = modified_y.get_witness_index(),
319 .x3 = result._x.get_witness_index(),
320 .y3 = result._y.get_witness_index(),
321 });
322
323 // Merge the x and y origin tags since the output depends on both
324 result._x.set_origin_tag(OriginTag(_x.get_origin_tag(), _y.get_origin_tag()));
325 result._y.set_origin_tag(OriginTag(_x.get_origin_tag(), _y.get_origin_tag()));
326 }
327
328 return result;
329}
330
343template <typename Builder>
345 bool is_addition,
346 const std::optional<AffineElement> hint) const
347{
348 // This method should not be called on known points at infinity
349 BB_ASSERT(!this->is_constant_point_at_infinity(),
350 "cycle_group::_unconditional_add_or_subtract called on constant point at infinity");
352 "cycle_group::_unconditional_add_or_subtract called on constant point at infinity");
353
354 auto context = get_context(other);
355
356 // If one point is a witness and the other is a constant, convert the constant to a fixed witness then call this
357 // method again so we can use the ecc_add gate
358 const bool lhs_constant = is_constant();
359 const bool rhs_constant = other.is_constant();
360
361 if (lhs_constant && !rhs_constant) {
362 auto lhs = cycle_group::from_constant_witness(context, get_value());
363 lhs.set_origin_tag(get_origin_tag()); // Maintain the origin tag
364 return lhs._unconditional_add_or_subtract(other, is_addition, hint);
365 }
366 if (!lhs_constant && rhs_constant) {
368 rhs.set_origin_tag(other.get_origin_tag()); // Maintain the origin tag
369 return _unconditional_add_or_subtract(rhs, is_addition, hint);
370 }
371
372 // Compute the result coordinates (either from hint or by native calculation)
373 bb::fr x3;
374 bb::fr y3;
375 if (hint.has_value()) {
376 x3 = hint.value().x;
377 y3 = hint.value().y;
378 } else {
379 const AffineElement p1 = get_value();
380 const AffineElement p2 = other.get_value();
381 AffineElement p3 = is_addition ? (Element(p1) + Element(p2)) : (Element(p1) - Element(p2));
382 x3 = p3.x;
383 y3 = p3.y;
384 }
385
386 // Construct the result based on whether inputs are constant or witness
387 cycle_group result;
388 if (lhs_constant && rhs_constant) {
389 result = cycle_group(x3, y3, /*is_infinity=*/false, /*assert_on_curve=*/false);
390 } else {
391 // Both points are witnesses - create result witness and construct ECC add constraint
392 result = cycle_group(
393 witness_t(context, x3), witness_t(context, y3), /*is_infinity=*/false, /*assert_on_curve=*/false);
394
395 context->create_ecc_add_gate(bb::ecc_add_gate_<bb::fr>{
396 .x1 = _x.get_witness_index(),
397 .y1 = _y.get_witness_index(),
398 .x2 = other._x.get_witness_index(),
399 .y2 = other._y.get_witness_index(),
400 .x3 = result._x.get_witness_index(),
401 .y3 = result._y.get_witness_index(),
402 .sign_coefficient = is_addition ? 1 : -1,
403 });
404 }
405
406 // Merge the origin tags from both inputs
407 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
408
409 return result;
410}
411
418template <typename Builder>
420 const std::optional<AffineElement> hint) const
421{
422 return _unconditional_add_or_subtract(other, /*is_addition=*/true, hint);
423}
424
431template <typename Builder>
433 const std::optional<AffineElement> hint) const
434{
435 return _unconditional_add_or_subtract(other, /*is_addition=*/false, hint);
436}
437
449template <typename Builder>
451 const std::optional<AffineElement> hint) const
452{
453 const field_t x_delta = this->_x - other._x;
454 if (x_delta.is_constant()) {
455 BB_ASSERT(x_delta.get_value() != 0);
456 } else {
457 x_delta.assert_is_not_zero("cycle_group::checked_unconditional_add, x-coordinate collision");
458 }
459 return unconditional_add(other, hint);
460}
461
474template <typename Builder>
476 const std::optional<AffineElement> hint) const
477{
478 const field_t x_delta = this->_x - other._x;
479 if (x_delta.is_constant()) {
480 BB_ASSERT(x_delta.get_value() != 0);
481 } else {
482 x_delta.assert_is_not_zero("cycle_group::checked_unconditional_subtract, x-coordinate collision");
483 }
484 return unconditional_subtract(other, hint);
485}
486
497template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator+(const cycle_group& other) const
498{
499 // If lhs is constant point at infinity, return the rhs and vice versa
500 if (this->is_constant_point_at_infinity()) {
501 return other;
502 }
503 if (other.is_constant_point_at_infinity()) {
504 return *this;
505 }
506
507 const bool_t x_coordinates_match = (_x == other._x);
508 const bool_t y_coordinates_match = (_y == other._y);
509
510 const field_t x1 = _x;
511 const field_t y1 = _y;
512 const field_t x2 = other._x;
513 const field_t y2 = other._y;
514
515 // Execute point addition with modified lambda = (y2 - y1)/(x2 - x1 + x_coordinates_match) to avoid the possibility
516 // of division by zero.
517 const field_t x_diff = x2.add_two(-x1, x_coordinates_match);
518 // Compute lambda in one of two ways depending on whether either numerator or denominator is constant or not
519 field_t lambda;
520 if ((y1.is_constant() && y2.is_constant()) || x_diff.is_constant()) {
521 lambda = (y2 - y1).divide_no_zero_check(x_diff);
522 } else {
523 // Note: branch saves one gate vs just using divide_no_zero_check because we avoid computing y2 - y1 in circuit
524 Builder* context = get_context(other);
525 lambda = field_t::from_witness(context, (y2.get_value() - y1.get_value()) / x_diff.get_value());
526 // We need to manually propagate the origin tag
528 // Constrain x_diff * lambda = y2 - y1
529 field_t::evaluate_polynomial_identity(x_diff, lambda, -y2, y1);
530 }
531 const field_t add_result_x = lambda.madd(lambda, -(x2 + x1)); // x3 = lambda^2 - x1 - x2
532 const field_t add_result_y = lambda.madd(x1 - add_result_x, -y1); // y3 = lambda * (x1 - x3) - y1
533
534 // Compute the doubling result
535 const cycle_group dbl_result = dbl();
536
537 // If the addition amounts to a doubling then the result is the doubling result, else the addition result.
538 const bool_t double_predicate = (x_coordinates_match && y_coordinates_match);
539 auto result_x = field_t::conditional_assign(double_predicate, dbl_result._x, add_result_x);
540 auto result_y = field_t::conditional_assign(double_predicate, dbl_result._y, add_result_y);
541
542 // If the lhs is the point at infinity, return rhs
543 const bool_t lhs_infinity = is_point_at_infinity();
544 result_x = field_t::conditional_assign(lhs_infinity, other._x, result_x);
545 result_y = field_t::conditional_assign(lhs_infinity, other._y, result_y);
546
547 // If the rhs is the point at infinity, return lhs
548 const bool_t rhs_infinity = other.is_point_at_infinity();
549 result_x = field_t::conditional_assign(rhs_infinity, _x, result_x);
550 result_y = field_t::conditional_assign(rhs_infinity, _y, result_y);
551
552 // The result is the point at infinity if:
553 // (lhs._x, lhs._y) == (rhs._x, -rhs._y) and neither are infinity, OR both are the point at infinity
554 const bool_t infinity_predicate = (x_coordinates_match && !y_coordinates_match);
555 bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
556 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
557
558 return cycle_group(result_x, result_y, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
559}
560
571template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator-(const cycle_group& other) const
572{
573 // If lhs is constant point at infinity, return -rhs
574 if (this->is_constant_point_at_infinity()) {
575 return -other;
576 }
577 // If rhs is constant point at infinity, return the lhs
578 if (other.is_constant_point_at_infinity()) {
579 return *this;
580 }
581
582 Builder* context = get_context(other);
583
584 const bool_t x_coordinates_match = (_x == other._x);
585 const bool_t y_coordinates_match = (_y == other._y);
586
587 const field_t x1 = _x;
588 const field_t y1 = _y;
589 const field_t x2 = other._x;
590 const field_t y2 = other._y;
591
592 // Execute point addition with modified lambda = (-y2 - y1)/(x2 - x1 + x_coordinates_match) to avoid the possibility
593 // of division by zero.
594 const field_t x_diff = x2.add_two(-x1, x_coordinates_match);
595 // Compute lambda in one of two ways depending on whether either numerator or denominator is constant or not
596 field_t lambda;
597 if ((y1.is_constant() && y2.is_constant()) || x_diff.is_constant()) {
598 lambda = (-y2 - y1).divide_no_zero_check(x_diff);
599 } else {
600 // Note: branch saves one gate vs using divide_no_zero_check because we avoid computing (-y2 - y1) in circuit
601 lambda = field_t::from_witness(context, (-y2.get_value() - y1.get_value()) / x_diff.get_value());
602 // We need to manually propagate the origin tag
604 // Constrain x_diff * lambda = -y2 - y1
605 field_t::evaluate_polynomial_identity(x_diff, lambda, y2, y1);
606 }
607 const field_t sub_result_x = lambda.madd(lambda, -(x2 + x1)); // x3 = lambda^2 - x1 - x2
608 const field_t sub_result_y = lambda.madd(x1 - sub_result_x, -y1); // y3 = lambda * (x1 - x3) - y1
609
610 // Compute the doubling result
611 const cycle_group dbl_result = dbl();
612
613 // If the subtraction amounts to a doubling then the result is the doubling result, else the subtraction result.
614 // Note: The assumption here is that x1 == x2 && y1 != y2 implies y1 == -y2 which is true assuming that the points
615 // are both on the curve.
616 const bool_t double_predicate = (x_coordinates_match && !y_coordinates_match);
617 auto result_x = field_t::conditional_assign(double_predicate, dbl_result._x, sub_result_x);
618 auto result_y = field_t::conditional_assign(double_predicate, dbl_result._y, sub_result_y);
619
620 mark_witness_as_used(result_x);
621 mark_witness_as_used(result_y);
622
623 // If the lhs is the point at infinity, return -rhs
624 const bool_t lhs_infinity = is_point_at_infinity();
625 result_x = field_t::conditional_assign(lhs_infinity, other._x, result_x);
626 result_y = field_t::conditional_assign(lhs_infinity, (-other._y), result_y);
627
628 // If the rhs is the point at infinity, return lhs
629 const bool_t rhs_infinity = other.is_point_at_infinity();
630 result_x = field_t::conditional_assign(rhs_infinity, _x, result_x);
631 result_y = field_t::conditional_assign(rhs_infinity, _y, result_y);
632
633 // The result is the point at infinity if:
634 // (lhs.x, lhs.y) == (rhs.x, rhs.y) and neither are infinity, OR both are the point at infinity
635 const bool_t infinity_predicate = (x_coordinates_match && y_coordinates_match);
636 mark_witness_as_used(field_t(infinity_predicate));
637 bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
638 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
639
640 return cycle_group(result_x, result_y, /*is_infinity=*/result_is_infinity, /*assert_on_curve=*/false);
641}
642
650template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator-() const
651{
652 cycle_group result(*this);
653 // We have to normalize immediately. All the methods, related to
654 // elliptic curve operations, assume that the coordinates are in normalized form and
655 // don't perform any extra normalizations
656 result._y = (-_y).normalize();
657 return result;
658}
659
660template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator+=(const cycle_group& other)
661{
662 *this = *this + other;
663 return *this;
664}
665
666template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator-=(const cycle_group& other)
667{
668 *this = *this - other;
669 return *this;
670}
671
692template <typename Builder>
694 const std::span<cycle_scalar> scalars,
695 const std::span<cycle_group> base_points,
696 const std::span<AffineElement const> offset_generators,
697 const bool unconditional_add)
698{
699 BB_ASSERT_EQ(!scalars.empty(), true, "Empty scalars provided to variable base batch mul!");
700 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in variable base batch mul!");
701 BB_ASSERT_EQ(offset_generators.size(), base_points.size() + 1, "Too few offset generators provided!");
702 const size_t num_points = scalars.size();
703
704 // Extract the circuit context from any scalar or point
705 Builder* context = nullptr;
706 for (auto [scalar, point] : zip_view(scalars, base_points)) {
707 if (context = scalar.get_context(); context != nullptr) {
708 break;
709 }
710 if (context = point.get_context(); context != nullptr) {
711 break;
712 }
713 }
714
715 // All cycle_scalars are guaranteed to be 254 bits
716 constexpr size_t num_rounds = numeric::ceil_div(cycle_scalar::NUM_BITS, ROM_TABLE_BITS);
717
718 // Decompose each scalar into 4-bit slices. Note: This operation enforces range constraints on the lo/hi limbs of
719 // each scalar (LO_BITS and (num_bits - LO_BITS) respectively).
721 scalar_slices.reserve(num_points);
722 for (const auto& scalar : scalars) {
723 scalar_slices.emplace_back(context, scalar, ROM_TABLE_BITS);
724 }
725
726 // Compute the result of each point addition involved in the Straus MSM algorithm natively so they can be used as
727 // "hints" in the in-circuit Straus algorithm. This includes the additions needed to construct the point tables and
728 // those needed to compute the MSM via Straus. Points are computed as Element types with a Z-coordinate then
729 // batch-converted to AffineElement types. This avoids the need to compute modular inversions for every group
730 // operation, which dramatically reduces witness generation times.
731 std::vector<Element> operation_transcript;
732 Element offset_generator_accumulator = offset_generators[0];
733 {
734 // For each point, construct native straus lookup table of the form {G , G + [1]P, G + [2]P, ... , G + [15]P}
735 std::vector<std::vector<Element>> native_straus_tables;
736 for (size_t i = 0; i < num_points; ++i) {
737 AffineElement point = base_points[i].get_value();
738 AffineElement offset = offset_generators[i + 1];
739 std::vector<Element> table = straus_lookup_table::compute_native_table(point, offset, ROM_TABLE_BITS);
740 // Copy all but the first entry (the offset generator) into the operation transcript for use as hints
741 std::copy(table.begin() + 1, table.end(), std::back_inserter(operation_transcript));
742 native_straus_tables.emplace_back(std::move(table));
743 }
744
745 // Perform the Straus algorithm natively to generate the witness values (hints) for all intermediate points
746 Element accumulator = offset_generators[0];
747 for (size_t i = 0; i < num_rounds; ++i) {
748 if (i != 0) {
749 // Perform doublings of the accumulator and offset generator accumulator
750 for (size_t j = 0; j < ROM_TABLE_BITS; ++j) {
751 accumulator = accumulator.dbl();
752 operation_transcript.push_back(accumulator);
753 offset_generator_accumulator = offset_generator_accumulator.dbl();
754 }
755 }
756 for (size_t j = 0; j < num_points; ++j) {
757 // Look up and accumulate the appropriate point for this scalar slice
758 auto slice_value = static_cast<size_t>(scalar_slices[j].slices_native[num_rounds - i - 1]);
759 const Element point = native_straus_tables[j][slice_value];
760 accumulator += point;
761
762 // Populate hint and update offset generator accumulator
763 operation_transcript.push_back(accumulator);
764 offset_generator_accumulator += Element(offset_generators[j + 1]);
765 }
766 }
767 }
768
769 // Normalize the computed witness points and convert them into AffineElements
770 Element::batch_normalize(&operation_transcript[0], operation_transcript.size());
771 std::vector<AffineElement> operation_hints;
772 operation_hints.reserve(operation_transcript.size());
773 for (const Element& element : operation_transcript) {
774 operation_hints.emplace_back(element.x, element.y);
775 }
776
777 // Construct an in-circuit Straus lookup table for each point
779 point_tables.reserve(num_points);
780 const size_t hints_per_table = (1ULL << ROM_TABLE_BITS) - 1;
781 for (size_t i = 0; i < num_points; ++i) {
782 // Construct Straus table
783 std::span<AffineElement> table_hints(&operation_hints[i * hints_per_table], hints_per_table);
784 straus_lookup_table table(context, base_points[i], offset_generators[i + 1], ROM_TABLE_BITS, table_hints);
785 point_tables.push_back(std::move(table));
786 }
787
788 // Initialize pointer to the precomputed Straus algorithm hints (stored just after the table construction hints)
789 AffineElement* hint_ptr = &operation_hints[num_points * hints_per_table];
790 cycle_group accumulator = offset_generators[0];
791
792 // Execute Straus algorithm in-circuit using the precomputed hints.
793 // If unconditional_add == false, accumulate x-coordinate differences to batch-validate no collisions.
794 field_t coordinate_check_product = 1;
795 for (size_t i = 0; i < num_rounds; ++i) {
796 // Double the accumulator ROM_TABLE_BITS times (except in first round)
797 if (i != 0) {
798 for (size_t j = 0; j < ROM_TABLE_BITS; ++j) {
799 accumulator = accumulator.dbl(*hint_ptr);
800 hint_ptr++;
801 }
802 }
803 // Add the contribution from each point's scalar slice for this round
804 for (size_t j = 0; j < num_points; ++j) {
805 const field_t scalar_slice = scalar_slices[j][num_rounds - i - 1];
806 BB_ASSERT_EQ(scalar_slice.get_value(), scalar_slices[j].slices_native[num_rounds - i - 1]); // Sanity check
807 const cycle_group point = point_tables[j].read(scalar_slice);
808 if (!unconditional_add) {
809 coordinate_check_product *= point._x - accumulator._x;
810 }
811 accumulator = accumulator.unconditional_add(point, *hint_ptr);
812 hint_ptr++;
813 }
814 }
815
816 // Batch-validate no x-coordinate collisions occurred. We batch because each assert_is_not_zero
817 // requires an expensive modular inversion during witness generation.
818 if (!unconditional_add) {
819 coordinate_check_product.assert_is_not_zero("_variable_base_batch_mul_internal x-coordinate collision");
820 }
821
822 // Note: offset_generator_accumulator represents the sum of all the offset generator terms present in `accumulator`.
823 // We don't subtract it off yet as we may be able to combine it with other constant terms in `batch_mul` before
824 // performing the subtraction.
825 return { accumulator, AffineElement(offset_generator_accumulator) };
826}
827
851template <typename Builder>
853 const std::span<cycle_scalar> scalars, const std::span<AffineElement> base_points)
854{
855 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in fixed-base batch mul");
856
860
861 std::vector<MultiTableId> multitable_ids;
862 std::vector<field_t> scalar_limbs;
863
864 for (const auto [point, scalar] : zip_view(base_points, scalars)) {
865 std::array<MultiTableId, 2> table_id = table::get_lookup_table_ids_for_point(point);
866 multitable_ids.push_back(table_id[0]);
867 multitable_ids.push_back(table_id[1]);
868 scalar_limbs.push_back(scalar.lo());
869 scalar_limbs.push_back(scalar.hi());
870 }
871
872 // Look up the multiples of each slice of each lo/hi scalar limb in the corresponding plookup table.
873 std::vector<cycle_group> lookup_points;
874 Element offset_generator_accumulator = Group::point_at_infinity;
875 for (const auto [table_id, scalar] : zip_view(multitable_ids, scalar_limbs)) {
876 // Each lookup returns multiple EC points corresponding to different bit slices of the scalar.
877 // For a scalar slice s_i at bit position (table_bits*i), the table stores the point:
878 // P_i = [offset_generator_i] + (s_i * 2^(table_bits*i)) * [base_point]
880 for (size_t j = 0; j < lookup_data[ColumnIdx::C2].size(); ++j) {
881 const field_t x = lookup_data[ColumnIdx::C2][j];
882 const field_t y = lookup_data[ColumnIdx::C3][j];
883 lookup_points.emplace_back(x, y, /*is_infinity=*/false, /*assert_on_curve=*/false);
884 }
885 // Update offset accumulator with the total offset for the corresponding multitable
886 offset_generator_accumulator += table::get_generator_offset_for_table_id(table_id);
887 }
888
889 // Compute the witness values of the batch_mul algorithm natively, as Element types with a Z-coordinate.
890 std::vector<Element> operation_transcript;
891 {
892 Element accumulator = lookup_points[0].get_value();
893 for (size_t i = 1; i < lookup_points.size(); ++i) {
894 accumulator += (lookup_points[i].get_value());
895 operation_transcript.push_back(accumulator);
896 }
897 }
898 // Batch-convert to AffineElement types, and feed these points as "hints" into the in-circuit addition. This avoids
899 // the need to compute modular inversions for every group operation, which dramatically reduces witness generation
900 // times.
901 Element::batch_normalize(&operation_transcript[0], operation_transcript.size());
902 std::vector<AffineElement> operation_hints;
903 operation_hints.reserve(operation_transcript.size());
904 for (const Element& element : operation_transcript) {
905 operation_hints.emplace_back(element.x, element.y);
906 }
907
908 // Perform the in-circuit point additions sequentially. Each addition costs 1 gate iff additions are chained such
909 // that the output of each addition is the input to the next. Otherwise, each addition costs 2 gates.
910 cycle_group accumulator = lookup_points[0];
911 for (size_t i = 1; i < lookup_points.size(); ++i) {
912 accumulator = accumulator.unconditional_add(lookup_points[i], operation_hints[i - 1]);
913 }
914
915 // The offset_generator_accumulator represents the sum of all the offset generator terms present in `accumulator`.
916 // We don't subtract off yet, as we may be able to combine `offset_generator_accumulator` with other constant
917 // terms in `batch_mul` before performing the subtraction.
918 return { accumulator, offset_generator_accumulator };
919}
920
957template <typename Builder>
959 const std::vector<cycle_scalar>& scalars,
961{
962 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in batch mul!");
963
964 if (scalars.empty()) {
965 cycle_group result{ Group::point_at_infinity };
966 return result;
967 }
968
969 std::vector<cycle_scalar> variable_base_scalars;
970 std::vector<cycle_group> variable_base_points;
971 std::vector<cycle_scalar> fixed_base_scalars;
972 std::vector<AffineElement> fixed_base_points;
973
974 // Merge all tags
975 OriginTag result_tag;
976 for (auto [point, scalar] : zip_view(base_points, scalars)) {
977 result_tag = OriginTag(result_tag, OriginTag(point.get_origin_tag(), scalar.get_origin_tag()));
978 }
979
980 // We can unconditionally add in the variable-base algorithm iff all of the input points are fixed-base points (i.e.
981 // we are doing fixed-base mul over points not present in our plookup tables)
982 bool can_unconditional_add = true;
983 bool has_non_constant_component = false;
984 Element constant_acc = Group::point_at_infinity;
985 for (const auto [point, scalar] : zip_view(base_points, scalars)) {
986 if (scalar.is_constant() && point.is_constant()) {
987 // Case 1: both point and scalar are constant; update constant accumulator without adding gates
988 constant_acc += (point.get_value()) * (scalar.get_value());
989 } else if (!scalar.is_constant() && point.is_constant()) {
990 if (point.get_value().is_point_at_infinity()) {
991 // oi mate, why are you creating a circuit that multiplies a known point at infinity?
992#ifndef FUZZING_DISABLE_WARNINGS
993 info("Warning: Performing batch mul with constant point at infinity!");
994#endif
995 continue;
996 }
998 // Case 2A: constant point is one of two for which we have plookup tables; use fixed-base Straus
999 fixed_base_scalars.push_back(scalar);
1000 fixed_base_points.push_back(point.get_value());
1001 } else {
1002 // Case 2B: constant point but no precomputed lookup tables; use variable-base Straus with ROM tables
1003 variable_base_scalars.push_back(scalar);
1004 variable_base_points.push_back(point);
1005 }
1006 has_non_constant_component = true;
1007 } else {
1008 // Case 3: point is a witness; use variable-base Straus with ROM tables
1009 variable_base_scalars.push_back(scalar);
1010 variable_base_points.push_back(point);
1011 can_unconditional_add = false;
1012 has_non_constant_component = true;
1013 }
1014 }
1015
1016 // If all inputs are constant, return the computed constant component and call it a day.
1017 if (!has_non_constant_component) {
1018 auto result = cycle_group(constant_acc);
1019 result.set_origin_tag(result_tag);
1020 return result;
1021 }
1022
1023 // Add the constant component into our offset accumulator. (Note: we'll subtract `offset_accumulator` from the MSM
1024 // output later on so we negate here to counter that future negation).
1025 Element offset_accumulator = -constant_acc;
1026 const bool has_variable_points = !variable_base_points.empty();
1027 const bool has_fixed_points = !fixed_base_points.empty();
1028
1029 cycle_group result;
1030 if (has_fixed_points) {
1031 // Compute the result of the fixed-base portion of the MSM and update the offset accumulator
1032 const auto [fixed_accumulator, offset_generator_delta] =
1033 _fixed_base_batch_mul_internal(fixed_base_scalars, fixed_base_points);
1034 offset_accumulator += offset_generator_delta;
1035 result = fixed_accumulator;
1036 }
1037
1038 if (has_variable_points) {
1039 // Compute required offset generators; one per point plus one extra for the initial accumulator
1040 const size_t num_offset_generators = variable_base_points.size() + 1;
1041 const std::span<AffineElement const> offset_generators =
1042 context.generators->get(num_offset_generators, 0, OFFSET_GENERATOR_DOMAIN_SEPARATOR);
1043
1044 // Compute the result of the variable-base portion of the MSM and update the offset accumulator
1045 const auto [variable_accumulator, offset_generator_delta] = _variable_base_batch_mul_internal(
1046 variable_base_scalars, variable_base_points, offset_generators, can_unconditional_add);
1047 offset_accumulator += offset_generator_delta;
1048
1049 // Combine the variable-base result with the fixed-base result (if present)
1050 if (has_fixed_points) {
1051 result = can_unconditional_add ? result.unconditional_add(variable_accumulator)
1052 : result.checked_unconditional_add(variable_accumulator);
1053 } else {
1054 result = variable_accumulator;
1055 }
1056 }
1057
1058 // Update `result` to remove the offset generator terms, and add in any constant terms from `constant_acc`.
1059 // We have two potential modes here:
1060 // 1. All inputs are fixed-base and constant_acc is not the point at infinity
1061 // 2. Everything else.
1062 // Case 1 is a special case, as we *know* we cannot hit incomplete addition edge cases, under the assumption that
1063 // all input points are linearly independent of one another. Because constant_acc is not the point at infinity we
1064 // know that at least 1 input scalar was not zero, i.e. the output will not be the point at infinity. We also know
1065 // that, under case 1, we won't trigger the doubling formula either, as every point is linearly independent of every
1066 // other point (including offset generators).
1067 if (!constant_acc.is_point_at_infinity() && can_unconditional_add) {
1068 result = result.unconditional_add(AffineElement(-offset_accumulator));
1069 } else {
1070 // For case 2, we must use a full subtraction operation that handles all possible edge cases, as the output
1071 // point may be the point at infinity.
1072 // Note about optimizations for posterity: An honest prover might hit the point at infinity, but won't
1073 // trigger the doubling edge case (since doubling edge case implies input points are also the offset generator
1074 // points). We could do the following which would be slightly cheaper than operator-:
1075 // 1. If x-coords match, assert y-coords do not match
1076 // 2. If x-coords match, return point at infinity, else unconditionally compute result - offset_accumulator.
1077 result = result - AffineElement(offset_accumulator);
1078 }
1079 // Ensure the tag of the result is a union of all inputs
1080 result.set_origin_tag(result_tag);
1081 return result;
1082}
1083
1084template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator*(const cycle_scalar& scalar) const
1085{
1086 return batch_mul({ *this }, { scalar });
1087}
1088
1089template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator*=(const cycle_scalar& scalar)
1090{
1091 *this = operator*(scalar);
1092 return *this;
1093}
1094
1095template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator*(const BigScalarField& scalar) const
1096{
1097 return batch_mul({ *this }, { scalar });
1098}
1099
1101{
1102 *this = operator*(scalar);
1103 return *this;
1104}
1105
1107{
1108 this->standardize();
1109 other.standardize();
1110 const auto equal = (_x == other._x) && (_y == other._y) && (this->_is_infinity == other._is_infinity);
1111 return equal;
1112}
1113
1114template <typename Builder> void cycle_group<Builder>::assert_equal(cycle_group& other, std::string const& msg)
1115{
1116 this->standardize();
1117 other.standardize();
1118 _x.assert_equal(other._x, msg);
1119 _y.assert_equal(other._y, msg);
1120 this->_is_infinity.assert_equal(other._is_infinity);
1121}
1122
1123template <typename Builder>
1125 const cycle_group& lhs,
1126 const cycle_group& rhs)
1127{
1128 auto x_res = field_t::conditional_assign(predicate, lhs._x, rhs._x);
1129 auto y_res = field_t::conditional_assign(predicate, lhs._y, rhs._y);
1130 auto _is_infinity_res =
1132
1133 cycle_group<Builder> result(x_res, y_res, _is_infinity_res, /*assert_on_curve=*/false);
1134 return result;
1135};
1136
1139
1140} // namespace bb::stdlib
#define BB_ASSERT(expression,...)
Definition assert.hpp:67
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:77
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:33
constexpr element dbl() const noexcept
BB_INLINE constexpr bool is_point_at_infinity() const noexcept
Container type for lookup table reads.
Definition types.hpp:341
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:59
bool get_value() const
Definition bool.hpp:124
bool is_constant() const
Definition bool.hpp:126
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:465
Builder * get_context() const
Definition bool.hpp:151
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...
stdlib::bool_t< Builder > bool_t
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 cycle_group constant_infinity(Builder *_context=nullptr)
Construct a constant point at infinity.
bool is_constant_point_at_infinity() const
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
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:930
field_t madd(const field_t &to_mul, const field_t &to_add) const
Definition field.cpp:510
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:1124
Builder * get_context() const
Definition field.hpp:419
OriginTag get_origin_tag() const
Definition field.hpp:346
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:828
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:454
void convert_constant_to_fixed_witness(Builder *ctx)
Definition field.hpp:444
bool is_constant() const
Definition field.hpp:429
void set_origin_tag(const OriginTag &new_tag) const
Definition field.hpp:345
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:575
static field_t conditional_assign(const bool_t< Builder > &predicate, const field_t &lhs, const field_t &rhs)
Definition field.hpp:372
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:710
uint32_t get_witness_index() const
Get the witness index of the current field element.
Definition field.hpp:506
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.
void info(Args... args)
Definition log.hpp:75
StrictMock< MockContext > context
ssize_t offset
Definition engine.cpp:36
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...
Definition biggroup.hpp:995
void mark_witness_as_used(const field_t< Builder > &field)
Mark a field_t witness as used (for UltraBuilder only).
Univariate< Fr, domain_end > operator*(const Fr &ff, const Univariate< Fr, domain_end > &uv)
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...
Curve::Element Element
BB_INLINE constexpr field sqr() const noexcept
static constexpr field zero()
Stores temporary variables produced by internal multiplication algorithms.