Skip to content

Commit d7ee6e5

Browse files
authored
feat: recursive verifier for decider and last folding proof (#9626)
Construct the _hiding_ circuit, which recursively verifies the last folding proof and the decider proof in Client IVC and amend the e2e prover accordingly. The ClientIVC proof becomes a mega proof for the hiding circuit and a goblin proof which simplifies the work required to transform this in a zero knowledge proof.
1 parent 2ab33e7 commit d7ee6e5

File tree

15 files changed

+2702
-144
lines changed

15 files changed

+2702
-144
lines changed

barretenberg/cpp/src/barretenberg/bb/main.cpp

+25-45
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,7 @@ void client_ivc_prove_output_all_msgpack(const std::string& bytecodePath,
380380

381381
// Write the proof and verification keys into the working directory in 'binary' format (in practice it seems this
382382
// directory is passed by bb.js)
383-
std::string vkPath = outputDir + "/final_decider_vk"; // the vk of the last circuit in the stack
384-
std::string accPath = outputDir + "/pg_acc";
383+
std::string vkPath = outputDir + "/mega_vk"; // the vk of the last circuit in the stack
385384
std::string proofPath = outputDir + "/client_ivc_proof";
386385
std::string translatorVkPath = outputDir + "/translator_vk";
387386
std::string eccVkPath = outputDir + "/ecc_vk";
@@ -391,12 +390,11 @@ void client_ivc_prove_output_all_msgpack(const std::string& bytecodePath,
391390
auto translator_vk = std::make_shared<TranslatorVK>(ivc.goblin.get_translator_proving_key());
392391

393392
auto last_vk = std::make_shared<DeciderVerificationKey>(ivc.honk_vk);
394-
vinfo("ensure valid proof: ", ivc.verify(proof, { ivc.verifier_accumulator, last_vk }));
393+
vinfo("ensure valid proof: ", ivc.verify(proof));
395394

396395
vinfo("write proof and vk data to files..");
397396
write_file(proofPath, to_buffer(proof));
398397
write_file(vkPath, to_buffer(ivc.honk_vk));
399-
write_file(accPath, to_buffer(ivc.verifier_accumulator));
400398
write_file(translatorVkPath, to_buffer(translator_vk));
401399
write_file(eccVkPath, to_buffer(eccvm_vk));
402400
}
@@ -419,26 +417,23 @@ template <typename T> std::shared_ptr<T> read_to_shared_ptr(const std::filesyste
419417
* @return true (resp., false) if the proof is valid (resp., invalid).
420418
*/
421419
bool verify_client_ivc(const std::filesystem::path& proof_path,
422-
const std::filesystem::path& accumulator_path,
423-
const std::filesystem::path& final_vk_path,
420+
const std::filesystem::path& mega_vk,
424421
const std::filesystem::path& eccvm_vk_path,
425422
const std::filesystem::path& translator_vk_path)
426423
{
427424
init_bn254_crs(1);
428425
init_grumpkin_crs(1 << 15);
429426

430427
const auto proof = from_buffer<ClientIVC::Proof>(read_file(proof_path));
431-
const auto accumulator = read_to_shared_ptr<ClientIVC::DeciderVerificationKey>(accumulator_path);
432-
accumulator->verification_key->pcs_verification_key = std::make_shared<VerifierCommitmentKey<curve::BN254>>();
433-
const auto final_vk = read_to_shared_ptr<ClientIVC::VerificationKey>(final_vk_path);
428+
const auto final_vk = read_to_shared_ptr<ClientIVC::VerificationKey>(mega_vk);
429+
final_vk->pcs_verification_key = std::make_shared<VerifierCommitmentKey<curve::BN254>>();
430+
434431
const auto eccvm_vk = read_to_shared_ptr<ECCVMFlavor::VerificationKey>(eccvm_vk_path);
435432
eccvm_vk->pcs_verification_key =
436433
std::make_shared<VerifierCommitmentKey<curve::Grumpkin>>(eccvm_vk->circuit_size + 1);
437434
const auto translator_vk = read_to_shared_ptr<TranslatorFlavor::VerificationKey>(translator_vk_path);
438435
translator_vk->pcs_verification_key = std::make_shared<VerifierCommitmentKey<curve::BN254>>();
439-
440-
const bool verified = ClientIVC::verify(
441-
proof, accumulator, std::make_shared<ClientIVC::DeciderVerificationKey>(final_vk), eccvm_vk, translator_vk);
436+
const bool verified = ClientIVC::verify(proof, final_vk, eccvm_vk, translator_vk);
442437
vinfo("verified: ", verified);
443438
return verified;
444439
}
@@ -499,7 +494,6 @@ void client_ivc_prove_output_all(const std::string& bytecodePath,
499494
using Builder = Flavor::CircuitBuilder;
500495
using ECCVMVK = ECCVMFlavor::VerificationKey;
501496
using TranslatorVK = TranslatorFlavor::VerificationKey;
502-
using DeciderVK = ClientIVC::DeciderVerificationKey;
503497

504498
init_bn254_crs(1 << 22);
505499
init_grumpkin_crs(1 << 16);
@@ -531,23 +525,19 @@ void client_ivc_prove_output_all(const std::string& bytecodePath,
531525

532526
// Write the proof and verification keys into the working directory in 'binary' format (in practice it seems this
533527
// directory is passed by bb.js)
534-
std::string vkPath = outputPath + "/final_decider_vk"; // the vk of the last circuit in the stack
535-
std::string accPath = outputPath + "/pg_acc";
528+
std::string vkPath = outputPath + "/mega_vk"; // the vk of the last circuit in the stack
536529
std::string proofPath = outputPath + "/client_ivc_proof";
537530
std::string translatorVkPath = outputPath + "/translator_vk";
538531
std::string eccVkPath = outputPath + "/ecc_vk";
539532

540533
auto proof = ivc.prove();
541534
auto eccvm_vk = std::make_shared<ECCVMVK>(ivc.goblin.get_eccvm_proving_key());
542535
auto translator_vk = std::make_shared<TranslatorVK>(ivc.goblin.get_translator_proving_key());
543-
544-
auto last_vk = std::make_shared<DeciderVK>(ivc.honk_vk);
545-
vinfo("ensure valid proof: ", ivc.verify(proof, { ivc.verifier_accumulator, last_vk }));
536+
vinfo("ensure valid proof: ", ivc.verify(proof));
546537

547538
vinfo("write proof and vk data to files..");
548539
write_file(proofPath, to_buffer(proof));
549540
write_file(vkPath, to_buffer(ivc.honk_vk)); // maybe dereference
550-
write_file(accPath, to_buffer(ivc.verifier_accumulator));
551541
write_file(translatorVkPath, to_buffer(translator_vk));
552542
write_file(eccVkPath, to_buffer(eccvm_vk));
553543
}
@@ -561,18 +551,15 @@ void client_ivc_prove_output_all(const std::string& bytecodePath,
561551
void prove_tube(const std::string& output_path)
562552
{
563553
using ClientIVC = stdlib::recursion::honk::ClientIVCRecursiveVerifier;
564-
using StackDeciderVK = ClientIVC::FoldVerifierInput::DeciderVK;
565554
using StackHonkVK = typename MegaFlavor::VerificationKey;
566555
using ECCVMVk = ECCVMFlavor::VerificationKey;
567556
using TranslatorVk = TranslatorFlavor::VerificationKey;
568-
using FoldVerifierInput = ClientIVC::FoldVerifierInput;
569557
using GoblinVerifierInput = ClientIVC::GoblinVerifierInput;
570558
using VerifierInput = ClientIVC::VerifierInput;
571559
using Builder = UltraCircuitBuilder;
572560
using GrumpkinVk = bb::VerifierCommitmentKey<curve::Grumpkin>;
573561

574-
std::string vkPath = output_path + "/final_decider_vk"; // the vk of the last circuit in the stack
575-
std::string accPath = output_path + "/pg_acc";
562+
std::string vkPath = output_path + "/mega_vk"; // the vk of the last circuit in the stack
576563
std::string proofPath = output_path + "/client_ivc_proof";
577564
std::string translatorVkPath = output_path + "/translator_vk";
578565
std::string eccVkPath = output_path + "/ecc_vk";
@@ -583,10 +570,7 @@ void prove_tube(const std::string& output_path)
583570

584571
// Read the proof and verification data from given files
585572
auto proof = from_buffer<ClientIVC::Proof>(read_file(proofPath));
586-
std::shared_ptr<StackHonkVK> final_stack_vk =
587-
std::make_shared<StackHonkVK>(from_buffer<StackHonkVK>(read_file(vkPath)));
588-
std::shared_ptr<StackDeciderVK> verifier_accumulator =
589-
std::make_shared<StackDeciderVK>(from_buffer<StackDeciderVK>(read_file(accPath)));
573+
std::shared_ptr<StackHonkVK> mega_vk = std::make_shared<StackHonkVK>(from_buffer<StackHonkVK>(read_file(vkPath)));
590574
std::shared_ptr<TranslatorVk> translator_vk =
591575
std::make_shared<TranslatorVk>(from_buffer<TranslatorVk>(read_file(translatorVkPath)));
592576
std::shared_ptr<ECCVMVk> eccvm_vk = std::make_shared<ECCVMVk>(from_buffer<ECCVMVk>(read_file(eccVkPath)));
@@ -595,30 +579,30 @@ void prove_tube(const std::string& output_path)
595579
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1025)
596580
eccvm_vk->pcs_verification_key = std::make_shared<GrumpkinVk>(eccvm_vk->circuit_size + 1);
597581

598-
FoldVerifierInput fold_verifier_input{ verifier_accumulator, { final_stack_vk } };
599582
GoblinVerifierInput goblin_verifier_input{ eccvm_vk, translator_vk };
600-
VerifierInput input{ fold_verifier_input, goblin_verifier_input };
583+
VerifierInput input{ mega_vk, goblin_verifier_input };
601584
auto builder = std::make_shared<Builder>();
602-
// Padding needed for sending the right number of public inputs
585+
586+
// Preserve the public inputs that should be passed to the base rollup by making them public inputs to the tube
587+
// circuit
603588
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1048): INSECURE - make this tube proof actually use
604-
// these public inputs by turning proof into witnesses and call
605-
// set_public on each witness
606-
auto num_public_inputs = static_cast<uint32_t>(static_cast<uint256_t>(proof.folding_proof[1]));
607-
num_public_inputs -= bb::AGGREGATION_OBJECT_SIZE; // don't add the agg object
608-
num_public_inputs -= bb::PROPAGATED_DATABUS_COMMITMENTS_SIZE; // exclude propagated databus commitments
589+
// these public inputs by turning proof into witnesses and calling set_public on each witness
590+
auto num_public_inputs = static_cast<uint32_t>(static_cast<uint256_t>(proof.mega_proof[1]));
591+
num_public_inputs -= bb::AGGREGATION_OBJECT_SIZE; // don't add the agg object
592+
609593
for (size_t i = 0; i < num_public_inputs; i++) {
610-
auto offset = acir_format::HONK_RECURSION_PUBLIC_INPUT_OFFSET;
611-
builder->add_public_variable(proof.folding_proof[i + offset]);
594+
auto offset = bb::HONK_PROOF_PUBLIC_INPUT_OFFSET;
595+
builder->add_public_variable(proof.mega_proof[i + offset]);
612596
}
613597
ClientIVC verifier{ builder, input };
614598

615599
verifier.verify(proof);
616600

617-
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1069): Add aggregation to goblin recursive verifiers.
618-
// This is currently just setting the aggregation object to the default one.
619601
AggregationObjectIndices current_aggregation_object =
620602
stdlib::recursion::init_default_agg_obj_indices<Builder>(*builder);
621603

604+
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1069): Add aggregation to goblin recursive verifiers.
605+
// This is currently just setting the aggregation object to the default one.
622606
builder->add_recursive_proof(current_aggregation_object);
623607

624608
using Prover = UltraProver_<UltraFlavor>;
@@ -1477,15 +1461,11 @@ int main(int argc, char* argv[])
14771461
if (command == "verify_client_ivc") {
14781462
std::filesystem::path output_dir = get_option(args, "-o", "./target");
14791463
std::filesystem::path client_ivc_proof_path = output_dir / "client_ivc_proof";
1480-
std::filesystem::path accumulator_path = output_dir / "pg_acc";
1481-
std::filesystem::path final_vk_path = output_dir / "final_decider_vk";
1464+
std::filesystem::path mega_vk_path = output_dir / "mega_vk";
14821465
std::filesystem::path eccvm_vk_path = output_dir / "ecc_vk";
14831466
std::filesystem::path translator_vk_path = output_dir / "translator_vk";
14841467

1485-
return verify_client_ivc(
1486-
client_ivc_proof_path, accumulator_path, final_vk_path, eccvm_vk_path, translator_vk_path)
1487-
? 0
1488-
: 1;
1468+
return verify_client_ivc(client_ivc_proof_path, mega_vk_path, eccvm_vk_path, translator_vk_path) ? 0 : 1;
14891469
}
14901470
if (command == "fold_and_verify_program") {
14911471
return foldAndVerifyProgram(bytecode_path, witness_path) ? 0 : 1;

barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp

+82-23
Original file line numberDiff line numberDiff line change
@@ -213,41 +213,102 @@ void ClientIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr<Verific
213213
}
214214
}
215215

216+
/**
217+
* @brief Construct the hiding circuit, which recursively verifies the last folding proof and decider proof, and
218+
* then produce a proof of the circuit's correctness with MegaHonk.
219+
*
220+
* @details The aim of this intermediate stage is to reduce the cost of producing a zero-knowledge ClientIVCProof.
221+
* @return HonkProof - a Mega proof
222+
*/
223+
HonkProof ClientIVC::construct_and_prove_hiding_circuit()
224+
{
225+
trace_usage_tracker.print(); // print minimum structured sizes for each block
226+
ASSERT(verification_queue.size() == 1);
227+
ASSERT(merge_verification_queue.size() == 1); // ensure only a single merge proof remains in the queue
228+
229+
FoldProof& fold_proof = verification_queue[0].proof;
230+
HonkProof decider_proof = decider_prove();
231+
232+
fold_output.accumulator = nullptr;
233+
234+
ClientCircuit builder{ goblin.op_queue };
235+
// The last circuit being folded is a kernel circuit whose public inputs need to be passed to the base rollup
236+
// circuit. So, these have to be preserved as public inputs to the hiding circuit (and, subsequently, as public
237+
// inputs to the tube circuit) which are intermediate stages.
238+
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1048): link these properly, likely insecure
239+
auto num_public_inputs = static_cast<uint32_t>(static_cast<uint256_t>(fold_proof[PUBLIC_INPUTS_SIZE_INDEX]));
240+
vinfo("num_public_inputs of the last folding proof BEFORE SUBTRACTION", num_public_inputs);
241+
num_public_inputs -= bb::AGGREGATION_OBJECT_SIZE; // exclude aggregation object
242+
num_public_inputs -= bb::PROPAGATED_DATABUS_COMMITMENTS_SIZE; // exclude propagated databus commitments
243+
vinfo("num_public_inputs of the last folding proof ", num_public_inputs);
244+
for (size_t i = 0; i < num_public_inputs; i++) {
245+
size_t offset = HONK_PROOF_PUBLIC_INPUT_OFFSET;
246+
builder.add_public_variable(fold_proof[i + offset]);
247+
}
248+
249+
process_recursive_merge_verification_queue(builder);
250+
251+
// Construct stdlib accumulator, decider vkey and folding proof
252+
auto stdlib_verifier_accumulator =
253+
std::make_shared<RecursiveDeciderVerificationKey>(&builder, verifier_accumulator);
254+
255+
auto stdlib_decider_vk =
256+
std::make_shared<RecursiveVerificationKey>(&builder, verification_queue[0].honk_verification_key);
257+
258+
auto stdlib_proof = bb::convert_proof_to_witness(&builder, fold_proof);
259+
260+
// Perform recursive folding verification of the last folding proof
261+
FoldingRecursiveVerifier folding_verifier{ &builder, stdlib_verifier_accumulator, { stdlib_decider_vk } };
262+
auto recursive_verifier_accumulator = folding_verifier.verify_folding_proof(stdlib_proof);
263+
verification_queue.clear();
264+
265+
// Perform recursive decider verification
266+
DeciderRecursiveVerifier decider{ &builder, recursive_verifier_accumulator };
267+
decider.verify_proof(decider_proof);
268+
269+
builder.add_recursive_proof(stdlib::recursion::init_default_agg_obj_indices<ClientCircuit>(builder));
270+
271+
// Construct the last merge proof for the present circuit and add to merge verification queue
272+
MergeProof merge_proof = goblin.prove_merge(builder);
273+
merge_verification_queue.emplace_back(merge_proof);
274+
275+
auto decider_pk = std::make_shared<DeciderProvingKey>(builder);
276+
honk_vk = std::make_shared<VerificationKey>(decider_pk->proving_key);
277+
MegaProver prover(decider_pk);
278+
279+
HonkProof proof = prover.construct_proof();
280+
281+
return proof;
282+
}
283+
216284
/**
217285
* @brief Construct a proof for the IVC, which, if verified, fully establishes its correctness
218286
*
219287
* @return Proof
220288
*/
221289
ClientIVC::Proof ClientIVC::prove()
222290
{
223-
trace_usage_tracker.print(); // print minimum structured sizes for each block
224-
ASSERT(verification_queue.size() == 1); // ensure only a single fold proof remains in the queue
291+
HonkProof mega_proof = construct_and_prove_hiding_circuit();
225292
ASSERT(merge_verification_queue.size() == 1); // ensure only a single merge proof remains in the queue
226-
FoldProof& fold_proof = verification_queue[0].proof;
227293
MergeProof& merge_proof = merge_verification_queue[0];
228-
HonkProof decider_proof = decider_prove();
229-
// Free the accumulator to save memory
230-
fold_output.accumulator = nullptr;
231-
return { fold_proof, std::move(decider_proof), goblin.prove(merge_proof) };
294+
return { mega_proof, goblin.prove(merge_proof) };
232295
};
233296

234297
bool ClientIVC::verify(const Proof& proof,
235-
const std::shared_ptr<DeciderVerificationKey>& accumulator,
236-
const std::shared_ptr<DeciderVerificationKey>& final_stack_vk,
298+
const std::shared_ptr<VerificationKey>& ultra_vk,
237299
const std::shared_ptr<ClientIVC::ECCVMVerificationKey>& eccvm_vk,
238300
const std::shared_ptr<ClientIVC::TranslatorVerificationKey>& translator_vk)
239301
{
240-
// Goblin verification (merge, eccvm, translator)
302+
303+
// Verify the hiding circuit proof
304+
MegaVerifier verifer{ ultra_vk };
305+
bool ultra_verified = verifer.verify_proof(proof.mega_proof);
306+
vinfo("Mega verified: ", ultra_verified);
307+
// Goblin verification (final merge, eccvm, translator)
241308
GoblinVerifier goblin_verifier{ eccvm_vk, translator_vk };
242309
bool goblin_verified = goblin_verifier.verify(proof.goblin_proof);
243-
244-
// Decider verification
245-
ClientIVC::FoldingVerifier folding_verifier({ accumulator, final_stack_vk });
246-
auto verifier_accumulator = folding_verifier.verify_folding_proof(proof.folding_proof);
247-
248-
ClientIVC::DeciderVerifier decider_verifier(verifier_accumulator);
249-
bool decision = decider_verifier.verify_proof(proof.decider_proof);
250-
return goblin_verified && decision;
310+
vinfo("Goblin verified: ", goblin_verified);
311+
return goblin_verified && ultra_verified;
251312
}
252313

253314
/**
@@ -256,11 +317,11 @@ bool ClientIVC::verify(const Proof& proof,
256317
* @param proof
257318
* @return bool
258319
*/
259-
bool ClientIVC::verify(const Proof& proof, const std::vector<std::shared_ptr<DeciderVerificationKey>>& vk_stack)
320+
bool ClientIVC::verify(const Proof& proof)
260321
{
261322
auto eccvm_vk = std::make_shared<ECCVMVerificationKey>(goblin.get_eccvm_proving_key());
262323
auto translator_vk = std::make_shared<TranslatorVerificationKey>(goblin.get_translator_proving_key());
263-
return verify(proof, vk_stack[0], vk_stack[1], eccvm_vk, translator_vk);
324+
return verify(proof, honk_vk, eccvm_vk, translator_vk);
264325
}
265326

266327
/**
@@ -283,9 +344,7 @@ HonkProof ClientIVC::decider_prove() const
283344
bool ClientIVC::prove_and_verify()
284345
{
285346
auto proof = prove();
286-
287-
auto verifier_inst = std::make_shared<DeciderVerificationKey>(this->verification_queue[0].honk_verification_key);
288-
return verify(proof, { this->verifier_accumulator, verifier_inst });
347+
return verify(proof);
289348
}
290349

291350
/**

0 commit comments

Comments
 (0)