diff --git a/barretenberg/cpp/src/barretenberg/bb/main.cpp b/barretenberg/cpp/src/barretenberg/bb/main.cpp index 66a4a831a6f..8daccc33257 100644 --- a/barretenberg/cpp/src/barretenberg/bb/main.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/main.cpp @@ -359,7 +359,7 @@ void client_ivc_prove_output_all_msgpack(const std::string& bytecodePath, // TODO(https://github.com/AztecProtocol/barretenberg/issues/1101): remove use of auto_verify_mode ClientIVC ivc; ivc.auto_verify_mode = true; - ivc.trace_structure = TraceStructure::E2E_FULL_TEST; + ivc.trace_settings.structure = TraceStructure::E2E_FULL_TEST; // Accumulate the entire program stack into the IVC // TODO(https://github.com/AztecProtocol/barretenberg/issues/1116): remove manual setting of is_kernel once databus @@ -448,7 +448,7 @@ bool foldAndVerifyProgram(const std::string& bytecodePath, const std::string& wi ClientIVC ivc; ivc.auto_verify_mode = true; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; auto program_stack = acir_format::get_acir_program_stack( bytecodePath, witnessPath, false); // TODO(https://github.com/AztecProtocol/barretenberg/issues/1013): this @@ -501,7 +501,7 @@ void client_ivc_prove_output_all(const std::string& bytecodePath, // TODO(https://github.com/AztecProtocol/barretenberg/issues/1101): remove use of auto_verify_mode ClientIVC ivc; ivc.auto_verify_mode = true; - ivc.trace_structure = TraceStructure::E2E_FULL_TEST; + ivc.trace_settings.structure = TraceStructure::E2E_FULL_TEST; auto program_stack = acir_format::get_acir_program_stack( bytecodePath, witnessPath, false); // TODO(https://github.com/AztecProtocol/barretenberg/issues/1013): this diff --git a/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp index 12cc8718749..f85bc066ce6 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp @@ -34,7 +34,7 @@ class ClientIVCBench : public benchmark::Fixture { BENCHMARK_DEFINE_F(ClientIVCBench, Full)(benchmark::State& state) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::CLIENT_IVC_BENCH; + ivc.trace_settings.structure = TraceStructure::CLIENT_IVC_BENCH; auto total_num_circuits = 2 * static_cast(state.range(0)); // 2x accounts for kernel circuits auto mocked_vkeys = mock_verification_keys(total_num_circuits); diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp index 8888104dca0..2c82c27acc0 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp @@ -173,11 +173,11 @@ void ClientIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr proving_key; if (!initialized) { - proving_key = std::make_shared(circuit, trace_structure); - trace_usage_tracker = ExecutionTraceUsageTracker(trace_structure); + proving_key = std::make_shared(circuit, trace_settings); + trace_usage_tracker = ExecutionTraceUsageTracker(trace_settings); } else { proving_key = std::make_shared( - circuit, trace_structure, fold_output.accumulator->proving_key.commitment_key); + circuit, trace_settings, fold_output.accumulator->proving_key.commitment_key); } // Update the accumulator trace usage based on the present circuit @@ -370,10 +370,10 @@ std::vector> ClientIVC::precompute_f } // Reset the scheme so it can be reused for actual accumulation, maintaining the trace structure setting as is - TraceStructure structure = trace_structure; + TraceSettings settings = trace_settings; bool auto_verify = auto_verify_mode; *this = ClientIVC(); - this->trace_structure = structure; + this->trace_settings = settings; this->auto_verify_mode = auto_verify; return vkeys; diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp index 76ae3840813..2baf842e13c 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp @@ -115,8 +115,8 @@ class ClientIVC { // 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 DeciderProvingKey - TraceStructure trace_structure = TraceStructure::NONE; + // Settings related to the use of fixed block sizes for each gate in the execution trace + TraceSettings trace_settings; // TODO(https://github.com/AztecProtocol/barretenberg/issues/1101): eventually do away with this. // Setting auto_verify_mode = true will cause kernel completion logic to be added to kernels automatically diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.test.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.test.cpp index c7e85ed675f..f61a6d786b3 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.test.cpp @@ -76,11 +76,11 @@ class ClientIVCTests : public ::testing::Test { } auto precompute_verification_keys(const size_t num_circuits, - TraceStructure trace_structure, + TraceSettings trace_settings, size_t log2_num_gates = 16) { ClientIVC ivc; // temporary IVC instance needed to produce the complete kernel circuits - ivc.trace_structure = trace_structure; + ivc.trace_settings = trace_settings; std::vector> vkeys; @@ -163,7 +163,7 @@ TEST_F(ClientIVCTests, BadProofFailure) // Confirm that the IVC verifies if nothing is tampered with { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; MockCircuitProducer circuit_producer; @@ -179,7 +179,7 @@ TEST_F(ClientIVCTests, BadProofFailure) // The IVC throws an exception if the FIRST fold proof is tampered with { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; MockCircuitProducer circuit_producer; @@ -204,7 +204,7 @@ TEST_F(ClientIVCTests, BadProofFailure) // The IVC fails if the SECOND fold proof is tampered with { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; MockCircuitProducer circuit_producer; @@ -229,7 +229,7 @@ TEST_F(ClientIVCTests, BadProofFailure) // The IVC fails if the 3rd/FINAL fold proof is tampered with { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; MockCircuitProducer circuit_producer; @@ -278,7 +278,7 @@ TEST_F(ClientIVCTests, BasicLarge) TEST_F(ClientIVCTests, BasicStructured) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; MockCircuitProducer circuit_producer; @@ -307,7 +307,7 @@ TEST_F(ClientIVCTests, PrecomputedVerificationKeys) MockCircuitProducer circuit_producer; - auto precomputed_vks = circuit_producer.precompute_verification_keys(NUM_CIRCUITS, TraceStructure::NONE); + auto precomputed_vks = circuit_producer.precompute_verification_keys(NUM_CIRCUITS, TraceSettings{}); // Construct and accumulate set of circuits using the precomputed vkeys for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { @@ -325,7 +325,7 @@ TEST_F(ClientIVCTests, PrecomputedVerificationKeys) TEST_F(ClientIVCTests, StructuredPrecomputedVKs) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; size_t NUM_CIRCUITS = 4; size_t log2_num_gates = 5; // number of gates in baseline mocked circuit @@ -333,7 +333,7 @@ TEST_F(ClientIVCTests, StructuredPrecomputedVKs) MockCircuitProducer circuit_producer; auto precomputed_vks = - circuit_producer.precompute_verification_keys(NUM_CIRCUITS, ivc.trace_structure, log2_num_gates); + circuit_producer.precompute_verification_keys(NUM_CIRCUITS, ivc.trace_settings, log2_num_gates); // Construct and accumulate set of circuits using the precomputed vkeys for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { @@ -357,10 +357,10 @@ TEST(ClientIVCBenchValidation, Full6) bb::srs::init_grumpkin_crs_factory("../srs_db/grumpkin"); ClientIVC ivc; - ivc.trace_structure = TraceStructure::CLIENT_IVC_BENCH; + ivc.trace_settings.structure = TraceStructure::CLIENT_IVC_BENCH; size_t total_num_circuits{ 12 }; PrivateFunctionExecutionMockCircuitProducer circuit_producer; - auto precomputed_vkeys = circuit_producer.precompute_verification_keys(total_num_circuits, ivc.trace_structure); + auto precomputed_vkeys = circuit_producer.precompute_verification_keys(total_num_circuits, ivc.trace_settings); perform_ivc_accumulation_rounds(total_num_circuits, ivc, precomputed_vkeys); auto proof = ivc.prove(); bool verified = verify_ivc(proof, ivc); @@ -377,7 +377,7 @@ TEST(ClientIVCBenchValidation, Full6MockedVKs) bb::srs::init_grumpkin_crs_factory("../srs_db/grumpkin"); ClientIVC ivc; - ivc.trace_structure = TraceStructure::CLIENT_IVC_BENCH; + ivc.trace_settings.structure = TraceStructure::CLIENT_IVC_BENCH; size_t total_num_circuits{ 12 }; PrivateFunctionExecutionMockCircuitProducer circuit_producer; auto mocked_vkeys = mock_verification_keys(total_num_circuits); @@ -386,4 +386,33 @@ TEST(ClientIVCBenchValidation, Full6MockedVKs) verify_ivc(proof, ivc); }; ASSERT_NO_FATAL_FAILURE(run_test()); -} \ No newline at end of file +} + +/** + * @brief Test use of structured trace overflow block mechanism + * @details Accumulate 4 circuits which have progressively more arithmetic gates. The final two overflow the prescribed + * arithmetic block size and make use of the overflow block which has sufficient capacity. + * + */ +TEST_F(ClientIVCTests, StructuredTraceOverflow) +{ + ClientIVC ivc; + + // Define trace settings with sufficient overflow capacity to accommodate each of the circuits to be accumulated + uint32_t overflow_capacity = 1 << 17; + ivc.trace_settings = { TraceStructure::SMALL_TEST, overflow_capacity }; + + MockCircuitProducer circuit_producer; + + size_t NUM_CIRCUITS = 4; + + // Construct and accumulate some circuits of varying size + size_t log2_num_gates = 14; + 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 += 1; + } + + EXPECT_TRUE(ivc.prove_and_verify()); +}; \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc_auto_verify.test.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc_auto_verify.test.cpp index dc76f673d21..5064d316794 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc_auto_verify.test.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc_auto_verify.test.cpp @@ -132,7 +132,7 @@ TEST_F(ClientIVCAutoVerifyTests, BasicStructured) { ClientIVC ivc; ivc.auto_verify_mode = true; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; // Construct some circuits of varying size Builder circuit_0 = create_mock_circuit(ivc, /*is_kernel=*/false, /*log2_num_gates=*/5); @@ -185,7 +185,7 @@ TEST_F(ClientIVCAutoVerifyTests, StructuredPrecomputedVKs) { ClientIVC ivc; ivc.auto_verify_mode = true; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; // Construct a set of arbitrary circuits size_t NUM_CIRCUITS = 4; diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc_integration.test.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc_integration.test.cpp index 5b08a08c810..9fed2ce5ca5 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc_integration.test.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc_integration.test.cpp @@ -37,7 +37,7 @@ class ClientIVCIntegrationTests : public ::testing::Test { TEST_F(ClientIVCIntegrationTests, BenchmarkCaseSimple) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::CLIENT_IVC_BENCH; + ivc.trace_settings.structure = TraceStructure::CLIENT_IVC_BENCH; MockCircuitProducer circuit_producer; @@ -61,7 +61,7 @@ TEST_F(ClientIVCIntegrationTests, BenchmarkCaseSimple) TEST_F(ClientIVCIntegrationTests, ConsecutiveKernels) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::CLIENT_IVC_BENCH; + ivc.trace_settings.structure = TraceStructure::CLIENT_IVC_BENCH; MockCircuitProducer circuit_producer; @@ -89,7 +89,7 @@ TEST_F(ClientIVCIntegrationTests, ConsecutiveKernels) TEST_F(ClientIVCIntegrationTests, BenchmarkCasePrecomputedVKs) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::CLIENT_IVC_BENCH; + ivc.trace_settings.structure = TraceStructure::CLIENT_IVC_BENCH; size_t NUM_CIRCUITS = 6; @@ -97,7 +97,7 @@ TEST_F(ClientIVCIntegrationTests, BenchmarkCasePrecomputedVKs) std::vector> precomputed_vks; { MockCircuitProducer circuit_producer; - precomputed_vks = circuit_producer.precompute_verification_keys(NUM_CIRCUITS, ivc.trace_structure); + precomputed_vks = circuit_producer.precompute_verification_keys(NUM_CIRCUITS, ivc.trace_settings); } MockCircuitProducer circuit_producer; @@ -123,7 +123,7 @@ TEST_F(ClientIVCIntegrationTests, BenchmarkCasePrecomputedVKs) TEST_F(ClientIVCIntegrationTests, DatabusFailure) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::CLIENT_IVC_BENCH; + ivc.trace_settings.structure = TraceStructure::CLIENT_IVC_BENCH; MockCircuitProducer circuit_producer; diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/mock_circuit_producer.hpp b/barretenberg/cpp/src/barretenberg/client_ivc/mock_circuit_producer.hpp index 7e2332f5309..eae4966e32a 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/mock_circuit_producer.hpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/mock_circuit_producer.hpp @@ -135,10 +135,10 @@ class PrivateFunctionExecutionMockCircuitProducer { * @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) + auto precompute_verification_keys(const size_t num_circuits, TraceSettings trace_settings) { ClientIVC ivc; // temporary IVC instance needed to produce the complete kernel circuits - ivc.trace_structure = trace_structure; + ivc.trace_settings = trace_settings; std::vector> vkeys; diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/mock_kernel_pinning.test.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/mock_kernel_pinning.test.cpp index 2dc33189cbc..c89c773e814 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/mock_kernel_pinning.test.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/mock_kernel_pinning.test.cpp @@ -24,7 +24,7 @@ class MockKernelTest : public ::testing::Test { TEST_F(MockKernelTest, PinFoldingKernelSizes) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::CLIENT_IVC_BENCH; + ivc.trace_settings.structure = TraceStructure::CLIENT_IVC_BENCH; MockCircuitProducer circuit_producer; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp index 3719a9ec15c..14ab23240c8 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp @@ -402,7 +402,7 @@ TEST_P(AcirIntegrationFoldingTest, DISABLED_FoldAndVerifyProgramStack) ClientIVC ivc; ivc.auto_verify_mode = true; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; while (!program_stack.empty()) { auto program = program_stack.back(); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.test.cpp index 11339b3de7b..285106061be 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/ivc_recursion_constraint.test.cpp @@ -160,7 +160,7 @@ class IvcRecursionConstraintTest : public ::testing::Test { TEST_F(IvcRecursionConstraintTest, AccumulateTwo) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; // construct a mock app_circuit Builder app_circuit = construct_mock_app_circuit(ivc); @@ -185,7 +185,7 @@ TEST_F(IvcRecursionConstraintTest, AccumulateTwo) TEST_F(IvcRecursionConstraintTest, AccumulateFour) { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; // construct a mock app_circuit Builder app_circuit_0 = construct_mock_app_circuit(ivc); @@ -232,7 +232,7 @@ TEST_F(IvcRecursionConstraintTest, AccumulateTwoFailure) VerifierInputs alternative_verification_queue_entry; { ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; // construct and accumulate a mock app circuit with a single unique public input Builder app_circuit = construct_mock_app_circuit(ivc); @@ -254,7 +254,7 @@ TEST_F(IvcRecursionConstraintTest, AccumulateTwoFailure) // valid) witnesses during constraint system construction VS recursive verifier construction. ClientIVC ivc; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; // construct and accumulate a mock app circuit with a single unique public input Builder app_circuit = construct_mock_app_circuit(ivc); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index b9c1253eaf5..38ed91e23d6 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -97,7 +97,7 @@ WASM_EXPORT void acir_fold_and_verify_program_stack(uint8_t const* acir_vec, ClientIVC ivc; ivc.auto_verify_mode = true; - ivc.trace_structure = TraceStructure::SMALL_TEST; + ivc.trace_settings.structure = TraceStructure::SMALL_TEST; bool is_kernel = false; while (!program_stack.empty()) { diff --git a/barretenberg/cpp/src/barretenberg/examples/join_split/join_split.test.cpp b/barretenberg/cpp/src/barretenberg/examples/join_split/join_split.test.cpp index 1ef0dfe3305..f56dbf882e7 100644 --- a/barretenberg/cpp/src/barretenberg/examples/join_split/join_split.test.cpp +++ b/barretenberg/cpp/src/barretenberg/examples/join_split/join_split.test.cpp @@ -703,7 +703,7 @@ TEST_F(join_split_tests, test_0_input_notes_and_detect_circuit_change) // The below part detects any changes in the join-split circuit constexpr size_t DYADIC_CIRCUIT_SIZE = 1 << 16; - constexpr uint256_t CIRCUIT_HASH("0x2b30566e4d921ea9b0c76802d86ea5b8381ffa78ef143af1b0d0e3045862cb6b"); + constexpr uint256_t CIRCUIT_HASH("0xe833d0ffaa99f39ca31ef79ded0089b0ec614eb4a528c4e1c4af6221a003fa6e"); const uint256_t circuit_hash = circuit.hash_circuit(); // circuit is finalized now diff --git a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp index 3b7fa66b826..b59338873cb 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp @@ -360,7 +360,7 @@ template concept IsUltraPlonkFlavor = IsAnyOf; template -concept IsUltraPlonkOrHonk = IsAnyOf; +concept IsUltraPlonkOrHonk = IsAnyOf; template concept IsHonkFlavor = IsAnyOf; diff --git a/barretenberg/cpp/src/barretenberg/goblin/mock_circuits_pinning.test.cpp b/barretenberg/cpp/src/barretenberg/goblin/mock_circuits_pinning.test.cpp index 53e7c5b2644..afba3ac5402 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/mock_circuits_pinning.test.cpp +++ b/barretenberg/cpp/src/barretenberg/goblin/mock_circuits_pinning.test.cpp @@ -58,7 +58,8 @@ TEST_F(MegaMockCircuitsPinning, SmallTestStructuredCircuitSize) { GoblinProver goblin; MegaCircuitBuilder app_circuit{ goblin.op_queue }; - auto proving_key = std::make_shared(app_circuit, TraceStructure::SMALL_TEST); + TraceSettings trace_settings{ TraceStructure::SMALL_TEST }; + auto proving_key = std::make_shared(app_circuit, trace_settings); EXPECT_EQ(proving_key->proving_key.log_circuit_size, 18); } @@ -66,7 +67,8 @@ TEST_F(MegaMockCircuitsPinning, ClientIVCBenchStructuredCircuitSize) { GoblinProver goblin; MegaCircuitBuilder app_circuit{ goblin.op_queue }; - auto proving_key = std::make_shared(app_circuit, TraceStructure::CLIENT_IVC_BENCH); + TraceSettings trace_settings{ TraceStructure::CLIENT_IVC_BENCH }; + auto proving_key = std::make_shared(app_circuit, trace_settings); EXPECT_EQ(proving_key->proving_key.log_circuit_size, 19); } @@ -74,6 +76,7 @@ TEST_F(MegaMockCircuitsPinning, E2EStructuredCircuitSize) { GoblinProver goblin; MegaCircuitBuilder app_circuit{ goblin.op_queue }; - auto proving_key = std::make_shared(app_circuit, TraceStructure::E2E_FULL_TEST); + TraceSettings trace_settings{ TraceStructure::E2E_FULL_TEST }; + auto proving_key = std::make_shared(app_circuit, trace_settings); EXPECT_EQ(proving_key->proving_key.log_circuit_size, 20); } \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp index b5408341edb..066504bdb40 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp @@ -30,7 +30,14 @@ struct StackTraces { // A set of fixed block size conigurations to be used with the structured execution trace. The actual block sizes // corresponding to these settings are defined in the corresponding arithmetization classes (Ultra/Mega). For efficiency // it is best to use the smallest possible block sizes to accommodate a given situation. -enum class TraceStructure { NONE, SMALL_TEST, CLIENT_IVC_BENCH, E2E_FULL_TEST }; +enum class TraceStructure { NONE, TINY_TEST, SMALL_TEST, CLIENT_IVC_BENCH, E2E_FULL_TEST }; + +struct TraceSettings { + TraceStructure structure = TraceStructure::NONE; + // The size of the overflow block. Specified separately because it is allowed to be determined at runtime in the + // context of VK computation + uint32_t overflow_capacity = 0; +}; /** * @brief Basic structure for storing gate data in a builder diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/execution_trace_usage_tracker.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/execution_trace_usage_tracker.hpp index b1fcfcb0702..53db481ff59 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/execution_trace_usage_tracker.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/execution_trace_usage_tracker.hpp @@ -29,15 +29,15 @@ struct ExecutionTraceUsageTracker { size_t max_databus_size = 0; size_t max_tables_size = 0; - TraceStructure trace_structure = TraceStructure::NONE; + TraceSettings trace_settings; - ExecutionTraceUsageTracker(const TraceStructure& trace_structure = TraceStructure::NONE) - : trace_structure(trace_structure) + ExecutionTraceUsageTracker(const TraceSettings& trace_settings = TraceSettings{}) + : trace_settings(trace_settings) { for (auto& size : max_sizes.get()) { size = 0; // init max sizes to zero } - fixed_sizes.set_fixed_block_sizes(trace_structure); + fixed_sizes.set_fixed_block_sizes(trace_settings); fixed_sizes.compute_offsets(/*is_structured=*/true); } @@ -78,7 +78,7 @@ struct ExecutionTraceUsageTracker { bool check_is_active(const size_t idx) { // If structured trace is not in use, assume the whole trace is active - if (trace_structure == TraceStructure::NONE) { + if (trace_settings.structure == TraceStructure::NONE) { return true; } for (auto& range : active_ranges.get()) { @@ -90,10 +90,17 @@ struct ExecutionTraceUsageTracker { } // For printing only. Must match the order of the members in the arithmetization - std::vector block_labels{ "ecc_op", "pub_inputs", "busread", - "arithmetic", "delta_range", "elliptic", - "aux", "poseidon2_external", "poseidon2_internal", - "lookup" }; + std::vector block_labels{ "ecc_op", + "pub_inputs", + "busread", + "arithmetic", + "delta_range", + "elliptic", + "aux", + "poseidon2_external", + "poseidon2_internal", + "lookup", + "overflow" }; void print() { @@ -140,7 +147,7 @@ struct ExecutionTraceUsageTracker { // Convert the active ranges for each gate type into a set of sorted non-overlapping ranges (union of the input) std::vector simplified_active_ranges; - if (trace_structure == TraceStructure::NONE) { + if (trace_settings.structure == TraceStructure::NONE) { // If not using a structured trace, set the active range to the whole domain simplified_active_ranges.push_back(Range{ 0, full_domain_size }); } else { 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 6e8ed04a669..7cb03ae310a 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 @@ -1,5 +1,6 @@ #pragma once +#include "barretenberg/common/ref_vector.hpp" #include "barretenberg/common/zip_view.hpp" #include "barretenberg/ecc/curves/bn254/fr.hpp" #include "barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp" @@ -34,20 +35,35 @@ template class MegaArith { T poseidon2_external; T poseidon2_internal; T lookup; + T overflow; // block gates of arbitrary type that overflow their designated block auto get() { - return RefArray{ ecc_op, pub_inputs, busread, - arithmetic, delta_range, elliptic, - aux, poseidon2_external, poseidon2_internal, - lookup }; + return RefArray{ ecc_op, + pub_inputs, + busread, + arithmetic, + delta_range, + elliptic, + aux, + poseidon2_external, + poseidon2_internal, + lookup, + overflow }; } auto get() const { - return RefArray{ ecc_op, pub_inputs, busread, - arithmetic, delta_range, elliptic, - aux, poseidon2_external, poseidon2_internal, - lookup }; + return RefArray{ ecc_op, + pub_inputs, + busread, + arithmetic, + delta_range, + elliptic, + aux, + poseidon2_external, + poseidon2_internal, + lookup, + overflow }; } auto get_gate_blocks() @@ -59,6 +75,23 @@ template class MegaArith { bool operator==(const MegaTraceBlocks& other) const = default; }; + // A tiny structuring (for testing without recursive verifications only) + struct TinyTestStructuredBlockSizes : public MegaTraceBlocks { + TinyTestStructuredBlockSizes() + { + this->ecc_op = 18; + this->pub_inputs = 1; + this->busread = 3; + this->arithmetic = 1 << 14; + this->delta_range = 5; + this->elliptic = 2; + this->aux = 10; + this->poseidon2_external = 2; + this->poseidon2_internal = 2; + this->lookup = 2; + this->overflow = 0; + } + }; // An arbitrary but small-ish structuring that can be used for generic unit testing with non-trivial circuits struct SmallTestStructuredBlockSizes : public MegaTraceBlocks { SmallTestStructuredBlockSizes() @@ -74,6 +107,7 @@ template class MegaArith { this->poseidon2_external = FIXED_SIZE; this->poseidon2_internal = 1 << 15; this->lookup = FIXED_SIZE; + this->overflow = 0; } }; @@ -92,6 +126,7 @@ template class MegaArith { this->poseidon2_external = 2500; this->poseidon2_internal = 14000; this->lookup = 72000; + this->overflow = 0; // Additional structurings for testing // // 2^18 (Only viable if no 2^19 circuit is used!) @@ -105,6 +140,7 @@ template class MegaArith { // this->poseidon2_external = 2500; // this->poseidon2_internal = 14000; // this->lookup = 36000; + // this->overflow = 0; // // 2^20 // this->ecc_op = 1 << 11; @@ -117,6 +153,7 @@ template class MegaArith { // this->poseidon2_external = 5000; // this->poseidon2_internal = 28000; // this->lookup = 144000; + // this->overflow = 0; } }; @@ -134,6 +171,7 @@ template class MegaArith { this->poseidon2_external = 30128; this->poseidon2_internal = 172000; this->lookup = 200000; + this->overflow = 0; } }; @@ -143,6 +181,8 @@ template class MegaArith { using FF = FF_; class MegaTraceBlock : public ExecutionTraceBlock { + using SelectorType = ExecutionTraceBlock::SelectorType; + public: void populate_wires(const uint32_t& idx_1, const uint32_t& idx_2, const uint32_t& idx_3, const uint32_t& idx_4) { @@ -176,6 +216,18 @@ template class MegaArith { auto& q_poseidon2_internal() { return this->selectors[12]; }; auto& q_lookup_type() { return this->selectors[13]; }; + RefVector get_gate_selectors() + { + return { q_busread(), + q_arith(), + q_delta_range(), + q_elliptic(), + q_aux(), + q_poseidon2_external(), + q_poseidon2_internal(), + q_lookup_type() }; + } + /** * @brief Add zeros to all selectors which are not part of the conventional Ultra arithmetization * @details Facilitates reuse of Ultra gate construction functions in arithmetizations which extend the @@ -195,6 +247,8 @@ template class MegaArith { struct TraceBlocks : public MegaTraceBlocks { + bool has_overflow = false; // indicates whether the overflow block has non-zero fixed or actual size + TraceBlocks() { this->aux.has_ram_rom = true; @@ -202,13 +256,16 @@ template class MegaArith { } // Set fixed block sizes for use in structured trace - void set_fixed_block_sizes(TraceStructure setting) + void set_fixed_block_sizes(TraceSettings settings) { MegaTraceBlocks fixed_block_sizes{}; // zero initialized - switch (setting) { + switch (settings.structure) { case TraceStructure::NONE: break; + case TraceStructure::TINY_TEST: + fixed_block_sizes = TinyTestStructuredBlockSizes(); + break; case TraceStructure::SMALL_TEST: fixed_block_sizes = SmallTestStructuredBlockSizes(); break; @@ -222,6 +279,8 @@ template class MegaArith { for (auto [block, size] : zip_view(this->get(), fixed_block_sizes.get())) { block.set_fixed_size(size); } + // Set the size of overflow block containing the overflow from all other blocks + this->overflow.set_fixed_size(settings.overflow_capacity); } void compute_offsets(bool is_structured) @@ -246,6 +305,7 @@ template class MegaArith { info("poseidon ext :\t", this->poseidon2_external.size(), "/", this->poseidon2_external.get_fixed_size()); info("poseidon int :\t", this->poseidon2_internal.size(), "/", this->poseidon2_internal.get_fixed_size()); info("lookups :\t", this->lookup.size(), "/", this->lookup.get_fixed_size()); + info("overflow :\t", this->overflow.size(), "/", this->overflow.get_fixed_size()); info(""); } @@ -258,19 +318,6 @@ template class MegaArith { return total_size; } - void check_within_fixed_sizes() - { - for (auto block : this->get()) { - if (block.size() > block.get_fixed_size()) { - - info("WARNING: Num gates in circuit block exceeds the specified fixed size - execution trace will " - "not be constructed correctly!"); - summarize(); - ASSERT(false); - } - } - } - bool operator==(const TraceBlocks& other) const = default; }; diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/ultra_arithmetization.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/ultra_arithmetization.hpp index be1e47fac17..a188d2faef5 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/ultra_arithmetization.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/ultra_arithmetization.hpp @@ -1,5 +1,6 @@ #pragma once +#include "barretenberg/common/ref_vector.hpp" #include "barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp" namespace bb { @@ -23,11 +24,12 @@ template class UltraArith { T lookup; T poseidon2_external; T poseidon2_internal; + T overflow; auto get() { - return RefArray{ pub_inputs, arithmetic, delta_range, elliptic, - aux, lookup, poseidon2_external, poseidon2_internal }; + return RefArray{ pub_inputs, arithmetic, delta_range, elliptic, aux, + lookup, poseidon2_external, poseidon2_internal, overflow }; } auto get_gate_blocks() @@ -51,6 +53,7 @@ template class UltraArith { this->lookup = FIXED_SIZE; this->poseidon2_external = FIXED_SIZE; this->poseidon2_internal = FIXED_SIZE; + this->overflow = 0; } }; @@ -60,6 +63,8 @@ template class UltraArith { using FF = FF_; class UltraTraceBlock : public ExecutionTraceBlock { + using SelectorType = ExecutionTraceBlock::SelectorType; + public: void populate_wires(const uint32_t& idx_1, const uint32_t& idx_2, const uint32_t& idx_3, const uint32_t& idx_4) { @@ -91,10 +96,19 @@ template class UltraArith { auto& q_lookup_type() { return this->selectors[10]; }; auto& q_poseidon2_external() { return this->selectors[11]; }; auto& q_poseidon2_internal() { return this->selectors[12]; }; + + RefVector get_gate_selectors() + { + return { q_arith(), q_delta_range(), q_elliptic(), + q_aux(), q_poseidon2_external(), q_poseidon2_internal(), + q_lookup_type() }; + } }; struct TraceBlocks : public UltraTraceBlocks { + bool has_overflow = false; + TraceBlocks() { this->aux.has_ram_rom = true; @@ -102,14 +116,15 @@ template class UltraArith { } // Set fixed block sizes for use in structured trace - void set_fixed_block_sizes(TraceStructure setting) + void set_fixed_block_sizes(TraceSettings settings) { UltraTraceBlocks fixed_block_sizes{}; // zero initialized - switch (setting) { + switch (settings.structure) { case TraceStructure::NONE: break; // We don't use Ultra in ClientIvc so no need for anything other than sizing for simple unit tests + case TraceStructure::TINY_TEST: case TraceStructure::SMALL_TEST: case TraceStructure::CLIENT_IVC_BENCH: case TraceStructure::E2E_FULL_TEST: @@ -119,6 +134,7 @@ template class UltraArith { for (auto [block, size] : zip_view(this->get(), fixed_block_sizes.get())) { block.set_fixed_size(size); } + this->overflow.set_fixed_size(settings.overflow_capacity); } void compute_offsets(bool is_structured) @@ -133,7 +149,8 @@ template class UltraArith { auto get() { return RefArray{ this->pub_inputs, this->arithmetic, this->delta_range, this->elliptic, - this->aux, this->lookup, this->poseidon2_external, this->poseidon2_internal }; + this->aux, this->lookup, this->poseidon2_external, this->poseidon2_internal, + this->overflow }; } void summarize() const @@ -147,6 +164,7 @@ template class UltraArith { info("lookups :\t", this->lookup.size()); info("poseidon ext :\t", this->poseidon2_external.size()); info("poseidon int :\t", this->poseidon2_internal.size()); + info("overflow :\t", this->overflow.size()); } size_t get_total_structured_size() @@ -158,22 +176,6 @@ template class UltraArith { return total_size; } - /** - * @brief Check that the number of rows populated in each block does not exceed the specified fixed size - * @note This check is only applicable when utilizing a structured trace - * - */ - void check_within_fixed_sizes() - { - for (auto block : this->get()) { - if (block.size() > block.get_fixed_size()) { - info("WARNING: Num gates in circuit block exceeds the specified fixed size - execution trace will " - "not be constructed correctly!"); - ASSERT(false); - } - } - } - bool operator==(const TraceBlocks& other) const = default; }; diff --git a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy.test.cpp b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy.test.cpp index 0483f2214a8..4db7309a7da 100644 --- a/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy.test.cpp +++ b/barretenberg/cpp/src/barretenberg/protogalaxy/protogalaxy.test.cpp @@ -54,10 +54,10 @@ template class ProtogalaxyTests : public testing::Test { } // Construct decider keys for a provided circuit and add to tuple - static void construct_keys(TupleOfKeys& keys, Builder& builder, TraceStructure structure = TraceStructure::NONE) + static void construct_keys(TupleOfKeys& keys, Builder& builder, TraceSettings trace_settings = TraceSettings{}) { - auto decider_proving_key = std::make_shared(builder, structure); + auto decider_proving_key = std::make_shared(builder, trace_settings); auto verification_key = std::make_shared(decider_proving_key->proving_key); auto decider_verification_keys = std::make_shared(verification_key); get<0>(keys).emplace_back(decider_proving_key); @@ -65,7 +65,7 @@ template class ProtogalaxyTests : public testing::Test { } // Construct a given numer of decider key pairs - static TupleOfKeys construct_keys(size_t num_keys, TraceStructure structure = TraceStructure::NONE) + static TupleOfKeys construct_keys(size_t num_keys, TraceSettings trace_settings = TraceSettings{}) { TupleOfKeys keys; // TODO(https://github.com/AztecProtocol/barretenberg/issues/938): Parallelize this loop @@ -73,7 +73,7 @@ template class ProtogalaxyTests : public testing::Test { auto builder = typename Flavor::CircuitBuilder(); construct_circuit(builder); - construct_keys(keys, builder, structure); + construct_keys(keys, builder, trace_settings); } return keys; } @@ -442,13 +442,13 @@ template class ProtogalaxyTests : public testing::Test { */ static void test_full_protogalaxy_structured_trace() { - TraceStructure trace_structure = TraceStructure::SMALL_TEST; - TupleOfKeys keys_1 = construct_keys(2, trace_structure); + TraceSettings trace_settings{ TraceStructure::SMALL_TEST }; + TupleOfKeys keys_1 = construct_keys(2, trace_settings); auto [prover_accumulator, verifier_accumulator] = fold_and_verify(get<0>(keys_1), get<1>(keys_1)); check_accumulator_target_sum_manual(prover_accumulator, true); - TupleOfKeys keys_2 = construct_keys(1, trace_structure); // just one key pair + TupleOfKeys keys_2 = construct_keys(1, trace_settings); // just one key pair auto [prover_accumulator_2, verifier_accumulator_2] = fold_and_verify({ prover_accumulator, get<0>(keys_2)[0] }, { verifier_accumulator, get<1>(keys_2)[0] }); @@ -465,7 +465,7 @@ template class ProtogalaxyTests : public testing::Test { */ static void test_full_protogalaxy_structured_trace_inhomogeneous_circuits() { - TraceStructure trace_structure = TraceStructure::SMALL_TEST; + TraceSettings trace_settings{ TraceStructure::SMALL_TEST }; // Construct three circuits to be folded, each with a different number of constraints Builder builder1; @@ -482,8 +482,8 @@ template class ProtogalaxyTests : public testing::Test { // Construct the decider key pairs for the first two circuits TupleOfKeys keys_1; - construct_keys(keys_1, builder1, trace_structure); - construct_keys(keys_1, builder2, trace_structure); + construct_keys(keys_1, builder1, trace_settings); + construct_keys(keys_1, builder2, trace_settings); // Fold the first two pairs auto [prover_accumulator, verifier_accumulator] = fold_and_verify(get<0>(keys_1), get<1>(keys_1)); @@ -491,7 +491,7 @@ template class ProtogalaxyTests : public testing::Test { // Construct the decider key pair for the third circuit TupleOfKeys keys_2; - construct_keys(keys_2, builder3, trace_structure); + construct_keys(keys_2, builder3, trace_settings); // Fold 3rd pair of keys into their respective accumulators auto [prover_accumulator_2, verifier_accumulator_2] = diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mock_circuits.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mock_circuits.hpp index 0682a8a44ab..070046f3f15 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mock_circuits.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mock_circuits.hpp @@ -87,6 +87,41 @@ class MockCircuits { } } + /** + * @brief Add some simple RAM (aux) gates for testing memory read/write functionality + * @brief Each iteration adds 18 gates (including finalization) + * + * @param builder + */ + template static void add_RAM_gates(Builder& builder) + { + std::array ram_values{ builder.add_variable(5), + builder.add_variable(10), + builder.add_variable(20) }; + + size_t ram_id = builder.create_RAM_array(3); + + for (size_t i = 0; i < 3; ++i) { + builder.init_RAM_element(ram_id, i, ram_values[i]); + } + + auto val_idx_1 = builder.read_RAM_array(ram_id, builder.add_variable(1)); + auto val_idx_2 = builder.read_RAM_array(ram_id, builder.add_variable(2)); + auto val_idx_3 = builder.read_RAM_array(ram_id, builder.add_variable(0)); + + builder.create_big_add_gate({ + val_idx_1, + val_idx_2, + val_idx_3, + builder.zero_idx, + 1, + 1, + 1, + 0, + -35, + }); + } + /** * @brief Populate a builder with a specified number of arithmetic gates; includes a PI * diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.cpp index baa129c691c..e89d27d23db 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.cpp @@ -80,6 +80,113 @@ void DeciderProvingKey_::construct_databus_polynomials(Circuit& circuit) } } +/** + * @brief Check that the number of gates in each block does not exceed its fixed capacity. Move any overflow to the + * overflow block. + * @details Using a structured trace (fixed capcity for each gate type) optimizes the efficiency of folding. However, + * to accommodate circuits which cannot fit into a prescribed trace, gates which overflow their corresponding block are + * placed into an overflow block which can contain arbitrary gate types. + * @note One sublety is that gates at row i may in general utilize the values at row i+1 via shifts. If the last row in + * a full-capacity block is such a gate, then moving the overflow out of sequence will cause that gate not to be + * satisfied. To avoid this, when a block overflows, the final gate in the block is duplicated, once in the main block + * with the selectors turned off but the wires values maintained (so that the prior gate can read into it but it does + * not itself try to read into the next row) and again as a normal gate in the overflow block. Therefore, the total + * number of gates in the circuit increases by one for each block that overflows. + * + * @tparam Flavor + * @param circuit + */ +template +void DeciderProvingKey_::move_structured_trace_overflow_to_overflow_block(Circuit& circuit) +{ + auto& blocks = circuit.blocks; + auto& overflow_block = circuit.blocks.overflow; + + // Set has_overflow to true if a nonzero fixed size has been prescribed for the overflow block + blocks.has_overflow = (overflow_block.get_fixed_size() > 0); + + blocks.compute_offsets(/*is_structured=*/true); // compute the offset of each fixed size block + + // Check each block for capacity overflow; if necessary move gates into the overflow block + for (auto& block : blocks.get()) { + size_t block_size = block.size(); + uint32_t fixed_block_size = block.get_fixed_size(); + if (block_size > fixed_block_size && block != overflow_block) { + // Disallow overflow in blocks that are not expected to be used by App circuits + ASSERT(!block.is_pub_inputs); + if constexpr (IsGoblinFlavor) { + ASSERT(block != blocks.ecc_op); + } + + // Set has_overflow to true if at least one block exceeds its capacity + blocks.has_overflow = true; + + // The circuit memory read/write records store the indices at which a RAM/ROM read/write has occurred. If + // the block containing RAM/ROM gates overflows, the indices of the corresponding gates in the memory + // records need to be updated to reflect their new position in the overflow block + if (block.has_ram_rom) { + + uint32_t overflow_cur_idx = overflow_block.trace_offset + static_cast(overflow_block.size()); + overflow_cur_idx -= block.trace_offset; // we'll add block.trace_offset to everything later + uint32_t offset = overflow_cur_idx + 1; // +1 accounts for duplication of final gate + for (auto& idx : circuit.memory_read_records) { + // last gate in the main block will be duplicated; if necessary, duplicate the memory read idx too + if (idx == fixed_block_size - 1) { + circuit.memory_read_records.push_back(overflow_cur_idx); + } + if (idx >= fixed_block_size) { + idx -= fixed_block_size; // redefine index from zero + idx += offset; // shift to correct location in overflow block + } + } + for (auto& idx : circuit.memory_write_records) { + // last gate in the main block will be duplicated; if necessary, duplicate the memory write idx too + if (idx == fixed_block_size - 1) { + circuit.memory_write_records.push_back(overflow_cur_idx); + } + if (idx >= fixed_block_size) { + idx -= fixed_block_size; // redefine index from zero + idx += offset; // shift to correct location in overflow block + } + } + } + + // Move the excess wire and selector data from the offending block to the overflow block + size_t overflow_start = fixed_block_size - 1; // the final gate in the main block is duplicated + size_t overflow_end = block_size; + for (auto [wire, overflow_wire] : zip_view(block.wires, overflow_block.wires)) { + for (size_t i = overflow_start; i < overflow_end; ++i) { + overflow_wire.push_back(wire[i]); + } + wire.resize(fixed_block_size); // shrink the main block to its max capacity + } + for (auto [selector, overflow_selector] : zip_view(block.selectors, overflow_block.selectors)) { + for (size_t i = overflow_start; i < overflow_end; ++i) { + overflow_selector.push_back(selector[i]); + } + selector.resize(fixed_block_size); // shrink the main block to its max capacity + } + // Convert duplicated final gate in the main block to a 'dummy' gate by turning off all selectors. This + // ensures it can be read into by the previous gate but does not itself try to read into the next gate. + for (auto& selector : block.get_gate_selectors()) { + selector.back() = 0; + } + } + } + + // Set the fixed size of the overflow block to its current size + if (overflow_block.size() > overflow_block.get_fixed_size()) { + info("WARNING: Structured trace overflowed beyond the prescribed fixed overflow size. This is valid in the " + "context of one-off VK/proof generation but not in the IVC setting. \nPrescribed overflow size: ", + overflow_block.get_fixed_size(), + ". \nActual overflow size: ", + overflow_block.size(), + "\n"); + overflow_block.set_fixed_size(static_cast(overflow_block.size())); + blocks.summarize(); + } +} + template class DeciderProvingKey_; template class DeciderProvingKey_; template class DeciderProvingKey_; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.hpp index 010784df2ea..eebf9cc6439 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_proving_key.hpp @@ -46,9 +46,9 @@ template class DeciderProvingKey_ { FF target_sum; DeciderProvingKey_(Circuit& circuit, - TraceStructure trace_structure = TraceStructure::NONE, + TraceSettings trace_settings = TraceSettings{}, std::shared_ptr commitment_key = nullptr) - : is_structured(trace_structure != TraceStructure::NONE) + : is_structured(trace_settings.structure != TraceStructure::NONE) { PROFILE_THIS_NAME("DeciderProvingKey(Circuit&)"); vinfo("DeciderProvingKey(Circuit&)"); @@ -60,8 +60,9 @@ template class DeciderProvingKey_ { // If using a structured trace, set fixed block sizes, check their validity, and set the dyadic circuit size if (is_structured) { - circuit.blocks.set_fixed_block_sizes(trace_structure); // set the fixed sizes for each block - circuit.blocks.check_within_fixed_sizes(); // ensure that no block exceeds its fixed size + circuit.blocks.set_fixed_block_sizes(trace_settings); // set the fixed sizes for each block + circuit.blocks.summarize(); + move_structured_trace_overflow_to_overflow_block(circuit); dyadic_circuit_size = compute_structured_dyadic_size(circuit); // set the dyadic size accordingly } else { dyadic_circuit_size = compute_dyadic_size(circuit); // set dyadic size directly from circuit block sizes @@ -85,7 +86,9 @@ template class DeciderProvingKey_ { vinfo("constructing proving key"); proving_key = ProvingKey(dyadic_circuit_size, circuit.public_inputs.size(), commitment_key); - if (IsGoblinFlavor && !is_structured) { + // If not using structured trace OR if using structured trace but overflow has occurred (overflow block in + // use), allocate full size polys + if ((IsGoblinFlavor && !is_structured) || (is_structured && circuit.blocks.has_overflow)) { // Allocate full size polynomials proving_key.polynomials = typename Flavor::ProverPolynomials(dyadic_circuit_size); } else { // Allocate only a correct amount of memory for each polynomial @@ -329,6 +332,8 @@ template class DeciderProvingKey_ { void construct_databus_polynomials(Circuit&) requires IsGoblinFlavor; + + static void move_structured_trace_overflow_to_overflow_block(Circuit& circuit); }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp index ae7ffca7466..92159f626a6 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp @@ -2,6 +2,7 @@ #include #include +#include "barretenberg/circuit_checker/circuit_checker.hpp" #include "barretenberg/common/log.hpp" #include "barretenberg/goblin/mock_circuits.hpp" #include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp" @@ -48,6 +49,28 @@ template class MegaHonkTests : public ::testing::Test { return verified; } + /** + * @brief Construct and a verify a Honk proof using a specified structured trace + * + */ + bool construct_and_verify_honk_proof_with_structured_trace(auto& builder, TraceSettings& trace_settings) + { + // no ZK flavor for now + using Prover = UltraProver_; + using Verifier = UltraVerifier_; + using VerificationKey = typename MegaFlavor::VerificationKey; + using DeciderProvingKey = DeciderProvingKey_; + auto proving_key = std::make_shared(builder, trace_settings); + + Prover prover(proving_key); + auto verification_key = std::make_shared(proving_key->proving_key); + Verifier verifier(verification_key); + auto proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + + return verified; + } + /** * @brief Construct and verify a Goblin ECC op queue merge proof * @@ -96,8 +119,8 @@ TYPED_TEST(MegaHonkTests, BasicStructured) GoblinMockCircuits::construct_simple_circuit(builder); // Construct and verify Honk proof using a structured trace - TraceStructure trace_structure = TraceStructure::SMALL_TEST; - auto proving_key = std::make_shared>(builder, trace_structure); + TraceSettings trace_settings{ TraceStructure::SMALL_TEST }; + auto proving_key = std::make_shared>(builder, trace_settings); Prover prover(proving_key); auto verification_key = std::make_shared(proving_key->proving_key); Verifier verifier(verification_key); @@ -225,3 +248,69 @@ TYPED_TEST(MegaHonkTests, MultipleCircuitsHonkAndMerge) EXPECT_EQ(result, expected); } } + +/** + * @brief Test the structured trace overflow mechanism for various circuits which overflow in different ways + * + */ +TYPED_TEST(MegaHonkTests, StructuredTraceOverflow) +{ + using Flavor = TypeParam; + using Builder = Flavor::CircuitBuilder; + + TraceSettings trace_settings{ TraceStructure::TINY_TEST }; + + { // Overflow in Arithmetic block only + Builder builder; + + GoblinMockCircuits::construct_simple_circuit(builder); + MockCircuits::add_arithmetic_gates(builder, 1 << 15); + + bool verified = this->construct_and_verify_honk_proof_with_structured_trace(builder, trace_settings); + EXPECT_TRUE(verified); + + // We expect that the circuit has overflowed the provided structured trace + EXPECT_TRUE(builder.blocks.has_overflow); + } + + { // Overflow in Aux block (RAM gates; uses memory records which requires specific logic in overflow mechanism) + Builder builder; + + GoblinMockCircuits::construct_simple_circuit(builder); + MockCircuits::add_RAM_gates(builder); + + bool verified = this->construct_and_verify_honk_proof_with_structured_trace(builder, trace_settings); + EXPECT_TRUE(verified); + + // We expect that the circuit has overflowed the provided structured trace + EXPECT_TRUE(builder.blocks.has_overflow); + } + + { // Overflow in Lookup block only + Builder builder; + + GoblinMockCircuits::construct_simple_circuit(builder); + MockCircuits::add_lookup_gates(builder, /*num_iterations=*/8); + + bool verified = this->construct_and_verify_honk_proof_with_structured_trace(builder, trace_settings); + EXPECT_TRUE(verified); + + // We expect that the circuit has overflowed the provided structured trace + EXPECT_TRUE(builder.blocks.has_overflow); + } + + { // Overflow in Multiple blocks simultaneously + Builder builder; + + GoblinMockCircuits::construct_simple_circuit(builder); + MockCircuits::add_arithmetic_gates(builder, 1 << 15); + MockCircuits::add_RAM_gates(builder); + MockCircuits::add_lookup_gates(builder, /*num_iterations=*/8); + + bool verified = this->construct_and_verify_honk_proof_with_structured_trace(builder, trace_settings); + EXPECT_TRUE(verified); + + // We expect that the circuit has overflowed the provided structured trace + EXPECT_TRUE(builder.blocks.has_overflow); + } +} diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp index 79f6406cbe6..b8564e9ae75 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp @@ -100,8 +100,8 @@ TYPED_TEST(UltraHonkTests, StructuredTrace) MockCircuits::add_arithmetic_gates_with_public_inputs(builder, num_gates); // Construct an proving_key with a structured execution trace - TraceStructure trace_structure = TraceStructure::SMALL_TEST; - auto proving_key = std::make_shared(builder, trace_structure); + TraceSettings trace_settings{ TraceStructure::SMALL_TEST }; + auto proving_key = std::make_shared(builder, trace_settings); typename TestFixture::Prover prover(proving_key); auto verification_key = std::make_shared(proving_key->proving_key); typename TestFixture::Verifier verifier(verification_key); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/tests/recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/tests/recursive_verifier.test.cpp index 1de397a31f2..d6c110e3499 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/tests/recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/tests/recursive_verifier.test.cpp @@ -120,7 +120,7 @@ TEST_F(AvmRecursiveTests, recursion) // Make a proof of the verification of an AVM proof const size_t srs_size = 1 << 23; auto ultra_instance = std::make_shared( - outer_circuit, TraceStructure::NONE, std::make_shared>(srs_size)); + outer_circuit, TraceSettings{}, std::make_shared>(srs_size)); OuterProver ultra_prover(ultra_instance); auto ultra_verification_key = std::make_shared(ultra_instance->proving_key); OuterVerifier ultra_verifier(ultra_verification_key);