Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ipa.hpp
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#pragma once
22#include <cstddef>
23#include <numeric>
24#include <string>
25#include <utility>
26#include <vector>
27
28namespace bb {
29// clang-format off
30// Note that an update of this constant requires updating the inputs to noir protocol circuit (rollup-base-private, rollup-base-public,
31// rollup-block-merge, rollup-block-root, rollup-merge, rollup-root), as well as updating IPA_PROOF_LENGTH in other places.
32static constexpr size_t IPA_PROOF_LENGTH = /* comms IPA_L and IPA_R */ 4 * CONST_ECCVM_LOG_N +
33 /* comm G_0 */ 2 +
34 /* eval a_0 */ 2;
35
95template <typename Curve_, size_t log_poly_length = CONST_ECCVM_LOG_N> class IPA {
96 public:
97 using Curve = Curve_;
98 using Fr = typename Curve::ScalarField;
99 using GroupElement = typename Curve::Element;
104
105 // Compute the length of the vector of coefficients of a polynomial being opened.
106 static constexpr size_t poly_length = 1UL<<log_poly_length;
107
108// These allow access to internal functions so that we can never use a mock transcript unless it's fuzzing or testing of IPA specifically
109#ifdef IPA_TEST
110 FRIEND_TEST(IPATest, ChallengesAreZero);
111 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
112#endif
113#ifdef IPA_FUZZ_TEST
114 friend class ProxyCaller;
115#endif
152 template <typename Transcript>
153 static void compute_opening_proof_internal(const CK& ck,
154 const ProverOpeningClaim<Curve>& opening_claim,
155 const std::shared_ptr<Transcript>& transcript)
156 {
157 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
158
159 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1150): Hash more things here.
160 // TODO(https://github.com/AztecProtocol/barretenberg/issues/1408): Make IPA fuzzer compatible with `add_to_hash_buffer`.
161 //
162 // Step 1.
163 // Add the commitment, challenge, and evaluation to the hash buffer.
164 // NOTE:
165 // a. This is a bit inefficient, as the prover otherwise doesn't need this commitment.
166 // However, the effect to performance of this MSM (in practice of size 2^16) is tiny.
167 // b. Note that we add these three pieces of information to the hash buffer, as opposed to
168 // calling the `send_to_verifier` method, as the verifier knows them.
169
170 const auto commitment = ck.commit(polynomial);
171 transcript->add_to_hash_buffer("IPA:commitment", commitment);
172 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
173 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
174
175
176 // Step 2.
177 // Receive challenge for the auxiliary generator
178 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
179
180 if (generator_challenge.is_zero()) {
181 throw_or_abort("The generator challenge can't be zero");
182 }
183
184 // Step 3.
185 // Compute auxiliary generator U
186 auto aux_generator = Commitment::one() * generator_challenge;
187
188 // Checks poly_degree is greater than zero and a power of two
189 // In the future, we might want to consider if non-powers of two are needed
190 ASSERT((poly_length > 0) && (!(poly_length & (poly_length - 1))) &&
191 "The polynomial degree plus 1 should be positive and a power of two");
192
193 // Step 4.
194 // Set initial vector a to the polynomial monomial coefficients and load vector G
195 // Ensure the polynomial copy is fully-formed
196 auto a_vec = polynomial.full();
197 std::span<Commitment> srs_elements = ck.srs->get_monomial_points();
198 std::vector<Commitment> G_vec_local(poly_length);
199
200 if (poly_length > srs_elements.size()) {
201 throw_or_abort("potential bug: Not enough SRS points for IPA!");
202 }
203
204 // Copy the SRS into a local data structure as we need to mutate this vector for every round
207 [&](size_t i) {
208 G_vec_local[i] = srs_elements[i];
210
211 // Step 5.
212 // Compute vector b (vector of the powers of the challenge)
213 OpeningPair<Curve> opening_pair = opening_claim.opening_pair;
214 std::vector<Fr> b_vec(poly_length);
217 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
218 Fr b_power = opening_pair.challenge.pow(start);
219 for (size_t i = start; i < end; i++) {
220 b_vec[i] = b_power;
221 b_power *= opening_pair.challenge;
222 }
224
225 // Iterate for log(poly_degree) rounds to compute the round commitments.
226
227 // Allocate space for L_i and R_i elements
228 GroupElement L_i;
229 GroupElement R_i;
230 std::size_t round_size = poly_length;
231
232 // Step 6.
233 // Perform IPA reduction rounds
234 for (size_t i = 0; i < log_poly_length; i++) {
235 round_size /= 2;
236 // Run scalar products in parallel
237 auto inner_prods = parallel_for_heuristic(
238 round_size,
239 std::pair{Fr::zero(), Fr::zero()},
240 [&](size_t j, std::pair<Fr, Fr>& inner_prod_left_right) {
241 // Compute inner_prod_L := < a_vec_lo, b_vec_hi >
242 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
243 // Compute inner_prod_R := < a_vec_hi, b_vec_lo >
244 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
246 // Sum inner product contributions computed in parallel and unpack the std::pair
247 auto [inner_prod_L, inner_prod_R] = sum_pairs(inner_prods);
248 // Step 6.a (using letters, because doxygen automatically converts the sublist counters to letters :( )
249 // L_i = < a_vec_lo, G_vec_hi > + inner_prod_L * aux_generator
250
251 L_i = scalar_multiplication::pippenger_unsafe<Curve>({0, {&a_vec.at(0), /*size*/ round_size}},{&G_vec_local[round_size], round_size});
252
253 L_i += aux_generator * inner_prod_L;
254
255 // Step 6.b
256 // R_i = < a_vec_hi, G_vec_lo > + inner_prod_R * aux_generator
257 R_i = scalar_multiplication::pippenger_unsafe<Curve>({0, {&a_vec.at(round_size), /*size*/ round_size}},{&G_vec_local[0], /*size*/ round_size});
258 R_i += aux_generator * inner_prod_R;
259
260 // Step 6.c
261 // Send commitments to the verifier
262 std::string index = std::to_string(log_poly_length - i - 1);
263 transcript->send_to_verifier("IPA:L_" + index, Commitment(L_i));
264 transcript->send_to_verifier("IPA:R_" + index, Commitment(R_i));
265
266 // Step 6.d
267 // Receive the challenge from the verifier
268 const Fr round_challenge = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
269
270 if (round_challenge.is_zero()) {
271 throw_or_abort("IPA round challenge is zero");
272 }
273 const Fr round_challenge_inv = round_challenge.invert();
274
275 // Step 6.e
276 // G_vec_new = G_vec_lo + G_vec_hi * round_challenge_inv
277 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
278 std::span{ G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size),
279 G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size * 2) },
280 round_challenge_inv);
281 GroupElement::batch_affine_add(
282 std::span{ G_vec_local.begin(), G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size) },
283 G_hi_by_inverse_challenge,
284 G_vec_local);
285
286 // Steps 6.e and 6.f
287 // Update the vectors a_vec, b_vec.
288 // a_vec_new = a_vec_lo + a_vec_hi * round_challenge
289 // b_vec_new = b_vec_lo + b_vec_hi * round_challenge_inv
291 round_size,
292 [&](size_t j) {
293 a_vec.at(j) += round_challenge * a_vec[round_size + j];
294 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
296 }
297
298 // Step 7
299 // Send G_0 to the verifier
300 transcript->send_to_verifier("IPA:G_0", G_vec_local[0]);
301
302 // Step 8
303 // Send a_0 to the verifier
304 transcript->send_to_verifier("IPA:a_0", a_vec[0]);
305 }
306
332 static bool reduce_verify_internal_native(const VK& vk,
333 const OpeningClaim<Curve>& opening_claim,
334 auto& transcript)
335 requires(!Curve::is_stdlib_type)
336 {
337 // Step 1.
338 // Add the commitment, challenge, and evaluation to the hash buffer.
339 transcript->add_to_hash_buffer("IPA:commitment", opening_claim.commitment);
340 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
341 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
342
343 // Step 2.
344 // Receive generator challenge u and compute auxiliary generator
345 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
346
347 if (generator_challenge.is_zero()) {
348 throw_or_abort("The generator challenge can't be zero");
349 }
350
351 Commitment aux_generator = Commitment::one() * generator_challenge;
352
353 // Step 3.
354 // Compute C' = C + f(\beta) ⋅ U
355 GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
356
357 auto pippenger_size = 2 * log_poly_length;
358 std::vector<Fr> round_challenges(log_poly_length);
359 // the group elements that will participate in our MSM.
360 std::vector<Commitment> msm_elements(pippenger_size);
361 // the scalars that will participate in our MSM.
362 std::vector<Fr> msm_scalars(pippenger_size);
363
364 // Step 4.
365 // Receive all L_i and R_i and populate msm_elements.
366 for (size_t i = 0; i < log_poly_length; i++) {
367 std::string index = std::to_string(log_poly_length - i - 1);
368 auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
369 auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
370 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
371 if (round_challenges[i].is_zero()) {
372 throw_or_abort("Round challenges can't be zero");
373 }
374 msm_elements[2 * i] = element_L;
375 msm_elements[2 * i + 1] = element_R;
376 }
377
378 std::vector<Fr> round_challenges_inv = round_challenges;
379 Fr::batch_invert(round_challenges_inv);
380
381 // populate msm_scalars.
382 for (size_t i = 0; i < log_poly_length; i++) {
383 msm_scalars[2 * i] = round_challenges_inv[i];
384 msm_scalars[2 * i + 1] = round_challenges[i];
385 }
386
387 // Step 5.
388 // Compute C₀ = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j
389 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>({0, {&msm_scalars[0], /*size*/ pippenger_size}},{&msm_elements[0], /*size*/ pippenger_size});
390 GroupElement C_zero = C_prime + LR_sums;
391
392 // Step 6.
393 // Compute b_zero where b_zero can be computed using the polynomial:
394 // g(X) = ∏_{i ∈ [k]} (1 + u_{i-1}^{-1}.X^{2^{i-1}}).
395 // b_zero = g(evaluation) = ∏_{i ∈ [k]} (1 + u_{i-1}^{-1}. (evaluation)^{2^{i-1}})
396 Fr b_zero = Fr::one();
397 for (size_t i = 0; i < log_poly_length; i++) {
398 b_zero *= Fr::one() + (round_challenges_inv[log_poly_length - 1 - i] *
399 opening_claim.opening_pair.challenge.pow(1 << i));
400 }
401
402 // Step 7.
403 // Construct vector s
404 Polynomial<Fr> s_poly(construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
405
407 if (poly_length > srs_elements.size()) {
408 throw_or_abort("potential bug: Not enough SRS points for IPA!");
409 }
410
411 // Step 8.
412 // Compute G₀
413 Commitment G_zero = scalar_multiplication::pippenger_unsafe<Curve>(s_poly,{&srs_elements[0], /*size*/ poly_length});
414 Commitment G_zero_sent = transcript->template receive_from_prover<Commitment>("IPA:G_0");
415 BB_ASSERT_EQ(G_zero, G_zero_sent, "G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
416
417 // Step 9.
418 // Receive a₀ from the prover
419 auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
420
421 // Step 10.
422 // Compute C_right
423 GroupElement right_hand_side = G_zero * a_zero + aux_generator * a_zero * b_zero;
424 // Step 11.
425 // Check if C_right == C₀
426 return (C_zero.normalize() == right_hand_side.normalize());
427 }
441 static VerifierAccumulator reduce_verify_internal_recursive(const OpeningClaim<Curve>& opening_claim,
442 auto& transcript)
443 requires Curve::is_stdlib_type
444 {
445 // Step 1.
446 // Add the commitment, challenge, and evaluation to the hash buffer.
447 transcript->add_to_hash_buffer("IPA:commitment", opening_claim.commitment);
448 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
449 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
450
451 // Step 2.
452 // Receive generator challenge u and compute auxiliary generator
453 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
454 typename Curve::Builder* builder = generator_challenge.get_context();
455
456 auto pippenger_size = 2 * log_poly_length;
457 std::vector<Fr> round_challenges(log_poly_length);
458 std::vector<Fr> round_challenges_inv(log_poly_length);
459 std::vector<Commitment> msm_elements(pippenger_size);
460 std::vector<Fr> msm_scalars(pippenger_size);
461
462
463 // Step 3.
464 // Receive all L_i and R_i and prepare for MSM
465 for (size_t i = 0; i < log_poly_length; i++) {
466
467 std::string index = std::to_string(log_poly_length - i - 1);
468 auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
469 auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
470 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
471 round_challenges_inv[i] = round_challenges[i].invert();
472
473 msm_elements[2 * i] = element_L;
474 msm_elements[2 * i + 1] = element_R;
475 msm_scalars[2 * i] = round_challenges_inv[i];
476 msm_scalars[2 * i + 1] = round_challenges[i];
477 }
478
479 // Step 4.
480 // Compute b_zero where b_zero can be computed using the polynomial:
481 // g(X) = ∏_{i ∈ [k]} (1 + u_{i-1}^{-1}.X^{2^{i-1}}).
482 // b_zero = g(evaluation) = ∏_{i ∈ [k]} (1 + u_{i-1}^{-1}. (evaluation)^{2^{i-1}})
483
484 Fr b_zero = Fr(1);
485 Fr challenge = opening_claim.opening_pair.challenge;
486 for (size_t i = 0; i < log_poly_length; i++) {
487
488 Fr monomial = round_challenges_inv[log_poly_length - 1 - i] * challenge;
489 b_zero *= Fr(1) + monomial;
490 if (i != log_poly_length - 1) // this if statement is fine because the number of iterations is constant
491 {
492 challenge = challenge.sqr();
493 }
494 }
495
496 // Step 5.
497 // Receive G₀ from the prover
498 Commitment G_zero = transcript->template receive_from_prover<Commitment>("IPA:G_0");
499
500 // Step 6.
501 // Receive a₀ from the prover
502 const auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
503
504 // Step 7.
505 // Compute R = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j - G₀ * a₀ - (f(\beta) + a₀ * b₀) ⋅ U
506 // This is a combination of several IPA relations into a large batch mul
507 // which should be equal to -C
508 msm_elements.emplace_back(-G_zero);
509 msm_elements.emplace_back(-Commitment::one(builder));
510 msm_scalars.emplace_back(a_zero);
511 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, {-opening_claim.opening_pair.evaluation}));
512 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
513 auto neg_commitment = -opening_claim.commitment;
514 ipa_relation.assert_equal(neg_commitment);
515
516
517 return { round_challenges_inv, G_zero};
518 }
519
531 static void compute_opening_proof(const CK& ck,
532 const ProverOpeningClaim<Curve>& opening_claim,
533 const std::shared_ptr<NativeTranscript>& transcript)
534 {
535 compute_opening_proof_internal(ck, opening_claim, transcript);
536 }
537
549 static bool reduce_verify(const VK& vk,
550 const OpeningClaim<Curve>& opening_claim,
551 const auto& transcript)
552 requires(!Curve::is_stdlib_type)
553 {
554 return reduce_verify_internal_native(vk, opening_claim, transcript);
555 }
556
568 static VerifierAccumulator reduce_verify(const OpeningClaim<Curve>& opening_claim,
569 const auto& transcript)
570 requires(Curve::is_stdlib_type)
571 {
572 return reduce_verify_internal_recursive(opening_claim, transcript);
573 }
574
588 static bool full_verify_recursive(const VK& vk,
589 const OpeningClaim<Curve>& opening_claim,
590 auto& transcript)
591 requires Curve::is_stdlib_type
592 {
593 // Step 1.
594 // Add the commitment, challenge, and evaluation to the hash buffer.
595 transcript->add_to_hash_buffer("IPA:commitment", opening_claim.commitment);
596 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
597 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
598
599 // Step 2.
600 // Receive generator challenge u and compute auxiliary generator
601 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
602 typename Curve::Builder* builder = generator_challenge.get_context();
603
604 static constexpr size_t pippenger_size = 2 * log_poly_length;
605 std::vector<Fr> round_challenges(log_poly_length);
606 std::vector<Fr> round_challenges_inv(log_poly_length);
607 std::vector<Commitment> msm_elements(pippenger_size);
608 std::vector<Fr> msm_scalars(pippenger_size);
609
610
611 // Step 3.
612 // Receive all L_i and R_i and prepare for MSM
613 for (size_t i = 0; i < log_poly_length; i++) {
614
615 std::string index = std::to_string(log_poly_length - i - 1);
616 auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
617 auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
618 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
619 round_challenges_inv[i] = round_challenges[i].invert();
620
621 msm_elements[2 * i] = element_L;
622 msm_elements[2 * i + 1] = element_R;
623 msm_scalars[2 * i] = round_challenges_inv[i];
624 msm_scalars[2 * i + 1] = round_challenges[i];
625 }
626
627 // Step 4.
628 // Compute b_zero where b_zero can be computed using the polynomial:
629 // g(X) = ∏_{i ∈ [k]} (1 + u_{i-1}^{-1}.X^{2^{i-1}}).
630 // b_zero = g(evaluation) = ∏_{i ∈ [k]} (1 + u_{i-1}^{-1}. (evaluation)^{2^{i-1}})
631
632 Fr b_zero = Fr(1);
633 Fr challenge = opening_claim.opening_pair.challenge;
634 for (size_t i = 0; i < log_poly_length; i++) {
635
636 Fr monomial = round_challenges_inv[log_poly_length - 1 - i] * challenge;
637 b_zero *= Fr(1) + monomial;
638 if (i != log_poly_length - 1) // this if statement is fine because the number of iterations is constant
639 {
640 challenge = challenge.sqr();
641 }
642 }
643
644 // Step 5.
645 // Construct vector s
646 // We implement a linear-time algorithm to optimally compute this vector
647 // Note: currently requires an extra vector of size `poly_length / 2` to cache temporaries
648 // this might able to be optimized if we care enough, but the size of this poly shouldn't be large relative to the builder polynomial sizes
649 std::vector<Fr> s_vec_temporaries(poly_length / 2);
650 std::vector<Fr> s_vec(poly_length);
651
652 Fr* previous_round_s = &s_vec_temporaries[0];
653 Fr* current_round_s = &s_vec[0];
654 // if number of rounds is even we need to swap these so that s_vec always contains the result
655 if constexpr ((log_poly_length & 1) == 0)
656 {
657 std::swap(previous_round_s, current_round_s);
658 }
659 previous_round_s[0] = Fr(1);
660 for (size_t i = 0; i < log_poly_length; ++i)
661 {
662 const size_t round_size = 1 << (i + 1);
663 const Fr round_challenge = round_challenges_inv[i];
664 for (size_t j = 0; j < round_size / 2; ++j)
665 {
666 current_round_s[j * 2] = previous_round_s[j];
667 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
668 }
669 std::swap(current_round_s, previous_round_s);
670 }
671 // Receive G₀ from the prover
672 Commitment transcript_G_zero = transcript->template receive_from_prover<Commitment>("IPA:G_0");
673 // Compute G₀
674 // Unlike the native verification function, the verifier commitment key only containts the SRS so we can apply
675 // batch_mul directly on it.
676 const std::vector<Commitment> srs_elements = vk.get_monomial_points();
677 Commitment G_zero = Commitment::batch_mul(srs_elements, s_vec);
678 transcript_G_zero.assert_equal(G_zero);
679 BB_ASSERT_EQ(G_zero.get_value(), transcript_G_zero.get_value(), "G_zero doesn't match received G_zero.");
680
681 // Step 6.
682 // Receive a₀ from the prover
683 const auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
684
685 // Step 7.
686 // Compute R = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j - G₀ * a₀ - (f(\beta) + a₀ * b₀) ⋅ U
687 // This is a combination of several IPA relations into a large batch mul
688 // which should be equal to -C
689 msm_elements.emplace_back(-G_zero);
690 msm_elements.emplace_back(-Commitment::one(builder));
691 msm_scalars.emplace_back(a_zero);
692 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, {-opening_claim.opening_pair.evaluation}));
693 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
694 auto neg_commitment = -opening_claim.commitment;
695 ipa_relation.assert_equal(neg_commitment);
696
697 return (ipa_relation.get_value() == -opening_claim.commitment.get_value());
698 }
699
709 static OpeningClaim<Curve> reduce_batch_opening_claim(
710 const BatchOpeningClaim<Curve>& batch_opening_claim)
711 {
712 // Extract batch_mul arguments from the accumulator
713 const auto& commitments = batch_opening_claim.commitments;
714 const auto& scalars = batch_opening_claim.scalars;
715 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
716 // Compute \f$ C = \sum \text{commitments}_i \cdot \text{scalars}_i \f$
717 GroupElement shplonk_output_commitment;
718 if constexpr (Curve::is_stdlib_type) {
719 shplonk_output_commitment =
720 GroupElement::batch_mul(commitments, scalars);
721 } else {
722 shplonk_output_commitment = batch_mul_native(commitments, scalars);
723 }
724 // Output an opening claim to be verified by the IPA opening protocol
725 return { { shplonk_eval_challenge, Fr(0) }, shplonk_output_commitment };
726 }
727
736 static bool reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
737 const VK& vk,
738 auto& transcript)
739 requires(!Curve::is_stdlib_type)
740 {
741 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
742 return reduce_verify_internal_native(vk, opening_claim, transcript);
743 }
744
753 static VerifierAccumulator reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
754 [[maybe_unused]] const std::shared_ptr<VK>& vk,
755 auto& transcript)
756 requires(Curve::is_stdlib_type)
757 {
758 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
759 return reduce_verify_internal_recursive(opening_claim, transcript);
760 }
761
770 static Fr evaluate_challenge_poly(const std::vector<Fr>& u_challenges_inv, Fr r) {
771
772 Fr challenge_poly_eval = 1;
773 Fr r_pow = r;
774
775 for (size_t i = 0; i < log_poly_length; i++) {
776
777 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
778
779 challenge_poly_eval *= (Fr(1) + monomial);
780 r_pow = r_pow.sqr();
781 }
782 return challenge_poly_eval;
783 }
784
794 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1, std::vector<Fr> u_challenges_inv_2, Fr r, Fr alpha) {
795 auto result = evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
796 return result;
797 }
798
805 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(const std::span<const bb::fq>& u_challenges_inv) {
806
807 // Construct vector s in linear time.
809
810 std::vector<bb::fq> s_vec_temporaries(poly_length / 2);
811
812 bb::fq* previous_round_s = &s_vec_temporaries[0];
813 bb::fq* current_round_s = &s_vec[0];
814 // if number of rounds is even we need to swap these so that s_vec always contains the result
815 if ((log_poly_length & 1) == 0)
816 {
817 std::swap(previous_round_s, current_round_s);
818 }
819 previous_round_s[0] = bb::fq(1);
820 for (size_t i = 0; i < log_poly_length; ++i)
821 {
822 const size_t round_size = 1 << (i + 1);
823 const fq round_challenge = u_challenges_inv[i];
825 round_size / 2,
826 [&](size_t j) {
827 current_round_s[j * 2] = previous_round_s[j];
828 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
830 std::swap(current_round_s, previous_round_s);
831 }
832 return {s_vec, poly_length};
833 }
834
843 static Polynomial<bb::fq> create_challenge_poly(const std::vector<bb::fq>& u_challenges_inv_1, const std::vector<bb::fq>& u_challenges_inv_2, bb::fq alpha) {
844 // Always extend each to 1<<log_poly_length length
845 Polynomial<bb::fq> challenge_poly(1<<log_poly_length);
846 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
847 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
848 challenge_poly += challenge_poly_1;
849 challenge_poly.add_scaled(challenge_poly_2, alpha);
850 return challenge_poly;
851 }
852
865 static std::pair<OpeningClaim<Curve>, HonkProof> accumulate(const CommitmentKey<curve::Grumpkin>& ck, auto& transcript_1, OpeningClaim<Curve> claim_1, auto& transcript_2, OpeningClaim<Curve> claim_2)
866 requires Curve::is_stdlib_type
867 {
868 using NativeCurve = curve::Grumpkin;
869 using Builder = typename Curve::Builder;
870 // Step 1: Run the verifier for each IPA instance
871 VerifierAccumulator pair_1 = reduce_verify(claim_1, transcript_1);
872 VerifierAccumulator pair_2 = reduce_verify(claim_2, transcript_2);
873
874 // Step 2: Generate the challenges by hashing the pairs
875 using StdlibTranscript = BaseTranscript<stdlib::recursion::honk::StdlibTranscriptParams<Builder>>;
876 StdlibTranscript transcript;
877 transcript.send_to_verifier("u_challenges_inv_1", pair_1.u_challenges_inv);
878 transcript.send_to_verifier("U_1", pair_1.comm);
879 transcript.send_to_verifier("u_challenges_inv_2", pair_2.u_challenges_inv);
880 transcript.send_to_verifier("U_2", pair_2.comm);
881 auto [alpha, r] = transcript.template get_challenges<Fr>("IPA:alpha", "IPA:r");
882
883 // Step 3: Compute the new accumulator
884 OpeningClaim<Curve> output_claim;
885 output_claim.commitment = pair_1.comm + pair_2.comm * alpha;
886 output_claim.opening_pair.challenge = r;
887 // Evaluate the challenge_poly polys at r and linearly combine them with alpha challenge
888 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(pair_1.u_challenges_inv, pair_2.u_challenges_inv, r, alpha);
889
890 // Step 4: Compute the new polynomial
891 std::vector<bb::fq> native_u_challenges_inv_1;
892 std::vector<bb::fq> native_u_challenges_inv_2;
893 for (Fr u_inv_i : pair_1.u_challenges_inv) {
894 native_u_challenges_inv_1.push_back(bb::fq(u_inv_i.get_value()));
895 }
896 for (Fr u_inv_i : pair_2.u_challenges_inv) {
897 native_u_challenges_inv_2.push_back(bb::fq(u_inv_i.get_value()));
898 }
899
900 // Compute proof for the claim
901 auto prover_transcript = std::make_shared<NativeTranscript>();
902 const OpeningPair<NativeCurve> opening_pair{ bb::fq(output_claim.opening_pair.challenge.get_value()),
903 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
904 Polynomial<fq> challenge_poly = create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2, fq(alpha.get_value()));
905
906 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge), opening_pair.evaluation, "Opening claim does not hold for challenge polynomial.");
907
908 IPA<NativeCurve, log_poly_length>::compute_opening_proof(ck, { challenge_poly, opening_pair }, prover_transcript);
909 BB_ASSERT_EQ(challenge_poly.evaluate(fq(output_claim.opening_pair.challenge.get_value())), fq(output_claim.opening_pair.evaluation.get_value()), "Opening claim does not hold for challenge polynomial.");
910
911 output_claim.opening_pair.evaluation.self_reduce();
912 return {output_claim, prover_transcript->export_proof()};
913 }
914
915 static std::pair<OpeningClaim<Curve>, HonkProof> create_fake_ipa_claim_and_proof(UltraCircuitBuilder& builder)
916 requires Curve::is_stdlib_type {
917 using NativeCurve = curve::Grumpkin;
918 using Builder = typename Curve::Builder;
919 using Curve = stdlib::grumpkin<Builder>;
920 auto ipa_transcript = std::make_shared<NativeTranscript>();
921 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
922 size_t n = poly_length;
923 auto poly = Polynomial<fq>(n);
924 for (size_t i = 0; i < n; i++) {
925 poly.at(i) = fq::random_element();
926 }
928 fq eval = poly.evaluate(x);
929 auto commitment = ipa_commitment_key.commit(poly);
930 const OpeningPair<NativeCurve> opening_pair = { x, eval };
931 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
932
933 auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
934 auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
935 auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
936 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
937
938 return {stdlib_opening_claim, ipa_transcript->export_proof()};
939 }
940};
941
942} // namespace bb
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:59
#define ASSERT(expression,...)
Definition assert.hpp:49
CommitmentKey object over a pairing group 𝔾₁.
IPA (inner product argument) commitment scheme class.
Definition ipa.hpp:95
typename Curve::Element GroupElement
Definition ipa.hpp:99
CommitmentKey< Curve > CK
Definition ipa.hpp:101
static constexpr size_t poly_length
Definition ipa.hpp:106
stdlib::recursion::honk::IpaAccumulator< Curve > VerifierAccumulator
Definition ipa.hpp:103
VerifierCommitmentKey< Curve > VK
Definition ipa.hpp:102
typename Curve::ScalarField Fr
Definition ipa.hpp:98
typename Curve::AffineElement Commitment
Definition ipa.hpp:100
Curve_ Curve
Definition ipa.hpp:97
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
Polynomial full() const
Copys the polynomial, but with the whole address space usable. The value of the polynomial remains th...
Polynomial p and an opening pair (r,v) such that p(r) = v.
Definition claim.hpp:34
Polynomial polynomial
Definition claim.hpp:39
OpeningPair< Curve > opening_pair
Definition claim.hpp:40
Class that allows us to call internal IPA methods, because it's friendly.
std::vector< Commitment > get_monomial_points() const
typename Group::element Element
Definition grumpkin.hpp:55
static constexpr bool is_stdlib_type
Definition grumpkin.hpp:62
typename Group::affine_element AffineElement
Definition grumpkin.hpp:56
typename Flavor::Polynomial Polynomial
#define BB_UNUSED
AluTraceBuilder builder
Definition alu.test.cpp:123
BaseTranscript< StdlibTranscriptParams< Builder > > StdlibTranscript
constexpr size_t FF_COPY_COST
Definition thread.hpp:146
constexpr size_t FF_ADDITION_COST
Definition thread.hpp:134
constexpr size_t FF_MULTIPLICATION_COST
Definition thread.hpp:136
Entry point for Barretenberg command-line interface.
std::vector< fr > HonkProof
Definition proof.hpp:15
std::pair< Left, Right > sum_pairs(Cont< std::pair< Left, Right >, Args... > const &in)
Definition container.hpp:81
field< Bn254FqParams > fq
Definition fq.hpp:169
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
void parallel_for_heuristic(size_t num_points, const std::function< void(size_t, size_t, size_t)> &func, size_t heuristic_cost)
Split a loop into several loops running in parallel based on operations in 1 iteration.
Definition thread.cpp:132
CommitmentKey< Curve > ck
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
static constexpr field one()
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field sqr() const noexcept
BB_INLINE constexpr bool is_zero() const noexcept
static void batch_invert(std::span< field > coeffs) noexcept
static constexpr field zero()
void throw_or_abort(std::string const &err)