93template <
typename Curve_,
size_t log_poly_length = CONST_ECCVM_LOG_N>
class IPA {
113 FRIEND_TEST(IPATest, ChallengesAreZero);
114 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
156 template <
typename Transcript>
157 static void compute_opening_proof_internal(
const CK&
ck,
159 const std::shared_ptr<Transcript>& transcript)
168 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
170 if (generator_challenge.is_zero()) {
181 auto aux_generator = Commitment::one() * generator_challenge;
186 "The polynomial degree plus 1 should be positive and a power of two");
191 auto a_vec = polynomial.
full();
205 OpeningPair<Curve> opening_pair = opening_claim.
opening_pair;
209 [&](
size_t start,
size_t end,
BB_UNUSED size_t chunk_index) {
210 Fr b_power = opening_pair.challenge.
pow(start);
211 for (
size_t i = start; i < end; i++) {
213 b_power *= opening_pair.challenge;
227 for (
size_t i = 0; i < log_poly_length; i++) {
235 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
237 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
241 auto [inner_prod_L, inner_prod_R] =
sum_pairs(inner_prods);
245 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), round_size } },
246 { &G_vec_local[round_size], round_size });
248 L_i += aux_generator * inner_prod_L;
252 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(round_size), round_size } },
253 { &G_vec_local[0], round_size });
254 R_i += aux_generator * inner_prod_R;
259 transcript->send_to_verifier(
"IPA:L_" + index,
Commitment(L_i));
260 transcript->send_to_verifier(
"IPA:R_" + index,
Commitment(R_i));
264 const Fr round_challenge = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" + index);
266 if (round_challenge.
is_zero()) {
269 const Fr round_challenge_inv = round_challenge.
invert();
273 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
274 std::span{ G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size),
275 G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size * 2) },
276 round_challenge_inv);
277 GroupElement::batch_affine_add(
278 std::span{ G_vec_local.begin(), G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size) },
279 G_hi_by_inverse_challenge,
289 a_vec.at(j) += round_challenge * a_vec[round_size + j];
290 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
297 transcript->send_to_verifier(
"IPA:G_0", G_vec_local[0]);
301 transcript->send_to_verifier(
"IPA:a_0", a_vec[0]);
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)
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);
360 static bool reduce_verify_internal_native(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
368 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
370 if (generator_challenge.
is_zero()) {
374 const Commitment aux_generator = Commitment::one() * generator_challenge;
378 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
380 const auto pippenger_size = 2 * log_poly_length;
381 std::vector<Fr> round_challenges(log_poly_length);
383 std::vector<Commitment> msm_elements(pippenger_size);
385 std::vector<Fr> msm_scalars(pippenger_size);
389 for (
size_t i = 0; i < log_poly_length; i++) {
391 const auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" + index);
392 const auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" + index);
393 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" + index);
394 if (round_challenges[i].is_zero()) {
397 msm_elements[2 * i] = element_L;
398 msm_elements[2 * i + 1] = element_R;
401 std::vector<Fr> round_challenges_inv = round_challenges;
405 for (
size_t i = 0; i < log_poly_length; i++) {
406 msm_scalars[2 * i] = round_challenges_inv[i];
407 msm_scalars[2 * i + 1] = round_challenges[i];
412 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>(
413 { 0, { &msm_scalars[0], pippenger_size } }, { &msm_elements[0], pippenger_size });
418 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
423 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
433 scalar_multiplication::pippenger_unsafe<Curve>(s_vec, { &srs_elements[0],
poly_length });
434 Commitment G_zero_sent = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
435 BB_ASSERT_EQ(G_zero, G_zero_sent,
"G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
439 auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
444 GroupElement right_hand_side = G_zero * a_zero + aux_generator * a_zero * b_zero;
447 return (C_zero.normalize() == right_hand_side.normalize());
460 template <
typename Transcript>
461 static void add_claim_to_hash_buffer(
const OpeningClaim<Curve>& opening_claim,
462 const std::shared_ptr<Transcript>& transcript)
468 transcript->add_to_hash_buffer(
"IPA:commitment", opening_claim.commitment);
469 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.opening_pair.challenge);
470 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.opening_pair.evaluation);
489 static VerifierAccumulator reduce_verify_internal_recursive(
const OpeningClaim<Curve>& opening_claim,
498 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
499 typename Curve::Builder*
builder = generator_challenge.get_context();
501 auto pippenger_size =
502 2 * log_poly_length +
505 std::vector<Fr> round_challenges(log_poly_length);
506 std::vector<Fr> round_challenges_inv(log_poly_length);
507 std::vector<Commitment> msm_elements(
509 std::vector<Fr> msm_scalars(pippenger_size);
514 for (
size_t i = 0; i < log_poly_length; i++) {
517 auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" + index);
518 auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" + index);
519 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" + index);
520 round_challenges_inv[i] = round_challenges[i].invert();
522 msm_elements[2 * i] = element_L;
523 msm_elements[2 * i + 1] = element_R;
524 msm_scalars[2 * i] = round_challenges_inv[i];
525 msm_scalars[2 * i + 1] = round_challenges[i];
530 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
534 Commitment G_zero = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
538 const auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
543 msm_elements.emplace_back(-G_zero);
544 msm_elements.emplace_back(-Commitment::one(
builder));
545 msm_scalars.emplace_back(a_zero);
546 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation }));
547 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
548 auto neg_commitment = -opening_claim.commitment;
551 ipa_relation.assert_equal(neg_commitment);
553 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
567 template <
typename Transcript = NativeTranscript>
568 static void compute_opening_proof(
const CK&
ck,
569 const ProverOpeningClaim<Curve>& opening_claim,
570 const std::shared_ptr<Transcript>& transcript)
572 add_claim_to_hash_buffer(
ck, opening_claim, transcript);
573 compute_opening_proof_internal(
ck, opening_claim, transcript);
587 template <
typename Transcript = NativeTranscript>
588 static bool reduce_verify(
const VK&
vk,
589 const OpeningClaim<Curve>& opening_claim,
590 const std::shared_ptr<Transcript>& transcript)
593 add_claim_to_hash_buffer(opening_claim, transcript);
594 return reduce_verify_internal_native(
vk, opening_claim, transcript);
608 static VerifierAccumulator reduce_verify(
const OpeningClaim<Curve>& opening_claim,
const auto& transcript)
614 add_claim_to_hash_buffer(opening_claim, transcript);
615 return reduce_verify_internal_recursive(opening_claim, transcript);
635 static bool full_verify_recursive(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
638 add_claim_to_hash_buffer(opening_claim, transcript);
639 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
640 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
641 auto claimed_G_zero = verifier_accumulator.comm;
651 std::vector<Fr> s_vec_temporaries(
poly_length / 2);
654 Fr* previous_round_s = &s_vec_temporaries[0];
655 Fr* current_round_s = &s_vec[0];
657 if constexpr ((log_poly_length & 1) == 0) {
658 std::swap(previous_round_s, current_round_s);
660 previous_round_s[0] =
Fr(1);
661 for (
size_t i = 0; i < log_poly_length; ++i) {
662 const size_t round_size = 1 << (i + 1);
663 const Fr round_challenge = round_challenges_inv[i];
664 for (
size_t j = 0; j < round_size / 2; ++j) {
665 current_round_s[j * 2] = previous_round_s[j];
666 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
668 std::swap(current_round_s, previous_round_s);
674 Commitment computed_G_zero = Commitment::batch_mul(srs_elements, s_vec);
676 claimed_G_zero.assert_equal(computed_G_zero);
677 BB_ASSERT_EQ(computed_G_zero.get_value(), claimed_G_zero.get_value(),
"G_zero doesn't match received G_zero.");
679 bool running_truth_value = verifier_accumulator.running_truth_value;
680 return running_truth_value;
695 static OpeningClaim<Curve> reduce_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim)
698 const auto& commitments = batch_opening_claim.commitments;
699 const auto& scalars = batch_opening_claim.scalars;
700 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
704 shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
706 shplonk_output_commitment = batch_mul_native(commitments, scalars);
709 return { { shplonk_eval_challenge,
Fr(0) }, shplonk_output_commitment };
720 static bool reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
725 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
726 add_claim_to_hash_buffer(opening_claim, transcript);
727 return reduce_verify_internal_native(
vk, opening_claim, transcript);
738 static VerifierAccumulator reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
743 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
744 add_claim_to_hash_buffer(opening_claim, transcript);
745 return reduce_verify_internal_recursive(opening_claim, transcript).verifier_accumulator;
756 static Fr evaluate_challenge_poly(
const std::vector<Fr>& u_challenges_inv,
Fr r)
760 Fr challenge_poly_eval = 1;
764 for (
size_t i = 0; i < log_poly_length - 1; i++) {
765 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
766 challenge_poly_eval *= (
Fr(1) + monomial);
770 Fr monomial = u_challenges_inv[0] * r_pow;
771 challenge_poly_eval *= (
Fr(1) + monomial);
772 return challenge_poly_eval;
786 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
787 std::vector<Fr> u_challenges_inv_2,
792 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
810 bb::fq* previous_round_s = &s_vec_temporaries[0];
811 bb::fq* current_round_s = &s_vec[0];
813 if ((log_poly_length & 1) == 0) {
814 std::swap(previous_round_s, current_round_s);
816 previous_round_s[0] =
bb::fq(1);
817 for (
size_t i = 0; i < log_poly_length; ++i) {
818 const size_t round_size = 1 << (i + 1);
819 const bb::fq round_challenge = u_challenges_inv[i];
823 current_round_s[j * 2] = previous_round_s[j];
824 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
827 std::swap(current_round_s, previous_round_s);
848 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
849 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
850 challenge_poly += challenge_poly_1;
851 challenge_poly.add_scaled(challenge_poly_2, alpha);
852 return challenge_poly;
872 OpeningClaim<Curve> claim_1,
874 OpeningClaim<Curve> claim_2)
877 using NativeCurve = curve::Grumpkin;
879 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
886 transcript.add_to_hash_buffer(
"u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
887 transcript.add_to_hash_buffer(
"U_1", verifier_accumulator_1.comm);
888 transcript.add_to_hash_buffer(
"u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
889 transcript.add_to_hash_buffer(
"U_2", verifier_accumulator_2.comm);
890 auto [alpha, r] = transcript.template get_challenges<Fr>(
"IPA:alpha",
"IPA:r");
893 OpeningClaim<Curve> output_claim;
894 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
895 output_claim.opening_pair.challenge = r;
897 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
898 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
903 for (
Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
904 native_u_challenges_inv_1.push_back(
bb::fq(u_inv_i.get_value()));
906 for (
Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
907 native_u_challenges_inv_2.push_back(
bb::fq(u_inv_i.get_value()));
911 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2,
bb::fq(alpha.get_value()));
915 const OpeningPair<NativeCurve> opening_pair{
bb::fq(output_claim.opening_pair.challenge.get_value()),
916 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
918 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
919 opening_pair.evaluation,
920 "Opening claim does not hold for challenge polynomial.");
922 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
923 ck, { challenge_poly, opening_pair }, prover_transcript);
924 BB_ASSERT_EQ(challenge_poly.evaluate(
bb::fq(output_claim.opening_pair.challenge.get_value())),
925 bb::fq(output_claim.opening_pair.evaluation.get_value()),
926 "Opening claim does not hold for challenge polynomial.");
928 output_claim.opening_pair.evaluation.self_reduce();
929 return { output_claim, prover_transcript->export_proof() };
936 using NativeCurve = curve::Grumpkin;
937 using Builder =
typename Curve::Builder;
938 using Curve = stdlib::grumpkin<Builder>;
940 CommitmentKey<NativeCurve> ipa_commitment_key(
poly_length);
943 for (
size_t i = 0; i < n; i++) {
947 bb::fq eval = poly.evaluate(x);
948 auto commitment = ipa_commitment_key.commit(poly);
949 const OpeningPair<NativeCurve> opening_pair = { x, eval };
950 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
952 auto stdlib_comm = Curve::Group::from_witness(&
builder, commitment);
953 auto stdlib_x = Curve::ScalarField::from_witness(&
builder, x);
954 auto stdlib_eval = Curve::ScalarField::from_witness(&
builder, eval);
955 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
957 return { stdlib_opening_claim, ipa_transcript->export_proof() };