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: split merge into recursive verification and proving #7801

Merged
merged 27 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
68922ac
basic structures and logic in place
ledwards2225 Jul 30, 2024
f399b75
Merge branch 'master' into lde/link_databus
ledwards2225 Jul 30, 2024
92626f0
Merge branch 'master' into lde/link_databus
ledwards2225 Aug 1, 2024
3c036a4
updates and moving bus depot to aztec ivc
ledwards2225 Aug 1, 2024
816193a
databus propagation data struct integration
ledwards2225 Aug 2, 2024
7f571e0
6 circuit IVC passes with databus checks, plus comments
ledwards2225 Aug 2, 2024
bfcf473
add issue
ledwards2225 Aug 2, 2024
0ec8528
delete unneeded test suite
ledwards2225 Aug 2, 2024
f14a53a
reinstate all aztec ivc tests, all pass
ledwards2225 Aug 2, 2024
cce6030
cleanup
ledwards2225 Aug 2, 2024
3784950
commenting methods
ledwards2225 Aug 2, 2024
e0f02d8
commentyclean
ledwards2225 Aug 3, 2024
57c3e68
more comments and cleanup
ledwards2225 Aug 3, 2024
71db843
reorder
ledwards2225 Aug 3, 2024
678d143
Merge branch 'master' into lde/link_databus
ledwards2225 Aug 3, 2024
c940cbc
expand comment
ledwards2225 Aug 3, 2024
50bee30
Merge branch 'master' into lde/link_databus
ledwards2225 Aug 6, 2024
d846296
split merge into rec verification and proof construction
ledwards2225 Aug 6, 2024
9f8886a
separate prove and verify
ledwards2225 Aug 6, 2024
a72312d
recursive merge verification in kernel only
ledwards2225 Aug 6, 2024
67bb279
clean up the circuit altering logic in accum
ledwards2225 Aug 6, 2024
0ed4099
Merge branch 'master' into lde/split_merge
ledwards2225 Aug 6, 2024
28b9da4
fix
ledwards2225 Aug 6, 2024
62c51f0
clean
ledwards2225 Aug 6, 2024
9749bbf
comments in az test
ledwards2225 Aug 7, 2024
f6f44da
reexpress accumulation in terms of circuit completion and proving
ledwards2225 Aug 7, 2024
d671dbf
cleanup
ledwards2225 Aug 7, 2024
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
101 changes: 67 additions & 34 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace bb {
* @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.
* Similarly, merge proofs are stored in a queue and recursively verified in kernels.
*
* @param circuit Circuit to be accumulated/folded
* @param precomputed_vk Optional precomputed VK (otherwise will be computed herein)
Expand All @@ -16,42 +16,73 @@ void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr<Verifica
{
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) {
// Perform folding recursive verification
FoldingRecursiveVerifier verifier{ &circuit, { verifier_accumulator, { vkey } } };
auto verifier_accum = verifier.verify_folding_proof(proof);
verifier_accumulator = std::make_shared<VerifierInstance>(verifier_accum->get_value());

// Perform databus commitment consistency checks and propagate return data commitments via the public inputs
bus_depot.execute(verifier.instances);
}
verification_queue.clear();
// The aztec architecture dictates that every second circuit is a kernel. This check can be triggered/replaced by
// the presence of the recursive folding verify opcode once it is introduced into noir.
is_kernel = (circuit_count % 2 == 0);

// If present circuit is a kernel, perform required recursive PG and/or merge verifications and databus checks
if (is_kernel) {
complete_kernel_circuit_logic(circuit);
}

// Perform PG and/or merge proving
execute_accumulation_prover(circuit, precomputed_vk);
}

/**
* @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)
{
BB_OP_COUNT_TIME_NAME("construct_circuits");

// The folding verification queue should be either empty or contain two fold proofs
ASSERT(verification_queue.empty() || verification_queue.size() == 2);

for (auto& [proof, vkey] : verification_queue) {
// Perform folding recursive verification
FoldingRecursiveVerifier verifier{ &circuit, { verifier_accumulator, { vkey } } };
auto verifier_accum = verifier.verify_folding_proof(proof);
verifier_accumulator = std::make_shared<VerifierInstance>(verifier_accum->get_value());

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier.instances);
}
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);
// Recusively verify all merge proofs in queue
for (auto& proof : merge_verification_queue) {
goblin.verify_merge(circuit, proof);
}
merge_verification_queue.clear();
}

/**
* @brief Execute prover work for instance accumulation
* @details Construct an instance for the provided circuit. If this is the first instance in the IVC, simply initialize
* the folding accumulator. Otherwise, execute the PG prover to fold the instance into the accumulator and produce a
* folding proof. Also execute the merge protocol to produce a merge proof.
*
* @param circuit
* @param precomputed_vk
*/
void AztecIVC::execute_accumulation_prover(ClientCircuit& circuit,
const std::shared_ptr<VerificationKey>& precomputed_vk)
{
// Construct merge proof for the present circuit and add to merge verification queue
MergeProof merge_proof = goblin.prove_merge(circuit);
merge_verification_queue.emplace_back(merge_proof);

// 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);
}

// Store whether the present circuit is a kernel (Note: the aztec architecture dictates that every second circuit
// is a kernel. This check can triggered/replaced by the presence of the recursive folding verify opcode once it is
// introduced into noir).
instance_vk->databus_propagation_data.is_kernel = (circuit_count % 2 == 0);
instance_vk = precomputed_vk ? precomputed_vk : std::make_shared<VerificationKey>(prover_instance->proving_key);
instance_vk->databus_propagation_data.is_kernel = is_kernel; // Store whether the present circuit is a kernel

// If this is the first circuit simply initialize the prover and verifier accumulator instances
if (circuit_count == 1) {
Expand All @@ -76,10 +107,12 @@ void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr<Verifica
*/
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() };
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
ASSERT(merge_verification_queue.size() == 1); // ensure only a single merge proof remains in the queue
FoldProof& fold_proof = verification_queue[0].proof;
MergeProof& merge_proof = merge_verification_queue[0];
return { fold_proof, decider_prove(), goblin.prove(merge_proof) };
};

bool AztecIVC::verify(const Proof& proof,
Expand Down
14 changes: 12 additions & 2 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class AztecIVC {
using VerificationKey = Flavor::VerificationKey;
using FF = Flavor::FF;
using FoldProof = std::vector<FF>;
using MergeProof = std::vector<FF>;
using ProverInstance = ProverInstance_<Flavor>;
using VerifierInstance = VerifierInstance_<Flavor>;
using ClientCircuit = MegaCircuitBuilder; // can only be Mega
Expand Down Expand Up @@ -80,15 +81,24 @@ class AztecIVC {

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

// Management of linking databus commitments between circuits in the IVC
DataBusDepot bus_depot;

// 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;
size_t circuit_count = 0; // the number of circuits processed into the IVC
bool is_kernel = false; // is the present circuit a kernel

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

// Perform prover work for accumulation (e.g. PG folding, merge proving)
void execute_accumulation_prover(ClientCircuit& circuit,
const std::shared_ptr<VerificationKey>& precomputed_vk = nullptr);

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

Expand Down
14 changes: 6 additions & 8 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ class AztecIVCTests : public ::testing::Test {

/**
* @brief Construct mock circuit with arithmetic gates and goblin ops
* @details Currently default sized to 2^16 to match kernel. (Note: dummy op gates added to avoid non-zero
* polynomials will bump size to next power of 2)
* @details Defaulted to add 2^16 gates (which will bump to next power of two with the addition of dummy gates).
* The size of the baseline circuit needs to be ~2x the number of gates appended to the kernel circuits via
* recursive verifications (currently ~60k) to ensure that the circuits being folded are equal in size. (This is
* only necessary if the structured trace is not in use).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into making this more robust but its already as good as it can be without making the logic much more complicated. The circuits created here are just over a power-of-two limit so the amount we can add to them is maximized. If recursive verification gets more expensive then this might go over the limit again but that's just the way it is.

*
*/
static Builder create_mock_circuit(AztecIVC& ivc, size_t log2_num_gates = 16)
Expand Down Expand Up @@ -88,10 +90,8 @@ TEST_F(AztecIVCTests, Basic)

/**
* @brief Check that the IVC fails to verify if an intermediate fold proof is invalid
* @details When accumulating 4 circuits, there are 3 fold proofs to verify (the first two are recursively verfied
and
* the 3rd is verified as part of the IVC proof). Check that if any of one of these proofs is invalid, the IVC will
fail
* @details When accumulating 4 circuits, there are 3 fold proofs to verify (the first two are recursively verfied and
* the 3rd is verified as part of the IVC proof). Check that if any of one of these proofs is invalid, the IVC will fail
* to verify.
*
*/
Expand Down Expand Up @@ -193,8 +193,6 @@ TEST_F(AztecIVCTests, BasicLarge)
ivc.accumulate(circuit);
}

info(ivc.goblin.op_queue->get_current_size());

EXPECT_TRUE(ivc.prove_and_verify());
};

Expand Down
46 changes: 35 additions & 11 deletions barretenberg/cpp/src/barretenberg/goblin/goblin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,18 @@ class GoblinProver {
using TranslatorProver = bb::TranslatorProver;
using TranslatorProvingKey = bb::TranslatorFlavor::ProvingKey;
using RecursiveMergeVerifier = bb::stdlib::recursion::goblin::MergeRecursiveVerifier_<MegaCircuitBuilder>;
using PairingPoints = RecursiveMergeVerifier::PairingPoints;
using MergeProver = bb::MergeProver_<MegaFlavor>;
using VerificationKey = MegaFlavor::VerificationKey;
using MergeProof = std::vector<FF>;
/**
* @brief Output of goblin::accumulate; an Ultra proof and the corresponding verification key
*
*/

std::shared_ptr<OpQueue> op_queue = std::make_shared<OpQueue>();

HonkProof merge_proof;
MergeProof merge_proof;
GoblinProof goblin_proof;

// on the first call to accumulate there is no merge proof to verify
Expand Down Expand Up @@ -115,13 +117,36 @@ class GoblinProver {
*/
void merge(MegaCircuitBuilder& circuit_builder)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when is this function used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in client ivc I guess? is the plan that aztec ivc replaces client ivc at some point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the plan is definitely that AztecIvc replaces ClientIvc as the IVC scheme used for aztec but it's not totally clear whether ClientIvc should go away or not. It might still have some use as a more general IVC scheme for noir or something

{
BB_OP_COUNT_TIME_NAME("Goblin::merge");
// Complete the circuit logic by recursively verifying previous merge proof if it exists
// Append a recursive merge verification of the merge proof
if (merge_proof_exists) {
RecursiveMergeVerifier merge_verifier{ &circuit_builder };
[[maybe_unused]] auto pairing_points = merge_verifier.verify_proof(merge_proof);
[[maybe_unused]] auto pairing_points = verify_merge(circuit_builder, merge_proof);
}

// Construct a merge proof for the present circuit
merge_proof = prove_merge(circuit_builder);
};

/**
* @brief Append recursive verification of a merge proof to a provided circuit
*
* @param circuit_builder
* @return PairingPoints
*/
PairingPoints verify_merge(MegaCircuitBuilder& circuit_builder, MergeProof& proof) const
{
BB_OP_COUNT_TIME_NAME("Goblin::merge");
RecursiveMergeVerifier merge_verifier{ &circuit_builder };
return merge_verifier.verify_proof(proof);
};

/**
* @brief Construct a merge proof for the goblin ECC ops in the provided circuit
*
* @param circuit_builder
*/
MergeProof prove_merge(MegaCircuitBuilder& circuit_builder)
{
BB_OP_COUNT_TIME_NAME("Goblin::merge");
// TODO(https://github.com/AztecProtocol/barretenberg/issues/993): Some circuits (particularly on the first call
// to accumulate) may not have any goblin ecc ops prior to the call to merge(), so the commitment to the new
// contribution (C_t_shift) in the merge prover will be the point at infinity. (Note: Some dummy ops are added
Expand All @@ -131,13 +156,12 @@ class GoblinProver {
MockCircuits::construct_goblin_ecc_op_circuit(circuit_builder); // Add some arbitrary goblin ECC ops
}

// Construct and store the merge proof to be recursively verified on the next call to accumulate
MergeProver merge_prover{ circuit_builder.op_queue };
merge_proof = merge_prover.construct_proof();

if (!merge_proof_exists) {
merge_proof_exists = true;
}

MergeProver merge_prover{ circuit_builder.op_queue };
return merge_prover.construct_proof();
};

/**
Expand Down Expand Up @@ -171,9 +195,9 @@ class GoblinProver {
*
* @return Proof
*/
GoblinProof prove()
GoblinProof prove(MergeProof merge_proof_in = {})
{
goblin_proof.merge_proof = std::move(merge_proof);
goblin_proof.merge_proof = merge_proof_in.empty() ? std::move(merge_proof) : std::move(merge_proof_in);
prove_eccvm();
prove_translator();
return goblin_proof;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ template <typename FF_> class MegaArith {
this->lookup = FIXED_SIZE;
this->busread = FIXED_SIZE;
this->poseidon_external = FIXED_SIZE;
this->poseidon_internal = FIXED_SIZE;
this->poseidon_internal = 1 << 15;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performing two recursive merge verifiers in one circuit bumps this just beyond the previous limit

}
};

Expand Down
Loading