92template <
typename Curve_,
size_t log_poly_length = CONST_ECCVM_LOG_N>
class IPA {
112 FRIEND_TEST(IPATest, ChallengesAreZero);
113 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
155 template <
typename Transcript>
156 static void compute_opening_proof_internal(
const CK&
ck,
158 const std::shared_ptr<Transcript>& transcript)
167 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
169 if (generator_challenge.is_zero()) {
180 auto aux_generator = Commitment::one() * generator_challenge;
185 "The polynomial degree plus 1 should be positive and a power of two");
190 auto a_vec = polynomial.
full();
204 OpeningPair<Curve> opening_pair = opening_claim.
opening_pair;
208 [&](
size_t start,
size_t end,
BB_UNUSED size_t chunk_index) {
209 Fr b_power = opening_pair.challenge.
pow(start);
210 for (
size_t i = start; i < end; i++) {
212 b_power *= opening_pair.challenge;
226 for (
size_t i = 0; i < log_poly_length; i++) {
234 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
236 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
240 auto [inner_prod_L, inner_prod_R] =
sum_pairs(inner_prods);
244 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), round_size } },
245 { &G_vec_local[round_size], round_size });
247 L_i += aux_generator * inner_prod_L;
251 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(round_size), round_size } },
252 { &G_vec_local[0], round_size });
253 R_i += aux_generator * inner_prod_R;
263 const Fr round_challenge = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
265 if (round_challenge.
is_zero()) {
268 const Fr round_challenge_inv = round_challenge.
invert();
272 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
273 std::span{ G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size),
274 G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size * 2) },
275 round_challenge_inv);
276 GroupElement::batch_affine_add(
277 std::span{ G_vec_local.begin(), G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size) },
278 G_hi_by_inverse_challenge,
288 a_vec.at(j) += round_challenge * a_vec[round_size + j];
289 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
296 transcript->send_to_verifier(
"IPA:G_0", G_vec_local[0]);
300 transcript->send_to_verifier(
"IPA:a_0", a_vec[0]);
313 template <
typename Transcript>
314 static void add_claim_to_hash_buffer(
const CK&
ck,
315 const ProverOpeningClaim<Curve>& opening_claim,
316 const std::shared_ptr<Transcript>& transcript)
328 const auto commitment =
ck.commit(polynomial);
329 transcript->add_to_hash_buffer(
"IPA:commitment", commitment);
330 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.opening_pair.challenge);
331 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.opening_pair.evaluation);
359 static bool reduce_verify_internal_native(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
367 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
369 if (generator_challenge.
is_zero()) {
373 const Commitment aux_generator = Commitment::one() * generator_challenge;
377 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
379 const auto pippenger_size = 2 * log_poly_length;
380 std::vector<Fr> round_challenges(log_poly_length);
382 std::vector<Commitment> msm_elements(pippenger_size);
384 std::vector<Fr> msm_scalars(pippenger_size);
388 for (
size_t i = 0; i < log_poly_length; i++) {
390 const auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" +
index);
391 const auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" +
index);
392 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
393 if (round_challenges[i].is_zero()) {
396 msm_elements[2 * i] = element_L;
397 msm_elements[2 * i + 1] = element_R;
400 std::vector<Fr> round_challenges_inv = round_challenges;
404 for (
size_t i = 0; i < log_poly_length; i++) {
405 msm_scalars[2 * i] = round_challenges_inv[i];
406 msm_scalars[2 * i + 1] = round_challenges[i];
411 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>(
412 { 0, { &msm_scalars[0], pippenger_size } }, { &msm_elements[0], pippenger_size });
417 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
421 Polynomial<Fr> s_vec(
422 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
432 scalar_multiplication::pippenger_unsafe<Curve>(s_vec, { &srs_elements[0],
poly_length });
433 Commitment G_zero_sent = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
434 BB_ASSERT_EQ(G_zero, G_zero_sent,
"G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
438 auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
443 GroupElement right_hand_side = G_zero * a_zero + aux_generator * a_zero * b_zero;
446 return (C_zero.normalize() == right_hand_side.normalize());
459 template <
typename Transcript>
460 static void add_claim_to_hash_buffer(
const OpeningClaim<Curve>& opening_claim,
461 const std::shared_ptr<Transcript>& transcript)
467 transcript->add_to_hash_buffer(
"IPA:commitment", opening_claim.commitment);
468 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.opening_pair.challenge);
469 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.opening_pair.evaluation);
488 static VerifierAccumulator reduce_verify_internal_recursive(
const OpeningClaim<Curve>& opening_claim,
497 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
498 typename Curve::Builder*
builder = generator_challenge.get_context();
500 auto pippenger_size =
501 2 * log_poly_length +
504 std::vector<Fr> round_challenges(log_poly_length);
505 std::vector<Fr> round_challenges_inv(log_poly_length);
506 std::vector<Commitment> msm_elements(
508 std::vector<Fr> msm_scalars(pippenger_size);
513 for (
size_t i = 0; i < log_poly_length; i++) {
516 auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" +
index);
517 auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" +
index);
518 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
519 round_challenges_inv[i] = round_challenges[i].invert();
521 msm_elements[2 * i] = element_L;
522 msm_elements[2 * i + 1] = element_R;
523 msm_scalars[2 * i] = round_challenges_inv[i];
524 msm_scalars[2 * i + 1] = round_challenges[i];
529 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
533 Commitment G_zero = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
537 const auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
542 msm_elements.emplace_back(-G_zero);
543 msm_elements.emplace_back(-Commitment::one(
builder));
544 msm_scalars.emplace_back(a_zero);
545 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation }));
546 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
547 auto neg_commitment = -opening_claim.commitment;
550 ipa_relation.assert_equal(neg_commitment);
552 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
566 template <
typename Transcript = NativeTranscript>
567 static void compute_opening_proof(
const CK&
ck,
568 const ProverOpeningClaim<Curve>& opening_claim,
569 const std::shared_ptr<Transcript>& transcript)
571 add_claim_to_hash_buffer(
ck, opening_claim, transcript);
572 compute_opening_proof_internal(
ck, opening_claim, transcript);
586 template <
typename Transcript = NativeTranscript>
587 static bool reduce_verify(
const VK&
vk,
588 const OpeningClaim<Curve>& opening_claim,
589 const std::shared_ptr<Transcript>& transcript)
592 add_claim_to_hash_buffer(opening_claim, transcript);
593 return reduce_verify_internal_native(
vk, opening_claim, transcript);
607 static VerifierAccumulator reduce_verify(
const OpeningClaim<Curve>& opening_claim,
const auto& transcript)
613 add_claim_to_hash_buffer(opening_claim, transcript);
614 return reduce_verify_internal_recursive(opening_claim, transcript);
634 static bool full_verify_recursive(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
637 add_claim_to_hash_buffer(opening_claim, transcript);
638 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
639 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
640 auto claimed_G_zero = verifier_accumulator.comm;
650 std::vector<Fr> s_vec_temporaries(
poly_length / 2);
653 Fr* previous_round_s = &s_vec_temporaries[0];
654 Fr* current_round_s = &s_vec[0];
656 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) {
661 const size_t round_size = 1 << (i + 1);
662 const Fr round_challenge = round_challenges_inv[i];
663 for (
size_t j = 0; j < round_size / 2; ++j) {
664 current_round_s[j * 2] = previous_round_s[j];
665 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
667 std::swap(current_round_s, previous_round_s);
672 const std::vector<Commitment> srs_elements =
vk.get_monomial_points();
673 Commitment computed_G_zero = Commitment::batch_mul(srs_elements, s_vec);
675 claimed_G_zero.assert_equal(computed_G_zero);
676 BB_ASSERT_EQ(computed_G_zero.get_value(), claimed_G_zero.get_value(),
"G_zero doesn't match received G_zero.");
678 bool running_truth_value = verifier_accumulator.running_truth_value;
679 return running_truth_value;
694 static OpeningClaim<Curve> reduce_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim)
697 const auto& commitments = batch_opening_claim.commitments;
698 const auto& scalars = batch_opening_claim.scalars;
699 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
701 GroupElement shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
703 return { { shplonk_eval_challenge,
Fr(0) }, shplonk_output_commitment };
714 static bool reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
719 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
720 add_claim_to_hash_buffer(opening_claim, transcript);
721 return reduce_verify_internal_native(
vk, opening_claim, transcript);
732 static VerifierAccumulator reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
737 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
738 add_claim_to_hash_buffer(opening_claim, transcript);
739 return reduce_verify_internal_recursive(opening_claim, transcript).verifier_accumulator;
750 static Fr evaluate_challenge_poly(
const std::vector<Fr>& u_challenges_inv,
Fr r)
754 Fr challenge_poly_eval = 1;
758 for (
size_t i = 0; i < log_poly_length - 1; i++) {
759 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
760 challenge_poly_eval *= (
Fr(1) + monomial);
764 Fr monomial = u_challenges_inv[0] * r_pow;
765 challenge_poly_eval *= (
Fr(1) + monomial);
766 return challenge_poly_eval;
780 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
781 std::vector<Fr> u_challenges_inv_2,
786 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
797 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(
const std::span<const bb::fq>& u_challenges_inv)
804 bb::fq* previous_round_s = &s_vec_temporaries[0];
805 bb::fq* current_round_s = &s_vec[0];
807 if ((log_poly_length & 1) == 0) {
808 std::swap(previous_round_s, current_round_s);
810 previous_round_s[0] =
bb::fq(1);
811 for (
size_t i = 0; i < log_poly_length; ++i) {
812 const size_t round_size = 1 << (i + 1);
813 const bb::fq round_challenge = u_challenges_inv[i];
817 current_round_s[j * 2] = previous_round_s[j];
818 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
821 std::swap(current_round_s, previous_round_s);
836 static Polynomial<bb::fq> create_challenge_poly(
const std::vector<bb::fq>& u_challenges_inv_1,
841 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
842 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
843 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
844 challenge_poly += challenge_poly_1;
845 challenge_poly.add_scaled(challenge_poly_2, alpha);
846 return challenge_poly;
866 OpeningClaim<Curve> claim_1,
868 OpeningClaim<Curve> claim_2)
873 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
880 transcript.add_to_hash_buffer(
"u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
881 transcript.add_to_hash_buffer(
"U_1", verifier_accumulator_1.comm);
882 transcript.add_to_hash_buffer(
"u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
883 transcript.add_to_hash_buffer(
"U_2", verifier_accumulator_2.comm);
887 OpeningClaim<Curve> output_claim;
888 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
889 output_claim.opening_pair.challenge = r;
891 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
892 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
897 for (
Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
898 native_u_challenges_inv_1.push_back(
bb::fq(u_inv_i.get_value()));
900 for (
Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
901 native_u_challenges_inv_2.push_back(
bb::fq(u_inv_i.get_value()));
904 Polynomial<bb::fq> challenge_poly =
905 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2,
bb::fq(alpha.get_value()));
909 const OpeningPair<NativeCurve> opening_pair{
bb::fq(output_claim.opening_pair.challenge.get_value()),
910 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
912 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
913 opening_pair.evaluation,
914 "Opening claim does not hold for challenge polynomial.");
916 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
917 ck, { challenge_poly, opening_pair }, prover_transcript);
918 BB_ASSERT_EQ(challenge_poly.evaluate(
bb::fq(output_claim.opening_pair.challenge.get_value())),
919 bb::fq(output_claim.opening_pair.evaluation.get_value()),
920 "Opening claim does not hold for challenge polynomial.");
922 output_claim.opening_pair.evaluation.self_reduce();
923 return { output_claim, prover_transcript->export_proof() };
931 using Builder =
typename Curve::Builder;
932 using Curve = stdlib::grumpkin<Builder>;
934 CommitmentKey<NativeCurve> ipa_commitment_key(
poly_length);
936 auto poly = Polynomial<bb::fq>(n);
937 for (
size_t i = 0; i < n; i++) {
941 bb::fq eval = poly.evaluate(x);
942 auto commitment = ipa_commitment_key.commit(poly);
943 const OpeningPair<NativeCurve> opening_pair = { x, eval };
944 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
946 auto stdlib_comm = Curve::Group::from_witness(&
builder, commitment);
947 auto stdlib_x = Curve::ScalarField::from_witness(&
builder, x);
948 auto stdlib_eval = Curve::ScalarField::from_witness(&
builder, eval);
949 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
951 return { stdlib_opening_claim, ipa_transcript->export_proof() };