Skip to content

Commit

Permalink
feat: update AztecIvc interface to facilitate acir-ivc (#8230)
Browse files Browse the repository at this point in the history
This PR completes the acir-ivc interface through which a bberg kernel
circuit can be properly constructed from a noir program. In particular,
the interface allows for the proof/verification_key witnesses used in
the noir program to be communicated to the backend via calls to
`verify_proof()` (with an appropriate `proof_type` indicator). They can
then be "linked" (asserted equal) to the corresponding witnesses used to
complete the kernel logic (recursive verifications, databus checks
etc.).

The main components of the PR are as follows:
- A new DSL test suite which demonstrates the new functionality via IVC
accumulation of mock kernel and app circuits constructed from acir
constraint systems. (Includes a failure test that demonstrates that the
"linking" of the witnesses does indeed result in failure when expected).
- Reorganization of the methods in AztecIvc related to kernel
completion. (basically just splitting things into sub-methods to
facilitate the main goal of the PR)
- Some minor renaming to remove the `_RECURSION` suffix from the
`proof_type` enum entries. (This accounts for most of the changed
files).

Note: In this PR the linking is actually only done on the proof (not yet
the verification key). I'm leaving this as a TODO (issue tagged in code)
since it really warrants some long overdue verification key cleanup.
  • Loading branch information
ledwards2225 authored Sep 6, 2024
1 parent f3e4f97 commit 665750a
Show file tree
Hide file tree
Showing 25 changed files with 536 additions and 112 deletions.
2 changes: 1 addition & 1 deletion barretenberg/cpp/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ include(GNUInstallDirs)
message(STATUS "Compiling all-in-one barretenberg archive")

set(BARRETENBERG_TARGET_OBJECTS
$<TARGET_OBJECTS:client_ivc_objects>
$<TARGET_OBJECTS:aztec_ivc_objects>
$<TARGET_OBJECTS:commitment_schemes_objects>
$<TARGET_OBJECTS:common_objects>
$<TARGET_OBJECTS:client_ivc_objects>
Expand Down
157 changes: 103 additions & 54 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,124 @@
namespace bb {

/**
* @brief Append logic to complete a kernel circuit
* @details A kernel circuit may contain some combination of PG recursive verification, merge recursive verification,
* and databus commitment consistency checks. This method appends this logic to a provided kernel circuit.
* @brief Instantiate a stdlib verification queue corresponding to the native counterpart
*
* @param circuit
*/
void AztecIVC::complete_kernel_circuit_logic(ClientCircuit& circuit)
void AztecIVC::instantiate_stdlib_verification_queue(ClientCircuit& circuit)
{
circuit.databus_propagation_data.is_kernel = true;

// Perform recursive verification and databus consistency checks for each entry in the verification queue
for (auto& [proof, vkey, type] : verification_queue) {
// Construct stdlib verification key and proof
auto stdlib_proof = bb::convert_proof_to_witness(&circuit, proof);
auto stdlib_vkey = std::make_shared<RecursiveVerificationKey>(&circuit, vkey);

switch (type) {
case QUEUE_TYPE::PG: {
// Construct stdlib verifier accumulator from the native counterpart computed on a previous round
auto stdlib_verifier_accum =
std::make_shared<RecursiveDeciderVerificationKey>(&circuit, verifier_accumulator);

// Perform folding recursive verification to update the verifier accumulator
FoldingRecursiveVerifier verifier{ &circuit, stdlib_verifier_accum, { stdlib_vkey } };
auto verifier_accum = verifier.verify_folding_proof(stdlib_proof);

// Extract native verifier accumulator from the stdlib accum for use on the next round
verifier_accumulator = std::make_shared<DeciderVerificationKey>(verifier_accum->get_value());

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier.keys_to_fold[1]->witness_commitments,
verifier.keys_to_fold[1]->public_inputs,
verifier.keys_to_fold[1]->verification_key->databus_propagation_data);
break;
}
case QUEUE_TYPE::OINK: {
// Construct an incomplete stdlib verifier accumulator from the corresponding stdlib verification key
auto verifier_accum = std::make_shared<RecursiveDeciderVerificationKey>(&circuit, stdlib_vkey);

// Perform oink recursive verification to complete the initial verifier accumulator
OinkRecursiveVerifier oink{ &circuit, verifier_accum };
oink.verify_proof(stdlib_proof);
verifier_accum->is_accumulator = true; // indicate to PG that it should not run oink on this key

// Extract native verifier accumulator from the stdlib accum for use on the next round
verifier_accumulator = std::make_shared<DeciderVerificationKey>(verifier_accum->get_value());
// Initialize the gate challenges to zero for use in first round of folding
verifier_accumulator->gate_challenges =
std::vector<FF>(verifier_accum->verification_key->log_circuit_size, 0);

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier_accum->witness_commitments,
verifier_accum->public_inputs,
verifier_accum->verification_key->databus_propagation_data);

break;
}
}
stdlib_verification_queue.emplace_back(stdlib_proof, stdlib_vkey, type);
}
verification_queue.clear(); // the native data is not needed beyond this point
}

/**
* @brief Populate the provided circuit with constraints for (1) recursive verification of the provided accumulation
* proof and (2) the associated databus commitment consistency checks.
* @details The recursive verifier will be either Oink or Protogalaxy depending on the specified proof type. In either
* case, the verifier accumulator is updated in place via the verification algorithm. Databus commitment consistency
* checks are performed on the witness commitments and public inputs extracted from the proof by the verifier.
*
* @param circuit The circuit to which the constraints are appended
* @param proof A stdlib proof to be recursively verified (either oink or PG)
* @param vkey The stdlib verfication key associated with the proof
* @param type The type of the proof (equivalently, the type of the verifier)
*/
void AztecIVC::perform_recursive_verification_and_databus_consistency_checks(
ClientCircuit& circuit,
const StdlibProof<ClientCircuit>& proof,
const std::shared_ptr<RecursiveVerificationKey>& vkey,
const QUEUE_TYPE type)
{
switch (type) {
case QUEUE_TYPE::PG: {
// Construct stdlib verifier accumulator from the native counterpart computed on a previous round
auto stdlib_verifier_accum = std::make_shared<RecursiveDeciderVerificationKey>(&circuit, verifier_accumulator);

// Perform folding recursive verification to update the verifier accumulator
FoldingRecursiveVerifier verifier{ &circuit, stdlib_verifier_accum, { vkey } };
auto verifier_accum = verifier.verify_folding_proof(proof);

// Extract native verifier accumulator from the stdlib accum for use on the next round
verifier_accumulator = std::make_shared<DeciderVerificationKey>(verifier_accum->get_value());

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier.keys_to_fold[1]->witness_commitments,
verifier.keys_to_fold[1]->public_inputs,
verifier.keys_to_fold[1]->verification_key->databus_propagation_data);
break;
}
verification_queue.clear();
case QUEUE_TYPE::OINK: {
// Construct an incomplete stdlib verifier accumulator from the corresponding stdlib verification key
auto verifier_accum = std::make_shared<RecursiveDeciderVerificationKey>(&circuit, vkey);

// Perform oink recursive verification to complete the initial verifier accumulator
OinkRecursiveVerifier oink{ &circuit, verifier_accum };
oink.verify_proof(proof);
verifier_accum->is_accumulator = true; // indicate to PG that it should not run oink

// Extract native verifier accumulator from the stdlib accum for use on the next round
verifier_accumulator = std::make_shared<DeciderVerificationKey>(verifier_accum->get_value());
// Initialize the gate challenges to zero for use in first round of folding
auto log_circuit_size = static_cast<size_t>(verifier_accum->verification_key->log_circuit_size);
verifier_accumulator->gate_challenges = std::vector<FF>(log_circuit_size, 0);

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier_accum->witness_commitments,
verifier_accum->public_inputs,
verifier_accum->verification_key->databus_propagation_data);

break;
}
}
}

/**
* @brief Perform recursive merge verification for each merge proof in the queue
*
* @param circuit
*/
void AztecIVC::process_recursive_merge_verification_queue(ClientCircuit& circuit)
{
// Recusively verify all merge proofs in queue
for (auto& proof : merge_verification_queue) {
goblin.verify_merge(circuit, proof);
}
merge_verification_queue.clear();
}

/**
* @brief Append logic to complete a kernel circuit
* @details A kernel circuit may contain some combination of PG recursive verification, merge recursive
* verification, and databus commitment consistency checks. This method appends this logic to a provided kernel
* circuit.
*
* @param circuit
*/
void AztecIVC::complete_kernel_circuit_logic(ClientCircuit& circuit)
{
circuit.databus_propagation_data.is_kernel = true;

// Instantiate stdlib verifier inputs from their native counterparts
if (stdlib_verification_queue.empty()) {
instantiate_stdlib_verification_queue(circuit);
}

// Peform recursive verification and databus consistency checks for each entry in the verification queue
for (auto& [proof, vkey, type] : stdlib_verification_queue) {
perform_recursive_verification_and_databus_consistency_checks(circuit, proof, vkey, type);
}
stdlib_verification_queue.clear();

// Perform recursive merge verification for every merge proof in the queue
process_recursive_merge_verification_queue(circuit);
}

/**
* @brief Execute prover work for accumulation
* @details Construct an proving key for the provided circuit. If this is the first step in the IVC, simply initialize
Expand Down Expand Up @@ -115,16 +165,15 @@ void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr<Verifica

// Add oink proof and corresponding verification key to the verification queue
verification_queue.push_back(
bb::AztecIVC::RecursiveVerifierInputs{ oink_prover.transcript->proof_data, honk_vk, QUEUE_TYPE::OINK });
bb::AztecIVC::VerifierInputs{ oink_prover.transcript->proof_data, honk_vk, QUEUE_TYPE::OINK });

initialized = true;
} else { // Otherwise, fold the new key into the accumulator
FoldingProver folding_prover({ fold_output.accumulator, proving_key });
fold_output = folding_prover.prove();

// Add fold proof and corresponding verification key to the verification queue
verification_queue.push_back(
bb::AztecIVC::RecursiveVerifierInputs{ fold_output.proof, honk_vk, QUEUE_TYPE::PG });
verification_queue.push_back(bb::AztecIVC::VerifierInputs{ fold_output.proof, honk_vk, QUEUE_TYPE::PG });
}

// Track the maximum size of each block for all circuits porcessed (for debugging purposes only)
Expand Down Expand Up @@ -171,7 +220,7 @@ bool AztecIVC::verify(const Proof& proof,
* @param proof
* @return bool
*/
bool AztecIVC::verify(Proof& proof, const std::vector<std::shared_ptr<DeciderVerificationKey>>& vk_stack)
bool AztecIVC::verify(const Proof& proof, const std::vector<std::shared_ptr<DeciderVerificationKey>>& vk_stack)
{
auto eccvm_vk = std::make_shared<ECCVMVerificationKey>(goblin.get_eccvm_proving_key());
auto translator_vk = std::make_shared<TranslatorVerificationKey>(goblin.get_translator_proving_key());
Expand Down
33 changes: 28 additions & 5 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,23 @@ class AztecIVC {
MSGPACK_FIELDS(folding_proof, decider_proof, goblin_proof);
};

enum class QUEUE_TYPE { OINK, PG };
struct RecursiveVerifierInputs {
enum class QUEUE_TYPE { OINK, PG }; // for specifying type of proof in the verification queue

// An entry in the native verification queue
struct VerifierInputs {
std::vector<FF> proof; // oink or PG
std::shared_ptr<VerificationKey> honk_verification_key;
QUEUE_TYPE type;
};
using VerificationQueue = std::vector<VerifierInputs>;

// An entry in the stdlib verification queue
struct StdlibVerifierInputs {
StdlibProof<ClientCircuit> proof; // oink or PG
std::shared_ptr<RecursiveVerificationKey> honk_verification_key;
QUEUE_TYPE type;
};
using StdlibVerificationQueue = std::vector<StdlibVerifierInputs>;

// Utility for tracking the max size of each block across the full IVC
MaxBlockSizeTracker max_block_size_tracker;
Expand All @@ -85,8 +96,10 @@ class AztecIVC {
std::shared_ptr<DeciderVerificationKey> verifier_accumulator; // verifier accumulator
std::shared_ptr<VerificationKey> honk_vk; // honk vk to be completed and folded into the accumulator

// Set of pairs of {fold_proof, verification_key} to be recursively verified
std::vector<RecursiveVerifierInputs> verification_queue;
// Set of tuples {proof, verification_key, type} to be recursively verified
VerificationQueue verification_queue;
// Set of tuples {stdlib_proof, stdlib_verification_key, type} corresponding to the native verification queue
StdlibVerificationQueue stdlib_verification_queue;
// Set of merge proofs to be recursively verified
std::vector<MergeProof> merge_verification_queue;

Expand All @@ -98,6 +111,16 @@ class AztecIVC {

bool initialized = false; // Is the IVC accumulator initialized

void instantiate_stdlib_verification_queue(ClientCircuit& circuit);

void perform_recursive_verification_and_databus_consistency_checks(
ClientCircuit& circuit,
const StdlibProof<ClientCircuit>& proof,
const std::shared_ptr<RecursiveVerificationKey>& vkey,
const QUEUE_TYPE type);

void process_recursive_merge_verification_queue(ClientCircuit& circuit);

// Complete the logic of a kernel circuit (e.g. PG/merge recursive verification, databus consistency checks)
void complete_kernel_circuit_logic(ClientCircuit& circuit);

Expand All @@ -112,7 +135,7 @@ class AztecIVC {
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<DeciderVerificationKey>>& vk_stack);
bool verify(const Proof& proof, const std::vector<std::shared_ptr<DeciderVerificationKey>>& vk_stack);

bool prove_and_verify();

Expand Down
1 change: 1 addition & 0 deletions barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# We do not need to bloat barretenberg.wasm with gzip functionality in a browser context as the browser can do this

set(DSL_DEPENDENCIES
aztec_ivc
plonk
ultra_honk
client_ivc
Expand Down
64 changes: 60 additions & 4 deletions barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,8 @@ void process_honk_recursion_constraints(Builder& builder,
*/
template <>
UltraCircuitBuilder create_circuit(AcirFormat& constraint_system,
size_t size_hint,
WitnessVector const& witness,
const size_t size_hint,
const WitnessVector& witness,
bool honk_recursion,
[[maybe_unused]] std::shared_ptr<ECCOpQueue>,
bool collect_gates_per_opcode)
Expand All @@ -411,8 +411,8 @@ UltraCircuitBuilder create_circuit(AcirFormat& constraint_system,
*/
template <>
MegaCircuitBuilder create_circuit(AcirFormat& constraint_system,
[[maybe_unused]] size_t size_hint,
WitnessVector const& witness,
[[maybe_unused]] const size_t size_hint,
const WitnessVector& witness,
bool honk_recursion,
std::shared_ptr<ECCOpQueue> op_queue,
bool collect_gates_per_opcode)
Expand All @@ -428,6 +428,62 @@ MegaCircuitBuilder create_circuit(AcirFormat& constraint_system,
return builder;
};

/**
* @brief Create a kernel circuit from a constraint system and an IVC instance
*
* @param constraint_system AcirFormat constraint system possibly containing IVC recursion constraints
* @param ivc An IVC instance containing internal data about proofs to be verified
* @param size_hint
* @param witness
* @return MegaCircuitBuilder
*/
MegaCircuitBuilder create_kernel_circuit(AcirFormat& constraint_system,
AztecIVC& ivc,
const WitnessVector& witness,
const size_t size_hint)
{
// Construct the main kernel circuit logic excluding recursive verifiers
auto circuit = create_circuit<MegaCircuitBuilder>(constraint_system,
size_hint,
witness,
/*honk_recursion=*/false,
ivc.goblin.op_queue,
/*collect_gates_per_opcode=*/false);

// We expect the length of the internal verification queue to matche the number of ivc recursion constraints
if (constraint_system.ivc_recursion_constraints.size() != ivc.verification_queue.size()) {
info("WARNING: Mismatch in number of recursive verifications during kernel creation!");
ASSERT(false);
}

// Create stdlib representations of each {proof, vkey} pair in the queue based on their native counterparts
ivc.instantiate_stdlib_verification_queue(circuit);

// Connect each {proof, vkey} pair from the constraint to the corresponding entry in the internal verification
// queue. This ensures that the witnesses utlized in constraints generated based on acir are properly connected to
// the constraints generated herein via the ivc scheme (e.g. recursive verifications).
for (auto [constraint, queue_entry] :
zip_view(constraint_system.ivc_recursion_constraints, ivc.stdlib_verification_queue)) {

// Reconstruct complete proof indices from acir constraint data (in which proof is stripped of public inputs)
std::vector<uint32_t> complete_proof_indices =
ProofSurgeon::create_indices_for_reconstructed_proof(constraint.proof, constraint.public_inputs);
ASSERT(complete_proof_indices.size() == queue_entry.proof.size());

// Assert equality between the proof indices from the constraint data and those of the internal proof
for (auto [proof_idx, proof_value] : zip_view(complete_proof_indices, queue_entry.proof)) {
circuit.assert_equal(proof_value.get_witness_index(), proof_idx);
}
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1090): assert equality between the internal vkey
// and the constaint vkey, or simply use the constraint vkey directly to construct the stdlib vkey used in IVC.
}

// Complete the kernel circuit with all required recursive verifications, databus consistency checks etc.
ivc.complete_kernel_circuit_logic(circuit);

return circuit;
};

template void build_constraints<MegaCircuitBuilder>(MegaCircuitBuilder&, AcirFormat&, bool, bool, bool);

} // namespace acir_format
Loading

0 comments on commit 665750a

Please sign in to comment.