Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New IVC class that better reflects the aztec architecture #7695

Merged
merged 6 commits into from
Aug 1, 2024
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
1 change: 1 addition & 0 deletions barretenberg/cpp/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ else()
message(STATUS "Using optimized assembly for field arithmetic.")
endif()

add_subdirectory(barretenberg/aztec_ivc)
add_subdirectory(barretenberg/bb)
add_subdirectory(barretenberg/circuit_checker)
add_subdirectory(barretenberg/client_ivc)
Expand Down
1 change: 1 addition & 0 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
barretenberg_module(aztec_ivc goblin)
165 changes: 165 additions & 0 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#include "barretenberg/aztec_ivc/aztec_ivc.hpp"

namespace bb {

/**
* @brief Accumulate a circuit into the IVC scheme
* @details If this is the first circuit being accumulated, initialize the prover and verifier accumulators. Otherwise,
* fold the instance for the provided circuit into the accumulator. When two fold proofs have been enqueued, two
* recursive folding verifications are appended to the next circuit that is accumulated, which must be a kernel.
* Similarly, if a merge proof exists, a recursive merge verifier is appended.
*
* @param circuit Circuit to be accumulated/folded
* @param precomputed_vk Optional precomputed VK (otherwise will be computed herein)
*/
void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr<VerificationKey>& precomputed_vk)
{
circuit_count++; // increment the count of circuits processed into the IVC

// When there are two fold proofs present, append two recursive verifiers to the kernel
if (verification_queue.size() == 2) {
BB_OP_COUNT_TIME_NAME("construct_circuits");
ASSERT(circuit_count % 2 == 0); // ensure this is a kernel

for (auto& [proof, vkey] : verification_queue) {
FoldingRecursiveVerifier verifier{ &circuit, { verifier_accumulator, { vkey } } };
auto verifier_accum = verifier.verify_folding_proof(proof);
verifier_accumulator = std::make_shared<VerifierInstance>(verifier_accum->get_value());
info("Num gates = ", circuit.get_num_gates());
}
verification_queue.clear();
}

// Construct a merge proof (and add a recursive merge verifier to the circuit if a previous merge proof exists)
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1063): update recursive merge verification to only
// occur in kernels, similar to folding recursive verification.
goblin.merge(circuit);

// Construct the prover instance for circuit
auto prover_instance = std::make_shared<ProverInstance>(circuit, trace_structure);

// Set the instance verification key from precomputed if available, else compute it
if (precomputed_vk) {
instance_vk = precomputed_vk;
} else {
instance_vk = std::make_shared<VerificationKey>(prover_instance->proving_key);
}

// If this is the first circuit simply initialize the prover and verifier accumulator instances
if (circuit_count == 1) {
fold_output.accumulator = prover_instance;
verifier_accumulator = std::make_shared<VerifierInstance>(instance_vk);
} else { // Otherwise, fold the new instance into the accumulator
FoldingProver folding_prover({ fold_output.accumulator, prover_instance });
fold_output = folding_prover.fold_instances();

// Add fold proof and corresponding verification key to the verification queue
verification_queue.emplace_back(fold_output.proof, instance_vk);
}

// Track the maximum size of each block for all circuits porcessed (for debugging purposes only)
max_block_size_tracker.update(circuit);
}

/**
* @brief Construct a proof for the IVC, which, if verified, fully establishes its correctness
*
* @return Proof
*/
AztecIVC::Proof AztecIVC::prove()
{
max_block_size_tracker.print(); // print minimum structured sizes for each block
ASSERT(verification_queue.size() == 1); // ensure only a single fold proof remains in the queue
auto& fold_proof = verification_queue[0].proof;
return { fold_proof, decider_prove(), goblin.prove() };
};

bool AztecIVC::verify(const Proof& proof,
const std::shared_ptr<VerifierInstance>& accumulator,
const std::shared_ptr<VerifierInstance>& final_verifier_instance,
const std::shared_ptr<AztecIVC::ECCVMVerificationKey>& eccvm_vk,
const std::shared_ptr<AztecIVC::TranslatorVerificationKey>& translator_vk)
{
// Goblin verification (merge, eccvm, translator)
GoblinVerifier goblin_verifier{ eccvm_vk, translator_vk };
bool goblin_verified = goblin_verifier.verify(proof.goblin_proof);

// Decider verification
AztecIVC::FoldingVerifier folding_verifier({ accumulator, final_verifier_instance });
auto verifier_accumulator = folding_verifier.verify_folding_proof(proof.folding_proof);

AztecIVC::DeciderVerifier decider_verifier(verifier_accumulator);
bool decision = decider_verifier.verify_proof(proof.decider_proof);
return goblin_verified && decision;
}

/**
* @brief Verify a full proof of the IVC
*
* @param proof
* @return bool
*/
bool AztecIVC::verify(Proof& proof, const std::vector<std::shared_ptr<VerifierInstance>>& verifier_instances)
{
auto eccvm_vk = std::make_shared<ECCVMVerificationKey>(goblin.get_eccvm_proving_key());
auto translator_vk = std::make_shared<TranslatorVerificationKey>(goblin.get_translator_proving_key());
return verify(proof, verifier_instances[0], verifier_instances[1], eccvm_vk, translator_vk);
}

/**
* @brief Internal method for constructing a decider proof
*
* @return HonkProof
*/
HonkProof AztecIVC::decider_prove() const
{
MegaDeciderProver decider_prover(fold_output.accumulator);
return decider_prover.construct_proof();
}

/**
* @brief Given a set of circuits, compute the verification keys that will be required by the IVC scheme
* @details The verification keys computed here are in general not the same as the verification keys for the
* raw input circuits because recursive verifier circuits (merge and/or folding) may be appended to the incoming
* circuits as part accumulation.
* @note This method exists for convenience and is not not meant to be used in practice for IVC. Given a set of
* circuits, it could be run once and for all to compute then save the required VKs. It also provides a convenient
* (albeit innefficient) way of separating out the cost of computing VKs from a benchmark.
*
* @param circuits A copy of the circuits to be accumulated (passing by reference would alter the original circuits)
* @return std::vector<std::shared_ptr<AztecIVC::VerificationKey>>
*/
std::vector<std::shared_ptr<AztecIVC::VerificationKey>> AztecIVC::precompute_folding_verification_keys(
std::vector<ClientCircuit> circuits)
{
std::vector<std::shared_ptr<VerificationKey>> vkeys;

for (auto& circuit : circuits) {
accumulate(circuit);
vkeys.emplace_back(instance_vk);
}

// Reset the scheme so it can be reused for actual accumulation, maintaining the trace structure setting as is
TraceStructure structure = trace_structure;
*this = AztecIVC();
this->trace_structure = structure;

return vkeys;
}

/**
* @brief Construct and verify a proof for the IVC
* @note Use of this method only makes sense when the prover and verifier are the same entity, e.g. in
* development/testing.
*
*/
bool AztecIVC::prove_and_verify()
{
auto proof = prove();

ASSERT(verification_queue.size() == 1); // ensure only a single fold proof remains in the queue
auto verifier_inst = std::make_shared<VerifierInstance>(this->verification_queue[0].instance_vk);
return verify(proof, { this->verifier_accumulator, verifier_inst });
}

} // namespace bb
105 changes: 105 additions & 0 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include "barretenberg/goblin/goblin.hpp"
#include "barretenberg/goblin/mock_circuits.hpp"
#include "barretenberg/plonk_honk_shared/arithmetization/max_block_size_tracker.hpp"
#include "barretenberg/protogalaxy/decider_verifier.hpp"
#include "barretenberg/protogalaxy/protogalaxy_prover.hpp"
#include "barretenberg/protogalaxy/protogalaxy_verifier.hpp"
#include "barretenberg/sumcheck/instance/instances.hpp"
#include "barretenberg/ultra_honk/decider_prover.hpp"
#include <algorithm>

namespace bb {

/**
* @brief The IVC scheme used by the aztec client for private function execution
* @details Combines Protogalaxy with Goblin to accumulate one circuit instance at a time with efficient EC group
* operations. It is assumed that the circuits being accumulated correspond alternatingly to an app and a kernel, as is
* the case in Aztec. Two recursive folding verifiers are appended to each kernel (except the first one) to verify the
* folding of a previous kernel and an app/function circuit. Due to this structure it is enforced that the total number
* of circuits being accumulated is even.
*
*/
class AztecIVC {

public:
using Flavor = MegaFlavor;
using VerificationKey = Flavor::VerificationKey;
using FF = Flavor::FF;
using FoldProof = std::vector<FF>;
using ProverInstance = ProverInstance_<Flavor>;
using VerifierInstance = VerifierInstance_<Flavor>;
using ClientCircuit = MegaCircuitBuilder; // can only be Mega
using DeciderProver = DeciderProver_<Flavor>;
using DeciderVerifier = DeciderVerifier_<Flavor>;
using ProverInstances = ProverInstances_<Flavor>;
using FoldingProver = ProtoGalaxyProver_<ProverInstances>;
using VerifierInstances = VerifierInstances_<Flavor>;
using FoldingVerifier = ProtoGalaxyVerifier_<VerifierInstances>;
using ECCVMVerificationKey = bb::ECCVMFlavor::VerificationKey;
using TranslatorVerificationKey = bb::TranslatorFlavor::VerificationKey;

using GURecursiveFlavor = MegaRecursiveFlavor_<bb::MegaCircuitBuilder>;
using RecursiveVerifierInstances = bb::stdlib::recursion::honk::RecursiveVerifierInstances_<GURecursiveFlavor, 2>;
using FoldingRecursiveVerifier =
bb::stdlib::recursion::honk::ProtoGalaxyRecursiveVerifier_<RecursiveVerifierInstances>;

// A full proof for the IVC scheme
struct Proof {
FoldProof folding_proof; // final fold proof
HonkProof decider_proof;
GoblinProof goblin_proof;

size_t size() const { return folding_proof.size() + decider_proof.size() + goblin_proof.size(); }

MSGPACK_FIELDS(folding_proof, decider_proof, goblin_proof);
};

struct FoldingVerifierInputs {
FoldProof proof;
std::shared_ptr<VerificationKey> instance_vk;
};

// Utility for tracking the max size of each block across the full IVC
MaxBlockSizeTracker max_block_size_tracker;

private:
using ProverFoldOutput = FoldingResult<Flavor>;

public:
GoblinProver goblin;

ProverFoldOutput fold_output; // prover accumulator instance and fold proof

std::shared_ptr<VerifierInstance> verifier_accumulator; // verifier accumulator instance
std::shared_ptr<VerificationKey> instance_vk; // verification key for instance to be folded

// Set of pairs of {fold_proof, verification_key} to be recursively verified
std::vector<FoldingVerifierInputs> verification_queue;

// A flag indicating whether or not to construct a structured trace in the ProverInstance
TraceStructure trace_structure = TraceStructure::NONE;

// The number of circuits processed into the IVC
size_t circuit_count = 0;

void accumulate(ClientCircuit& circuit, const std::shared_ptr<VerificationKey>& precomputed_vk = nullptr);

Proof prove();

static bool verify(const Proof& proof,
const std::shared_ptr<VerifierInstance>& accumulator,
const std::shared_ptr<VerifierInstance>& final_verifier_instance,
const std::shared_ptr<AztecIVC::ECCVMVerificationKey>& eccvm_vk,
const std::shared_ptr<AztecIVC::TranslatorVerificationKey>& translator_vk);

bool verify(Proof& proof, const std::vector<std::shared_ptr<VerifierInstance>>& verifier_instances);

bool prove_and_verify();

HonkProof decider_prove() const;

std::vector<std::shared_ptr<VerificationKey>> precompute_folding_verification_keys(std::vector<ClientCircuit>);
};
} // namespace bb
Loading
Loading