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"
12
13#include "./cycle_group.hpp"
19namespace bb::stdlib {
20
26// AUDITTODO: Used only by fuzzer. Remove if possible, otherwise mark it accordingly.
27template <typename Builder> cycle_group<Builder>::cycle_group(Builder* _context)
28{
29 *this = constant_infinity(_context);
30}
31
41template <typename Builder>
43 : x(_x.normalize())
44 , y(_y.normalize())
45 , _is_infinity(is_infinity)
46 , _is_standard(is_infinity.is_constant())
47{
48 if (_x.get_context() != nullptr) {
49 context = _x.get_context();
50 } else if (_y.get_context() != nullptr) {
51 context = _y.get_context();
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 ASSERT(get_value().on_curve());
61}
62
75template <typename Builder>
76cycle_group<Builder>::cycle_group(const bb::fr& _x, const bb::fr& _y, bool is_infinity)
77 : x(is_infinity ? 0 : _x)
78 , y(is_infinity ? 0 : _y)
79 , _is_infinity(is_infinity)
80 , _is_standard(true)
81 , context(nullptr)
82{
83 ASSERT(get_value().on_curve());
84}
85
95// AUDITTODO: Used only by fuzzer. Remove if possible, otherwise mark it accordingly.
96template <typename Builder>
98 : x(_in.is_point_at_infinity() ? 0 : _in.x)
99 , y(_in.is_point_at_infinity() ? 0 : _in.y)
100 , _is_infinity(_in.is_point_at_infinity())
101 , _is_standard(true)
102 , context(nullptr)
112template <typename Builder> cycle_group<Builder> cycle_group<Builder>::one(Builder* _context)
113{
114 field_t x(_context, Group::one.x);
115 field_t y(_context, Group::one.y);
116 bool_t is_infinity(_context, false);
118 return cycle_group<Builder>(x, y, is_infinity);
119}
127 cycle_group result(bb::fr(0), bb::fr(0), /*is_infinity=*/true);
128
129 // If context provided, create field_t/bool_t with that context
130 if (_context != nullptr) {
131 result.x = field_t(_context, bb::fr(0));
132 result.y = field_t(_context, bb::fr(0));
133 result._is_infinity = bool_t(_context, true);
134 result.context = _context;
135 }
136
137 return result;
138}
139
152template <typename Builder>
154{
155 cycle_group result(_context);
156
157 // By convention we set the coordinates of the point at infinity to (0,0).
158 if (_in.is_point_at_infinity()) {
159 result.x = field_t::from_witness(_context, bb::fr::zero());
160 result.y = field_t::from_witness(_context, bb::fr::zero());
161 } else {
162 result.x = field_t::from_witness(_context, _in.x);
163 result.y = field_t::from_witness(_context, _in.y);
164 }
165 result._is_infinity = bool_t(witness_t(_context, _in.is_point_at_infinity()));
166 result._is_standard = true;
167 result.validate_on_curve();
168 result.set_free_witness_tag();
169 return result;
170}
171
184template <typename Builder>
186{
187 cycle_group result(_context);
188
189 if (_in.is_point_at_infinity()) {
190 result = constant_infinity(_context);
191 } else {
192 result.x = field_t::from_witness(_context, _in.x);
193 result.y = field_t::from_witness(_context, _in.y);
194 result.x.assert_equal(result.x.get_value());
195 result.y.assert_equal(result.y.get_value());
196 }
197 // point at infinity is circuit constant
198 result._is_infinity = _in.is_point_at_infinity();
199 result._is_standard = true;
200 result.unset_free_witness_tag();
201 return result;
202}
203
204template <typename Builder> Builder* cycle_group<Builder>::get_context(const cycle_group& other) const
205{
206 if (get_context() != nullptr) {
207 return get_context();
208 }
209 return other.get_context();
210}
211
213{
214 AffineElement result(x.get_value(), y.get_value());
215 if (is_point_at_infinity().get_value()) {
216 result.self_set_infinity();
217 }
218 return result;
219}
220
227template <typename Builder> void cycle_group<Builder>::validate_on_curve() const
228{
229 // This class is for short Weierstrass curves only!
230 static_assert(Group::curve_a == 0);
231 auto xx = x * x;
232 auto xxx = xx * x;
233 auto res = y.madd(y, -xxx - Group::curve_b);
234 // If this is the point at infinity, then res is changed to 0, otherwise it remains unchanged
235 res *= !is_point_at_infinity();
236 res.assert_is_zero();
237}
238
246{
247 this->standardize();
248 return *this;
249}
250
251#ifdef FUZZING
257template <typename Builder> void cycle_group<Builder>::set_point_at_infinity(const bool_t& is_infinity)
258{
259 this->_is_standard = true;
260
261 if (is_infinity.is_constant() && this->_is_infinity.is_constant()) {
262 // Check that it's not possible to enter the case when
263 // The point is already infinity, but `is_infinity` = false
264 ASSERT((this->_is_infinity.get_value() == is_infinity.get_value()) || is_infinity.get_value());
265
266 if (is_infinity.get_value()) {
267 *this = constant_infinity(this->context);
268 }
269 return;
270 }
271
272 if (is_infinity.is_constant() && !this->_is_infinity.is_constant()) {
273 if (is_infinity.get_value()) {
274 *this = constant_infinity(this->context);
275 } else {
276 this->_is_infinity.assert_equal(false);
277 this->_is_infinity = false;
278 }
279 return;
280 }
281
282 if (this->is_constant_point_at_infinity()) {
283 // I can't imagine this case happening, but still
284 is_infinity.assert_equal(true);
285
286 *this = constant_infinity(this->context);
287 return;
288 }
289
290 this->x = field_t::conditional_assign(is_infinity, 0, this->x).normalize();
291 this->y = field_t::conditional_assign(is_infinity, 0, this->y).normalize();
292
293 // We won't bump into the case where we end up with non constant coordinates
294 ASSERT(!this->x.is_constant());
295 ASSERT(!this->y.is_constant());
296
297 // We have to check this to avoid the situation, where we change the infinity
298 bool_t set_allowed = (this->_is_infinity == is_infinity) || is_infinity;
299 set_allowed.assert_equal(true);
300 this->_is_infinity = is_infinity;
301
302 // In case we set point at infinity on a constant without an existing context
303 if (this->context == nullptr) {
304 this->context = is_infinity.get_context();
305 }
306}
307#endif
308
315template <typename Builder> void cycle_group<Builder>::standardize()
316{
317 if (this->is_constant_point_at_infinity()) {
318 ASSERT(this->is_constant());
319 ASSERT(this->_is_standard);
320 }
321
322 if (this->_is_standard) {
323 return;
324 }
325 this->_is_standard = true;
326
327 this->x = field_t::conditional_assign(this->_is_infinity, 0, this->x).normalize();
328 this->y = field_t::conditional_assign(this->_is_infinity, 0, this->y).normalize();
329}
330
338template <typename Builder>
340{
341 // If this is a constant point at infinity, return early
342 if (this->is_constant_point_at_infinity()) {
343 return *this;
344 }
345
346 // To support the point at infinity, we conditionally modify y to be 1 to avoid division by zero in the
347 // doubling formula
348 auto modified_y = field_t::conditional_assign(is_point_at_infinity(), 1, y).normalize();
349
350 // Compute the doubled point coordinates (either from hint or by native calculation)
351 bb::fr x3;
352 bb::fr y3;
353 if (hint.has_value()) {
354 x3 = hint.value().x;
355 y3 = hint.value().y;
356 } else {
357 const bb::fr x1 = x.get_value();
358 const bb::fr y1 = modified_y.get_value();
359
360 // N.B. the formula to derive the witness value for x3 mirrors the formula in elliptic_relation.hpp
361 // Specifically, we derive x^4 via the Short Weierstrass curve formula y^2 = x^3 + b i.e. x^4 = x * (y^2 - b)
362 // We must follow this pattern exactly to support the edge-case where the input is the point at infinity.
363 const bb::fr y_pow_2 = y1.sqr();
364 const bb::fr x_pow_4 = x1 * (y_pow_2 - Group::curve_b);
365 const bb::fr lambda_squared = (x_pow_4 * 9) / (y_pow_2 * 4);
366 const bb::fr lambda = (x1 * x1 * 3) / (y1 + y1);
367 x3 = lambda_squared - x1 - x1;
368 y3 = lambda * (x1 - x3) - y1;
369 }
370
371 // Construct the doubled point based on whether this is a constant or witness
372 cycle_group result;
373 if (is_constant()) {
374 result = cycle_group(x3, y3, is_point_at_infinity());
375 // Propagate the origin tag as-is
376 result.set_origin_tag(get_origin_tag());
377 } else {
378 // Create result witness and construct ECC double constraint
379 result = cycle_group(witness_t(context, x3), witness_t(context, y3), is_point_at_infinity());
380
381 context->create_ecc_dbl_gate(bb::ecc_dbl_gate_<bb::fr>{
382 .x1 = x.get_witness_index(),
383 .y1 = modified_y.get_witness_index(),
384 .x3 = result.x.get_witness_index(),
385 .y3 = result.y.get_witness_index(),
386 });
387
388 // Merge the x and y origin tags since the output depends on both
389 result.x.set_origin_tag(OriginTag(x.get_origin_tag(), y.get_origin_tag()));
390 result.y.set_origin_tag(OriginTag(x.get_origin_tag(), y.get_origin_tag()));
391 }
392
393 return result;
394}
395
408template <typename Builder>
410 bool is_addition,
411 const std::optional<AffineElement> hint) const
412{
413 // This method should not be called on known points at infinity
414 ASSERT(!this->is_constant_point_at_infinity(),
415 "cycle_group::_unconditional_add_or_subtract called on constant point at infinity");
417 "cycle_group::_unconditional_add_or_subtract called on constant point at infinity");
418
419 auto context = get_context(other);
420
421 // If one point is a witness and the other is a constant, convert the constant to a fixed witness then call this
422 // method again so we can use the ecc_add gate
423 const bool lhs_constant = is_constant();
424 const bool rhs_constant = other.is_constant();
425
426 if (lhs_constant && !rhs_constant) {
427 auto lhs = cycle_group::from_constant_witness(context, get_value());
428 lhs.set_origin_tag(get_origin_tag()); // Maintain the origin tag
429 return lhs._unconditional_add_or_subtract(other, is_addition, hint);
430 }
431 if (!lhs_constant && rhs_constant) {
433 rhs.set_origin_tag(other.get_origin_tag()); // Maintain the origin tag
434 return _unconditional_add_or_subtract(rhs, is_addition, hint);
435 }
436
437 // Compute the result coordinates (either from hint or by native calculation)
438 bb::fr x3;
439 bb::fr y3;
440 if (hint.has_value()) {
441 x3 = hint.value().x;
442 y3 = hint.value().y;
443 } else {
444 const AffineElement p1 = get_value();
445 const AffineElement p2 = other.get_value();
446 AffineElement p3 = is_addition ? (Element(p1) + Element(p2)) : (Element(p1) - Element(p2));
447 x3 = p3.x;
448 y3 = p3.y;
449 }
450
451 // Construct the result based on whether inputs are constant or witness
452 cycle_group result;
453 if (lhs_constant && rhs_constant) {
454 result = cycle_group(x3, y3, /*is_infinity=*/false);
455 } else {
456 // Both points are witnesses - create result witness and construct ECC add constraint
457 result = cycle_group(witness_t(context, x3), witness_t(context, y3), /*is_infinity=*/false);
458
459 context->create_ecc_add_gate(bb::ecc_add_gate_<bb::fr>{
460 .x1 = x.get_witness_index(),
461 .y1 = y.get_witness_index(),
462 .x2 = other.x.get_witness_index(),
463 .y2 = other.y.get_witness_index(),
464 .x3 = result.x.get_witness_index(),
465 .y3 = result.y.get_witness_index(),
466 .sign_coefficient = is_addition ? 1 : -1,
467 });
468 }
469
470 // Merge the origin tags from both inputs
471 result.set_origin_tag(OriginTag(get_origin_tag(), other.get_origin_tag()));
472
473 return result;
474}
475
476template <typename Builder>
478 const std::optional<AffineElement> hint) const
479{
480 return _unconditional_add_or_subtract(other, /*is_addition=*/true, hint);
481}
482
483template <typename Builder>
485 const std::optional<AffineElement> hint) const
486{
487 return _unconditional_add_or_subtract(other, /*is_addition=*/false, hint);
488}
489
501template <typename Builder>
503 const std::optional<AffineElement> hint) const
504{
505 const field_t x_delta = this->x - other.x;
506 if (x_delta.is_constant()) {
507 ASSERT(x_delta.get_value() != 0);
508 } else {
509 x_delta.assert_is_not_zero("cycle_group::checked_unconditional_add, x-coordinate collision");
510 }
511 return unconditional_add(other, hint);
512}
513
525template <typename Builder>
527 const std::optional<AffineElement> hint) const
528{
529 const field_t x_delta = this->x - other.x;
530 if (x_delta.is_constant()) {
531 ASSERT(x_delta.get_value() != 0);
532 } else {
533 x_delta.assert_is_not_zero("cycle_group::checked_unconditional_subtract, x-coordinate collision");
534 }
535 return unconditional_subtract(other, hint);
536}
537
548template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator+(const cycle_group& other) const
549{
550 // If lhs is constant point at infinity, return the rhs and vice versa
551 if (this->is_constant_point_at_infinity()) {
552 return other;
553 }
554 if (other.is_constant_point_at_infinity()) {
555 return *this;
556 }
557
558 const bool_t x_coordinates_match = (x == other.x);
559 const bool_t y_coordinates_match = (y == other.y);
560
561 const field_t x1 = x;
562 const field_t y1 = y;
563 const field_t x2 = other.x;
564 const field_t y2 = other.y;
565
566 // Execute point addition with modified lambda = (y2 - y1)/(x2 - x1 + x_coordinates_match) to avoid the possibility
567 // of division by zero.
568 const field_t x_diff = x2.add_two(-x1, x_coordinates_match);
569 // Compute lambda in one of two ways depending on whether either numerator or denominator is constant or not
570 field_t lambda;
571 if ((y1.is_constant() && y2.is_constant()) || x_diff.is_constant()) {
572 lambda = (y2 - y1).divide_no_zero_check(x_diff);
573 } else {
574 // Note: branch saves one gate vs just using divide_no_zero_check because we avoid computing y2 - y1 in circuit
575 Builder* context = get_context(other);
576 lambda = field_t::from_witness(context, (y2.get_value() - y1.get_value()) / x_diff.get_value());
577 // We need to manually propagate the origin tag
579 // Constrain x_diff * lambda = y2 - y1
580 field_t::evaluate_polynomial_identity(x_diff, lambda, -y2, y1);
581 }
582 const field_t add_result_x = lambda.madd(lambda, -(x2 + x1)); // x3 = lambda^2 - x1 - x2
583 const field_t add_result_y = lambda.madd(x1 - add_result_x, -y1); // y3 = lambda * (x1 - x3) - y1
584
585 // Compute the doubling result
586 const cycle_group dbl_result = dbl();
587
588 // If the addition amounts to a doubling then the result is the doubling result, else the addition result.
589 const bool_t double_predicate = (x_coordinates_match && y_coordinates_match);
590 auto result_x = field_t::conditional_assign(double_predicate, dbl_result.x, add_result_x);
591 auto result_y = field_t::conditional_assign(double_predicate, dbl_result.y, add_result_y);
592
593 // If the lhs is the point at infinity, return rhs
594 const bool_t lhs_infinity = is_point_at_infinity();
595 result_x = field_t::conditional_assign(lhs_infinity, other.x, result_x);
596 result_y = field_t::conditional_assign(lhs_infinity, other.y, result_y);
597
598 // If the rhs is the point at infinity, return lhs
599 const bool_t rhs_infinity = other.is_point_at_infinity();
600 result_x = field_t::conditional_assign(rhs_infinity, x, result_x).normalize();
601 result_y = field_t::conditional_assign(rhs_infinity, y, result_y).normalize();
602
603 // The result is the point at infinity if:
604 // (lhs.x, lhs.y) == (rhs.x, -rhs.y) and neither are infinity, OR both are the point at infinity
605 const bool_t infinity_predicate = (x_coordinates_match && !y_coordinates_match);
606 bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
607 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
608
609 return cycle_group(result_x, result_y, /*is_infinity=*/result_is_infinity);
610}
611
622template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator-(const cycle_group& other) const
623{
624 // If lhs is constant point at infinity, return -rhs
625 if (this->is_constant_point_at_infinity()) {
626 return -other;
627 }
628 // If rhs is constant point at infinity, return the lhs
629 if (other.is_constant_point_at_infinity()) {
630 return *this;
631 }
632
633 Builder* context = get_context(other);
634
635 const bool_t x_coordinates_match = (x == other.x);
636 const bool_t y_coordinates_match = (y == other.y);
637
638 const field_t x1 = x;
639 const field_t y1 = y;
640 const field_t x2 = other.x;
641 const field_t y2 = other.y;
642
643 // Execute point addition with modified lambda = (-y2 - y1)/(x2 - x1 + x_coordinates_match) to avoid the possibility
644 // of division by zero.
645 const field_t x_diff = x2.add_two(-x1, x_coordinates_match);
646 // Compute lambda in one of two ways depending on whether either numerator or denominator is constant or not
647 field_t lambda;
648 if ((y1.is_constant() && y2.is_constant()) || x_diff.is_constant()) {
649 lambda = (-y2 - y1).divide_no_zero_check(x_diff);
650 } else {
651 // Note: branch saves one gate vs using divide_no_zero_check because we avoid computing (-y2 - y1) in circuit
652 lambda = field_t::from_witness(context, (-y2.get_value() - y1.get_value()) / x_diff.get_value());
653 // We need to manually propagate the origin tag
655 // Constrain x_diff * lambda = -y2 - y1
656 field_t::evaluate_polynomial_identity(x_diff, lambda, y2, y1);
657 }
658 const field_t sub_result_x = lambda.madd(lambda, -(x2 + x1)); // x3 = lambda^2 - x1 - x2
659 const field_t sub_result_y = lambda.madd(x1 - sub_result_x, -y1); // y3 = lambda * (x1 - x3) - y1
660
661 // Compute the doubling result
662 const cycle_group dbl_result = dbl();
663
664 // If the subtraction amounts to a doubling then the result is the doubling result, else the subtraction result.
665 // AUDITTODO: The assumption here is that is y1 != y2 implies y1 == -y2. This is only true if the points are
666 // guaranteed to be on the curve. Ideally we can ensure that on-curve checks are applied to all cycle_group
667 // elements, otherwise we may need to be more precise with these predicates.
668 const bool_t double_predicate = (x_coordinates_match && !y_coordinates_match);
669 auto result_x = field_t::conditional_assign(double_predicate, dbl_result.x, sub_result_x);
670 auto result_y = field_t::conditional_assign(double_predicate, dbl_result.y, sub_result_y);
671
672 if (!result_x.is_constant()) {
673 context->update_used_witnesses(result_x.witness_index);
674 }
675 if (!result_y.is_constant()) {
676 context->update_used_witnesses(result_y.witness_index);
677 }
678
679 // If the lhs is the point at infinity, return -rhs
680 const bool_t lhs_infinity = is_point_at_infinity();
681 result_x = field_t::conditional_assign(lhs_infinity, other.x, result_x);
682 result_y = field_t::conditional_assign(lhs_infinity, (-other.y).normalize(), result_y);
683
684 // If the rhs is the point at infinity, return lhs
685 const bool_t rhs_infinity = other.is_point_at_infinity();
686 result_x = field_t::conditional_assign(rhs_infinity, x, result_x).normalize();
687 result_y = field_t::conditional_assign(rhs_infinity, y, result_y).normalize();
688
689 // The result is the point at infinity if:
690 // (lhs.x, lhs.y) == (rhs.x, rhs.y) and neither are infinity, OR both are the point at infinity
691 const bool_t infinity_predicate = (x_coordinates_match && y_coordinates_match).normalize();
692 if (!infinity_predicate.is_constant()) {
693 context->update_used_witnesses(infinity_predicate.get_normalized_witness_index());
694 }
695 bool_t result_is_infinity = infinity_predicate && (!lhs_infinity && !rhs_infinity);
696 result_is_infinity = result_is_infinity || (lhs_infinity && rhs_infinity);
697
698 return cycle_group(result_x, result_y, /*is_infinity=*/result_is_infinity);
699}
700
708template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator-() const
709{
710 cycle_group result(*this);
711 // We have to normalize immediately. All the methods, related to
712 // elliptic curve operations, assume that the coordinates are in normalized form and
713 // don't perform any extra normalizations
714 result.y = (-y).normalize();
715 return result;
716}
717
718template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator+=(const cycle_group& other)
719{
720 *this = *this + other;
721 return *this;
722}
723
724template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator-=(const cycle_group& other)
725{
726 *this = *this - other;
727 return *this;
728}
729
750template <typename Builder>
752 const std::span<cycle_scalar> scalars,
753 const std::span<cycle_group> base_points,
754 const std::span<AffineElement const> offset_generators,
755 const bool unconditional_add)
756{
757 BB_ASSERT_EQ(!scalars.empty(), true, "Empty scalars provided to variable base batch mul!");
758 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in variable base batch mul!");
759 BB_ASSERT_EQ(offset_generators.size(), base_points.size() + 1, "Too few offset generators provided!");
760 const size_t num_points = scalars.size();
761
762 // Extract the circuit context from any scalar or point
763 Builder* context = nullptr;
764 for (auto [scalar, point] : zip_view(scalars, base_points)) {
765 if (context = scalar.get_context(); context != nullptr) {
766 break;
767 }
768 if (context = point.get_context(); context != nullptr) {
769 break;
770 }
771 }
772
773 // Validate all scalars have the same bit length (required for Straus algorithm to process slices)
774 size_t num_bits = scalars[0].num_bits();
775 for (auto& s : scalars) {
776 BB_ASSERT_EQ(s.num_bits(), num_bits, "Scalars of different bit-lengths not supported!");
777 }
778 size_t num_rounds = numeric::ceil_div(num_bits, ROM_TABLE_BITS);
779
780 // Decompose each scalar into 4-bit slices. Note: This operation enforces range constraints on the lo/hi limbs of
781 // each scalar (LO_BITS and (num_bits - LO_BITS) respectively).
783 scalar_slices.reserve(num_points);
784 for (const auto& scalar : scalars) {
785 scalar_slices.emplace_back(context, scalar, ROM_TABLE_BITS);
786 }
787
788 // Compute the result of each point addition involved in the Straus MSM algorithm natively so they can be used as
789 // "hints" in the in-circuit Straus algorithm. This includes the additions needed to construct the point tables and
790 // those needed to compute the MSM via Straus. Points are computed as Element types with a Z-coordinate then
791 // batch-converted to AffineElement types. This avoids the need to compute modular inversions for every group
792 // operation, which dramatically reduces witness generation times.
793 std::vector<Element> operation_transcript;
794 Element offset_generator_accumulator = offset_generators[0];
795 {
796 // For each point, construct native straus lookup table of the form {G , G + [1]P, G + [2]P, ... , G + [15]P}
797 std::vector<std::vector<Element>> native_straus_tables;
798 for (size_t i = 0; i < num_points; ++i) {
799 AffineElement point = base_points[i].get_value();
800 AffineElement offset = offset_generators[i + 1];
801 std::vector<Element> table = straus_lookup_table::compute_native_table(point, offset, ROM_TABLE_BITS);
802 // Copy all but the first entry (the offset generator) into the operation transcript for use as hints
803 std::copy(table.begin() + 1, table.end(), std::back_inserter(operation_transcript));
804 native_straus_tables.emplace_back(std::move(table));
805 }
806
807 // Perform the Straus algorithm natively to generate the witness values (hints) for all intermediate points
808 Element accumulator = offset_generators[0];
809 for (size_t i = 0; i < num_rounds; ++i) {
810 if (i != 0) {
811 // Perform doublings of the accumulator and offset generator accumulator
812 for (size_t j = 0; j < ROM_TABLE_BITS; ++j) {
813 accumulator = accumulator.dbl();
814 operation_transcript.push_back(accumulator);
815 offset_generator_accumulator = offset_generator_accumulator.dbl();
816 }
817 }
818 for (size_t j = 0; j < num_points; ++j) {
819 // Look up and accumulate the appropriate point for this scalar slice
820 auto slice_value = static_cast<size_t>(scalar_slices[j].slices_native[num_rounds - i - 1]);
821 const Element point = native_straus_tables[j][slice_value];
822 accumulator += point;
823
824 // Populate hint and update offset generator accumulator
825 operation_transcript.push_back(accumulator);
826 offset_generator_accumulator += Element(offset_generators[j + 1]);
827 }
828 }
829 }
830
831 // Normalize the computed witness points and convert them into AffineElements
832 Element::batch_normalize(&operation_transcript[0], operation_transcript.size());
833 std::vector<AffineElement> operation_hints;
834 operation_hints.reserve(operation_transcript.size());
835 for (const Element& element : operation_transcript) {
836 operation_hints.emplace_back(element.x, element.y);
837 }
838
839 // Construct an in-circuit Straus lookup table for each point
841 point_tables.reserve(num_points);
842 const size_t hints_per_table = (1ULL << ROM_TABLE_BITS) - 1;
843 OriginTag tag{};
844 for (size_t i = 0; i < num_points; ++i) {
845 // Merge tags
846 tag = OriginTag(tag, scalars[i].get_origin_tag(), base_points[i].get_origin_tag());
847
848 // Construct Straus table
849 std::span<AffineElement> table_hints(&operation_hints[i * hints_per_table], hints_per_table);
850 straus_lookup_table table(context, base_points[i], offset_generators[i + 1], ROM_TABLE_BITS, table_hints);
851 point_tables.push_back(std::move(table));
852 }
853
854 // Initialize pointer to the precomputed Straus algorithm hints (stored just after the table construction hints)
855 AffineElement* hint_ptr = &operation_hints[num_points * hints_per_table];
856 cycle_group accumulator = offset_generators[0];
857
858 // Execute Straus algorithm in-circuit using the precomputed hints.
859 // If unconditional_add == false, accumulate x-coordinate differences to batch-validate no collisions.
860 field_t coordinate_check_product = 1;
861 for (size_t i = 0; i < num_rounds; ++i) {
862 // Double the accumulator ROM_TABLE_BITS times (except in first round)
863 if (i != 0) {
864 for (size_t j = 0; j < ROM_TABLE_BITS; ++j) {
865 accumulator = accumulator.dbl(*hint_ptr);
866 hint_ptr++;
867 }
868 }
869 // Add the contribution from each point's scalar slice for this round
870 for (size_t j = 0; j < num_points; ++j) {
871 const field_t scalar_slice = scalar_slices[j][num_rounds - i - 1];
872 BB_ASSERT_EQ(scalar_slice.get_value(), scalar_slices[j].slices_native[num_rounds - i - 1]); // Sanity check
873 const cycle_group point = point_tables[j].read(scalar_slice);
874 if (!unconditional_add) {
875 coordinate_check_product *= point.x - accumulator.x;
876 }
877 accumulator = accumulator.unconditional_add(point, *hint_ptr);
878 hint_ptr++;
879 }
880 }
881
882 // Batch-validate no x-coordinate collisions occurred. We batch because each assert_is_not_zero
883 // requires an expensive modular inversion during witness generation.
884 if (!unconditional_add) {
885 coordinate_check_product.assert_is_not_zero("_variable_base_batch_mul_internal x-coordinate collision");
886 }
887
888 // Set the final accumulator's tag to the union of all points' and scalars' tags
889 accumulator.set_origin_tag(tag);
890
891 // Note: offset_generator_accumulator represents the sum of all the offset generator terms present in `accumulator`.
892 // We don't subtract it off yet as we may be able to combine it with other constant terms in `batch_mul` before
893 // performing the subtraction.
894 return { accumulator, AffineElement(offset_generator_accumulator) };
895}
896
920template <typename Builder>
922 const std::span<cycle_scalar> scalars, const std::span<AffineElement> base_points)
923{
924 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in fixed-base batch mul");
925
929
930 std::vector<MultiTableId> multitable_ids;
931 std::vector<field_t> scalar_limbs;
932
933 OriginTag tag{};
934 for (const auto [point, scalar] : zip_view(base_points, scalars)) {
935 // Merge all scalar tags
936 // AUDITTODO: in the variable base method we combine point and scalar tags - should we do the same here?
937 tag = OriginTag(tag, scalar.get_origin_tag());
938 std::array<MultiTableId, 2> table_id = table::get_lookup_table_ids_for_point(point);
939 multitable_ids.push_back(table_id[0]);
940 multitable_ids.push_back(table_id[1]);
941 scalar_limbs.push_back(scalar.lo);
942 scalar_limbs.push_back(scalar.hi);
943 }
944
945 // Look up the multiples of each slice of each lo/hi scalar limb in the corresponding plookup table.
946 std::vector<cycle_group> lookup_points;
947 Element offset_generator_accumulator = Group::point_at_infinity;
948 for (const auto [table_id, scalar] : zip_view(multitable_ids, scalar_limbs)) {
949 // Each lookup returns multiple EC points corresponding to different bit slices of the scalar.
950 // For a scalar slice s_i at bit position (table_bits*i), the table stores the point:
951 // P_i = [offset_generator_i] + (s_i * 2^(table_bits*i)) * [base_point]
953 for (size_t j = 0; j < lookup_data[ColumnIdx::C2].size(); ++j) {
954 const field_t x = lookup_data[ColumnIdx::C2][j];
955 const field_t y = lookup_data[ColumnIdx::C3][j];
956 lookup_points.emplace_back(x, y, /*is_infinity=*/false);
957 }
958 // Update offset accumulator with the total offset for the corresponding multitable
959 offset_generator_accumulator += table::get_generator_offset_for_table_id(table_id);
960 }
961
962 // Compute the witness values of the batch_mul algorithm natively, as Element types with a Z-coordinate.
963 std::vector<Element> operation_transcript;
964 {
965 Element accumulator = lookup_points[0].get_value();
966 for (size_t i = 1; i < lookup_points.size(); ++i) {
967 accumulator += (lookup_points[i].get_value());
968 operation_transcript.push_back(accumulator);
969 }
970 }
971 // Batch-convert to AffineElement types, and feed these points as "hints" into the in-circuit addition. This avoids
972 // the need to compute modular inversions for every group operation, which dramatically reduces witness generation
973 // times.
974 Element::batch_normalize(&operation_transcript[0], operation_transcript.size());
975 std::vector<AffineElement> operation_hints;
976 operation_hints.reserve(operation_transcript.size());
977 for (const Element& element : operation_transcript) {
978 operation_hints.emplace_back(element.x, element.y);
979 }
980
981 // Perform the in-circuit point additions sequentially. Each addition costs 1 gate iff additions are chained such
982 // that the output of each addition is the input to the next. Otherwise, each addition costs 2 gates.
983 cycle_group accumulator = lookup_points[0];
984 for (size_t i = 1; i < lookup_points.size(); ++i) {
985 accumulator = accumulator.unconditional_add(lookup_points[i], operation_hints[i - 1]);
986 }
987
988 // The offset_generator_accumulator represents the sum of all the offset generator terms present in `accumulator`.
989 // We don't subtract off yet, as we may be able to combine `offset_generator_accumulator` with other constant
990 // terms in `batch_mul` before performing the subtraction.
991 accumulator.set_origin_tag(tag); // Set accumulator's origin tag to the union of all scalars' tags
992 return { accumulator, offset_generator_accumulator };
993}
994
1031template <typename Builder>
1033 const std::vector<cycle_scalar>& scalars,
1035{
1036 BB_ASSERT_EQ(scalars.size(), base_points.size(), "Points/scalars size mismatch in batch mul!");
1037
1038 std::vector<cycle_scalar> variable_base_scalars;
1039 std::vector<cycle_group> variable_base_points;
1040 std::vector<cycle_scalar> fixed_base_scalars;
1041 std::vector<AffineElement> fixed_base_points;
1042
1043 // Merge all tags
1044 OriginTag result_tag;
1045 for (auto [point, scalar] : zip_view(base_points, scalars)) {
1046 result_tag = OriginTag(result_tag, OriginTag(point.get_origin_tag(), scalar.get_origin_tag()));
1047 }
1048 size_t num_bits = 0;
1049 for (auto& s : scalars) {
1050 num_bits = std::max(num_bits, s.num_bits());
1051
1052 // Note: is this the best place to put `validate_is_in_field`? Should it not be part of the constructor?
1053 // Note note: validate_scalar_is_in_field does not apply range checks to the hi/lo slices, this is performed
1054 // implicitly via the scalar mul algorithm
1055 s.validate_scalar_is_in_field();
1056 }
1057
1058 // If scalars are not full sized, we skip lookup-version of fixed-base scalar mul. too much complexity
1059 bool scalars_are_full_sized = (num_bits == NUM_BITS_FULL_FIELD_SIZE);
1060
1061 // We can unconditionally add in the variable-base algorithm iff all of the input points are fixed-base points (i.e.
1062 // we are doing fixed-base mul over points not present in our plookup tables)
1063 bool can_unconditional_add = true;
1064 bool has_non_constant_component = false;
1065 Element constant_acc = Group::point_at_infinity;
1066 for (const auto [point, scalar] : zip_view(base_points, scalars)) {
1067 if (scalar.is_constant() && point.is_constant()) {
1068 // Case 1: both point and scalar are constant; update constant accumulator without adding gates
1069 constant_acc += (point.get_value()) * (scalar.get_value());
1070 } else if (!scalar.is_constant() && point.is_constant()) {
1071 if (point.get_value().is_point_at_infinity()) {
1072 // oi mate, why are you creating a circuit that multiplies a known point at infinity?
1073#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1074 info("Warning: Performing batch mul with constant point at infinity!");
1075#endif
1076 continue;
1077 }
1078 if (scalars_are_full_sized &&
1080 // Case 2A: constant point is one of two for which we have plookup tables; use fixed-base Straus
1081 fixed_base_scalars.push_back(scalar);
1082 fixed_base_points.push_back(point.get_value());
1083 } else {
1084 // Case 2B: constant point but no precomputed lookup tables; use variable-base Straus with ROM tables
1085 variable_base_scalars.push_back(scalar);
1086 variable_base_points.push_back(point);
1087 }
1088 has_non_constant_component = true;
1089 } else {
1090 // Case 3: point is a witness; use variable-base Straus with ROM tables
1091 variable_base_scalars.push_back(scalar);
1092 variable_base_points.push_back(point);
1093 can_unconditional_add = false;
1094 has_non_constant_component = true;
1095 }
1096 }
1097
1098 // If all inputs are constant, return the computed constant component and call it a day.
1099 if (!has_non_constant_component) {
1100 auto result = cycle_group(constant_acc);
1101 result.set_origin_tag(result_tag);
1102 return result;
1103 }
1104
1105 // Add the constant component into our offset accumulator. (Note: we'll subtract `offset_accumulator` from the MSM
1106 // output later on so we negate here to counter that future negation).
1107 Element offset_accumulator = -constant_acc;
1108 const bool has_variable_points = !variable_base_points.empty();
1109 const bool has_fixed_points = !fixed_base_points.empty();
1110
1111 cycle_group result;
1112 if (has_fixed_points) {
1113 // Compute the result of the fixed-base portion of the MSM and update the offset accumulator
1114 const auto [fixed_accumulator, offset_generator_delta] =
1115 _fixed_base_batch_mul_internal(fixed_base_scalars, fixed_base_points);
1116 offset_accumulator += offset_generator_delta;
1117 result = fixed_accumulator;
1118 }
1119
1120 if (has_variable_points) {
1121 // Compute required offset generators; one per point plus one extra for the initial accumulator
1122 const size_t num_offset_generators = variable_base_points.size() + 1;
1123 const std::span<AffineElement const> offset_generators =
1124 context.generators->get(num_offset_generators, 0, OFFSET_GENERATOR_DOMAIN_SEPARATOR);
1125
1126 // Compute the result of the variable-base portion of the MSM and update the offset accumulator
1127 const auto [variable_accumulator, offset_generator_delta] = _variable_base_batch_mul_internal(
1128 variable_base_scalars, variable_base_points, offset_generators, can_unconditional_add);
1129 offset_accumulator += offset_generator_delta;
1130
1131 // Combine the variable-base result with the fixed-base result (if present)
1132 if (has_fixed_points) {
1133 result = can_unconditional_add ? result.unconditional_add(variable_accumulator)
1134 : result.checked_unconditional_add(variable_accumulator);
1135 } else {
1136 result = variable_accumulator;
1137 }
1138 }
1139
1140 // Update `result` to remove the offset generator terms, and add in any constant terms from `constant_acc`.
1141 // We have two potential modes here:
1142 // 1. All inputs are fixed-base and constant_acc is not the point at infinity
1143 // 2. Everything else.
1144 // Case 1 is a special case, as we *know* we cannot hit incomplete addition edge cases, under the assumption that
1145 // all input points are linearly independent of one another. Because constant_acc is not the point at infinity we
1146 // know that at least 1 input scalar was not zero, i.e. the output will not be the point at infinity. We also know
1147 // that, under case 1, we won't trigger the doubling formula either, as every point is linearly independent of every
1148 // other point (including offset generators).
1149 if (!constant_acc.is_point_at_infinity() && can_unconditional_add) {
1150 result = result.unconditional_add(AffineElement(-offset_accumulator));
1151 } else {
1152 // For case 2, we must use a full subtraction operation that handles all possible edge cases, as the output
1153 // point may be the point at infinity.
1154 // Note about optimizations for posterity: An honest prover might hit the point at infinity, but won't
1155 // trigger the doubling edge case (since doubling edge case implies input points are also the offset generator
1156 // points). We could do the following which would be slightly cheaper than operator-:
1157 // 1. If x-coords match, assert y-coords do not match
1158 // 2. If x-coords match, return point at infinity, else unconditionally compute result - offset_accumulator.
1159 result = result - AffineElement(offset_accumulator);
1160 }
1161 // Ensure the tag of the result is a union of all inputs
1162 result.set_origin_tag(result_tag);
1163 return result;
1164}
1165
1166template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator*(const cycle_scalar& scalar) const
1167{
1168 return batch_mul({ *this }, { scalar });
1169}
1170
1171template <typename Builder> cycle_group<Builder>& cycle_group<Builder>::operator*=(const cycle_scalar& scalar)
1172{
1173 *this = operator*(scalar);
1174 return *this;
1175}
1176
1177template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator*(const BigScalarField& scalar) const
1178{
1179 return batch_mul({ *this }, { scalar });
1180}
1181
1183{
1184 *this = operator*(scalar);
1185 return *this;
1186}
1187
1189{
1190 this->standardize();
1191 other.standardize();
1192 const auto equal = (x == other.x) && (y == other.y) && (this->_is_infinity == other._is_infinity);
1193 return equal;
1194}
1195
1196template <typename Builder> void cycle_group<Builder>::assert_equal(cycle_group& other, std::string const& msg)
1197{
1198 this->standardize();
1199 other.standardize();
1200 x.assert_equal(other.x, msg);
1201 y.assert_equal(other.y, msg);
1202 this->_is_infinity.assert_equal(other._is_infinity);
1203}
1204
1205template <typename Builder>
1207 const cycle_group& lhs,
1208 const cycle_group& rhs)
1209{
1210 auto x_res = field_t::conditional_assign(predicate, lhs.x, rhs.x).normalize();
1211 auto y_res = field_t::conditional_assign(predicate, lhs.y, rhs.y).normalize();
1212 auto _is_infinity_res =
1214
1215 bool _is_standard_res = lhs._is_standard && rhs._is_standard;
1216 if (predicate.is_constant()) {
1217 _is_standard_res = predicate.get_value() ? lhs._is_standard : rhs._is_standard;
1218 }
1219
1220 // AUDITTODO: Talk to Sasha. Comment seems to be unrelated and its not clear why the logic is needed.
1221 // Rare case when we bump into two constants, s.t. lhs = -rhs
1222 if (x_res.is_constant() && !y_res.is_constant()) {
1223 auto ctx = predicate.get_context();
1224 x_res = field_t::from_witness_index(ctx, ctx->put_constant_variable(x_res.get_value()));
1225 }
1226
1227 cycle_group<Builder> result(x_res, y_res, _is_infinity_res);
1228 result._is_standard = _is_standard_res;
1229 return result;
1230};
1231
1232template <typename Builder> cycle_group<Builder> cycle_group<Builder>::operator/(const cycle_group& /*unused*/) const
1233{
1234 // TODO(@kevaundray solve the discrete logarithm problem)
1235 throw_or_abort("Implementation under construction...");
1236}
1237
1240
1241} // namespace bb::stdlib
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:88
#define ASSERT(expression,...)
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:357
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:111
bool is_constant() const
Definition bool.hpp:113
bool_t normalize() const
A bool_t element is normalized if witness_inverted == false. For a given *this, output its normalized...
Definition bool.cpp:505
uint32_t get_normalized_witness_index() const
Definition bool.hpp:124
static bool_t conditional_assign(const bool_t< Builder > &predicate, const bool_t &lhs, const bool_t &rhs)
Implements the ternary operator - if predicate == true then return lhs, else return rhs.
Definition bool.cpp:456
Builder * get_context() const
Definition bool.hpp:126
void assert_equal(const bool_t &rhs, std::string const &msg="bool_t::assert_equal") const
Implements copy constraint for bool_t elements.
Definition bool.cpp:423
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
void validate_on_curve() const
On-curve check.
cycle_group get_standard_form()
Convert the point to standard form.
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
Will evaluate ECC point subtraction over *this and other.
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) const
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
Will evaluate ECC point addition over *this and other.
cycle_group unconditional_subtract(const cycle_group &other, const std::optional< AffineElement > hint=std::nullopt) const
Builder * get_context() const
cycle_group checked_unconditional_add(const cycle_group &other, const std::optional< AffineElement > hint=std::nullopt) const
Will evaluate ECC point addition over *this and other.
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).
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:931
field_t madd(const field_t &to_mul, const field_t &to_add) const
Definition field.cpp:509
static field_t from_witness_index(Builder *ctx, uint32_t witness_index)
Definition field.cpp:61
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:1114
static field_t conditional_assign(const bool_t< Builder > &predicate, const field_t &lhs, const field_t &rhs)
If predicate == true then return lhs, else return rhs.
Definition field.cpp:886
Builder * get_context() const
Definition field.hpp:397
OriginTag get_origin_tag() const
Definition field.hpp:333
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:829
field_t normalize() const
Return a new element, where the in-circuit witness contains the actual represented value (multiplicat...
Definition field.cpp:637
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:432
bool is_constant() const
Definition field.hpp:407
void set_origin_tag(const OriginTag &new_tag) const
Definition field.hpp:332
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:574
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:709
uint32_t get_witness_index() const
Get the witness index of the current field element.
Definition field.hpp:469
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:74
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...
Univariate< Fr, domain_end, domain_start, skip_count > operator*(const Fr &ff, const Univariate< Fr, domain_end, domain_start, skip_count > &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.
void throw_or_abort(std::string const &err)