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
22#include <cstddef>
23#include <numeric>
24#include <string>
25#include <utility>
26#include <vector>
27
28namespace bb {
87template <typename Curve_, size_t log_poly_length = CONST_ECCVM_LOG_N> class IPA {
88 public:
89 using Curve = Curve_;
90 using Fr = typename Curve::ScalarField;
91 using GroupElement = typename Curve::Element;
95
96 // records the `u_challenges_inv`, the Pederson commitment to the `h` -polynomial, a.k.a. the challenge
97 // polynomial, given as ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.X^{2^{i-1}}), and the running truth value of the IPA
98 // accumulation claim.
100
109 std::vector<Fr> u_challenges_inv; // IPA round challenge inverses; define the challenge polynomial
110 Commitment claimed_commitment; // prover-claimed G_0 = <challenge_poly(u), SRS>, verified later
111 bool relation_succeeded = false; // cheap IPA group relation (excludes the deferred SRS-MSM)
112 };
113
114 // Compute the length of the vector of coefficients of a polynomial being opened.
115 static constexpr size_t poly_length = 1UL << log_poly_length;
116 static_assert(log_poly_length >= 1, "log_poly_length must be at least 1");
117
118// These allow access to internal functions so that we can never use a mock transcript unless it's fuzzing or testing of
119// IPA specifically
120#ifdef IPA_TEST
121 FRIEND_TEST(IPATest, ChallengesAreZero);
122 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
123#endif
124#ifdef IPA_FUZZ_TEST
125 friend class ProxyCaller;
126#endif
127
136 {
137 std::vector<Fr> squares(log_poly_length);
138 Fr square = r;
139 for (size_t i = 0; i < log_poly_length; ++i) {
140 squares[i] = square;
141 square = square.sqr();
142 }
143 return GateSeparatorPolynomial<Fr>::compute_beta_products(squares, log_poly_length);
144 }
145
182 template <typename Transcript>
183 static void compute_opening_proof_internal(const CK& ck,
184 const ProverOpeningClaim<Curve>& opening_claim,
185 const std::shared_ptr<Transcript>& transcript)
186 {
187 bb::Polynomial<Fr> b_vec = powers_tensor(opening_claim.opening_pair.challenge);
188 compute_inner_product_proof_internal(ck, opening_claim.polynomial, std::move(b_vec), transcript);
189 }
190
191 template <typename Transcript>
192 static void compute_inner_product_proof_internal(const CK& ck,
193 const bb::Polynomial<Fr>& witness,
194 bb::Polynomial<Fr> b_vec,
195 const std::shared_ptr<Transcript>& transcript)
196 {
197 BB_BENCH_NAME("IPA::compute_opening_proof");
198
199 // Step 1.
200 // Done in `add_claim_to_hash_buffer`.
201 // Step 2.
202 // Receive challenge for the auxiliary generator.
203 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
204 if (generator_challenge.is_zero()) {
205 throw_or_abort("The generator challenge can't be zero");
206 }
207
208 // Step 3.
209 // Compute auxiliary generator U, which is used to bind together the inner product claim and the commitment.
210 // This yields the binding property because we assume it is computationally difficult to find a linear relation
211 // between the CRS and `Commitment::one()`.
212 const auto aux_generator = Commitment::one() * generator_challenge;
213
214 // Checks poly_degree is greater than zero and a power of two.
215 // In the future, we might want to consider if non-powers of two are needed.
216 BB_ASSERT((poly_length > 0) && (!(poly_length & (poly_length - 1))),
217 "The polynomial degree plus 1 should be positive and a power of two");
218 // Step 4.
219 // Set initial vector a to the witness coefficients and load vector G.
220 // Ensure the witness copy is fully formed.
221 auto a_vec = witness.full();
222 // This IPA is compile-time fixed to `poly_length = 2^log_poly_length`; the reduction loop
223 // runs `log_poly_length` rounds over `round_size = poly_length`. A witness of any other
224 // length makes the rounds index past `a_vec` (heap corruption), so require an exact match.
225 BB_ASSERT_EQ(a_vec.size(),
227 "IPA witness length must equal the compile-time poly_length (2^log_poly_length)");
228 std::span<Commitment> srs_elements = ck.get_monomial_points();
229 if (poly_length > srs_elements.size()) {
230 throw_or_abort("potential bug: Not enough SRS points for IPA!");
231 }
232
233 // Copy the SRS into a local data structure as we need to mutate this vector for every round.
234 std::vector<Commitment> G_vec_local(poly_length);
237 [&](size_t idx) {
238 BB_BENCH_TRACY_NAME("IPA::copy_srs");
239 G_vec_local[idx] = srs_elements[idx];
240 },
242
243 // Step 5.
244 // The caller supplies vector b: powers of the challenge for classical IPA, or another tensor for TripleIPA.
245 // Allocate space for L_i and R_i elements.
246 GroupElement L_i;
247 GroupElement R_i;
248 std::size_t round_size = poly_length;
249
250 // The verifier's IPA fold is the classical G' = G_lo + u⁻¹·G_hi. The prover computes an
251 // equivalent rescaled fold G' = u·G_lo + G_hi, so each round's batch_mul multiplies by the
252 // raw 127-bit challenge (short-scalar schedule) instead of the full-width inverse u⁻¹. This
253 // scales the running SRS by ∏u and the running witness by its inverse; the final G_0 and a_0
254 // are unscaled below, so the transcript — and the verifier — are byte-identical to the
255 // classical fold.
256 Fr challenge_product = Fr::one();
257 // Step 6. IPA reduction rounds, processed in fused pairs: the first round defers its SRS
258 // fold and the second round's L/R run against the pre-fold SRS, then both folds apply at
259 // once via batch_two_round_fold. The proof is identical to folding every round.
260 // See ipa/ELEMENT_IMPL_FOLD.md ("Use in IPA").
261
262 // Cross inner products are the U-coefficients of the L/R commitments. The caller supplies b_vec, so this
263 // same IPA core works for both classical evaluation vectors and TripleIPA tensors.
264 // `half` is the lo/hi split point of the current (folded) a_vec/b_vec.
265 const auto compute_inner_prods = [&](size_t half) {
266 auto inner_prods = parallel_for_heuristic(
267 half,
268 std::pair{ Fr::zero(), Fr::zero() },
269 [&](size_t idx, std::pair<Fr, Fr>& inner_prod_left_right) {
270 BB_BENCH_TRACY_NAME("IPA::inner_product");
271 // Compute inner_prod_L := < a_vec_lo, b_vec_hi >
272 inner_prod_left_right.first += a_vec[idx] * b_vec[half + idx];
273 // Compute inner_prod_R := < a_vec_hi, b_vec_lo >
274 inner_prod_left_right.second += a_vec[half + idx] * b_vec[idx];
275 },
277 // Sum inner product contributions computed in parallel and unpack the std::pair.
278 return sum_pairs(inner_prods);
279 };
280
281 // Steps 6.c and 6.d for one round: send L_i/R_i, receive the round challenge, and
282 // fold a_vec/b_vec (steps 6.f and 6.g). Returns the round challenge.
283 const auto finish_round = [&](size_t round_idx, size_t half) {
284 const std::string index = std::to_string(log_poly_length - round_idx - 1);
285 transcript->send_to_verifier("IPA:L_" + index, Commitment(L_i));
286 transcript->send_to_verifier("IPA:R_" + index, Commitment(R_i));
287
288 const Fr round_challenge = transcript->template get_short_challenge<Fr>("IPA:round_challenge_" + index);
289 if (round_challenge.is_zero()) {
290 throw_or_abort("IPA round challenge is zero");
291 }
292 const Fr round_challenge_inv = round_challenge.invert();
293 challenge_product *= round_challenge;
294
295 // a_vec_new = a_vec_lo * round_challenge_inv + a_vec_hi
296 // b_vec_new = b_vec_lo * round_challenge + b_vec_hi
298 half,
299 [&](size_t idx) {
300 BB_BENCH_TRACY_NAME("IPA::fold_vecs");
301 a_vec.at(idx) = round_challenge_inv * a_vec[idx] + a_vec[half + idx];
302 b_vec.at(idx) = round_challenge * b_vec[idx] + b_vec[half + idx];
303 },
305 return round_challenge;
306 };
307
308 size_t round_idx = 0;
309 while (round_idx < log_poly_length) {
310 const size_t half = round_size / 2;
311
312 // === First round of the pair (or a lone final round). ===
313 auto [inner_prod_L, inner_prod_R] = compute_inner_prods(half);
314
315 // Step 6.a
316 // L_i = < a_vec_lo, G_vec_hi > + inner_prod_L * aux_generator
317 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), half } },
318 { &G_vec_local[half], half });
319 L_i += aux_generator * inner_prod_L;
320
321 // Step 6.b
322 // R_i = < a_vec_hi, G_vec_lo > + inner_prod_R * aux_generator
323 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(half), half } },
324 { &G_vec_local[0], half });
325 R_i += aux_generator * inner_prod_R;
326
327 const Fr u1 = finish_round(round_idx, half);
328
329 if (round_idx + 1 == log_poly_length) {
330 // Lone final round of the odd round count: a single point remains (half == 1), so
331 // fold it directly. G_vec_new = u1·G_vec_lo + G_vec_hi.
332 G_vec_local[0] = Commitment(G_vec_local[0] * u1 + G_vec_local[1]);
333 round_size = half;
334 ++round_idx;
335 continue;
336 }
337
338 // === Second round of the pair: L/R against the pre-fold SRS. ===
339 // With H = u1·G_lo + G_hi deferred, the half-size SRS splits over quarters of G:
340 // H_lo[i] = u1·G[i] + G[i+2q], H_hi[i] = u1·G[i+q] + G[i+3q], q = round_size/4,
341 // so < s, H_lo > = < u1·s, G[0:q] > + < s, G[2q:3q] > and similarly for H_hi.
342 const size_t quarter = half / 2;
343 auto [inner_prod_L2, inner_prod_R2] = compute_inner_prods(quarter);
344
345 // u1·a_vec[0:2q], reused by both L and R.
346 std::vector<Fr> a_scaled(half);
348 half, [&](size_t idx) { a_scaled[idx] = u1 * a_vec[idx]; }, thread_heuristics::FF_MULTIPLICATION_COST);
349
350 // L_i = < a_vec_lo, H_hi > + inner_prod_L * aux_generator
351 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_scaled[0], quarter } },
352 { &G_vec_local[quarter], quarter });
353 L_i += scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), quarter } },
354 { &G_vec_local[3 * quarter], quarter });
355 L_i += aux_generator * inner_prod_L2;
356
357 // R_i = < a_vec_hi, H_lo > + inner_prod_R * aux_generator
358 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_scaled[quarter], quarter } },
359 { &G_vec_local[0], quarter });
360 R_i += scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(quarter), quarter } },
361 { &G_vec_local[2 * quarter], quarter });
362 R_i += aux_generator * inner_prod_R2;
363
364 const Fr u2 = finish_round(round_idx + 1, quarter);
365
366 // Step 6.e, fused: G_vec_new[i] = u1·u2·G[i] + u1·G[i+q] + u2·G[i+2q] + G[i+3q].
367 // Round challenges are 127-bit limbs (transcript split_challenge); batch_two_round_fold
368 // BB_ASSERTs that precondition.
369 auto G_folded = GroupElement::batch_two_round_fold(
370 std::span<const Commitment>{ G_vec_local.data(), 4 * quarter }, u1, u2);
372 quarter, [&](size_t idx) { G_vec_local[idx] = G_folded[idx]; }, thread_heuristics::FF_COPY_COST * 2);
373
374 round_size = quarter;
375 round_idx += 2;
376 }
377
378 // Step 7.
379 // Unscale the rescaled fold outputs back to the classical transcript values: the running SRS
380 // carries a factor of challenge_product = ∏ u_i and the running witness its inverse.
381 const Fr challenge_product_inv = challenge_product.invert();
382 // Send G_0 to the verifier.
383 transcript->send_to_verifier("IPA:G_0", Commitment(G_vec_local[0] * challenge_product_inv));
384 // Step 8.
385 // Send a_0 to the verifier.
386 transcript->send_to_verifier("IPA:a_0", a_vec[0] * challenge_product);
387 }
399 template <typename Transcript>
400 static void add_claim_to_hash_buffer(const CK& ck,
401 const ProverOpeningClaim<Curve>& opening_claim,
402 const std::shared_ptr<Transcript>& transcript)
403 {
404 const bb::Polynomial<Fr>& polynomial = opening_claim.polynomial;
405
406 // Step 1.
407 // Add the commitment, challenge, and evaluation to the hash buffer.
408 // NOTE:
409 // a. This is a bit inefficient, as the prover otherwise doesn't need this commitment.
410 // However, the effect to performance of this MSM (in practice of size 2^16) is tiny.
411 // b. Note that we add these three pieces of information to the hash buffer, as opposed to
412 // calling the `send_to_verifier` method, as the verifier knows them.
413
414 const auto commitment = ck.commit(polynomial);
415 transcript->add_to_hash_buffer("IPA:commitment", commitment);
416 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
417 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
418 }
419
426 struct TranscriptData {
427 GroupElement C_zero;
428 Fr b_zero;
429 Polynomial<Fr> s_vec;
431 Fr gen_challenge;
432 Commitment G_zero_from_prover;
433 Fr a_zero;
434 std::vector<Fr> round_challenges_inv;
435 };
436
460 template <typename Transcript, typename BZeroFromRounds>
461 static TranscriptData read_inner_product_transcript_data(const Commitment& commitment,
462 const Fr& evaluation,
463 BZeroFromRounds&& b_zero_from_round_challenges,
464 const std::shared_ptr<Transcript>& transcript)
465 requires(!Curve::is_stdlib_type)
466 {
467 // Step 2.
468 // Receive generator challenge u and compute auxiliary generator.
469 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
470 if (generator_challenge.is_zero()) {
471 throw_or_abort("The generator challenge can't be zero");
472 }
473 const Commitment aux_generator = Commitment::one() * generator_challenge;
474
475 // Step 3.
476 // C' is the joint commitment C + vU to the committed vector and the claimed inner product value v.
477 const GroupElement C_prime = commitment + (aux_generator * evaluation);
478
479 const auto pippenger_size = 2 * log_poly_length;
480 std::vector<Fr> round_challenges(log_poly_length);
481 std::vector<Commitment> msm_elements(pippenger_size);
482 std::vector<Fr> msm_scalars(pippenger_size);
483
484 // Step 4.
485 // Receive all L_j, R_j and compute round challenges u_j.
486 for (size_t round_idx = 0; round_idx < log_poly_length; ++round_idx) {
487 const std::string index = std::to_string(log_poly_length - round_idx - 1);
488 const auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
489 const auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
490 round_challenges[round_idx] = transcript->template get_short_challenge<Fr>("IPA:round_challenge_" + index);
491 if (round_challenges[round_idx].is_zero()) {
492 throw_or_abort("Round challenges can't be zero");
493 }
494 msm_elements[2 * round_idx] = element_L;
495 msm_elements[2 * round_idx + 1] = element_R;
496 }
497
498 std::vector<Fr> round_challenges_inv = round_challenges;
499 Fr::batch_invert(round_challenges_inv);
500
501 // Populate msm_scalars.
502 for (size_t round_idx = 0; round_idx < log_poly_length; ++round_idx) {
503 msm_scalars[2 * round_idx] = round_challenges_inv[round_idx];
504 msm_scalars[2 * round_idx + 1] = round_challenges[round_idx];
505 }
506
507 // Step 5.
508 // Compute C_zero = C' + ∑_{j ∈ [k]} u_j^{-1}L_j + ∑_{j ∈ [k]} u_jR_j
509 GroupElement LR_sums = scalar_multiplication::pippenger<Curve>(
510 { 0, { &msm_scalars[0], /*size*/ pippenger_size } }, { &msm_elements[0], /*size*/ pippenger_size });
511 GroupElement C_zero = C_prime + LR_sums;
512
513 // Step 6.
514 // Compute b_zero succinctly.
515 const Fr b_zero = b_zero_from_round_challenges(std::span<const Fr>(round_challenges_inv));
516 // Step 7.
517 // Construct vector s.
518 bb::Polynomial<Fr> s_vec(construct_poly_from_u_challenges_inv(
519 std::span<const Fr>(round_challenges_inv).subspan(0, log_poly_length)));
520
521 // Receive G_0 and a_0 from prover (advances transcript; G_0 not recomputed here).
522 Commitment G_zero_from_prover = transcript->template receive_from_prover<Commitment>("IPA:G_0");
523 Fr a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
524
525 return { C_zero,
526 b_zero,
527 std::move(s_vec),
528 generator_challenge,
529 G_zero_from_prover,
530 a_zero,
531 std::move(round_challenges_inv) };
532 }
533
534 template <typename Transcript>
535 static TranscriptData read_transcript_data(const OpeningClaim<Curve>& opening_claim,
536 const std::shared_ptr<Transcript>& transcript)
537 requires(!Curve::is_stdlib_type)
538 {
539 return read_inner_product_transcript_data(
540 opening_claim.commitment,
541 opening_claim.opening_pair.evaluation,
542 [&](std::span<const Fr> round_challenges_inv) {
543 return evaluate_challenge_poly(
544 std::vector<Fr>(round_challenges_inv.begin(), round_challenges_inv.end()),
545 opening_claim.opening_pair.challenge);
546 },
547 transcript);
548 }
549
575 static bool reduce_verify_internal_native(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
576 requires(!Curve::is_stdlib_type)
577 {
578 BB_BENCH_NAME("IPA::reduce_verify");
579
580 // Steps 2–7, 9: Process transcript and extract per-proof data (step 1 done by add_claim_to_hash_buffer)
581 auto data = read_transcript_data(opening_claim, transcript);
582
583 // Step 8.
584 // Compute G_s = <s, G> via SRS MSM and verify against prover's G_0
585 std::span<const Commitment> srs_elements = vk.get_monomial_points();
586 if (poly_length > srs_elements.size()) {
587 throw_or_abort("potential bug: Not enough SRS points for IPA!");
588 }
589 Commitment G_zero;
590 {
591 BB_BENCH_NAME("IPA::srs_msm");
592 G_zero =
593 scalar_multiplication::pippenger_unsafe<Curve>(data.s_vec, { &srs_elements[0], /*size*/ poly_length });
594 }
595 if (G_zero != data.G_zero_from_prover) {
596 info("IPA verification failed: G_0 mismatch");
597 return false;
598 }
599
600 // Step 10.
601 // Compute C_right = a_0 * G_s + a_0 * b_0 * U
602 Commitment aux_generator = Commitment::one() * data.gen_challenge;
603 GroupElement right_hand_side = G_zero * data.a_zero + aux_generator * data.a_zero * data.b_zero;
604
605 // Step 11.
606 // Check if C_right == C_0
607 return (data.C_zero.normalize() == right_hand_side.normalize());
608 }
609
620 template <typename Transcript>
621 static void add_claim_to_hash_buffer(const OpeningClaim<Curve>& opening_claim,
622 const std::shared_ptr<Transcript>& transcript)
623 {
624
625 // Step 1.
626 // Add the commitment, challenge, and evaluation to the hash buffer.
627
628 transcript->add_to_hash_buffer("IPA:commitment", opening_claim.commitment);
629 transcript->add_to_hash_buffer("IPA:challenge", opening_claim.opening_pair.challenge);
630 transcript->add_to_hash_buffer("IPA:evaluation", opening_claim.opening_pair.evaluation);
631 }
649 template <typename BZeroFromRounds>
650 static VerifierAccumulator reduce_verify_inner_product_recursive(const Commitment& commitment,
651 const Fr& evaluation,
652 BZeroFromRounds&& b_zero_from_round_challenges,
653 auto& transcript)
654 requires Curve::is_stdlib_type
655 {
656 // Step 2.
657 // Receive generator challenge u and compute auxiliary generator.
658 const Fr generator_challenge = transcript->template get_challenge<Fr>("IPA:generator_challenge");
659 typename Curve::Builder* builder = generator_challenge.get_context();
660
661 auto pippenger_size = 2 * log_poly_length + 2;
662 std::vector<Fr> round_challenges(log_poly_length);
663 std::vector<Fr> round_challenges_inv(log_poly_length);
664
665 // Step 3.
666 // Receive all L_i and R_i and prepare for MSM.
667 // L_{k-1}, R_{k-1}, ..., L_0, R_0, -G_0, -Commitment::one().
668 std::vector<Commitment> msm_elements(pippenger_size);
669 // u_{k-1}^{-1}, u_{k-1}, ..., u_0^{-1}, u_0, a_0, (a_0 b_0 - v) * generator_challenge.
670 std::vector<Fr> msm_scalars(pippenger_size);
671
672 for (size_t round_idx = 0; round_idx < log_poly_length; ++round_idx) {
673 const std::string index = std::to_string(log_poly_length - round_idx - 1);
674 auto element_L = transcript->template receive_from_prover<Commitment>("IPA:L_" + index);
675 auto element_R = transcript->template receive_from_prover<Commitment>("IPA:R_" + index);
676 round_challenges[round_idx] = transcript->template get_short_challenge<Fr>("IPA:round_challenge_" + index);
677 round_challenges_inv[round_idx] = round_challenges[round_idx].invert();
678
679 msm_elements[2 * round_idx] = element_L;
680 msm_elements[2 * round_idx + 1] = element_R;
681 msm_scalars[2 * round_idx] = round_challenges_inv[round_idx];
682 msm_scalars[2 * round_idx + 1] = round_challenges[round_idx];
683 }
684
685 // Step 4.
686 // Compute b_zero succinctly.
687 Fr b_zero = b_zero_from_round_challenges(std::span<const Fr>(round_challenges_inv));
688 // Step 5.
689 // Receive G_zero from the prover.
690 Commitment G_zero = transcript->template receive_from_prover<Commitment>("IPA:G_0");
691 // Step 6.
692 // Receive a_zero from the prover.
693 auto a_zero = transcript->template receive_from_prover<Fr>("IPA:a_0");
694
695 // OriginTag false positive: G_zero and a_zero are fully determined once all round challenges are fixed; the
696 // prover must send the correct values or the final relation check fails.
697 if constexpr (Curve::is_stdlib_type) {
698 const auto last_round_tag = round_challenges.back().get_origin_tag();
699 G_zero.set_origin_tag(last_round_tag);
700 a_zero.set_origin_tag(last_round_tag);
701 }
702
703 // Step 7.
704 // R = ∑(u_j^{-1}L_j + u_jR_j) - a_0 G_0 - (a_0 b_0 - v)U. Correctness is R == -C.
705 msm_elements[2 * log_poly_length] = -G_zero;
706 msm_elements[(2 * log_poly_length) + 1] = -Commitment::one(builder);
707 msm_scalars[2 * log_poly_length] = a_zero;
708 msm_scalars[(2 * log_poly_length) + 1] = generator_challenge * a_zero.madd(b_zero, { -evaluation });
709 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
710 auto neg_commitment = -commitment;
711 ipa_relation.assert_equal(neg_commitment);
712
713 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -commitment.get_value() };
714 }
715
716 static VerifierAccumulator reduce_verify_internal_recursive(const OpeningClaim<Curve>& opening_claim,
717 auto& transcript)
718 requires Curve::is_stdlib_type
719 {
720 return reduce_verify_inner_product_recursive(
721 opening_claim.commitment,
722 opening_claim.opening_pair.evaluation,
723 [&](std::span<const Fr> round_challenges_inv) {
724 return evaluate_challenge_poly(
725 std::vector<Fr>(round_challenges_inv.begin(), round_challenges_inv.end()),
726 opening_claim.opening_pair.challenge);
727 },
728 transcript);
729 }
730
742 template <typename Transcript = NativeTranscript>
743 static void compute_opening_proof(const CK& ck,
744 const ProverOpeningClaim<Curve>& opening_claim,
745 const std::shared_ptr<Transcript>& transcript)
746 {
747 add_claim_to_hash_buffer(ck, opening_claim, transcript);
748 compute_opening_proof_internal(ck, opening_claim, transcript);
749 }
750
762 template <typename Transcript = NativeTranscript>
763 static bool reduce_verify(const VK& vk,
764 const OpeningClaim<Curve>& opening_claim,
765 const std::shared_ptr<Transcript>& transcript)
766 requires(!Curve::is_stdlib_type)
767 {
768 add_claim_to_hash_buffer(opening_claim, transcript);
769 return reduce_verify_internal_native(vk, opening_claim, transcript);
770 }
771
795 template <typename Transcript = NativeTranscript>
796 static bool batch_reduce_verify(const VK& vk,
797 const std::vector<OpeningClaim<Curve>>& opening_claims,
798 const std::vector<std::shared_ptr<Transcript>>& transcripts)
799 requires(!Curve::is_stdlib_type)
800 {
801 const size_t num_claims = opening_claims.size();
802 if (num_claims != transcripts.size()) {
803 info("IPA batch verification failed: claims/transcripts size mismatch");
804 return false;
805 }
806 if (num_claims == 0) {
807 info("IPA batch verification failed: no claims provided");
808 return false;
809 }
810
811 // Phase 1: Per-proof transcript processing (sequential, each proof is cheap)
812 std::vector<GroupElement> C_zeros(num_claims);
813 std::vector<Fr> a_zeros(num_claims);
814 std::vector<Fr> b_zeros(num_claims);
815 std::vector<Fr> gen_challenges(num_claims);
816 std::vector<Commitment> G_zeros_from_prover(num_claims);
817 std::vector<Polynomial<Fr>> s_vecs(num_claims);
818
819 for (size_t i = 0; i < num_claims; i++) {
820 add_claim_to_hash_buffer(opening_claims[i], transcripts[i]);
821 auto data = read_transcript_data(opening_claims[i], transcripts[i]);
822 C_zeros[i] = std::move(data.C_zero);
823 b_zeros[i] = data.b_zero;
824 s_vecs[i] = std::move(data.s_vec);
825 gen_challenges[i] = data.gen_challenge;
826 G_zeros_from_prover[i] = data.G_zero_from_prover;
827 a_zeros[i] = data.a_zero;
828 }
829
830 // Phase 2: Batched computation using random challenges alpha, beta and gamma.
831 // alpha batches the IPA relations across claims, beta batches the G_0 binding checks, and gamma folds
832 // the G_0 binding into the main relation so the whole batch costs a single IPA commitment (one SRS MSM).
833 Fr alpha = Fr::random_element();
834 std::vector<Fr> alpha_pows(num_claims);
835 alpha_pows[0] = Fr::one();
836 for (size_t i = 1; i < num_claims; i++) {
837 alpha_pows[i] = alpha_pows[i - 1] * alpha;
838 }
839 Fr beta = Fr::random_element();
840 std::vector<Fr> beta_pows(num_claims);
841 beta_pows[0] = Fr::one();
842 for (size_t i = 1; i < num_claims; i++) {
843 beta_pows[i] = beta_pows[i - 1] * beta;
844 }
845 Fr gamma = Fr::random_element();
846
847 std::span<const Commitment> srs_elements = vk.get_monomial_points();
848 if (poly_length > srs_elements.size()) {
849 throw_or_abort("potential bug: Not enough SRS points for IPA!");
850 }
851
852 // The batch verifier enforces two families of equations over the SRS:
853 // (1) main IPA relation: C_batch == <\sum_i alpha^i a_i s_i, SRS> + bU_scalar * G
854 // (2) G_0 binding: \sum_i beta^i G_0_i == <\sum_i beta^i s_i, SRS>
855 // The single-proof verifier checks G_0 == <s_vec, SRS>; (2) enforces the same condition across the
856 // batch. Both right-hand sides are SRS MSMs, so we random-linear-combine them with gamma to collapse
857 // both into one IPA commitment:
858 // <gamma * (\sum_i beta^i s_i) - (\sum_i alpha^i a_i s_i), SRS> + C_batch
859 // == gamma * (\sum_i beta^i G_0_i) + bU_scalar * G
860 // A prover that violates either family passes the folded check only for a gamma-measure-zero set.
861 Polynomial<Fr> combined_msm_scalars(poly_length);
862 for (size_t i = 0; i < num_claims; i++) {
863 combined_msm_scalars.add_scaled(s_vecs[i], gamma * beta_pows[i] - alpha_pows[i] * a_zeros[i]);
864 }
865 Commitment batched_commitment = scalar_multiplication::pippenger_unsafe<Curve>(
866 combined_msm_scalars, { &srs_elements[0], /*size*/ poly_length });
867
868 // beta-weighted sum of the prover-supplied G_0 values (gamma is applied in the final check).
869 GroupElement prover_g_zero_batch = G_zeros_from_prover[0] * beta_pows[0];
870 for (size_t i = 1; i < num_claims; i++) {
871 prover_g_zero_batch += G_zeros_from_prover[i] * beta_pows[i];
872 }
873
874 // Batched claim commitment: C_batch = \sum \alpha^i * C_zero_i
875 GroupElement C_batch = C_zeros[0];
876 for (size_t i = 1; i < num_claims; i++) {
877 C_batch = C_batch + C_zeros[i] * alpha_pows[i];
878 }
879
880 // Combined scalar for U terms: bU_scalar = \sum \alpha^i * a_zero_i * b_zero_i * gen_challenge_i
881 Fr bU_scalar = Fr::zero();
882 for (size_t i = 0; i < num_claims; i++) {
883 bU_scalar += alpha_pows[i] * a_zeros[i] * b_zeros[i] * gen_challenges[i];
884 }
885
886 // Single folded check: the IPA relation and the G_0 binding both hold iff this holds (up to the
887 // negligible gamma-collision probability).
888 GroupElement left_hand_side = batched_commitment + C_batch;
889 GroupElement right_hand_side = prover_g_zero_batch * gamma + Commitment::one() * bU_scalar;
890 return (left_hand_side.normalize() == right_hand_side.normalize());
891 }
892
904 static VerifierAccumulator reduce_verify(const OpeningClaim<Curve>& opening_claim, const auto& transcript)
905 requires(Curve::is_stdlib_type)
906 {
907 // The output of `reduce_verify_internal_recursive` consists of a `VerifierAccumulator` and a boolean, recording
908 // the truth value of the last verifier-compatibility check. This simply forgets the boolean and returns the
909 // `VerifierAccumulator`.
910 add_claim_to_hash_buffer(opening_claim, transcript);
911 return reduce_verify_internal_recursive(opening_claim, transcript);
912 }
913
931 static bool full_verify_recursive(const VK& vk, const OpeningClaim<Curve>& opening_claim, auto& transcript)
932 requires Curve::is_stdlib_type
933 {
934 // Check SRS size up front before any circuit construction
935 if (vk.get_monomial_points().size() < poly_length) {
936 throw_or_abort("IPA recursive verification: not enough SRS points (need " + std::to_string(poly_length) +
937 ", have " + std::to_string(vk.get_monomial_points().size()) + ")");
938 }
939
940 add_claim_to_hash_buffer(opening_claim, transcript);
941 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
942 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
943 auto claimed_G_zero = verifier_accumulator.comm;
944
945 // Construct vector s, whose rth entry is ∏ (u_i)^{-1 * r_i}, where (r_i) is the binary expansion of r. This
946 // is required to _compute_ G_zero (rather than just passively receive G_zero from the Prover).
947 //
948 // We implement a linear-time algorithm to optimally compute this vector
949 // Note: currently requires an extra vector of size
950 // `poly_length / 2` to cache temporaries
951 // this might able to be optimized if we care enough, but the size of this poly shouldn't be large
952 // relative to the builder polynomial sizes
953 std::vector<Fr> s_vec_temporaries(poly_length / 2);
954 std::vector<Fr> s_vec(poly_length);
955
956 Fr* previous_round_s = &s_vec_temporaries[0];
957 Fr* current_round_s = &s_vec[0];
958 // if number of rounds is even we need to swap these so that s_vec always contains the result
959 if constexpr ((log_poly_length & 1) == 0) {
960 std::swap(previous_round_s, current_round_s);
961 }
962 previous_round_s[0] = Fr(1);
963 for (size_t i = 0; i < log_poly_length; ++i) {
964 const size_t round_size = 1 << (i + 1);
965 const Fr round_challenge = round_challenges_inv[i];
966 for (size_t j = 0; j < round_size / 2; ++j) {
967 current_round_s[j * 2] = previous_round_s[j];
968 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
969 }
970 std::swap(current_round_s, previous_round_s);
971 }
972
973 // Compute G_zero
974 // In the native verifier, this uses pippenger. Here we use fixed_batch_mul since all SRS points are
975 // circuit constants, which uses plookup tables instead of ROM tables and is significantly cheaper.
976 // We use 8-bit tables (table_bits=8, 32 rounds) to minimise gate count. However, with N=32768 SRS points
977 // and 8-bit tables, the total table rows = 32768 × 256 = 2^23 exactly. The 5 mandatory overhead rows
978 // (NUM_DISABLED_ROWS_IN_SUMCHECK=4, NUM_ZERO_ROWS=1) push the total to 2^23+5, forcing dyadic_size = 2^24.
979 // To stay within 2^23 we handle the first SRS point separately using operator*.
980 std::vector<Commitment> srs_elements = vk.get_monomial_points();
981 srs_elements.resize(poly_length);
982 std::vector<Commitment> remaining_srs(srs_elements.begin() + 1, srs_elements.end());
983 std::vector<Fr> remaining_s(s_vec.begin() + 1, s_vec.end());
984 Commitment first_term = srs_elements[0] * s_vec[0];
985 Commitment remaining_term = Commitment::fixed_batch_mul(remaining_srs, remaining_s, {}, /*table_bits=*/8);
986 Commitment computed_G_zero = first_term + remaining_term;
987 // check the computed G_zero and the claimed G_zero are the same.
988 // The circuit constraint enforces correctness; mismatched witnesses will produce an unsatisfiable circuit.
989 claimed_G_zero.assert_equal(computed_G_zero, "G_zero doesn't match received G_zero.");
990
991 bool running_truth_value = verifier_accumulator.running_truth_value;
992 return running_truth_value;
993 }
994
1003 static Fr evaluate_challenge_poly(const std::vector<Fr>& u_challenges_inv, Fr r)
1004 {
1005 // Runs the obvious algorithm to compute the product ∏_{i ∈ [k]} (1 + u_{len-i}^{-1}.r^{2^{i-1}}) by
1006 // remembering the current 2-primary power of r.
1007 Fr challenge_poly_eval = 1;
1008 Fr r_pow = r;
1009 // the loop runs to `log_poly_length - 1` because we don't want to superfluously compute r_pow.sqr() in the last
1010 // round.
1011 for (size_t i = 0; i < log_poly_length - 1; i++) {
1012 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
1013 challenge_poly_eval *= (Fr(1) + monomial);
1014 r_pow = r_pow.sqr();
1015 }
1016 // same as the body of the loop, without `r_pow = r_pow.sqr()`
1017 Fr monomial = u_challenges_inv[0] * r_pow;
1018 challenge_poly_eval *= (Fr(1) + monomial);
1019 return challenge_poly_eval;
1020 }
1021
1033 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
1034 std::vector<Fr> u_challenges_inv_2,
1035 Fr r,
1036 Fr alpha)
1037 {
1038 auto result =
1039 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
1040 return result;
1041 }
1042
1050 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(const std::span<const bb::fq>& u_challenges_inv)
1051 {
1052 // Each round consumes exactly one inverse challenge; a short span would read out of bounds below.
1053 BB_ASSERT_EQ(u_challenges_inv.size(), log_poly_length);
1054
1055 // Construct vector s in linear time.
1056 std::vector<bb::fq> s_vec(poly_length, bb::fq::one());
1057 std::vector<bb::fq> s_vec_temporaries(poly_length / 2);
1058
1059 bb::fq* previous_round_s = &s_vec_temporaries[0];
1060 bb::fq* current_round_s = &s_vec[0];
1061 // if number of rounds is even we need to swap these so that s_vec always contains the result
1062 if ((log_poly_length & 1) == 0) {
1063 std::swap(previous_round_s, current_round_s);
1064 }
1065 previous_round_s[0] = bb::fq(1);
1066 for (size_t i = 0; i < log_poly_length; ++i) {
1067 const size_t round_size = 1 << (i + 1);
1068 const bb::fq round_challenge = u_challenges_inv[i];
1070 round_size / 2,
1071 [&](size_t j) {
1072 current_round_s[j * 2] = previous_round_s[j];
1073 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
1074 },
1076 std::swap(current_round_s, previous_round_s);
1077 }
1078 return { s_vec, poly_length };
1079 }
1080
1091 static Polynomial<bb::fq> create_challenge_poly(const std::vector<bb::fq>& u_challenges_inv_1,
1092 const std::vector<bb::fq>& u_challenges_inv_2,
1093 bb::fq alpha)
1094 {
1095 // Always extend each to 1<<log_poly_length length
1096 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
1097 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
1098 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
1099 challenge_poly += challenge_poly_1;
1100 challenge_poly.add_scaled(challenge_poly_2, alpha);
1101 return challenge_poly;
1102 }
1103
1118 static std::pair<OpeningClaim<Curve>, HonkProof> accumulate(const CommitmentKey<curve::Grumpkin>& ck,
1119 auto& transcript_1,
1120 OpeningClaim<Curve> claim_1,
1121 auto& transcript_2,
1122 OpeningClaim<Curve> claim_2)
1123 requires Curve::is_stdlib_type
1124 {
1125 // Step 1: Run the partial verifier for each IPA instance. The two reductions are sequenced explicitly (not
1126 // via argument evaluation order) so the circuit gate ordering is deterministic.
1127 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
1128 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
1129 return accumulate(ck, std::move(verifier_accumulator_1), std::move(verifier_accumulator_2));
1130 }
1131
1137 static std::pair<OpeningClaim<Curve>, HonkProof> prove_challenge_poly_opening(
1138 const CommitmentKey<curve::Grumpkin>& ck,
1139 OpeningClaim<Curve> output_claim,
1140 const Polynomial<bb::fq>& challenge_poly)
1141 requires Curve::is_stdlib_type
1142 {
1143 using NativeCurve = curve::Grumpkin;
1144 auto prover_transcript = std::make_shared<NativeTranscript>();
1145 const OpeningPair<NativeCurve> opening_pair{ bb::fq(output_claim.opening_pair.challenge.get_value()),
1146 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
1147 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
1148 opening_pair.evaluation,
1149 "Opening claim does not hold for challenge polynomial.");
1150 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
1151 ck, { challenge_poly, opening_pair }, prover_transcript);
1152
1153 output_claim.opening_pair.evaluation.self_reduce();
1154 return { output_claim, prover_transcript->export_proof() };
1155 }
1156
1166 static std::pair<OpeningClaim<Curve>, HonkProof> accumulate(const CommitmentKey<curve::Grumpkin>& ck,
1167 VerifierAccumulator verifier_accumulator_1,
1168 VerifierAccumulator verifier_accumulator_2)
1169 requires Curve::is_stdlib_type
1170 {
1171 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
1172
1173 // Step 2: Generate the challenges by hashing the pairs.
1174 // Rehash: bind both accumulators before squeezing the folding challenges.
1175 UltraStdlibTranscript transcript;
1176 transcript.add_to_hash_buffer("u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
1177 transcript.add_to_hash_buffer("U_1", verifier_accumulator_1.comm);
1178 transcript.add_to_hash_buffer("u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
1179 transcript.add_to_hash_buffer("U_2", verifier_accumulator_2.comm);
1180 auto [alpha, r] = transcript.template get_challenges<Fr>(std::array<std::string, 2>{ "IPA:alpha", "IPA:r" });
1181
1182 // Step 3: Compute the new accumulator.
1183 OpeningClaim<Curve> output_claim;
1184 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
1185 output_claim.opening_pair.challenge = r;
1186 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
1187 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
1188
1189 // Step 4: Compute the new challenge polynomial natively.
1190 std::vector<bb::fq> native_u_challenges_inv_1;
1191 std::vector<bb::fq> native_u_challenges_inv_2;
1192 for (Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
1193 native_u_challenges_inv_1.push_back(bb::fq(u_inv_i.get_value()));
1194 }
1195 for (Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
1196 native_u_challenges_inv_2.push_back(bb::fq(u_inv_i.get_value()));
1197 }
1198 Polynomial<bb::fq> challenge_poly =
1199 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2, bb::fq(alpha.get_value()));
1200
1201 return prove_challenge_poly_opening(ck, std::move(output_claim), challenge_poly);
1202 }
1203
1211 static std::pair<OpeningClaim<Curve>, HonkProof> prove_accumulator_claim(const CommitmentKey<curve::Grumpkin>& ck,
1212 VerifierAccumulator verifier_accumulator)
1213 requires Curve::is_stdlib_type
1214 {
1215 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
1216
1217 UltraStdlibTranscript transcript;
1218 transcript.add_to_hash_buffer("u_challenges_inv", verifier_accumulator.u_challenges_inv);
1219 transcript.add_to_hash_buffer("U", verifier_accumulator.comm);
1220 auto r = transcript.template get_challenge<Fr>("IPA:r");
1221
1222 OpeningClaim<Curve> output_claim;
1223 output_claim.commitment = verifier_accumulator.comm;
1224 output_claim.opening_pair.challenge = r;
1225 output_claim.opening_pair.evaluation = evaluate_challenge_poly(verifier_accumulator.u_challenges_inv, r);
1226
1227 std::vector<bb::fq> native_u_challenges_inv;
1228 for (Fr u_inv_i : verifier_accumulator.u_challenges_inv) {
1229 native_u_challenges_inv.push_back(bb::fq(u_inv_i.get_value()));
1230 }
1231 Polynomial<bb::fq> challenge_poly =
1232 construct_poly_from_u_challenges_inv(std::span<const bb::fq>(native_u_challenges_inv));
1233
1234 return prove_challenge_poly_opening(ck, std::move(output_claim), challenge_poly);
1235 }
1236
1240 static bool verify_accumulator(const VK& vk, const NativeAccumulator& accumulator)
1241 requires(!Curve::is_stdlib_type)
1242 {
1243 // Single-accumulator discharge is the B=1 case of the batched check (rho^0 = 1 makes the rehash and
1244 // rho-scaling a no-op on the result), so route through the batched implementation to avoid a second copy
1245 // of the deferred-G_0 SRS-MSM.
1246 return batch_verify_accumulators(vk, std::span<const NativeAccumulator>(&accumulator, 1));
1247 }
1248
1255 static bool batch_verify_accumulators(const VK& vk, std::span<const NativeAccumulator> accumulators)
1256 requires(!Curve::is_stdlib_type)
1257 {
1258 BB_BENCH_NAME("IPA::batch_verify_accumulators");
1259 if (accumulators.empty()) {
1260 return true;
1261 }
1262 std::span<const Commitment> srs_elements = vk.get_monomial_points();
1263 if (poly_length > srs_elements.size()) {
1264 throw_or_abort("potential bug: Not enough SRS points for IPA::batch_verify_accumulators!");
1265 }
1266
1267 // Rehash: bind every (u_challenges_inv, claimed G_0) before squeezing the batching challenge.
1268 NativeTranscript transcript;
1269 for (size_t idx = 0; idx < accumulators.size(); ++idx) {
1270 transcript.add_to_hash_buffer("IPA:batch_u_" + std::to_string(idx), accumulators[idx].u_challenges_inv);
1271 transcript.add_to_hash_buffer("IPA:batch_U_" + std::to_string(idx), accumulators[idx].claimed_commitment);
1272 }
1273 const Fr rho = transcript.template get_challenge<Fr>("IPA:batch_rho");
1274
1275 Polynomial<Fr> combined_challenge_poly(poly_length);
1276 GroupElement combined_commitment = GroupElement::infinity();
1277 Fr rho_power = Fr::one();
1278 bool all_relations_succeeded = true;
1279 for (const auto& accumulator : accumulators) {
1280 Polynomial<Fr> challenge_poly(
1281 construct_poly_from_u_challenges_inv(std::span<const Fr>(accumulator.u_challenges_inv)));
1282 combined_challenge_poly.add_scaled(challenge_poly, rho_power);
1283 combined_commitment += GroupElement(accumulator.claimed_commitment) * rho_power;
1284 all_relations_succeeded = all_relations_succeeded && accumulator.relation_succeeded;
1285 rho_power *= rho;
1286 }
1287
1288 GroupElement combined_G_zero;
1289 {
1290 BB_BENCH_NAME("IPA::srs_msm");
1291 combined_G_zero = scalar_multiplication::pippenger_unsafe<Curve>(combined_challenge_poly,
1292 { &srs_elements[0], poly_length });
1293 }
1294 if (combined_G_zero.normalize() != combined_commitment.normalize()) {
1295 info("IPA batch verification failed: combined G_0 mismatch");
1296 return false;
1297 }
1298 return all_relations_succeeded;
1299 }
1300
1301 static std::pair<OpeningClaim<Curve>, HonkProof> create_random_valid_ipa_claim_and_proof(
1303 requires Curve::is_stdlib_type
1304 {
1305 using NativeCurve = curve::Grumpkin;
1306 using Builder = typename Curve::Builder;
1307 using Curve = stdlib::grumpkin<Builder>;
1308 auto ipa_transcript = std::make_shared<NativeTranscript>();
1309 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
1310 size_t n = poly_length;
1311 auto poly = Polynomial<bb::fq>(n);
1312 for (size_t i = 0; i < n; i++) {
1313 poly.at(i) = bb::fq::random_element();
1314 }
1316 bb::fq eval = poly.evaluate(x);
1317 auto commitment = ipa_commitment_key.commit(poly);
1318 const OpeningPair<NativeCurve> opening_pair = { x, eval };
1319 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
1320
1321 auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
1322 auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
1323 auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
1324 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
1325
1326 return { stdlib_opening_claim, ipa_transcript->export_proof() };
1327 }
1328};
1329
1330} // 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:87
typename Curve::Element GroupElement
Definition ipa.hpp:91
CommitmentKey< Curve > CK
Definition ipa.hpp:93
static constexpr size_t poly_length
Definition ipa.hpp:115
stdlib::recursion::honk::IpaAccumulator< Curve > VerifierAccumulator
Definition ipa.hpp:99
VerifierCommitmentKey< Curve > VK
Definition ipa.hpp:94
typename Curve::ScalarField Fr
Definition ipa.hpp:90
typename Curve::AffineElement Commitment
Definition ipa.hpp:92
Curve_ Curve
Definition ipa.hpp:89
static bb::Polynomial< Fr > powers_tensor(const Fr &r)
The power tensor (1, r, r^2, ..., r^{poly_length-1}), the IPA b-vector for a univariate opening.
Definition ipa.hpp:135
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
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
AluTraceBuilder builder
Definition alu.test.cpp:124
AffineElement * accumulator
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:172
BaseTranscript< FrCodec, bb::crypto::Poseidon2< bb::crypto::Poseidon2Bn254ScalarFieldParams > > NativeTranscript
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
std::byte * data
static BB_PROFILE Polynomial< FF > compute_beta_products(const std::vector< FF > &betas, const size_t log_num_monomials, const FF &scaling_factor=FF(1))
Given compute for .
Deferred native IPA verification state — the native counterpart of VerifierAccumulator.
Definition ipa.hpp:108
std::vector< Fr > u_challenges_inv
Definition ipa.hpp:109
Commitment claimed_commitment
Definition ipa.hpp:110
static constexpr field one()
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)
VectorField result