86template <
typename Curve_,
size_t log_poly_length = CONST_ECCVM_LOG_N>
class IPA {
102 static_assert(log_poly_length >= 1,
"log_poly_length must be at least 1");
107 FRIEND_TEST(IPATest, ChallengesAreZero);
108 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
150 template <
typename Transcript>
151 static void compute_opening_proof_internal(
const CK&
ck,
153 const std::shared_ptr<Transcript>& transcript)
163 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
165 if (generator_challenge.is_zero()) {
173 auto aux_generator = Commitment::one() * generator_challenge;
178 "The polynomial degree plus 1 should be positive and a power of two");
183 auto a_vec = polynomial.
full();
196 G_vec_local[i] = srs_elements[i];
202 OpeningPair<Curve> opening_pair = opening_claim.
opening_pair;
206 [&](
size_t start,
size_t end,
BB_UNUSED size_t chunk_index) {
208 Fr b_power = opening_pair.challenge.
pow(start);
209 for (
size_t i = start; i < end; i++) {
211 b_power *= opening_pair.challenge;
225 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,
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);
341 struct TranscriptData {
344 Polynomial<Fr> s_vec;
347 Commitment G_zero_from_prover;
374 template <
typename Transcript>
375 static TranscriptData read_transcript_data(
const OpeningClaim<Curve>& opening_claim,
376 const std::shared_ptr<Transcript>& transcript)
381 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
382 if (generator_challenge.
is_zero()) {
385 const Commitment aux_generator = Commitment::one() * generator_challenge;
389 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
391 const auto pippenger_size = 2 * log_poly_length;
392 std::vector<Fr> round_challenges(log_poly_length);
393 std::vector<Commitment> msm_elements(pippenger_size);
394 std::vector<Fr> msm_scalars(pippenger_size);
398 for (
size_t i = 0; i < log_poly_length; i++) {
400 const auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" +
index);
401 const auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" +
index);
402 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
403 if (round_challenges[i].is_zero()) {
406 msm_elements[2 * i] = element_L;
407 msm_elements[2 * i + 1] = element_R;
410 std::vector<Fr> round_challenges_inv = round_challenges;
414 for (
size_t i = 0; i < log_poly_length; i++) {
415 msm_scalars[2 * i] = round_challenges_inv[i];
416 msm_scalars[2 * i + 1] = round_challenges[i];
421 GroupElement LR_sums = scalar_multiplication::pippenger<Curve>(
422 { 0, { &msm_scalars[0], pippenger_size } }, { &msm_elements[0], pippenger_size });
427 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
431 Polynomial<Fr> s_vec(
432 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
435 Commitment G_zero_from_prover = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
436 Fr a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
438 return { C_zero, b_zero,
std::move(s_vec), generator_challenge, G_zero_from_prover, a_zero };
466 static bool reduce_verify_internal_native(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
472 auto data = read_transcript_data(opening_claim, transcript);
484 scalar_multiplication::pippenger_unsafe<Curve>(
data.s_vec, { &srs_elements[0], poly_length });
486 if (G_zero !=
data.G_zero_from_prover) {
487 info(
"IPA verification failed: G_0 mismatch");
493 Commitment aux_generator = Commitment::one() *
data.gen_challenge;
498 return (
data.C_zero.normalize() == right_hand_side.normalize());
511 template <
typename Transcript>
512 static void add_claim_to_hash_buffer(
const OpeningClaim<Curve>& opening_claim,
513 const std::shared_ptr<Transcript>& transcript)
519 transcript->add_to_hash_buffer(
"IPA:commitment", opening_claim.commitment);
520 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.opening_pair.challenge);
521 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.opening_pair.evaluation);
540 static VerifierAccumulator reduce_verify_internal_recursive(
const OpeningClaim<Curve>& opening_claim,
549 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
550 typename Curve::Builder*
builder = generator_challenge.get_context();
552 auto pippenger_size =
553 2 * log_poly_length +
556 std::vector<Fr> round_challenges(log_poly_length);
557 std::vector<Fr> round_challenges_inv(log_poly_length);
558 std::vector<Commitment> msm_elements(
560 std::vector<Fr> msm_scalars(pippenger_size);
565 for (
size_t i = 0; i < log_poly_length; i++) {
568 auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" +
index);
569 auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" +
index);
570 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
571 round_challenges_inv[i] = round_challenges[i].invert();
573 msm_elements[2 * i] = element_L;
574 msm_elements[2 * i + 1] = element_R;
575 msm_scalars[2 * i] = round_challenges_inv[i];
576 msm_scalars[2 * i + 1] = round_challenges[i];
581 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
585 Commitment G_zero = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
589 auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
594 const auto last_round_tag = round_challenges.back().get_origin_tag();
595 G_zero.set_origin_tag(last_round_tag);
596 a_zero.set_origin_tag(last_round_tag);
602 msm_elements[(2 * log_poly_length)] = -G_zero;
603 msm_elements[(2 * log_poly_length) + 1] = -Commitment::one(
builder);
604 msm_scalars[(2 * log_poly_length)] = a_zero;
605 msm_scalars[(2 * log_poly_length) + 1] =
606 generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation });
607 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
608 auto neg_commitment = -opening_claim.commitment;
611 ipa_relation.assert_equal(neg_commitment);
613 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
627 template <
typename Transcript = NativeTranscript>
628 static void compute_opening_proof(
const CK&
ck,
629 const ProverOpeningClaim<Curve>& opening_claim,
630 const std::shared_ptr<Transcript>& transcript)
632 add_claim_to_hash_buffer(
ck, opening_claim, transcript);
633 compute_opening_proof_internal(
ck, opening_claim, transcript);
647 template <
typename Transcript = NativeTranscript>
648 static bool reduce_verify(
const VK&
vk,
649 const OpeningClaim<Curve>& opening_claim,
650 const std::shared_ptr<Transcript>& transcript)
653 add_claim_to_hash_buffer(opening_claim, transcript);
654 return reduce_verify_internal_native(
vk, opening_claim, transcript);
680 template <
typename Transcript = NativeTranscript>
681 static bool batch_reduce_verify(
const VK&
vk,
682 const std::vector<OpeningClaim<Curve>>& opening_claims,
683 const std::vector<std::shared_ptr<Transcript>>& transcripts)
686 const size_t num_claims = opening_claims.size();
687 if (num_claims != transcripts.size()) {
688 info(
"IPA batch verification failed: claims/transcripts size mismatch");
691 if (num_claims == 0) {
692 info(
"IPA batch verification failed: no claims provided");
698 std::vector<Fr> a_zeros(num_claims);
699 std::vector<Fr> b_zeros(num_claims);
700 std::vector<Fr> gen_challenges(num_claims);
701 std::vector<Commitment> G_zeros_from_prover(num_claims);
704 for (
size_t i = 0; i < num_claims; i++) {
705 add_claim_to_hash_buffer(opening_claims[i], transcripts[i]);
706 auto data = read_transcript_data(opening_claims[i], transcripts[i]);
708 b_zeros[i] =
data.b_zero;
710 gen_challenges[i] =
data.gen_challenge;
711 G_zeros_from_prover[i] =
data.G_zero_from_prover;
712 a_zeros[i] =
data.a_zero;
719 std::vector<Fr> alpha_pows(num_claims);
721 for (
size_t i = 1; i < num_claims; i++) {
722 alpha_pows[i] = alpha_pows[i - 1] * alpha;
725 std::vector<Fr> beta_pows(num_claims);
727 for (
size_t i = 1; i < num_claims; i++) {
728 beta_pows[i] = beta_pows[i - 1] * beta;
733 if (poly_length > srs_elements.size()) {
746 Polynomial<Fr> combined_msm_scalars(poly_length);
747 for (
size_t i = 0; i < num_claims; i++) {
748 combined_msm_scalars.add_scaled(s_vecs[i], gamma * beta_pows[i] - alpha_pows[i] * a_zeros[i]);
750 Commitment batched_commitment = scalar_multiplication::pippenger_unsafe<Curve>(
751 combined_msm_scalars, { &srs_elements[0], poly_length });
754 GroupElement prover_g_zero_batch = G_zeros_from_prover[0] * beta_pows[0];
755 for (
size_t i = 1; i < num_claims; i++) {
756 prover_g_zero_batch += G_zeros_from_prover[i] * beta_pows[i];
760 GroupElement C_batch = C_zeros[0];
761 for (
size_t i = 1; i < num_claims; i++) {
762 C_batch = C_batch + C_zeros[i] * alpha_pows[i];
767 for (
size_t i = 0; i < num_claims; i++) {
768 bU_scalar += alpha_pows[i] * a_zeros[i] * b_zeros[i] * gen_challenges[i];
773 GroupElement left_hand_side = batched_commitment + C_batch;
774 GroupElement right_hand_side = prover_g_zero_batch * gamma + Commitment::one() * bU_scalar;
775 return (left_hand_side.normalize() == right_hand_side.normalize());
789 static VerifierAccumulator reduce_verify(
const OpeningClaim<Curve>& opening_claim,
const auto& transcript)
795 add_claim_to_hash_buffer(opening_claim, transcript);
796 return reduce_verify_internal_recursive(opening_claim, transcript);
816 static bool full_verify_recursive(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
820 if (
vk.get_monomial_points().size() < poly_length) {
825 add_claim_to_hash_buffer(opening_claim, transcript);
826 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
827 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
828 auto claimed_G_zero = verifier_accumulator.comm;
838 std::vector<Fr> s_vec_temporaries(poly_length / 2);
839 std::vector<Fr> s_vec(poly_length);
841 Fr* previous_round_s = &s_vec_temporaries[0];
842 Fr* current_round_s = &s_vec[0];
844 if constexpr ((log_poly_length & 1) == 0) {
845 std::swap(previous_round_s, current_round_s);
847 previous_round_s[0] =
Fr(1);
848 for (
size_t i = 0; i < log_poly_length; ++i) {
849 const size_t round_size = 1 << (i + 1);
850 const Fr round_challenge = round_challenges_inv[i];
851 for (
size_t j = 0; j < round_size / 2; ++j) {
852 current_round_s[j * 2] = previous_round_s[j];
853 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
855 std::swap(current_round_s, previous_round_s);
865 std::vector<Commitment> srs_elements =
vk.get_monomial_points();
866 srs_elements.resize(poly_length);
867 std::vector<Commitment> remaining_srs(srs_elements.begin() + 1, srs_elements.end());
868 std::vector<Fr> remaining_s(s_vec.begin() + 1, s_vec.end());
869 Commitment first_term = srs_elements[0] * s_vec[0];
870 Commitment remaining_term = Commitment::fixed_batch_mul(remaining_srs, remaining_s, {}, 8);
871 Commitment computed_G_zero = first_term + remaining_term;
874 claimed_G_zero.assert_equal(computed_G_zero,
"G_zero doesn't match received G_zero.");
876 bool running_truth_value = verifier_accumulator.running_truth_value;
877 return running_truth_value;
892 static OpeningClaim<Curve> reduce_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim)
895 const auto& commitments = batch_opening_claim.commitments;
896 auto scalars = batch_opening_claim.scalars;
897 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
899 GroupElement shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
901 return { { shplonk_eval_challenge,
Fr(0) }, shplonk_output_commitment };
912 static bool reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
917 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
918 add_claim_to_hash_buffer(opening_claim, transcript);
919 return reduce_verify_internal_native(
vk, opening_claim, transcript);
930 static VerifierAccumulator reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
935 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
936 add_claim_to_hash_buffer(opening_claim, transcript);
937 return reduce_verify_internal_recursive(opening_claim, transcript);
948 static Fr evaluate_challenge_poly(
const std::vector<Fr>& u_challenges_inv,
Fr r)
952 Fr challenge_poly_eval = 1;
956 for (
size_t i = 0; i < log_poly_length - 1; i++) {
957 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
958 challenge_poly_eval *= (
Fr(1) + monomial);
962 Fr monomial = u_challenges_inv[0] * r_pow;
963 challenge_poly_eval *= (
Fr(1) + monomial);
964 return challenge_poly_eval;
978 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
979 std::vector<Fr> u_challenges_inv_2,
984 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
995 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(
const std::span<const bb::fq>& u_challenges_inv)
1002 bb::fq* previous_round_s = &s_vec_temporaries[0];
1003 bb::fq* current_round_s = &s_vec[0];
1005 if ((log_poly_length & 1) == 0) {
1006 std::swap(previous_round_s, current_round_s);
1008 previous_round_s[0] =
bb::fq(1);
1009 for (
size_t i = 0; i < log_poly_length; ++i) {
1010 const size_t round_size = 1 << (i + 1);
1011 const bb::fq round_challenge = u_challenges_inv[i];
1015 current_round_s[j * 2] = previous_round_s[j];
1016 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
1019 std::swap(current_round_s, previous_round_s);
1021 return { s_vec, poly_length };
1034 static Polynomial<bb::fq> create_challenge_poly(
const std::vector<bb::fq>& u_challenges_inv_1,
1039 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
1040 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
1041 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
1042 challenge_poly += challenge_poly_1;
1043 challenge_poly.add_scaled(challenge_poly_2, alpha);
1044 return challenge_poly;
1063 OpeningClaim<Curve> claim_1,
1065 OpeningClaim<Curve> claim_2)
1070 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
1072 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
1073 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
1077 transcript.add_to_hash_buffer(
"u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
1078 transcript.add_to_hash_buffer(
"U_1", verifier_accumulator_1.comm);
1079 transcript.add_to_hash_buffer(
"u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
1080 transcript.add_to_hash_buffer(
"U_2", verifier_accumulator_2.comm);
1084 OpeningClaim<Curve> output_claim;
1085 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
1086 output_claim.opening_pair.challenge = r;
1088 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
1089 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
1094 for (
Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
1095 native_u_challenges_inv_1.push_back(
bb::fq(u_inv_i.get_value()));
1097 for (
Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
1098 native_u_challenges_inv_2.push_back(
bb::fq(u_inv_i.get_value()));
1101 Polynomial<bb::fq> challenge_poly =
1102 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2,
bb::fq(alpha.get_value()));
1106 const OpeningPair<NativeCurve> opening_pair{
bb::fq(output_claim.opening_pair.challenge.get_value()),
1107 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
1109 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
1110 opening_pair.evaluation,
1111 "Opening claim does not hold for challenge polynomial.");
1113 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
1114 ck, { challenge_poly, opening_pair }, prover_transcript);
1116 output_claim.opening_pair.evaluation.self_reduce();
1117 return { output_claim, prover_transcript->export_proof() };
1125 using Builder =
typename Curve::Builder;
1126 using Curve = stdlib::grumpkin<Builder>;
1128 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
1129 size_t n = poly_length;
1130 auto poly = Polynomial<bb::fq>(n);
1131 for (
size_t i = 0; i < n; i++) {
1135 bb::fq eval = poly.evaluate(x);
1136 auto commitment = ipa_commitment_key.commit(poly);
1137 const OpeningPair<NativeCurve> opening_pair = { x, eval };
1138 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
1140 auto stdlib_comm = Curve::Group::from_witness(&
builder, commitment);
1141 auto stdlib_x = Curve::ScalarField::from_witness(&
builder, x);
1142 auto stdlib_eval = Curve::ScalarField::from_witness(&
builder, eval);
1143 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
1145 return { stdlib_opening_claim, ipa_transcript->export_proof() };