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);
675 template <
typename Transcript = NativeTranscript>
676 static bool batch_reduce_verify(
const VK&
vk,
677 const std::vector<OpeningClaim<Curve>>& opening_claims,
678 const std::vector<std::shared_ptr<Transcript>>& transcripts)
681 const size_t num_claims = opening_claims.size();
682 if (num_claims != transcripts.size()) {
683 info(
"IPA batch verification failed: claims/transcripts size mismatch");
686 if (num_claims == 0) {
687 info(
"IPA batch verification failed: no claims provided");
693 std::vector<Fr> a_zeros(num_claims);
694 std::vector<Fr> b_zeros(num_claims);
695 std::vector<Fr> gen_challenges(num_claims);
698 for (
size_t i = 0; i < num_claims; i++) {
699 add_claim_to_hash_buffer(opening_claims[i], transcripts[i]);
700 auto data = read_transcript_data(opening_claims[i], transcripts[i]);
702 b_zeros[i] =
data.b_zero;
704 gen_challenges[i] =
data.gen_challenge;
705 a_zeros[i] =
data.a_zero;
710 std::vector<Fr> alpha_pows(num_claims);
712 for (
size_t i = 1; i < num_claims; i++) {
713 alpha_pows[i] = alpha_pows[i - 1] * alpha;
717 Polynomial<Fr> combined_s(poly_length);
718 for (
size_t i = 0; i < num_claims; i++) {
719 Fr scalar = alpha_pows[i] * a_zeros[i];
720 combined_s.add_scaled(s_vecs[i], scalar);
725 if (poly_length > srs_elements.size()) {
729 scalar_multiplication::pippenger_unsafe<Curve>(combined_s, { &srs_elements[0], poly_length });
732 GroupElement C_batch = C_zeros[0];
733 for (
size_t i = 1; i < num_claims; i++) {
734 C_batch = C_batch + C_zeros[i] * alpha_pows[i];
739 for (
size_t i = 0; i < num_claims; i++) {
740 bU_scalar += alpha_pows[i] * a_zeros[i] * b_zeros[i] * gen_challenges[i];
744 GroupElement right_hand_side = G_batch + Commitment::one() * bU_scalar;
745 return (C_batch.normalize() == right_hand_side.normalize());
759 static VerifierAccumulator reduce_verify(
const OpeningClaim<Curve>& opening_claim,
const auto& transcript)
765 add_claim_to_hash_buffer(opening_claim, transcript);
766 return reduce_verify_internal_recursive(opening_claim, transcript);
786 static bool full_verify_recursive(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
790 if (
vk.get_monomial_points().size() < poly_length) {
795 add_claim_to_hash_buffer(opening_claim, transcript);
796 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
797 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
798 auto claimed_G_zero = verifier_accumulator.comm;
808 std::vector<Fr> s_vec_temporaries(poly_length / 2);
809 std::vector<Fr> s_vec(poly_length);
811 Fr* previous_round_s = &s_vec_temporaries[0];
812 Fr* current_round_s = &s_vec[0];
814 if constexpr ((log_poly_length & 1) == 0) {
815 std::swap(previous_round_s, current_round_s);
817 previous_round_s[0] =
Fr(1);
818 for (
size_t i = 0; i < log_poly_length; ++i) {
819 const size_t round_size = 1 << (i + 1);
820 const Fr round_challenge = round_challenges_inv[i];
821 for (
size_t j = 0; j < round_size / 2; ++j) {
822 current_round_s[j * 2] = previous_round_s[j];
823 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
825 std::swap(current_round_s, previous_round_s);
835 std::vector<Commitment> srs_elements =
vk.get_monomial_points();
836 srs_elements.resize(poly_length);
837 std::vector<Commitment> remaining_srs(srs_elements.begin() + 1, srs_elements.end());
838 std::vector<Fr> remaining_s(s_vec.begin() + 1, s_vec.end());
839 Commitment first_term = srs_elements[0] * s_vec[0];
840 Commitment remaining_term = Commitment::fixed_batch_mul(remaining_srs, remaining_s, {}, 8);
841 Commitment computed_G_zero = first_term + remaining_term;
844 claimed_G_zero.assert_equal(computed_G_zero,
"G_zero doesn't match received G_zero.");
846 bool running_truth_value = verifier_accumulator.running_truth_value;
847 return running_truth_value;
862 static OpeningClaim<Curve> reduce_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim)
865 const auto& commitments = batch_opening_claim.commitments;
866 auto scalars = batch_opening_claim.scalars;
867 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
869 GroupElement shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
871 return { { shplonk_eval_challenge,
Fr(0) }, shplonk_output_commitment };
882 static bool reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
887 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
888 add_claim_to_hash_buffer(opening_claim, transcript);
889 return reduce_verify_internal_native(
vk, opening_claim, transcript);
900 static VerifierAccumulator reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
905 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
906 add_claim_to_hash_buffer(opening_claim, transcript);
907 return reduce_verify_internal_recursive(opening_claim, transcript);
918 static Fr evaluate_challenge_poly(
const std::vector<Fr>& u_challenges_inv,
Fr r)
922 Fr challenge_poly_eval = 1;
926 for (
size_t i = 0; i < log_poly_length - 1; i++) {
927 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
928 challenge_poly_eval *= (
Fr(1) + monomial);
932 Fr monomial = u_challenges_inv[0] * r_pow;
933 challenge_poly_eval *= (
Fr(1) + monomial);
934 return challenge_poly_eval;
948 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
949 std::vector<Fr> u_challenges_inv_2,
954 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
965 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(
const std::span<const bb::fq>& u_challenges_inv)
972 bb::fq* previous_round_s = &s_vec_temporaries[0];
973 bb::fq* current_round_s = &s_vec[0];
975 if ((log_poly_length & 1) == 0) {
976 std::swap(previous_round_s, current_round_s);
978 previous_round_s[0] =
bb::fq(1);
979 for (
size_t i = 0; i < log_poly_length; ++i) {
980 const size_t round_size = 1 << (i + 1);
981 const bb::fq round_challenge = u_challenges_inv[i];
985 current_round_s[j * 2] = previous_round_s[j];
986 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
989 std::swap(current_round_s, previous_round_s);
991 return { s_vec, poly_length };
1004 static Polynomial<bb::fq> create_challenge_poly(
const std::vector<bb::fq>& u_challenges_inv_1,
1009 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
1010 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
1011 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
1012 challenge_poly += challenge_poly_1;
1013 challenge_poly.add_scaled(challenge_poly_2, alpha);
1014 return challenge_poly;
1033 OpeningClaim<Curve> claim_1,
1035 OpeningClaim<Curve> claim_2)
1040 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
1042 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
1043 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
1047 transcript.add_to_hash_buffer(
"u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
1048 transcript.add_to_hash_buffer(
"U_1", verifier_accumulator_1.comm);
1049 transcript.add_to_hash_buffer(
"u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
1050 transcript.add_to_hash_buffer(
"U_2", verifier_accumulator_2.comm);
1054 OpeningClaim<Curve> output_claim;
1055 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
1056 output_claim.opening_pair.challenge = r;
1058 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
1059 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
1064 for (
Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
1065 native_u_challenges_inv_1.push_back(
bb::fq(u_inv_i.get_value()));
1067 for (
Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
1068 native_u_challenges_inv_2.push_back(
bb::fq(u_inv_i.get_value()));
1071 Polynomial<bb::fq> challenge_poly =
1072 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2,
bb::fq(alpha.get_value()));
1076 const OpeningPair<NativeCurve> opening_pair{
bb::fq(output_claim.opening_pair.challenge.get_value()),
1077 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
1079 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
1080 opening_pair.evaluation,
1081 "Opening claim does not hold for challenge polynomial.");
1083 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
1084 ck, { challenge_poly, opening_pair }, prover_transcript);
1086 output_claim.opening_pair.evaluation.self_reduce();
1087 return { output_claim, prover_transcript->export_proof() };
1095 using Builder =
typename Curve::Builder;
1096 using Curve = stdlib::grumpkin<Builder>;
1098 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
1099 size_t n = poly_length;
1100 auto poly = Polynomial<bb::fq>(n);
1101 for (
size_t i = 0; i < n; i++) {
1105 bb::fq eval = poly.evaluate(x);
1106 auto commitment = ipa_commitment_key.commit(poly);
1107 const OpeningPair<NativeCurve> opening_pair = { x, eval };
1108 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
1110 auto stdlib_comm = Curve::Group::from_witness(&
builder, commitment);
1111 auto stdlib_x = Curve::ScalarField::from_witness(&
builder, x);
1112 auto stdlib_eval = Curve::ScalarField::from_witness(&
builder, eval);
1113 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
1115 return { stdlib_opening_claim, ipa_transcript->export_proof() };