Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions barretenberg/cpp/src/barretenberg/api/api_chonk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,36 @@ bool ChonkAPI::verify([[maybe_unused]] const Flags& flags,
return response.valid;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the use-case, I guess it is better that the batch size is not one of the arguments?

bool ChonkAPI::batch_verify([[maybe_unused]] const Flags& flags, const std::filesystem::path& proofs_dir)
{
BB_BENCH_NAME("ChonkAPI::batch_verify");

std::vector<ChonkProof> proofs;
std::vector<std::vector<uint8_t>> vks;

for (size_t i = 0;; ++i) {
auto proof_file = proofs_dir / ("proof_" + std::to_string(i));
auto vk_file = proofs_dir / ("vk_" + std::to_string(i));

if (!std::filesystem::exists(proof_file) || !std::filesystem::exists(vk_file)) {
break;
}

auto proof_fields = many_from_buffer<fr>(read_file(proof_file));
proofs.push_back(ChonkProof::from_field_elements(proof_fields));
vks.push_back(read_vk_file(vk_file));
}

if (proofs.empty()) {
throw_or_abort("batch_verify: no proof_0/vk_0 pairs found in " + proofs_dir.string());
}

info("ChonkAPI::batch_verify - found ", proofs.size(), " proof/vk pairs in ", proofs_dir.string());

auto response = bbapi::ChonkBatchVerify{ .proofs = std::move(proofs), .vks = std::move(vks) }.execute();
return response.valid;
}

// WORKTODO(bbapi) remove this
bool ChonkAPI::prove_and_verify(const std::filesystem::path& input_path)
{
Expand Down
8 changes: 8 additions & 0 deletions barretenberg/cpp/src/barretenberg/api/api_chonk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ class ChonkAPI : public API {
const std::filesystem::path& bytecode_path,
const std::filesystem::path& output_path) override;

/**
* @brief Batch-verify multiple Chonk proofs from a directory of proof_N/vk_N pairs.
*
* @param proofs_dir Directory containing proof_0/vk_0, proof_1/vk_1, ...
* @return true if all proofs verify
*/
bool batch_verify(const Flags& flags, const std::filesystem::path& proofs_dir);

/**
* @brief Validate that precomputed VKs in ivc-inputs.msgpack match computed VKs.
*
Expand Down
19 changes: 19 additions & 0 deletions barretenberg/cpp/src/barretenberg/bb/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,20 @@ int parse_and_run_cli_command(int argc, char* argv[])
remove_zk_option(verify);
add_ipa_accumulation_flag(verify);

/***************************************************************************************************************
* Subcommand: batch_verify
***************************************************************************************************************/
std::filesystem::path batch_verify_proofs_dir{ "./proofs" };
CLI::App* batch_verify =
app.add_subcommand("batch_verify", "Batch-verify multiple Chonk proofs with a single IPA SRS MSM.");

add_help_extended_flag(batch_verify);
add_scheme_option(batch_verify);
batch_verify->add_option("--proofs_dir", batch_verify_proofs_dir, "Directory containing proof_N/vk_N pairs.");
add_verbose_flag(batch_verify);
add_debug_flag(batch_verify);
add_crs_path_option(batch_verify);

/***************************************************************************************************************
* Subcommand: write_solidity_verifier
***************************************************************************************************************/
Expand Down Expand Up @@ -954,6 +968,11 @@ int parse_and_run_cli_command(int argc, char* argv[])
}
return api.check_precomputed_vks(flags, ivc_inputs_path) ? 0 : 1;
}
if (batch_verify->parsed()) {
const bool verified = api.batch_verify(flags, batch_verify_proofs_dir);
vinfo("batch verified: ", verified);
return verified ? 0 : 1;
}
return execute_non_prove_command(api);
} else if (flags.scheme == "ultra_honk") {
UltraHonkAPI api;
Expand Down
3 changes: 2 additions & 1 deletion barretenberg/cpp/src/barretenberg/bbapi/bbapi.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ using Commands = ::testing::Types<bbapi::CircuitProve,
bbapi::ChonkAccumulate,
bbapi::ChonkProve,
bbapi::ChonkComputeVk,
bbapi::ChonkCheckPrecomputedVk>;
bbapi::ChonkCheckPrecomputedVk,
bbapi::ChonkBatchVerify>;

// Typed test suites
template <typename T> class BBApiMsgpack : public ::testing::Test {};
Expand Down
38 changes: 38 additions & 0 deletions barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "barretenberg/bbapi/bbapi_chonk.hpp"
#include "barretenberg/chonk/chonk_batch_verifier.hpp"
#include "barretenberg/chonk/chonk_verifier.hpp"
#include "barretenberg/chonk/mock_circuit_producer.hpp"
#include "barretenberg/chonk/proof_compression.hpp"
Expand Down Expand Up @@ -157,6 +158,43 @@ ChonkVerify::Response ChonkVerify::execute(const BBApiRequest& /*request*/) &&
return { .valid = verified };
}

ChonkBatchVerify::Response ChonkBatchVerify::execute(const BBApiRequest& /*request*/) &&
{
BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);

if (proofs.size() != vks.size()) {
throw_or_abort("ChonkBatchVerify: proofs.size() (" + std::to_string(proofs.size()) + ") != vks.size() (" +
std::to_string(vks.size()) + ")");
}
if (proofs.empty()) {
throw_or_abort("ChonkBatchVerify: no proofs provided");
}

using VerificationKey = Chonk::MegaVerificationKey;

std::vector<ChonkBatchVerifier::Input> inputs;
inputs.reserve(proofs.size());

for (size_t i = 0; i < proofs.size(); ++i) {
validate_vk_size<VerificationKey>(vks[i]);
auto hiding_kernel_vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vks[i]));

const size_t expected_proof_size =
static_cast<size_t>(hiding_kernel_vk->num_public_inputs) + ChonkProof::PROOF_LENGTH_WITHOUT_PUB_INPUTS;
if (proofs[i].size() != expected_proof_size) {
throw_or_abort("ChonkBatchVerify: proof[" + std::to_string(i) + "] has wrong size: expected " +
std::to_string(expected_proof_size) + ", got " + std::to_string(proofs[i].size()));
}

auto vk_and_hash = std::make_shared<ChonkNativeVerifier::VKAndHash>(hiding_kernel_vk);
inputs.push_back({ .proof = std::move(proofs[i]), .vk_and_hash = std::move(vk_and_hash) });
}

const bool verified = ChonkBatchVerifier::verify(inputs);

return { .valid = verified };
}

static std::shared_ptr<Chonk::ProverInstance> get_acir_program_prover_instance(acir_format::AcirProgram& program)
{
Chonk::ClientCircuit builder = acir_format::create_circuit<Chonk::ClientCircuit>(program);
Expand Down
21 changes: 21 additions & 0 deletions barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,27 @@ struct ChonkStats {
bool operator==(const ChonkStats&) const = default;
};

/**
* @struct ChonkBatchVerify
* @brief Batch-verify multiple Chonk proofs with a single IPA SRS MSM
*/
struct ChonkBatchVerify {
static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkBatchVerify";

struct Response {
static constexpr const char MSGPACK_SCHEMA_NAME[] = "ChonkBatchVerifyResponse";
bool valid;
MSGPACK_FIELDS(valid);
bool operator==(const Response&) const = default;
};

std::vector<ChonkProof> proofs;
std::vector<std::vector<uint8_t>> vks;
Response execute(const BBApiRequest& request = {}) &&;
MSGPACK_FIELDS(proofs, vks);
bool operator==(const ChonkBatchVerify&) const = default;
};

/**
* @struct ChonkCompressProof
* @brief Compress a Chonk proof to a compact byte representation
Expand Down
2 changes: 2 additions & 0 deletions barretenberg/cpp/src/barretenberg/bbapi/bbapi_execute.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ using Command = NamedUnion<CircuitProve,
ChonkAccumulate,
ChonkProve,
ChonkVerify,
ChonkBatchVerify,
VkAsFields,
MegaVkAsFields,
CircuitWriteSolidityVerifier,
Expand Down Expand Up @@ -79,6 +80,7 @@ using CommandResponse = NamedUnion<ErrorResponse,
ChonkAccumulate::Response,
ChonkProve::Response,
ChonkVerify::Response,
ChonkBatchVerify::Response,
VkAsFields::Response,
MegaVkAsFields::Response,
CircuitWriteSolidityVerifier::Response,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <benchmark/benchmark.h>
#include <chrono>

#include "barretenberg/chonk/chonk_batch_verifier.hpp"
#include "barretenberg/chonk/chonk_verifier.hpp"
#include "barretenberg/chonk/proof_compression.hpp"
#include "barretenberg/chonk/test_bench_shared.hpp"
Expand Down Expand Up @@ -40,6 +41,7 @@ BENCHMARK_DEFINE_F(ChonkBench, VerificationOnly)(benchmark::State& state)
auto [proof, vk_and_hash] = accumulate_and_prove_with_precomputed_vks(NUM_APP_CIRCUITS, precomputed_vks);

for (auto _ : state) {
GOOGLE_BB_BENCH_REPORTER(state);
ChonkNativeVerifier verifier(vk_and_hash);
benchmark::DoNotOptimize(verifier.verify(proof));
}
Expand Down Expand Up @@ -90,12 +92,53 @@ BENCHMARK_DEFINE_F(ChonkBench, ProofDecompress)(benchmark::State& state)
}
}

/**
* @brief Benchmark N individual Chonk verifications (sequential). Baseline for batch comparison.
*/
BENCHMARK_DEFINE_F(ChonkBench, VerifyIndividual)(benchmark::State& state)
{
const size_t num_proofs = static_cast<size_t>(state.range(0));
auto precomputed_vks = precompute_vks(1);

// Generate a single proof and reuse it N times
auto [proof, vk_and_hash] = accumulate_and_prove_with_precomputed_vks(1, precomputed_vks);

for (auto _ : state) {
for (size_t i = 0; i < num_proofs; i++) {
ChonkNativeVerifier verifier(vk_and_hash);
benchmark::DoNotOptimize(verifier.verify(proof));
}
}
}

/**
* @brief Benchmark batch verification of N Chonk proofs (single SRS MSM).
*/
BENCHMARK_DEFINE_F(ChonkBench, BatchVerify)(benchmark::State& state)
{
const size_t num_proofs = static_cast<size_t>(state.range(0));
auto precomputed_vks = precompute_vks(1);

// Generate a single proof and reuse it N times
auto [proof, vk_and_hash] = accumulate_and_prove_with_precomputed_vks(1, precomputed_vks);
std::vector<ChonkBatchVerifier::Input> inputs(num_proofs);
for (size_t i = 0; i < num_proofs; i++) {
inputs[i] = { proof, vk_and_hash };
}

for (auto _ : state) {
benchmark::DoNotOptimize(ChonkBatchVerifier::verify(inputs));
}
}

#define ARGS Arg(ChonkBench::NUM_ITERATIONS_MEDIUM_COMPLEXITY)->Arg(2)

BENCHMARK_REGISTER_F(ChonkBench, Full)->Unit(benchmark::kMillisecond)->ARGS;
BENCHMARK_REGISTER_F(ChonkBench, VerificationOnly)->Unit(benchmark::kMillisecond);
BENCHMARK_REGISTER_F(ChonkBench, ProofCompress)->Unit(benchmark::kMillisecond);
BENCHMARK_REGISTER_F(ChonkBench, ProofDecompress)->Unit(benchmark::kMillisecond);
BENCHMARK_REGISTER_F(ChonkBench, VerifyIndividual)->Unit(benchmark::kMillisecond)->Arg(1)->Arg(2)->Arg(4)->Arg(8);
BENCHMARK_REGISTER_F(ChonkBench, BatchVerify)->Unit(benchmark::kMillisecond)->Arg(1)->Arg(2)->Arg(4)->Arg(8);

} // namespace

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,83 @@ void ipa_verify(State& state) noexcept
BB_ASSERT(result);
}
}

// Batch verification benchmarks: compare N individual verifications vs one batch verification.
// Uses default IPA poly_length (ECCVM size = 2^17) to match real Chonk usage.

// Pre-generated single proof for batch benchmarks (reused N times to avoid expensive setup)
OpeningClaim<Curve> batch_claim;
HonkProof batch_proof_data_single;

static void DoBatchSetup(const benchmark::State&)
{
srs::init_file_crs_factory(srs::bb_crs_path());
static constexpr size_t BENCH_POLY_LENGTH = IPA<Curve>::poly_length;
static bool initialized = false;
if (initialized) {
return;
}
initialized = true;

ck = CommitmentKey<Curve>(BENCH_POLY_LENGTH);
vk = VerifierCommitmentKey<Curve>(BENCH_POLY_LENGTH, srs::get_grumpkin_crs_factory());

numeric::RNG& engine = numeric::get_debug_randomness();
Polynomial poly(BENCH_POLY_LENGTH);
for (size_t j = 0; j < BENCH_POLY_LENGTH; j++) {
poly.at(j) = Fr::random_element(&engine);
}
auto x = Fr::random_element(&engine);
auto eval = poly.evaluate(x);
batch_claim = { { x, eval }, ck.commit(poly) };

auto pt = std::make_shared<NativeTranscript>();
IPA<Curve>::compute_opening_proof(ck, { poly, { x, eval } }, pt);
batch_proof_data_single = pt->export_proof();
}

/**
* @brief Verify N IPA proofs individually (sequential). Baseline for comparison.
*/
void ipa_verify_individual(State& state) noexcept
{
const size_t num_proofs = static_cast<size_t>(state.range(0));
for (auto _ : state) {
state.PauseTiming();
// Create fresh verifier transcripts from the same proof data
std::vector<std::shared_ptr<NativeTranscript>> transcripts(num_proofs);
for (size_t i = 0; i < num_proofs; i++) {
transcripts[i] = std::make_shared<NativeTranscript>(batch_proof_data_single);
}
state.ResumeTiming();

for (size_t i = 0; i < num_proofs; i++) {
auto result = IPA<Curve>::reduce_verify(vk, batch_claim, transcripts[i]);
BB_ASSERT(result);
}
}
}

/**
* @brief Verify N IPA proofs with batched SRS MSM.
*/
void ipa_batch_verify(State& state) noexcept
{
const size_t num_proofs = static_cast<size_t>(state.range(0));
for (auto _ : state) {
state.PauseTiming();
std::vector<OpeningClaim<Curve>> claims(num_proofs, batch_claim);
std::vector<std::shared_ptr<NativeTranscript>> transcripts(num_proofs);
for (size_t i = 0; i < num_proofs; i++) {
transcripts[i] = std::make_shared<NativeTranscript>(batch_proof_data_single);
}
state.ResumeTiming();

auto result = IPA<Curve>::batch_reduce_verify(vk, claims, transcripts);
BB_ASSERT(result);
}
}

} // namespace
BENCHMARK(ipa_open)
->Unit(kMillisecond)
Expand All @@ -75,4 +152,6 @@ BENCHMARK(ipa_verify)
->Unit(kMillisecond)
->DenseRange(MIN_POLYNOMIAL_DEGREE_LOG2, MAX_POLYNOMIAL_DEGREE_LOG2)
->Setup(DoSetup);
BENCHMARK(ipa_verify_individual)->Unit(kMillisecond)->Arg(1)->Arg(2)->Arg(4)->Arg(8)->Setup(DoBatchSetup);
BENCHMARK(ipa_batch_verify)->Unit(kMillisecond)->Arg(1)->Arg(2)->Arg(4)->Arg(8)->Setup(DoBatchSetup);
BENCHMARK_MAIN();
38 changes: 38 additions & 0 deletions barretenberg/cpp/src/barretenberg/chonk/chonk_batch_verifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "chonk_batch_verifier.hpp"
#include "barretenberg/commitment_schemes/ipa/ipa.hpp"
#include "barretenberg/commitment_schemes/verification_key.hpp"
#include "barretenberg/eccvm/eccvm_flavor.hpp"

namespace bb {

bool ChonkBatchVerifier::verify(std::span<const Input> inputs)
{
const size_t num_proofs = inputs.size();
if (num_proofs == 0) {
return true;
}

// Phase 1: Run all non-IPA verification for each proof, collecting IPA claims
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1651): Consider batching and/or multithreading the
// non-IPA portion of verification as well. Becomes significant for moderate batch sizes.
std::vector<OpeningClaim<curve::Grumpkin>> ipa_claims;
std::vector<std::shared_ptr<NativeTranscript>> ipa_transcripts;
ipa_claims.reserve(num_proofs);
ipa_transcripts.reserve(num_proofs);

for (const auto& input : inputs) {
ChonkNativeVerifier verifier(input.vk_and_hash);
auto result = verifier.reduce_to_ipa_claim(input.proof);
if (!result.all_checks_passed) {
return false;
}
ipa_claims.push_back(result.ipa_claim);
ipa_transcripts.push_back(std::make_shared<NativeTranscript>(result.ipa_proof));
}

// Phase 2: Batch IPA verification with single SRS MSM
auto ipa_vk = VerifierCommitmentKey<curve::Grumpkin>{ ECCVMFlavor::ECCVM_FIXED_SIZE };
return IPA<curve::Grumpkin>::batch_reduce_verify(ipa_vk, ipa_claims, ipa_transcripts);
}

} // namespace bb
Loading
Loading