95template <
typename Curve_,
size_t log_poly_length = CONST_ECCVM_LOG_N>
class IPA {
110 FRIEND_TEST(IPATest, ChallengesAreZero);
111 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
152 template <
typename Transcript>
153 static void compute_opening_proof_internal(
const CK&
ck,
155 const std::shared_ptr<Transcript>& transcript)
170 const auto commitment =
ck.commit(polynomial);
171 transcript->add_to_hash_buffer(
"IPA:commitment", commitment);
172 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.
opening_pair.challenge);
173 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.
opening_pair.evaluation);
178 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
180 if (generator_challenge.is_zero()) {
186 auto aux_generator = Commitment::one() * generator_challenge;
191 "The polynomial degree plus 1 should be positive and a power of two");
196 auto a_vec = polynomial.
full();
208 G_vec_local[i] = srs_elements[i];
213 OpeningPair<Curve> opening_pair = opening_claim.
opening_pair;
217 [&](
size_t start,
size_t end,
BB_UNUSED size_t chunk_index) {
218 Fr b_power = opening_pair.challenge.
pow(start);
219 for (
size_t i = start; i < end; i++) {
221 b_power *= opening_pair.challenge;
234 for (
size_t i = 0; i < log_poly_length; i++) {
242 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
244 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
247 auto [inner_prod_L, inner_prod_R] =
sum_pairs(inner_prods);
251 L_i = scalar_multiplication::pippenger_unsafe<Curve>({0, {&a_vec.at(0), round_size}},{&G_vec_local[round_size], round_size});
253 L_i += aux_generator * inner_prod_L;
257 R_i = scalar_multiplication::pippenger_unsafe<Curve>({0, {&a_vec.at(round_size), round_size}},{&G_vec_local[0], round_size});
258 R_i += aux_generator * inner_prod_R;
263 transcript->send_to_verifier(
"IPA:L_" + index,
Commitment(L_i));
264 transcript->send_to_verifier(
"IPA:R_" + index,
Commitment(R_i));
268 const Fr round_challenge = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" + index);
270 if (round_challenge.
is_zero()) {
273 const Fr round_challenge_inv = round_challenge.
invert();
277 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
278 std::span{ G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size),
279 G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size * 2) },
280 round_challenge_inv);
281 GroupElement::batch_affine_add(
282 std::span{ G_vec_local.begin(), G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size) },
283 G_hi_by_inverse_challenge,
293 a_vec.at(j) += round_challenge * a_vec[round_size + j];
294 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
300 transcript->send_to_verifier(
"IPA:G_0", G_vec_local[0]);
304 transcript->send_to_verifier(
"IPA:a_0", a_vec[0]);
332 static bool reduce_verify_internal_native(
const VK&
vk,
333 const OpeningClaim<Curve>& opening_claim,
339 transcript->add_to_hash_buffer(
"IPA:commitment", opening_claim.commitment);
340 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.
opening_pair.challenge);
341 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.
opening_pair.evaluation);
345 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
347 if (generator_challenge.
is_zero()) {
351 Commitment aux_generator = Commitment::one() * generator_challenge;
357 auto pippenger_size = 2 * log_poly_length;
358 std::vector<Fr> round_challenges(log_poly_length);
360 std::vector<Commitment> msm_elements(pippenger_size);
362 std::vector<Fr> msm_scalars(pippenger_size);
366 for (
size_t i = 0; i < log_poly_length; i++) {
368 auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" + index);
369 auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" + index);
370 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" + index);
371 if (round_challenges[i].is_zero()) {
374 msm_elements[2 * i] = element_L;
375 msm_elements[2 * i + 1] = element_R;
378 std::vector<Fr> round_challenges_inv = round_challenges;
382 for (
size_t i = 0; i < log_poly_length; i++) {
383 msm_scalars[2 * i] = round_challenges_inv[i];
384 msm_scalars[2 * i + 1] = round_challenges[i];
389 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>({0, {&msm_scalars[0], pippenger_size}},{&msm_elements[0], pippenger_size});
397 for (
size_t i = 0; i < log_poly_length; i++) {
398 b_zero *=
Fr::one() + (round_challenges_inv[log_poly_length - 1 - i] *
404 Polynomial<Fr> s_poly(construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
413 Commitment G_zero = scalar_multiplication::pippenger_unsafe<Curve>(s_poly,{&srs_elements[0],
poly_length});
414 Commitment G_zero_sent = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
415 BB_ASSERT_EQ(G_zero, G_zero_sent,
"G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
419 auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
423 GroupElement right_hand_side = G_zero * a_zero + aux_generator * a_zero * b_zero;
426 return (C_zero.normalize() == right_hand_side.normalize());
441 static VerifierAccumulator reduce_verify_internal_recursive(
const OpeningClaim<Curve>& opening_claim,
447 transcript->add_to_hash_buffer(
"IPA:commitment", opening_claim.commitment);
448 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.
opening_pair.challenge);
449 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.
opening_pair.evaluation);
453 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
454 typename Curve::Builder*
builder = generator_challenge.get_context();
456 auto pippenger_size = 2 * log_poly_length;
457 std::vector<Fr> round_challenges(log_poly_length);
458 std::vector<Fr> round_challenges_inv(log_poly_length);
459 std::vector<Commitment> msm_elements(pippenger_size);
460 std::vector<Fr> msm_scalars(pippenger_size);
465 for (
size_t i = 0; i < log_poly_length; i++) {
468 auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" + index);
469 auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" + index);
470 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" + index);
471 round_challenges_inv[i] = round_challenges[i].invert();
473 msm_elements[2 * i] = element_L;
474 msm_elements[2 * i + 1] = element_R;
475 msm_scalars[2 * i] = round_challenges_inv[i];
476 msm_scalars[2 * i + 1] = round_challenges[i];
486 for (
size_t i = 0; i < log_poly_length; i++) {
488 Fr monomial = round_challenges_inv[log_poly_length - 1 - i] * challenge;
489 b_zero *=
Fr(1) + monomial;
490 if (i != log_poly_length - 1)
492 challenge = challenge.
sqr();
498 Commitment G_zero = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
502 const auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
508 msm_elements.emplace_back(-G_zero);
509 msm_elements.emplace_back(-Commitment::one(
builder));
510 msm_scalars.emplace_back(a_zero);
511 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, {-opening_claim.opening_pair.evaluation}));
512 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
513 auto neg_commitment = -opening_claim.commitment;
514 ipa_relation.assert_equal(neg_commitment);
517 return { round_challenges_inv, G_zero};
531 static void compute_opening_proof(
const CK&
ck,
532 const ProverOpeningClaim<Curve>& opening_claim,
535 compute_opening_proof_internal(
ck, opening_claim, transcript);
549 static bool reduce_verify(
const VK&
vk,
550 const OpeningClaim<Curve>& opening_claim,
551 const auto& transcript)
554 return reduce_verify_internal_native(
vk, opening_claim, transcript);
569 const auto& transcript)
572 return reduce_verify_internal_recursive(opening_claim, transcript);
588 static bool full_verify_recursive(
const VK&
vk,
589 const OpeningClaim<Curve>& opening_claim,
595 transcript->add_to_hash_buffer(
"IPA:commitment", opening_claim.commitment);
596 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.
opening_pair.challenge);
597 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.
opening_pair.evaluation);
601 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
602 typename Curve::Builder*
builder = generator_challenge.get_context();
604 static constexpr size_t pippenger_size = 2 * log_poly_length;
605 std::vector<Fr> round_challenges(log_poly_length);
606 std::vector<Fr> round_challenges_inv(log_poly_length);
607 std::vector<Commitment> msm_elements(pippenger_size);
608 std::vector<Fr> msm_scalars(pippenger_size);
613 for (
size_t i = 0; i < log_poly_length; i++) {
616 auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" + index);
617 auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" + index);
618 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" + index);
619 round_challenges_inv[i] = round_challenges[i].invert();
621 msm_elements[2 * i] = element_L;
622 msm_elements[2 * i + 1] = element_R;
623 msm_scalars[2 * i] = round_challenges_inv[i];
624 msm_scalars[2 * i + 1] = round_challenges[i];
634 for (
size_t i = 0; i < log_poly_length; i++) {
636 Fr monomial = round_challenges_inv[log_poly_length - 1 - i] * challenge;
637 b_zero *=
Fr(1) + monomial;
638 if (i != log_poly_length - 1)
640 challenge = challenge.
sqr();
649 std::vector<Fr> s_vec_temporaries(
poly_length / 2);
652 Fr* previous_round_s = &s_vec_temporaries[0];
653 Fr* current_round_s = &s_vec[0];
655 if constexpr ((log_poly_length & 1) == 0)
657 std::swap(previous_round_s, current_round_s);
659 previous_round_s[0] =
Fr(1);
660 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)
666 current_round_s[j * 2] = previous_round_s[j];
667 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
669 std::swap(current_round_s, previous_round_s);
672 Commitment transcript_G_zero = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
677 Commitment G_zero = Commitment::batch_mul(srs_elements, s_vec);
678 transcript_G_zero.assert_equal(G_zero);
679 BB_ASSERT_EQ(G_zero.get_value(), transcript_G_zero.get_value(),
"G_zero doesn't match received G_zero.");
683 const auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
689 msm_elements.emplace_back(-G_zero);
690 msm_elements.emplace_back(-Commitment::one(
builder));
691 msm_scalars.emplace_back(a_zero);
692 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, {-opening_claim.opening_pair.evaluation}));
693 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
694 auto neg_commitment = -opening_claim.commitment;
695 ipa_relation.assert_equal(neg_commitment);
697 return (ipa_relation.get_value() == -opening_claim.commitment.get_value());
709 static OpeningClaim<Curve> reduce_batch_opening_claim(
710 const BatchOpeningClaim<Curve>& batch_opening_claim)
713 const auto& commitments = batch_opening_claim.commitments;
714 const auto& scalars = batch_opening_claim.scalars;
715 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
719 shplonk_output_commitment =
720 GroupElement::batch_mul(commitments, scalars);
722 shplonk_output_commitment = batch_mul_native(commitments, scalars);
725 return { { shplonk_eval_challenge,
Fr(0) }, shplonk_output_commitment };
736 static bool reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
741 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
742 return reduce_verify_internal_native(
vk, opening_claim, transcript);
753 static VerifierAccumulator reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
758 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
759 return reduce_verify_internal_recursive(opening_claim, transcript);
770 static Fr evaluate_challenge_poly(
const std::vector<Fr>& u_challenges_inv,
Fr r) {
772 Fr challenge_poly_eval = 1;
775 for (
size_t i = 0; i < log_poly_length; i++) {
777 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
779 challenge_poly_eval *= (
Fr(1) + monomial);
782 return challenge_poly_eval;
794 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1, std::vector<Fr> u_challenges_inv_2,
Fr r,
Fr alpha) {
795 auto result = evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
812 bb::fq* previous_round_s = &s_vec_temporaries[0];
813 bb::fq* current_round_s = &s_vec[0];
815 if ((log_poly_length & 1) == 0)
817 std::swap(previous_round_s, current_round_s);
819 previous_round_s[0] =
bb::fq(1);
820 for (
size_t i = 0; i < log_poly_length; ++i)
822 const size_t round_size = 1 << (i + 1);
823 const fq round_challenge = u_challenges_inv[i];
827 current_round_s[j * 2] = previous_round_s[j];
828 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
830 std::swap(current_round_s, previous_round_s);
846 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
847 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
848 challenge_poly += challenge_poly_1;
849 challenge_poly.add_scaled(challenge_poly_2, alpha);
850 return challenge_poly;
865 static std::pair<OpeningClaim<Curve>,
HonkProof> accumulate(
const CommitmentKey<curve::Grumpkin>&
ck,
auto& transcript_1, OpeningClaim<Curve> claim_1,
auto& transcript_2, OpeningClaim<Curve> claim_2)
868 using NativeCurve = curve::Grumpkin;
869 using Builder =
typename Curve::Builder;
875 using StdlibTranscript = BaseTranscript<stdlib::recursion::honk::StdlibTranscriptParams<Builder>>;
877 transcript.send_to_verifier(
"u_challenges_inv_1", pair_1.u_challenges_inv);
878 transcript.send_to_verifier(
"U_1", pair_1.comm);
879 transcript.send_to_verifier(
"u_challenges_inv_2", pair_2.u_challenges_inv);
880 transcript.send_to_verifier(
"U_2", pair_2.comm);
881 auto [alpha, r] = transcript.template get_challenges<Fr>(
"IPA:alpha",
"IPA:r");
884 OpeningClaim<Curve> output_claim;
885 output_claim.commitment = pair_1.comm + pair_2.comm * alpha;
886 output_claim.opening_pair.challenge = r;
888 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(pair_1.u_challenges_inv, pair_2.u_challenges_inv, r, alpha);
893 for (
Fr u_inv_i : pair_1.u_challenges_inv) {
894 native_u_challenges_inv_1.push_back(
bb::fq(u_inv_i.get_value()));
896 for (
Fr u_inv_i : pair_2.u_challenges_inv) {
897 native_u_challenges_inv_2.push_back(
bb::fq(u_inv_i.get_value()));
902 const OpeningPair<NativeCurve> opening_pair{
bb::fq(output_claim.opening_pair.challenge.get_value()),
903 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
904 Polynomial<fq> challenge_poly = create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2,
fq(alpha.get_value()));
906 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge), opening_pair.evaluation,
"Opening claim does not hold for challenge polynomial.");
908 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
ck, { challenge_poly, opening_pair }, prover_transcript);
909 BB_ASSERT_EQ(challenge_poly.evaluate(
fq(output_claim.opening_pair.challenge.get_value())),
fq(output_claim.opening_pair.evaluation.get_value()),
"Opening claim does not hold for challenge polynomial.");
911 output_claim.opening_pair.evaluation.self_reduce();
912 return {output_claim, prover_transcript->export_proof()};
917 using NativeCurve = curve::Grumpkin;
918 using Builder =
typename Curve::Builder;
919 using Curve = stdlib::grumpkin<Builder>;
921 CommitmentKey<NativeCurve> ipa_commitment_key(
poly_length);
924 for (
size_t i = 0; i < n; i++) {
928 fq eval = poly.evaluate(x);
929 auto commitment = ipa_commitment_key.commit(poly);
930 const OpeningPair<NativeCurve> opening_pair = { x, eval };
931 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
933 auto stdlib_comm = Curve::Group::from_witness(&
builder, commitment);
934 auto stdlib_x = Curve::ScalarField::from_witness(&
builder, x);
935 auto stdlib_eval = Curve::ScalarField::from_witness(&
builder, eval);
936 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
938 return {stdlib_opening_claim, ipa_transcript->export_proof()};