From c527ae94521c4d76153224b7e10cf176038df76b Mon Sep 17 00:00:00 2001 From: ledwards2225 <98505400+ledwards2225@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:43:00 -0700 Subject: [PATCH] feat: IVC integration tests using new accumulate model (#7946) Introduces more in depth testing of the AztecIvc scheme, including tests that mimic the benchmark setup as well as more in depth tests for the inter-circuit databus consistency checks. Other noteworthy components of this PR: - Introduces utility class `PrivateFunctionExecutionMockCircuitProducer` for consistently generating mock circuits (shared between the benchmark and this new test suite). Subclass `MockDatabusProducer` incorporates nontrivial databus interactions into these circuits for the first time. - Removes the old `accumulate()` method on AztecIvc (which simultaneously added recursive verifiers and performed prover work) in favor of two methods that separate the logic but combine to the same effect: `complete_kernel_circuit_logic()` and a new `accumulate()` (which now only performs the prover work). Confusing jump in benchmark explained in [this](https://github.com/AztecProtocol/barretenberg/issues/1072) issue: ``` ----------------------------------------------------------------------------------------- Benchmark Time CPU Iterations UserCounters... ----------------------------------------------------------------------------------------- AztecIVCBench/FullStructured/6 38855 ms 36066 ms 1 Arithmetic::accumulate=4.03742M DEBUG:ProverPolynomials()=12 DEBUG:ProverPolynomials()(t)=3.68002G function ms % sum construct_circuits(t) 4474 11.61% ProverInstance(Circuit&)(t) 6787 17.62% ProtogalaxyProver::fold_instances(t) 21788 56.55% Decider::construct_proof(t) 1655 4.30% ECCVMProver(CircuitBuilder&)(t) 235 0.61% ECCVMProver::construct_proof(t) 2585 6.71% TranslatorProver::construct_proof(t) 824 2.14% Goblin::merge(t) 177 0.46% ``` --- .../src/barretenberg/aztec_ivc/aztec_ivc.cpp | 66 +------- .../src/barretenberg/aztec_ivc/aztec_ivc.hpp | 8 +- .../barretenberg/aztec_ivc/aztec_ivc.test.cpp | 138 ++++++++++------ .../aztec_ivc/aztec_ivc_integration.test.cpp | 111 +++++++++++++ .../aztec_ivc/mock_circuit_producer.hpp | 150 ++++++++++++++++++ .../aztec_ivc_bench/aztec_ivc.bench.cpp | 55 +------ .../arithmetization/mega_arithmetization.hpp | 4 +- .../stdlib/primitives/databus/databus.hpp | 3 + .../stdlib_circuit_builders/mega_flavor.hpp | 4 +- 9 files changed, 372 insertions(+), 167 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc_integration.test.cpp create mode 100644 barretenberg/cpp/src/barretenberg/aztec_ivc/mock_circuit_producer.hpp diff --git a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp index 1513dec8a83..ea6a7629476 100644 --- a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp @@ -2,33 +2,6 @@ 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, 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) - */ -void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk) -{ - circuit_count++; // increment the count of circuits processed into the IVC - - // 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, @@ -38,7 +11,7 @@ void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk) +void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk) { // Construct merge proof for the present circuit and add to merge verification queue MergeProof merge_proof = goblin.prove_merge(circuit); @@ -86,12 +58,12 @@ void AztecIVC::execute_accumulation_prover(ClientCircuit& circuit, // Set the instance verification key from precomputed if available, else compute it instance_vk = precomputed_vk ? precomputed_vk : std::make_shared(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) { + if (!initialized) { fold_output.accumulator = prover_instance; verifier_accumulator = std::make_shared(instance_vk); + initialized = true; } else { // Otherwise, fold the new instance into the accumulator FoldingProver folding_prover({ fold_output.accumulator, prover_instance }); fold_output = folding_prover.fold_instances(); @@ -162,36 +134,6 @@ HonkProof AztecIVC::decider_prove() const 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::vector> AztecIVC::precompute_folding_verification_keys( - std::vector circuits) -{ - std::vector> 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 diff --git a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp index c6da51d99e8..eafb711549d 100644 --- a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp @@ -90,16 +90,12 @@ class AztecIVC { // A flag indicating whether or not to construct a structured trace in the ProverInstance TraceStructure trace_structure = TraceStructure::NONE; - size_t circuit_count = 0; // the number of circuits processed into the IVC - bool is_kernel = false; // is the present circuit a kernel + bool initialized = false; // Is the IVC accumulator initialized // 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& precomputed_vk = nullptr); - void accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk = nullptr); Proof prove(); @@ -115,7 +111,5 @@ class AztecIVC { bool prove_and_verify(); HonkProof decider_prove() const; - - std::vector> precompute_folding_verification_keys(std::vector); }; } // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.test.cpp b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.test.cpp index 1f0cf11a807..ee3a66d1aba 100644 --- a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.test.cpp @@ -52,6 +52,48 @@ class AztecIVCTests : public ::testing::Test { return circuit; } + /** + * @brief A test utility for generating alternating mock app and kernel circuits and precomputing verification keys + * + */ + class MockCircuitProducer { + using ClientCircuit = AztecIVC::ClientCircuit; + + bool is_kernel = false; + + public: + ClientCircuit create_next_circuit(AztecIVC& ivc, size_t log2_num_gates = 16) + { + ClientCircuit circuit{ ivc.goblin.op_queue }; + circuit = create_mock_circuit(ivc, log2_num_gates); // construct mock base logic + if (is_kernel) { + ivc.complete_kernel_circuit_logic(circuit); // complete with recursive verifiers etc + } + is_kernel = !is_kernel; // toggle is_kernel on/off alternatingly + + return circuit; + } + + auto precompute_verification_keys(const size_t num_circuits, + TraceStructure trace_structure, + size_t log2_num_gates = 16) + { + AztecIVC ivc; // temporary IVC instance needed to produce the complete kernel circuits + ivc.trace_structure = trace_structure; + + std::vector> vkeys; + + for (size_t idx = 0; idx < num_circuits; ++idx) { + ClientCircuit circuit = create_next_circuit(ivc, log2_num_gates); // create the next circuit + ivc.accumulate(circuit); // accumulate the circuit + vkeys.emplace_back(ivc.instance_vk); // save the VK for the circuit + } + is_kernel = false; + + return vkeys; + } + }; + /** * @brief Tamper with a proof by finding the first non-zero value and incrementing it by 1 * @@ -77,12 +119,14 @@ TEST_F(AztecIVCTests, Basic) { AztecIVC ivc; + MockCircuitProducer circuit_producer; + // Initialize the IVC with an arbitrary circuit - Builder circuit_0 = create_mock_circuit(ivc); + Builder circuit_0 = circuit_producer.create_next_circuit(ivc); ivc.accumulate(circuit_0); // Create another circuit and accumulate - Builder circuit_1 = create_mock_circuit(ivc); + Builder circuit_1 = circuit_producer.create_next_circuit(ivc); ivc.accumulate(circuit_1); EXPECT_TRUE(ivc.prove_and_verify()); @@ -102,10 +146,12 @@ TEST_F(AztecIVCTests, BadProofFailure) AztecIVC ivc; ivc.trace_structure = TraceStructure::SMALL_TEST; - // Construct a set of arbitrary circuits + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits size_t NUM_CIRCUITS = 4; for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = create_mock_circuit(ivc, /*log2_num_gates=*/5); + auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); ivc.accumulate(circuit); } EXPECT_TRUE(ivc.prove_and_verify()); @@ -116,10 +162,12 @@ TEST_F(AztecIVCTests, BadProofFailure) AztecIVC ivc; ivc.trace_structure = TraceStructure::SMALL_TEST; - // Construct a set of arbitrary circuits + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits size_t NUM_CIRCUITS = 4; for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = create_mock_circuit(ivc, /*log2_num_gates=*/5); + auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); ivc.accumulate(circuit); if (idx == 2) { @@ -136,10 +184,12 @@ TEST_F(AztecIVCTests, BadProofFailure) AztecIVC ivc; ivc.trace_structure = TraceStructure::SMALL_TEST; - // Construct a set of arbitrary circuits + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits size_t NUM_CIRCUITS = 4; for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = create_mock_circuit(ivc, /*log2_num_gates=*/5); + auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); ivc.accumulate(circuit); if (idx == 2) { @@ -156,10 +206,12 @@ TEST_F(AztecIVCTests, BadProofFailure) AztecIVC ivc; ivc.trace_structure = TraceStructure::SMALL_TEST; - // Construct a set of arbitrary circuits + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits size_t NUM_CIRCUITS = 4; for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = create_mock_circuit(ivc, /*log2_num_gates=*/5); + auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); ivc.accumulate(circuit); } @@ -181,15 +233,13 @@ TEST_F(AztecIVCTests, BasicLarge) { AztecIVC ivc; - // Construct a set of arbitrary circuits + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits size_t NUM_CIRCUITS = 6; std::vector circuits; for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - circuits.emplace_back(create_mock_circuit(ivc)); - } - - // Accumulate each circuit - for (auto& circuit : circuits) { + auto circuit = circuit_producer.create_next_circuit(ivc); ivc.accumulate(circuit); } @@ -205,17 +255,17 @@ TEST_F(AztecIVCTests, BasicStructured) AztecIVC ivc; ivc.trace_structure = TraceStructure::SMALL_TEST; - // Construct some circuits of varying size - Builder circuit_0 = create_mock_circuit(ivc, /*log2_num_gates=*/5); - Builder circuit_1 = create_mock_circuit(ivc, /*log2_num_gates=*/6); - Builder circuit_2 = create_mock_circuit(ivc, /*log2_num_gates=*/7); - Builder circuit_3 = create_mock_circuit(ivc, /*log2_num_gates=*/8); + MockCircuitProducer circuit_producer; - // The circuits can be accumulated as normal due to the structured trace - ivc.accumulate(circuit_0); - ivc.accumulate(circuit_1); - ivc.accumulate(circuit_2); - ivc.accumulate(circuit_3); + size_t NUM_CIRCUITS = 4; + + // Construct and accumulate some circuits of varying size + size_t log2_num_gates = 5; + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto circuit = circuit_producer.create_next_circuit(ivc, log2_num_gates); + ivc.accumulate(circuit); + log2_num_gates += 2; + } EXPECT_TRUE(ivc.prove_and_verify()); }; @@ -228,19 +278,16 @@ TEST_F(AztecIVCTests, PrecomputedVerificationKeys) { AztecIVC ivc; - // Construct a set of arbitrary circuits size_t NUM_CIRCUITS = 4; - std::vector circuits; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - circuits.emplace_back(create_mock_circuit(ivc)); - } - // Precompute the verification keys that will be needed for the IVC - auto precomputed_vkeys = ivc.precompute_folding_verification_keys(circuits); + MockCircuitProducer circuit_producer; + + auto precomputed_vks = circuit_producer.precompute_verification_keys(NUM_CIRCUITS, TraceStructure::NONE); - // Accumulate each circuit using the precomputed VKs - for (auto [circuit, precomputed_vk] : zip_view(circuits, precomputed_vkeys)) { - ivc.accumulate(circuit, precomputed_vk); + // Construct and accumulate set of circuits using the precomputed vkeys + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto circuit = circuit_producer.create_next_circuit(ivc); + ivc.accumulate(circuit, precomputed_vks[idx]); } EXPECT_TRUE(ivc.prove_and_verify()); @@ -255,19 +302,18 @@ TEST_F(AztecIVCTests, StructuredPrecomputedVKs) AztecIVC ivc; ivc.trace_structure = TraceStructure::SMALL_TEST; - // Construct a set of arbitrary circuits size_t NUM_CIRCUITS = 4; - std::vector circuits; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - circuits.emplace_back(create_mock_circuit(ivc, /*log2_num_gates=*/5)); - } + size_t log2_num_gates = 5; // number of gates in baseline mocked circuit - // Precompute the (structured) verification keys that will be needed for the IVC - auto precomputed_vkeys = ivc.precompute_folding_verification_keys(circuits); + MockCircuitProducer circuit_producer; - // Accumulate each circuit - for (auto [circuit, precomputed_vk] : zip_view(circuits, precomputed_vkeys)) { - ivc.accumulate(circuit, precomputed_vk); + auto precomputed_vks = + circuit_producer.precompute_verification_keys(NUM_CIRCUITS, ivc.trace_structure, log2_num_gates); + + // Construct and accumulate set of circuits using the precomputed vkeys + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto circuit = circuit_producer.create_next_circuit(ivc, log2_num_gates); + ivc.accumulate(circuit, precomputed_vks[idx]); } EXPECT_TRUE(ivc.prove_and_verify()); diff --git a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc_integration.test.cpp b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc_integration.test.cpp new file mode 100644 index 00000000000..55eca16b126 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc_integration.test.cpp @@ -0,0 +1,111 @@ +#include "barretenberg/aztec_ivc/aztec_ivc.hpp" +#include "barretenberg/aztec_ivc/mock_circuit_producer.hpp" +#include "barretenberg/goblin/goblin.hpp" +#include "barretenberg/goblin/mock_circuits.hpp" +#include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" + +#include + +using namespace bb; + +/** + * @brief A test suite that mirrors the logic in the nominal IVC benchmark case + * + */ +class AztecIVCIntegrationTests : public ::testing::Test { + protected: + static void SetUpTestSuite() + { + srs::init_crs_factory("../srs_db/ignition"); + srs::init_grumpkin_crs_factory("../srs_db/grumpkin"); + } + + using Flavor = AztecIVC::Flavor; + using FF = typename Flavor::FF; + using VerificationKey = Flavor::VerificationKey; + using Builder = AztecIVC::ClientCircuit; + using MockCircuitProducer = PrivateFunctionExecutionMockCircuitProducer; +}; + +/** + * @brief Prove and verify accumulation of a set of mocked private function execution circuits + * @details This case is meant to mirror the medium complexity benchmark configuration case but processes only 6 + * circuits total (3 app, 3 kernel) to save time. + * + */ +TEST_F(AztecIVCIntegrationTests, BenchmarkCaseSimple) +{ + AztecIVC ivc; + ivc.trace_structure = TraceStructure::AZTEC_IVC_BENCH; + + MockCircuitProducer circuit_producer; + + // Construct and accumulate a series of mocked private function execution circuits + size_t NUM_CIRCUITS = 6; + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + Builder circuit = circuit_producer.create_next_circuit(ivc); + + ivc.accumulate(circuit); + } + + EXPECT_TRUE(ivc.prove_and_verify()); +}; + +/** + * @brief Prove and verify accumulation of a set of mocked private function execution circuits with precomputed + * verification keys + * + */ +TEST_F(AztecIVCIntegrationTests, BenchmarkCasePrecomputedVKs) +{ + AztecIVC ivc; + ivc.trace_structure = TraceStructure::AZTEC_IVC_BENCH; + + size_t NUM_CIRCUITS = 6; + + MockCircuitProducer circuit_producer; + + auto precomputed_vks = circuit_producer.precompute_verification_keys(NUM_CIRCUITS, ivc.trace_structure); + + // Construct and accumulate a series of mocked private function execution circuits + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + Builder circuit = circuit_producer.create_next_circuit(ivc); + + ivc.accumulate(circuit, precomputed_vks[idx]); + } + + EXPECT_TRUE(ivc.prove_and_verify()); +}; + +/** + * @brief Demonstrate that a databus inconsistency leads to verification failure for the IVC + * @details Kernel circuits contain databus consistency checks that establish that data was passed faithfully between + * circuits, e.g. the output (return_data) of an app was the input (secondary_calldata) of a kernel. This test tampers + * with the databus in such a way that one of the kernels receives secondary_calldata based on tampered app return data. + * This leads to an invalid witness in the check that ensures that the two corresponding commitments are equal and thus + * causes failure of the IVC to verify. + * + */ +TEST_F(AztecIVCIntegrationTests, DatabusFailure) +{ + AztecIVC ivc; + ivc.trace_structure = TraceStructure::AZTEC_IVC_BENCH; + + MockCircuitProducer circuit_producer; + + // Construct and accumulate a series of mocked private function execution circuits + size_t NUM_CIRCUITS = 6; + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + Builder circuit = circuit_producer.create_next_circuit(ivc); + + // Tamper with the return data of the second app circuit before it is processed as input to the next kernel + if (idx == 2) { + circuit_producer.tamper_with_databus(); + } + + ivc.accumulate(circuit); + } + + EXPECT_FALSE(ivc.prove_and_verify()); +}; diff --git a/barretenberg/cpp/src/barretenberg/aztec_ivc/mock_circuit_producer.hpp b/barretenberg/cpp/src/barretenberg/aztec_ivc/mock_circuit_producer.hpp new file mode 100644 index 00000000000..e6d48e08b32 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/mock_circuit_producer.hpp @@ -0,0 +1,150 @@ + +#include "barretenberg/aztec_ivc/aztec_ivc.hpp" +#include "barretenberg/common/op_count.hpp" +#include "barretenberg/goblin/mock_circuits.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" +#include "barretenberg/ultra_honk/ultra_verifier.hpp" + +using namespace bb; + +namespace { + +/** + * @brief Test utility for coordinating passing of databus data between mocked private function execution circuits + * @details Facilitates testing of the databus consistency checks that establish the correct passing of databus data + * between circuits. Generates arbitrary return data for each app/kernel. Sets the kernel calldata and + * secondary_calldata based respectively on the previous kernel return data and app return data. + */ +class MockDatabusProducer { + private: + using ClientCircuit = AztecIVC::ClientCircuit; + using Flavor = MegaFlavor; + using FF = Flavor::FF; + using BusDataArray = std::vector; + + static constexpr size_t BUS_ARRAY_SIZE = 3; // arbitrary length of mock bus inputs + BusDataArray app_return_data; + BusDataArray kernel_return_data; + + FF dummy_return_val = 1; // use simple return val for easier test debugging + + BusDataArray generate_random_bus_array() + { + BusDataArray result; + for (size_t i = 0; i < BUS_ARRAY_SIZE; ++i) { + result.emplace_back(dummy_return_val); + } + dummy_return_val += 1; + return result; + } + + public: + /** + * @brief Update the app return data and populate it in the app circuit + */ + void populate_app_databus(ClientCircuit& circuit) + { + app_return_data = generate_random_bus_array(); + for (auto& val : app_return_data) { + circuit.add_public_return_data(circuit.add_variable(val)); + } + }; + + /** + * @brief Populate the calldata and secondary calldata in the kernel from respectively the previous kernel and app + * return data. Update and populate the return data for the present kernel. + */ + void populate_kernel_databus(ClientCircuit& circuit) + { + for (auto& val : kernel_return_data) { // populate calldata from previous kernel return data + circuit.add_public_calldata(circuit.add_variable(val)); + } + for (auto& val : app_return_data) { // populate secondary_calldata from app return data + circuit.add_public_secondary_calldata(circuit.add_variable(val)); + } + kernel_return_data = generate_random_bus_array(); // update the return data for the present kernel circuit + for (auto& val : kernel_return_data) { + circuit.add_public_return_data(circuit.add_variable(val)); + } + }; + + /** + * @brief Add an arbitrary value to the app return data. This leads to a descrepency between the values used by the + * app itself and the secondary_calldata values in the kernel that will be set based on these tampered values. + */ + void tamper_with_app_return_data() { app_return_data.emplace_back(17); } +}; + +/** + * @brief Manage the construction of mock app/kernel circuits for the private function execution setting + * @details Per the medium complexity benchmark spec, the first app circuit is size 2^19. Subsequent app and kernel + * circuits are size 2^17. Circuits produced are alternatingly app and kernel. Mock databus data is passed between the + * circuits in a manor conistent with the real architecture in order to facilitate testing of databus consistency + * checks. + */ +class PrivateFunctionExecutionMockCircuitProducer { + using ClientCircuit = AztecIVC::ClientCircuit; + using Flavor = MegaFlavor; + using VerificationKey = Flavor::VerificationKey; + + size_t circuit_counter = 0; + + MockDatabusProducer mock_databus; + + public: + /** + * @brief Create a the next circuit (app/kernel) in a mocked private function execution stack + */ + ClientCircuit create_next_circuit(AztecIVC& ivc) + { + circuit_counter++; + + bool is_kernel = (circuit_counter % 2 == 0); // Every other circuit is a kernel, starting from the second + + ClientCircuit circuit{ ivc.goblin.op_queue }; + if (is_kernel) { + GoblinMockCircuits::construct_mock_folding_kernel(circuit); // construct mock base logic + mock_databus.populate_kernel_databus(circuit); // populate databus inputs/outputs + ivc.complete_kernel_circuit_logic(circuit); // complete with recursive verifiers etc + } else { + bool use_large_circuit = (circuit_counter == 1); // first circuit is size 2^19 + GoblinMockCircuits::construct_mock_app_circuit(circuit, use_large_circuit); // construct mock app + mock_databus.populate_app_databus(circuit); // populate databus outputs + } + return circuit; + } + + /** + * @brief Tamper with databus data to facilitate failure testing + */ + void tamper_with_databus() { mock_databus.tamper_with_app_return_data(); } + + /** + * @brief Compute and return the verification keys for a mocked private function execution IVC + * @details For testing/benchmarking only. This method is robust at the cost of being extremely inefficient. It + * simply executes a full IVC for a given number of circuits and stores the verification keys along the way. (In + * practice these VKs will be known to a client prover in advance). + * + * @param num_circuits + * @param trace_structure Trace structuring must be known in advance because it effects the VKs + * @return set of num_circuits-many verification keys + */ + auto precompute_verification_keys(const size_t num_circuits, TraceStructure trace_structure) + { + AztecIVC ivc; // temporary IVC instance needed to produce the complete kernel circuits + ivc.trace_structure = trace_structure; + + std::vector> vkeys; + + for (size_t idx = 0; idx < num_circuits; ++idx) { + ClientCircuit circuit = create_next_circuit(ivc); // create the next circuit + ivc.accumulate(circuit); // accumulate the circuit + vkeys.emplace_back(ivc.instance_vk); // save the VK for the circuit + } + circuit_counter = 0; // reset the internal circuit counter back to 0 + + return vkeys; + } +}; + +} // namespace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/benchmark/aztec_ivc_bench/aztec_ivc.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/aztec_ivc_bench/aztec_ivc.bench.cpp index a24535ea22b..96004352476 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/aztec_ivc_bench/aztec_ivc.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/aztec_ivc_bench/aztec_ivc.bench.cpp @@ -2,6 +2,7 @@ #include #include "barretenberg/aztec_ivc/aztec_ivc.hpp" +#include "barretenberg/aztec_ivc/mock_circuit_producer.hpp" #include "barretenberg/common/op_count.hpp" #include "barretenberg/common/op_count_google_bench.hpp" #include "barretenberg/goblin/mock_circuits.hpp" @@ -22,6 +23,7 @@ class AztecIVCBench : public benchmark::Fixture { using Builder = MegaCircuitBuilder; using VerifierInstance = VerifierInstance_; using Proof = AztecIVC::Proof; + using MockCircuitProducer = PrivateFunctionExecutionMockCircuitProducer; // Number of function circuits to accumulate(based on Zacs target numbers) static constexpr size_t NUM_ITERATIONS_MEDIUM_COMPLEXITY = 6; @@ -50,52 +52,6 @@ class AztecIVCBench : public benchmark::Fixture { return verified; } - /** - * @brief Precompute the verification keys for the bench given the number of circuits in the IVC - * - * @param ivc - * @param num_function_circuits - * @return auto - */ - static auto precompute_verification_keys(AztecIVC& ivc, const size_t num_circuits) - { - // Produce the set of mocked circuits to be accumulated - MockCircuitMaker mock_circuit_maker; - std::vector circuits; - for (size_t circuit_idx = 0; circuit_idx < num_circuits; ++circuit_idx) { - circuits.emplace_back(mock_circuit_maker.create_next_circuit(ivc)); - } - - // Compute and return the corresponding set of verfication keys - return ivc.precompute_folding_verification_keys(circuits); - } - - /** - * @brief Manage the construction of mock app/kernel circuits - * @details Per the medium complexity benchmark spec, the first app circuit is size 2^19. Subsequent app and kernel - * circuits are size 2^17. Circuits produced are alternatingly app and kernel. - */ - class MockCircuitMaker { - size_t circuit_counter = 0; - - public: - Builder create_next_circuit(AztecIVC& ivc) - { - circuit_counter++; - - bool is_kernel = (circuit_counter % 2 == 0); // Every other circuit is a kernel, starting from the second - - Builder circuit{ ivc.goblin.op_queue }; - if (is_kernel) { // construct mock kernel - GoblinMockCircuits::construct_mock_folding_kernel(circuit); - } else { // construct mock app - bool use_large_circuit = (circuit_counter == 1); - GoblinMockCircuits::construct_mock_app_circuit(circuit, use_large_circuit); - } - return circuit; - } - }; - /** * @brief Perform a specified number of circuit accumulation rounds * @@ -105,13 +61,13 @@ class AztecIVCBench : public benchmark::Fixture { { ASSERT(precomputed_vks.size() == NUM_CIRCUITS); // ensure presence of a precomputed VK for each circuit - MockCircuitMaker mock_circuit_maker; + MockCircuitProducer circuit_producer; for (size_t circuit_idx = 0; circuit_idx < NUM_CIRCUITS; ++circuit_idx) { Builder circuit; { BB_OP_COUNT_TIME_NAME("construct_circuits"); - circuit = mock_circuit_maker.create_next_circuit(ivc); + circuit = circuit_producer.create_next_circuit(ivc); } ivc.accumulate(circuit, precomputed_vks[circuit_idx]); @@ -131,7 +87,8 @@ BENCHMARK_DEFINE_F(AztecIVCBench, FullStructured)(benchmark::State& state) auto total_num_circuits = 2 * static_cast(state.range(0)); // 2x accounts for kernel circuits // Precompute the verification keys for the benchmark circuits - auto precomputed_vkeys = precompute_verification_keys(ivc, total_num_circuits); + MockCircuitProducer circuit_producer; + auto precomputed_vkeys = circuit_producer.precompute_verification_keys(total_num_circuits, ivc.trace_structure); Proof proof; for (auto _ : state) { diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp index 8f0e9e35721..c3f898f7d70 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/mega_arithmetization.hpp @@ -89,8 +89,8 @@ template class MegaArith { this->aux = 137000; this->lookup = 72000; this->busread = 1 << 7; - this->poseidon_external = 3000; - this->poseidon_internal = 17000; + this->poseidon_external = 3500; + this->poseidon_internal = 17500; } }; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp index 6979a32a85b..f7085bc1ea3 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp @@ -225,6 +225,9 @@ template class DataBusDepot { void assert_equality_of_commitments(Commitment& P0, Commitment& P1) { + if (P0.get_value() != P1.get_value()) { // debug print indicating consistency check failure + info("DataBusDepot: Databus consistency check failed!"); + } P0.x.assert_equal(P1.x); P0.y.assert_equal(P1.y); } diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_flavor.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_flavor.hpp index 618b726c2ff..e3e3b71cbd2 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_flavor.hpp @@ -7,7 +7,6 @@ #include "barretenberg/honk/proof_system/types/proof.hpp" #include "barretenberg/plonk_honk_shared/library/grand_product_delta.hpp" #include "barretenberg/plonk_honk_shared/library/grand_product_library.hpp" -#include "barretenberg/polynomials/polynomial.hpp" #include "barretenberg/polynomials/univariate.hpp" #include "barretenberg/relations/auxiliary_relation.hpp" #include "barretenberg/relations/databus_lookup_relation.hpp" @@ -376,6 +375,9 @@ class MegaFlavor { ProverPolynomials() = default; ProverPolynomials(size_t circuit_size) { // Initialize all unshifted polynomials to the zero polynomial and initialize the shifted polys + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1072): Unexpected jump in time to allocate all + // of these polys (in aztec_ivc_bench only). + BB_OP_COUNT_TIME_NAME("ProverPolynomials(size_t)"); for (auto& poly : get_unshifted()) { poly = Polynomial{ circuit_size }; }