85template <
typename Curve_,
size_t log_poly_length = CONST_ECCVM_LOG_N>
class IPA {
105 FRIEND_TEST(IPATest, ChallengesAreZero);
106 FRIEND_TEST(IPATest, AIsZeroAfterOneRound);
148 template <
typename Transcript>
149 static void compute_opening_proof_internal(
const CK&
ck,
151 const std::shared_ptr<Transcript>& transcript)
160 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
162 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();
197 OpeningPair<Curve> opening_pair = opening_claim.
opening_pair;
201 [&](
size_t start,
size_t end,
BB_UNUSED size_t chunk_index) {
202 Fr b_power = opening_pair.challenge.
pow(start);
203 for (
size_t i = start; i < end; i++) {
205 b_power *= opening_pair.challenge;
219 for (
size_t i = 0; i < log_poly_length; i++) {
227 inner_prod_left_right.first += a_vec[j] * b_vec[round_size + j];
229 inner_prod_left_right.second += a_vec[round_size + j] * b_vec[j];
233 auto [inner_prod_L, inner_prod_R] =
sum_pairs(inner_prods);
237 L_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(0), round_size } },
238 { &G_vec_local[round_size], round_size });
240 L_i += aux_generator * inner_prod_L;
244 R_i = scalar_multiplication::pippenger_unsafe<Curve>({ 0, { &a_vec.at(round_size), round_size } },
245 { &G_vec_local[0], round_size });
246 R_i += aux_generator * inner_prod_R;
256 const Fr round_challenge = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
258 if (round_challenge.
is_zero()) {
261 const Fr round_challenge_inv = round_challenge.
invert();
265 auto G_hi_by_inverse_challenge = GroupElement::batch_mul_with_endomorphism(
266 std::span{ G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size),
267 G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size * 2) },
268 round_challenge_inv);
269 GroupElement::batch_affine_add(
270 std::span{ G_vec_local.begin(), G_vec_local.begin() +
static_cast<std::ptrdiff_t>(round_size) },
271 G_hi_by_inverse_challenge,
281 a_vec.at(j) += round_challenge * a_vec[round_size + j];
282 b_vec[j] += round_challenge_inv * b_vec[round_size + j];
289 transcript->send_to_verifier(
"IPA:G_0", G_vec_local[0]);
293 transcript->send_to_verifier(
"IPA:a_0", a_vec[0]);
306 template <
typename Transcript>
307 static void add_claim_to_hash_buffer(
const CK&
ck,
308 const ProverOpeningClaim<Curve>& opening_claim,
309 const std::shared_ptr<Transcript>& transcript)
321 const auto commitment =
ck.commit(polynomial);
322 transcript->add_to_hash_buffer(
"IPA:commitment", commitment);
323 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.opening_pair.challenge);
324 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.opening_pair.evaluation);
352 static bool reduce_verify_internal_native(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
360 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
362 if (generator_challenge.
is_zero()) {
366 const Commitment aux_generator = Commitment::one() * generator_challenge;
370 const GroupElement C_prime = opening_claim.commitment + (aux_generator * opening_claim.opening_pair.evaluation);
372 const auto pippenger_size = 2 * log_poly_length;
373 std::vector<Fr> round_challenges(log_poly_length);
375 std::vector<Commitment> msm_elements(pippenger_size);
377 std::vector<Fr> msm_scalars(pippenger_size);
381 for (
size_t i = 0; i < log_poly_length; i++) {
383 const auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" +
index);
384 const auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" +
index);
385 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
386 if (round_challenges[i].is_zero()) {
389 msm_elements[2 * i] = element_L;
390 msm_elements[2 * i + 1] = element_R;
393 std::vector<Fr> round_challenges_inv = round_challenges;
397 for (
size_t i = 0; i < log_poly_length; i++) {
398 msm_scalars[2 * i] = round_challenges_inv[i];
399 msm_scalars[2 * i + 1] = round_challenges[i];
404 GroupElement LR_sums = scalar_multiplication::pippenger_unsafe<Curve>(
405 { 0, { &msm_scalars[0], pippenger_size } }, { &msm_elements[0], pippenger_size });
410 const Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
414 Polynomial<Fr> s_vec(
415 construct_poly_from_u_challenges_inv(std::span(round_challenges_inv).subspan(0, log_poly_length)));
425 scalar_multiplication::pippenger_unsafe<Curve>(s_vec, { &srs_elements[0],
poly_length });
426 Commitment G_zero_sent = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
427 BB_ASSERT_EQ(G_zero, G_zero_sent,
"G_0 should be equal to G_0 sent in transcript. IPA verification fails.");
431 auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
436 GroupElement right_hand_side = G_zero * a_zero + aux_generator * a_zero * b_zero;
439 return (C_zero.normalize() == right_hand_side.normalize());
452 template <
typename Transcript>
453 static void add_claim_to_hash_buffer(
const OpeningClaim<Curve>& opening_claim,
454 const std::shared_ptr<Transcript>& transcript)
460 transcript->add_to_hash_buffer(
"IPA:commitment", opening_claim.commitment);
461 transcript->add_to_hash_buffer(
"IPA:challenge", opening_claim.opening_pair.challenge);
462 transcript->add_to_hash_buffer(
"IPA:evaluation", opening_claim.opening_pair.evaluation);
481 static VerifierAccumulator reduce_verify_internal_recursive(
const OpeningClaim<Curve>& opening_claim,
490 const Fr generator_challenge = transcript->template get_challenge<Fr>(
"IPA:generator_challenge");
491 typename Curve::Builder*
builder = generator_challenge.get_context();
493 auto pippenger_size =
494 2 * log_poly_length +
497 std::vector<Fr> round_challenges(log_poly_length);
498 std::vector<Fr> round_challenges_inv(log_poly_length);
499 std::vector<Commitment> msm_elements(
501 std::vector<Fr> msm_scalars(pippenger_size);
506 for (
size_t i = 0; i < log_poly_length; i++) {
509 auto element_L = transcript->template receive_from_prover<Commitment>(
"IPA:L_" +
index);
510 auto element_R = transcript->template receive_from_prover<Commitment>(
"IPA:R_" +
index);
511 round_challenges[i] = transcript->template get_challenge<Fr>(
"IPA:round_challenge_" +
index);
512 round_challenges_inv[i] = round_challenges[i].invert();
514 msm_elements[2 * i] = element_L;
515 msm_elements[2 * i + 1] = element_R;
516 msm_scalars[2 * i] = round_challenges_inv[i];
517 msm_scalars[2 * i + 1] = round_challenges[i];
522 Fr b_zero = evaluate_challenge_poly(round_challenges_inv, opening_claim.opening_pair.challenge);
526 Commitment G_zero = transcript->template receive_from_prover<Commitment>(
"IPA:G_0");
530 const auto a_zero = transcript->template receive_from_prover<Fr>(
"IPA:a_0");
535 const auto last_round_tag = round_challenges.back().get_origin_tag();
536 G_zero.set_origin_tag(last_round_tag);
537 const_cast<Fr&
>(a_zero).set_origin_tag(last_round_tag);
543 msm_elements.emplace_back(-G_zero);
544 msm_elements.emplace_back(-Commitment::one(
builder));
545 msm_scalars.emplace_back(a_zero);
546 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation }));
547 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
548 auto neg_commitment = -opening_claim.commitment;
551 ipa_relation.assert_equal(neg_commitment);
553 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
567 template <
typename Transcript = NativeTranscript>
568 static void compute_opening_proof(
const CK&
ck,
569 const ProverOpeningClaim<Curve>& opening_claim,
570 const std::shared_ptr<Transcript>& transcript)
572 add_claim_to_hash_buffer(
ck, opening_claim, transcript);
573 compute_opening_proof_internal(
ck, opening_claim, transcript);
587 template <
typename Transcript = NativeTranscript>
588 static bool reduce_verify(
const VK&
vk,
589 const OpeningClaim<Curve>& opening_claim,
590 const std::shared_ptr<Transcript>& transcript)
593 add_claim_to_hash_buffer(opening_claim, transcript);
594 return reduce_verify_internal_native(
vk, opening_claim, transcript);
608 static VerifierAccumulator reduce_verify(
const OpeningClaim<Curve>& opening_claim,
const auto& transcript)
614 add_claim_to_hash_buffer(opening_claim, transcript);
615 return reduce_verify_internal_recursive(opening_claim, transcript);
635 static bool full_verify_recursive(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
638 add_claim_to_hash_buffer(opening_claim, transcript);
639 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
640 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
641 auto claimed_G_zero = verifier_accumulator.comm;
651 std::vector<Fr> s_vec_temporaries(poly_length / 2);
652 std::vector<Fr> s_vec(poly_length);
654 Fr* previous_round_s = &s_vec_temporaries[0];
655 Fr* current_round_s = &s_vec[0];
657 if constexpr ((log_poly_length & 1) == 0) {
658 std::swap(previous_round_s, current_round_s);
660 previous_round_s[0] =
Fr(1);
661 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) {
665 current_round_s[j * 2] = previous_round_s[j];
666 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
668 std::swap(current_round_s, previous_round_s);
673 const std::vector<Commitment> srs_elements =
vk.get_monomial_points();
674 Commitment computed_G_zero = Commitment::batch_mul(srs_elements, s_vec);
676 claimed_G_zero.assert_equal(computed_G_zero);
677 BB_ASSERT_EQ(computed_G_zero.get_value(), claimed_G_zero.get_value(),
"G_zero doesn't match received G_zero.");
679 bool running_truth_value = verifier_accumulator.running_truth_value;
680 return running_truth_value;
695 static OpeningClaim<Curve> reduce_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim)
698 const auto& commitments = batch_opening_claim.commitments;
699 const auto& scalars = batch_opening_claim.scalars;
700 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
702 GroupElement shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
704 return { { shplonk_eval_challenge,
Fr(0) }, shplonk_output_commitment };
715 static bool reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
720 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
721 add_claim_to_hash_buffer(opening_claim, transcript);
722 return reduce_verify_internal_native(
vk, opening_claim, transcript);
733 static VerifierAccumulator reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
738 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
739 add_claim_to_hash_buffer(opening_claim, transcript);
740 return reduce_verify_internal_recursive(opening_claim, transcript).verifier_accumulator;
751 static Fr evaluate_challenge_poly(
const std::vector<Fr>& u_challenges_inv,
Fr r)
755 Fr challenge_poly_eval = 1;
759 for (
size_t i = 0; i < log_poly_length - 1; i++) {
760 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
761 challenge_poly_eval *= (
Fr(1) + monomial);
765 Fr monomial = u_challenges_inv[0] * r_pow;
766 challenge_poly_eval *= (
Fr(1) + monomial);
767 return challenge_poly_eval;
781 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
782 std::vector<Fr> u_challenges_inv_2,
787 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
798 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(
const std::span<const bb::fq>& u_challenges_inv)
805 bb::fq* previous_round_s = &s_vec_temporaries[0];
806 bb::fq* current_round_s = &s_vec[0];
808 if ((log_poly_length & 1) == 0) {
809 std::swap(previous_round_s, current_round_s);
811 previous_round_s[0] =
bb::fq(1);
812 for (
size_t i = 0; i < log_poly_length; ++i) {
813 const size_t round_size = 1 << (i + 1);
814 const bb::fq round_challenge = u_challenges_inv[i];
818 current_round_s[j * 2] = previous_round_s[j];
819 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
822 std::swap(current_round_s, previous_round_s);
824 return { s_vec, poly_length };
837 static Polynomial<bb::fq> create_challenge_poly(
const std::vector<bb::fq>& u_challenges_inv_1,
842 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
843 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
844 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
845 challenge_poly += challenge_poly_1;
846 challenge_poly.add_scaled(challenge_poly_2, alpha);
847 return challenge_poly;
867 OpeningClaim<Curve> claim_1,
869 OpeningClaim<Curve> claim_2)
874 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
876 VerifierAccumulator verifier_accumulator_1 = reduce_verify(claim_1, transcript_1);
877 VerifierAccumulator verifier_accumulator_2 = reduce_verify(claim_2, transcript_2);
881 transcript.add_to_hash_buffer(
"u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
882 transcript.add_to_hash_buffer(
"U_1", verifier_accumulator_1.comm);
883 transcript.add_to_hash_buffer(
"u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
884 transcript.add_to_hash_buffer(
"U_2", verifier_accumulator_2.comm);
888 OpeningClaim<Curve> output_claim;
889 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
890 output_claim.opening_pair.challenge = r;
892 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
893 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
898 for (
Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
899 native_u_challenges_inv_1.push_back(
bb::fq(u_inv_i.get_value()));
901 for (
Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
902 native_u_challenges_inv_2.push_back(
bb::fq(u_inv_i.get_value()));
905 Polynomial<bb::fq> challenge_poly =
906 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2,
bb::fq(alpha.get_value()));
910 const OpeningPair<NativeCurve> opening_pair{
bb::fq(output_claim.opening_pair.challenge.get_value()),
911 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
913 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
914 opening_pair.evaluation,
915 "Opening claim does not hold for challenge polynomial.");
917 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
918 ck, { challenge_poly, opening_pair }, prover_transcript);
919 BB_ASSERT_EQ(challenge_poly.evaluate(
bb::fq(output_claim.opening_pair.challenge.get_value())),
920 bb::fq(output_claim.opening_pair.evaluation.get_value()),
921 "Opening claim does not hold for challenge polynomial.");
923 output_claim.opening_pair.evaluation.self_reduce();
924 return { output_claim, prover_transcript->export_proof() };
932 using Builder =
typename Curve::Builder;
933 using Curve = stdlib::grumpkin<Builder>;
935 CommitmentKey<NativeCurve> ipa_commitment_key(poly_length);
936 size_t n = poly_length;
937 auto poly = Polynomial<bb::fq>(n);
938 for (
size_t i = 0; i < n; i++) {
942 bb::fq eval = poly.evaluate(x);
943 auto commitment = ipa_commitment_key.commit(poly);
944 const OpeningPair<NativeCurve> opening_pair = { x, eval };
945 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
947 auto stdlib_comm = Curve::Group::from_witness(&
builder, commitment);
948 auto stdlib_x = Curve::ScalarField::from_witness(&
builder, x);
949 auto stdlib_eval = Curve::ScalarField::from_witness(&
builder, eval);
950 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
952 return { stdlib_opening_claim, ipa_transcript->export_proof() };