86template <
typename Curve_,
size_t log_poly_length = CONST_ECCVM_LOG_N>
class IPA {
106 FRIEND_TEST(IPATest, ChallengesAreZero);
107 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
149 template <
typename Transcript>
150 static void compute_opening_proof_internal(
const CK&
ck,
152 const std::shared_ptr<Transcript>& transcript)
161 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
163 if (generator_challenge.is_zero()) {
174 auto aux_generator = Commitment::one() * generator_challenge;
179 "The polynomial degree plus 1 should be positive and a power of two");
184 auto a_vec = polynomial.
full();
198 OpeningPair<Curve> opening_pair = opening_claim.
opening_pair;
202 [&](
size_t start,
size_t end,
BB_UNUSED size_t chunk_index) {
203 Fr b_power = opening_pair.challenge.
pow(start);
204 for (
size_t i = start; i < end; i++) {
206 b_power *= opening_pair.challenge;
220 for (
size_t i = 0; i < log_poly_length; i++) {
228 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
230 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
234 auto [inner_prod_L, inner_prod_R] =
sum_pairs(inner_prods);
238 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), round_size } },
239 { &G_vec_local[round_size], round_size });
241 L_i += aux_generator * inner_prod_L;
245 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(round_size), round_size } },
246 { &G_vec_local[0], round_size });
247 R_i += aux_generator * inner_prod_R;
257 const Fr round_challenge = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
259 if (round_challenge.
is_zero()) {
262 const Fr round_challenge_inv = round_challenge.
invert();
266 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
267 std::span{ G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size),
268 G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size * 2) },
269 round_challenge_inv);
270 GroupElement::batch_affine_add(
271 std::span{ G_vec_local.begin(), G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size) },
272 G_hi_by_inverse_challenge,
282 a_vec.at(j) += round_challenge * a_vec[round_size + j];
283 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
290 transcript->send_to_verifier(
"IPA:G_0", G_vec_local[0]);
294 transcript->send_to_verifier(
"IPA:a_0", a_vec[0]);
307 template <
typename Transcript>
308 static void add_claim_to_hash_buffer(
const CK&
ck,
309 const ProverOpeningClaim<Curve>& opening_claim,
310 const std::shared_ptr<Transcript>& transcript)
322 const auto commitment =
ck.commit(polynomial);
323 transcript->add_to_hash_buffer(
"IPA:commitment", commitment);
324 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.opening_pair.challenge);
325 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.opening_pair.evaluation);
334 struct TranscriptData {
337 Polynomial<Fr> s_vec;
340 Commitment G_zero_from_prover;
367 template <
typename Transcript>
368 static TranscriptData read_transcript_data(
const OpeningClaim<Curve>& opening_claim,
369 const std::shared_ptr<Transcript>& transcript)
374 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
375 if (generator_challenge.
is_zero()) {
378 const Commitment aux_generator = Commitment::one() * generator_challenge;
382 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
384 const auto pippenger_size = 2 * log_poly_length;
385 std::vector<Fr> round_challenges(log_poly_length);
386 std::vector<Commitment> msm_elements(pippenger_size);
387 std::vector<Fr> msm_scalars(pippenger_size);
391 for (
size_t i = 0; i < log_poly_length; i++) {
393 const auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" +
index);
394 const auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" +
index);
395 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
396 if (round_challenges[i].is_zero()) {
399 msm_elements[2 * i] = element_L;
400 msm_elements[2 * i + 1] = element_R;
403 std::vector<Fr> round_challenges_inv = round_challenges;
407 for (
size_t i = 0; i < log_poly_length; i++) {
408 msm_scalars[2 * i] = round_challenges_inv[i];
409 msm_scalars[2 * i + 1] = round_challenges[i];
414 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>(
415 { 0, { &msm_scalars[0], pippenger_size } }, { &msm_elements[0], pippenger_size });
420 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
424 Polynomial<Fr> s_vec(
425 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
428 Commitment G_zero_from_prover = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
429 Fr a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
431 return { C_zero, b_zero,
std::move(s_vec), generator_challenge, G_zero_from_prover, a_zero };
459 static bool reduce_verify_internal_native(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
465 auto data = read_transcript_data(opening_claim, transcript);
477 scalar_multiplication::pippenger_unsafe<Curve>(
data.s_vec, { &srs_elements[0], poly_length });
480 G_zero,
data.G_zero_from_prover,
"G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
484 Commitment aux_generator = Commitment::one() *
data.gen_challenge;
489 return (
data.C_zero.normalize() == right_hand_side.normalize());
502 template <
typename Transcript>
503 static void add_claim_to_hash_buffer(
const OpeningClaim<Curve>& opening_claim,
504 const std::shared_ptr<Transcript>& transcript)
510 transcript->add_to_hash_buffer(
"IPA:commitment", opening_claim.commitment);
511 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.opening_pair.challenge);
512 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.opening_pair.evaluation);
531 static VerifierAccumulator reduce_verify_internal_recursive(
const OpeningClaim<Curve>& opening_claim,
540 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
541 typename Curve::Builder*
builder = generator_challenge.get_context();
543 auto pippenger_size =
544 2 * log_poly_length +
547 std::vector<Fr> round_challenges(log_poly_length);
548 std::vector<Fr> round_challenges_inv(log_poly_length);
549 std::vector<Commitment> msm_elements(
551 std::vector<Fr> msm_scalars(pippenger_size);
556 for (
size_t i = 0; i < log_poly_length; i++) {
559 auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" +
index);
560 auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" +
index);
561 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
562 round_challenges_inv[i] = round_challenges[i].invert();
564 msm_elements[2 * i] = element_L;
565 msm_elements[2 * i + 1] = element_R;
566 msm_scalars[2 * i] = round_challenges_inv[i];
567 msm_scalars[2 * i + 1] = round_challenges[i];
572 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
576 Commitment G_zero = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
580 const auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
585 const auto last_round_tag = round_challenges.back().get_origin_tag();
586 G_zero.set_origin_tag(last_round_tag);
587 const_cast<Fr&
>(a_zero).set_origin_tag(last_round_tag);
593 msm_elements.emplace_back(-G_zero);
594 msm_elements.emplace_back(-Commitment::one(
builder));
595 msm_scalars.emplace_back(a_zero);
596 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation }));
597 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
598 auto neg_commitment = -opening_claim.commitment;
601 ipa_relation.assert_equal(neg_commitment);
603 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
617 template <
typename Transcript = NativeTranscript>
618 static void compute_opening_proof(
const CK&
ck,
619 const ProverOpeningClaim<Curve>& opening_claim,
620 const std::shared_ptr<Transcript>& transcript)
622 add_claim_to_hash_buffer(
ck, opening_claim, transcript);
623 compute_opening_proof_internal(
ck, opening_claim, transcript);
637 template <
typename Transcript = NativeTranscript>
638 static bool reduce_verify(
const VK&
vk,
639 const OpeningClaim<Curve>& opening_claim,
640 const std::shared_ptr<Transcript>& transcript)
643 add_claim_to_hash_buffer(opening_claim, transcript);
644 return reduce_verify_internal_native(
vk, opening_claim, transcript);
665 template <
typename Transcript = NativeTranscript>
666 static bool batch_reduce_verify(
const VK&
vk,
667 const std::vector<OpeningClaim<Curve>>& opening_claims,
668 const std::vector<std::shared_ptr<Transcript>>& transcripts)
671 const size_t num_claims = opening_claims.size();
672 BB_ASSERT(num_claims == transcripts.size());
677 std::vector<Fr> a_zeros(num_claims);
678 std::vector<Fr> b_zeros(num_claims);
679 std::vector<Fr> gen_challenges(num_claims);
682 for (
size_t i = 0; i < num_claims; i++) {
683 add_claim_to_hash_buffer(opening_claims[i], transcripts[i]);
684 auto data = read_transcript_data(opening_claims[i], transcripts[i]);
686 b_zeros[i] =
data.b_zero;
688 gen_challenges[i] =
data.gen_challenge;
689 a_zeros[i] =
data.a_zero;
694 std::vector<Fr> alpha_pows(num_claims);
696 for (
size_t i = 1; i < num_claims; i++) {
697 alpha_pows[i] = alpha_pows[i - 1] * alpha;
701 Polynomial<Fr> combined_s(poly_length);
702 for (
size_t i = 0; i < num_claims; i++) {
703 Fr scalar = alpha_pows[i] * a_zeros[i];
704 combined_s.add_scaled(s_vecs[i], scalar);
709 if (poly_length > srs_elements.size()) {
713 scalar_multiplication::pippenger_unsafe<Curve>(combined_s, { &srs_elements[0], poly_length });
716 GroupElement C_batch = C_zeros[0];
717 for (
size_t i = 1; i < num_claims; i++) {
718 C_batch = C_batch + C_zeros[i] * alpha_pows[i];
723 for (
size_t i = 0; i < num_claims; i++) {
724 bU_scalar += alpha_pows[i] * a_zeros[i] * b_zeros[i] * gen_challenges[i];
728 GroupElement right_hand_side = G_batch + Commitment::one() * bU_scalar;
729 return (C_batch.normalize() == right_hand_side.normalize());
743 static VerifierAccumulator reduce_verify(
const OpeningClaim<Curve>& opening_claim,
const auto& transcript)
749 add_claim_to_hash_buffer(opening_claim, transcript);
750 return reduce_verify_internal_recursive(opening_claim, transcript);
770 static bool full_verify_recursive(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
773 add_claim_to_hash_buffer(opening_claim, transcript);
774 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
775 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
776 auto claimed_G_zero = verifier_accumulator.comm;
786 std::vector<Fr> s_vec_temporaries(poly_length / 2);
787 std::vector<Fr> s_vec(poly_length);
789 Fr* previous_round_s = &s_vec_temporaries[0];
790 Fr* current_round_s = &s_vec[0];
792 if constexpr ((log_poly_length & 1) == 0) {
793 std::swap(previous_round_s, current_round_s);
795 previous_round_s[0] =
Fr(1);
796 for (
size_t i = 0; i < log_poly_length; ++i) {
797 const size_t round_size = 1 << (i + 1);
798 const Fr round_challenge = round_challenges_inv[i];
799 for (
size_t j = 0; j < round_size / 2; ++j) {
800 current_round_s[j * 2] = previous_round_s[j];
801 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
803 std::swap(current_round_s, previous_round_s);
808 const std::vector<Commitment> srs_elements =
vk.get_monomial_points();
809 Commitment computed_G_zero = Commitment::batch_mul(srs_elements, s_vec);
811 claimed_G_zero.assert_equal(computed_G_zero);
812 BB_ASSERT_EQ(computed_G_zero.get_value(), claimed_G_zero.get_value(),
"G_zero doesn't match received G_zero.");
814 bool running_truth_value = verifier_accumulator.running_truth_value;
815 return running_truth_value;
830 static OpeningClaim<Curve> reduce_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim)
833 const auto& commitments = batch_opening_claim.commitments;
834 const auto& scalars = batch_opening_claim.scalars;
835 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
837 GroupElement shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
839 return { { shplonk_eval_challenge,
Fr(0) }, shplonk_output_commitment };
850 static bool reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
855 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
856 add_claim_to_hash_buffer(opening_claim, transcript);
857 return reduce_verify_internal_native(
vk, opening_claim, transcript);
868 static VerifierAccumulator reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
873 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
874 add_claim_to_hash_buffer(opening_claim, transcript);
875 return reduce_verify_internal_recursive(opening_claim, transcript).verifier_accumulator;
886 static Fr evaluate_challenge_poly(
const std::vector<Fr>& u_challenges_inv,
Fr r)
890 Fr challenge_poly_eval = 1;
894 for (
size_t i = 0; i < log_poly_length - 1; i++) {
895 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
896 challenge_poly_eval *= (
Fr(1) + monomial);
900 Fr monomial = u_challenges_inv[0] * r_pow;
901 challenge_poly_eval *= (
Fr(1) + monomial);
902 return challenge_poly_eval;
916 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
917 std::vector<Fr> u_challenges_inv_2,
922 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
933 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(
const std::span<const bb::fq>& u_challenges_inv)
940 bb::fq* previous_round_s = &s_vec_temporaries[0];
941 bb::fq* current_round_s = &s_vec[0];
943 if ((log_poly_length & 1) == 0) {
944 std::swap(previous_round_s, current_round_s);
946 previous_round_s[0] =
bb::fq(1);
947 for (
size_t i = 0; i < log_poly_length; ++i) {
948 const size_t round_size = 1 << (i + 1);
949 const bb::fq round_challenge = u_challenges_inv[i];
953 current_round_s[j * 2] = previous_round_s[j];
954 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
957 std::swap(current_round_s, previous_round_s);
959 return { s_vec, poly_length };
972 static Polynomial<bb::fq> create_challenge_poly(
const std::vector<bb::fq>& u_challenges_inv_1,
977 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
978 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
979 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
980 challenge_poly += challenge_poly_1;
981 challenge_poly.add_scaled(challenge_poly_2, alpha);
982 return challenge_poly;
1002 OpeningClaim<Curve> claim_1,
1004 OpeningClaim<Curve> claim_2)
1009 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
1011 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
1012 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
1016 transcript.add_to_hash_buffer(
"u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
1017 transcript.add_to_hash_buffer(
"U_1", verifier_accumulator_1.comm);
1018 transcript.add_to_hash_buffer(
"u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
1019 transcript.add_to_hash_buffer(
"U_2", verifier_accumulator_2.comm);
1023 OpeningClaim<Curve> output_claim;
1024 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
1025 output_claim.opening_pair.challenge = r;
1027 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
1028 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
1033 for (
Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
1034 native_u_challenges_inv_1.push_back(
bb::fq(u_inv_i.get_value()));
1036 for (
Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
1037 native_u_challenges_inv_2.push_back(
bb::fq(u_inv_i.get_value()));
1040 Polynomial<bb::fq> challenge_poly =
1041 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2,
bb::fq(alpha.get_value()));
1045 const OpeningPair<NativeCurve> opening_pair{
bb::fq(output_claim.opening_pair.challenge.get_value()),
1046 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
1048 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
1049 opening_pair.evaluation,
1050 "Opening claim does not hold for challenge polynomial.");
1052 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
1053 ck, { challenge_poly, opening_pair }, prover_transcript);
1054 BB_ASSERT_EQ(challenge_poly.evaluate(
bb::fq(output_claim.opening_pair.challenge.get_value())),
1055 bb::fq(output_claim.opening_pair.evaluation.get_value()),
1056 "Opening claim does not hold for challenge polynomial.");
1058 output_claim.opening_pair.evaluation.self_reduce();
1059 return { output_claim, prover_transcript->export_proof() };
1067 using Builder =
typename Curve::Builder;
1068 using Curve = stdlib::grumpkin<Builder>;
1070 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
1071 size_t n = poly_length;
1072 auto poly = Polynomial<bb::fq>(n);
1073 for (
size_t i = 0; i < n; i++) {
1077 bb::fq eval = poly.evaluate(x);
1078 auto commitment = ipa_commitment_key.commit(poly);
1079 const OpeningPair<NativeCurve> opening_pair = { x, eval };
1080 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
1082 auto stdlib_comm = Curve::Group::from_witness(&
builder, commitment);
1083 auto stdlib_x = Curve::ScalarField::from_witness(&
builder, x);
1084 auto stdlib_eval = Curve::ScalarField::from_witness(&
builder, eval);
1085 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
1087 return { stdlib_opening_claim, ipa_transcript->export_proof() };