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
197 validate_vk_size<VerificationKey>(vk);
198
199 auto hiding_kernel_vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vk));
200
201 // Validate total field count: must match num_public_inputs + fixed overhead.
202 const size_t expected_field_count =
203 static_cast<size_t>(hiding_kernel_vk->num_public_inputs) + ChonkProof::PROOF_LENGTH_WITHOUT_PUB_INPUTS;
204 if (proof.size() != expected_field_count) {
205 throw_or_abort("ChonkVerifyFromFields: proof has wrong field count: expected " +
206 std::to_string(expected_field_count) + ", got " + std::to_string(proof.size()));
207 }
208
209 // Split the flat field array into the structured ChonkProof. Layout knowledge stays here.
210 auto structured = ChonkProof::from_field_elements(proof);
211
212 auto vk_and_hash = std::make_shared<ChonkNativeVerifier::VKAndHash>(hiding_kernel_vk);
213 ChonkNativeVerifier verifier(vk_and_hash);
214 const bool verified = verifier.verify(structured);
215
216 return { .valid = verified };
217}
218
220{
221 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
222
223 if (proofs.size() != vks.size()) {
224 throw_or_abort("ChonkBatchVerify: proofs.size() (" + std::to_string(proofs.size()) + ") != vks.size() (" +
225 std::to_string(vks.size()) + ")");
226 }
227 if (proofs.empty()) {
228 throw_or_abort("ChonkBatchVerify: no proofs provided");
229 }
230
232
233 // Phase 1: Run all non-IPA verification for each proof, collecting IPA claims
236 ipa_claims.reserve(proofs.size());
237 ipa_transcripts.reserve(proofs.size());
238
239 for (size_t i = 0; i < proofs.size(); ++i) {
240 validate_vk_size<VerificationKey>(vks[i]);
241 auto hiding_kernel_vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vks[i]));
242
243 const size_t expected_proof_size =
244 static_cast<size_t>(hiding_kernel_vk->num_public_inputs) + ChonkProof::PROOF_LENGTH_WITHOUT_PUB_INPUTS;
245 if (proofs[i].size() != expected_proof_size) {
246 throw_or_abort("ChonkBatchVerify: proof[" + std::to_string(i) + "] has wrong size: expected " +
247 std::to_string(expected_proof_size) + ", got " + std::to_string(proofs[i].size()));
248 }
249
250 auto vk_and_hash = std::make_shared<ChonkNativeVerifier::VKAndHash>(hiding_kernel_vk);
251 ChonkNativeVerifier verifier(vk_and_hash);
252 auto result = verifier.reduce_to_ipa_claim(std::move(proofs[i]));
253 if (!result.all_checks_passed) {
254 return { .valid = false };
255 }
256 ipa_claims.push_back(std::move(result.ipa_claim));
257 ipa_transcripts.push_back(std::make_shared<NativeTranscript>(std::move(result.ipa_proof)));
258 }
259
260 // Phase 2: Batch IPA verification with single SRS MSM
262 const bool verified = IPA<curve::Grumpkin>::batch_reduce_verify(ipa_vk, ipa_claims, ipa_transcripts);
263
264 return { .valid = verified };
265}
266
267static std::shared_ptr<Chonk::MegaVerificationKey> compute_chonk_vk_from_program(acir_format::AcirProgram& program,
268 bool use_zk_flavor)
269{
270 Chonk::ClientCircuit builder = acir_format::create_circuit<Chonk::ClientCircuit>(program);
271 if (use_zk_flavor) {
273 Chonk::HidingKernelProverInstance(builder).get_precomputed());
274 }
276}
277
279{
280 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
281 info("ChonkComputeVk - deriving MegaVerificationKey for circuit '",
282 circuit.name,
283 "'",
284 use_zk_flavor ? " (MegaZK)" : "");
285
286 auto constraint_system = acir_format::circuit_buf_to_acir_format(std::move(circuit.bytecode));
287
288 acir_format::AcirProgram program{ constraint_system, /*witness=*/{} };
289 auto verification_key = compute_chonk_vk_from_program(program, use_zk_flavor);
290
291 info("ChonkComputeVk - VK derived, size: ", to_buffer(*verification_key).size(), " bytes");
292
293 return { .bytes = to_buffer(*verification_key), .fields = verification_key->to_field_elements() };
294}
295
297{
298 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
300 /*witness=*/{} };
301
302 auto computed_vk = compute_chonk_vk_from_program(program, use_zk_flavor);
303
304 if (circuit.verification_key.empty()) {
305 info("FAIL: Expected precomputed vk for function ", circuit.name);
306 throw_or_abort("Missing precomputed VK");
307 }
308
309 validate_vk_size<Chonk::MegaVerificationKey>(circuit.verification_key);
310
311 // Deserialize directly from buffer
312 auto precomputed_vk = from_buffer<std::shared_ptr<Chonk::MegaVerificationKey>>(circuit.verification_key);
313
314 Response response;
315 response.valid = true;
316 if (*computed_vk != *precomputed_vk) {
317 response.valid = false;
318 response.actual_vk = to_buffer(computed_vk);
319 }
320 return response;
321}
322
324{
325 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
326 Response response;
327
328 const auto constraint_system = acir_format::circuit_buf_to_acir_format(std::move(circuit.bytecode));
329 acir_format::AcirProgram program{ constraint_system, {} };
330
331 // Get IVC constraints if any
332 const auto& ivc_constraints = constraint_system.hn_recursion_constraints;
333
334 // Create metadata with appropriate IVC context
336 .ivc = ivc_constraints.empty() ? nullptr : acir_format::create_mock_chonk_from_constraints(ivc_constraints),
337 .collect_gates_per_opcode = include_gates_per_opcode
338 };
339
340 // Create and finalize circuit
341 auto builder = acir_format::create_circuit<MegaCircuitBuilder>(program, metadata);
342 builder.finalize_circuit();
343
344 // Set response values
345 response.acir_opcodes = program.constraints.num_acir_opcodes;
346 response.circuit_size = static_cast<uint32_t>(builder.num_gates());
347
348 // Optionally include gates per opcode
349 if (include_gates_per_opcode) {
350 response.gates_per_opcode = std::vector<uint32_t>(program.constraints.gates_per_opcode.begin(),
351 program.constraints.gates_per_opcode.end());
352 }
353
354 // Log circuit details
355 info("ChonkStats - circuit: ",
356 circuit.name,
357 ", acir_opcodes: ",
358 response.acir_opcodes,
359 ", circuit_size: ",
360 response.circuit_size);
361
362 // Print execution trace details
363 builder.blocks.summarize();
364
365 return response;
366}
367
369{
370 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
371 return { .compressed_proof = ProofCompressor::compress_chonk_proof(proof) };
372}
373
375{
376 BB_BENCH_NAME(MSGPACK_SCHEMA_NAME);
377 size_t mega_num_pub = ProofCompressor::compressed_mega_num_public_inputs(compressed_proof.size());
378 return { .proof = ProofCompressor::decompress_chonk_proof(compressed_proof, mega_num_pub) };
379}
380
381// ── Batch Verifier Service ──────────────────────────────────────────────────
382
383#ifndef __wasm__
384
386 uint32_t num_cores,
387 uint32_t batch_size,
388 const std::string& fifo_path)
389{
390 if (running_) {
391 info("ChonkBatchVerifierService: already running, ignoring start()");
392 return;
393 }
394
395 if (num_cores == 0) {
396 num_cores = static_cast<uint32_t>(std::thread::hardware_concurrency());
397 if (num_cores == 0) {
398 num_cores = 1;
399 }
400 }
401
402 writer_shutdown_ = false;
403 running_ = true;
404
405 // Start the writer thread (opens the FIFO, drains result_queue_)
406 writer_thread_ = std::thread([this, path = fifo_path]() { writer_loop(path); });
407
408 // Start the batch processor with a callback that pushes to result_queue_
409 verifier_.start(std::move(vks), num_cores, batch_size, [this](VerifyResult result) {
410 {
411 std::lock_guard lock(result_mutex_);
412 result_queue_.push(std::move(result));
413 }
414 result_cv_.notify_one();
415 });
416
417 info("ChonkBatchVerifierService started, fifo=", fifo_path);
418}
419
424
426{
427 if (!running_) {
428 return;
429 }
430
431 // Stop the processor first (flushes remaining proofs → result_queue_)
432 verifier_.stop();
433
434 // Signal the writer to drain and exit
435 {
436 std::lock_guard lock(result_mutex_);
437 writer_shutdown_ = true;
438 }
439 result_cv_.notify_one();
440
441 if (writer_thread_.joinable()) {
442 writer_thread_.join();
443 }
444
445 running_ = false;
446 info("ChonkBatchVerifierService stopped");
447}
448
455
456void ChonkBatchVerifierService::writer_loop(const std::string& fifo_path)
457{
458 // Open FIFO for writing (blocks until a reader connects)
459 int fd = open(fifo_path.c_str(), O_WRONLY);
460 if (fd < 0) {
461 info("ChonkBatchVerifierService: failed to open FIFO '", fifo_path, "': ", strerror(errno));
462 return;
463 }
464
465 while (true) {
466 VerifyResult result;
467 {
468 std::unique_lock lock(result_mutex_);
469 result_cv_.wait(lock, [this] { return writer_shutdown_ || !result_queue_.empty(); });
470
471 if (!result_queue_.empty()) {
472 result = std::move(result_queue_.front());
473 result_queue_.pop();
474 } else if (writer_shutdown_) {
475 break;
476 } else {
477 continue;
478 }
479 }
480
481 // Serialize to msgpack and write as a length-delimited frame
482 msgpack::sbuffer buf;
483 msgpack::pack(buf, result);
484
485 if (!write_frame(fd, buf.data(), buf.size())) {
486 info("ChonkBatchVerifierService: FIFO write failed, stopping writer");
487 break;
488 }
489 }
490
491 close(fd);
492}
493
494// ── Batch Verifier RPC Commands ─────────────────────────────────────────────
495
497{
498 if (request.batch_verifier_service && request.batch_verifier_service->is_running()) {
499 throw_or_abort("ChonkBatchVerifierStart: service already running. Call ChonkBatchVerifierStop first.");
500 }
501
503
505 parsed_vks.reserve(vks.size());
506
507 for (size_t i = 0; i < vks.size(); ++i) {
508 validate_vk_size<VerificationKey>(vks[i]);
509 auto vk = std::make_shared<VerificationKey>(from_buffer<VerificationKey>(vks[i]));
510 parsed_vks.push_back(std::make_shared<MegaZKFlavor::VKAndHash>(vk));
511 }
512
513 request.batch_verifier_service = std::make_shared<ChonkBatchVerifierService>();
514 request.batch_verifier_service->start(std::move(parsed_vks), num_cores, batch_size, fifo_path);
515 return {};
516}
517
519{
520 if (!request.batch_verifier_service || !request.batch_verifier_service->is_running()) {
521 throw_or_abort("ChonkBatchVerifierQueue: service not running. Call ChonkBatchVerifierStart first.");
522 }
523
524 request.batch_verifier_service->enqueue(VerifyRequest{
525 .request_id = request_id,
526 .vk_index = vk_index,
527 .proof = ChonkProof::from_field_elements(proof_fields),
528 });
529
530 return {};
531}
532
534{
535 if (!request.batch_verifier_service || !request.batch_verifier_service->is_running()) {
536 throw_or_abort("ChonkBatchVerifierStop: service not running.");
537 }
538
539 request.batch_verifier_service->stop();
540 request.batch_verifier_service.reset();
541 return {};
542}
543
544#else // __wasm__
545
547{
548 throw_or_abort("ChonkBatchVerifierStart is not supported in WASM builds");
549}
550
551ChonkBatchVerifierQueue::Response ChonkBatchVerifierQueue::execute(BBApiRequest& /*request*/) &&
552{
553 throw_or_abort("ChonkBatchVerifierQueue is not supported in WASM builds");
554}
555
556ChonkBatchVerifierStop::Response ChonkBatchVerifierStop::execute(BBApiRequest& /*request*/) &&
557{
558 throw_or_abort("ChonkBatchVerifierStop is not supported in WASM builds");
559}
560
561#endif // __wasm__
562
563} // 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={}) &&
Response execute(const BBApiRequest &request={}) &&
void set_circuit_name(const std::string &name)
void throw_or_abort(std::string const &err)