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 {
85template <typename Curve_, size_t log_poly_length = CONST_ECCVM_LOG_N> class IPA {
86 public:
87 using Curve = Curve_;
88 using Fr = typename Curve::ScalarField;
89 using GroupElement = typename Curve::Element;
93
94 // records the `u_challenges_inv`, the Pederson commitment to the `h` -polynomial, a.k.a. the challenge
95 // polynomial, given as ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.X^{2^{i-1}}), and the running truth value of the IPA
96 // accumulation claim.
98
99 // Compute the length of the vector of coefficients of a polynomial being opened.
100 static constexpr size_t poly_length = 1UL << log_poly_length;
101
102// These allow access to internal functions so that we can never use a mock transcript unless it's fuzzing or testing of
103// IPA specifically
104#ifdef IPA_TEST
105 FRIEND_TEST(IPATest, ChallengesAreZero);
106 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
107#endif
108#ifdef IPA_FUZZ_TEST
109 friend class ProxyCaller;
110#endif
111
148 template <typename Transcript>
149 static void compute_opening_proof_internal(const CK& ck,
150 const ProverOpeningClaim<Curve>& opening_claim,
151 const std::shared_ptr<Transcript>& transcript)
152 {
153 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
154
155 // Step 1.
156 // Done in `add_claim_to_hash_buffer`.
157
158 // Step 2.
159 // Receive challenge for the auxiliary generator
160 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
161
162 if (generator_challenge.is_zero()) {
163 throw_or_abort("The generator challenge can't be zero");
164 }
165
166 // Step 3.
167 // Compute auxiliary generator U, which is used to bind together the inner product claim and the commitment.
168 // This yields the binding property because we assume it is computationally difficult to find a linear relation
169 // between the CRS and `Commitment::one()`.
170 // Compute auxiliary generator U, which is used to bind together the inner product claim and the commitment.
171 // This yields the binding property because we assume it is computationally difficult to find a linear relation
172 // between the CRS and `Commitment::one()`.
173 auto aux_generator = Commitment::one() * generator_challenge;
174
175 // Checks poly_degree is greater than zero and a power of two
176 // In the future, we might want to consider if non-powers of two are needed
177 BB_ASSERT((poly_length > 0) && (!(poly_length & (poly_length - 1))) &&
178 "The polynomial degree plus 1 should be positive and a power of two");
179
180 // Step 4.
181 // Set initial vector a to the polynomial monomial coefficients and load vector G
182 // Ensure the polynomial copy is fully-formed
183 auto a_vec = polynomial.full();
184 std::span<Commitment> srs_elements = ck.get_monomial_points();
185 std::vector<Commitment> G_vec_local(poly_length);
186
187 if (poly_length > srs_elements.size()) {
188 throw_or_abort("potential bug: Not enough SRS points for IPA!");
189 }
190
191 // Copy the SRS into a local data structure as we need to mutate this vector for every round
193 poly_length, [&](size_t i) { G_vec_local[i] = srs_elements[i]; }, thread_heuristics::FF_COPY_COST);
194
195 // Step 5.
196 // Compute vector b (vector of the powers of the challenge)
197 OpeningPair<Curve> opening_pair = opening_claim.opening_pair;
198 std::vector<Fr> b_vec(poly_length);
201 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
202 Fr b_power = opening_pair.challenge.pow(start);
203 for (size_t i = start; i < end; i++) {
204 b_vec[i] = b_power;
205 b_power *= opening_pair.challenge;
206 }
207 },
209
210 // Iterate for log(poly_degree) rounds to compute the round commitments.
211
212 // Allocate space for L_i and R_i elements
213 GroupElement L_i;
214 GroupElement R_i;
215 std::size_t round_size = poly_length;
216
217 // Step 6.
218 // Perform IPA reduction rounds
219 for (size_t i = 0; i < log_poly_length; i++) {
220 round_size /= 2;
221 // Run scalar products in parallel
222 auto inner_prods = parallel_for_heuristic(
223 round_size,
224 std::pair{ Fr::zero(), Fr::zero() },
225 [&](size_t j, std::pair<Fr, Fr>& inner_prod_left_right) {
226 // Compute inner_prod_L := < a_vec_lo, b_vec_hi >
227 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
228 // Compute inner_prod_R := < a_vec_hi, b_vec_lo >
229 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
230 },
232 // Sum inner product contributions computed in parallel and unpack the std::pair
233 auto [inner_prod_L, inner_prod_R] = sum_pairs(inner_prods);
234 // Step 6.a (using letters, because doxygen automatically converts the sublist counters to letters :( )
235 // L_i = < a_vec_lo, G_vec_hi > + inner_prod_L * aux_generator
236
237 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), /*size*/ round_size } },
238 { &G_vec_local[round_size], round_size });
239
240 L_i += aux_generator * inner_prod_L;
241
242 // Step 6.b
243 // R_i = < a_vec_hi, G_vec_lo > + inner_prod_R * aux_generator
244 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(round_size), /*size*/ round_size } },
245 { &G_vec_local[0], /*size*/ round_size });
246 R_i += aux_generator * inner_prod_R;
247
248 // Step 6.c
249 // Send L_i and R_i to the verifier
250 std::string index = std::to_string(log_poly_length - i - 1);
251 transcript->send_to_verifier("IPA:L_" + index, Commitment(L_i));
252 transcript->send_to_verifier("IPA:R_" + index, Commitment(R_i));
253
254 // Step 6.d
255 // Receive the challenge from the verifier
256 const Fr round_challenge = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
257
258 if (round_challenge.is_zero()) {
259 throw_or_abort("IPA round challenge is zero");
260 }
261 const Fr round_challenge_inv = round_challenge.invert();
262
263 // Step 6.e
264 // G_vec_new = G_vec_lo + G_vec_hi * round_challenge_inv
265 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
266 std::span{ G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size),
267 G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size * 2) },
268 round_challenge_inv);
269 GroupElement::batch_affine_add(
270 std::span{ G_vec_local.begin(), G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size) },
271 G_hi_by_inverse_challenge,
272 G_vec_local);
273
274 // Steps 6.e and 6.f
275 // Update the vectors a_vec, b_vec.
276 // a_vec_new = a_vec_lo + a_vec_hi * round_challenge
277 // b_vec_new = b_vec_lo + b_vec_hi * round_challenge_inv
279 round_size,
280 [&](size_t j) {
281 a_vec.at(j) += round_challenge * a_vec[round_size + j];
282 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
283 },
285 }
286
287 // Step 7
288 // Send G_0 to the verifier
289 transcript->send_to_verifier("IPA:G_0", G_vec_local[0]);
290
291 // Step 8
292 // Send a_0 to the verifier
293 transcript->send_to_verifier("IPA:a_0", a_vec[0]);
294 }
306 template <typename Transcript>
307 static void add_claim_to_hash_buffer(const CK& ck,
308 const ProverOpeningClaim<Curve>& opening_claim,
309 const std::shared_ptr<Transcript>& transcript)
310 {
311 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
312
313 // Step 1.
314 // Add the commitment, challenge, and evaluation to the hash buffer.
315 // NOTE:
316 // a. This is a bit inefficient, as the prover otherwise doesn't need this commitment.
317 // However, the effect to performance of this MSM (in practice of size 2^16) is tiny.
318 // b. Note that we add these three pieces of information to the hash buffer, as opposed to
319 // calling the `send_to_verifier` method, as the verifier knows them.
320
321 const auto commitment = ck.commit(polynomial);
322 transcript->add_to_hash_buffer("IPA:commitment", commitment);
323 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
324 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
325 }
326
352 static bool reduce_verify_internal_native(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
353 requires(!Curve::is_stdlib_type)
354 {
355 // Step 1
356 // Done by `add_claim_to_hash_buffer`.
357
358 // Step 2.
359 // Receive generator challenge u and compute auxiliary generator
360 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
361
362 if (generator_challenge.is_zero()) {
363 throw_or_abort("The generator challenge can't be zero");
364 }
365
366 const Commitment aux_generator = Commitment::one() * generator_challenge;
367
368 // Step 3.
369 // Compute C' = C + f(\beta) ⋅ U, i.e., the _joint_ commitment of f and f(\beta).
370 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
371
372 const auto pippenger_size = 2 * log_poly_length;
373 std::vector<Fr> round_challenges(log_poly_length);
374 // the group elements that will participate in our MSM.
375 std::vector<Commitment> msm_elements(pippenger_size); // L_{k-1}, R_{k-1}, L_{k-2}, ..., L_0, R_0.
376 // the scalars that will participate in our MSM.
377 std::vector<Fr> msm_scalars(pippenger_size); // w_{k-1}^{-1}, w_{k-1}, ..., w_{0}^{-1}, w_{0}.
378
379 // Step 4.
380 // Receive all L_i and R_i and populate msm_elements.
381 for (size_t i = 0; i < log_poly_length; i++) {
382 std::string index = std::to_string(log_poly_length - i - 1);
383 const auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
384 const auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
385 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
386 if (round_challenges[i].is_zero()) {
387 throw_or_abort("Round challenges can't be zero");
388 }
389 msm_elements[2 * i] = element_L;
390 msm_elements[2 * i + 1] = element_R;
391 }
392
393 std::vector<Fr> round_challenges_inv = round_challenges;
394 Fr::batch_invert(round_challenges_inv);
395
396 // populate msm_scalars.
397 for (size_t i = 0; i < log_poly_length; i++) {
398 msm_scalars[2 * i] = round_challenges_inv[i];
399 msm_scalars[2 * i + 1] = round_challenges[i];
400 }
401
402 // Step 5.
403 // Compute C_zero = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j
404 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>(
405 { 0, { &msm_scalars[0], /*size*/ pippenger_size } }, { &msm_elements[0], /*size*/ pippenger_size });
406 GroupElement C_zero = C_prime + LR_sums;
407
408 // Step 6.
409 // Compute b_zero succinctly
410 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
411
412 // Step 7.
413 // Construct vector s
414 Polynomial<Fr> s_vec(
415 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
416
417 std::span<const Commitment> srs_elements = vk.get_monomial_points();
418 if (poly_length > srs_elements.size()) {
419 throw_or_abort("potential bug: Not enough SRS points for IPA!");
420 }
421
422 // Step 8.
423 // Compute G_zero
424 Commitment G_zero =
425 scalar_multiplication::pippenger_unsafe<Curve>(s_vec, { &srs_elements[0], /*size*/ poly_length });
426 Commitment G_zero_sent = transcript->template receive_from_prover<Commitment>("IPA:G_0");
427 BB_ASSERT_EQ(G_zero, G_zero_sent, "G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
428
429 // Step 9.
430 // Receive a_zero from the prover
431 auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
432
433 // Step 10.
434 // Compute C_right. Implicitly, this is an IPA statement for the length 1 vectors <a_0> and <b_0> together with
435 // the URS G_0.
436 GroupElement right_hand_side = G_zero * a_zero + aux_generator * a_zero * b_zero;
437 // Step 11.
438 // Check if C_right == C_zero
439 return (C_zero.normalize() == right_hand_side.normalize());
440 }
441
452 template <typename Transcript>
453 static void add_claim_to_hash_buffer(const OpeningClaim<Curve>& opening_claim,
454 const std::shared_ptr<Transcript>& transcript)
455 {
456
457 // Step 1.
458 // Add the commitment, challenge, and evaluation to the hash buffer.
459
460 transcript->add_to_hash_buffer("IPA:commitment", opening_claim.commitment);
461 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
462 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
463 }
481 static VerifierAccumulator reduce_verify_internal_recursive(const OpeningClaim<Curve>& opening_claim,
482 auto& transcript)
483 requires Curve::is_stdlib_type
484 {
485 // Step 1.
486 // Done by `add_claim_to_hash_buffer`.
487
488 // Step 2.
489 // Receive generator challenge u and compute auxiliary generator
490 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
491 typename Curve::Builder* builder = generator_challenge.get_context();
492
493 auto pippenger_size =
494 2 * log_poly_length +
495 2; // the only check we perform will involve an MSM. we make the MSM "as big as possible" for efficiency,
496 // which is why `pippenger_size` is bigger here than in the native verifier.
497 std::vector<Fr> round_challenges(log_poly_length);
498 std::vector<Fr> round_challenges_inv(log_poly_length);
499 std::vector<Commitment> msm_elements(
500 pippenger_size); // L_{k-1}, R_{k-1}, L_{k-2}, ..., L_0, R_0, -G_0, -Commitment::one()
501 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 -
502 // f(\beta)) * generator_challenge
503
504 // Step 3.
505 // Receive all L_i and R_i and prepare for MSM
506 for (size_t i = 0; i < log_poly_length; i++) {
507
508 std::string index = std::to_string(log_poly_length - i - 1);
509 auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
510 auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
511 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
512 round_challenges_inv[i] = round_challenges[i].invert();
513
514 msm_elements[2 * i] = element_L;
515 msm_elements[2 * i + 1] = element_R;
516 msm_scalars[2 * i] = round_challenges_inv[i];
517 msm_scalars[2 * i + 1] = round_challenges[i];
518 }
519
520 // Step 4.
521 // Compute b_zero succinctly
522 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
523
524 // Step 5.
525 // Receive G_zero from the prover
526 Commitment G_zero = transcript->template receive_from_prover<Commitment>("IPA:G_0");
527
528 // Step 6.
529 // Receive a_zero from the prover
530 const auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
531
532 // OriginTag false positive: G_zero and a_zero are fully determined once all round challenges are fixed - the
533 // prover must send the correct values or the final relation check fails.
534 if constexpr (Curve::is_stdlib_type) {
535 const auto last_round_tag = round_challenges.back().get_origin_tag();
536 G_zero.set_origin_tag(last_round_tag);
537 const_cast<Fr&>(a_zero).set_origin_tag(last_round_tag);
538 }
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 = GroupElement::batch_mul(commitments, scalars);
703 // Output an opening claim, which in practice will be verified by the IPA opening protocol
704 return { { shplonk_eval_challenge, Fr(0) }, shplonk_output_commitment };
705 }
706
715 static bool reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
716 const VK& vk,
717 auto& transcript)
718 requires(!Curve::is_stdlib_type)
719 {
720 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
721 add_claim_to_hash_buffer(opening_claim, transcript);
722 return reduce_verify_internal_native(vk, opening_claim, transcript);
723 }
724
733 static VerifierAccumulator reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
734 [[maybe_unused]] const std::shared_ptr<VK>& vk,
735 auto& transcript)
736 requires(Curve::is_stdlib_type)
737 {
738 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
739 add_claim_to_hash_buffer(opening_claim, transcript);
740 return reduce_verify_internal_recursive(opening_claim, transcript).verifier_accumulator;
741 }
742
751 static Fr evaluate_challenge_poly(const std::vector<Fr>& u_challenges_inv, Fr r)
752 {
753 // Runs the obvious algorithm to compute the product ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.r^{2^{i-1}}) by
754 // remembering the current 2-primary power of r.
755 Fr challenge_poly_eval = 1;
756 Fr r_pow = r;
757 // the loop runs to `log_poly_length - 1` because we don't want to superfluously compute r_pow.sqr() in the last
758 // round.
759 for (size_t i = 0; i < log_poly_length - 1; i++) {
760 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
761 challenge_poly_eval *= (Fr(1) + monomial);
762 r_pow = r_pow.sqr();
763 }
764 // same as the body of the loop, without `r_pow = r_pow.sqr()`
765 Fr monomial = u_challenges_inv[0] * r_pow;
766 challenge_poly_eval *= (Fr(1) + monomial);
767 return challenge_poly_eval;
768 }
769
781 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
782 std::vector<Fr> u_challenges_inv_2,
783 Fr r,
784 Fr alpha)
785 {
786 auto result =
787 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
788 return result;
789 }
790
798 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(const std::span<const bb::fq>& u_challenges_inv)
799 {
800
801 // Construct vector s in linear time.
802 std::vector<bb::fq> s_vec(poly_length, bb::fq::one());
803 std::vector<bb::fq> s_vec_temporaries(poly_length / 2);
804
805 bb::fq* previous_round_s = &s_vec_temporaries[0];
806 bb::fq* current_round_s = &s_vec[0];
807 // if number of rounds is even we need to swap these so that s_vec always contains the result
808 if ((log_poly_length & 1) == 0) {
809 std::swap(previous_round_s, current_round_s);
810 }
811 previous_round_s[0] = bb::fq(1);
812 for (size_t i = 0; i < log_poly_length; ++i) {
813 const size_t round_size = 1 << (i + 1);
814 const bb::fq round_challenge = u_challenges_inv[i];
816 round_size / 2,
817 [&](size_t j) {
818 current_round_s[j * 2] = previous_round_s[j];
819 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
820 },
822 std::swap(current_round_s, previous_round_s);
823 }
824 return { s_vec, poly_length };
825 }
826
837 static Polynomial<bb::fq> create_challenge_poly(const std::vector<bb::fq>& u_challenges_inv_1,
838 const std::vector<bb::fq>& u_challenges_inv_2,
839 bb::fq alpha)
840 {
841 // Always extend each to 1<<log_poly_length length
842 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
843 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
844 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
845 challenge_poly += challenge_poly_1;
846 challenge_poly.add_scaled(challenge_poly_2, alpha);
847 return challenge_poly;
848 }
849
865 static std::pair<OpeningClaim<Curve>, HonkProof> accumulate(const CommitmentKey<curve::Grumpkin>& ck,
866 auto& transcript_1,
867 OpeningClaim<Curve> claim_1,
868 auto& transcript_2,
869 OpeningClaim<Curve> claim_2)
870 requires Curve::is_stdlib_type
871 {
872 using NativeCurve = curve::Grumpkin;
873 // Sanity check that we are not using Grumpkin with MegaCircuitBuilder designed to delegate BN254 ops.
874 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
875 // Step 1: Run the partial verifier for each IPA instance
876 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
877 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
878
879 // Step 2: Generate the challenges by hashing the pairs
880 UltraStdlibTranscript transcript;
881 transcript.add_to_hash_buffer("u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
882 transcript.add_to_hash_buffer("U_1", verifier_accumulator_1.comm);
883 transcript.add_to_hash_buffer("u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
884 transcript.add_to_hash_buffer("U_2", verifier_accumulator_2.comm);
885 auto [alpha, r] = transcript.template get_challenges<Fr>(std::array<std::string, 2>{ "IPA:alpha", "IPA:r" });
886
887 // Step 3: Compute the new accumulator
888 OpeningClaim<Curve> output_claim;
889 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
890 output_claim.opening_pair.challenge = r;
891 // Evaluate the challenge_poly polys at r and linearly combine them with alpha challenge
892 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
893 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
894
895 // Step 4: Compute the new challenge polynomial natively
896 std::vector<bb::fq> native_u_challenges_inv_1;
897 std::vector<bb::fq> native_u_challenges_inv_2;
898 for (Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
899 native_u_challenges_inv_1.push_back(bb::fq(u_inv_i.get_value()));
900 }
901 for (Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
902 native_u_challenges_inv_2.push_back(bb::fq(u_inv_i.get_value()));
903 }
904
905 Polynomial<bb::fq> challenge_poly =
906 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2, bb::fq(alpha.get_value()));
907
908 // Compute proof for the claim
909 auto prover_transcript = std::make_shared<NativeTranscript>();
910 const OpeningPair<NativeCurve> opening_pair{ bb::fq(output_claim.opening_pair.challenge.get_value()),
911 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
912
913 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
914 opening_pair.evaluation,
915 "Opening claim does not hold for challenge polynomial.");
916
917 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
918 ck, { challenge_poly, opening_pair }, prover_transcript);
919 BB_ASSERT_EQ(challenge_poly.evaluate(bb::fq(output_claim.opening_pair.challenge.get_value())),
920 bb::fq(output_claim.opening_pair.evaluation.get_value()),
921 "Opening claim does not hold for challenge polynomial.");
922
923 output_claim.opening_pair.evaluation.self_reduce();
924 return { output_claim, prover_transcript->export_proof() };
925 }
926
927 static std::pair<OpeningClaim<Curve>, HonkProof> create_random_valid_ipa_claim_and_proof(
929 requires Curve::is_stdlib_type
930 {
931 using NativeCurve = curve::Grumpkin;
932 using Builder = typename Curve::Builder;
933 using Curve = stdlib::grumpkin<Builder>;
934 auto ipa_transcript = std::make_shared<NativeTranscript>();
935 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
936 size_t n = poly_length;
937 auto poly = Polynomial<bb::fq>(n);
938 for (size_t i = 0; i < n; i++) {
939 poly.at(i) = bb::fq::random_element();
940 }
942 bb::fq eval = poly.evaluate(x);
943 auto commitment = ipa_commitment_key.commit(poly);
944 const OpeningPair<NativeCurve> opening_pair = { x, eval };
945 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
946
947 auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
948 auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
949 auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
950 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
951
952 return { stdlib_opening_claim, ipa_transcript->export_proof() };
953 }
954};
955
956} // 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:85
typename Curve::Element GroupElement
Definition ipa.hpp:89
CommitmentKey< Curve > CK
Definition ipa.hpp:91
static constexpr size_t poly_length
Definition ipa.hpp:100
stdlib::recursion::honk::IpaAccumulator< Curve > VerifierAccumulator
Definition ipa.hpp:97
VerifierCommitmentKey< Curve > VK
Definition ipa.hpp:92
typename Curve::ScalarField Fr
Definition ipa.hpp:88
typename Curve::AffineElement Commitment
Definition ipa.hpp:90
Curve_ Curve
Definition ipa.hpp:87
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:36
Polynomial polynomial
Definition claim.hpp:41
OpeningPair< Curve > opening_pair
Definition claim.hpp:42
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:144
constexpr size_t FF_ADDITION_COST
Definition thread.hpp:132
constexpr size_t FF_MULTIPLICATION_COST
Definition thread.hpp:134
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:170
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)
Curve::ScalarField Fr
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
Batch invert a collection of field elements using Montgomery's trick.
static constexpr field zero()
void throw_or_abort(std::string const &err)