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
21#include <cstddef>
22#include <numeric>
23#include <string>
24#include <utility>
25#include <vector>
26
27namespace bb {
28// Note that an update of this constant requires updating the inputs to noir protocol circuit (rollup-base-private,
29// rollup-base-public, rollup-block-merge, rollup-block-root, rollup-merge, rollup-root), as well as updating
30// IPA_PROOF_LENGTH in other places.
31static constexpr size_t IPA_PROOF_LENGTH = /* comms IPA_L and IPA_R */ 4 * CONST_ECCVM_LOG_N +
32 /* comm G_0 */ 2 +
33 /* eval a_0 */ 2;
34
93template <typename Curve_, size_t log_poly_length = CONST_ECCVM_LOG_N> class IPA {
94 public:
95 using Curve = Curve_;
96 using Fr = typename Curve::ScalarField;
97 using GroupElement = typename Curve::Element;
101
102 // records the `u_challenges_inv`, the Pederson commitment to the `h` -polynomial, a.k.a. the challenge
103 // polynomial, given as ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.X^{2^{i-1}}), and the running truth value of the IPA
104 // accumulation claim.
106
107 // Compute the length of the vector of coefficients of a polynomial being opened.
108 static constexpr size_t poly_length = 1UL << log_poly_length;
109
110// These allow access to internal functions so that we can never use a mock transcript unless it's fuzzing or testing of
111// IPA specifically
112#ifdef IPA_TEST
113 FRIEND_TEST(IPATest, ChallengesAreZero);
114 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
115#endif
116#ifdef IPA_FUZZ_TEST
117 friend class ProxyCaller;
118#endif
119
156 template <typename Transcript>
157 static void compute_opening_proof_internal(const CK& ck,
158 const ProverOpeningClaim<Curve>& opening_claim,
159 const std::shared_ptr<Transcript>& transcript)
160 {
161 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
162
163 // Step 1.
164 // Done in `add_claim_to_hash_buffer`.
165
166 // Step 2.
167 // Receive challenge for the auxiliary generator
168 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
169
170 if (generator_challenge.is_zero()) {
171 throw_or_abort("The generator challenge can't be zero");
172 }
173
174 // Step 3.
175 // Compute auxiliary generator U, which is used to bind together the inner product claim and the commitment.
176 // This yields the binding property because we assume it is computationally difficult to find a linear relation
177 // between the CRS and `Commitment::one()`.
178 // Compute auxiliary generator U, which is used to bind together the inner product claim and the commitment.
179 // This yields the binding property because we assume it is computationally difficult to find a linear relation
180 // between the CRS and `Commitment::one()`.
181 auto aux_generator = Commitment::one() * generator_challenge;
182
183 // Checks poly_degree is greater than zero and a power of two
184 // In the future, we might want to consider if non-powers of two are needed
185 ASSERT((poly_length > 0) && (!(poly_length & (poly_length - 1))) &&
186 "The polynomial degree plus 1 should be positive and a power of two");
187
188 // Step 4.
189 // Set initial vector a to the polynomial monomial coefficients and load vector G
190 // Ensure the polynomial copy is fully-formed
191 auto a_vec = polynomial.full();
192 std::span<Commitment> srs_elements = ck.srs->get_monomial_points();
193 std::vector<Commitment> G_vec_local(poly_length);
194
195 if (poly_length > srs_elements.size()) {
196 throw_or_abort("potential bug: Not enough SRS points for IPA!");
197 }
198
199 // Copy the SRS into a local data structure as we need to mutate this vector for every round
201 poly_length, [&](size_t i) { G_vec_local[i] = srs_elements[i]; }, thread_heuristics::FF_COPY_COST);
202
203 // Step 5.
204 // Compute vector b (vector of the powers of the challenge)
205 OpeningPair<Curve> opening_pair = opening_claim.opening_pair;
206 std::vector<Fr> b_vec(poly_length);
209 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
210 Fr b_power = opening_pair.challenge.pow(start);
211 for (size_t i = start; i < end; i++) {
212 b_vec[i] = b_power;
213 b_power *= opening_pair.challenge;
214 }
215 },
217
218 // Iterate for log(poly_degree) rounds to compute the round commitments.
219
220 // Allocate space for L_i and R_i elements
221 GroupElement L_i;
222 GroupElement R_i;
223 std::size_t round_size = poly_length;
224
225 // Step 6.
226 // Perform IPA reduction rounds
227 for (size_t i = 0; i < log_poly_length; i++) {
228 round_size /= 2;
229 // Run scalar products in parallel
230 auto inner_prods = parallel_for_heuristic(
231 round_size,
232 std::pair{ Fr::zero(), Fr::zero() },
233 [&](size_t j, std::pair<Fr, Fr>& inner_prod_left_right) {
234 // Compute inner_prod_L := < a_vec_lo, b_vec_hi >
235 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
236 // Compute inner_prod_R := < a_vec_hi, b_vec_lo >
237 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
238 },
240 // Sum inner product contributions computed in parallel and unpack the std::pair
241 auto [inner_prod_L, inner_prod_R] = sum_pairs(inner_prods);
242 // Step 6.a (using letters, because doxygen automatically converts the sublist counters to letters :( )
243 // L_i = < a_vec_lo, G_vec_hi > + inner_prod_L * aux_generator
244
245 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), /*size*/ round_size } },
246 { &G_vec_local[round_size], round_size });
247
248 L_i += aux_generator * inner_prod_L;
249
250 // Step 6.b
251 // R_i = < a_vec_hi, G_vec_lo > + inner_prod_R * aux_generator
252 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(round_size), /*size*/ round_size } },
253 { &G_vec_local[0], /*size*/ round_size });
254 R_i += aux_generator * inner_prod_R;
255
256 // Step 6.c
257 // Send L_i and R_i to the verifier
258 std::string index = std::to_string(log_poly_length - i - 1);
259 transcript->send_to_verifier("IPA:L_" + index, Commitment(L_i));
260 transcript->send_to_verifier("IPA:R_" + index, Commitment(R_i));
261
262 // Step 6.d
263 // Receive the challenge from the verifier
264 const Fr round_challenge = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
265
266 if (round_challenge.is_zero()) {
267 throw_or_abort("IPA round challenge is zero");
268 }
269 const Fr round_challenge_inv = round_challenge.invert();
270
271 // Step 6.e
272 // G_vec_new = G_vec_lo + G_vec_hi * round_challenge_inv
273 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
274 std::span{ G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size),
275 G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size * 2) },
276 round_challenge_inv);
277 GroupElement::batch_affine_add(
278 std::span{ G_vec_local.begin(), G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size) },
279 G_hi_by_inverse_challenge,
280 G_vec_local);
281
282 // Steps 6.e and 6.f
283 // Update the vectors a_vec, b_vec.
284 // a_vec_new = a_vec_lo + a_vec_hi * round_challenge
285 // b_vec_new = b_vec_lo + b_vec_hi * round_challenge_inv
287 round_size,
288 [&](size_t j) {
289 a_vec.at(j) += round_challenge * a_vec[round_size + j];
290 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
291 },
293 }
294
295 // Step 7
296 // Send G_0 to the verifier
297 transcript->send_to_verifier("IPA:G_0", G_vec_local[0]);
298
299 // Step 8
300 // Send a_0 to the verifier
301 transcript->send_to_verifier("IPA:a_0", a_vec[0]);
302 }
314 template <typename Transcript>
315 static void add_claim_to_hash_buffer(const CK& ck,
316 const ProverOpeningClaim<Curve>& opening_claim,
317 const std::shared_ptr<Transcript>& transcript)
318 {
319 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
320
321 // Step 1.
322 // Add the commitment, challenge, and evaluation to the hash buffer.
323 // NOTE:
324 // a. This is a bit inefficient, as the prover otherwise doesn't need this commitment.
325 // However, the effect to performance of this MSM (in practice of size 2^16) is tiny.
326 // b. Note that we add these three pieces of information to the hash buffer, as opposed to
327 // calling the `send_to_verifier` method, as the verifier knows them.
328
329 const auto commitment = ck.commit(polynomial);
330 transcript->add_to_hash_buffer("IPA:commitment", commitment);
331 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
332 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
333 }
334
360 static bool reduce_verify_internal_native(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
361 requires(!Curve::is_stdlib_type)
362 {
363 // Step 1
364 // Done by `add_claim_to_hash_buffer`.
365
366 // Step 2.
367 // Receive generator challenge u and compute auxiliary generator
368 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
369
370 if (generator_challenge.is_zero()) {
371 throw_or_abort("The generator challenge can't be zero");
372 }
373
374 const Commitment aux_generator = Commitment::one() * generator_challenge;
375
376 // Step 3.
377 // Compute C' = C + f(\beta) ⋅ U, i.e., the _joint_ commitment of f and f(\beta).
378 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
379
380 const auto pippenger_size = 2 * log_poly_length;
381 std::vector<Fr> round_challenges(log_poly_length);
382 // the group elements that will participate in our MSM.
383 std::vector<Commitment> msm_elements(pippenger_size); // L_{k-1}, R_{k-1}, L_{k-2}, ..., L_0, R_0.
384 // the scalars that will participate in our MSM.
385 std::vector<Fr> msm_scalars(pippenger_size); // w_{k-1}^{-1}, w_{k-1}, ..., w_{0}^{-1}, w_{0}.
386
387 // Step 4.
388 // Receive all L_i and R_i and populate msm_elements.
389 for (size_t i = 0; i < log_poly_length; i++) {
390 std::string index = std::to_string(log_poly_length - i - 1);
391 const auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
392 const auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
393 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
394 if (round_challenges[i].is_zero()) {
395 throw_or_abort("Round challenges can't be zero");
396 }
397 msm_elements[2 * i] = element_L;
398 msm_elements[2 * i + 1] = element_R;
399 }
400
401 std::vector<Fr> round_challenges_inv = round_challenges;
402 Fr::batch_invert(round_challenges_inv);
403
404 // populate msm_scalars.
405 for (size_t i = 0; i < log_poly_length; i++) {
406 msm_scalars[2 * i] = round_challenges_inv[i];
407 msm_scalars[2 * i + 1] = round_challenges[i];
408 }
409
410 // Step 5.
411 // Compute C_zero = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j
412 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>(
413 { 0, { &msm_scalars[0], /*size*/ pippenger_size } }, { &msm_elements[0], /*size*/ pippenger_size });
414 GroupElement C_zero = C_prime + LR_sums;
415
416 // Step 6.
417 // Compute b_zero succinctly
418 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
419
420 // Step 7.
421 // Construct vector s
422 Polynomial<Fr> s_vec(
423 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
424
426 if (poly_length > srs_elements.size()) {
427 throw_or_abort("potential bug: Not enough SRS points for IPA!");
428 }
429
430 // Step 8.
431 // Compute G_zero
432 Commitment G_zero =
433 scalar_multiplication::pippenger_unsafe<Curve>(s_vec, { &srs_elements[0], /*size*/ poly_length });
434 Commitment G_zero_sent = transcript->template receive_from_prover<Commitment>("IPA:G_0");
435 BB_ASSERT_EQ(G_zero, G_zero_sent, "G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
436
437 // Step 9.
438 // Receive a_zero from the prover
439 auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
440
441 // Step 10.
442 // Compute C_right. Implicitly, this is an IPA statement for the length 1 vectors <a_0> and <b_0> together with
443 // the URS G_0.
444 GroupElement right_hand_side = G_zero * a_zero + aux_generator * a_zero * b_zero;
445 // Step 11.
446 // Check if C_right == C_zero
447 return (C_zero.normalize() == right_hand_side.normalize());
448 }
449
460 template <typename Transcript>
461 static void add_claim_to_hash_buffer(const OpeningClaim<Curve>& opening_claim,
462 const std::shared_ptr<Transcript>& transcript)
463 {
464
465 // Step 1.
466 // Add the commitment, challenge, and evaluation to the hash buffer.
467
468 transcript->add_to_hash_buffer("IPA:commitment", opening_claim.commitment);
469 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
470 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
471 }
489 static VerifierAccumulator reduce_verify_internal_recursive(const OpeningClaim<Curve>& opening_claim,
490 auto& transcript)
491 requires Curve::is_stdlib_type
492 {
493 // Step 1.
494 // Done by `add_claim_to_hash_buffer`.
495
496 // Step 2.
497 // Receive generator challenge u and compute auxiliary generator
498 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
499 typename Curve::Builder* builder = generator_challenge.get_context();
500
501 auto pippenger_size =
502 2 * log_poly_length +
503 2; // the only check we perform will involve an MSM. we make the MSM "as big as possible" for efficiency,
504 // which is why `pippenger_size` is bigger here than in the native verifier.
505 std::vector<Fr> round_challenges(log_poly_length);
506 std::vector<Fr> round_challenges_inv(log_poly_length);
507 std::vector<Commitment> msm_elements(
508 pippenger_size); // L_{k-1}, R_{k-1}, L_{k-2}, ..., L_0, R_0, -G_0, -Commitment::one()
509 std::vector<Fr> msm_scalars(pippenger_size); // w_{k-1}^{-1}, w_{k-1}, ..., w_{0}^{-1}, w_{0}, a_0, (a_0 * b_0 -
510 // f(\beta)) * generator_challenge
511
512 // Step 3.
513 // Receive all L_i and R_i and prepare for MSM
514 for (size_t i = 0; i < log_poly_length; i++) {
515
516 std::string index = std::to_string(log_poly_length - i - 1);
517 auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
518 auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
519 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
520 round_challenges_inv[i] = round_challenges[i].invert();
521
522 msm_elements[2 * i] = element_L;
523 msm_elements[2 * i + 1] = element_R;
524 msm_scalars[2 * i] = round_challenges_inv[i];
525 msm_scalars[2 * i + 1] = round_challenges[i];
526 }
527
528 // Step 4.
529 // Compute b_zero succinctly
530 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
531
532 // Step 5.
533 // Receive G_zero from the prover
534 Commitment G_zero = transcript->template receive_from_prover<Commitment>("IPA:G_0");
535
536 // Step 6.
537 // Receive a_zero from the prover
538 const auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
539
540 // Step 7.
541 // Compute R = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j - G₀ * a₀ - (f(\beta) - a₀ * b₀) ⋅ U
542 // If everything is correct, then R == -C, as C':= C + f(\beta) ⋅ U
543 msm_elements.emplace_back(-G_zero);
544 msm_elements.emplace_back(-Commitment::one(builder));
545 msm_scalars.emplace_back(a_zero);
546 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation }));
547 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
548 auto neg_commitment = -opening_claim.commitment;
549
550 // the below is the only constraint.
551 ipa_relation.assert_equal(neg_commitment);
552
553 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
554 }
555
567 template <typename Transcript = NativeTranscript>
568 static void compute_opening_proof(const CK& ck,
569 const ProverOpeningClaim<Curve>& opening_claim,
570 const std::shared_ptr<Transcript>& transcript)
571 {
572 add_claim_to_hash_buffer(ck, opening_claim, transcript);
573 compute_opening_proof_internal(ck, opening_claim, transcript);
574 }
575
587 template <typename Transcript = NativeTranscript>
588 static bool reduce_verify(const VK& vk,
589 const OpeningClaim<Curve>& opening_claim,
590 const std::shared_ptr<Transcript>& transcript)
591 requires(!Curve::is_stdlib_type)
592 {
593 add_claim_to_hash_buffer(opening_claim, transcript);
594 return reduce_verify_internal_native(vk, opening_claim, transcript);
595 }
596
608 static VerifierAccumulator reduce_verify(const OpeningClaim<Curve>& opening_claim, const auto& transcript)
609 requires(Curve::is_stdlib_type)
610 {
611 // The output of `reduce_verify_internal_recursive` consists of a `VerifierAccumulator` and a boolean, recording
612 // the truth value of the last verifier-compatibility check. This simply forgets the boolean and returns the
613 // `VerifierAccumulator`.
614 add_claim_to_hash_buffer(opening_claim, transcript);
615 return reduce_verify_internal_recursive(opening_claim, transcript);
616 }
617
635 static bool full_verify_recursive(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
636 requires Curve::is_stdlib_type
637 {
638 add_claim_to_hash_buffer(opening_claim, transcript);
639 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
640 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
641 auto claimed_G_zero = verifier_accumulator.comm;
642
643 // Construct vector s, whose rth entry is ∏ (u_i)^{-1 * r_i}, where (r_i) is the binary expansion of r. This
644 // is required to _compute_ G_zero (rather than just passively receive G_zero from the Prover).
645 //
646 // We implement a linear-time algorithm to optimally compute this vector
647 // Note: currently requires an extra vector of size
648 // `poly_length / 2` to cache temporaries
649 // this might able to be optimized if we care enough, but the size of this poly shouldn't be large
650 // relative to the builder polynomial sizes
651 std::vector<Fr> s_vec_temporaries(poly_length / 2);
652 std::vector<Fr> s_vec(poly_length);
653
654 Fr* previous_round_s = &s_vec_temporaries[0];
655 Fr* current_round_s = &s_vec[0];
656 // if number of rounds is even we need to swap these so that s_vec always contains the result
657 if constexpr ((log_poly_length & 1) == 0) {
658 std::swap(previous_round_s, current_round_s);
659 }
660 previous_round_s[0] = Fr(1);
661 for (size_t i = 0; i < log_poly_length; ++i) {
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 current_round_s[j * 2] = previous_round_s[j];
666 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
667 }
668 std::swap(current_round_s, previous_round_s);
669 }
670
671 // Compute G_zero
672 // In the native verifier, this uses pippenger. Here we were batch_mul.
673 const std::vector<Commitment> srs_elements = vk.get_monomial_points();
674 Commitment computed_G_zero = Commitment::batch_mul(srs_elements, s_vec);
675 // check the computed G_zero and the claimed G_zero are the same.
676 claimed_G_zero.assert_equal(computed_G_zero);
677 BB_ASSERT_EQ(computed_G_zero.get_value(), claimed_G_zero.get_value(), "G_zero doesn't match received G_zero.");
678
679 bool running_truth_value = verifier_accumulator.running_truth_value;
680 return running_truth_value;
681 }
682
695 static OpeningClaim<Curve> reduce_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim)
696 {
697 // Extract batch_mul arguments from the accumulator
698 const auto& commitments = batch_opening_claim.commitments;
699 const auto& scalars = batch_opening_claim.scalars;
700 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
701 // Compute \f$ C = \sum \text{commitments}_i \cdot \text{scalars}_i \f$
702 GroupElement shplonk_output_commitment;
703 if constexpr (Curve::is_stdlib_type) {
704 shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
705 } else {
706 shplonk_output_commitment = batch_mul_native(commitments, scalars);
707 }
708 // Output an opening claim, which in practice will be verified by the IPA opening protocol
709 return { { shplonk_eval_challenge, Fr(0) }, shplonk_output_commitment };
710 }
711
720 static bool reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
721 const VK& vk,
722 auto& transcript)
723 requires(!Curve::is_stdlib_type)
724 {
725 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
726 add_claim_to_hash_buffer(opening_claim, transcript);
727 return reduce_verify_internal_native(vk, opening_claim, transcript);
728 }
729
738 static VerifierAccumulator reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
739 [[maybe_unused]] const std::shared_ptr<VK>& vk,
740 auto& transcript)
741 requires(Curve::is_stdlib_type)
742 {
743 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
744 add_claim_to_hash_buffer(opening_claim, transcript);
745 return reduce_verify_internal_recursive(opening_claim, transcript).verifier_accumulator;
746 }
747
756 static Fr evaluate_challenge_poly(const std::vector<Fr>& u_challenges_inv, Fr r)
757 {
758 // Runs the obvious algorithm to compute the product ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.r^{2^{i-1}}) by
759 // remembering the current 2-primary power of r.
760 Fr challenge_poly_eval = 1;
761 Fr r_pow = r;
762 // the loop runs to `log_poly_length - 1` because we don't want to superfluously compute r_pow.sqr() in the last
763 // round.
764 for (size_t i = 0; i < log_poly_length - 1; i++) {
765 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
766 challenge_poly_eval *= (Fr(1) + monomial);
767 r_pow = r_pow.sqr();
768 }
769 // same as the body of the loop, without `r_pow = r_pow.sqr()`
770 Fr monomial = u_challenges_inv[0] * r_pow;
771 challenge_poly_eval *= (Fr(1) + monomial);
772 return challenge_poly_eval;
773 }
774
786 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
787 std::vector<Fr> u_challenges_inv_2,
788 Fr r,
789 Fr alpha)
790 {
791 auto result =
792 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
793 return result;
794 }
795
803 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(const std::span<const bb::fq>& u_challenges_inv)
804 {
805
806 // Construct vector s in linear time.
808 std::vector<bb::fq> s_vec_temporaries(poly_length / 2);
809
810 bb::fq* previous_round_s = &s_vec_temporaries[0];
811 bb::fq* current_round_s = &s_vec[0];
812 // if number of rounds is even we need to swap these so that s_vec always contains the result
813 if ((log_poly_length & 1) == 0) {
814 std::swap(previous_round_s, current_round_s);
815 }
816 previous_round_s[0] = bb::fq(1);
817 for (size_t i = 0; i < log_poly_length; ++i) {
818 const size_t round_size = 1 << (i + 1);
819 const bb::fq round_challenge = u_challenges_inv[i];
821 round_size / 2,
822 [&](size_t j) {
823 current_round_s[j * 2] = previous_round_s[j];
824 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
825 },
827 std::swap(current_round_s, previous_round_s);
828 }
829 return { s_vec, poly_length };
830 }
831
842 static Polynomial<bb::fq> create_challenge_poly(const std::vector<bb::fq>& u_challenges_inv_1,
843 const std::vector<bb::fq>& u_challenges_inv_2,
844 bb::fq alpha)
845 {
846 // Always extend each to 1<<log_poly_length length
847 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
848 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
849 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
850 challenge_poly += challenge_poly_1;
851 challenge_poly.add_scaled(challenge_poly_2, alpha);
852 return challenge_poly;
853 }
854
870 static std::pair<OpeningClaim<Curve>, HonkProof> accumulate(const CommitmentKey<curve::Grumpkin>& ck,
871 auto& transcript_1,
872 OpeningClaim<Curve> claim_1,
873 auto& transcript_2,
874 OpeningClaim<Curve> claim_2)
875 requires Curve::is_stdlib_type
876 {
877 using NativeCurve = curve::Grumpkin;
878 // Sanity check that we are not using Grumpkin with MegaCircuitBuilder designed to delegate BN254 ops.
879 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
880 // Step 1: Run the partial verifier for each IPA instance
881 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
882 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
883
884 // Step 2: Generate the challenges by hashing the pairs
885 UltraStdlibTranscript transcript;
886 transcript.add_to_hash_buffer("u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
887 transcript.add_to_hash_buffer("U_1", verifier_accumulator_1.comm);
888 transcript.add_to_hash_buffer("u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
889 transcript.add_to_hash_buffer("U_2", verifier_accumulator_2.comm);
890 auto [alpha, r] = transcript.template get_challenges<Fr>("IPA:alpha", "IPA:r");
891
892 // Step 3: Compute the new accumulator
893 OpeningClaim<Curve> output_claim;
894 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
895 output_claim.opening_pair.challenge = r;
896 // Evaluate the challenge_poly polys at r and linearly combine them with alpha challenge
897 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
898 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
899
900 // Step 4: Compute the new challenge polynomial natively
901 std::vector<bb::fq> native_u_challenges_inv_1;
902 std::vector<bb::fq> native_u_challenges_inv_2;
903 for (Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
904 native_u_challenges_inv_1.push_back(bb::fq(u_inv_i.get_value()));
905 }
906 for (Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
907 native_u_challenges_inv_2.push_back(bb::fq(u_inv_i.get_value()));
908 }
909
910 Polynomial<bb::fq> challenge_poly =
911 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2, bb::fq(alpha.get_value()));
912
913 // Compute proof for the claim
914 auto prover_transcript = std::make_shared<NativeTranscript>();
915 const OpeningPair<NativeCurve> opening_pair{ bb::fq(output_claim.opening_pair.challenge.get_value()),
916 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
917
918 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
919 opening_pair.evaluation,
920 "Opening claim does not hold for challenge polynomial.");
921
922 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
923 ck, { challenge_poly, opening_pair }, prover_transcript);
924 BB_ASSERT_EQ(challenge_poly.evaluate(bb::fq(output_claim.opening_pair.challenge.get_value())),
925 bb::fq(output_claim.opening_pair.evaluation.get_value()),
926 "Opening claim does not hold for challenge polynomial.");
927
928 output_claim.opening_pair.evaluation.self_reduce();
929 return { output_claim, prover_transcript->export_proof() };
930 }
931
932 static std::pair<OpeningClaim<Curve>, HonkProof> create_random_valid_ipa_claim_and_proof(
934 requires Curve::is_stdlib_type
935 {
936 using NativeCurve = curve::Grumpkin;
937 using Builder = typename Curve::Builder;
938 using Curve = stdlib::grumpkin<Builder>;
939 auto ipa_transcript = std::make_shared<NativeTranscript>();
940 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
941 size_t n = poly_length;
942 auto poly = Polynomial<bb::fq>(n);
943 for (size_t i = 0; i < n; i++) {
944 poly.at(i) = bb::fq::random_element();
945 }
947 bb::fq eval = poly.evaluate(x);
948 auto commitment = ipa_commitment_key.commit(poly);
949 const OpeningPair<NativeCurve> opening_pair = { x, eval };
950 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
951
952 auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
953 auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
954 auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
955 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
956
957 return { stdlib_opening_claim, ipa_transcript->export_proof() };
958 }
959};
960
961} // namespace bb
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:88
#define ASSERT(expression,...)
Definition assert.hpp:77
CommitmentKey object over a pairing group 𝔾₁.
IPA (inner product argument) commitment scheme class.
Definition ipa.hpp:93
typename Curve::Element GroupElement
Definition ipa.hpp:97
CommitmentKey< Curve > CK
Definition ipa.hpp:99
static constexpr size_t poly_length
Definition ipa.hpp:108
stdlib::recursion::honk::IpaAccumulator< Curve > VerifierAccumulator
Definition ipa.hpp:105
VerifierCommitmentKey< Curve > VK
Definition ipa.hpp:100
typename Curve::ScalarField Fr
Definition ipa.hpp:96
typename Curve::AffineElement Commitment
Definition ipa.hpp:98
Curve_ Curve
Definition ipa.hpp:95
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
Wrapper class that allows us to call IPA methods.
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
constexpr size_t FF_COPY_COST
Definition thread.hpp:153
constexpr size_t FF_ADDITION_COST
Definition thread.hpp:141
constexpr size_t FF_MULTIPLICATION_COST
Definition thread.hpp:143
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
BaseTranscript< stdlib::StdlibCodec< stdlib::field_t< UltraCircuitBuilder > >, stdlib::poseidon2< UltraCircuitBuilder > > UltraStdlibTranscript
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:171
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(C &coeffs) noexcept
static constexpr field zero()
void throw_or_abort(std::string const &err)