Skip to content

Commit

Permalink
feat: IPA Accumulation implementation (#9494)
Browse files Browse the repository at this point in the history
Adds new functions to the IPA class that accumulate two IPA claims in
circuit, and does prover work to prove new accumulated IPA claim.

Also adds new tests for IPA recursion. The new RecursiveSmall test
allowed us to find a subtle bug in IPA where one value was not being
negated, leading to an incorrect identity. This was not caught
previously because this value, the evaluation, was always 0 in the
existing tests, as Shplonk always sets it to 0.
  • Loading branch information
lucasxia01 authored Nov 4, 2024
1 parent 9d8b43a commit 1a935d0
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 71 deletions.
269 changes: 212 additions & 57 deletions barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,7 @@ template <class PCS> class ZeroMorphTest : public CommitmentTest<typename PCS::C
verified = this->vk()->pairing_check(result[0], result[1]);
} else {
// Execute Verifier protocol with vk
result = PCS::reduce_verify(this->vk(), verifier_opening_claim, verifier_transcript);

verified = result;
verified = PCS::reduce_verify(this->vk(), verifier_opening_claim, verifier_transcript);
}

// The prover and verifier manifests should agree
Expand Down Expand Up @@ -297,9 +295,7 @@ template <class PCS> class ZeroMorphTest : public CommitmentTest<typename PCS::C
verified = this->vk()->pairing_check(result[0], result[1]);
} else {
// Execute Verifier protocol with vk
result = PCS::reduce_verify(this->vk(), verifier_opening_claim, verifier_transcript);

verified = result;
verified = PCS::reduce_verify(this->vk(), verifier_opening_claim, verifier_transcript);
}

// The prover and verifier manifests should agree
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@

#include "barretenberg/circuit_checker/circuit_checker.hpp"
#include "barretenberg/commitment_schemes/commitment_key.test.hpp"
#include "barretenberg/commitment_schemes/ipa/ipa.hpp"
#include "barretenberg/commitment_schemes/shplonk/shplemini.hpp"
#include "barretenberg/stdlib/eccvm_verifier/verifier_commitment_key.hpp"
#include "barretenberg/stdlib/primitives/curves/grumpkin.hpp"
#include "barretenberg/stdlib/transcript/transcript.hpp"
#include "barretenberg/transcript/transcript.hpp"

using namespace bb;

namespace {
using NativeCurve = curve::Grumpkin;
using Builder = UltraCircuitBuilder;
using Curve = stdlib::grumpkin<Builder>;

class IPARecursiveTests : public CommitmentTest<NativeCurve> {
public:
using Fr = typename NativeCurve::ScalarField;
using GroupElement = typename NativeCurve::Element;
using CK = CommitmentKey<NativeCurve>;
using VK = VerifierCommitmentKey<NativeCurve>;
using Polynomial = bb::Polynomial<Fr>;
using Commitment = typename NativeCurve::AffineElement;
using NativeIPA = IPA<NativeCurve>;
using RecursiveIPA = IPA<Curve>;
using StdlibTranscript = bb::stdlib::recursion::honk::UltraStdlibTranscript;

std::pair<std::shared_ptr<StdlibTranscript>, OpeningClaim<Curve>> create_ipa_claim(Builder& builder, const size_t n)
{
// First generate an ipa proof
auto poly = Polynomial::random(n);
// Commit to a zero polynomial
Commitment commitment = this->commit(poly);

auto [x, eval] = this->random_eval(poly);
const OpeningPair<NativeCurve> opening_pair = { x, eval };
const OpeningClaim<NativeCurve> opening_claim{ opening_pair, commitment };

// initialize empty prover transcript
auto prover_transcript = std::make_shared<NativeTranscript>();
NativeIPA::compute_opening_proof(this->ck(), { poly, opening_pair }, prover_transcript);

// initialize verifier transcript from proof data
auto verifier_transcript = std::make_shared<NativeTranscript>(prover_transcript->proof_data);

auto result = NativeIPA::reduce_verify(this->vk(), opening_claim, verifier_transcript);
EXPECT_TRUE(result);

// Recursively verify the proof
auto stdlib_comm = Curve::Group::from_witness(&builder, commitment);
auto stdlib_x = Curve::ScalarField::from_witness(&builder, x);
auto stdlib_eval = Curve::ScalarField::from_witness(&builder, eval);
OpeningClaim<Curve> stdlib_opening_claim{ { stdlib_x, stdlib_eval }, stdlib_comm };

// Construct stdlib verifier transcript
auto recursive_verifier_transcript =
std::make_shared<StdlibTranscript>(bb::convert_proof_to_witness(&builder, prover_transcript->proof_data));
return { recursive_verifier_transcript, stdlib_opening_claim };
}

/**
* @brief Tests IPA recursion
* @details Creates an IPA claim and then runs the recursive IPA verification and checks that the circuit is valid.
* @param POLY_LENGTH
*/
void test_recursive_ipa(const size_t POLY_LENGTH)
{
Builder builder;
auto recursive_verifier_ck = std::make_shared<VerifierCommitmentKey<Curve>>(&builder, POLY_LENGTH, this->vk());
auto [stdlib_transcript, stdlib_claim] = create_ipa_claim(builder, POLY_LENGTH);

RecursiveIPA::reduce_verify(recursive_verifier_ck, stdlib_claim, stdlib_transcript);
builder.finalize_circuit(/*ensure_nonzero=*/false);
info("IPA Recursive Verifier num finalizedgates = ", builder.get_num_finalized_gates());
EXPECT_TRUE(CircuitChecker::check(builder));
}

/**
* @brief Tests IPA accumulation by accumulating two IPA claims and proving the accumulated claim
* @details Creates two IPA claims, and then two IPA accumulators through recursive verification. Proves the
* accumulated claim and checks that it verifies.
* @param POLY_LENGTH
*/
void test_accumulation(const size_t POLY_LENGTH)
{
// We create a circuit that does two IPA verifications. However, we don't do the full verifications and instead
// accumulate the claims into one claim. This accumulation is done in circuit. Create two accumulators, which
// contain the commitment and an opening claim.
Builder builder;
auto recursive_verifier_ck = std::make_shared<VerifierCommitmentKey<Curve>>(&builder, POLY_LENGTH, this->vk());

auto [transcript_1, claim_1] = create_ipa_claim(builder, POLY_LENGTH);
auto [transcript_2, claim_2] = create_ipa_claim(builder, POLY_LENGTH);

// Creates two IPA accumulators and accumulators from the two claims. Also constructs the accumulated h
// polynomial.
auto [output_claim, h_poly] =
RecursiveIPA::accumulate(recursive_verifier_ck, transcript_1, claim_1, transcript_2, claim_2);
builder.finalize_circuit(/*ensure_nonzero=*/false);
info("Circuit with 2 IPA Recursive Verifiers and IPA Accumulation num finalized gates = ",
builder.get_num_finalized_gates());

EXPECT_TRUE(CircuitChecker::check(builder));

// Run the IPA prover on this new accumulated claim.
auto prover_transcript = std::make_shared<NativeTranscript>();
const OpeningPair<NativeCurve> opening_pair{ bb::fq(output_claim.opening_pair.challenge.get_value()),
bb::fq(output_claim.opening_pair.evaluation.get_value()) };
Commitment native_comm = output_claim.commitment.get_value();
const OpeningClaim<NativeCurve> opening_claim{ opening_pair, native_comm };

NativeIPA::compute_opening_proof(this->ck(), { h_poly, opening_pair }, prover_transcript);
// Natively verify this proof to check it.
auto verifier_transcript = std::make_shared<NativeTranscript>(prover_transcript->proof_data);

auto result = NativeIPA::reduce_verify(this->vk(), opening_claim, verifier_transcript);
EXPECT_TRUE(result);
}
};
} // namespace

#define IPA_TEST

/**
* @brief Tests IPA recursion with polynomial of length 4
* @details More details in test_recursive_ipa
*/
TEST_F(IPARecursiveTests, RecursiveSmall)
{
test_recursive_ipa(/*POLY_LENGTH=*/4);
}

/**
* @brief Tests IPA recursion with polynomial of length 1024
* @details More details in test_recursive_ipa
*/
TEST_F(IPARecursiveTests, RecursiveMedium)
{
test_recursive_ipa(/*POLY_LENGTH=*/1024);
}

/**
* @brief Test accumulation with polynomials of length 4
* @details More details in test_accumulation
*/
TEST_F(IPARecursiveTests, AccumulateSmall)
{
test_accumulation(/*POLY_LENGTH=*/4);
}

/**
* @brief Test accumulation with polynomials of length 1024
* @details More details in test_accumulation
*/
TEST_F(IPARecursiveTests, AccumulateMedium)
{
test_accumulation(/*POLY_LENGTH=*/1024);
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,11 @@ template <typename Flavor> void ECCVMRecursiveVerifier_<Flavor>::verify_proof(co
const OpeningClaim batch_opening_claim =
Shplonk::reduce_verification(key->pcs_verification_key->get_g1_identity(), opening_claims, transcript);

const auto batched_opening_verified =
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1142): Handle this return value correctly.
const typename PCS::VerifierAccumulator batched_opening_accumulator =
PCS::reduce_verify(key->pcs_verification_key, batch_opening_claim, transcript);

ASSERT(sumcheck_verified && batched_opening_verified);
ASSERT(sumcheck_verified);
}

template class ECCVMRecursiveVerifier_<ECCVMRecursiveFlavor_<UltraCircuitBuilder>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ template <typename Curve_> class VerifierCommitmentKey {
* @details The Grumpkin SRS points will be initialised as constants in the circuit but might be subsequently
* turned into constant witnesses to make operations in the circuit more efficient.
*/
VerifierCommitmentKey([[maybe_unused]] Builder* builder,
size_t num_points,
std::shared_ptr<VerifierCommitmentKey<NativeEmbeddedCurve>>& native_pcs_verification_key)
VerifierCommitmentKey(
[[maybe_unused]] Builder* builder,
size_t num_points,
const std::shared_ptr<VerifierCommitmentKey<NativeEmbeddedCurve>>& native_pcs_verification_key)
: g1_identity(Commitment(native_pcs_verification_key->get_g1_identity()))
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ TEST_F(GoblinRecursiveVerifierTests, ECCVMFailure)
Builder builder;
GoblinRecursiveVerifier verifier{ &builder, verifier_input };

EXPECT_DEBUG_DEATH(verifier.verify(proof), "(sumcheck_verified && batched_opening_verified)");
EXPECT_DEBUG_DEATH(verifier.verify(proof), "(ipa_relation.get_value.* == -opening_claim.commitment.get_value.*)");
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@


#include <vector>
namespace bb::stdlib::recursion::honk {

// TODO(https://github.com/AztecProtocol/barretenberg/issues/1009): Make the us vector an array after we constify eccvm
// circuit size.
template <typename Curve> struct IpaAccumulator {
std::vector<typename Curve::ScalarField>
u_challenges_inv; // inverses of u challenges that represent the polynomial h
typename Curve::Group comm; // commitment to the polynomial h
};

} // namespace bb::stdlib::recursion::honk
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ template <typename Builder> struct StdlibTranscriptParams {

template <typename T> static inline std::vector<Fr> convert_to_bn254_frs(const T& element)
{
Builder* builder = element.get_context();
return bb::stdlib::field_conversion::convert_to_bn254_frs<Builder, T>(*builder, element);
return bb::stdlib::field_conversion::convert_to_bn254_frs<Builder, T>(element);
}
};

Expand Down

0 comments on commit 1a935d0

Please sign in to comment.