Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
bbapi_chonk.cpp
Go to the documentation of this file.
18
19#ifndef __wasm__
20#include <fcntl.h>
21#include <unistd.h>
22#endif
23
24namespace bb::bbapi {
25
27{
28 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
29
30 request.ivc_in_progress = std::make_shared<Chonk>(num_circuits);
31 request.ivc_stack_depth = 0;
32
33 // Clear any stale loaded-circuit state from a previous session so that
34 // ChonkAccumulate cannot silently reuse a circuit loaded before this ChonkStart.
35 request.loaded_circuit_name.clear();
36 request.loaded_circuit_constraints.reset();
37 request.loaded_circuit_vk.clear();
38
39 return Response{};
40}
41
43{
44 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
45 if (!request.ivc_in_progress) {
46 throw_or_abort("Chonk not started. Call ChonkStart first.");
47 }
48
49 request.loaded_circuit_name = circuit.name;
50 request.loaded_circuit_constraints = acir_format::circuit_buf_to_acir_format(std::move(circuit.bytecode));
51 request.loaded_circuit_vk = circuit.verification_key;
52
53 info("ChonkLoad - loaded circuit '", request.loaded_circuit_name, "'");
54
55 return Response{};
56}
57
59{
60 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
61 if (!request.ivc_in_progress) {
62 throw_or_abort("Chonk not started. Call ChonkStart first.");
63 }
64
65 if (!request.loaded_circuit_constraints.has_value()) {
66 throw_or_abort("No circuit loaded. Call ChonkLoad first.");
67 }
68
70 acir_format::AcirProgram program{ std::move(request.loaded_circuit_constraints.value()), std::move(witness_data) };
71
72 // Clear loaded state immediately after moving out of it. This ensures that if any subsequent
73 // step throws, the request won't appear to still have a valid circuit loaded (the optional
74 // would be in a moved-from state, which is technically has_value()==true but poisoned).
75 auto loaded_vk = std::move(request.loaded_circuit_vk);
76 auto circuit_name = std::move(request.loaded_circuit_name);
77 request.loaded_circuit_constraints.reset();
78 request.loaded_circuit_vk.clear();
79 request.loaded_circuit_name.clear();
80
81 // The hiding kernel (MegaZK) is definitionally the last circuit in the IVC stack; derive flag accordingly.
82 auto chonk = std::dynamic_pointer_cast<Chonk>(request.ivc_in_progress);
83 const bool is_hiding_kernel = (request.ivc_stack_depth + 1 == chonk->get_num_circuits());
84
85 const acir_format::ProgramMetadata metadata{ .ivc = request.ivc_in_progress };
86 auto circuit = acir_format::create_circuit<IVCBase::ClientCircuit>(program, metadata);
87
89
90 if (request.vk_policy == VkPolicy::RECOMPUTE) {
91 precomputed_vk = nullptr;
92 } else if (request.vk_policy == VkPolicy::DEFAULT || request.vk_policy == VkPolicy::CHECK) {
93 if (!loaded_vk.empty()) {
94 validate_vk_size<Chonk::MegaVerificationKey>(loaded_vk);
95 precomputed_vk = from_buffer<std::shared_ptr<Chonk::MegaVerificationKey>>(loaded_vk);
96
97 if (request.vk_policy == VkPolicy::CHECK) {
98 // Note that MegaZKVerificationKey = MegaVerificationKey as C++ classes but their content differs
99 // between ZK and non-ZK flavors.
100 auto computed_vk = is_hiding_kernel ? std::make_shared<Chonk::MegaVerificationKey>(
101 Chonk::HidingKernelProverInstance(circuit).get_precomputed())
103 Chonk::ProverInstance(circuit).get_precomputed());
104
105 // Dereference to compare VK contents
106 if (*precomputed_vk != *computed_vk) {
107 throw_or_abort("VK check failed for circuit '" + circuit_name +
108 "': provided VK does not match computed VK");
109 }
110 }
111 }
112 } else {
113 throw_or_abort("Invalid VK policy. Valid options: default, check, recompute");
114 }
115
116 info("ChonkAccumulate - accumulating circuit '", circuit_name, "'");
119 }
120 request.ivc_in_progress->accumulate(circuit, precomputed_vk);
121 request.ivc_stack_depth++;
122
123 return Response{};
124}
125
127{
128 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
129 if (!request.ivc_in_progress) {
130 throw_or_abort("Chonk not started. Call ChonkStart first.");
131 }
132
133 if (request.ivc_stack_depth == 0) {
134 throw_or_abort("No circuits accumulated. Call ChonkAccumulate first.");
135 }
136
137 info("ChonkProve - generating proof for ", request.ivc_stack_depth, " accumulated circuits");
138
139 // Call prove and verify using the appropriate IVC type
140 Response response;
141 bool verification_passed = false;
142
143 info("ChonkProve - using Chonk");
144 auto chonk = std::dynamic_pointer_cast<Chonk>(request.ivc_in_progress);
145 auto proof = chonk->prove();
146 auto vk_and_hash = chonk->get_hiding_kernel_vk_and_hash();
147
148 // We verify this proof. Another bb call to verify has some overhead of loading VK/proof/SRS,
149 // and it is mysterious if this transaction fails later in the lifecycle.
150 info("ChonkProve - verifying the generated proof as a sanity check");
151 ChonkNativeVerifier verifier(vk_and_hash);
152 verification_passed = verifier.verify(proof);
153
154 if (!verification_passed) {
155 throw_or_abort("Failed to verify the generated proof!");
156 }
157
158 response.proof = std::move(proof);
159
160 request.ivc_in_progress.reset();
161 request.ivc_stack_depth = 0;
162
163 return response;
164}
165
167{
168 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
169
171 validate_vk_size<VerificationKey>(vk);
172
173 // Deserialize the hiding kernel verification key directly from buffer
174 auto hiding_kernel_vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vk));
175
176 // Validate total proof size: must match num_public_inputs + fixed overhead
177 const size_t expected_proof_size =
178 static_cast<size_t>(hiding_kernel_vk->num_public_inputs) + ChonkProof::PROOF_LENGTH_WITHOUT_PUB_INPUTS;
179 if (proof.size() != expected_proof_size) {
180 throw_or_abort("ChonkVerify: proof has wrong size: expected " + std::to_string(expected_proof_size) + ", got " +
181 std::to_string(proof.size()));
182 }
183
184 // Verify the proof using ChonkNativeVerifier
185 auto vk_and_hash = std::make_shared<ChonkNativeVerifier::VKAndHash>(hiding_kernel_vk);
186 ChonkNativeVerifier verifier(vk_and_hash);
187 const bool verified = verifier.verify(proof);
188
189 return { .valid = verified };
190}
191
193{
194 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
195
196 if (proofs.size() != vks.size()) {
197 throw_or_abort("ChonkBatchVerify: proofs.size() (" + std::to_string(proofs.size()) + ") != vks.size() (" +
198 std::to_string(vks.size()) + ")");
199 }
200 if (proofs.empty()) {
201 throw_or_abort("ChonkBatchVerify: no proofs provided");
202 }
203
205
206 // Phase 1: Run all non-IPA verification for each proof, collecting IPA claims
209 ipa_claims.reserve(proofs.size());
210 ipa_transcripts.reserve(proofs.size());
211
212 for (size_t i = 0; i < proofs.size(); ++i) {
213 validate_vk_size<VerificationKey>(vks[i]);
214 auto hiding_kernel_vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vks[i]));
215
216 const size_t expected_proof_size =
217 static_cast<size_t>(hiding_kernel_vk->num_public_inputs) + ChonkProof::PROOF_LENGTH_WITHOUT_PUB_INPUTS;
218 if (proofs[i].size() != expected_proof_size) {
219 throw_or_abort("ChonkBatchVerify: proof[" + std::to_string(i) + "] has wrong size: expected " +
220 std::to_string(expected_proof_size) + ", got " + std::to_string(proofs[i].size()));
221 }
222
223 auto vk_and_hash = std::make_shared<ChonkNativeVerifier::VKAndHash>(hiding_kernel_vk);
224 ChonkNativeVerifier verifier(vk_and_hash);
225 auto result = verifier.reduce_to_ipa_claim(std::move(proofs[i]));
226 if (!result.all_checks_passed) {
227 return { .valid = false };
228 }
229 ipa_claims.push_back(std::move(result.ipa_claim));
230 ipa_transcripts.push_back(std::make_shared<NativeTranscript>(std::move(result.ipa_proof)));
231 }
232
233 // Phase 2: Batch IPA verification with single SRS MSM
235 const bool verified = IPA<curve::Grumpkin>::batch_reduce_verify(ipa_vk, ipa_claims, ipa_transcripts);
236
237 return { .valid = verified };
238}
239
240static std::shared_ptr<Chonk::MegaVerificationKey> compute_chonk_vk_from_program(acir_format::AcirProgram& program,
241 bool use_zk_flavor)
242{
243 Chonk::ClientCircuit builder = acir_format::create_circuit<Chonk::ClientCircuit>(program);
244 if (use_zk_flavor) {
246 Chonk::HidingKernelProverInstance(builder).get_precomputed());
247 }
249}
250
252{
253 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
254 info("ChonkComputeVk - deriving MegaVerificationKey for circuit '",
255 circuit.name,
256 "'",
257 use_zk_flavor ? " (MegaZK)" : "");
258
259 auto constraint_system = acir_format::circuit_buf_to_acir_format(std::move(circuit.bytecode));
260
261 acir_format::AcirProgram program{ constraint_system, /*witness=*/{} };
262 auto verification_key = compute_chonk_vk_from_program(program, use_zk_flavor);
263
264 info("ChonkComputeVk - VK derived, size: ", to_buffer(*verification_key).size(), " bytes");
265
266 return { .bytes = to_buffer(*verification_key), .fields = verification_key->to_field_elements() };
267}
268
270{
271 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
273 /*witness=*/{} };
274
275 auto computed_vk = compute_chonk_vk_from_program(program, use_zk_flavor);
276
277 if (circuit.verification_key.empty()) {
278 info("FAIL: Expected precomputed vk for function ", circuit.name);
279 throw_or_abort("Missing precomputed VK");
280 }
281
282 validate_vk_size<Chonk::MegaVerificationKey>(circuit.verification_key);
283
284 // Deserialize directly from buffer
285 auto precomputed_vk = from_buffer<std::shared_ptr<Chonk::MegaVerificationKey>>(circuit.verification_key);
286
287 Response response;
288 response.valid = true;
289 if (*computed_vk != *precomputed_vk) {
290 response.valid = false;
291 response.actual_vk = to_buffer(computed_vk);
292 }
293 return response;
294}
295
297{
298 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
299 Response response;
300
301 const auto constraint_system = acir_format::circuit_buf_to_acir_format(std::move(circuit.bytecode));
302 acir_format::AcirProgram program{ constraint_system, {} };
303
304 // Get IVC constraints if any
305 const auto& ivc_constraints = constraint_system.hn_recursion_constraints;
306
307 // Create metadata with appropriate IVC context
309 .ivc = ivc_constraints.empty() ? nullptr : acir_format::create_mock_chonk_from_constraints(ivc_constraints),
310 .collect_gates_per_opcode = include_gates_per_opcode
311 };
312
313 // Create and finalize circuit
314 auto builder = acir_format::create_circuit<MegaCircuitBuilder>(program, metadata);
315 builder.finalize_circuit();
316
317 // Set response values
318 response.acir_opcodes = program.constraints.num_acir_opcodes;
319 response.circuit_size = static_cast<uint32_t>(builder.num_gates());
320
321 // Optionally include gates per opcode
322 if (include_gates_per_opcode) {
323 response.gates_per_opcode = std::vector<uint32_t>(program.constraints.gates_per_opcode.begin(),
324 program.constraints.gates_per_opcode.end());
325 }
326
327 // Log circuit details
328 info("ChonkStats - circuit: ",
329 circuit.name,
330 ", acir_opcodes: ",
331 response.acir_opcodes,
332 ", circuit_size: ",
333 response.circuit_size);
334
335 // Print execution trace details
336 builder.blocks.summarize();
337
338 return response;
339}
340
342{
343 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
344 return { .compressed_proof = ProofCompressor::compress_chonk_proof(proof) };
345}
346
348{
349 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
350 size_t mega_num_pub = ProofCompressor::compressed_mega_num_public_inputs(compressed_proof.size());
351 return { .proof = ProofCompressor::decompress_chonk_proof(compressed_proof, mega_num_pub) };
352}
353
354// ── Batch Verifier Service ──────────────────────────────────────────────────
355
356#ifndef __wasm__
357
359 uint32_t num_cores,
360 uint32_t batch_size,
361 const std::string& fifo_path)
362{
363 if (running_) {
364 info("ChonkBatchVerifierService: already running, ignoring start()");
365 return;
366 }
367
368 if (num_cores == 0) {
369 num_cores = static_cast<uint32_t>(std::thread::hardware_concurrency());
370 if (num_cores == 0) {
371 num_cores = 1;
372 }
373 }
374
375 writer_shutdown_ = false;
376 running_ = true;
377
378 // Start the writer thread (opens the FIFO, drains result_queue_)
379 writer_thread_ = std::thread([this, path = fifo_path]() { writer_loop(path); });
380
381 // Start the batch processor with a callback that pushes to result_queue_
382 verifier_.start(std::move(vks), num_cores, batch_size, [this](VerifyResult result) {
383 {
384 std::lock_guard lock(result_mutex_);
385 result_queue_.push(std::move(result));
386 }
387 result_cv_.notify_one();
388 });
389
390 info("ChonkBatchVerifierService started, fifo=", fifo_path);
391}
392
397
399{
400 if (!running_) {
401 return;
402 }
403
404 // Stop the processor first (flushes remaining proofs → result_queue_)
405 verifier_.stop();
406
407 // Signal the writer to drain and exit
408 {
409 std::lock_guard lock(result_mutex_);
410 writer_shutdown_ = true;
411 }
412 result_cv_.notify_one();
413
414 if (writer_thread_.joinable()) {
415 writer_thread_.join();
416 }
417
418 running_ = false;
419 info("ChonkBatchVerifierService stopped");
420}
421
428
429void ChonkBatchVerifierService::writer_loop(const std::string& fifo_path)
430{
431 // Open FIFO for writing (blocks until a reader connects)
432 int fd = open(fifo_path.c_str(), O_WRONLY);
433 if (fd < 0) {
434 info("ChonkBatchVerifierService: failed to open FIFO '", fifo_path, "': ", strerror(errno));
435 return;
436 }
437
438 while (true) {
439 VerifyResult result;
440 {
441 std::unique_lock lock(result_mutex_);
442 result_cv_.wait(lock, [this] { return writer_shutdown_ || !result_queue_.empty(); });
443
444 if (!result_queue_.empty()) {
445 result = std::move(result_queue_.front());
446 result_queue_.pop();
447 } else if (writer_shutdown_) {
448 break;
449 } else {
450 continue;
451 }
452 }
453
454 // Serialize to msgpack and write as a length-delimited frame
455 msgpack::sbuffer buf;
456 msgpack::pack(buf, result);
457
458 if (!write_frame(fd, buf.data(), buf.size())) {
459 info("ChonkBatchVerifierService: FIFO write failed, stopping writer");
460 break;
461 }
462 }
463
464 close(fd);
465}
466
467// ── Batch Verifier RPC Commands ─────────────────────────────────────────────
468
470{
471 if (request.batch_verifier_service && request.batch_verifier_service->is_running()) {
472 throw_or_abort("ChonkBatchVerifierStart: service already running. Call ChonkBatchVerifierStop first.");
473 }
474
476
478 parsed_vks.reserve(vks.size());
479
480 for (size_t i = 0; i < vks.size(); ++i) {
481 validate_vk_size<VerificationKey>(vks[i]);
482 auto vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vks[i]));
483 parsed_vks.push_back(std::make_shared<MegaZKFlavor::VKAndHash>(vk));
484 }
485
486 request.batch_verifier_service = std::make_shared<ChonkBatchVerifierService>();
487 request.batch_verifier_service->start(std::move(parsed_vks), num_cores, batch_size, fifo_path);
488 return {};
489}
490
492{
493 if (!request.batch_verifier_service || !request.batch_verifier_service->is_running()) {
494 throw_or_abort("ChonkBatchVerifierQueue: service not running. Call ChonkBatchVerifierStart first.");
495 }
496
497 request.batch_verifier_service->enqueue(VerifyRequest{
498 .request_id = request_id,
499 .vk_index = vk_index,
500 .proof = ChonkProof::from_field_elements(proof_fields),
501 });
502
503 return {};
504}
505
507{
508 if (!request.batch_verifier_service || !request.batch_verifier_service->is_running()) {
509 throw_or_abort("ChonkBatchVerifierStop: service not running.");
510 }
511
512 request.batch_verifier_service->stop();
513 request.batch_verifier_service.reset();
514 return {};
515}
516
517#else // __wasm__
518
520{
521 throw_or_abort("ChonkBatchVerifierStart is not supported in WASM builds");
522}
523
524ChonkBatchVerifierQueue::Response ChonkBatchVerifierQueue::execute(BBApiRequest& /*request*/) &&
525{
526 throw_or_abort("ChonkBatchVerifierQueue is not supported in WASM builds");
527}
528
529ChonkBatchVerifierStop::Response ChonkBatchVerifierStop::execute(BBApiRequest& /*request*/) &&
530{
531 throw_or_abort("ChonkBatchVerifierStop is not supported in WASM builds");
532}
533
534#endif // __wasm__
535
536} // namespace bb::bbapi
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
Chonk-specific command definitions for the Barretenberg RPC API.
void enqueue(VerifyRequest request)
Enqueue a proof for verification.
void stop()
Stop the processor, flushing remaining proofs.
void start(std::vector< std::shared_ptr< MegaZKFlavor::VKAndHash > > vks, uint32_t num_cores, uint32_t batch_size, ResultCallback on_result)
Start the coordinator thread.
Flavor::VerificationKey MegaVerificationKey
Definition chonk.hpp:44
Verifier for Chonk IVC proofs (both native and recursive).
IPAReductionResult reduce_to_ipa_claim(const Proof &proof)
Run Chonk verification up to but not including IPA, returning the IPA claim for deferred verification...
Output verify(const Proof &proof)
Verify a Chonk proof.
static constexpr size_t ECCVM_FIXED_SIZE
IPA (inner product argument) commitment scheme class.
Definition ipa.hpp:86
Base Native verification key class.
Definition flavor.hpp:135
static std::vector< uint8_t > compress_chonk_proof(const ChonkProof &proof)
static ChonkProof decompress_chonk_proof(const std::vector< uint8_t > &compressed, size_t mega_num_public_inputs)
static size_t compressed_mega_num_public_inputs(size_t compressed_bytes)
Derive mega_num_public_inputs from compressed proof size.
Contains all the information required by a Honk prover to create a proof, constructed from a finalize...
Representation of the Grumpkin Verifier Commitment Key inside a bn254 circuit.
void enqueue(VerifyRequest request)
void writer_loop(const std::string &fifo_path)
std::queue< VerifyResult > result_queue_
std::condition_variable result_cv_
void start(std::vector< std::shared_ptr< MegaZKFlavor::VKAndHash > > vks, uint32_t num_cores, uint32_t batch_size, const std::string &fifo_path)
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
WitnessVector witness_buf_to_witness_vector(std::vector< uint8_t > &&buf)
Convert a buffer representing a witness vector into Barretenberg's internal WitnessVector format.
std::shared_ptr< Chonk > create_mock_chonk_from_constraints(const std::vector< RecursionConstraint > &constraints)
Create a Chonk instance with mocked state corresponding to a set of IVC recursion constraints.
std::vector< bb::fr > WitnessVector
AcirFormat circuit_buf_to_acir_format(std::vector< uint8_t > &&buf)
Convert a buffer representing a circuit into Barretenberg's internal AcirFormat representation.
bool use_memory_profile
MemoryProfile GLOBAL_MEMORY_PROFILE
bool write_frame(int fd, const void *data, size_t len)
Write a length-delimited frame to a file descriptor.
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
std::vector< uint8_t > to_buffer(T const &value)
std::vector< size_t > gates_per_opcode
Struct containing both the constraints to be added to the circuit and the witness vector.
Metadata required to create a circuit.
std::shared_ptr< bb::IVCBase > ivc
static constexpr size_t PROOF_LENGTH_WITHOUT_PUB_INPUTS
static ChonkProof_ from_field_elements(const std::vector< FF > &fields)
Reconstruct proof from field elements.
A request to verify a single Chonk proof.
Result of verifying a single proof within a batch.
Empty response indicating successful circuit accumulation.
Response execute(BBApiRequest &request) &&
Response execute(BBApiRequest &request) &&
Response execute(BBApiRequest &request) &&
Response execute(BBApiRequest &request) &&
Response execute(const BBApiRequest &request={}) &&
Contains the validation result.
bool valid
True if the precomputed VK matches the circuit.
Response execute(const BBApiRequest &request={}) &&
Response execute(const BBApiRequest &request={}) &&
Contains the computed verification key in multiple formats.
Response execute(const BBApiRequest &request={}) &&
Response execute(const BBApiRequest &request={}) &&
Empty response indicating successful circuit loading.
Response execute(BBApiRequest &request) &&
Contains the generated IVC proof.
ChonkProof proof
Complete IVC proof for all accumulated circuits.
Response execute(BBApiRequest &request) &&
Empty response indicating successful initialization.
Response execute(BBApiRequest &request) &&
Contains gate count information.
uint32_t circuit_size
Circuit size (total number of gates)
uint32_t acir_opcodes
Number of ACIR opcodes.
std::vector< uint32_t > gates_per_opcode
Optional: gate counts per opcode.
Response execute(BBApiRequest &request) &&
Contains the verification result.
Response execute(const BBApiRequest &request={}) &&
void set_circuit_name(const std::string &name)
void throw_or_abort(std::string const &err)