diff --git a/barretenberg/cpp/src/barretenberg/api/api_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/api/api_client_ivc.cpp index c37678571190..45f8eecfdba9 100644 --- a/barretenberg/cpp/src/barretenberg/api/api_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/api/api_client_ivc.cpp @@ -289,8 +289,8 @@ void write_arbitrary_valid_client_ivc_proof_and_vk_to_file(const std::filesystem // Construct and accumulate a series of mocked private function execution circuits PrivateFunctionExecutionMockCircuitProducer circuit_producer; for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit, vk); } ClientIVC::Proof proof = ivc.prove(); diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_client_ivc.cpp index 05b8b9015722..25c5704058ca 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_client_ivc.cpp @@ -117,18 +117,23 @@ static std::shared_ptr get_acir_program_decider_pr ClientIVC::VerificationKey compute_civc_vk(const BBApiRequest& request, size_t num_public_inputs_in_final_circuit) { ClientIVC ivc{ /* num_circuits */ 2, request.trace_settings }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Initialize the IVC with an arbitrary circuit // We segfault if we only call accumulate once static constexpr size_t SMALL_ARBITRARY_LOG_CIRCUIT_SIZE{ 5 }; - MegaCircuitBuilder circuit_0 = circuit_producer.create_next_circuit(ivc, SMALL_ARBITRARY_LOG_CIRCUIT_SIZE); - ivc.accumulate(circuit_0); + auto [circuit_0, vk_0] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_ARBITRARY_LOG_CIRCUIT_SIZE }); + ivc.accumulate(circuit_0, vk_0); // Create another circuit and accumulate - MegaCircuitBuilder circuit_1 = - circuit_producer.create_next_circuit(ivc, SMALL_ARBITRARY_LOG_CIRCUIT_SIZE, num_public_inputs_in_final_circuit); - ivc.accumulate(circuit_1); + auto [circuit_1, vk_1] = + circuit_producer.create_next_circuit_and_vk(ivc, + { + .num_public_inputs = num_public_inputs_in_final_circuit, + .log2_num_gates = SMALL_ARBITRARY_LOG_CIRCUIT_SIZE, + }); + ivc.accumulate(circuit_1, vk_1); // Construct the hiding circuit and its VK (stored internally in the IVC) ivc.construct_hiding_circuit_key(); 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 812008b1cd83..96541f01f81c 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,15 +34,15 @@ BENCHMARK_DEFINE_F(ClientIVCBench, VerificationOnly)(benchmark::State& state) { ClientIVC ivc{ /*num_circuits=*/2, { AZTEC_TRACE_STRUCTURE } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Initialize the IVC with an arbitrary circuit - auto circuit_0 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_0); + auto [circuit_0, vk_0] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit_0, vk_0); // Create another circuit and accumulate - auto circuit_1 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_1); + auto [circuit_1, vk_1] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit_1, vk_1); auto proof = ivc.prove(); @@ -63,7 +63,7 @@ BENCHMARK_DEFINE_F(ClientIVCBench, Full)(benchmark::State& state) for (auto _ : state) { BB_REPORT_OP_COUNT_IN_BENCH(state); - perform_ivc_accumulation_rounds(total_num_circuits, ivc, mocked_vks, /* mock_vk */ true); + perform_ivc_accumulation_rounds(total_num_circuits, ivc, mocked_vks); ivc.prove(); } } @@ -76,12 +76,12 @@ BENCHMARK_DEFINE_F(ClientIVCBench, Ambient_17_in_20)(benchmark::State& state) auto total_num_circuits = 2 * static_cast(state.range(0)); // 2x accounts for kernel circuits ClientIVC ivc{ total_num_circuits, { AZTEC_TRACE_STRUCTURE } }; - auto mocked_vks = mock_vks(total_num_circuits); + const bool large_first_app = false; + auto mocked_vks = mock_vks(total_num_circuits, large_first_app); for (auto _ : state) { BB_REPORT_OP_COUNT_IN_BENCH(state); - perform_ivc_accumulation_rounds( - total_num_circuits, ivc, mocked_vks, /* mock_vk */ true, /* large_first_app */ false); + perform_ivc_accumulation_rounds(total_num_circuits, ivc, mocked_vks, large_first_app); ivc.prove(); } } diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp index e8b45b3515b0..d5d318f3e524 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp @@ -271,18 +271,17 @@ void ClientIVC::complete_kernel_circuit_logic(ClientCircuit& circuit) * this case, just produce a Honk proof for that circuit and do no folding. * @param precomputed_vk */ -void ClientIVC::accumulate(ClientCircuit& circuit, - const std::shared_ptr& precomputed_vk, - const bool mock_vk) +void ClientIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk) { BB_ASSERT_LT( num_circuits_accumulated, num_circuits, "ClientIVC: Attempting to accumulate more circuits than expected."); - if (circuit.is_kernel) { // Transcript to be shared across folding of K_{i} (kernel), A_{i+1,1} (app), .., A_{i+1, n} (app) accumulation_transcript = std::make_shared(); } + ASSERT(precomputed_vk != nullptr, "ClientIVC::acumulate - VK expected for the provided circuit"); + // Construct the proving key for circuit std::shared_ptr proving_key = std::make_shared(circuit, trace_settings); @@ -294,22 +293,9 @@ void ClientIVC::accumulate(ClientCircuit& circuit, goblin.commitment_key = bn254_commitment_key; } proving_key->commitment_key = bn254_commitment_key; - - vinfo("getting honk vk... precomputed?: ", precomputed_vk); - // Update the accumulator trace usage based on the present circuit trace_usage_tracker.update(circuit); - // Set the verification key from precomputed if available, else compute it - { - PROFILE_THIS_NAME("ClientIVC::accumulate create MegaVerificationKey"); - honk_vk = - precomputed_vk ? precomputed_vk : std::make_shared(proving_key->get_precomputed()); - } - // mock_vk is used in benchmarks to avoid any VK construction. - if (mock_vk) { - honk_vk->set_metadata(proving_key->get_metadata()); - vinfo("set honk vk metadata"); - } + honk_vk = precomputed_vk; VerifierInputs queue_entry{ .honk_vk = honk_vk, .is_kernel = circuit.is_kernel }; if (num_circuits_accumulated == 0) { // First circuit in the IVC diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp index bb3d445b70f3..8688ada82fb3 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp @@ -225,9 +225,7 @@ class ClientIVC { * set using the proving key produced from `circuit` in order to pass some assertions in the Oink prover. * @param mock_vk A boolean to say whether the precomputed vk should have its metadata set. */ - void accumulate(ClientCircuit& circuit, - const std::shared_ptr& precomputed_vk = nullptr, - const bool mock_vk = false); + void accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk); Proof prove(); 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 8f368d3c352c..62b32a654b8c 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.test.cpp @@ -16,6 +16,8 @@ using namespace bb; static constexpr size_t MAX_NUM_KERNELS = 15; +static constexpr size_t SMALL_LOG_2_NUM_GATES = 5; +static constexpr size_t MEDIUM_LOG_2_NUM_GATES = 16; class ClientIVCTests : public ::testing::Test { protected: @@ -58,10 +60,11 @@ class ClientIVCTests : public ::testing::Test { static std::pair generate_ivc_proof(size_t num_circuits) { ClientIVC ivc{ num_circuits, { SMALL_TEST_STRUCTURE } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; for (size_t j = 0; j < num_circuits; ++j) { - auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); + auto [circuit, vk] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES }); + ivc.accumulate(circuit, vk); } return { ivc.prove(), ivc.get_vk() }; }; @@ -77,15 +80,17 @@ TEST_F(ClientIVCTests, Basic) { ClientIVC ivc{ /*num_circuits=*/2 }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Initialize the IVC with an arbitrary circuit - Builder circuit_0 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_0); + auto [circuit_0, vk_0] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = MEDIUM_LOG_2_NUM_GATES }); + ivc.accumulate(circuit_0, vk_0); // Create another circuit and accumulate - Builder circuit_1 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_1); + auto [circuit_1, vk_1] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = MEDIUM_LOG_2_NUM_GATES }); + ivc.accumulate(circuit_1, vk_1); EXPECT_TRUE(ivc.prove_and_verify()); }; @@ -99,10 +104,10 @@ TEST_F(ClientIVCTests, BasicFour) { ClientIVC ivc{ /*num_circuits=*/4 }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; for (size_t idx = 0; idx < 4; ++idx) { - Builder circuit = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit, vk); } EXPECT_TRUE(ivc.prove_and_verify()); @@ -122,12 +127,13 @@ TEST_F(ClientIVCTests, BadProofFailure) { ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Construct and accumulate a set of mocked private function execution circuits for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); + auto [circuit, vk] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES }); + ivc.accumulate(circuit, vk); } EXPECT_TRUE(ivc.prove_and_verify()); } @@ -136,14 +142,15 @@ TEST_F(ClientIVCTests, BadProofFailure) { ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; 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 = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); + 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(); @@ -162,12 +169,13 @@ TEST_F(ClientIVCTests, BadProofFailure) { ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Construct and accumulate a set of mocked private function execution circuits for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); + 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 @@ -182,14 +190,15 @@ TEST_F(ClientIVCTests, BadProofFailure) { ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; 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 = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); + 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 == NUM_CIRCUITS - 1) { num_public_inputs = circuit.num_public_inputs(); @@ -207,27 +216,6 @@ TEST_F(ClientIVCTests, BadProofFailure) EXPECT_TRUE(true); }; -/** - * @brief Prove and verify accumulation of an arbitrary set of circuits - * - */ -TEST_F(ClientIVCTests, BasicLarge) -{ - const size_t NUM_CIRCUITS = 6; - ClientIVC ivc{ NUM_CIRCUITS }; - - ClientIVCMockCircuitProducer circuit_producer; - - // Construct and accumulate a set of mocked private function execution circuits - std::vector circuits; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit); - } - - EXPECT_TRUE(ivc.prove_and_verify()); -}; - /** * @brief Using a structured trace allows for the accumulation of circuits of varying size * @@ -237,68 +225,20 @@ TEST_F(ClientIVCTests, BasicStructured) const size_t NUM_CIRCUITS = 4; ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE } }; - ClientIVCMockCircuitProducer circuit_producer; - + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Construct and accumulate some circuits of varying size - size_t log2_num_gates = 5; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc, log2_num_gates); - ivc.accumulate(circuit); - log2_num_gates += 2; - } - - EXPECT_TRUE(ivc.prove_and_verify()); -}; - -/** - * @brief Prove and verify accumulation of an arbitrary set of circuits using precomputed verification keys - * - */ -TEST_F(ClientIVCTests, PrecomputedVerificationKeys) -{ - - const size_t NUM_CIRCUITS = 4; - ClientIVC ivc{ NUM_CIRCUITS }; - - ClientIVCMockCircuitProducer circuit_producer; - - auto precomputed_vks = circuit_producer.precompute_vks(NUM_CIRCUITS, TraceSettings{}); - - // Construct and accumulate set of circuits using the precomputed vkeys for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit, precomputed_vks[idx]); + auto [circuit, vk] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES + idx }); + ivc.accumulate(circuit, vk); } EXPECT_TRUE(ivc.prove_and_verify()); }; /** - * @brief Perform accumulation with a structured trace and precomputed verification keys - * - */ -TEST_F(ClientIVCTests, StructuredPrecomputedVKs) -{ - const size_t NUM_CIRCUITS = 4; - ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE } }; - - size_t log2_num_gates = 5; // number of gates in baseline mocked circuit - - ClientIVCMockCircuitProducer circuit_producer; - - auto precomputed_vks = circuit_producer.precompute_vks(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) { - auto circuit = circuit_producer.create_next_circuit(ivc, log2_num_gates); - ivc.accumulate(circuit, precomputed_vks[idx]); - } - - EXPECT_TRUE(ivc.prove_and_verify()); -}; - -/** - * @brief Produce 2 valid CIVC proofs. Ensure that replacing a proof component with a component from a different proof + * @brief Produce 2 valid CIVC proofs. Ensure that replacing a proof component with a component from a different + proof * leads to a verification failure. * */ @@ -361,14 +301,14 @@ TEST_F(ClientIVCTests, VKIndependenceTest) const size_t MIN_NUM_CIRCUITS = 2; // Folding more than 20 circuits requires to double the number of gates in Translator. const size_t MAX_NUM_CIRCUITS = 20; - const size_t log2_num_gates = 5; // number of gates in baseline mocked circuit auto generate_vk = [&](size_t num_circuits) { ClientIVC ivc{ num_circuits, { SMALL_TEST_STRUCTURE } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; for (size_t j = 0; j < num_circuits; ++j) { - auto circuit = circuit_producer.create_next_circuit(ivc, log2_num_gates); - ivc.accumulate(circuit); + auto [circuit, vk] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = SMALL_LOG_2_NUM_GATES }); + ivc.accumulate(circuit, vk); } ivc.prove(); auto ivc_vk = ivc.get_vk(); @@ -395,8 +335,10 @@ TEST_F(ClientIVCTests, VKIndependenceTest) /** * @brief Ensure that the CIVC VK is independent of whether any of the circuits being accumulated overflows the * structured trace - * @details If one of the circuits being accumulated overflows the structured trace, the dyadic size of the accumulator - * may increase. In this case we want to ensure that the CIVC VK (and in particular the hiding circuit VK) is identical + * @details If one of the circuits being accumulated overflows the structured trace, the dyadic size of the + accumulator + * may increase. In this case we want to ensure that the CIVC VK (and in particular the hiding circuit VK) is + identical * to the non-overflow case. This requires, for example, that the padding_indicator_array logic used in somecheck is * functioning properly. */ @@ -415,10 +357,10 @@ TEST_F(ClientIVCTests, VKIndependenceWithOverflow) auto generate_vk = [&](size_t num_circuits, size_t log2_num_gates) { ClientIVC ivc{ num_circuits, { trace_structure } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; for (size_t j = 0; j < num_circuits; ++j) { - auto circuit = circuit_producer.create_next_circuit(ivc, log2_num_gates); - ivc.accumulate(circuit); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = log2_num_gates }); + ivc.accumulate(circuit, vk); } ivc.prove(); auto ivc_vk = ivc.get_vk(); @@ -442,27 +384,6 @@ TEST_F(ClientIVCTests, VKIndependenceWithOverflow) EXPECT_EQ(*civc_vk_nominal.translator.get(), *civc_vk_overflow.translator.get()); }; -/** - * @brief Run a test using functions shared with the ClientIVC benchmark. - * @details We do have this in addition to the above tests anyway so we can believe that the benchmark is running on - * real data EXCEPT the verification keys, whose correctness is not needed to assess the performance of the folding - * prover. Before this test was added, we spend more than 50% of the benchmarking time running an entire IVC prover - * protocol just to precompute valid verification keys. - */ -HEAVY_TEST(ClientIVCBenchValidation, Full6) -{ - bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); - - const size_t total_num_circuits{ 12 }; - ClientIVC ivc{ total_num_circuits, { AZTEC_TRACE_STRUCTURE } }; - PrivateFunctionExecutionMockCircuitProducer circuit_producer; - auto precomputed_vks = circuit_producer.precompute_vks(total_num_circuits, ivc.trace_settings); - perform_ivc_accumulation_rounds(total_num_circuits, ivc, precomputed_vks); - auto proof = ivc.prove(); - bool verified = verify_ivc(proof, ivc); - EXPECT_TRUE(verified); -} - /** * @brief Test that running the benchmark suite with mocked verification keys will not error out. */ @@ -489,8 +410,10 @@ HEAVY_TEST(ClientIVCKernelCapacity, MaxCapacityPassing) const size_t total_num_circuits{ 2 * MAX_NUM_KERNELS }; ClientIVC ivc{ total_num_circuits, { AZTEC_TRACE_STRUCTURE } }; PrivateFunctionExecutionMockCircuitProducer circuit_producer; - auto precomputed_vks = circuit_producer.precompute_vks(total_num_circuits, ivc.trace_settings); - perform_ivc_accumulation_rounds(total_num_circuits, ivc, precomputed_vks); + for (size_t j = 0; j < total_num_circuits; ++j) { + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit, vk); + } auto proof = ivc.prove(); bool verified = verify_ivc(proof, ivc); EXPECT_TRUE(verified); @@ -503,14 +426,17 @@ HEAVY_TEST(ClientIVCKernelCapacity, MaxCapacityFailing) const size_t total_num_circuits{ 2 * (MAX_NUM_KERNELS + 1) }; ClientIVC ivc{ total_num_circuits, { AZTEC_TRACE_STRUCTURE } }; PrivateFunctionExecutionMockCircuitProducer circuit_producer; - auto precomputed_vks = circuit_producer.precompute_vks(total_num_circuits, ivc.trace_settings); - perform_ivc_accumulation_rounds(total_num_circuits, ivc, precomputed_vks); + for (size_t j = 0; j < total_num_circuits; ++j) { + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit, vk); + } EXPECT_ANY_THROW(ivc.prove()); } /** * @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 + * @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. * */ @@ -521,13 +447,13 @@ TEST_F(ClientIVCTests, StructuredTraceOverflow) const size_t NUM_CIRCUITS = 4; ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE, /*overflow_capacity=*/1 << 17 } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // 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); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = log2_num_gates }); + ivc.accumulate(circuit, vk); log2_num_gates += 1; } @@ -562,12 +488,13 @@ TEST_F(ClientIVCTests, DynamicTraceOverflow) uint32_t overflow_capacity = 0; const size_t NUM_CIRCUITS = test.log2_num_arith_gates.size(); ClientIVC ivc{ NUM_CIRCUITS, { SMALL_TEST_STRUCTURE_FOR_OVERFLOWS, overflow_capacity } }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Accumulate for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc, test.log2_num_arith_gates[idx]); - ivc.accumulate(circuit); + auto [circuit, vk] = + circuit_producer.create_next_circuit_and_vk(ivc, { .log2_num_gates = test.log2_num_arith_gates[idx] }); + ivc.accumulate(circuit, vk); } EXPECT_EQ(check_accumulator_target_sum_manual(ivc.fold_output.accumulator), true); @@ -583,15 +510,15 @@ TEST_F(ClientIVCTests, MsgpackProofFromFile) { ClientIVC ivc{ /*num_circuits=*/2 }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Initialize the IVC with an arbitrary circuit - Builder circuit_0 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_0); + auto [circuit_0, vk_0] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit_0, vk_0); // Create another circuit and accumulate - Builder circuit_1 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_1); + auto [circuit_1, vk_1] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit_1, vk_1); const auto proof = ivc.prove(); @@ -611,15 +538,15 @@ TEST_F(ClientIVCTests, MsgpackProofFromBuffer) { ClientIVC ivc{ /*num_circuits=*/2 }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Initialize the IVC with an arbitrary circuit - Builder circuit_0 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_0); + auto [circuit_0, vk_0] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit_0, vk_0); // Create another circuit and accumulate - Builder circuit_1 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_1); + auto [circuit_1, vk_1] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit_1, vk_1); const auto proof = ivc.prove(); @@ -633,22 +560,22 @@ TEST_F(ClientIVCTests, MsgpackProofFromBuffer) }; /** - * @brief Check that a CIVC proof can be serialized and deserialized via msgpack and that attempting to deserialize a - * random buffer of bytes fails gracefully with a type error + * @brief Check that a CIVC proof can be serialized and deserialized via msgpack and that attempting to deserialize + * a random buffer of bytes fails gracefully with a type error */ TEST_F(ClientIVCTests, RandomProofBytes) { ClientIVC ivc{ /*num_circuits=*/2 }; - ClientIVCMockCircuitProducer circuit_producer; + PrivateFunctionExecutionMockCircuitProducer circuit_producer; // Initialize the IVC with an arbitrary circuit - Builder circuit_0 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_0); + auto [circuit_0, vk_0] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit_0, vk_0); // Create another circuit and accumulate - Builder circuit_1 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_1); + auto [circuit_1, vk_1] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit_1, vk_1); const auto proof = ivc.prove(); 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 cf54354bb081..64bccc7f03fc 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 @@ -39,9 +39,9 @@ TEST_F(ClientIVCIntegrationTests, BenchmarkCaseSimple) // Construct and accumulate a series of mocked private function execution circuits for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - Builder circuit = circuit_producer.create_next_circuit(ivc); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); - ivc.accumulate(circuit); + ivc.accumulate(circuit, vk); } EXPECT_TRUE(ivc.prove_and_verify()); @@ -62,43 +62,15 @@ TEST_F(ClientIVCIntegrationTests, ConsecutiveKernels) // Accumulate a series of mocked circuits (app, kernel, app, kernel) for (size_t idx = 0; idx < NUM_CIRCUITS - 2; ++idx) { - Builder circuit = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit, vk); } // Cap the IVC with two more kernels (say, a 'reset' and a 'tail') without intermittent apps - Builder reset_kernel = circuit_producer.create_next_circuit(ivc, /*force_is_kernel=*/true); - ivc.accumulate(reset_kernel); - Builder tail_kernel = circuit_producer.create_next_circuit(ivc, /*force_is_kernel=*/true); - ivc.accumulate(tail_kernel); - - EXPECT_TRUE(ivc.prove_and_verify()); -}; - -/** - * @brief Prove and verify accumulation of a set of mocked private function execution circuits with precomputed - * verification keys - * - */ -TEST_F(ClientIVCIntegrationTests, BenchmarkCasePrecomputedVKs) -{ - const size_t NUM_CIRCUITS = 6; - ClientIVC ivc{ NUM_CIRCUITS, { AZTEC_TRACE_STRUCTURE } }; - - // Precompute the verification keys for each circuit in the IVC - std::vector> precomputed_vks; - { - MockCircuitProducer circuit_producer; - precomputed_vks = circuit_producer.precompute_vks(NUM_CIRCUITS, ivc.trace_settings); - } - - MockCircuitProducer circuit_producer; - // Construct and accumulate a series of mocked private function execution circuits - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - Builder circuit = circuit_producer.create_next_circuit(ivc); - - ivc.accumulate(circuit, precomputed_vks[idx]); - } + auto [reset_kernel, reset_vk] = circuit_producer.create_next_circuit_and_vk(ivc, { .force_is_kernel = true }); + ivc.accumulate(reset_kernel, reset_vk); + auto [tail_kernel, tail_vk] = circuit_producer.create_next_circuit_and_vk(ivc, { .force_is_kernel = true }); + ivc.accumulate(tail_kernel, tail_vk); EXPECT_TRUE(ivc.prove_and_verify()); }; @@ -106,9 +78,12 @@ TEST_F(ClientIVCIntegrationTests, BenchmarkCasePrecomputedVKs) /** * @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 + * 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. * */ @@ -121,14 +96,14 @@ TEST_F(ClientIVCIntegrationTests, DatabusFailure) // Construct and accumulate a series of mocked private function execution circuits for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - Builder circuit = circuit_producer.create_next_circuit(ivc); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); // Tamper with the return data of the second app circuit before it is processed as input to the next kernel if (idx == 2) { circuit_producer.tamper_with_databus(); } - ivc.accumulate(circuit); + ivc.accumulate(circuit, vk); } EXPECT_FALSE(ivc.prove_and_verify()); 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 58715b9479ea..145fa7f8a86b 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/mock_circuit_producer.hpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/mock_circuit_producer.hpp @@ -87,12 +87,30 @@ class MockDatabusProducer { void tamper_with_app_return_data() { app_return_data.emplace_back(17); } }; +/** + * @brief Customises the production of mock circuits for Client IVC testing + * + */ +struct TestSettings { + // number of public inputs to manually add to circuits, by default this would be 0 because we use the + // MockDatabusProducer to test public inputs handling + size_t num_public_inputs = 0; + // force the next circuit to be a kernel in order to test the occurence of consecutive kernels (expected behaviour + // in real flows) + bool force_is_kernel = false; + // by default we will create more complex apps and kernel with various types of gates but in case we want to + // specifically test overflow behaviour or unstructured circuits we can manually construct simple circuits with a + // specified number of gates + size_t log2_num_gates = 0; +}; + /** * @brief Manage the construction of mock app/kernel circuits for the private function execution setting * @details Per the medium complexity benchmark spec, the first app circuit is size 2^19. Subsequent app and kernel * circuits are size 2^17. Circuits produced are alternatingly app and kernel. Mock databus data is passed between the * circuits in a manor conistent with the real architecture in order to facilitate testing of databus consistency - * checks. + * checks. Additionally, we allow for the creation of simpler circuits with public inputs set manually but also for + * testing consecutive kernels. These can be configured via TestSettings. */ class PrivateFunctionExecutionMockCircuitProducer { using ClientCircuit = ClientIVC::ClientCircuit; @@ -102,8 +120,8 @@ class PrivateFunctionExecutionMockCircuitProducer { size_t circuit_counter = 0; MockDatabusProducer mock_databus; - - bool large_first_app = true; // if true, first app is 2^19, else 2^17 + bool large_first_app = true; + bool is_kernel = false; // whether the next circuit is a kernel or not public: PrivateFunctionExecutionMockCircuitProducer(bool large_first_app = true) @@ -111,17 +129,56 @@ class PrivateFunctionExecutionMockCircuitProducer { {} /** - * @brief Create the next circuit (app/kernel) in a mocked private function execution stack + * @brief Precompute the verification key for the given circuit. + * */ - ClientCircuit create_next_circuit(ClientIVC& ivc, bool force_is_kernel = false) + static std::shared_ptr get_verification_key(ClientCircuit& builder_in, + TraceSettings& trace_settings) { - circuit_counter++; + // This is a workaround to ensure that the circuit is finalized before we create the verification key + // In practice, this should not be needed as the circuit will be finalized when it is accumulated into the IVC + // but this is a workaround for the test setup. + MegaCircuitBuilder_ builder{ builder_in }; + + // Deepcopy the opqueue to avoid modifying the original one when finalising the circuit + builder.op_queue = std::make_shared(*builder.op_queue); + std::shared_ptr proving_key = + std::make_shared(builder, trace_settings); + std::shared_ptr vk = std::make_shared(proving_key->get_precomputed()); + return vk; + } - // Assume only every second circuit is a kernel, unless force_is_kernel == true - bool is_kernel = (circuit_counter % 2 == 0) || force_is_kernel; + ClientCircuit create_simple_circuit(ClientIVC& ivc, size_t log2_num_gates, size_t num_public_inputs) + { + circuit_counter++; + is_kernel = (circuit_counter % 2 == 0); + ClientCircuit circuit{ ivc.goblin.op_queue, is_kernel }; + MockCircuits::construct_arithmetic_circuit(circuit, log2_num_gates, /* include_public_inputs= */ false); + if (num_public_inputs > 0) { + // Add some public inputs to the circuit + for (size_t i = 0; i < num_public_inputs; ++i) { + circuit.add_public_variable(13634816 + i); // arbitrary number + } + } + if (circuit.is_kernel) { + ivc.complete_kernel_circuit_logic(circuit); + } else { + stdlib::recursion::PairingPoints::add_default_to_public_inputs(circuit); + } + return circuit; + } + /** + * @brief Create a more realistic circuit (withv various custom gates and databus usage) that is also filled up to + * 2^17 or 2^19 if large. + * + */ + ClientCircuit create_next_circuit(ClientIVC& ivc, bool force_is_kernel = false) + { + circuit_counter++; + is_kernel = (circuit_counter % 2 == 0) || force_is_kernel; ClientCircuit circuit{ ivc.goblin.op_queue, is_kernel }; - if (is_kernel) { + if (circuit.is_kernel) { GoblinMockCircuits::construct_mock_folding_kernel(circuit); // construct mock base logic mock_databus.populate_kernel_databus(circuit); // populate databus inputs/outputs ivc.complete_kernel_circuit_logic(circuit); // complete with recursive verifiers etc @@ -134,95 +191,28 @@ class PrivateFunctionExecutionMockCircuitProducer { } /** - * @brief Tamper with databus data to facilitate failure testing - */ - void tamper_with_databus() { mock_databus.tamper_with_app_return_data(); } - - /** - * @brief Compute and return the verification keys for a mocked private function execution IVC - * @details For testing/benchmarking only. This method is robust at the cost of being extremely inefficient. It - * simply executes a full IVC for a given number of circuits and stores the verification keys along the way. (In - * practice these VKs will be known to a client prover in advance). - * - * @param num_circuits - * @param trace_structure Trace structuring must be known in advance because it effects the VKs - * @return set of num_circuits-many verification keys + * @brief Create the next circuit (app/kernel) in a mocked private function execution stack */ - auto precompute_vks(const size_t num_circuits, TraceSettings trace_settings) + std::pair> create_next_circuit_and_vk(ClientIVC& ivc, + TestSettings settings = {}) { - ClientIVC ivc{ num_circuits, - trace_settings }; // temporary IVC instance needed to produce the complete kernel circuits - std::vector> vks; - - for (size_t idx = 0; idx < num_circuits; ++idx) { - ClientCircuit circuit = create_next_circuit(ivc); // create the next circuit - ivc.accumulate(circuit); // accumulate the circuit - vks.emplace_back(ivc.honk_vk); // save the VK for the circuit + // If a specific number of gates is specified we create a simple circuit with only arithmetic gates to easily + // control the total number of gates. + if (settings.log2_num_gates != 0) { + ClientCircuit circuit = create_simple_circuit(ivc, settings.log2_num_gates, settings.num_public_inputs); + return { circuit, get_verification_key(circuit, ivc.trace_settings) }; } - circuit_counter = 0; // reset the internal circuit counter back to 0 - return vks; - } -}; + ClientCircuit circuit = create_next_circuit(ivc, settings.force_is_kernel); // construct the circuit -/** - * @brief A test utility for generating alternating mock app and kernel circuits and precomputing verification keys - * - */ -class ClientIVCMockCircuitProducer { - using ClientCircuit = ClientIVC::ClientCircuit; - - bool is_kernel = false; + return { circuit, get_verification_key(circuit, ivc.trace_settings) }; + } /** - * @brief Construct mock circuit with arithmetic gates and goblin ops - * @details Defaulted to add 2^16 gates (which will bump to next power of two with the addition of dummy gates). - * The size of the baseline circuit needs to be ~2x the number of gates appended to the kernel circuits via - * recursive verifications (currently ~60k) to ensure that the circuits being folded are equal in size. (This is - * only necessary if the structured trace is not in use). - * + * @brief Tamper with databus data to facilitate failure testing */ - static ClientCircuit create_mock_circuit(ClientIVC& ivc, size_t log2_num_gates = 16, bool is_kernel = false) - { - ClientCircuit circuit{ ivc.goblin.op_queue, is_kernel }; - MockCircuits::construct_arithmetic_circuit(circuit, log2_num_gates, /* include_public_inputs= */ false); - return circuit; - } - - public: - ClientCircuit create_next_circuit(ClientIVC& ivc, size_t log2_num_gates = 16, const size_t num_public_inputs = 0) - { - ClientCircuit circuit = create_mock_circuit(ivc, log2_num_gates, is_kernel); // construct mock base logic - while (circuit.num_public_inputs() < num_public_inputs) { - circuit.add_public_variable(13634816); // arbitrary number - } - if (is_kernel) { - ivc.complete_kernel_circuit_logic(circuit); // complete with recursive verifiers etc - } else { - stdlib::recursion::PairingPoints::add_default_to_public_inputs(circuit); - } - is_kernel = !is_kernel; // toggle is_kernel on/off alternatingly - - return circuit; - } - - auto precompute_vks(const size_t num_circuits, TraceSettings trace_settings, size_t log2_num_gates = 16) - { - ClientIVC ivc{ num_circuits, - trace_settings }; // temporary IVC instance needed to produce the complete kernel circuits - - std::vector> vks; - - for (size_t idx = 0; idx < num_circuits; ++idx) { - ClientCircuit circuit = create_next_circuit(ivc, log2_num_gates); // create the next circuit - ivc.accumulate(circuit); // accumulate the circuit - vks.emplace_back(ivc.honk_vk); // save the VK for the circuit - } - is_kernel = false; - - return vks; - } + void tamper_with_databus() { mock_databus.tamper_with_app_return_data(); } }; } // namespace 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 1100d47afdb4..f093a0a02996 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 @@ -30,9 +30,9 @@ TEST_F(MockKernelTest, PinFoldingKernelSizes) // Construct and accumulate a series of mocked private function execution circuits for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - Builder circuit = circuit_producer.create_next_circuit(ivc); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); - ivc.accumulate(circuit); + ivc.accumulate(circuit, vk); EXPECT_TRUE(circuit.blocks.has_overflow); // trace overflow mechanism should be triggered } diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/private_execution_steps.cpp b/barretenberg/cpp/src/barretenberg/client_ivc/private_execution_steps.cpp index 237f3a60ea55..1d0ef3fdfe2c 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/private_execution_steps.cpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/private_execution_steps.cpp @@ -148,8 +148,7 @@ std::shared_ptr PrivateExecutionSteps::accumulate() for (auto& vk : precomputed_vks) { if (vk == nullptr) { - info("DEPRECATED: No VK was provided for at least one client IVC step and it will be computed. This is " - "slower and insecure."); + info("DEPRECATED: Precomputed VKs expected for the given circuits."); break; } } diff --git a/barretenberg/cpp/src/barretenberg/client_ivc/test_bench_shared.hpp b/barretenberg/cpp/src/barretenberg/client_ivc/test_bench_shared.hpp index 37eb5b516ce1..43d84305f9a5 100644 --- a/barretenberg/cpp/src/barretenberg/client_ivc/test_bench_shared.hpp +++ b/barretenberg/cpp/src/barretenberg/client_ivc/test_bench_shared.hpp @@ -38,7 +38,7 @@ bool verify_ivc(ClientIVC::Proof& proof, ClientIVC& ivc) void perform_ivc_accumulation_rounds(size_t NUM_CIRCUITS, ClientIVC& ivc, auto& precomputed_vks, - const bool& mock_vk = false, + const bool large_first_app = true) { BB_ASSERT_EQ(precomputed_vks.size(), NUM_CIRCUITS, "There should be a precomputed VK for each circuit"); @@ -52,20 +52,24 @@ void perform_ivc_accumulation_rounds(size_t NUM_CIRCUITS, circuit = circuit_producer.create_next_circuit(ivc); } - ivc.accumulate(circuit, precomputed_vks[circuit_idx], mock_vk); + ivc.accumulate(circuit, precomputed_vks[circuit_idx]); } } -std::vector> mock_vks(const size_t num_circuits) +std::vector> mock_vks(const size_t num_circuits, + const bool large_first_app = true) { + // Create an app and kernel vk with metadata set + PrivateFunctionExecutionMockCircuitProducer circuit_producer{ large_first_app }; + ClientIVC ivc{ 2, { AZTEC_TRACE_STRUCTURE } }; + auto [app_circuit, app_vk] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(app_circuit, app_vk); + auto [kernel_circuit, kernel_vk] = circuit_producer.create_next_circuit_and_vk(ivc); std::vector> vkeys; for (size_t idx = 0; idx < num_circuits; ++idx) { - auto key = std::make_shared(); - for (auto& commitment : key->get_all()) { - commitment = MegaFlavor::Commitment::random_element(); - } + auto key = idx % 2 == 0 ? app_vk : kernel_vk; // alternate between app and kernel vks vkeys.push_back(key); } 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 b4fe5f15f649..e323da50d752 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 @@ -21,6 +21,7 @@ class IvcRecursionConstraintTest : public ::testing::Test { public: using Builder = MegaCircuitBuilder; using Flavor = MegaFlavor; + using VerificationKey = MegaFlavor::VerificationKey; using FF = Flavor::FF; using VerifierInputs = ClientIVC::VerifierInputs; using QUEUE_TYPE = ClientIVC::QUEUE_TYPE; @@ -41,6 +42,23 @@ class IvcRecursionConstraintTest : public ::testing::Test { return circuit; } + static std::shared_ptr get_verification_key(Builder& builder_in, + const TraceSettings& trace_settings) + { + // This is a workaround to ensure that the circuit is finalized before we create the verification key + // In practice, this should not be needed as the circuit will be finalized when it is accumulated into the IVC + // but this is a workaround for the test setup. + // Create a copy of the input circuit + MegaCircuitBuilder_ builder{ builder_in }; + + // Deepcopy the opqueue to avoid modifying the original one + builder.op_queue = std::make_shared(*builder.op_queue); + std::shared_ptr proving_key = + std::make_shared(builder, trace_settings); + std::shared_ptr vk = std::make_shared(proving_key->get_precomputed()); + return vk; + } + static UltraCircuitBuilder create_inner_circuit(size_t log_num_gates = 10) { using InnerPairingPoints = bb::stdlib::recursion::PairingPoints; @@ -230,19 +248,20 @@ TEST_F(IvcRecursionConstraintTest, AccumulateTwo) auto ivc = std::make_shared(/*num_circuits=*/2, trace_settings); // construct a mock app_circuit - Builder app_circuit = construct_mock_app_circuit(ivc); - + auto app_circuit = construct_mock_app_circuit(ivc); + auto app_vk = get_verification_key(app_circuit, trace_settings); // Complete instance and generate an oink proof - ivc->accumulate(app_circuit); + ivc->accumulate(app_circuit, app_vk); // Construct kernel consisting only of the kernel completion logic AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); const ProgramMetadata metadata{ ivc }; - Builder kernel = acir_format::create_circuit(program, metadata); + auto kernel = acir_format::create_circuit(program, metadata); + auto kernel_vk = construct_kernel_vk_from_acir_program(program, trace_settings); EXPECT_TRUE(CircuitChecker::check(kernel)); - ivc->accumulate(kernel); + ivc->accumulate(kernel, kernel_vk); EXPECT_TRUE(ivc->prove_and_verify()); } @@ -258,25 +277,26 @@ TEST_F(IvcRecursionConstraintTest, AccumulateFour) // construct a mock app_circuit Builder app_circuit_0 = construct_mock_app_circuit(ivc); - ivc->accumulate(app_circuit_0); + ivc->accumulate(app_circuit_0, get_verification_key(app_circuit_0, trace_settings)); const ProgramMetadata metadata{ ivc }; // Construct kernel_0; consists of a single oink recursive verification for app (plus databus/merge logic) AcirProgram program_0 = construct_mock_kernel_program(ivc->verification_queue); Builder kernel_0 = acir_format::create_circuit(program_0, metadata); - ivc->accumulate(kernel_0); + ivc->accumulate(kernel_0, construct_kernel_vk_from_acir_program(program_0, trace_settings)); // construct a mock app_circuit Builder app_circuit_1 = construct_mock_app_circuit(ivc); - ivc->accumulate(app_circuit_1); + ivc->accumulate(app_circuit_1, get_verification_key(app_circuit_1, trace_settings)); - // Construct kernel_1; consists of two PG recursive verifications for kernel_0 and app_1 (plus databus/merge logic) + // Construct kernel_1; consists of two PG recursive verifications for kernel_0 and app_1 (plus databus/merge + // logic AcirProgram program_1 = construct_mock_kernel_program(ivc->verification_queue); Builder kernel_1 = acir_format::create_circuit(program_1, metadata); EXPECT_TRUE(CircuitChecker::check(kernel_1)); - ivc->accumulate(kernel_1); + ivc->accumulate(kernel_1, construct_kernel_vk_from_acir_program(program_1, trace_settings)); EXPECT_TRUE(ivc->prove_and_verify()); } @@ -293,14 +313,14 @@ TEST_F(IvcRecursionConstraintTest, GenerateInitKernelVKFromConstraints) // Construct and accumulate mock app_circuit Builder app_circuit = construct_mock_app_circuit(ivc); - ivc->accumulate(app_circuit); + ivc->accumulate(app_circuit, get_verification_key(app_circuit, trace_settings)); // Construct and accumulate kernel consisting only of the kernel completion logic AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); const ProgramMetadata metadata{ ivc }; Builder kernel = acir_format::create_circuit(program, metadata); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); expected_kernel_vk = ivc->verification_queue.back().honk_vk; } @@ -335,12 +355,12 @@ TEST_F(IvcRecursionConstraintTest, GenerateResetKernelVKFromConstraints) // Construct and accumulate mock app_circuit Builder app_circuit = construct_mock_app_circuit(ivc); - ivc->accumulate(app_circuit); + ivc->accumulate(app_circuit, get_verification_key(app_circuit, trace_settings)); { // Construct and accumulate a mock INIT kernel (oink recursion for app accumulation) AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); Builder kernel = acir_format::create_circuit(program, metadata); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); } { // Construct and accumulate a mock RESET/TAIL kernel (PG recursion for kernel accumulation) @@ -348,7 +368,7 @@ TEST_F(IvcRecursionConstraintTest, GenerateResetKernelVKFromConstraints) EXPECT_TRUE(ivc->verification_queue[0].type == bb::ClientIVC::QUEUE_TYPE::PG); AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); Builder kernel = acir_format::create_circuit(program, metadata); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); } expected_kernel_vk = ivc->verification_queue.back().honk_vk; @@ -385,18 +405,18 @@ TEST_F(IvcRecursionConstraintTest, GenerateInnerKernelVKFromConstraints) { // Construct and accumulate mock app_circuit Builder app_circuit = construct_mock_app_circuit(ivc); - ivc->accumulate(app_circuit); + ivc->accumulate(app_circuit, get_verification_key(app_circuit, trace_settings)); } { // Construct and accumulate a mock INIT kernel (oink recursion for app accumulation) AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); Builder kernel = acir_format::create_circuit(program, metadata); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); } { // Construct and accumulate a second mock app_circuit Builder app_circuit = construct_mock_app_circuit(ivc); - ivc->accumulate(app_circuit); + ivc->accumulate(app_circuit, get_verification_key(app_circuit, trace_settings)); } { // Construct and accumulate a mock INNER kernel (PG recursion for kernel accumulation) @@ -405,7 +425,7 @@ TEST_F(IvcRecursionConstraintTest, GenerateInnerKernelVKFromConstraints) EXPECT_TRUE(ivc->verification_queue[1].type == bb::ClientIVC::QUEUE_TYPE::PG); AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); Builder kernel = acir_format::create_circuit(program, metadata); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); } expected_kernel_vk = ivc->verification_queue.back().honk_vk; @@ -443,14 +463,14 @@ TEST_F(IvcRecursionConstraintTest, GenerateHidingKernelVKFromConstraints) { // Construct and accumulate mock app_circuit Builder app_circuit = construct_mock_app_circuit(ivc); - ivc->accumulate(app_circuit); + ivc->accumulate(app_circuit, get_verification_key(app_circuit, trace_settings)); } { // Construct and accumulate a mock INIT kernel (oink recursion for app accumulation) AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); Builder kernel = acir_format::create_circuit(program, metadata); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); } { // Construct and accumulate a mock TAIL kernel (PG recursion for kernel accumulation) @@ -458,7 +478,7 @@ TEST_F(IvcRecursionConstraintTest, GenerateHidingKernelVKFromConstraints) EXPECT_TRUE(ivc->verification_queue[0].type == bb::ClientIVC::QUEUE_TYPE::PG); AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); Builder kernel = acir_format::create_circuit(program, metadata); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); } { @@ -503,7 +523,7 @@ TEST_F(IvcRecursionConstraintTest, RecursiveVerifierAppCircuitTest) Builder app_circuit = construct_mock_UH_recursion_app_circuit(ivc, /*tamper_vk=*/false); // Complete instance and generate an oink proof - ivc->accumulate(app_circuit); + ivc->accumulate(app_circuit, get_verification_key(app_circuit, trace_settings)); // Construct kernel consisting only of the kernel completion logic AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); @@ -512,7 +532,7 @@ TEST_F(IvcRecursionConstraintTest, RecursiveVerifierAppCircuitTest) Builder kernel = acir_format::create_circuit(program, metadata); EXPECT_TRUE(CircuitChecker::check(kernel)); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); EXPECT_TRUE(ivc->prove_and_verify()); } @@ -530,7 +550,7 @@ TEST_F(IvcRecursionConstraintTest, BadRecursiveVerifierAppCircuitTest) Builder app_circuit = construct_mock_UH_recursion_app_circuit(ivc, /*tamper_vk=*/true); // Complete instance and generate an oink proof - ivc->accumulate(app_circuit); + ivc->accumulate(app_circuit, get_verification_key(app_circuit, trace_settings)); // Construct kernel consisting only of the kernel completion logic AcirProgram program = construct_mock_kernel_program(ivc->verification_queue); @@ -539,7 +559,7 @@ TEST_F(IvcRecursionConstraintTest, BadRecursiveVerifierAppCircuitTest) Builder kernel = acir_format::create_circuit(program, metadata); EXPECT_TRUE(CircuitChecker::check(kernel)); - ivc->accumulate(kernel); + ivc->accumulate(kernel, construct_kernel_vk_from_acir_program(program, trace_settings)); // We expect the CIVC proof to fail due to the app with a failed UH recursive verification EXPECT_FALSE(ivc->prove_and_verify()); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/client_ivc_verifier/client_ivc_recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/client_ivc_verifier/client_ivc_recursive_verifier.test.cpp index ebbf7b48ce34..e62d208430a7 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/client_ivc_verifier/client_ivc_recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/client_ivc_verifier/client_ivc_recursive_verifier.test.cpp @@ -38,8 +38,8 @@ class ClientIVCRecursionTests : public testing::Test { MockCircuitProducer circuit_producer; for (size_t idx = 0; idx < ivc.get_num_circuits(); ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit); + auto [circuit, vk] = circuit_producer.create_next_circuit_and_vk(ivc); + ivc.accumulate(circuit, vk); } return { ivc.prove(), ivc.get_vk() }; diff --git a/yarn-project/ivc-integration/src/prove_wasm.ts b/yarn-project/ivc-integration/src/prove_wasm.ts index 2352ec1226d0..1d06e3b4f032 100644 --- a/yarn-project/ivc-integration/src/prove_wasm.ts +++ b/yarn-project/ivc-integration/src/prove_wasm.ts @@ -13,6 +13,7 @@ function base64ToUint8Array(base64: string): Uint8Array { export async function proveClientIVC( bytecodes: string[], witnessStack: Uint8Array[], + vks: string[], threads?: number, ): Promise { const { AztecClientBackend } = await import('@aztec/bb.js'); @@ -21,7 +22,10 @@ export async function proveClientIVC( { threads: threads || Math.min(os.cpus().length, 16), logger: logger.info }, ); try { - const [proof] = await backend.prove(witnessStack.map((arr: Uint8Array) => ungzip(arr))); + const [proof] = await backend.prove( + witnessStack.map((arr: Uint8Array) => ungzip(arr)), + vks.map(hex => new Uint8Array(Buffer.from(hex, 'hex'))), + ); return new ClientIvcProof(Buffer.from(proof)); } finally { await backend.destroy(); @@ -31,6 +35,7 @@ export async function proveClientIVC( export async function proveThenVerifyAztecClient( bytecodes: string[], witnessStack: Uint8Array[], + vks: string[], threads?: number, ): Promise { const { AztecClientBackend } = await import('@aztec/bb.js'); @@ -40,7 +45,10 @@ export async function proveThenVerifyAztecClient( ); try { // These are optional - easier not to pass them. - const [proof, vk] = await backend.prove(witnessStack.map((arr: Uint8Array) => ungzip(arr))); + const [proof, vk] = await backend.prove( + witnessStack.map((arr: Uint8Array) => ungzip(arr)), + vks.map(hex => new Uint8Array(Buffer.from(hex, 'hex'))), + ); const verified = await backend.verify(proof, vk); return verified; } finally { diff --git a/yarn-project/ivc-integration/src/serve.ts b/yarn-project/ivc-integration/src/serve.ts index 0483b4f70bd8..3af5d33db97d 100644 --- a/yarn-project/ivc-integration/src/serve.ts +++ b/yarn-project/ivc-integration/src/serve.ts @@ -88,9 +88,9 @@ document.addEventListener('DOMContentLoaded', function () { // eslint-disable-next-line @typescript-eslint/no-misused-promises button.addEventListener('click', async () => { logger.info(`generating circuit and witness...`); - const [bytecodes, witnessStack] = await generate3FunctionTestingIVCStack(); + const [bytecodes, witnessStack, _publicInputs, precomputedVks] = await generate3FunctionTestingIVCStack(); logger.info(`done. proving and verifying...`); - const verified = await proveThenVerifyAztecClient(bytecodes, witnessStack); + const verified = await proveThenVerifyAztecClient(bytecodes, witnessStack, precomputedVks); logger.info(`verified? ${verified}`); }); document.body.appendChild(button); diff --git a/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts b/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts index f8d70711bf2d..d91353b6a1a9 100644 --- a/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts +++ b/yarn-project/ivc-integration/src/wasm_client_ivc_integration.test.ts @@ -22,6 +22,7 @@ import { MockPrivateKernelResetCircuit, MockPrivateKernelResetVk, MockPrivateKernelTailCircuit, + MockPrivateKernelTailVk, generate3FunctionTestingIVCStack, getVkAsFields, witnessGenCreatorAppMockCircuit, @@ -57,7 +58,7 @@ describe('Client IVC Integration', () => { const clientIVCWorkingDirectory = await getWorkingDirectory('bb-client-ivc-integration-'); const tasks = [ proveClientIVCNative(bbBinaryPath, clientIVCWorkingDirectory, witnessStack, bytecodes, vks, logger), - proveClientIVCWasm(bytecodes, witnessStack), + proveClientIVCWasm(bytecodes, witnessStack, vks), ]; const [_, wasmProof] = await Promise.all(tasks); @@ -155,7 +156,15 @@ describe('Client IVC Integration', () => { tailWitnessGenResult.witness, ]; - const verifyResult = await proveThenVerifyAztecClient(bytecodes, witnessStack); + const precomputedVks = [ + MockAppCreatorVk.keyAsBytes, + MockPrivateKernelInitVk.keyAsBytes, + MockAppReaderVk.keyAsBytes, + MockPrivateKernelInnerVk.keyAsBytes, + MockPrivateKernelResetVk.keyAsBytes, + MockPrivateKernelTailVk.keyAsBytes, + ]; + const verifyResult = await proveThenVerifyAztecClient(bytecodes, witnessStack, precomputedVks); logger.info(`generated then verified proof. result: ${verifyResult}`); expect(verifyResult).toEqual(true);