barretenberg
Loading...
Searching...
No Matches
biggroup_impl.hpp
1#pragma once
2
3#include "../bit_array/bit_array.hpp"
4#include "../circuit_builders/circuit_builders.hpp"
5
6using namespace barretenberg;
7
8namespace proof_system::plonk::stdlib {
9
10template <typename C, class Fq, class Fr, class G>
11element<C, Fq, Fr, G>::element()
12 : x()
13 , y()
14{}
15
16template <typename C, class Fq, class Fr, class G>
17element<C, Fq, Fr, G>::element(const typename G::affine_element& input)
18 : x(nullptr, input.x)
19 , y(nullptr, input.y)
20{}
21
22template <typename C, class Fq, class Fr, class G>
23element<C, Fq, Fr, G>::element(const Fq& x_in, const Fq& y_in)
24 : x(x_in)
25 , y(y_in)
26{}
27
28template <typename C, class Fq, class Fr, class G>
29element<C, Fq, Fr, G>::element(const element& other)
30 : x(other.x)
31 , y(other.y)
32{}
33
34template <typename C, class Fq, class Fr, class G>
35element<C, Fq, Fr, G>::element(element&& other)
36 : x(other.x)
37 , y(other.y)
38{}
39
40template <typename C, class Fq, class Fr, class G>
41element<C, Fq, Fr, G>& element<C, Fq, Fr, G>::operator=(const element& other)
42{
43 x = other.x;
44 y = other.y;
45 return *this;
46}
47
48template <typename C, class Fq, class Fr, class G>
49element<C, Fq, Fr, G>& element<C, Fq, Fr, G>::operator=(element&& other)
50{
51 x = other.x;
52 y = other.y;
53 return *this;
54}
55
56template <typename C, class Fq, class Fr, class G>
57element<C, Fq, Fr, G> element<C, Fq, Fr, G>::operator+(const element& other) const
58{
59 if constexpr (IsGoblinBuilder<C> && std::same_as<G, barretenberg::g1>) {
60 // TODO(https://github.com/AztecProtocol/barretenberg/issues/707) Optimize
61 // Current gate count: 6398
62 std::vector<element> points{ *this, other };
63 std::vector<Fr> scalars{ 1, 1 };
64 return goblin_batch_mul(points, scalars);
65 }
66
67 other.x.assert_is_not_equal(x);
68 const Fq lambda = Fq::div_without_denominator_check({ other.y, -y }, (other.x - x));
69 const Fq x3 = lambda.sqradd({ -other.x, -x });
70 const Fq y3 = lambda.madd(x - x3, { -y });
71 return element(x3, y3);
72}
73
74template <typename C, class Fq, class Fr, class G>
75element<C, Fq, Fr, G> element<C, Fq, Fr, G>::operator-(const element& other) const
76{
77 if constexpr (IsGoblinBuilder<C> && std::same_as<G, barretenberg::g1>) {
78 // TODO(https://github.com/AztecProtocol/barretenberg/issues/707) Optimize
79 std::vector<element> points{ *this, other };
80 std::vector<Fr> scalars{ 1, -Fr(1) };
81 return goblin_batch_mul(points, scalars);
82 }
83
84 other.x.assert_is_not_equal(x);
85 const Fq lambda = Fq::div_without_denominator_check({ other.y, y }, (other.x - x));
86 const Fq x_3 = lambda.sqradd({ -other.x, -x });
87 const Fq y_3 = lambda.madd(x_3 - x, { -y });
88
89 return element(x_3, y_3);
90}
91
106// TODO(https://github.com/AztecProtocol/barretenberg/issues/657): This function is untested
107template <typename C, class Fq, class Fr, class G>
108std::array<element<C, Fq, Fr, G>, 2> element<C, Fq, Fr, G>::add_sub(const element& other) const
109{
110 if constexpr (IsGoblinBuilder<C> && std::same_as<G, barretenberg::g1>) {
111 return { *this + other, *this - other };
112 }
113
114 other.x.assert_is_not_equal(x);
115
116 const Fq denominator = other.x - x;
117 const Fq x2x1 = -(other.x + x);
118
119 const Fq lambda1 = Fq::div_without_denominator_check({ other.y, -y }, denominator);
120 const Fq x_3 = lambda1.sqradd({ x2x1 });
121 const Fq y_3 = lambda1.madd(x - x_3, { -y });
122 const Fq lambda2 = Fq::div_without_denominator_check({ -other.y, -y }, denominator);
123 const Fq x_4 = lambda2.sqradd({ x2x1 });
124 const Fq y_4 = lambda2.madd(x - x_4, { -y });
125
126 return { element(x_3, y_3), element(x_4, y_4) };
127}
128
129template <typename C, class Fq, class Fr, class G> element<C, Fq, Fr, G> element<C, Fq, Fr, G>::dbl() const
130{
131
132 Fq two_x = x + x;
133 if constexpr (G::has_a) {
134 Fq a(get_context(), uint256_t(G::curve_a));
135 Fq neg_lambda = Fq::msub_div({ x }, { (two_x + x) }, (y + y), { a });
136 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
137 Fq y_3 = neg_lambda.madd(x_3 - x, { -y });
138 return element(x_3, y_3);
139 }
140 Fq neg_lambda = Fq::msub_div({ x }, { (two_x + x) }, (y + y), {});
141 Fq x_3 = neg_lambda.sqradd({ -(two_x) });
142 Fq y_3 = neg_lambda.madd(x_3 - x, { -y });
143 return element(x_3, y_3);
144}
145
164template <typename C, class Fq, class Fr, class G>
166 const element& p2)
168{
170 output.x1_prev = p1.x;
171 output.y1_prev = p1.y;
172
173 p1.x.assert_is_not_equal(p2.x);
174 const Fq lambda = Fq::div_without_denominator_check({ p2.y, -p1.y }, (p2.x - p1.x));
175
176 const Fq x3 = lambda.sqradd({ -p2.x, -p1.x });
177 output.x3_prev = x3;
178 output.lambda_prev = lambda;
179 return output;
180}
181
182template <typename C, class Fq, class Fr, class G>
184 const chain_add_accumulator& acc)
186{
187 // use `chain_add_start` to start an addition chain (i.e. if acc has a y-coordinate)
188 if (acc.is_element) {
189 return chain_add_start(p1, element(acc.x3_prev, acc.y3_prev));
190 }
191 // validate we can use incomplete addition formulae
192 p1.x.assert_is_not_equal(acc.x3_prev);
193
194 // lambda = (y2 - y1) / (x2 - x1)
195 // but we don't have y2!
196 // however, we do know that y2 = lambda_prev * (x1_prev - x2) - y1_prev
197 // => lambda * (x2 - x1) = lambda_prev * (x1_prev - x2) - y1_prev - y1
198 // => lambda * (x2 - x1) + lambda_prev * (x2 - x1_prev) + y1 + y1_pev = 0
199 // => lambda = lambda_prev * (x1_prev - x2) - y1_prev - y1 / (x2 - x1)
200 // => lambda = - (lambda_prev * (x2 - x1_prev) + y1_prev + y1) / (x2 - x1)
201
211 auto& x2 = acc.x3_prev;
212 const auto lambda = Fq::msub_div({ acc.lambda_prev }, { (x2 - acc.x1_prev) }, (x2 - p1.x), { acc.y1_prev, p1.y });
213 const auto x3 = lambda.sqradd({ -x2, -p1.x });
214
216 output.x3_prev = x3;
217 output.x1_prev = p1.x;
218 output.y1_prev = p1.y;
219 output.lambda_prev = lambda;
220
221 return output;
222}
223
227template <typename C, class Fq, class Fr, class G>
230{
231 if (acc.is_element) {
232 return element(acc.x3_prev, acc.y3_prev);
233 }
234 auto& x3 = acc.x3_prev;
235 auto& lambda = acc.lambda_prev;
236
237 Fq y3 = lambda.madd((acc.x1_prev - x3), { -acc.y1_prev });
238 return element(x3, y3);
239}
261// #################################
262// ### SCALAR MULTIPLICATION METHODS
263// #################################
281template <typename C, class Fq, class Fr, class G>
284{
285 other.x.assert_is_not_equal(x);
286 const Fq lambda_1 = Fq::div_without_denominator_check({ other.y - y }, (other.x - x));
287
288 const Fq x_3 = lambda_1.sqradd({ -other.x, -x });
289
290 const Fq minus_lambda_2 = lambda_1 + Fq::div_without_denominator_check({ y + y }, (x_3 - x));
291
292 const Fq x_4 = minus_lambda_2.sqradd({ -x, -x_3 });
293
294 const Fq y_4 = minus_lambda_2.madd(x_4 - x, { -y });
295 return element(x_4, y_4);
296}
297
318template <typename C, class Fq, class Fr, class G>
321{
322 if (to_add.is_element) {
323 throw_or_abort("An accumulator expected");
324 }
325 x.assert_is_not_equal(to_add.x3_prev);
326
327 // lambda = (y2 - y1) / (x2 - x1)
328 // but we don't have y2!
329 // however, we do know that y2 = lambda_prev * (x1_prev - x2) - y1_prev
330 // => lambda * (x2 - x1) = lambda_prev * (x1_prev - x2) - y1_prev - y1
331 // => lambda * (x2 - x1) + lambda_prev * (x2 - x1_prev) + y1 + y1_pev = 0
332 // => lambda = lambda_prev * (x1_prev - x2) - y1_prev - y1 / (x2 - x1)
333 // => lambda = - (lambda_prev * (x2 - x1_prev) + y1_prev + y1) / (x2 - x1)
334
335 auto& x2 = to_add.x3_prev;
336 const auto lambda =
337 Fq::msub_div({ to_add.lambda_prev }, { (x2 - to_add.x1_prev) }, (x2 - x), { to_add.y1_prev, y });
338 const auto x3 = lambda.sqradd({ -x2, -x });
339
340 const Fq minus_lambda_2 = lambda + Fq::div_without_denominator_check({ y + y }, (x3 - x));
341
342 const Fq x4 = minus_lambda_2.sqradd({ -x, -x3 });
343
344 const Fq y4 = minus_lambda_2.madd(x4 - x, { -y });
345 return element(x4, y4);
346}
347
362template <typename C, class Fq, class Fr, class G>
365{
366 const Fq two_x = x + x;
367 Fq x_1;
368 Fq minus_lambda_dbl;
369 if constexpr (G::has_a) {
370 Fq a(get_context(), uint256_t(G::curve_a));
371 minus_lambda_dbl = Fq::msub_div({ x }, { (two_x + x) }, (y + y), { a });
372 x_1 = minus_lambda_dbl.sqradd({ -(two_x) });
373 } else {
374 minus_lambda_dbl = Fq::msub_div({ x }, { (two_x + x) }, (y + y), {});
375 x_1 = minus_lambda_dbl.sqradd({ -(two_x) });
376 }
377
378 ASSERT(to_add.size() > 0);
379 to_add[0].x.assert_is_not_equal(x_1);
380
381 const Fq x_minus_x_1 = x - x_1;
382
383 const Fq lambda_1 = Fq::msub_div({ minus_lambda_dbl }, { x_minus_x_1 }, (x_1 - to_add[0].x), { to_add[0].y, y });
384
385 const Fq x_3 = lambda_1.sqradd({ -to_add[0].x, -x_1 });
386
387 const Fq half_minus_lambda_2_minus_lambda_1 =
388 Fq::msub_div({ minus_lambda_dbl }, { x_minus_x_1 }, (x_3 - x_1), { y });
389
390 const Fq minus_lambda_2_minus_lambda_1 = half_minus_lambda_2_minus_lambda_1 + half_minus_lambda_2_minus_lambda_1;
391 const Fq minus_lambda_2 = minus_lambda_2_minus_lambda_1 + lambda_1;
392
393 const Fq x_4 = minus_lambda_2.sqradd({ -x_1, -x_3 });
394
395 const Fq x_4_sub_x_1 = x_4 - x_1;
396
397 if (to_add.size() == 1) {
398 const Fq y_4 = Fq::dual_madd(minus_lambda_2, x_4_sub_x_1, minus_lambda_dbl, x_minus_x_1, { y });
399 return element(x_4, y_4);
400 }
401 to_add[1].x.assert_is_not_equal(to_add[0].x);
402
403 Fq minus_lambda_3 = Fq::msub_div(
404 { minus_lambda_dbl, minus_lambda_2 }, { x_minus_x_1, x_4_sub_x_1 }, (x_4 - to_add[1].x), { y, -(to_add[1].y) });
405
406 // X5 = L3.L3 - X4 - XB
407 const Fq x_5 = minus_lambda_3.sqradd({ -x_4, -to_add[1].x });
408
409 if (to_add.size() == 2) {
410 // Y5 = L3.(XB - X5) - YB
411 const Fq y_5 = minus_lambda_3.madd(x_5 - to_add[1].x, { -to_add[1].y });
412 return element(x_5, y_5);
413 }
414
415 Fq x_prev = x_5;
416 Fq minus_lambda_prev = minus_lambda_3;
417
418 for (size_t i = 2; i < to_add.size(); ++i) {
419
420 to_add[i].x.assert_is_not_equal(to_add[i - 1].x);
421 // Lambda = Yprev - Yadd[i] / Xprev - Xadd[i]
422 // = -Lprev.(Xprev - Xadd[i-1]) - Yadd[i - 1] - Yadd[i] / Xprev - Xadd[i]
423 const Fq minus_lambda = Fq::msub_div({ minus_lambda_prev },
424 { to_add[i - 1].x - x_prev },
425 (to_add[i].x - x_prev),
426 { to_add[i - 1].y, to_add[i].y });
427 // X = Lambda * Lambda - Xprev - Xadd[i]
428 const Fq x_out = minus_lambda.sqradd({ -x_prev, -to_add[i].x });
429
430 x_prev = x_out;
431 minus_lambda_prev = minus_lambda;
432 }
433 const Fq y_out = minus_lambda_prev.madd(x_prev - to_add[to_add.size() - 1].x, { -to_add[to_add.size() - 1].y });
434
435 return element(x_prev, y_out);
436}
437
457template <typename C, class Fq, class Fr, class G>
459 const std::vector<chain_add_accumulator>& add) const
461{
462 struct composite_y {
463 std::vector<Fq> mul_left;
464 std::vector<Fq> mul_right;
465 std::vector<Fq> add;
466 bool is_negative = false;
467 };
468
469 Fq previous_x = x;
470 composite_y previous_y{ std::vector<Fq>(), std::vector<Fq>(), std::vector<Fq>(), false };
471 for (size_t i = 0; i < add.size(); ++i) {
472 previous_x.assert_is_not_equal(add[i].x3_prev);
473
474 // composite_y add_y;
475 bool negate_add_y = (i > 0) && !previous_y.is_negative;
476 std::vector<Fq> lambda1_left;
477 std::vector<Fq> lambda1_right;
478 std::vector<Fq> lambda1_add;
479
480 if (i == 0) {
481 lambda1_add.emplace_back(-y);
482 } else {
483 lambda1_left = previous_y.mul_left;
484 lambda1_right = previous_y.mul_right;
485 lambda1_add = previous_y.add;
486 }
487
488 if (!add[i].is_element) {
489 lambda1_left.emplace_back(add[i].lambda_prev);
490 lambda1_right.emplace_back(negate_add_y ? add[i].x3_prev - add[i].x1_prev
491 : add[i].x1_prev - add[i].x3_prev);
492 lambda1_add.emplace_back(negate_add_y ? add[i].y1_prev : -add[i].y1_prev);
493 } else if (i > 0) {
494 lambda1_add.emplace_back(negate_add_y ? -add[i].y3_prev : add[i].y3_prev);
495 }
496 // if previous_y is negated then add stays positive
497 // if previous_y is positive then add stays negated
498 // | add.y is negated | previous_y is negated | output of msub_div is -lambda |
499 // | --- | --- | --- |
500 // | no | yes | yes |
501 // | yes | no | no |
502
503 Fq lambda1;
504 if (!add[i].is_element || i > 0) {
505 bool flip_lambda1_denominator = !negate_add_y;
506 Fq denominator = flip_lambda1_denominator ? previous_x - add[i].x3_prev : add[i].x3_prev - previous_x;
507 lambda1 = Fq::msub_div(lambda1_left, lambda1_right, denominator, lambda1_add);
508 } else {
509 lambda1 = Fq::div_without_denominator_check({ add[i].y3_prev - y }, (add[i].x3_prev - x));
510 }
511
512 Fq x_3 = lambda1.madd(lambda1, { -add[i].x3_prev, -previous_x });
513
514 // We can avoid computing y_4, instead substituting the expression `minus_lambda_2 * (x_4 - x) - y` where
515 // needed. This is cheaper, because we can evaluate two field multiplications (or a field multiplication + a
516 // field division) with only one non-native field reduction. E.g. evaluating (a * b) + (c * d) = e mod p only
517 // requires 1 quotient and remainder, which is the major cost of a non-native field multiplication
518 Fq lambda2;
519 if (i == 0) {
520 lambda2 = Fq::div_without_denominator_check({ y + y }, (previous_x - x_3)) - lambda1;
521 } else {
522 Fq l2_denominator = previous_y.is_negative ? previous_x - x_3 : x_3 - previous_x;
523 Fq partial_lambda2 =
524 Fq::msub_div(previous_y.mul_left, previous_y.mul_right, l2_denominator, previous_y.add);
525 partial_lambda2 = partial_lambda2 + partial_lambda2;
526 lambda2 = partial_lambda2 - lambda1;
527 }
528
529 Fq x_4 = lambda2.sqradd({ -x_3, -previous_x });
530 composite_y y_4;
531 if (i == 0) {
532 // We want to make sure that at the final iteration, `y_previous.is_negative = false`
533 // Each iteration flips the sign of y_previous.is_negative.
534 // i.e. whether we store y_4 or -y_4 depends on the number of points we have
535 bool num_points_even = ((add.size() & 0x01UL) == 0);
536 y_4.add.emplace_back(num_points_even ? y : -y);
537 y_4.mul_left.emplace_back(lambda2);
538 y_4.mul_right.emplace_back(num_points_even ? x_4 - previous_x : previous_x - x_4);
539 y_4.is_negative = num_points_even;
540 } else {
541 y_4.is_negative = !previous_y.is_negative;
542 y_4.mul_left.emplace_back(lambda2);
543 y_4.mul_right.emplace_back(previous_y.is_negative ? previous_x - x_4 : x_4 - previous_x);
544 // append terms in previous_y to y_4. We want to make sure the terms above are added into the start of y_4.
545 // This is to ensure they are cached correctly when
546 // `builder::evaluate_partial_non_native_field_multiplication` is called.
547 // (the 1st mul_left, mul_right elements will trigger builder::evaluate_non_native_field_multiplication
548 // when Fq::mult_madd is called - this term cannot be cached so we want to make sure it is unique)
549 std::copy(previous_y.mul_left.begin(), previous_y.mul_left.end(), std::back_inserter(y_4.mul_left));
550 std::copy(previous_y.mul_right.begin(), previous_y.mul_right.end(), std::back_inserter(y_4.mul_right));
551 std::copy(previous_y.add.begin(), previous_y.add.end(), std::back_inserter(y_4.add));
552 }
553 previous_x = x_4;
554 previous_y = y_4;
555 }
556 Fq x_out = previous_x;
557
558 ASSERT(!previous_y.is_negative);
559
560 Fq y_out = Fq::mult_madd(previous_y.mul_left, previous_y.mul_right, previous_y.add);
561 return element(x_out, y_out);
562}
563
601template <typename C, class Fq, class Fr, class G>
603 const size_t num_rounds)
604{
605 constexpr typename G::affine_element offset_generator = G::derive_generators("biggroup offset generator", 1)[0];
606
607 const uint256_t offset_multiplier = uint256_t(1) << uint256_t(num_rounds - 1);
608
609 const typename G::affine_element offset_generator_end = typename G::element(offset_generator) * offset_multiplier;
610 return std::make_pair<element, element>(offset_generator, offset_generator_end);
611}
612
619template <typename C, class Fq, class Fr, class G>
621 const std::vector<Fr>& scalars,
622 const size_t max_num_bits)
623{
624 // Perform goblinized batched mul if available; supported only for BN254
625 if constexpr (IsGoblinBuilder<C> && std::same_as<G, barretenberg::g1>) {
626 return goblin_batch_mul(points, scalars);
627 } else {
628
629 const size_t num_points = points.size();
630 ASSERT(scalars.size() == num_points);
631 batch_lookup_table point_table(points);
632 const size_t num_rounds = (max_num_bits == 0) ? Fr::modulus.get_msb() + 1 : max_num_bits;
633
634 std::vector<std::vector<bool_t<C>>> naf_entries;
635 for (size_t i = 0; i < num_points; ++i) {
636 naf_entries.emplace_back(compute_naf(scalars[i], max_num_bits));
637 }
638 const auto offset_generators = compute_offset_generators(num_rounds);
639 element accumulator =
640 element::chain_add_end(element::chain_add(offset_generators.first, point_table.get_chain_initial_entry()));
641
642 constexpr size_t num_rounds_per_iteration = 4;
643 size_t num_iterations = num_rounds / num_rounds_per_iteration;
644 num_iterations += ((num_iterations * num_rounds_per_iteration) == num_rounds) ? 0 : 1;
645 const size_t num_rounds_per_final_iteration =
646 (num_rounds - 1) - ((num_iterations - 1) * num_rounds_per_iteration);
647 for (size_t i = 0; i < num_iterations; ++i) {
648
649 std::vector<bool_t<C>> nafs(num_points);
650 std::vector<element::chain_add_accumulator> to_add;
651 const size_t inner_num_rounds =
652 (i != num_iterations - 1) ? num_rounds_per_iteration : num_rounds_per_final_iteration;
653 for (size_t j = 0; j < inner_num_rounds; ++j) {
654 for (size_t k = 0; k < num_points; ++k) {
655 nafs[k] = (naf_entries[k][i * num_rounds_per_iteration + j + 1]);
656 }
657 to_add.emplace_back(point_table.get_chain_add_accumulator(nafs));
658 }
659 accumulator = accumulator.multiple_montgomery_ladder(to_add);
660 }
661 for (size_t i = 0; i < num_points; ++i) {
662 element skew = accumulator - points[i];
663 Fq out_x = accumulator.x.conditional_select(skew.x, naf_entries[i][num_rounds]);
664 Fq out_y = accumulator.y.conditional_select(skew.y, naf_entries[i][num_rounds]);
665 accumulator = element(out_x, out_y);
666 }
667 accumulator = accumulator - offset_generators.second;
668
669 return accumulator;
670 }
671}
672
678template <typename C, class Fq, class Fr, class G>
680{
705 if constexpr (IsGoblinBuilder<C> && std::same_as<G, barretenberg::g1>) {
706 std::vector<element> points{ *this };
707 std::vector<Fr> scalars{ scalar };
708 return goblin_batch_mul(points, scalars);
709 } else {
710 constexpr uint64_t num_rounds = Fr::modulus.get_msb() + 1;
711
712 std::vector<bool_t<C>> naf_entries = compute_naf(scalar);
713
714 const auto offset_generators = compute_offset_generators(num_rounds);
715
716 element accumulator = *this + offset_generators.first;
717
718 for (size_t i = 1; i < num_rounds; ++i) {
719 bool_t<C> predicate = naf_entries[i];
720 bigfield y_test = y.conditional_negate(predicate);
721 element to_add(x, y_test);
722 accumulator = accumulator.montgomery_ladder(to_add);
723 }
724
725 element skew_output = accumulator - (*this);
726
727 Fq out_x = accumulator.x.conditional_select(skew_output.x, naf_entries[num_rounds]);
728 Fq out_y = accumulator.y.conditional_select(skew_output.y, naf_entries[num_rounds]);
729
730 return element(out_x, out_y) - element(offset_generators.second);
731 }
732}
733} // namespace proof_system::plonk::stdlib
Definition: uint256.hpp:25
Definition: bigfield.hpp:17
Definition: biggroup.hpp:22
element multiple_montgomery_ladder(const std::vector< chain_add_accumulator > &to_add) const
Perform repeated iterations of the montgomery ladder algorithm.
Definition: biggroup_impl.hpp:458
element montgomery_ladder(const element &other) const
Definition: biggroup_impl.hpp:282
Definition: circuit_builders.hpp:15
Definition: biggroup.hpp:17
constexpr_utils defines some helper methods that perform some stl-equivalent operations but in a cons...
Definition: constexpr_utils.hpp:16