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 index 5ff25da16f1..abc34ab49bd 100644 --- a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc_integration.test.cpp +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc_integration.test.cpp @@ -9,6 +9,10 @@ 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() @@ -49,9 +53,8 @@ TEST_F(AztecIVCIntegrationTests, BenchmarkCaseSimple) }; /** - * @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. + * @brief Prove and verify accumulation of a set of mocked private function execution circuits with precomputed + * verification keys * */ TEST_F(AztecIVCIntegrationTests, BenchmarkCasePrecomputedVKs) @@ -74,3 +77,35 @@ TEST_F(AztecIVCIntegrationTests, BenchmarkCasePrecomputedVKs) 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.execute_accumulation_prover(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 index 148af19bbce..4963197203e 100644 --- a/barretenberg/cpp/src/barretenberg/aztec_ivc/mock_circuit_producer.hpp +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/mock_circuit_producer.hpp @@ -22,55 +22,76 @@ class PrivateFunctionExecutionMockCircuitProducer { size_t circuit_counter = 0; - struct MockDatabusProducer { + /** + * @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 BusArray = std::vector; static constexpr size_t BUS_ARRAY_SIZE = 3; // arbitrary length of mock bus arrrays BusArray app_return_data; BusArray kernel_return_data; - FF dummy_val = 1; + FF dummy_return_val = 1; // use simple return val for easier test debugging BusArray generate_random_bus_array() { BusArray result; for (size_t i = 0; i < BUS_ARRAY_SIZE; ++i) { - result.emplace_back(dummy_val); - // result.emplace_back(FF::random_element()); + result.emplace_back(dummy_return_val); } - dummy_val += 1; + 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) { - // update the app return data and populate it in the 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) { - // populate the two calldata inputs from the previous kernel and app return data (if it exists) - for (auto& val : kernel_return_data) { + 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) { + for (auto& val : app_return_data) { // populate secondary_calldata from app return data circuit.add_public_secondary_calldata(circuit.add_variable(val)); } - // update the kernel return data and populate it in the circuit - kernel_return_data = generate_random_bus_array(); + 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); } }; 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++; @@ -90,6 +111,11 @@ class PrivateFunctionExecutionMockCircuitProducer { 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 diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp index c8133192a53..f7085bc1ea3 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp @@ -119,8 +119,6 @@ template class DataBusDepot { auto& public_inputs = inst_2->public_inputs; auto& commitments = inst_2->witness_commitments; - info("Databus execute: is_kernel = ", is_kernel_instance); - // Assert equality between return data commitments propagated via the public inputs and the corresponding // calldata commitment if (is_kernel_instance) { // only kernels can contain commitments propagated via public inputs @@ -227,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); }