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 msm_elements.emplace_back(-G_zero);
536 msm_elements.emplace_back(-Commitment::one(
builder));
537 msm_scalars.emplace_back(a_zero);
538 msm_scalars.emplace_back(generator_challenge * a_zero.madd(b_zero, { -opening_claim.opening_pair.evaluation }));
539 GroupElement ipa_relation = GroupElement::batch_mul(msm_elements, msm_scalars);
540 auto neg_commitment = -opening_claim.commitment;
543 ipa_relation.assert_equal(neg_commitment);
545 return { round_challenges_inv, G_zero, ipa_relation.get_value() == -opening_claim.commitment.get_value() };
559 template <
typename Transcript = NativeTranscript>
560 static void compute_opening_proof(
const CK&
ck,
561 const ProverOpeningClaim<Curve>& opening_claim,
562 const std::shared_ptr<Transcript>& transcript)
564 add_claim_to_hash_buffer(
ck, opening_claim, transcript);
565 compute_opening_proof_internal(
ck, opening_claim, transcript);
579 template <
typename Transcript = NativeTranscript>
580 static bool reduce_verify(
const VK&
vk,
581 const OpeningClaim<Curve>& opening_claim,
582 const std::shared_ptr<Transcript>& transcript)
585 add_claim_to_hash_buffer(opening_claim, transcript);
586 return reduce_verify_internal_native(
vk, opening_claim, transcript);
600 static VerifierAccumulator reduce_verify(
const OpeningClaim<Curve>& opening_claim,
const auto& transcript)
606 add_claim_to_hash_buffer(opening_claim, transcript);
607 return reduce_verify_internal_recursive(opening_claim, transcript);
627 static bool full_verify_recursive(
const VK&
vk,
const OpeningClaim<Curve>& opening_claim,
auto& transcript)
630 add_claim_to_hash_buffer(opening_claim, transcript);
631 VerifierAccumulator verifier_accumulator = reduce_verify_internal_recursive(opening_claim, transcript);
632 auto round_challenges_inv = verifier_accumulator.u_challenges_inv;
633 auto claimed_G_zero = verifier_accumulator.comm;
643 std::vector<Fr> s_vec_temporaries(
poly_length / 2);
646 Fr* previous_round_s = &s_vec_temporaries[0];
647 Fr* current_round_s = &s_vec[0];
649 if constexpr ((log_poly_length & 1) == 0) {
650 std::swap(previous_round_s, current_round_s);
652 previous_round_s[0] =
Fr(1);
653 for (
size_t i = 0; i < log_poly_length; ++i) {
654 const size_t round_size = 1 << (i + 1);
655 const Fr round_challenge = round_challenges_inv[i];
656 for (
size_t j = 0; j < round_size / 2; ++j) {
657 current_round_s[j * 2] = previous_round_s[j];
658 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
660 std::swap(current_round_s, previous_round_s);
665 const std::vector<Commitment> srs_elements =
vk.get_monomial_points();
666 Commitment computed_G_zero = Commitment::batch_mul(srs_elements, s_vec);
668 claimed_G_zero.assert_equal(computed_G_zero);
669 BB_ASSERT_EQ(computed_G_zero.get_value(), claimed_G_zero.get_value(),
"G_zero doesn't match received G_zero.");
671 bool running_truth_value = verifier_accumulator.running_truth_value;
672 return running_truth_value;
687 static OpeningClaim<Curve> reduce_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim)
690 const auto& commitments = batch_opening_claim.commitments;
691 const auto& scalars = batch_opening_claim.scalars;
692 const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
694 GroupElement shplonk_output_commitment = GroupElement::batch_mul(commitments, scalars);
696 return { { shplonk_eval_challenge,
Fr(0) }, shplonk_output_commitment };
707 static bool reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
712 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
713 add_claim_to_hash_buffer(opening_claim, transcript);
714 return reduce_verify_internal_native(
vk, opening_claim, transcript);
725 static VerifierAccumulator reduce_verify_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim,
730 const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
731 add_claim_to_hash_buffer(opening_claim, transcript);
732 return reduce_verify_internal_recursive(opening_claim, transcript).verifier_accumulator;
743 static Fr evaluate_challenge_poly(
const std::vector<Fr>& u_challenges_inv,
Fr r)
747 Fr challenge_poly_eval = 1;
751 for (
size_t i = 0; i < log_poly_length - 1; i++) {
752 Fr monomial = u_challenges_inv[log_poly_length - 1 - i] * r_pow;
753 challenge_poly_eval *= (
Fr(1) + monomial);
757 Fr monomial = u_challenges_inv[0] * r_pow;
758 challenge_poly_eval *= (
Fr(1) + monomial);
759 return challenge_poly_eval;
773 static Fr evaluate_and_accumulate_challenge_polys(std::vector<Fr> u_challenges_inv_1,
774 std::vector<Fr> u_challenges_inv_2,
779 evaluate_challenge_poly(u_challenges_inv_1, r) + alpha * evaluate_challenge_poly(u_challenges_inv_2, r);
790 static Polynomial<bb::fq> construct_poly_from_u_challenges_inv(
const std::span<const bb::fq>& u_challenges_inv)
797 bb::fq* previous_round_s = &s_vec_temporaries[0];
798 bb::fq* current_round_s = &s_vec[0];
800 if ((log_poly_length & 1) == 0) {
801 std::swap(previous_round_s, current_round_s);
803 previous_round_s[0] =
bb::fq(1);
804 for (
size_t i = 0; i < log_poly_length; ++i) {
805 const size_t round_size = 1 << (i + 1);
806 const bb::fq round_challenge = u_challenges_inv[i];
810 current_round_s[j * 2] = previous_round_s[j];
811 current_round_s[j * 2 + 1] = previous_round_s[j] * round_challenge;
814 std::swap(current_round_s, previous_round_s);
829 static Polynomial<bb::fq> create_challenge_poly(
const std::vector<bb::fq>& u_challenges_inv_1,
834 Polynomial<bb::fq> challenge_poly(1 << log_poly_length);
835 Polynomial challenge_poly_1 = construct_poly_from_u_challenges_inv(u_challenges_inv_1);
836 Polynomial challenge_poly_2 = construct_poly_from_u_challenges_inv(u_challenges_inv_2);
837 challenge_poly += challenge_poly_1;
838 challenge_poly.add_scaled(challenge_poly_2, alpha);
839 return challenge_poly;
859 OpeningClaim<Curve> claim_1,
861 OpeningClaim<Curve> claim_2)
866 static_assert(IsAnyOf<typename Curve::Builder, UltraCircuitBuilder>);
873 transcript.add_to_hash_buffer(
"u_challenges_inv_1", verifier_accumulator_1.u_challenges_inv);
874 transcript.add_to_hash_buffer(
"U_1", verifier_accumulator_1.comm);
875 transcript.add_to_hash_buffer(
"u_challenges_inv_2", verifier_accumulator_2.u_challenges_inv);
876 transcript.add_to_hash_buffer(
"U_2", verifier_accumulator_2.comm);
880 OpeningClaim<Curve> output_claim;
881 output_claim.commitment = verifier_accumulator_1.comm + verifier_accumulator_2.comm * alpha;
882 output_claim.opening_pair.challenge = r;
884 output_claim.opening_pair.evaluation = evaluate_and_accumulate_challenge_polys(
885 verifier_accumulator_1.u_challenges_inv, verifier_accumulator_2.u_challenges_inv, r, alpha);
890 for (
Fr u_inv_i : verifier_accumulator_1.u_challenges_inv) {
891 native_u_challenges_inv_1.push_back(
bb::fq(u_inv_i.get_value()));
893 for (
Fr u_inv_i : verifier_accumulator_2.u_challenges_inv) {
894 native_u_challenges_inv_2.push_back(
bb::fq(u_inv_i.get_value()));
897 Polynomial<bb::fq> challenge_poly =
898 create_challenge_poly(native_u_challenges_inv_1, native_u_challenges_inv_2,
bb::fq(alpha.get_value()));
902 const OpeningPair<NativeCurve> opening_pair{
bb::fq(output_claim.opening_pair.challenge.get_value()),
903 bb::fq(output_claim.opening_pair.evaluation.get_value()) };
905 BB_ASSERT_EQ(challenge_poly.evaluate(opening_pair.challenge),
906 opening_pair.evaluation,
907 "Opening claim does not hold for challenge polynomial.");
909 IPA<NativeCurve, log_poly_length>::compute_opening_proof(
910 ck, { challenge_poly, opening_pair }, prover_transcript);
911 BB_ASSERT_EQ(challenge_poly.evaluate(
bb::fq(output_claim.opening_pair.challenge.get_value())),
912 bb::fq(output_claim.opening_pair.evaluation.get_value()),
913 "Opening claim does not hold for challenge polynomial.");
915 output_claim.opening_pair.evaluation.self_reduce();
916 return { output_claim, prover_transcript->export_proof() };
924 using Builder =
typename Curve::Builder;
925 using Curve = stdlib::grumpkin<Builder>;
927 CommitmentKey<NativeCurve> ipa_commitment_key(
poly_length);
929 auto poly = Polynomial<bb::fq>(n);
930 for (
size_t i = 0; i < n; i++) {
934 bb::fq eval = poly.evaluate(x);
935 auto commitment = ipa_commitment_key.commit(poly);
936 const OpeningPair<NativeCurve> opening_pair = { x, eval };
937 IPA<NativeCurve>::compute_opening_proof(ipa_commitment_key, { poly, opening_pair }, ipa_transcript);
939 auto stdlib_comm = Curve::Group::from_witness(&
builder, commitment);
940 auto stdlib_x = Curve::ScalarField::from_witness(&
builder, x);
941 auto stdlib_eval = Curve::ScalarField::from_witness(&
builder, eval);
942 OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };
944 return { stdlib_opening_claim, ipa_transcript->export_proof() };