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
21#include <cstddef>
22#include <numeric>
23#include <string>
24#include <utility>
25#include <vector>
26
27namespace bb {
86template <typename Curve_, size_t log_poly_length = CONST_ECCVM_LOG_N> class IPA {
87 public:
88 using Curve = Curve_;
89 using Fr = typename Curve::ScalarField;
90 using GroupElement = typename Curve::Element;
94
95 // records the `u_challenges_inv`, the Pederson commitment to the `h` -polynomial, a.k.a. the challenge
96 // polynomial, given as ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.X^{2^{i-1}}), and the running truth value of the IPA
97 // accumulation claim.
99
100 // Compute the length of the vector of coefficients of a polynomial being opened.
101 static constexpr size_t poly_length = 1UL << log_poly_length;
102 static_assert(log_poly_length >= 1, "log_poly_length must be at least 1");
103
104// These allow access to internal functions so that we can never use a mock transcript unless it's fuzzing or testing of
105// IPA specifically
106#ifdef IPA_TEST
107 FRIEND_TEST(IPATest, ChallengesAreZero);
108 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
109#endif
110#ifdef IPA_FUZZ_TEST
111 friend class ProxyCaller;
112#endif
113
150 template <typename Transcript>
151 static void compute_opening_proof_internal(const CK& ck,
152 const ProverOpeningClaim<Curve>& opening_claim,
153 const std::shared_ptr<Transcript>& transcript)
154 {
155 BB_BENCH_NAME("IPA::compute_opening_proof");
156 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
157
158 // Step 1.
159 // Done in `add_claim_to_hash_buffer`.
160
161 // Step 2.
162 // Receive challenge for the auxiliary generator
163 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
164
165 if (generator_challenge.is_zero()) {
166 throw_or_abort("The generator challenge can't be zero");
167 }
168
169 // Step 3.
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
194 [&](size_t i) {
195 BB_BENCH_TRACY_NAME("IPA::copy_srs");
196 G_vec_local[i] = srs_elements[i];
197 },
199
200 // Step 5.
201 // Compute vector b (vector of the powers of the challenge)
202 OpeningPair<Curve> opening_pair = opening_claim.opening_pair;
203 std::vector<Fr> b_vec(poly_length);
206 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
207 BB_BENCH_TRACY_NAME("IPA::compute_b_vec");
208 Fr b_power = opening_pair.challenge.pow(start);
209 for (size_t i = start; i < end; i++) {
210 b_vec[i] = b_power;
211 b_power *= opening_pair.challenge;
212 }
213 },
215
216 // Iterate for log(poly_degree) rounds to compute the round commitments.
217
218 // Allocate space for L_i and R_i elements
219 GroupElement L_i;
220 GroupElement R_i;
221 std::size_t round_size = poly_length;
222
223 // Step 6.
224 // Perform IPA reduction rounds
225 for (size_t i = 0; i < log_poly_length; i++) {
226 round_size /= 2;
227 // Run scalar products in parallel
228 auto inner_prods = parallel_for_heuristic(
229 round_size,
230 std::pair{ Fr::zero(), Fr::zero() },
231 [&](size_t j, std::pair<Fr, Fr>& inner_prod_left_right) {
232 BB_BENCH_TRACY_NAME("IPA::inner_product");
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 BB_BENCH_TRACY_NAME("IPA::fold_vecs");
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
341 struct TranscriptData {
342 GroupElement C_zero;
343 Fr b_zero;
344 Polynomial<Fr> s_vec;
346 Fr gen_challenge;
347 Commitment G_zero_from_prover;
348 Fr a_zero;
349 };
350
374 template <typename Transcript>
375 static TranscriptData read_transcript_data(const OpeningClaim<Curve>& opening_claim,
376 const std::shared_ptr<Transcript>& transcript)
377 requires(!Curve::is_stdlib_type)
378 {
379 // Step 2.
380 // Receive generator challenge u and compute auxiliary generator
381 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
382 if (generator_challenge.is_zero()) {
383 throw_or_abort("The generator challenge can't be zero");
384 }
385 const Commitment aux_generator = Commitment::one() * generator_challenge;
386
387 // Step 3.
388 // Compute C' = C + f(\beta) ⋅ U, i.e., the _joint_ commitment of f and f(\beta).
389 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
390
391 const auto pippenger_size = 2 * log_poly_length;
392 std::vector<Fr> round_challenges(log_poly_length);
393 std::vector<Commitment> msm_elements(pippenger_size);
394 std::vector<Fr> msm_scalars(pippenger_size);
395
396 // Step 4.
397 // Receive all L_j, R_j and compute round challenges u_j
398 for (size_t i = 0; i < log_poly_length; i++) {
399 std::string index = std::to_string(log_poly_length - i - 1);
400 const auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
401 const auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
402 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
403 if (round_challenges[i].is_zero()) {
404 throw_or_abort("Round challenges can't be zero");
405 }
406 msm_elements[2 * i] = element_L;
407 msm_elements[2 * i + 1] = element_R;
408 }
409
410 std::vector<Fr> round_challenges_inv = round_challenges;
411 Fr::batch_invert(round_challenges_inv);
412
413 // populate msm_scalars.
414 for (size_t i = 0; i < log_poly_length; i++) {
415 msm_scalars[2 * i] = round_challenges_inv[i];
416 msm_scalars[2 * i + 1] = round_challenges[i];
417 }
418
419 // Step 5.
420 // Compute C_zero = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j
421 GroupElement LR_sums = scalar_multiplication::pippenger<Curve>(
422 { 0, { &msm_scalars[0], /*size*/ pippenger_size } }, { &msm_elements[0], /*size*/ pippenger_size });
423 GroupElement C_zero = C_prime + LR_sums;
424
425 // Step 6.
426 // Compute b_zero succinctly
427 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
428
429 // Step 7.
430 // Construct vector s
431 Polynomial<Fr> s_vec(
432 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
433
434 // Receive G_0 and a_0 from prover (advances transcript; G_0 not recomputed here)
435 Commitment G_zero_from_prover = transcript->template receive_from_prover<Commitment>("IPA:G_0");
436 Fr a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
437
438 return { C_zero, b_zero, std::move(s_vec), generator_challenge, G_zero_from_prover, a_zero };
439 }
440
466 static bool reduce_verify_internal_native(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
467 requires(!Curve::is_stdlib_type)
468 {
469 BB_BENCH_NAME("IPA::reduce_verify");
470
471 // Steps 2–7, 9: Process transcript and extract per-proof data (step 1 done by add_claim_to_hash_buffer)
472 auto data = read_transcript_data(opening_claim, transcript);
473
474 // Step 8.
475 // Compute G_s = <s, G> via SRS MSM and verify against prover's G_0
476 std::span<const Commitment> srs_elements = vk.get_monomial_points();
477 if (poly_length > srs_elements.size()) {
478 throw_or_abort("potential bug: Not enough SRS points for IPA!");
479 }
480 Commitment G_zero;
481 {
482 BB_BENCH_NAME("IPA::srs_msm");
483 G_zero =
484 scalar_multiplication::pippenger_unsafe<Curve>(data.s_vec, { &srs_elements[0], /*size*/ poly_length });
485 }
486 if (G_zero != data.G_zero_from_prover) {
487 info("IPA verification failed: G_0 mismatch");
488 return false;
489 }
490
491 // Step 10.
492 // Compute C_right = a_0 * G_s + a_0 * b_0 * U
493 Commitment aux_generator = Commitment::one() * data.gen_challenge;
494 GroupElement right_hand_side = G_zero * data.a_zero + aux_generator * data.a_zero * data.b_zero;
495
496 // Step 11.
497 // Check if C_right == C_0
498 return (data.C_zero.normalize() == right_hand_side.normalize());
499 }
500
511 template <typename Transcript>
512 static void add_claim_to_hash_buffer(const OpeningClaim<Curve>& opening_claim,
513 const std::shared_ptr<Transcript>& transcript)
514 {
515
516 // Step 1.
517 // Add the commitment, challenge, and evaluation to the hash buffer.
518
519 transcript->add_to_hash_buffer("IPA:commitment", opening_claim.commitment);
520 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
521 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
522 }
540 static VerifierAccumulator reduce_verify_internal_recursive(const OpeningClaim<Curve>& opening_claim,
541 auto& transcript)
542 requires Curve::is_stdlib_type
543 {
544 // Step 1.
545 // Done by `add_claim_to_hash_buffer`.
546
547 // Step 2.
548 // Receive generator challenge u and compute auxiliary generator
549 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
550 typename Curve::Builder* builder = generator_challenge.get_context();
551
552 auto pippenger_size =
553 2 * log_poly_length +
554 2; // the only check we perform will involve an MSM. we make the MSM "as big as possible" for efficiency,
555 // which is why `pippenger_size` is bigger here than in the native verifier.
556 std::vector<Fr> round_challenges(log_poly_length);
557 std::vector<Fr> round_challenges_inv(log_poly_length);
558 std::vector<Commitment> msm_elements(
559 pippenger_size); // L_{k-1}, R_{k-1}, L_{k-2}, ..., L_0, R_0, -G_0, -Commitment::one()
560 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 -
561 // f(\beta)) * generator_challenge
562
563 // Step 3.
564 // Receive all L_i and R_i and prepare for MSM
565 for (size_t i = 0; i < log_poly_length; i++) {
566
567 std::string index = std::to_string(log_poly_length - i - 1);
568 auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
569 auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
570 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
571 round_challenges_inv[i] = round_challenges[i].invert();
572
573 msm_elements[2 * i] = element_L;
574 msm_elements[2 * i + 1] = element_R;
575 msm_scalars[2 * i] = round_challenges_inv[i];
576 msm_scalars[2 * i + 1] = round_challenges[i];
577 }
578
579 // Step 4.
580 // Compute b_zero succinctly
581 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
582
583 // Step 5.
584 // Receive G_zero from the prover
585 Commitment G_zero = transcript->template receive_from_prover<Commitment>("IPA:G_0");
586
587 // Step 6.
588 // Receive a_zero from the prover
589 auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
590
591 // OriginTag false positive: G_zero and a_zero are fully determined once all round challenges are fixed - the
592 // prover must send the correct values or the final relation check fails.
593 if constexpr (Curve::is_stdlib_type) {
594 const auto last_round_tag = round_challenges.back().get_origin_tag();
595 G_zero.set_origin_tag(last_round_tag);
596 a_zero.set_origin_tag(last_round_tag);
597 }
598
599 // Step 7.
600 // Compute R = ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j - G₀ * a₀ - (a₀ * b₀ - f(\beta)) ⋅ U
601 // If everything is correct, then R == -C.
602 msm_elements[(2 * log_poly_length)] = -G_zero;
603 msm_elements[(2 * log_poly_length) + 1] = -Commitment::one(builder);
604 msm_scalars[(2 * log_poly_length)] = a_zero;
605 msm_scalars[(2 * log_poly_length) + 1] =
606 generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation });
607 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
608 auto neg_commitment = -opening_claim.commitment;
609
610 // the below is the only constraint.
611 ipa_relation.assert_equal(neg_commitment);
612
613 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
614 }
615
627 template <typename Transcript = NativeTranscript>
628 static void compute_opening_proof(const CK& ck,
629 const ProverOpeningClaim<Curve>& opening_claim,
630 const std::shared_ptr<Transcript>& transcript)
631 {
632 add_claim_to_hash_buffer(ck, opening_claim, transcript);
633 compute_opening_proof_internal(ck, opening_claim, transcript);
634 }
635
647 template <typename Transcript = NativeTranscript>
648 static bool reduce_verify(const VK& vk,
649 const OpeningClaim<Curve>& opening_claim,
650 const std::shared_ptr<Transcript>& transcript)
651 requires(!Curve::is_stdlib_type)
652 {
653 add_claim_to_hash_buffer(opening_claim, transcript);
654 return reduce_verify_internal_native(vk, opening_claim, transcript);
655 }
656
675 template <typename Transcript = NativeTranscript>
676 static bool batch_reduce_verify(const VK& vk,
677 const std::vector<OpeningClaim<Curve>>& opening_claims,
678 const std::vector<std::shared_ptr<Transcript>>& transcripts)
679 requires(!Curve::is_stdlib_type)
680 {
681 const size_t num_claims = opening_claims.size();
682 if (num_claims != transcripts.size()) {
683 info("IPA batch verification failed: claims/transcripts size mismatch");
684 return false;
685 }
686 if (num_claims == 0) {
687 info("IPA batch verification failed: no claims provided");
688 return false;
689 }
690
691 // Phase 1: Per-proof transcript processing (sequential, each proof is cheap)
692 std::vector<GroupElement> C_zeros(num_claims);
693 std::vector<Fr> a_zeros(num_claims);
694 std::vector<Fr> b_zeros(num_claims);
695 std::vector<Fr> gen_challenges(num_claims);
696 std::vector<Polynomial<Fr>> s_vecs(num_claims);
697
698 for (size_t i = 0; i < num_claims; i++) {
699 add_claim_to_hash_buffer(opening_claims[i], transcripts[i]);
700 auto data = read_transcript_data(opening_claims[i], transcripts[i]);
701 C_zeros[i] = std::move(data.C_zero);
702 b_zeros[i] = data.b_zero;
703 s_vecs[i] = std::move(data.s_vec);
704 gen_challenges[i] = data.gen_challenge;
705 a_zeros[i] = data.a_zero;
706 }
707
708 // Phase 2: Batched computation using random challenge alpha
709 Fr alpha = Fr::random_element();
710 std::vector<Fr> alpha_pows(num_claims);
711 alpha_pows[0] = Fr::one();
712 for (size_t i = 1; i < num_claims; i++) {
713 alpha_pows[i] = alpha_pows[i - 1] * alpha;
714 }
715
716 // Combined s_vec: combined_s[j] = \sum \alpha^i * a_zero_i * s_vec_i[j]
717 Polynomial<Fr> combined_s(poly_length);
718 for (size_t i = 0; i < num_claims; i++) {
719 Fr scalar = alpha_pows[i] * a_zeros[i];
720 combined_s.add_scaled(s_vecs[i], scalar);
721 }
722
723 // Single MSM over combined scalars
724 std::span<const Commitment> srs_elements = vk.get_monomial_points();
725 if (poly_length > srs_elements.size()) {
726 throw_or_abort("potential bug: Not enough SRS points for IPA!");
727 }
728 Commitment G_batch =
729 scalar_multiplication::pippenger_unsafe<Curve>(combined_s, { &srs_elements[0], /*size*/ poly_length });
730
731 // Combined LHS: C_batch = \sum \alpha^i * C_zero_i
732 GroupElement C_batch = C_zeros[0];
733 for (size_t i = 1; i < num_claims; i++) {
734 C_batch = C_batch + C_zeros[i] * alpha_pows[i];
735 }
736
737 // Combined scalar for U terms: bU_scalar = \sum \alpha^i * a_zero_i * b_zero_i * gen_challenge_i
738 Fr bU_scalar = Fr::zero();
739 for (size_t i = 0; i < num_claims; i++) {
740 bU_scalar += alpha_pows[i] * a_zeros[i] * b_zeros[i] * gen_challenges[i];
741 }
742
743 // Check: C_batch == G_batch + bU_scalar * G
744 GroupElement right_hand_side = G_batch + Commitment::one() * bU_scalar;
745 return (C_batch.normalize() == right_hand_side.normalize());
746 }
747
759 static VerifierAccumulator reduce_verify(const OpeningClaim<Curve>& opening_claim, const auto& transcript)
760 requires(Curve::is_stdlib_type)
761 {
762 // The output of `reduce_verify_internal_recursive` consists of a `VerifierAccumulator` and a boolean, recording
763 // the truth value of the last verifier-compatibility check. This simply forgets the boolean and returns the
764 // `VerifierAccumulator`.
765 add_claim_to_hash_buffer(opening_claim, transcript);
766 return reduce_verify_internal_recursive(opening_claim, transcript);
767 }
768
786 static bool full_verify_recursive(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
787 requires Curve::is_stdlib_type
788 {
789 // Check SRS size up front before any circuit construction
790 if (vk.get_monomial_points().size() < poly_length) {
791 throw_or_abort("IPA recursive verification: not enough SRS points (need " + std::to_string(poly_length) +
792 ", have " + std::to_string(vk.get_monomial_points().size()) + ")");
793 }
794
795 add_claim_to_hash_buffer(opening_claim, transcript);
796 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
797 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
798 auto claimed_G_zero = verifier_accumulator.comm;
799
800 // Construct vector s, whose rth entry is ∏ (u_i)^{-1 * r_i}, where (r_i) is the binary expansion of r. This
801 // is required to _compute_ G_zero (rather than just passively receive G_zero from the Prover).
802 //
803 // We implement a linear-time algorithm to optimally compute this vector
804 // Note: currently requires an extra vector of size
805 // `poly_length / 2` to cache temporaries
806 // this might able to be optimized if we care enough, but the size of this poly shouldn't be large
807 // relative to the builder polynomial sizes
808 std::vector<Fr> s_vec_temporaries(poly_length / 2);
809 std::vector<Fr> s_vec(poly_length);
810
811 Fr* previous_round_s = &s_vec_temporaries[0];
812 Fr* current_round_s = &s_vec[0];
813 // if number of rounds is even we need to swap these so that s_vec always contains the result
814 if constexpr ((log_poly_length & 1) == 0) {
815 std::swap(previous_round_s, current_round_s);
816 }
817 previous_round_s[0] = Fr(1);
818 for (size_t i = 0; i < log_poly_length; ++i) {
819 const size_t round_size = 1 << (i + 1);
820 const Fr round_challenge = round_challenges_inv[i];
821 for (size_t j = 0; j < round_size / 2; ++j) {
822 current_round_s[j * 2] = previous_round_s[j];
823 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
824 }
825 std::swap(current_round_s, previous_round_s);
826 }
827
828 // Compute G_zero
829 // In the native verifier, this uses pippenger. Here we use fixed_batch_mul since all SRS points are
830 // circuit constants, which uses plookup tables instead of ROM tables and is significantly cheaper.
831 // We use 8-bit tables (table_bits=8, 32 rounds) to minimise gate count. However, with N=32768 SRS points
832 // and 8-bit tables, the total table rows = 32768 × 256 = 2^23 exactly. The 5 mandatory overhead rows
833 // (NUM_DISABLED_ROWS_IN_SUMCHECK=4, NUM_ZERO_ROWS=1) push the total to 2^23+5, forcing dyadic_size = 2^24.
834 // To stay within 2^23 we handle the first SRS point separately using operator*.
835 std::vector<Commitment> srs_elements = vk.get_monomial_points();
836 srs_elements.resize(poly_length);
837 std::vector<Commitment> remaining_srs(srs_elements.begin() + 1, srs_elements.end());
838 std::vector<Fr> remaining_s(s_vec.begin() + 1, s_vec.end());
839 Commitment first_term = srs_elements[0] * s_vec[0];
840 Commitment remaining_term = Commitment::fixed_batch_mul(remaining_srs, remaining_s, {}, /*table_bits=*/8);
841 Commitment computed_G_zero = first_term + remaining_term;
842 // check the computed G_zero and the claimed G_zero are the same.
843 // The circuit constraint enforces correctness; mismatched witnesses will produce an unsatisfiable circuit.
844 claimed_G_zero.assert_equal(computed_G_zero, "G_zero doesn't match received G_zero.");
845
846 bool running_truth_value = verifier_accumulator.running_truth_value;
847 return running_truth_value;
848 }
849
862 static OpeningClaim<Curve> reduce_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim)
863 {
864 // Extract batch_mul arguments from the accumulator
865 const auto& commitments = batch_opening_claim.commitments;
866 auto scalars = batch_opening_claim.scalars; // mutable copy: batch_mul temporarily modifies scalars
867 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
868 // Compute \f$ C = \sum \text{commitments}_i \cdot \text{scalars}_i \f$
869 GroupElement shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
870 // Output an opening claim, which in practice will be verified by the IPA opening protocol
871 return { { shplonk_eval_challenge, Fr(0) }, shplonk_output_commitment };
872 }
873
882 static bool reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
883 const VK& vk,
884 auto& transcript)
885 requires(!Curve::is_stdlib_type)
886 {
887 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
888 add_claim_to_hash_buffer(opening_claim, transcript);
889 return reduce_verify_internal_native(vk, opening_claim, transcript);
890 }
891
900 static VerifierAccumulator reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
901 [[maybe_unused]] const std::shared_ptr<VK>& vk,
902 auto& transcript)
903 requires(Curve::is_stdlib_type)
904 {
905 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
906 add_claim_to_hash_buffer(opening_claim, transcript);
907 return reduce_verify_internal_recursive(opening_claim, transcript);
908 }
909
918 static Fr evaluate_challenge_poly(const std::vector<Fr>& u_challenges_inv, Fr r)
919 {
920 // Runs the obvious algorithm to compute the product ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.r^{2^{i-1}}) by
921 // remembering the current 2-primary power of r.
922 Fr challenge_poly_eval = 1;
923 Fr r_pow = r;
924 // the loop runs to `log_poly_length - 1` because we don't want to superfluously compute r_pow.sqr() in the last
925 // round.
926 for (size_t i = 0; i < log_poly_length - 1; i++) {
927 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
928 challenge_poly_eval *= (Fr(1) + monomial);
929 r_pow = r_pow.sqr();
930 }
931 // same as the body of the loop, without `r_pow = r_pow.sqr()`
932 Fr monomial = u_challenges_inv[0] * r_pow;
933 challenge_poly_eval *= (Fr(1) + monomial);
934 return challenge_poly_eval;
935 }
936
948 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
949 std::vector<Fr> u_challenges_inv_2,
950 Fr r,
951 Fr alpha)
952 {
953 auto result =
954 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
955 return result;
956 }
957
965 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(const std::span<const bb::fq>& u_challenges_inv)
966 {
967
968 // Construct vector s in linear time.
969 std::vector<bb::fq> s_vec(poly_length, bb::fq::one());
970 std::vector<bb::fq> s_vec_temporaries(poly_length / 2);
971
972 bb::fq* previous_round_s = &s_vec_temporaries[0];
973 bb::fq* current_round_s = &s_vec[0];
974 // if number of rounds is even we need to swap these so that s_vec always contains the result
975 if ((log_poly_length & 1) == 0) {
976 std::swap(previous_round_s, current_round_s);
977 }
978 previous_round_s[0] = bb::fq(1);
979 for (size_t i = 0; i < log_poly_length; ++i) {
980 const size_t round_size = 1 << (i + 1);
981 const bb::fq round_challenge = u_challenges_inv[i];
983 round_size / 2,
984 [&](size_t j) {
985 current_round_s[j * 2] = previous_round_s[j];
986 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
987 },
989 std::swap(current_round_s, previous_round_s);
990 }
991 return { s_vec, poly_length };
992 }
993
1004 static Polynomial<bb::fq> create_challenge_poly(const std::vector<bb::fq>& u_challenges_inv_1,
1005 const std::vector<bb::fq>& u_challenges_inv_2,
1006 bb::fq alpha)
1007 {
1008 // Always extend each to 1<<log_poly_length length
1009 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
1010 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
1011 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
1012 challenge_poly += challenge_poly_1;
1013 challenge_poly.add_scaled(challenge_poly_2, alpha);
1014 return challenge_poly;
1015 }
1016
1031 static std::pair<OpeningClaim<Curve>, HonkProof> accumulate(const CommitmentKey<curve::Grumpkin>& ck,
1032 auto& transcript_1,
1033 OpeningClaim<Curve> claim_1,
1034 auto& transcript_2,
1035 OpeningClaim<Curve> claim_2)
1036 requires Curve::is_stdlib_type
1037 {
1038 using NativeCurve = curve::Grumpkin;
1039 // Sanity check that we are not using Grumpkin with MegaCircuitBuilder designed to delegate BN254 ops.
1040 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
1041 // Step 1: Run the partial verifier for each IPA instance
1042 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
1043 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
1044
1045 // Step 2: Generate the challenges by hashing the pairs
1046 UltraStdlibTranscript transcript;
1047 transcript.add_to_hash_buffer("u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
1048 transcript.add_to_hash_buffer("U_1", verifier_accumulator_1.comm);
1049 transcript.add_to_hash_buffer("u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
1050 transcript.add_to_hash_buffer("U_2", verifier_accumulator_2.comm);
1051 auto [alpha, r] = transcript.template get_challenges<Fr>(std::array<std::string, 2>{ "IPA:alpha", "IPA:r" });
1052
1053 // Step 3: Compute the new accumulator
1054 OpeningClaim<Curve> output_claim;
1055 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
1056 output_claim.opening_pair.challenge = r;
1057 // Evaluate the challenge_poly polys at r and linearly combine them with alpha challenge
1058 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
1059 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
1060
1061 // Step 4: Compute the new challenge polynomial natively
1062 std::vector<bb::fq> native_u_challenges_inv_1;
1063 std::vector<bb::fq> native_u_challenges_inv_2;
1064 for (Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
1065 native_u_challenges_inv_1.push_back(bb::fq(u_inv_i.get_value()));
1066 }
1067 for (Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
1068 native_u_challenges_inv_2.push_back(bb::fq(u_inv_i.get_value()));
1069 }
1070
1071 Polynomial<bb::fq> challenge_poly =
1072 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2, bb::fq(alpha.get_value()));
1073
1074 // Compute proof for the claim
1075 auto prover_transcript = std::make_shared<NativeTranscript>();
1076 const OpeningPair<NativeCurve> opening_pair{ bb::fq(output_claim.opening_pair.challenge.get_value()),
1077 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
1078
1079 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
1080 opening_pair.evaluation,
1081 "Opening claim does not hold for challenge polynomial.");
1082
1083 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
1084 ck, { challenge_poly, opening_pair }, prover_transcript);
1085
1086 output_claim.opening_pair.evaluation.self_reduce();
1087 return { output_claim, prover_transcript->export_proof() };
1088 }
1089
1090 static std::pair<OpeningClaim<Curve>, HonkProof> create_random_valid_ipa_claim_and_proof(
1092 requires Curve::is_stdlib_type
1093 {
1094 using NativeCurve = curve::Grumpkin;
1095 using Builder = typename Curve::Builder;
1096 using Curve = stdlib::grumpkin<Builder>;
1097 auto ipa_transcript = std::make_shared<NativeTranscript>();
1098 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
1099 size_t n = poly_length;
1100 auto poly = Polynomial<bb::fq>(n);
1101 for (size_t i = 0; i < n; i++) {
1102 poly.at(i) = bb::fq::random_element();
1103 }
1105 bb::fq eval = poly.evaluate(x);
1106 auto commitment = ipa_commitment_key.commit(poly);
1107 const OpeningPair<NativeCurve> opening_pair = { x, eval };
1108 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
1109
1110 auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
1111 auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
1112 auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
1113 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
1114
1115 return { stdlib_opening_claim, ipa_transcript->export_proof() };
1116 }
1117};
1118
1119} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
#define BB_BENCH_TRACY_NAME(name)
Definition bb_bench.hpp:256
CommitmentKey object over a pairing group 𝔾₁.
IPA (inner product argument) commitment scheme class.
Definition ipa.hpp:86
typename Curve::Element GroupElement
Definition ipa.hpp:90
CommitmentKey< Curve > CK
Definition ipa.hpp:92
static constexpr size_t poly_length
Definition ipa.hpp:101
stdlib::recursion::honk::IpaAccumulator< Curve > VerifierAccumulator
Definition ipa.hpp:98
VerifierCommitmentKey< Curve > VK
Definition ipa.hpp:93
typename Curve::ScalarField Fr
Definition ipa.hpp:89
typename Curve::AffineElement Commitment
Definition ipa.hpp:91
Curve_ Curve
Definition ipa.hpp:88
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:63
static constexpr bool is_stdlib_type
Definition grumpkin.hpp:67
typename Group::affine_element AffineElement
Definition grumpkin.hpp:64
#define info(...)
Definition log.hpp:93
#define BB_UNUSED
AluTraceBuilder builder
Definition alu.test.cpp:124
const std::vector< MemoryValue > data
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:153
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)