diff --git a/.github/workflows/ci3-external.yml b/.github/workflows/ci3-external.yml index fddfd9af34b6..072f7be0e5bc 100644 --- a/.github/workflows/ci3-external.yml +++ b/.github/workflows/ci3-external.yml @@ -95,7 +95,12 @@ jobs: uses: actions/cache@v3 with: path: ci-success.txt - key: ci-external-${{ env.TREE_HASH }} + key: ci-external-${{ env.CI_MERGE_QUEUE == '1' && 'merge-queue' }}-${{ env.CI_FULL == '1' && 'full' }}-${{ env.NO_CACHE == '1' && 'no-cache' }}-${{ env.NO_FAIL_FAST == '1' && 'no-fail-fast' }}-${{ env.TREE_HASH }} + + - name: CI Cached + if: steps.ci_cache.outputs.cache-hit == 'true' + run: | + echo "Previous run: $(cat ci-success.txt)" ############# # Run @@ -123,7 +128,7 @@ jobs: - name: Save CI Success if: steps.ci_cache.outputs.cache-hit != 'true' - run: echo "success" > ci-success.txt + run: echo "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" > ci-success.txt # If we have passed CI and labelled with ci-squash-and-merge, squash the PR. # This will rerun CI on the squash commit - but is intended to be a no-op due to caching. diff --git a/.github/workflows/ci3.yml b/.github/workflows/ci3.yml index 64058919ec76..f7748c500e57 100644 --- a/.github/workflows/ci3.yml +++ b/.github/workflows/ci3.yml @@ -110,6 +110,11 @@ jobs: path: ci-success.txt key: ci-${{ github.event_name == 'merge_group' && 'merge-queue' || env.CI_FULL == '1' && 'full' || env.CI_DOCS == '1' && 'docs' || env.CI_BARRETENBERG == '1' && 'barretenberg' || 'fast' }}-${{ env.TREE_HASH }} + - name: CI Cached + if: steps.ci_cache.outputs.cache-hit == 'true' + run: | + echo "Previous run: $(cat ci-success.txt)" + ############# # Run ############# @@ -151,7 +156,7 @@ jobs: - name: Save CI Success if: steps.ci_cache.outputs.cache-hit != 'true' - run: echo "success" > ci-success.txt + run: echo "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" > ci-success.txt # If we have passed CI and labelled with ci-squash-and-merge, squash the PR. # This will rerun CI on the squash commit - but is intended to be a no-op due to caching. diff --git a/barretenberg/cpp/cmake/nlohmann_json.cmake b/barretenberg/cpp/cmake/nlohmann_json.cmake index cbbf8b0af3c8..c078bdca9ab6 100644 --- a/barretenberg/cpp/cmake/nlohmann_json.cmake +++ b/barretenberg/cpp/cmake/nlohmann_json.cmake @@ -1,23 +1,21 @@ -include(FetchContent) +# Download nlohmann_json at configure time +set(NLOHMANN_JSON_SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/nlohmann_json-src") +set(NLOHMANN_JSON_COMMIT_HASH "9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03") -set(NLOHMANN_JSON_INCLUDE "${CMAKE_BINARY_DIR}/_deps/nlohmann_json-src/include") - -FetchContent_Declare( - nlohmann_json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.11.3 - GIT_SHALLOW TRUE - FIND_PACKAGE_ARGS +execute_process( + COMMAND sh -c "mkdir -p ${NLOHMANN_JSON_SOURCE_DIR} && cd ${NLOHMANN_JSON_SOURCE_DIR} && git init . && (git remote add origin https://github.com/nlohmann/json.git || true) && git fetch --depth 1 origin ${NLOHMANN_JSON_COMMIT_HASH} && git checkout FETCH_HEAD" + RESULT_VARIABLE result ) +if(result) + message(FATAL_ERROR "Failed to download nlohmann_json") +endif() + +# Set the same variables as FetchContent would have +set(NLOHMANN_JSON_INCLUDE "${NLOHMANN_JSON_SOURCE_DIR}/include") +# Set options (same as before) set(JSON_BuildTests OFF CACHE INTERNAL "") set(JSON_Install OFF CACHE INTERNAL "") -FetchContent_MakeAvailable(nlohmann_json) - -if(NOT nlohmann_json_FOUND) - # FetchContent_MakeAvailable calls FetchContent_Populate if `find_package` is unsuccessful - # so these variables will be available if we reach this case - set_property(DIRECTORY ${nlohmann_json_SOURCE_DIR} PROPERTY EXCLUDE_FROM_ALL) - set_property(DIRECTORY ${nlohmann_json_BINARY_DIR} PROPERTY EXCLUDE_FROM_ALL) -endif() +# Add nlohmann_json as a subdirectory (same as FetchContent_MakeAvailable would do) +add_subdirectory(${NLOHMANN_JSON_SOURCE_DIR} ${CMAKE_BINARY_DIR}/_deps/nlohmann_json-build EXCLUDE_FROM_ALL) diff --git a/barretenberg/cpp/cmake/tracy.cmake b/barretenberg/cpp/cmake/tracy.cmake index 32f596a7d710..7ababa9d1cbe 100644 --- a/barretenberg/cpp/cmake/tracy.cmake +++ b/barretenberg/cpp/cmake/tracy.cmake @@ -1,16 +1,20 @@ -include(FetchContent) +# Download Tracy at configure time +set(TRACY_SOURCE_DIR "${CMAKE_BINARY_DIR}/_deps/tracy-src") +set(TRACY_COMMIT_HASH "5d542dc09f3d9378d005092a4ad446bd405f819a") -# Find the path where we will download the Tracy github repository -# we need this to find where the Tracy header files are for inclusion. -set(TRACY_INCLUDE "${CMAKE_BINARY_DIR}/_deps/tracy-src/public") +execute_process( + COMMAND sh -c "mkdir -p ${TRACY_SOURCE_DIR} && cd ${TRACY_SOURCE_DIR} && git init . && (git remote add origin https://github.com/wolfpld/tracy.git || true) && git fetch --depth 1 origin ${TRACY_COMMIT_HASH} && git checkout FETCH_HEAD" + RESULT_VARIABLE result +) +if(result) + message(FATAL_ERROR "Failed to download Tracy") +endif() + +# Set the same variables as FetchContent would have +set(TRACY_INCLUDE "${TRACY_SOURCE_DIR}/public") # Work around an issue finding threads. set(CMAKE_THREAD_LIBS_INIT "-lpthread") -# Download the Tracy github project and do an add_subdirectory on it. -FetchContent_Declare(tracy - GIT_REPOSITORY https://github.com/wolfpld/tracy - GIT_TAG 5d542dc09f3d9378d005092a4ad446bd405f819a - GIT_SHALLOW TRUE -) -FetchContent_MakeAvailable(tracy) +# Add Tracy as a subdirectory (same as FetchContent_MakeAvailable would do) +add_subdirectory(${TRACY_SOURCE_DIR} ${CMAKE_BINARY_DIR}/_deps/tracy-build EXCLUDE_FROM_ALL) diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_client_ivc.cpp index 988cc1d31fe9..b9e7e704bd0d 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_client_ivc.cpp @@ -271,7 +271,8 @@ void SumcheckClientIVC::complete_kernel_circuit_logic(ClientCircuit& circuit) PairingPoints points_accumulator; std::optional current_stdlib_verifier_accumulator; if (!is_init_kernel) { - current_stdlib_verifier_accumulator = RecursiveVerifierAccumulator(&circuit, recursive_verifier_native_accum); + current_stdlib_verifier_accumulator = RecursiveVerifierAccumulator::stdlib_from_native( + &circuit, recursive_verifier_native_accum); } while (!stdlib_verification_queue.empty()) { const StdlibVerifierInputs& verifier_input = stdlib_verification_queue.front(); @@ -298,7 +299,7 @@ void SumcheckClientIVC::complete_kernel_circuit_logic(ClientCircuit& circuit) } else { BB_ASSERT_NEQ(current_stdlib_verifier_accumulator.has_value(), false); // Extract native verifier accumulator from the stdlib accum for use on the next round - recursive_verifier_native_accum = current_stdlib_verifier_accumulator->get_value(); + recursive_verifier_native_accum = current_stdlib_verifier_accumulator->get_value(); KernelIO kernel_output; kernel_output.pairing_inputs = points_accumulator; diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_client_ivc.test.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_client_ivc.test.cpp index 474a2cb96796..d46d0bedbad4 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_client_ivc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_client_ivc.test.cpp @@ -15,7 +15,7 @@ using namespace bb; -// static constexpr size_t SMALL_LOG_2_NUM_GATES = 5; +static constexpr size_t SMALL_LOG_2_NUM_GATES = 5; // TODO(https://github.com/AztecProtocol/barretenberg/issues/1511): The CIVC class should enforce the minimum number of // circuits in a test flow. @@ -34,28 +34,346 @@ class SumcheckClientIVCTests : public ::testing::Test { using CircuitProducer = PrivateFunctionExecutionMockCircuitProducer; public: + /** + * @brief Tamper with a proof + * @details The first value in the proof after the public inputs is the commitment to the wire w.l (see + * OinkProver). We modify the commitment by adding Commitment::one(). + * + */ + static void tamper_with_proof(HonkProof& proof, size_t public_inputs_offset) + { + // Tamper with the commitment in the proof + Commitment commitment = FrCodec::deserialize_from_fields( + std::span{ proof }.subspan(public_inputs_offset, FrCodec::template calc_num_fields())); + commitment = commitment + Commitment::one(); + auto commitment_frs = FrCodec::serialize_to_fields(commitment); + for (size_t idx = 0; idx < 4; ++idx) { + proof[public_inputs_offset + idx] = commitment_frs[idx]; + } + } + static std::pair accumulate_and_prove_ivc( - size_t num_app_circuits) + size_t num_app_circuits, TestSettings settings = {}, bool check_circuit_sizes = false) { CircuitProducer circuit_producer(num_app_circuits); const size_t num_circuits = circuit_producer.total_num_circuits; SumcheckClientIVC ivc{ num_circuits }; + for (size_t j = 0; j < num_circuits; ++j) { - circuit_producer.construct_and_accumulate_next_circuit(ivc); + circuit_producer.construct_and_accumulate_next_circuit(ivc, settings, check_circuit_sizes); } - return { ivc.prove(), ivc.get_vk() }; }; }; /** - * @brief Using a structured trace allows for the accumulation of circuits of varying size + * @brief Test sizes of the circuits generated by MockCircuitProducer + * + * @details The sizes of the circuits depends on the TestSettings: + * - No settings: first app is 2^19, all other apps are 2^17, all the kernels are 2^18 + * - Settings: apps are 2^(log2_num_gates + 2), all kernels are smaller than 2^19 + */ +TEST_F(SumcheckClientIVCTests, TestCircuitSizes) +{ + const size_t NUM_APP_CIRCUITS = 2; + + // Check circuit sizes when no settings are passed + { + auto [proof, vk] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS, {}, true); + EXPECT_TRUE(SumcheckClientIVC::verify(proof, vk)); + } + + // Check circuit sizes when no settings are passed + { + auto [proof, vk] = + accumulate_and_prove_ivc(NUM_APP_CIRCUITS, { .log2_num_gates = SMALL_LOG_2_NUM_GATES }, true); + EXPECT_TRUE(SumcheckClientIVC::verify(proof, vk)); + } +}; + +/** + * @brief Test basic IVC. + * + * @note The circuits are of varying size: first circuit is 2^19, kernels are 2^18, apps are 2^17. * */ -TEST_F(SumcheckClientIVCTests, BasicStructured) +TEST_F(SumcheckClientIVCTests, Basic) { const size_t NUM_APP_CIRCUITS = 5; - auto [proof, vk] = SumcheckClientIVCTests::accumulate_and_prove_ivc(NUM_APP_CIRCUITS); + auto [proof, vk] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS); EXPECT_TRUE(SumcheckClientIVC::verify(proof, vk)); }; + +/** + * @brief Check that the IVC fails 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. + * + */ +TEST_F(SumcheckClientIVCTests, BadProofFailure) +{ + BB_DISABLE_ASSERTS(); // Disable assert in PG prover + + const size_t NUM_APP_CIRCUITS = 2; + // Confirm that the IVC verifies if nothing is tampered with + { + + CircuitProducer circuit_producer(NUM_APP_CIRCUITS); + const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; + SumcheckClientIVC ivc{ NUM_CIRCUITS }; + TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES }; + + // Construct and accumulate a set of mocked private function execution circuits + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + circuit_producer.construct_and_accumulate_next_circuit(ivc, settings); + } + auto proof = ivc.prove(); + EXPECT_TRUE(SumcheckClientIVC::verify(proof, ivc.get_vk())); + } + + // The IVC throws an exception if the FIRST fold proof is tampered with + { + CircuitProducer circuit_producer(NUM_APP_CIRCUITS); + const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; + SumcheckClientIVC ivc{ NUM_CIRCUITS }; + + size_t num_public_inputs = 0; + + // Construct and accumulate a set of mocked private function execution circuits + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto [circuit, vk] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES }); + ivc.accumulate(circuit, vk); + + if (idx == 1) { + num_public_inputs = circuit.num_public_inputs(); + } + + if (idx == 2) { + EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation + tamper_with_proof(ivc.verification_queue[0].proof, + num_public_inputs); // tamper with first proof + } + } + auto proof = ivc.prove(); + EXPECT_FALSE(SumcheckClientIVC::verify(proof, ivc.get_vk())); + } + + // The IVC fails if the SECOND fold proof is tampered with + { + CircuitProducer circuit_producer(NUM_APP_CIRCUITS); + const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; + SumcheckClientIVC ivc{ NUM_CIRCUITS }; + + // Construct and accumulate a set of mocked private function execution circuits + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto [circuit, vk] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES }); + ivc.accumulate(circuit, vk); + + if (idx == 2) { + EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation + tamper_with_proof(ivc.verification_queue[1].proof, + circuit.num_public_inputs()); // tamper with second proof + } + } + auto proof = ivc.prove(); + EXPECT_FALSE(SumcheckClientIVC::verify(proof, ivc.get_vk())); + } + + EXPECT_TRUE(true); +}; + +/** + * @brief Produce 2 valid CIVC proofs. Ensure that replacing a proof component with a component from a different proof + * leads to a verification failure. + * + */ +TEST_F(SumcheckClientIVCTests, WrongProofComponentFailure) +{ + // Produce two valid proofs + auto [civc_proof_1, civc_vk_1] = accumulate_and_prove_ivc(/*num_app_circuits=*/1); + { + EXPECT_TRUE(SumcheckClientIVC::verify(civc_proof_1, civc_vk_1)); + } + + auto [civc_proof_2, civc_vk_2] = accumulate_and_prove_ivc(/*num_app_circuits=*/1); + { + EXPECT_TRUE(SumcheckClientIVC::verify(civc_proof_2, civc_vk_2)); + } + + { + // Replace Merge proof + SumcheckClientIVC::Proof tampered_proof = civc_proof_1; + + tampered_proof.goblin_proof.merge_proof = civc_proof_2.goblin_proof.merge_proof; + + EXPECT_THROW_OR_ABORT(SumcheckClientIVC::verify(tampered_proof, civc_vk_1), ".*IPA verification fails.*"); + } + + { + // Replace hiding circuit proof + SumcheckClientIVC::Proof tampered_proof = civc_proof_1; + + tampered_proof.mega_proof = civc_proof_2.mega_proof; + + EXPECT_THROW_OR_ABORT(SumcheckClientIVC::verify(tampered_proof, civc_vk_1), ".*IPA verification fails.*"); + } + + { + // Replace ECCVM proof + SumcheckClientIVC::Proof tampered_proof = civc_proof_1; + + tampered_proof.goblin_proof.eccvm_proof = civc_proof_2.goblin_proof.eccvm_proof; + + EXPECT_THROW_OR_ABORT(SumcheckClientIVC::verify(tampered_proof, civc_vk_1), ".*IPA verification fails.*"); + } + + { + // Replace Translator proof + SumcheckClientIVC::Proof tampered_proof = civc_proof_1; + + tampered_proof.goblin_proof.translator_proof = civc_proof_2.goblin_proof.translator_proof; + + EXPECT_FALSE(SumcheckClientIVC::verify(tampered_proof, civc_vk_1)); + } +}; + +/** + * @brief Ensure that the CIVC VK is independent of the number of circuits accumulated + * + */ +TEST_F(SumcheckClientIVCTests, VKIndependenceFromNumberOfCircuits) +{ + const TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES }; + + auto [unused_1, civc_vk_1] = accumulate_and_prove_ivc(/*num_app_circuits=*/1, settings); + auto [unused_2, civc_vk_2] = accumulate_and_prove_ivc(/*num_app_circuits=*/3, settings); + + // Check the equality of the Mega components of the SumcheckClientIVC VKeys. + EXPECT_EQ(*civc_vk_1.mega.get(), *civc_vk_2.mega.get()); + + // Check the equality of the ECCVM components of the SumcheckClientIVC VKeys. + EXPECT_EQ(*civc_vk_1.eccvm.get(), *civc_vk_2.eccvm.get()); + + // Check the equality of the Translator components of the SumcheckClientIVC VKeys. + EXPECT_EQ(*civc_vk_1.translator.get(), *civc_vk_2.translator.get()); +}; + +/** + * @brief Ensure that the CIVC VK is independent of the sizes of the circuits being accumulated + * + */ +TEST_F(SumcheckClientIVCTests, VKIndependenceFromCircuitSize) +{ + // Run IVC for two sets of circuits + const size_t NUM_APP_CIRCUITS = 1; + const size_t log2_num_gates_small = 5; + const size_t log2_num_gates_big = 18; + + const TestSettings settings_1{ .log2_num_gates = log2_num_gates_small }; + const TestSettings settings_2{ .log2_num_gates = log2_num_gates_big }; + + auto [unused_1, civc_vk_1] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS, settings_1); + auto [unused_2, civc_vk_2] = accumulate_and_prove_ivc(NUM_APP_CIRCUITS, settings_2); + + // Check the equality of the Mega components of the SumcheckClientIVC VKeys. + EXPECT_EQ(*civc_vk_1.mega.get(), *civc_vk_2.mega.get()); + + // Check the equality of the ECCVM components of the SumcheckClientIVC VKeys. + EXPECT_EQ(*civc_vk_1.eccvm.get(), *civc_vk_2.eccvm.get()); + + // Check the equality of the Translator components of the SumcheckClientIVC VKeys. + EXPECT_EQ(*civc_vk_1.translator.get(), *civc_vk_2.translator.get()); +}; + +/** + * @brief Test to establish the "max" number of apps that can be accumulated due to limitations on the ECCVM size + * + */ +HEAVY_TEST(SumcheckClientIVCKernelCapacity, MaxCapacityPassing) +{ + bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); + + const size_t NUM_APP_CIRCUITS = 24; + auto [proof, vk] = SumcheckClientIVCTests::accumulate_and_prove_ivc(NUM_APP_CIRCUITS); + + bool verified = SumcheckClientIVC::verify(proof, vk); + EXPECT_TRUE(verified); +}; + +/** + * @brief Test methods for serializing and deserializing a proof to/from a file/buffer in msgpack format + * + */ +TEST_F(SumcheckClientIVCTests, MsgpackProofFromFileOrBuffer) +{ + // Generate an arbitrary valid CICV proof + TestSettings settings{ .log2_num_gates = SMALL_LOG_2_NUM_GATES }; + auto [proof, vk] = accumulate_and_prove_ivc(/*num_app_circuits=*/1, settings); + + { // Serialize/deserialize the proof to/from a file, check that it verifies + const std::string filename = "proof.msgpack"; + proof.to_file_msgpack(filename); + auto proof_deserialized = SumcheckClientIVC::Proof::from_file_msgpack(filename); + + EXPECT_TRUE(SumcheckClientIVC::verify(proof_deserialized, vk)); + } + + { // Serialize/deserialize proof to/from a heap buffer, check that it verifies + uint8_t* buffer = proof.to_msgpack_heap_buffer(); + auto uint8_buffer = from_buffer>(buffer); + uint8_t const* uint8_ptr = uint8_buffer.data(); + auto proof_deserialized = SumcheckClientIVC::Proof::from_msgpack_buffer(uint8_ptr); + + EXPECT_TRUE(SumcheckClientIVC::verify(proof_deserialized, vk)); + } + + { // Check that attempting to deserialize a proof from a buffer with random bytes fails gracefully + msgpack::sbuffer buffer = proof.to_msgpack_buffer(); + auto proof_deserialized = SumcheckClientIVC::Proof::from_msgpack_buffer(buffer); + EXPECT_TRUE(SumcheckClientIVC::verify(proof_deserialized, vk)); + + std::vector random_bytes(buffer.size()); + std::generate(random_bytes.begin(), random_bytes.end(), []() { return static_cast(rand() % 256); }); + std::copy(random_bytes.begin(), random_bytes.end(), buffer.data()); + + // Expect deserialization to fail with error msgpack::v1::type_error with description "std::bad_cast" + EXPECT_THROW(SumcheckClientIVC::Proof::from_msgpack_buffer(buffer), msgpack::v1::type_error); + } +}; + +/** + * @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(SumcheckClientIVCTests, DatabusFailure) +{ + BB_DISABLE_ASSERTS(); // Disable assert in PG prover + + PrivateFunctionExecutionMockCircuitProducer circuit_producer{ /*num_app_circuits=*/1 }; + const size_t NUM_CIRCUITS = circuit_producer.total_num_circuits; + SumcheckClientIVC ivc{ NUM_CIRCUITS }; + + // Construct and accumulate a series of mocked private function execution circuits + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); + + // Tamper with the return data of the app circuit before it is processed as input to the next kernel + if (idx == 0) { + circuit_producer.tamper_with_databus(); + } + + ivc.accumulate(circuit, vk); + } + + auto proof = ivc.prove(); + EXPECT_FALSE(SumcheckClientIVC::verify(proof, ivc.get_vk())); +}; diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_mock_circuit_producer.hpp b/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_mock_circuit_producer.hpp index 814451212cab..ed2ef145c87f 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_mock_circuit_producer.hpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/sumcheck_mock_circuit_producer.hpp @@ -161,11 +161,13 @@ class PrivateFunctionExecutionMockCircuitProducer { * large. * */ - ClientCircuit create_next_circuit(SumcheckClientIVC& ivc, size_t log2_num_gates = 0, size_t num_public_inputs = 0) + ClientCircuit create_next_circuit(SumcheckClientIVC& ivc, + size_t log2_num_gates = 0, + size_t num_public_inputs = 0, + bool check_circuit_sizes = false) { - const bool is_kernel = is_kernel_flags[circuit_counter]; - - circuit_counter++; + const bool is_kernel = is_kernel_flags[circuit_counter++]; + const bool use_large_circuit = large_first_app && (circuit_counter == 1); // first circuit is size 2^19 ClientCircuit circuit{ ivc.goblin.op_queue }; // if the number of gates is specified we just add a number of arithmetic gates @@ -181,7 +183,6 @@ class PrivateFunctionExecutionMockCircuitProducer { GoblinMockCircuits::construct_mock_folding_kernel(circuit); // construct mock base logic mock_databus.populate_kernel_databus(circuit); // populate databus inputs/outputs } else { - bool use_large_circuit = large_first_app && (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 } @@ -192,26 +193,61 @@ class PrivateFunctionExecutionMockCircuitProducer { } else { stdlib::recursion::PairingPoints::add_default_to_public_inputs(circuit); } + + if (check_circuit_sizes) { + auto prover_instance = std::make_shared(circuit); + size_t log2_dyadic_size = numeric::get_msb(prover_instance->get_metadata().dyadic_size); + if (log2_num_gates != 0) { + if (is_kernel) { + // There are various possibilities here, so we provide a bound + BB_ASSERT_LTE( + log2_dyadic_size, + 19UL, + "Log number of gates in a kernel with fixed number of arithmetic gates has exceeded bound."); + vinfo("Log number of gates in a kernel with fixed number of arithmetic gates is: ", + log2_dyadic_size); + } else { + // The offset is due to the fact that finalization adds a certain number of gates + size_t LOG2_OFFSET = 2; + BB_ASSERT_LTE(log2_dyadic_size, + log2_num_gates + LOG2_OFFSET, + "Log number of arithemtic gates produced is different from the one requested."); + } + } else { + if (is_kernel) { + BB_ASSERT_EQ(log2_dyadic_size, + 18UL, + "There has been a change in the number of gates of a mock kernel circuit."); + } else { + BB_ASSERT_EQ(log2_dyadic_size, + use_large_circuit ? 19UL : 17UL, + "There has been a change in the of gates generated for a mock app circuit."); + } + } + } return circuit; } /** * @brief Create the next circuit (app/kernel) in a mocked private function execution stack */ - std::pair> create_next_circuit_and_vk(SumcheckClientIVC& ivc, - TestSettings settings = {}) + std::pair> create_next_circuit_and_vk( + SumcheckClientIVC& ivc, TestSettings settings = {}, bool check_circuit_size = false) { // If this is a mock hiding kernel, remove the settings and use a default (non-structured) trace if (ivc.num_circuits_accumulated == ivc.get_num_circuits() - 1) { settings = TestSettings{}; } - auto circuit = create_next_circuit(ivc, settings.log2_num_gates, settings.num_public_inputs); + auto circuit = + create_next_circuit(ivc, settings.log2_num_gates, settings.num_public_inputs, check_circuit_size); return { circuit, get_verification_key(circuit) }; } - void construct_and_accumulate_next_circuit(SumcheckClientIVC& ivc, TestSettings settings = {}) + void construct_and_accumulate_next_circuit(SumcheckClientIVC& ivc, + TestSettings settings = {}, + bool check_circuit_sizes = false) { - auto [circuit, vk] = create_next_circuit_and_vk(ivc, settings); + auto [circuit, vk] = create_next_circuit_and_vk(ivc, settings, check_circuit_sizes); ivc.accumulate(circuit, vk); } diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp index b23ad4abac82..80e24236d7a6 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp @@ -259,37 +259,6 @@ template class GeminiProver_ { return full_batched; } - /** - * @brief Compute batched polynomial - */ - template - static Polynomial compute_batched(RefArray& polynomials_to_batch, - const size_t full_batched_size, - const std::array& challenges, - const bool shift = false) - { - BB_BENCH_NAME("compute_batched"); - - size_t challenge_idx = 0; - - if (shift) { - auto full_batched = Polynomial::shiftable(full_batched_size); - for (auto& poly : polynomials_to_batch) { - full_batched.add_scaled(poly, challenges[challenge_idx]); - challenge_idx += 1; - } - return full_batched; - } - - Polynomial full_batched(full_batched_size); - for (auto& poly : polynomials_to_batch) { - full_batched.add_scaled(poly, challenges[challenge_idx]); - challenge_idx += 1; - } - - return full_batched; - } - /** * @brief Compute partially evaluated batched polynomials A₀(X, r) = A₀₊ = F + G/r, A₀(X, -r) = A₀₋ = F - * G/r diff --git a/barretenberg/cpp/src/barretenberg/common/tracy_mem/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/common/tracy_mem/CMakeLists.txt index 5ee67454aca2..a07c62109b8b 100644 --- a/barretenberg/cpp/src/barretenberg/common/tracy_mem/CMakeLists.txt +++ b/barretenberg/cpp/src/barretenberg/common/tracy_mem/CMakeLists.txt @@ -1,2 +1 @@ add_library(tracy_mem OBJECT overload_operator_new.cpp) -target_compile_definitions(tracy_mem PRIVATE TRACY_MEMORY) diff --git a/barretenberg/cpp/src/barretenberg/common/tracy_mem/overload_operator_new.cpp b/barretenberg/cpp/src/barretenberg/common/tracy_mem/overload_operator_new.cpp index f6f8114a7577..254cbcc95deb 100644 --- a/barretenberg/cpp/src/barretenberg/common/tracy_mem/overload_operator_new.cpp +++ b/barretenberg/cpp/src/barretenberg/common/tracy_mem/overload_operator_new.cpp @@ -1,6 +1,7 @@ #include "../mem.hpp" #ifdef TRACY_MEMORY + void* operator new(std::size_t count) { // NOLINTBEGIN(cppcoreguidelines-no-malloc) @@ -54,11 +55,39 @@ void operator delete[](void* ptr, std::size_t) noexcept // C++17 aligned new void* operator new(std::size_t size, std::align_val_t alignment) { - return aligned_alloc(static_cast(alignment), size); + void* ptr = aligned_alloc(static_cast(alignment), size); + TRACY_ALLOC(ptr, size); + return ptr; +} + +void* operator new[](std::size_t size, std::align_val_t alignment) +{ + void* ptr = aligned_alloc(static_cast(alignment), size); + TRACY_ALLOC(ptr, size); + return ptr; } void operator delete(void* ptr, std::align_val_t) noexcept { + TRACY_FREE(ptr); + aligned_free(ptr); +} + +void operator delete(void* ptr, std::size_t, std::align_val_t) noexcept +{ + TRACY_FREE(ptr); + aligned_free(ptr); +} + +void operator delete[](void* ptr, std::align_val_t) noexcept +{ + TRACY_FREE(ptr); + aligned_free(ptr); +} + +void operator delete[](void* ptr, std::size_t, std::align_val_t) noexcept +{ + TRACY_FREE(ptr); aligned_free(ptr); } diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp index b7b658f5aa1e..dbfc09028edd 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp @@ -580,12 +580,13 @@ class ECCVMFlavor { const size_t num_rows = std::max({ point_table_rows.size(), msm_rows.size(), transcript_rows.size() }) + NUM_DISABLED_ROWS_IN_SUMCHECK; + vinfo("Num rows in the ECCVM: ", num_rows); const auto log_num_rows = static_cast(numeric::get_msb64(num_rows)); size_t dyadic_num_rows = 1UL << (log_num_rows + (1UL << log_num_rows == num_rows ? 0 : 1)); - if (ECCVM_FIXED_SIZE < dyadic_num_rows) { - throw_or_abort("The ECCVM circuit size has exceeded the fixed upper bound! Fixed size: " + - std::to_string(ECCVM_FIXED_SIZE) + " actual size: " + std::to_string(dyadic_num_rows)); - } + BB_ASSERT_LTE(dyadic_num_rows, + ECCVM_FIXED_SIZE, + "The ECCVM circuit size has exceeded the fixed upper bound! Fixed size: " + + std::to_string(ECCVM_FIXED_SIZE) + " actual size: " + std::to_string(dyadic_num_rows)); #ifdef FUZZING // We don't want to spend all the time generating the full trace if we are just fuzzing eccvm. diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.cpp index 3df2d33a59a5..ac78e3961444 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.cpp @@ -25,13 +25,15 @@ HypernovaFoldingProver::Accumulator HypernovaFoldingProver::sumcheck_output_to_a for (size_t idx = 0; idx < Flavor::NUM_SHIFTED_ENTITIES; idx++) { labels_shifted_witnesses[idx] = "shifted_challenge_" + std::to_string(idx); } - auto unshifted_challenges = transcript->template get_challenges(labels_unshifted_entities); - auto shifted_challenges = transcript->template get_challenges(labels_shifted_witnesses); + std::array unshifted_challenges = + transcript->template get_challenges(labels_unshifted_entities); + std::array shifted_challenges = + transcript->template get_challenges(labels_shifted_witnesses); // Batch polynomials - auto batched_unshifted_polynomial = batch_polynomials( + Polynomial batched_unshifted_polynomial = batch_polynomials( instance->polynomials.get_unshifted(), instance->dyadic_size(), unshifted_challenges); - auto batched_shifted_polynomial = batch_polynomials( + Polynomial batched_shifted_polynomial = batch_polynomials( instance->polynomials.get_to_be_shifted(), instance->dyadic_size(), shifted_challenges); // Batch evaluations @@ -70,11 +72,11 @@ HypernovaFoldingProver::Accumulator HypernovaFoldingProver::sumcheck_output_to_a batched_shifted_commitment = batch_mul_native(points, scalars); return Accumulator{ - .challenge = sumcheck_output.challenge, + .challenge = std::move(sumcheck_output.challenge), .shifted_evaluation = batched_shifted_evaluation, .non_shifted_evaluation = batched_unshifted_evaluation, - .non_shifted_polynomial = batched_unshifted_polynomial, - .shifted_polynomial = batched_shifted_polynomial, + .non_shifted_polynomial = std::move(batched_unshifted_polynomial), + .shifted_polynomial = std::move(batched_shifted_polynomial), .non_shifted_commitment = batched_unshifted_commitment, .shifted_commitment = batched_shifted_commitment, .dyadic_size = instance->dyadic_size(), @@ -82,8 +84,10 @@ HypernovaFoldingProver::Accumulator HypernovaFoldingProver::sumcheck_output_to_a }; template -HypernovaFoldingProver::Polynomial HypernovaFoldingProver::batch_polynomials( - RefArray polynomials_to_batch, const size_t& full_batched_size, const std::array& challenges) +Polynomial HypernovaFoldingProver::batch_polynomials( + RefArray, N> polynomials_to_batch, + const size_t& full_batched_size, + const std::array& challenges) { BB_BENCH(); BB_ASSERT_EQ(full_batched_size, diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.hpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.hpp index f8a05fb2f9fa..2762ae43d5ba 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.hpp @@ -16,7 +16,6 @@ class HypernovaFoldingProver { public: using Flavor = MegaFlavor; using FF = Flavor::FF; - using Polynomial = bb::Polynomial; using Commitment = Flavor::Commitment; using ProverInstance = ProverInstance_; using Accumulator = MultilinearBatchingProverClaim; @@ -71,9 +70,9 @@ class HypernovaFoldingProver { * @param shiftable If it is set to true, then the polynomials are aggregated as shiftable polynomials */ template - static Polynomial batch_polynomials(RefArray polynomials_to_batch, - const size_t& full_batched_size, - const std::array& challenges); + static Polynomial batch_polynomials(RefArray, N> polynomials_to_batch, + const size_t& full_batched_size, + const std::array& challenges); }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp index 5446295c4f9f..d85b922ad1a9 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp @@ -136,7 +136,8 @@ class HypernovaFoldingVerifierTests : public ::testing::Test { EXPECT_EQ(first_sumcheck_recursive, first_sumcheck_native); EXPECT_TRUE(second_sumcheck_recursive); EXPECT_EQ(second_sumcheck_recursive, second_sumcheck_native); - EXPECT_TRUE(compare_prover_verifier_accumulators(folded_accumulator, folded_verifier_accumulator.get_value())); + EXPECT_TRUE(compare_prover_verifier_accumulators( + folded_accumulator, folded_verifier_accumulator.get_value())); } }; diff --git a/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_claims.hpp b/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_claims.hpp index f10533e38b21..ea2995dc065d 100644 --- a/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_claims.hpp +++ b/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_claims.hpp @@ -41,22 +41,42 @@ template struct MultilinearBatchingVerifierClaim { , shifted_commitment(shifted_commitment) {} - MultilinearBatchingVerifierClaim(auto* builder, const auto& native_claim) + /** + * @brief Constructor for instantiating a recursive claim from a native one + * + * @tparam RecursiveCurve + * @param builder + * @param native_claim + * @return MultilinearBatchingVerifierClaim + */ + template + static MultilinearBatchingVerifierClaim stdlib_from_native( + typename RecursiveCurve::Builder* builder, + const MultilinearBatchingVerifierClaim& native_claim) requires Curve::is_stdlib_type - : shifted_evaluation(FF::from_witness(builder, native_claim.shifted_evaluation)) - , non_shifted_evaluation(FF::from_witness(builder, native_claim.non_shifted_evaluation)) - , non_shifted_commitment(Commitment::from_witness(builder, native_claim.non_shifted_commitment)) - , shifted_commitment(Commitment::from_witness(builder, native_claim.shifted_commitment)) { + MultilinearBatchingVerifierClaim result; + for (auto& element : native_claim.challenge) { - challenge.emplace_back(FF::from_witness(builder, element)); + result.challenge.emplace_back(FF::from_witness(builder, element)); } + + result.shifted_evaluation = FF::from_witness(builder, native_claim.shifted_evaluation); + result.non_shifted_evaluation = FF::from_witness(builder, native_claim.non_shifted_evaluation); + result.non_shifted_commitment = Commitment::from_witness(builder, native_claim.non_shifted_commitment); + result.shifted_commitment = Commitment::from_witness(builder, native_claim.shifted_commitment); + + return result; } - auto get_value() + /** + * @brief Return the native claim underlying the recursive one + */ + template + T get_value() requires Curve::is_stdlib_type { - MultilinearBatchingVerifierClaim native_claim; + T native_claim; native_claim.challenge.reserve(challenge.size()); for (auto& recursive_challenge : challenge) { @@ -70,7 +90,10 @@ template struct MultilinearBatchingVerifierClaim { return native_claim; } - FF hash_through_transcript(const std::string& domain_separator, auto& transcript) const + /** + * @brief Hash the claim via the transcript mechanism + */ + template FF hash_through_transcript(const std::string& domain_separator, T& transcript) const { for (size_t idx = 0; auto& element : challenge) { transcript.add_to_independent_hash_buffer(domain_separator + "challenge_" + std::to_string(idx), element);