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
72 changes: 22 additions & 50 deletions barretenberg/cpp/src/barretenberg/api/api_chonk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
#include "barretenberg/api/log.hpp"
#include "barretenberg/bbapi/bbapi.hpp"
#include "barretenberg/chonk/chonk.hpp"
#include "barretenberg/chonk/chonk_verifier.hpp"
#include "barretenberg/chonk/mock_circuit_producer.hpp"
#include "barretenberg/chonk/private_execution_steps.hpp"
#include "barretenberg/chonk/proof_compression.hpp"
#include "barretenberg/common/get_bytecode.hpp"
Expand Down Expand Up @@ -68,7 +66,8 @@ void ChonkAPI::prove(const Flags& flags,
BB_BENCH_NAME("ChonkAPI::prove");
bbapi::BBApiRequest request;
request.vk_policy = bbapi::parse_vk_policy(flags.vk_policy);
std::vector<PrivateExecutionStepRaw> raw_steps = PrivateExecutionStepRaw::load_and_decompress(input_path);
// Steps stay compressed until accumulated, so only one decompressed step is resident at a time.
std::vector<PrivateExecutionStepRaw> raw_steps = PrivateExecutionStepRaw::load(input_path);

std::vector<CircuitKind> kinds;
kinds.reserve(raw_steps.size());
Expand All @@ -77,48 +76,38 @@ void ChonkAPI::prove(const Flags& flags,
}
bbapi::ChonkStart{ .kinds = std::move(kinds) }.execute(request);
info("Chonk: starting with ", raw_steps.size(), " circuits");
for (size_t i = 0; i < raw_steps.size(); ++i) {
const auto& step = raw_steps[i];

for (auto& step : raw_steps) {
step.self_decompress();
bbapi::ChonkLoad{
.circuit = { .name = step.function_name, .bytecode = step.bytecode, .verification_key = step.vk },
.circuit = { .name = step.function_name,
.bytecode = std::move(step.bytecode),
.verification_key = std::move(step.vk) },
.kind = step.kind,
}
.execute(request);

// NOLINTNEXTLINE(bugprone-unchecked-optional-access): we know the optional has been set here.
info("Chonk: accumulating " + step.function_name);
bbapi::ChonkAccumulate{ .witness = step.witness }.execute(request);
bbapi::ChonkAccumulate{ .witness = std::move(step.witness) }.execute(request);
}

auto proof = bbapi::ChonkProve{}.execute(request).proof;

const bool output_to_stdout = output_dir == "-";

const auto write_proof = [&]() {
const auto proof_fields = proof.to_field_elements();
if (output_to_stdout) {
vinfo("writing Chonk proof to stdout");
write_bytes_to_stdout(to_buffer(proof_fields));
} else if (flags.output_format == "json") {
vinfo("writing Chonk proof (JSON) in directory ", output_dir);
// Note: Chonk proof doesn't have a vk_hash, so we pass an empty string
std::string json_content = ProofJson::build(proof_fields, "", flags.scheme);
write_file(output_dir / "proof.json", std::vector<uint8_t>(json_content.begin(), json_content.end()));
info("Proof (JSON) saved to ", output_dir / "proof.json");
} else {
vinfo("writing Chonk proof in directory ", output_dir);
write_file(output_dir / "proof", to_buffer(proof_fields));
}
};

write_proof();

if (flags.write_vk) {
vinfo("writing Chonk vk in directory ", output_dir);
// Write CHONK vk for the hiding kernel (last step) — proven as MegaZK.
Flags hiding_flags = flags;
hiding_flags.circuit_kind = "hiding";
write_chonk_vk(raw_steps[raw_steps.size() - 1].bytecode, output_dir, hiding_flags);
const auto proof_fields = proof.to_field_elements();
if (output_to_stdout) {
vinfo("writing Chonk proof to stdout");
write_bytes_to_stdout(to_buffer(proof_fields));
} else if (flags.output_format == "json") {
vinfo("writing Chonk proof (JSON) in directory ", output_dir);
// Note: Chonk proof doesn't have a vk_hash, so we pass an empty string
std::string json_content = ProofJson::build(proof_fields, "", flags.scheme);
write_file(output_dir / "proof.json", std::vector<uint8_t>(json_content.begin(), json_content.end()));
info("Proof (JSON) saved to ", output_dir / "proof.json");
} else {
vinfo("writing Chonk proof in directory ", output_dir);
write_file(output_dir / "proof", to_buffer(proof_fields));
}
}

Expand Down Expand Up @@ -192,22 +181,6 @@ void ChonkAPI::proof_stats(const std::filesystem::path& proof_path, const std::f
}
}

// WORKTODO(bbapi) remove this
bool ChonkAPI::prove_and_verify(const std::filesystem::path& input_path)
{
PrivateExecutionSteps steps;
steps.parse(PrivateExecutionStepRaw::load_and_decompress(input_path));

std::shared_ptr<Chonk> ivc = steps.accumulate();
// Construct the hiding kernel as the final step of the IVC

auto proof = ivc->prove();
auto vk_and_hash = ivc->get_hiding_kernel_vk_and_hash();
ChonkNativeVerifier verifier(vk_and_hash);
const bool verified = verifier.verify(proof);
return verified;
}

void ChonkAPI::gates(const Flags& flags, const std::filesystem::path& bytecode_path)
{
BB_BENCH_NAME("ChonkAPI::gates");
Expand All @@ -225,7 +198,6 @@ void ChonkAPI::write_solidity_verifier([[maybe_unused]] const Flags& flags,
bool ChonkAPI::check_precomputed_vks(const Flags& flags, const std::filesystem::path& input_path)
{
BB_BENCH_NAME("ChonkAPI::check_precomputed_vks");
bbapi::BBApiRequest request;
std::vector<PrivateExecutionStepRaw> raw_steps = PrivateExecutionStepRaw::load_and_decompress(input_path);

bbapi::VkPolicy vk_policy = bbapi::parse_vk_policy(flags.vk_policy);
Expand Down
15 changes: 5 additions & 10 deletions barretenberg/cpp/src/barretenberg/api/api_chonk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace bb {
* TypeScript (private function calls)
* → msgpack encode to ivc-inputs.msgpack
* → ChonkAPI::prove() loads and accumulates all steps
* → Chonk proof + VK written to output directory
* → Chonk proof written to output directory
* ```
*
* **Key entry points:**
Expand All @@ -27,7 +27,7 @@ namespace bb {
* - `write_vk()`: Computes verification key for standalone or IVC circuits
* - `check_precomputed_vks()`: Validates that precomputed VKs match computed ones
*
* @see PrivateExecutionSteps for the input format
* @see PrivateExecutionStepRaw for the input format
* @see Chonk for the underlying IVC implementation
*/
class ChonkAPI : public API {
Expand All @@ -37,11 +37,11 @@ class ChonkAPI : public API {
* @brief Main production entry point: generate a Chonk proof from private execution steps.
*
* @details Loads execution steps from ivc-inputs.msgpack, accumulates each circuit via Chonk,
* and writes the final proof (and optionally VK) to the output directory.
* and writes the final proof to the output directory.
*
* @param flags Runtime flags (vk_policy, write_vk, etc.)
* @param flags Runtime flags (vk_policy, output_format, etc.)
* @param input_path Path to ivc-inputs.msgpack containing PrivateExecutionStepRaw entries
* @param output_dir Directory to write proof (and vk if flags.write_vk is set)
* @param output_dir Directory to write the proof
*/
void prove(const Flags& flags, const std::filesystem::path& input_path, const std::filesystem::path& output_dir);

Expand All @@ -57,11 +57,6 @@ class ChonkAPI : public API {
const std::filesystem::path& proof_path,
const std::filesystem::path& vk_path) override;

/**
* @brief Test/debug function: prove and verify in one call (bypasses serialization).
*/
bool prove_and_verify(const std::filesystem::path& input_path);

/**
* @brief Output gate count statistics for a circuit.
*/
Expand Down
4 changes: 2 additions & 2 deletions barretenberg/cpp/src/barretenberg/api/file_io.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ inline std::vector<uint8_t> read_vk_file(const std::filesystem::path& vk_path)
return read_file(vk_path.string());
} catch (const std::runtime_error&) {
THROW std::runtime_error("Unable to open file: " + vk_path.string() +
"\nGenerate a vk during proving by running `bb prove` with an additional `--write_vk` "
"flag, or run `bb write_vk` to generate a standalone vk."
"\nRun `bb write_vk` to generate a vk. For UltraHonk, you can also generate it during "
"proving with `bb prove --write_vk`."
"\nIf you already have a vk file, specify its path with `--vk_path <path>`.");
}
}
Expand Down
7 changes: 6 additions & 1 deletion barretenberg/cpp/src/barretenberg/bb/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,8 @@ int parse_and_run_cli_command(int argc, char* argv[])
};

const auto add_write_vk_flag = [&](CLI::App* subcommand) {
return subcommand->add_flag("--write_vk", flags.write_vk, "Write the provided circuit's verification key");
return subcommand->add_flag(
"--write_vk", flags.write_vk, "Write the provided circuit's verification key (UltraHonk only)");
};

const auto remove_zk_option = [&](CLI::App* subcommand) {
Expand Down Expand Up @@ -1007,6 +1008,10 @@ int parse_and_run_cli_command(int argc, char* argv[])
} else if (flags.scheme == "chonk") {
ChonkAPI api;
if (prove->parsed()) {
if (flags.write_vk) {
throw_or_abort("prove --write_vk is not supported for Chonk. Use bb write_vk --scheme chonk "
"--circuit_kind hiding -b <hiding-kernel-bytecode> -o <output-directory>.");
}
if (!std::filesystem::exists(ivc_inputs_path)) {
throw_or_abort("The prove command for Chonk expect a valid file passed with --ivc_inputs_path "
"<ivc-inputs.msgpack> (default ./ivc-inputs.msgpack)");
Expand Down
45 changes: 45 additions & 0 deletions barretenberg/cpp/src/barretenberg/bbapi/bbapi.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,51 @@ TEST(BBApiInputValidation, MsgpackLoadRejectsTrailingData)
std::filesystem::remove(tmp);
}

TEST(PrivateExecutionStepRaw, SelfDecompressIsIdempotent)
{
const PrivateExecutionStepRaw step{ .bytecode = { 1, 2, 3 }, .witness = { 4, 5 }, .vk = {}, .function_name = "f" };
auto tmp = std::filesystem::temp_directory_path() / "bb_test_step_idempotent.msgpack";
PrivateExecutionStepRaw::compress_and_save({ step }, tmp);

auto loaded = PrivateExecutionStepRaw::load(tmp);
std::filesystem::remove(tmp);
ASSERT_EQ(loaded.size(), 1);
loaded[0].self_decompress();
loaded[0].self_decompress();

EXPECT_EQ(loaded[0].bytecode, step.bytecode);
EXPECT_EQ(loaded[0].witness, step.witness);
}

TEST(PrivateExecutionStepRaw, SelfDecompressLeavesUncompressedStepsUnchanged)
{
const PrivateExecutionStepRaw step{ .bytecode = { 1, 2, 3 }, .witness = { 4, 5 }, .vk = {}, .function_name = "f" };
auto parsed = PrivateExecutionStepRaw::parse_uncompressed(pack_steps({ step }));
ASSERT_EQ(parsed.size(), 1);

parsed[0].self_decompress();

EXPECT_EQ(parsed[0].bytecode, step.bytecode);
EXPECT_EQ(parsed[0].witness, step.witness);
}

TEST(PrivateExecutionStepRaw, CompressAndSaveDoesNotRecompressLoadedSteps)
{
const PrivateExecutionStepRaw step{ .bytecode = { 1, 2, 3 }, .witness = { 4, 5 }, .vk = {}, .function_name = "f" };
auto first = std::filesystem::temp_directory_path() / "bb_test_step_first.msgpack";
auto second = std::filesystem::temp_directory_path() / "bb_test_step_second.msgpack";
PrivateExecutionStepRaw::compress_and_save({ step }, first);

PrivateExecutionStepRaw::compress_and_save(PrivateExecutionStepRaw::load(first), second);
auto reloaded = PrivateExecutionStepRaw::load_and_decompress(second);
std::filesystem::remove(first);
std::filesystem::remove(second);

ASSERT_EQ(reloaded.size(), 1);
EXPECT_EQ(reloaded[0].bytecode, step.bytecode);
EXPECT_EQ(reloaded[0].witness, step.witness);
}

// Regression tests for AesEncrypt/AesDecrypt input validation.
// Without these guards, a socket client could force a ~4 GB allocation via the
// uint32_t `length` field, or pass `length != plaintext.size()` and silently
Expand Down
5 changes: 2 additions & 3 deletions barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,8 @@ ChonkComputeVk::Response ChonkComputeVk::execute([[maybe_unused]] const BBApiReq
static_cast<int>(kind),
")");

auto constraint_system = acir_format::circuit_buf_to_mega_acir_format(std::move(circuit.bytecode));

acir_format::AcirProgram program{ constraint_system, /*witness=*/{} };
acir_format::AcirProgram program{ acir_format::circuit_buf_to_mega_acir_format(std::move(circuit.bytecode)),
/*witness=*/{} };
auto verification_key = compute_chonk_vk(program, kind);
info("ChonkComputeVk - VK derived, size: ", verification_key.bytes.size(), " bytes");
return { .bytes = std::move(verification_key.bytes), .fields = std::move(verification_key.fields) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,13 @@ class ChonkPinnedIvcInputsTest : public ::testing::Test {
auto raw_steps = bb::PrivateExecutionStepRaw::load_and_decompress(inputs_path);
ASSERT_FALSE(raw_steps.empty()) << "no execution steps in " << inputs_path;

const auto hiding_bytecode = raw_steps.back().bytecode;
ASSERT_EQ(raw_steps.back().kind, bb::CircuitKind::HidingKernel);
auto hiding_bytecode = raw_steps.back().bytecode;
const auto hiding_vk = raw_steps.back().vk;
const auto derived_vk = bb::bbapi::ChonkComputeVk{ .circuit = { .bytecode = std::move(hiding_bytecode) },
.kind = bb::CircuitKind::HidingKernel }
.execute();
ASSERT_EQ(hiding_vk, derived_vk.bytes) << "embedded hiding VK differs from bytecode-derived VK";

bb::bbapi::BBApiRequest request;
request.vk_policy = bb::bbapi::VkPolicy::DEFAULT;
Expand All @@ -108,7 +113,7 @@ class ChonkPinnedIvcInputsTest : public ::testing::Test {
auto prove_response = bb::bbapi::ChonkProve{}.execute(request);

auto verify_response =
bb::bbapi::ChonkVerify{ .proof = std::move(prove_response.proof), .vk = hiding_vk }.execute();
bb::bbapi::ChonkVerify{ .proof = std::move(prove_response.proof), .vk = derived_vk.bytes }.execute();
EXPECT_TRUE(verify_response.valid) << "ChonkVerify rejected " << flow_dir.filename();
}
};
Expand Down
26 changes: 14 additions & 12 deletions barretenberg/cpp/src/barretenberg/chonk/chonk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,10 +499,8 @@ void Chonk::accumulate_hiding_kernel(ClientCircuit& circuit,
vinfo("Constructing hiding kernel instance (proving deferred to prove())");
hiding_prover_inst = std::make_shared<HidingKernelProverInstance>(circuit);

// Free circuit block memory now that trace data has been copied to prover polynomials
for (auto& block : circuit.blocks.get()) {
block.free_data();
}
// The circuit has been copied into the prover polynomials; free its construction data.
circuit.free_construction_data();

if (precomputed_vk) {
#ifndef NDEBUG
Expand Down Expand Up @@ -531,10 +529,8 @@ HonkProof Chonk::instance_to_accumulator(ClientCircuit& circuit,
#ifndef NDEBUG
debug_incoming_circuit<InstanceFlavor>(circuit, prover_instance, vk);
#endif
// Free circuit block memory (wires and selectors) now that they've been copied to prover polynomials.
for (auto& block : circuit.blocks.get()) {
block.free_data();
}
// The circuit has been copied into the prover polynomials; free its construction data.
circuit.free_construction_data();

// Run sumcheck on the incoming instance; the folding prover caches the resulting claim. The claims of the whole
// group are batched together once, when the group's last circuit is accumulated (see prove_multilinear_batching).
Expand Down Expand Up @@ -662,6 +658,9 @@ void Chonk::accumulate(ClientCircuit& circuit, const CircuitVerificationKey& vk)
#ifndef NDEBUG
verify_decider_natively();
#endif
// The decider proof is the last use of the final accumulator's polynomials.
prover_accumulator.non_shifted_polynomial = ProverAccumulator::Polynomial{};
prover_accumulator.shifted_polynomial = ProverAccumulator::Polynomial{};
goblin.prove_batch_merge();
}
break;
Expand Down Expand Up @@ -749,10 +748,13 @@ ChonkProof Chonk::prove()
goblin.prove_eccvm();
vinfo("finished eccvm proving.");

// Phase 4: Build translator proving key from ECCVM-derived challenges.
TranslatorCircuitBuilder translator_builder(
goblin.translation_batching_challenge_v, goblin.evaluation_challenge_x, goblin.op_queue);
auto translator_key = std::make_shared<TranslatorProvingKey>(translator_builder);
// Phase 4: Build translator proving key from ECCVM-derived challenges. The key copies the circuit into its
// polynomials, so the circuit builder is released before proving.
auto translator_key = [&] {
TranslatorCircuitBuilder translator_builder(
goblin.translation_batching_challenge_v, goblin.evaluation_challenge_x, goblin.op_queue);
return std::make_shared<TranslatorProvingKey>(translator_builder);
}();

// Phase 5: Translator Oink + Joint Sumcheck + Joint PCS on the shared transcript.
vinfo("prove translator and joint...");
Expand Down
32 changes: 20 additions & 12 deletions barretenberg/cpp/src/barretenberg/chonk/chonk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,20 +160,32 @@ class Chonk {
size_t num_circuits; // total number of circuits to be accumulated in the IVC

size_t num_circuits_accumulated = 0; // number of circuits accumulated so far
public:
ProverAccumulator prover_accumulator; // previous accumulator (output of the previous kernel's batch)

std::shared_ptr<FoldingProver> folding_prover; // folds the current group; (re)created at each group start
#ifndef NDEBUG
VerifierAccumulator native_verifier_accum;
std::shared_ptr<HypernovaFoldingNativeVerifier> native_folding_verifier; // native cross-check of the current group
std::shared_ptr<Transcript> native_verifier_accumulation_transcript = std::make_shared<Transcript>();
#endif

// Management of linking databus commitments between circuits in the IVC
DataBusDepot bus_depot;

// Hiding kernel prover instance: built during accumulate_hiding_kernel(), consumed by prove().
std::shared_ptr<HidingKernelProverInstance> hiding_prover_inst;

protected:
// Previous accumulator (output of the previous kernel's batch). Its polynomials are released once the hiding
// kernel's decider proof is constructed.
ProverAccumulator prover_accumulator;

public:
HonkProof
multilinear_batch_proof; // current kernel's multilinear batching proof (consumed in its recursive verifier)

HonkProof decider_proof; // decider proof to be verified in the Hiding kernel

VerifierAccumulator recursive_verifier_native_accum; // native value of the previous accumulator
#ifndef NDEBUG
VerifierAccumulator native_verifier_accum;
std::shared_ptr<HypernovaFoldingNativeVerifier> native_folding_verifier; // native cross-check of the current group
std::shared_ptr<Transcript> native_verifier_accumulation_transcript = std::make_shared<Transcript>();
#endif

// PARALLEL QUEUES: These two queues must stay synchronized.
// - verification_queue: Native proofs created by accumulate() (prover side)
Expand All @@ -182,13 +194,9 @@ class Chonk {
VerificationQueue verification_queue;
StdlibVerificationQueue stdlib_verification_queue;

// Management of linking databus commitments between circuits in the IVC
DataBusDepot bus_depot;

Goblin goblin;

// Hiding kernel prover state: built during accumulate_hiding_kernel(), consumed by prove().
std::shared_ptr<HidingKernelProverInstance> hiding_prover_inst;
// Hiding kernel VK: set during accumulate_hiding_kernel().
std::shared_ptr<MegaZKVerificationKey> hiding_vk;

/**
Expand Down
Loading
Loading