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