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
103// These allow access to internal functions so that we can never use a mock transcript unless it's fuzzing or testing of
104// IPA specifically
105#ifdef IPA_TEST
106 FRIEND_TEST(IPATest, ChallengesAreZero);
107 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
108#endif
109#ifdef IPA_FUZZ_TEST
110 friend class ProxyCaller;
111#endif
112
149 template <typename Transcript>
150 static void compute_opening_proof_internal(const CK& ck,
151 const ProverOpeningClaim<Curve>& opening_claim,
152 const std::shared_ptr<Transcript>& transcript)
153 {
154 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
155
156 // Step 1.
157 // Done in `add_claim_to_hash_buffer`.
158
159 // Step 2.
160 // Receive challenge for the auxiliary generator
161 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
162
163 if (generator_challenge.is_zero()) {
164 throw_or_abort("The generator challenge can't be zero");
165 }
166
167 // Step 3.
168 // Compute auxiliary generator U, which is used to bind together the inner product claim and the commitment.
169 // This yields the binding property because we assume it is computationally difficult to find a linear relation
170 // between the CRS and `Commitment::one()`.
171 // Compute auxiliary generator U, which is used to bind together the inner product claim and the commitment.
172 // This yields the binding property because we assume it is computationally difficult to find a linear relation
173 // between the CRS and `Commitment::one()`.
174 auto aux_generator = Commitment::one() * generator_challenge;
175
176 // Checks poly_degree is greater than zero and a power of two
177 // In the future, we might want to consider if non-powers of two are needed
178 BB_ASSERT((poly_length > 0) && (!(poly_length & (poly_length - 1))) &&
179 "The polynomial degree plus 1 should be positive and a power of two");
180
181 // Step 4.
182 // Set initial vector a to the polynomial monomial coefficients and load vector G
183 // Ensure the polynomial copy is fully-formed
184 auto a_vec = polynomial.full();
185 std::span<Commitment> srs_elements = ck.get_monomial_points();
186 std::vector<Commitment> G_vec_local(poly_length);
187
188 if (poly_length > srs_elements.size()) {
189 throw_or_abort("potential bug: Not enough SRS points for IPA!");
190 }
191
192 // Copy the SRS into a local data structure as we need to mutate this vector for every round
194 poly_length, [&](size_t i) { G_vec_local[i] = srs_elements[i]; }, thread_heuristics::FF_COPY_COST);
195
196 // Step 5.
197 // Compute vector b (vector of the powers of the challenge)
198 OpeningPair<Curve> opening_pair = opening_claim.opening_pair;
199 std::vector<Fr> b_vec(poly_length);
202 [&](size_t start, size_t end, BB_UNUSED size_t chunk_index) {
203 Fr b_power = opening_pair.challenge.pow(start);
204 for (size_t i = start; i < end; i++) {
205 b_vec[i] = b_power;
206 b_power *= opening_pair.challenge;
207 }
208 },
210
211 // Iterate for log(poly_degree) rounds to compute the round commitments.
212
213 // Allocate space for L_i and R_i elements
214 GroupElement L_i;
215 GroupElement R_i;
216 std::size_t round_size = poly_length;
217
218 // Step 6.
219 // Perform IPA reduction rounds
220 for (size_t i = 0; i < log_poly_length; i++) {
221 round_size /= 2;
222 // Run scalar products in parallel
223 auto inner_prods = parallel_for_heuristic(
224 round_size,
225 std::pair{ Fr::zero(), Fr::zero() },
226 [&](size_t j, std::pair<Fr, Fr>& inner_prod_left_right) {
227 // Compute inner_prod_L := < a_vec_lo, b_vec_hi >
228 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
229 // Compute inner_prod_R := < a_vec_hi, b_vec_lo >
230 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
231 },
233 // Sum inner product contributions computed in parallel and unpack the std::pair
234 auto [inner_prod_L, inner_prod_R] = sum_pairs(inner_prods);
235 // Step 6.a (using letters, because doxygen automatically converts the sublist counters to letters :( )
236 // L_i = < a_vec_lo, G_vec_hi > + inner_prod_L * aux_generator
237
238 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), /*size*/ round_size } },
239 { &G_vec_local[round_size], round_size });
240
241 L_i += aux_generator * inner_prod_L;
242
243 // Step 6.b
244 // R_i = < a_vec_hi, G_vec_lo > + inner_prod_R * aux_generator
245 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(round_size), /*size*/ round_size } },
246 { &G_vec_local[0], /*size*/ round_size });
247 R_i += aux_generator * inner_prod_R;
248
249 // Step 6.c
250 // Send L_i and R_i to the verifier
251 std::string index = std::to_string(log_poly_length - i - 1);
252 transcript->send_to_verifier("IPA:L_" + index, Commitment(L_i));
253 transcript->send_to_verifier("IPA:R_" + index, Commitment(R_i));
254
255 // Step 6.d
256 // Receive the challenge from the verifier
257 const Fr round_challenge = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
258
259 if (round_challenge.is_zero()) {
260 throw_or_abort("IPA round challenge is zero");
261 }
262 const Fr round_challenge_inv = round_challenge.invert();
263
264 // Step 6.e
265 // G_vec_new = G_vec_lo + G_vec_hi * round_challenge_inv
266 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
267 std::span{ G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size),
268 G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size * 2) },
269 round_challenge_inv);
270 GroupElement::batch_affine_add(
271 std::span{ G_vec_local.begin(), G_vec_local.begin() + static_cast<std::ptrdiff_t>(round_size) },
272 G_hi_by_inverse_challenge,
273 G_vec_local);
274
275 // Steps 6.e and 6.f
276 // Update the vectors a_vec, b_vec.
277 // a_vec_new = a_vec_lo + a_vec_hi * round_challenge
278 // b_vec_new = b_vec_lo + b_vec_hi * round_challenge_inv
280 round_size,
281 [&](size_t j) {
282 a_vec.at(j) += round_challenge * a_vec[round_size + j];
283 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
284 },
286 }
287
288 // Step 7
289 // Send G_0 to the verifier
290 transcript->send_to_verifier("IPA:G_0", G_vec_local[0]);
291
292 // Step 8
293 // Send a_0 to the verifier
294 transcript->send_to_verifier("IPA:a_0", a_vec[0]);
295 }
307 template <typename Transcript>
308 static void add_claim_to_hash_buffer(const CK& ck,
309 const ProverOpeningClaim<Curve>& opening_claim,
310 const std::shared_ptr<Transcript>& transcript)
311 {
312 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
313
314 // Step 1.
315 // Add the commitment, challenge, and evaluation to the hash buffer.
316 // NOTE:
317 // a. This is a bit inefficient, as the prover otherwise doesn't need this commitment.
318 // However, the effect to performance of this MSM (in practice of size 2^16) is tiny.
319 // b. Note that we add these three pieces of information to the hash buffer, as opposed to
320 // calling the `send_to_verifier` method, as the verifier knows them.
321
322 const auto commitment = ck.commit(polynomial);
323 transcript->add_to_hash_buffer("IPA:commitment", commitment);
324 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
325 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
326 }
327
334 struct TranscriptData {
335 GroupElement C_zero;
336 Fr b_zero;
337 Polynomial<Fr> s_vec;
339 Fr gen_challenge;
340 Commitment G_zero_from_prover;
341 Fr a_zero;
342 };
343
367 template <typename Transcript>
368 static TranscriptData read_transcript_data(const OpeningClaim<Curve>& opening_claim,
369 const std::shared_ptr<Transcript>& transcript)
370 requires(!Curve::is_stdlib_type)
371 {
372 // Step 2.
373 // Receive generator challenge u and compute auxiliary generator
374 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
375 if (generator_challenge.is_zero()) {
376 throw_or_abort("The generator challenge can't be zero");
377 }
378 const Commitment aux_generator = Commitment::one() * generator_challenge;
379
380 // Step 3.
381 // Compute C' = C + f(\beta) ⋅ U, i.e., the _joint_ commitment of f and f(\beta).
382 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
383
384 const auto pippenger_size = 2 * log_poly_length;
385 std::vector<Fr> round_challenges(log_poly_length);
386 std::vector<Commitment> msm_elements(pippenger_size);
387 std::vector<Fr> msm_scalars(pippenger_size);
388
389 // Step 4.
390 // Receive all L_j, R_j and compute round challenges u_j
391 for (size_t i = 0; i < log_poly_length; i++) {
392 std::string index = std::to_string(log_poly_length - i - 1);
393 const auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
394 const auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
395 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
396 if (round_challenges[i].is_zero()) {
397 throw_or_abort("Round challenges can't be zero");
398 }
399 msm_elements[2 * i] = element_L;
400 msm_elements[2 * i + 1] = element_R;
401 }
402
403 std::vector<Fr> round_challenges_inv = round_challenges;
404 Fr::batch_invert(round_challenges_inv);
405
406 // populate msm_scalars.
407 for (size_t i = 0; i < log_poly_length; i++) {
408 msm_scalars[2 * i] = round_challenges_inv[i];
409 msm_scalars[2 * i + 1] = round_challenges[i];
410 }
411
412 // Step 5.
413 // Compute C_zero = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j
414 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>(
415 { 0, { &msm_scalars[0], /*size*/ pippenger_size } }, { &msm_elements[0], /*size*/ pippenger_size });
416 GroupElement C_zero = C_prime + LR_sums;
417
418 // Step 6.
419 // Compute b_zero succinctly
420 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
421
422 // Step 7.
423 // Construct vector s
424 Polynomial<Fr> s_vec(
425 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
426
427 // Receive G_0 and a_0 from prover (advances transcript; G_0 not recomputed here)
428 Commitment G_zero_from_prover = transcript->template receive_from_prover<Commitment>("IPA:G_0");
429 Fr a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
430
431 return { C_zero, b_zero, std::move(s_vec), generator_challenge, G_zero_from_prover, a_zero };
432 }
433
459 static bool reduce_verify_internal_native(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
460 requires(!Curve::is_stdlib_type)
461 {
462 BB_BENCH_NAME("IPA::reduce_verify");
463
464 // Steps 2–7, 9: Process transcript and extract per-proof data (step 1 done by add_claim_to_hash_buffer)
465 auto data = read_transcript_data(opening_claim, transcript);
466
467 // Step 8.
468 // Compute G_s = <s, G> via SRS MSM and verify against prover's G_0
469 std::span<const Commitment> srs_elements = vk.get_monomial_points();
470 if (poly_length > srs_elements.size()) {
471 throw_or_abort("potential bug: Not enough SRS points for IPA!");
472 }
473 Commitment G_zero;
474 {
475 BB_BENCH_NAME("IPA::srs_msm");
476 G_zero =
477 scalar_multiplication::pippenger_unsafe<Curve>(data.s_vec, { &srs_elements[0], /*size*/ poly_length });
478 }
480 G_zero, data.G_zero_from_prover, "G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
481
482 // Step 10.
483 // Compute C_right = a_0 * G_s + a_0 * b_0 * U
484 Commitment aux_generator = Commitment::one() * data.gen_challenge;
485 GroupElement right_hand_side = G_zero * data.a_zero + aux_generator * data.a_zero * data.b_zero;
486
487 // Step 11.
488 // Check if C_right == C_0
489 return (data.C_zero.normalize() == right_hand_side.normalize());
490 }
491
502 template <typename Transcript>
503 static void add_claim_to_hash_buffer(const OpeningClaim<Curve>& opening_claim,
504 const std::shared_ptr<Transcript>& transcript)
505 {
506
507 // Step 1.
508 // Add the commitment, challenge, and evaluation to the hash buffer.
509
510 transcript->add_to_hash_buffer("IPA:commitment", opening_claim.commitment);
511 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
512 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
513 }
531 static VerifierAccumulator reduce_verify_internal_recursive(const OpeningClaim<Curve>& opening_claim,
532 auto& transcript)
533 requires Curve::is_stdlib_type
534 {
535 // Step 1.
536 // Done by `add_claim_to_hash_buffer`.
537
538 // Step 2.
539 // Receive generator challenge u and compute auxiliary generator
540 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
541 typename Curve::Builder* builder = generator_challenge.get_context();
542
543 auto pippenger_size =
544 2 * log_poly_length +
545 2; // the only check we perform will involve an MSM. we make the MSM "as big as possible" for efficiency,
546 // which is why `pippenger_size` is bigger here than in the native verifier.
547 std::vector<Fr> round_challenges(log_poly_length);
548 std::vector<Fr> round_challenges_inv(log_poly_length);
549 std::vector<Commitment> msm_elements(
550 pippenger_size); // L_{k-1}, R_{k-1}, L_{k-2}, ..., L_0, R_0, -G_0, -Commitment::one()
551 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 -
552 // f(\beta)) * generator_challenge
553
554 // Step 3.
555 // Receive all L_i and R_i and prepare for MSM
556 for (size_t i = 0; i < log_poly_length; i++) {
557
558 std::string index = std::to_string(log_poly_length - i - 1);
559 auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
560 auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
561 round_challenges[i] = transcript->template get_challenge<Fr>("IPA:round_challenge_" + index);
562 round_challenges_inv[i] = round_challenges[i].invert();
563
564 msm_elements[2 * i] = element_L;
565 msm_elements[2 * i + 1] = element_R;
566 msm_scalars[2 * i] = round_challenges_inv[i];
567 msm_scalars[2 * i + 1] = round_challenges[i];
568 }
569
570 // Step 4.
571 // Compute b_zero succinctly
572 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
573
574 // Step 5.
575 // Receive G_zero from the prover
576 Commitment G_zero = transcript->template receive_from_prover<Commitment>("IPA:G_0");
577
578 // Step 6.
579 // Receive a_zero from the prover
580 const auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
581
582 // OriginTag false positive: G_zero and a_zero are fully determined once all round challenges are fixed - the
583 // prover must send the correct values or the final relation check fails.
584 if constexpr (Curve::is_stdlib_type) {
585 const auto last_round_tag = round_challenges.back().get_origin_tag();
586 G_zero.set_origin_tag(last_round_tag);
587 const_cast<Fr&>(a_zero).set_origin_tag(last_round_tag);
588 }
589
590 // Step 7.
591 // Compute R = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j - G₀ * a₀ - (f(\beta) - a₀ * b₀) ⋅ U
592 // If everything is correct, then R == -C, as C':= C + f(\beta) ⋅ U
593 msm_elements.emplace_back(-G_zero);
594 msm_elements.emplace_back(-Commitment::one(builder));
595 msm_scalars.emplace_back(a_zero);
596 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation }));
597 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
598 auto neg_commitment = -opening_claim.commitment;
599
600 // the below is the only constraint.
601 ipa_relation.assert_equal(neg_commitment);
602
603 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
604 }
605
617 template <typename Transcript = NativeTranscript>
618 static void compute_opening_proof(const CK& ck,
619 const ProverOpeningClaim<Curve>& opening_claim,
620 const std::shared_ptr<Transcript>& transcript)
621 {
622 add_claim_to_hash_buffer(ck, opening_claim, transcript);
623 compute_opening_proof_internal(ck, opening_claim, transcript);
624 }
625
637 template <typename Transcript = NativeTranscript>
638 static bool reduce_verify(const VK& vk,
639 const OpeningClaim<Curve>& opening_claim,
640 const std::shared_ptr<Transcript>& transcript)
641 requires(!Curve::is_stdlib_type)
642 {
643 add_claim_to_hash_buffer(opening_claim, transcript);
644 return reduce_verify_internal_native(vk, opening_claim, transcript);
645 }
646
665 template <typename Transcript = NativeTranscript>
666 static bool batch_reduce_verify(const VK& vk,
667 const std::vector<OpeningClaim<Curve>>& opening_claims,
668 const std::vector<std::shared_ptr<Transcript>>& transcripts)
669 requires(!Curve::is_stdlib_type)
670 {
671 const size_t num_claims = opening_claims.size();
672 BB_ASSERT(num_claims == transcripts.size());
673 BB_ASSERT(num_claims > 0);
674
675 // Phase 1: Per-proof transcript processing (sequential, each proof is cheap)
676 std::vector<GroupElement> C_zeros(num_claims);
677 std::vector<Fr> a_zeros(num_claims);
678 std::vector<Fr> b_zeros(num_claims);
679 std::vector<Fr> gen_challenges(num_claims);
680 std::vector<Polynomial<Fr>> s_vecs(num_claims);
681
682 for (size_t i = 0; i < num_claims; i++) {
683 add_claim_to_hash_buffer(opening_claims[i], transcripts[i]);
684 auto data = read_transcript_data(opening_claims[i], transcripts[i]);
685 C_zeros[i] = std::move(data.C_zero);
686 b_zeros[i] = data.b_zero;
687 s_vecs[i] = std::move(data.s_vec);
688 gen_challenges[i] = data.gen_challenge;
689 a_zeros[i] = data.a_zero;
690 }
691
692 // Phase 2: Batched computation using random challenge alpha
693 Fr alpha = Fr::random_element();
694 std::vector<Fr> alpha_pows(num_claims);
695 alpha_pows[0] = Fr::one();
696 for (size_t i = 1; i < num_claims; i++) {
697 alpha_pows[i] = alpha_pows[i - 1] * alpha;
698 }
699
700 // Combined s_vec: combined_s[j] = \sum \alpha^i * a_zero_i * s_vec_i[j]
701 Polynomial<Fr> combined_s(poly_length);
702 for (size_t i = 0; i < num_claims; i++) {
703 Fr scalar = alpha_pows[i] * a_zeros[i];
704 combined_s.add_scaled(s_vecs[i], scalar);
705 }
706
707 // Single MSM over combined scalars
708 std::span<const Commitment> srs_elements = vk.get_monomial_points();
709 if (poly_length > srs_elements.size()) {
710 throw_or_abort("potential bug: Not enough SRS points for IPA!");
711 }
712 Commitment G_batch =
713 scalar_multiplication::pippenger_unsafe<Curve>(combined_s, { &srs_elements[0], /*size*/ poly_length });
714
715 // Combined LHS: C_batch = \sum \alpha^i * C_zero_i
716 GroupElement C_batch = C_zeros[0];
717 for (size_t i = 1; i < num_claims; i++) {
718 C_batch = C_batch + C_zeros[i] * alpha_pows[i];
719 }
720
721 // Combined scalar for U terms: bU_scalar = \sum \alpha^i * a_zero_i * b_zero_i * gen_challenge_i
722 Fr bU_scalar = Fr::zero();
723 for (size_t i = 0; i < num_claims; i++) {
724 bU_scalar += alpha_pows[i] * a_zeros[i] * b_zeros[i] * gen_challenges[i];
725 }
726
727 // Check: C_batch == G_batch + bU_scalar * G
728 GroupElement right_hand_side = G_batch + Commitment::one() * bU_scalar;
729 return (C_batch.normalize() == right_hand_side.normalize());
730 }
731
743 static VerifierAccumulator reduce_verify(const OpeningClaim<Curve>& opening_claim, const auto& transcript)
744 requires(Curve::is_stdlib_type)
745 {
746 // The output of `reduce_verify_internal_recursive` consists of a `VerifierAccumulator` and a boolean, recording
747 // the truth value of the last verifier-compatibility check. This simply forgets the boolean and returns the
748 // `VerifierAccumulator`.
749 add_claim_to_hash_buffer(opening_claim, transcript);
750 return reduce_verify_internal_recursive(opening_claim, transcript);
751 }
752
770 static bool full_verify_recursive(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
771 requires Curve::is_stdlib_type
772 {
773 add_claim_to_hash_buffer(opening_claim, transcript);
774 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
775 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
776 auto claimed_G_zero = verifier_accumulator.comm;
777
778 // Construct vector s, whose rth entry is ∏ (u_i)^{-1 * r_i}, where (r_i) is the binary expansion of r. This
779 // is required to _compute_ G_zero (rather than just passively receive G_zero from the Prover).
780 //
781 // We implement a linear-time algorithm to optimally compute this vector
782 // Note: currently requires an extra vector of size
783 // `poly_length / 2` to cache temporaries
784 // this might able to be optimized if we care enough, but the size of this poly shouldn't be large
785 // relative to the builder polynomial sizes
786 std::vector<Fr> s_vec_temporaries(poly_length / 2);
787 std::vector<Fr> s_vec(poly_length);
788
789 Fr* previous_round_s = &s_vec_temporaries[0];
790 Fr* current_round_s = &s_vec[0];
791 // if number of rounds is even we need to swap these so that s_vec always contains the result
792 if constexpr ((log_poly_length & 1) == 0) {
793 std::swap(previous_round_s, current_round_s);
794 }
795 previous_round_s[0] = Fr(1);
796 for (size_t i = 0; i < log_poly_length; ++i) {
797 const size_t round_size = 1 << (i + 1);
798 const Fr round_challenge = round_challenges_inv[i];
799 for (size_t j = 0; j < round_size / 2; ++j) {
800 current_round_s[j * 2] = previous_round_s[j];
801 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
802 }
803 std::swap(current_round_s, previous_round_s);
804 }
805
806 // Compute G_zero
807 // In the native verifier, this uses pippenger. Here we were batch_mul.
808 const std::vector<Commitment> srs_elements = vk.get_monomial_points();
809 Commitment computed_G_zero = Commitment::batch_mul(srs_elements, s_vec);
810 // check the computed G_zero and the claimed G_zero are the same.
811 claimed_G_zero.assert_equal(computed_G_zero);
812 BB_ASSERT_EQ(computed_G_zero.get_value(), claimed_G_zero.get_value(), "G_zero doesn't match received G_zero.");
813
814 bool running_truth_value = verifier_accumulator.running_truth_value;
815 return running_truth_value;
816 }
817
830 static OpeningClaim<Curve> reduce_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim)
831 {
832 // Extract batch_mul arguments from the accumulator
833 const auto& commitments = batch_opening_claim.commitments;
834 const auto& scalars = batch_opening_claim.scalars;
835 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
836 // Compute \f$ C = \sum \text{commitments}_i \cdot \text{scalars}_i \f$
837 GroupElement shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
838 // Output an opening claim, which in practice will be verified by the IPA opening protocol
839 return { { shplonk_eval_challenge, Fr(0) }, shplonk_output_commitment };
840 }
841
850 static bool reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
851 const VK& vk,
852 auto& transcript)
853 requires(!Curve::is_stdlib_type)
854 {
855 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
856 add_claim_to_hash_buffer(opening_claim, transcript);
857 return reduce_verify_internal_native(vk, opening_claim, transcript);
858 }
859
868 static VerifierAccumulator reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
869 [[maybe_unused]] const std::shared_ptr<VK>& vk,
870 auto& transcript)
871 requires(Curve::is_stdlib_type)
872 {
873 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
874 add_claim_to_hash_buffer(opening_claim, transcript);
875 return reduce_verify_internal_recursive(opening_claim, transcript).verifier_accumulator;
876 }
877
886 static Fr evaluate_challenge_poly(const std::vector<Fr>& u_challenges_inv, Fr r)
887 {
888 // Runs the obvious algorithm to compute the product ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.r^{2^{i-1}}) by
889 // remembering the current 2-primary power of r.
890 Fr challenge_poly_eval = 1;
891 Fr r_pow = r;
892 // the loop runs to `log_poly_length - 1` because we don't want to superfluously compute r_pow.sqr() in the last
893 // round.
894 for (size_t i = 0; i < log_poly_length - 1; i++) {
895 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
896 challenge_poly_eval *= (Fr(1) + monomial);
897 r_pow = r_pow.sqr();
898 }
899 // same as the body of the loop, without `r_pow = r_pow.sqr()`
900 Fr monomial = u_challenges_inv[0] * r_pow;
901 challenge_poly_eval *= (Fr(1) + monomial);
902 return challenge_poly_eval;
903 }
904
916 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
917 std::vector<Fr> u_challenges_inv_2,
918 Fr r,
919 Fr alpha)
920 {
921 auto result =
922 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
923 return result;
924 }
925
933 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(const std::span<const bb::fq>& u_challenges_inv)
934 {
935
936 // Construct vector s in linear time.
937 std::vector<bb::fq> s_vec(poly_length, bb::fq::one());
938 std::vector<bb::fq> s_vec_temporaries(poly_length / 2);
939
940 bb::fq* previous_round_s = &s_vec_temporaries[0];
941 bb::fq* current_round_s = &s_vec[0];
942 // if number of rounds is even we need to swap these so that s_vec always contains the result
943 if ((log_poly_length & 1) == 0) {
944 std::swap(previous_round_s, current_round_s);
945 }
946 previous_round_s[0] = bb::fq(1);
947 for (size_t i = 0; i < log_poly_length; ++i) {
948 const size_t round_size = 1 << (i + 1);
949 const bb::fq round_challenge = u_challenges_inv[i];
951 round_size / 2,
952 [&](size_t j) {
953 current_round_s[j * 2] = previous_round_s[j];
954 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
955 },
957 std::swap(current_round_s, previous_round_s);
958 }
959 return { s_vec, poly_length };
960 }
961
972 static Polynomial<bb::fq> create_challenge_poly(const std::vector<bb::fq>& u_challenges_inv_1,
973 const std::vector<bb::fq>& u_challenges_inv_2,
974 bb::fq alpha)
975 {
976 // Always extend each to 1<<log_poly_length length
977 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
978 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
979 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
980 challenge_poly += challenge_poly_1;
981 challenge_poly.add_scaled(challenge_poly_2, alpha);
982 return challenge_poly;
983 }
984
1000 static std::pair<OpeningClaim<Curve>, HonkProof> accumulate(const CommitmentKey<curve::Grumpkin>& ck,
1001 auto& transcript_1,
1002 OpeningClaim<Curve> claim_1,
1003 auto& transcript_2,
1004 OpeningClaim<Curve> claim_2)
1005 requires Curve::is_stdlib_type
1006 {
1007 using NativeCurve = curve::Grumpkin;
1008 // Sanity check that we are not using Grumpkin with MegaCircuitBuilder designed to delegate BN254 ops.
1009 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
1010 // Step 1: Run the partial verifier for each IPA instance
1011 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
1012 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
1013
1014 // Step 2: Generate the challenges by hashing the pairs
1015 UltraStdlibTranscript transcript;
1016 transcript.add_to_hash_buffer("u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
1017 transcript.add_to_hash_buffer("U_1", verifier_accumulator_1.comm);
1018 transcript.add_to_hash_buffer("u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
1019 transcript.add_to_hash_buffer("U_2", verifier_accumulator_2.comm);
1020 auto [alpha, r] = transcript.template get_challenges<Fr>(std::array<std::string, 2>{ "IPA:alpha", "IPA:r" });
1021
1022 // Step 3: Compute the new accumulator
1023 OpeningClaim<Curve> output_claim;
1024 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
1025 output_claim.opening_pair.challenge = r;
1026 // Evaluate the challenge_poly polys at r and linearly combine them with alpha challenge
1027 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
1028 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
1029
1030 // Step 4: Compute the new challenge polynomial natively
1031 std::vector<bb::fq> native_u_challenges_inv_1;
1032 std::vector<bb::fq> native_u_challenges_inv_2;
1033 for (Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
1034 native_u_challenges_inv_1.push_back(bb::fq(u_inv_i.get_value()));
1035 }
1036 for (Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
1037 native_u_challenges_inv_2.push_back(bb::fq(u_inv_i.get_value()));
1038 }
1039
1040 Polynomial<bb::fq> challenge_poly =
1041 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2, bb::fq(alpha.get_value()));
1042
1043 // Compute proof for the claim
1044 auto prover_transcript = std::make_shared<NativeTranscript>();
1045 const OpeningPair<NativeCurve> opening_pair{ bb::fq(output_claim.opening_pair.challenge.get_value()),
1046 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
1047
1048 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
1049 opening_pair.evaluation,
1050 "Opening claim does not hold for challenge polynomial.");
1051
1052 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
1053 ck, { challenge_poly, opening_pair }, prover_transcript);
1054 BB_ASSERT_EQ(challenge_poly.evaluate(bb::fq(output_claim.opening_pair.challenge.get_value())),
1055 bb::fq(output_claim.opening_pair.evaluation.get_value()),
1056 "Opening claim does not hold for challenge polynomial.");
1057
1058 output_claim.opening_pair.evaluation.self_reduce();
1059 return { output_claim, prover_transcript->export_proof() };
1060 }
1061
1062 static std::pair<OpeningClaim<Curve>, HonkProof> create_random_valid_ipa_claim_and_proof(
1064 requires Curve::is_stdlib_type
1065 {
1066 using NativeCurve = curve::Grumpkin;
1067 using Builder = typename Curve::Builder;
1068 using Curve = stdlib::grumpkin<Builder>;
1069 auto ipa_transcript = std::make_shared<NativeTranscript>();
1070 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
1071 size_t n = poly_length;
1072 auto poly = Polynomial<bb::fq>(n);
1073 for (size_t i = 0; i < n; i++) {
1074 poly.at(i) = bb::fq::random_element();
1075 }
1077 bb::fq eval = poly.evaluate(x);
1078 auto commitment = ipa_commitment_key.commit(poly);
1079 const OpeningPair<NativeCurve> opening_pair = { x, eval };
1080 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
1081
1082 auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
1083 auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
1084 auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
1085 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
1086
1087 return { stdlib_opening_claim, ipa_transcript->export_proof() };
1088 }
1089};
1090
1091} // 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:225
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:64
static constexpr bool is_stdlib_type
Definition grumpkin.hpp:68
typename Group::affine_element AffineElement
Definition grumpkin.hpp:65
#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)