From 4dedf776a66c6a519e5779d7932c381abb6a6f36 Mon Sep 17 00:00:00 2001 From: Cody Date: Tue, 17 Dec 2024 21:14:15 +0000 Subject: [PATCH 1/9] cp and reduce a bit --- barretenberg/cpp/src/CMakeLists.txt | 4 +- .../ultra_vanilla_client_ivc/CMakeLists.txt | 1 + .../mock_circuit_producer.hpp | 163 +++++++ .../ultra_vanilla_client_ivc.cpp | 413 ++++++++++++++++++ .../ultra_vanilla_client_ivc.hpp | 197 +++++++++ .../ultra_vanilla_client_ivc.test.cpp | 270 ++++++++++++ 6 files changed, 1047 insertions(+), 1 deletion(-) create mode 100644 barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt create mode 100644 barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp create mode 100644 barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp create mode 100644 barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp create mode 100644 barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp diff --git a/barretenberg/cpp/src/CMakeLists.txt b/barretenberg/cpp/src/CMakeLists.txt index 93f4bb4eb45..1a9590e4d67 100644 --- a/barretenberg/cpp/src/CMakeLists.txt +++ b/barretenberg/cpp/src/CMakeLists.txt @@ -61,10 +61,10 @@ if (ENABLE_PIC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_subdirectory(barretenberg/world_state_napi) endif() -add_subdirectory(barretenberg/client_ivc) add_subdirectory(barretenberg/bb) add_subdirectory(barretenberg/boomerang_value_detection) add_subdirectory(barretenberg/circuit_checker) +add_subdirectory(barretenberg/client_ivc) add_subdirectory(barretenberg/commitment_schemes) add_subdirectory(barretenberg/commitment_schemes_recursion) add_subdirectory(barretenberg/common) @@ -87,6 +87,7 @@ add_subdirectory(barretenberg/relations) add_subdirectory(barretenberg/serialize) add_subdirectory(barretenberg/solidity_helpers) add_subdirectory(barretenberg/srs) +add_subdirectory(barretenberg/ultra_vanilla_client_ivc) add_subdirectory(barretenberg/stdlib) add_subdirectory(barretenberg/stdlib_circuit_builders) add_subdirectory(barretenberg/sumcheck) @@ -139,6 +140,7 @@ set(BARRETENBERG_TARGET_OBJECTS $ $ $ + $ $ $ $ diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt new file mode 100644 index 00000000000..26ce7caca10 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt @@ -0,0 +1 @@ +barretenberg_module(ultra_vanilla_client_ivc stdlib_protogalaxy_verifier stdlib_goblin_verifier) \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp new file mode 100644 index 00000000000..c1d6abaf6c9 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp @@ -0,0 +1,163 @@ + +#include "barretenberg/common/op_count.hpp" +#include "barretenberg/goblin/mock_circuits.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" +#include "barretenberg/ultra_honk/ultra_verifier.hpp" +#include "barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp" + +using namespace bb; + +namespace { + +/** + * @brief Test utility for coordinating passing of databus data between mocked private function execution circuits + * @details Facilitates testing of the databus consistency checks that establish the correct passing of databus data + * between circuits. Generates arbitrary return data for each app/kernel. Sets the kernel calldata and + * secondary_calldata based respectively on the previous kernel return data and app return data. + */ +class MockDatabusProducer { + private: + using ClientCircuit = UltraVanillaClientIVC::ClientCircuit; + using Flavor = MegaFlavor; + using FF = Flavor::FF; + using BusDataArray = std::vector; + + static constexpr size_t BUS_ARRAY_SIZE = 3; // arbitrary length of mock bus inputs + BusDataArray app_return_data; + BusDataArray kernel_return_data; + + FF dummy_return_val = 1; // use simple return val for easier test debugging + + BusDataArray generate_random_bus_array() + { + BusDataArray result; + for (size_t i = 0; i < BUS_ARRAY_SIZE; ++i) { + result.emplace_back(dummy_return_val); + } + dummy_return_val += 1; + return result; + } + + public: + /** + * @brief Update the app return data and populate it in the app circuit + */ + void populate_app_databus(ClientCircuit& circuit) + { + app_return_data = generate_random_bus_array(); + for (auto& val : app_return_data) { + circuit.add_public_return_data(circuit.add_variable(val)); + } + }; + + /** + * @brief Populate the calldata and secondary calldata in the kernel from respectively the previous kernel and app + * return data. Update and populate the return data for the present kernel. + */ + void populate_kernel_databus(ClientCircuit& circuit) + { + // Populate calldata from previous kernel return data (if it exists) + for (auto& val : kernel_return_data) { + circuit.add_public_calldata(circuit.add_variable(val)); + } + // Populate secondary_calldata from app return data (if it exists), then clear the app return data + for (auto& val : app_return_data) { + circuit.add_public_secondary_calldata(circuit.add_variable(val)); + } + app_return_data.clear(); + + // Mock the return data for the present kernel circuit + kernel_return_data = generate_random_bus_array(); + for (auto& val : kernel_return_data) { + circuit.add_public_return_data(circuit.add_variable(val)); + } + }; + + /** + * @brief Add an arbitrary value to the app return data. This leads to a descrepency between the values used by the + * app itself and the secondary_calldata values in the kernel that will be set based on these tampered values. + */ + void tamper_with_app_return_data() { app_return_data.emplace_back(17); } +}; + +/** + * @brief Manage the construction of mock app/kernel circuits for the private function execution setting + * @details Per the medium complexity benchmark spec, the first app circuit is size 2^19. Subsequent app and kernel + * circuits are size 2^17. Circuits produced are alternatingly app and kernel. Mock databus data is passed between the + * circuits in a manor conistent with the real architecture in order to facilitate testing of databus consistency + * checks. + */ +class PrivateFunctionExecutionMockCircuitProducer { + using ClientCircuit = UltraVanillaClientIVC::ClientCircuit; + using Flavor = MegaFlavor; + using VerificationKey = Flavor::VerificationKey; + + size_t circuit_counter = 0; + + MockDatabusProducer mock_databus; + + bool large_first_app = true; // if true, first app is 2^19, else 2^17 + + public: + PrivateFunctionExecutionMockCircuitProducer(bool large_first_app = true) + : large_first_app(large_first_app) + {} + + /** + * @brief Create the next circuit (app/kernel) in a mocked private function execution stack + */ + ClientCircuit create_next_circuit(UltraVanillaClientIVC& ivc, bool force_is_kernel = false) + { + circuit_counter++; + + // Assume only every second circuit is a kernel, unless force_is_kernel == true + bool is_kernel = (circuit_counter % 2 == 0) || force_is_kernel; + + ClientCircuit circuit{ ivc.goblin.op_queue }; + if (is_kernel) { + GoblinMockCircuits::construct_mock_folding_kernel(circuit); // construct mock base logic + mock_databus.populate_kernel_databus(circuit); // populate databus inputs/outputs + ivc.complete_kernel_circuit_logic(circuit); // complete with recursive verifiers etc + } else { + bool use_large_circuit = large_first_app && (circuit_counter == 1); // first circuit is size 2^19 + GoblinMockCircuits::construct_mock_app_circuit(circuit, use_large_circuit); // construct mock app + mock_databus.populate_app_databus(circuit); // populate databus outputs + } + return circuit; + } + + /** + * @brief Tamper with databus data to facilitate failure testing + */ + void tamper_with_databus() { mock_databus.tamper_with_app_return_data(); } + + /** + * @brief Compute and return the verification keys for a mocked private function execution IVC + * @details For testing/benchmarking only. This method is robust at the cost of being extremely inefficient. It + * simply executes a full IVC for a given number of circuits and stores the verification keys along the way. (In + * practice these VKs will be known to a client prover in advance). + * + * @param num_circuits + * @param trace_structure Trace structuring must be known in advance because it effects the VKs + * @return set of num_circuits-many verification keys + */ + auto precompute_verification_keys(const size_t num_circuits, TraceSettings trace_settings) + { + UltraVanillaClientIVC ivc{ + trace_settings + }; // temporary IVC instance needed to produce the complete kernel circuits + + std::vector> vkeys; + + for (size_t idx = 0; idx < num_circuits; ++idx) { + ClientCircuit circuit = create_next_circuit(ivc); // create the next circuit + ivc.accumulate(circuit); // accumulate the circuit + vkeys.emplace_back(ivc.honk_vk); // save the VK for the circuit + } + circuit_counter = 0; // reset the internal circuit counter back to 0 + + return vkeys; + } +}; + +} // namespace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp new file mode 100644 index 00000000000..e9511c96847 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp @@ -0,0 +1,413 @@ +#include "barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp" +#include "barretenberg/ultra_honk/oink_prover.hpp" + +namespace bb { + +/** + * @brief Instantiate a stdlib verification queue for use in the kernel completion logic + * @details Construct a stdlib proof/verification_key for each entry in the native verification queue. By default, both + * are constructed from their counterpart in the native queue. Alternatively, Stdlib verification keys can be provided + * directly as input to this method. (The later option is used, for example, when constructing recursive verifiers based + * on the verification key witnesses from an acir recursion constraint. This option is not provided for proofs since + * valid proof witnesses are in general not known at the time of acir constraint generation). + * + * @param circuit + */ +void UltraVanillaClientIVC::instantiate_stdlib_verification_queue( + ClientCircuit& circuit, const std::vector>& input_keys) +{ + bool vkeys_provided = !input_keys.empty(); + if (vkeys_provided && verification_queue.size() != input_keys.size()) { + info("Warning: Incorrect number of verification keys provided in stdlib verification queue instantiation."); + ASSERT(false); + } + + size_t key_idx = 0; + for (auto& [proof, vkey, type] : verification_queue) { + // Construct stdlib proof directly from the internal native queue data + auto stdlib_proof = bb::convert_native_proof_to_stdlib(&circuit, proof); + + // Use the provided stdlib vkey if present, otherwise construct one from the internal native queue + auto stdlib_vkey = + vkeys_provided ? input_keys[key_idx++] : std::make_shared(&circuit, vkey); + + stdlib_verification_queue.push_back({ stdlib_proof, stdlib_vkey, type }); + } + verification_queue.clear(); // the native data is not needed beyond this point +} + +/** + * @brief Populate the provided circuit with constraints for (1) recursive verification of the provided accumulation + * proof and (2) the associated databus commitment consistency checks. + * @details The recursive verifier will be either Oink or Protogalaxy depending on the specified proof type. In either + * case, the verifier accumulator is updated in place via the verification algorithm. Databus commitment consistency + * checks are performed on the witness commitments and public inputs extracted from the proof by the verifier. + * + * @param circuit The circuit to which the constraints are appended + * @param proof A stdlib proof to be recursively verified (either oink or PG) + * @param vkey The stdlib verfication key associated with the proof + * @param type The type of the proof (equivalently, the type of the verifier) + */ +void UltraVanillaClientIVC::perform_recursive_verification_and_databus_consistency_checks( + ClientCircuit& circuit, + const StdlibProof& proof, + const std::shared_ptr& vkey, + const QUEUE_TYPE type) +{ + // Store the decider vk for the incoming circuit; its data is used in the databus consistency checks below + std::shared_ptr decider_vk; + + switch (type) { + case QUEUE_TYPE::PG: { + // Construct stdlib verifier accumulator from the native counterpart computed on a previous round + auto stdlib_verifier_accum = std::make_shared(&circuit, verifier_accumulator); + + // Perform folding recursive verification to update the verifier accumulator + FoldingRecursiveVerifier verifier{ &circuit, stdlib_verifier_accum, { vkey } }; + auto verifier_accum = verifier.verify_folding_proof(proof); + + // Extract native verifier accumulator from the stdlib accum for use on the next round + verifier_accumulator = std::make_shared(verifier_accum->get_value()); + + decider_vk = verifier.keys_to_fold[1]; // decider vk for the incoming circuit + + break; + } + case QUEUE_TYPE::OINK: { + // Construct an incomplete stdlib verifier accumulator from the corresponding stdlib verification key + auto verifier_accum = std::make_shared(&circuit, vkey); + + // Perform oink recursive verification to complete the initial verifier accumulator + OinkRecursiveVerifier oink{ &circuit, verifier_accum }; + oink.verify_proof(proof); + verifier_accum->is_accumulator = true; // indicate to PG that it should not run oink + + // Extract native verifier accumulator from the stdlib accum for use on the next round + verifier_accumulator = std::make_shared(verifier_accum->get_value()); + // Initialize the gate challenges to zero for use in first round of folding + verifier_accumulator->gate_challenges = std::vector(CONST_PG_LOG_N, 0); + + decider_vk = verifier_accum; // decider vk for the incoming circuit + + break; + } + } + + // Set the return data commitment to be propagated on the public inputs of the present kernel and peform consistency + // checks between the calldata commitments and the return data commitments contained within the public inputs + bus_depot.set_return_data_to_be_propagated_and_perform_consistency_checks( + decider_vk->witness_commitments.return_data, + decider_vk->witness_commitments.calldata, + decider_vk->witness_commitments.secondary_calldata, + decider_vk->public_inputs, + decider_vk->verification_key->databus_propagation_data); +} + +/** + * @brief Perform recursive merge verification for each merge proof in the queue + * + * @param circuit + */ +void UltraVanillaClientIVC::process_recursive_merge_verification_queue(ClientCircuit& circuit) +{ + // Recusively verify all merge proofs in queue + for (auto& proof : merge_verification_queue) { + goblin.verify_merge(circuit, proof); + } + merge_verification_queue.clear(); +} + +/** + * @brief Append logic to complete a kernel circuit + * @details A kernel circuit may contain some combination of PG recursive verification, merge recursive + * verification, and databus commitment consistency checks. This method appends this logic to a provided kernel + * circuit. + * + * @param circuit + */ +void UltraVanillaClientIVC::complete_kernel_circuit_logic(ClientCircuit& circuit) +{ + circuit.databus_propagation_data.is_kernel = true; + + // Instantiate stdlib verifier inputs from their native counterparts + if (stdlib_verification_queue.empty()) { + instantiate_stdlib_verification_queue(circuit); + } + + // Peform recursive verification and databus consistency checks for each entry in the verification queue + for (auto& [proof, vkey, type] : stdlib_verification_queue) { + perform_recursive_verification_and_databus_consistency_checks(circuit, proof, vkey, type); + } + stdlib_verification_queue.clear(); + + // Propagate return data commitments via the public inputs for use in databus consistency checks + bus_depot.propagate_return_data_commitments(circuit); + + // Perform recursive merge verification for every merge proof in the queue + process_recursive_merge_verification_queue(circuit); +} + +/** + * @brief Execute prover work for accumulation + * @details Construct an proving key for the provided circuit. If this is the first step in the IVC, simply initialize + * the folding accumulator. Otherwise, execute the PG prover to fold the proving key into the accumulator and produce a + * folding proof. Also execute the merge protocol to produce a merge proof. + * + * @param circuit + * @param precomputed_vk + */ +void UltraVanillaClientIVC::accumulate(ClientCircuit& circuit, + const bool _one_circuit, + const std::shared_ptr& precomputed_vk, + const bool mock_vk) +{ + // Construct merge proof for the present circuit and add to merge verification queue + MergeProof merge_proof = goblin.prove_merge(circuit); + merge_verification_queue.emplace_back(merge_proof); + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1069): Do proper aggregation with merge recursive + // verifier. + circuit.add_pairing_point_accumulator(stdlib::recursion::init_default_agg_obj_indices(circuit)); + + // Construct the proving key for circuit + std::shared_ptr proving_key = std::make_shared(circuit, trace_settings); + + // The commitment key is initialised with the number of points determined by the trace_settings' dyadic size. If a + // circuit overflows past the dyadic size the commitment key will not have enough points so we need to increase it + if (proving_key->proving_key.circuit_size > trace_settings.dyadic_size()) { + bn254_commitment_key = std::make_shared>(proving_key->proving_key.circuit_size); + goblin.commitment_key = bn254_commitment_key; + } + proving_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 + honk_vk = precomputed_vk ? precomputed_vk : std::make_shared(proving_key->proving_key); + if (mock_vk) { + honk_vk->set_metadata(proving_key->proving_key); + vinfo("set honk vk metadata"); + } + + if (_one_circuit) { + one_circuit = _one_circuit; + MegaProver prover{ proving_key }; + vinfo("computing mega proof..."); + mega_proof = prover.prove(); + vinfo("mega proof computed"); + + proving_key->is_accumulator = true; // indicate to PG that it should not run oink on this key + // Initialize the gate challenges to zero for use in first round of folding + proving_key->gate_challenges = std::vector(CONST_PG_LOG_N, 0); + + fold_output.accumulator = proving_key; + } else if (!initialized) { + // If this is the first circuit in the IVC, use oink to complete the decider proving key and generate an oink + // proof + MegaOinkProver oink_prover{ proving_key }; + vinfo("computing oink proof..."); + oink_prover.prove(); + vinfo("oink proof constructed"); + proving_key->is_accumulator = true; // indicate to PG that it should not run oink on this key + // Initialize the gate challenges to zero for use in first round of folding + proving_key->gate_challenges = std::vector(CONST_PG_LOG_N, 0); + + fold_output.accumulator = proving_key; // initialize the prover accum with the completed key + + // Add oink proof and corresponding verification key to the verification queue + verification_queue.push_back( + bb::UltraVanillaClientIVC::VerifierInputs{ oink_prover.transcript->proof_data, honk_vk, QUEUE_TYPE::OINK }); + + initialized = true; + } else { // Otherwise, fold the new key into the accumulator + vinfo("computing folding proof"); + FoldingProver folding_prover({ fold_output.accumulator, proving_key }, trace_usage_tracker); + fold_output = folding_prover.prove(); + vinfo("constructed folding proof"); + + // Add fold proof and corresponding verification key to the verification queue + verification_queue.push_back( + bb::UltraVanillaClientIVC::VerifierInputs{ fold_output.proof, honk_vk, QUEUE_TYPE::PG }); + } +} + +/** + * @brief Construct the hiding circuit, which recursively verifies the last folding proof and decider proof, and + * then produce a proof of the circuit's correctness with MegaHonk. + * + * @details The aim of this intermediate stage is to reduce the cost of producing a zero-knowledge ClientIVCProof. + * @return HonkProof - a Mega proof + */ +HonkProof UltraVanillaClientIVC::construct_and_prove_hiding_circuit() +{ + trace_usage_tracker.print(); // print minimum structured sizes for each block + ASSERT(verification_queue.size() == 1); + ASSERT(merge_verification_queue.size() == 1); // ensure only a single merge proof remains in the queue + + FoldProof& fold_proof = verification_queue[0].proof; + HonkProof decider_proof = decider_prove(); + + fold_output.accumulator = nullptr; + + ClientCircuit builder{ goblin.op_queue }; + // The last circuit being folded is a kernel circuit whose public inputs need to be passed to the base rollup + // circuit. So, these have to be preserved as public inputs to the hiding circuit (and, subsequently, as public + // inputs to the tube circuit) which are intermediate stages. + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1048): link these properly, likely insecure + auto num_public_inputs = static_cast(static_cast(fold_proof[PUBLIC_INPUTS_SIZE_INDEX])); + num_public_inputs -= bb::PAIRING_POINT_ACCUMULATOR_SIZE; // exclude aggregation object + num_public_inputs -= bb::PROPAGATED_DATABUS_COMMITMENTS_SIZE; // exclude propagated databus commitments + for (size_t i = 0; i < num_public_inputs; i++) { + size_t offset = HONK_PROOF_PUBLIC_INPUT_OFFSET; + builder.add_public_variable(fold_proof[i + offset]); + } + + process_recursive_merge_verification_queue(builder); + + // Construct stdlib accumulator, decider vkey and folding proof + auto stdlib_verifier_accumulator = + std::make_shared(&builder, verifier_accumulator); + + auto stdlib_decider_vk = + std::make_shared(&builder, verification_queue[0].honk_verification_key); + + auto stdlib_proof = bb::convert_native_proof_to_stdlib(&builder, fold_proof); + + // Perform recursive folding verification of the last folding proof + FoldingRecursiveVerifier folding_verifier{ &builder, stdlib_verifier_accumulator, { stdlib_decider_vk } }; + auto recursive_verifier_accumulator = folding_verifier.verify_folding_proof(stdlib_proof); + verification_queue.clear(); + + // Perform recursive decider verification + DeciderRecursiveVerifier decider{ &builder, recursive_verifier_accumulator }; + decider.verify_proof(decider_proof); + + builder.add_pairing_point_accumulator(stdlib::recursion::init_default_agg_obj_indices(builder)); + + // Construct the last merge proof for the present circuit and add to merge verification queue + MergeProof merge_proof = goblin.prove_merge(builder); + merge_verification_queue.emplace_back(merge_proof); + + auto decider_pk = std::make_shared(builder, TraceSettings(), bn254_commitment_key); + honk_vk = std::make_shared(decider_pk->proving_key); + MegaProver prover(decider_pk); + + HonkProof proof = prover.construct_proof(); + + return proof; +} + +/** + * @brief Construct a proof for the IVC, which, if verified, fully establishes its correctness + * + * @return Proof + */ +UltraVanillaClientIVC::Proof UltraVanillaClientIVC::prove() +{ + if (!one_circuit) { + mega_proof = construct_and_prove_hiding_circuit(); + ASSERT(merge_verification_queue.size() == 1); // ensure only a single merge proof remains in the queue + } + + MergeProof& merge_proof = merge_verification_queue[0]; + return { mega_proof, goblin.prove(merge_proof) }; +}; + +bool UltraVanillaClientIVC::verify(const Proof& proof, const VerificationKey& vk) +{ + + // Verify the hiding circuit proof + MegaVerifier verifer{ vk.mega }; + bool mega_verified = verifer.verify_proof(proof.mega_proof); + vinfo("Mega verified: ", mega_verified); + // Goblin verification (final merge, eccvm, translator) + GoblinVerifier goblin_verifier{ vk.eccvm, vk.translator }; + bool goblin_verified = goblin_verifier.verify(proof.goblin_proof); + vinfo("Goblin verified: ", goblin_verified); + return goblin_verified && mega_verified; +} + +/** + * @brief Verify a full proof of the IVC + * + * @param proof + * @return bool + */ +bool UltraVanillaClientIVC::verify(const Proof& proof) +{ + auto eccvm_vk = std::make_shared(goblin.get_eccvm_proving_key()); + auto translator_vk = std::make_shared(goblin.get_translator_proving_key()); + return verify(proof, { honk_vk, eccvm_vk, translator_vk }); +} + +/** + * @brief Internal method for constructing a decider proof + * + * @return HonkProof + */ +HonkProof UltraVanillaClientIVC::decider_prove() const +{ + vinfo("prove decider..."); + fold_output.accumulator->proving_key.commitment_key = bn254_commitment_key; + MegaDeciderProver decider_prover(fold_output.accumulator); + vinfo("finished decider proving."); + return decider_prover.construct_proof(); +} + +/** + * @brief Construct and verify a proof for the IVC + * @note Use of this method only makes sense when the prover and verifier are the same entity, e.g. in + * development/testing. + * + */ +bool UltraVanillaClientIVC::prove_and_verify() +{ + auto start = std::chrono::steady_clock::now(); + const auto proof = prove(); + auto end = std::chrono::steady_clock::now(); + auto diff = std::chrono::duration_cast(end - start); + vinfo("time to call UltraVanillaClientIVC::prove: ", diff.count(), " ms."); + + start = end; + const bool verified = verify(proof); + end = std::chrono::steady_clock::now(); + + diff = std::chrono::duration_cast(end - start); + vinfo("time to verify UltraVanillaClientIVC proof: ", diff.count(), " ms."); + + return verified; +} + +/** + * @brief Given a set of circuits, compute the verification keys that will be required by the IVC scheme + * @details The verification keys computed here are in general not the same as the verification keys for the + * raw input circuits because recursive verifier circuits (merge and/or folding) may be appended to the incoming + * circuits as part accumulation. + * @note This method exists for convenience and is not not meant to be used in practice for IVC. Given a set of + * circuits, it could be run once and for all to compute then save the required VKs. It also provides a convenient + * (albeit innefficient) way of separating out the cost of computing VKs from a benchmark. + * + * @param circuits A copy of the circuits to be accumulated (passing by reference would alter the original circuits) + * @return std::vector> + */ +std::vector> UltraVanillaClientIVC::precompute_folding_verification_keys( + std::vector circuits) +{ + std::vector> vkeys; + + for (auto& circuit : circuits) { + accumulate(circuit); + vkeys.emplace_back(honk_vk); + } + + // Reset the scheme so it can be reused for actual accumulation, maintaining the trace structure setting as is + TraceSettings settings = trace_settings; + *this = UltraVanillaClientIVC(); + this->trace_settings = settings; + + return vkeys; +} + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp new file mode 100644 index 00000000000..3ef09507292 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp @@ -0,0 +1,197 @@ +#pragma once + +#include "barretenberg/goblin/goblin.hpp" +#include "barretenberg/goblin/mock_circuits.hpp" +#include "barretenberg/plonk_honk_shared/execution_trace/execution_trace_usage_tracker.hpp" +#include "barretenberg/protogalaxy/protogalaxy_prover.hpp" +#include "barretenberg/protogalaxy/protogalaxy_verifier.hpp" +#include "barretenberg/stdlib/honk_verifier/decider_recursive_verifier.hpp" +#include "barretenberg/stdlib/primitives/databus/databus.hpp" +#include "barretenberg/ultra_honk/decider_keys.hpp" +#include "barretenberg/ultra_honk/decider_prover.hpp" +#include "barretenberg/ultra_honk/decider_verifier.hpp" +#include + +namespace bb { + +/** + * @brief The IVC scheme used by the aztec client for private function execution + * @details Combines Protogalaxy with Goblin to accumulate one circuit at a time with efficient EC group + * operations. It is assumed that the circuits being accumulated correspond alternatingly to an app and a kernel, as is + * the case in Aztec. Two recursive folding verifiers are appended to each kernel (except the first one) to verify the + * folding of a previous kernel and an app/function circuit. Due to this structure it is enforced that the total number + * of circuits being accumulated is even. + * + */ +class UltraVanillaClientIVC { + + public: + using Flavor = MegaFlavor; + using MegaVerificationKey = Flavor::VerificationKey; + using FF = Flavor::FF; + using FoldProof = std::vector; + using MergeProof = std::vector; + using DeciderProvingKey = DeciderProvingKey_; + using DeciderVerificationKey = DeciderVerificationKey_; + using ClientCircuit = MegaCircuitBuilder; // can only be Mega + using DeciderProver = DeciderProver_; + using DeciderVerifier = DeciderVerifier_; + using DeciderProvingKeys = DeciderProvingKeys_; + using FoldingProver = ProtogalaxyProver_; + using DeciderVerificationKeys = DeciderVerificationKeys_; + using FoldingVerifier = ProtogalaxyVerifier_; + using ECCVMVerificationKey = bb::ECCVMFlavor::VerificationKey; + using TranslatorVerificationKey = bb::TranslatorFlavor::VerificationKey; + using MegaProver = UltraProver_; + using MegaVerifier = UltraVerifier_; + + using RecursiveFlavor = MegaRecursiveFlavor_; + using RecursiveDeciderVerificationKeys = + bb::stdlib::recursion::honk::RecursiveDeciderVerificationKeys_; + using RecursiveDeciderVerificationKey = RecursiveDeciderVerificationKeys::DeciderVK; + using RecursiveVerificationKey = RecursiveFlavor::VerificationKey; + using FoldingRecursiveVerifier = + bb::stdlib::recursion::honk::ProtogalaxyRecursiveVerifier_; + using OinkRecursiveVerifier = stdlib::recursion::honk::OinkRecursiveVerifier_; + using DeciderRecursiveVerifier = stdlib::recursion::honk::DeciderRecursiveVerifier_; + + using DataBusDepot = stdlib::DataBusDepot; + + /** + * @brief A full proof for the IVC scheme containing a Mega proof showing correctness of the hiding circuit (which + * recursive verified the last folding and decider proof) and a Goblin proof (translator VM, ECCVM and last merge + * proof). + * + * @details This proof will be zero-knowledge. + */ + struct Proof { + HonkProof mega_proof; + GoblinProof goblin_proof; + + size_t size() const { return mega_proof.size() + goblin_proof.size(); } + + MSGPACK_FIELDS(mega_proof, goblin_proof); + }; + + struct VerificationKey { + std::shared_ptr mega; + std::shared_ptr eccvm; + std::shared_ptr translator; + + MSGPACK_FIELDS(mega, eccvm, translator); + }; + + enum class QUEUE_TYPE { OINK, PG }; // for specifying type of proof in the verification queue + + // An entry in the native verification queue + struct VerifierInputs { + std::vector proof; // oink or PG + std::shared_ptr honk_verification_key; + QUEUE_TYPE type; + }; + using VerificationQueue = std::vector; + + // An entry in the stdlib verification queue + struct StdlibVerifierInputs { + StdlibProof proof; // oink or PG + std::shared_ptr honk_verification_key; + QUEUE_TYPE type; + }; + + using StdlibVerificationQueue = std::vector; + + // Utility for tracking the max size of each block across the full IVC + ExecutionTraceUsageTracker trace_usage_tracker; + + private: + using ProverFoldOutput = FoldingResult; + + public: + ProverFoldOutput fold_output; // prover accumulator and fold proof + HonkProof mega_proof; + + std::shared_ptr verifier_accumulator; // verifier accumulator + std::shared_ptr honk_vk; // honk vk to be completed and folded into the accumulator + + // Set of tuples {proof, verification_key, type} to be recursively verified + VerificationQueue verification_queue; + // Set of tuples {stdlib_proof, stdlib_verification_key, type} corresponding to the native verification queue + StdlibVerificationQueue stdlib_verification_queue; + // Set of merge proofs to be recursively verified + std::vector merge_verification_queue; + + // Management of linking databus commitments between circuits in the IVC + DataBusDepot bus_depot; + + // Settings related to the use of fixed block sizes for each gate in the execution trace + TraceSettings trace_settings; + + std::shared_ptr bn254_commitment_key; + + GoblinProver goblin; + + // We dynamically detect whether the input stack consists of one circuit, in which case we do not construct the + // hiding circuit and instead simply prove the single input circuit. + bool one_circuit = false; + + bool initialized = false; // Is the IVC accumulator initialized + + UltraVanillaClientIVC(TraceSettings trace_settings = {}) + : trace_usage_tracker(trace_settings) + , trace_settings(trace_settings) + , bn254_commitment_key(trace_settings.structure.has_value() + ? std::make_shared>(trace_settings.dyadic_size()) + : nullptr) + , goblin(bn254_commitment_key) + {} + + void instantiate_stdlib_verification_queue( + ClientCircuit& circuit, const std::vector>& input_keys = {}); + + void perform_recursive_verification_and_databus_consistency_checks( + ClientCircuit& circuit, + const StdlibProof& proof, + const std::shared_ptr& vkey, + const QUEUE_TYPE type); + + void process_recursive_merge_verification_queue(ClientCircuit& circuit); + + // Complete the logic of a kernel circuit (e.g. PG/merge recursive verification, databus consistency checks) + void complete_kernel_circuit_logic(ClientCircuit& circuit); + + /** + * @brief Perform prover work for accumulation (e.g. PG folding, merge proving) + * + * @param circuit The incoming statement + * @param precomputed_vk The verification key of the incoming statement OR a mocked key whose metadata needs to be + * 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 shoudl have its metadata set. + */ + void accumulate(ClientCircuit& circuit, + const bool _one_circuit = false, + const std::shared_ptr& precomputed_vk = nullptr, + const bool mock_vk = false); + + Proof prove(); + + HonkProof construct_and_prove_hiding_circuit(); + + static bool verify(const Proof& proof, const VerificationKey& vk); + + bool verify(const Proof& proof); + + bool prove_and_verify(); + + HonkProof decider_prove() const; + + std::vector> precompute_folding_verification_keys( + std::vector circuits); + + VerificationKey get_vk() const + { + return { honk_vk, + std::make_shared(goblin.get_eccvm_proving_key()), + std::make_shared(goblin.get_translator_proving_key()) }; + } +}; +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp new file mode 100644 index 00000000000..12cc0aa89f0 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp @@ -0,0 +1,270 @@ +#include "barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp" +#include "barretenberg/goblin/goblin.hpp" +#include "barretenberg/goblin/mock_circuits.hpp" +#include "barretenberg/protogalaxy/folding_test_utils.hpp" +#include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" +#include + +using namespace bb; + +class UltraVanillaClientIVCTests : public ::testing::Test { + protected: + static void SetUpTestSuite() + { + srs::init_crs_factory("../srs_db/ignition"); + srs::init_grumpkin_crs_factory("../srs_db/grumpkin"); + } + + using Flavor = UltraVanillaClientIVC::Flavor; + using FF = typename Flavor::FF; + using VerificationKey = Flavor::VerificationKey; + using Builder = UltraVanillaClientIVC::ClientCircuit; + using DeciderProvingKey = UltraVanillaClientIVC::DeciderProvingKey; + using DeciderVerificationKey = UltraVanillaClientIVC::DeciderVerificationKey; + using FoldProof = UltraVanillaClientIVC::FoldProof; + using DeciderProver = UltraVanillaClientIVC::DeciderProver; + using DeciderVerifier = UltraVanillaClientIVC::DeciderVerifier; + using DeciderProvingKeys = DeciderProvingKeys_; + using FoldingProver = ProtogalaxyProver_; + using DeciderVerificationKeys = DeciderVerificationKeys_; + using FoldingVerifier = ProtogalaxyVerifier_; + + /** + * @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). + * + */ + static Builder create_mock_circuit(UltraVanillaClientIVC& ivc, size_t log2_num_gates = 16) + { + Builder circuit{ ivc.goblin.op_queue }; + MockCircuits::construct_arithmetic_circuit(circuit, log2_num_gates); + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/911): We require goblin ops to be added to the + // function circuit because we cannot support zero commtiments. While the builder handles this at + // finalisation stage via the add_gates_to_ensure_all_polys_are_non_zero function for other MegaHonk + // circuits (where we don't explicitly need to add goblin ops), in UltraVanillaClientIVC merge proving happens + // prior to folding where the absense of goblin ecc ops will result in zero commitments. + MockCircuits::construct_goblin_ecc_op_circuit(circuit); + return circuit; + } + + /** + * @brief A test utility for generating alternating mock app and kernel circuits and precomputing verification keys + * + */ + class MockCircuitProducer { + using ClientCircuit = UltraVanillaClientIVC::ClientCircuit; + + bool is_kernel = false; + + public: + ClientCircuit create_next_circuit(UltraVanillaClientIVC& ivc, size_t log2_num_gates = 16) + { + ClientCircuit circuit{ ivc.goblin.op_queue }; + circuit = create_mock_circuit(ivc, log2_num_gates); // construct mock base logic + if (is_kernel) { + ivc.complete_kernel_circuit_logic(circuit); // complete with recursive verifiers etc + } + is_kernel = !is_kernel; // toggle is_kernel on/off alternatingly + + return circuit; + } + + auto precompute_verification_keys(const size_t num_circuits, + TraceSettings trace_settings, + size_t log2_num_gates = 16) + { + UltraVanillaClientIVC ivc{ + trace_settings + }; // temporary IVC instance needed to produce the complete kernel circuits + + std::vector> vkeys; + + for (size_t idx = 0; idx < num_circuits; ++idx) { + ClientCircuit circuit = create_next_circuit(ivc, log2_num_gates); // create the next circuit + ivc.accumulate(circuit); // accumulate the circuit + vkeys.emplace_back(ivc.honk_vk); // save the VK for the circuit + } + is_kernel = false; + + return vkeys; + } + }; + + /** + * @brief Tamper with a proof by finding the first non-zero value and incrementing it by 1 + * + */ + static void tamper_with_proof(FoldProof& proof) + { + for (auto& val : proof) { + if (val > 0) { + val += 1; + break; + } + } + } +}; + +/** + * @brief A simple-as-possible test demonstrating IVC for two mock circuits + * @details When accumulating only two circuits, only a single round of folding is performed thus no recursive + * verfication occurs. + * + */ +TEST_F(UltraVanillaClientIVCTests, Basic) +{ + UltraVanillaClientIVC ivc; + + MockCircuitProducer circuit_producer; + + // Initialize the IVC with an arbitrary circuit + Builder circuit_0 = circuit_producer.create_next_circuit(ivc); + ivc.accumulate(circuit_0); + + // Create another circuit and accumulate + Builder circuit_1 = circuit_producer.create_next_circuit(ivc); + ivc.accumulate(circuit_1); + + EXPECT_TRUE(ivc.prove_and_verify()); +}; + +/** + * @brief A simple test demonstrating IVC for four mock circuits, which is slightly more than minimal. + * @details When accumulating only four circuits, we execute all the functionality of a full UltraVanillaClientIVC run. + * + */ +TEST_F(UltraVanillaClientIVCTests, BasicFour) +{ + UltraVanillaClientIVC ivc; + + MockCircuitProducer circuit_producer; + for (size_t idx = 0; idx < 4; ++idx) { + Builder circuit = circuit_producer.create_next_circuit(ivc); + ivc.accumulate(circuit); + } + + EXPECT_TRUE(ivc.prove_and_verify()); +}; + +/** + * @brief Check that the IVC fails if an intermediate fold proof is invalid + * @details When accumulating 4 circuits, there are 3 fold proofs to verify (the first two are recursively verfied and + * the 3rd is verified as part of the IVC proof). Check that if any of one of these proofs is invalid, the IVC will + * fail. + * + */ +TEST_F(UltraVanillaClientIVCTests, BadProofFailure) +{ + // Confirm that the IVC verifies if nothing is tampered with + { + UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; + + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits + size_t NUM_CIRCUITS = 4; + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); + ivc.accumulate(circuit); + } + EXPECT_TRUE(ivc.prove_and_verify()); + } + + // The IVC throws an exception if the FIRST fold proof is tampered with + { + UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; + + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits + size_t NUM_CIRCUITS = 4; + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + if (idx == 3) { // At idx = 3, we've tampered with the one of the folding proofs so create the recursive + // folding verifier will throw an error. + EXPECT_ANY_THROW(circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5)); + break; + } + auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); + ivc.accumulate(circuit); + + if (idx == 2) { + EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation + tamper_with_proof(ivc.verification_queue[0].proof); // tamper with first proof + } + } + } + + // The IVC fails if the SECOND fold proof is tampered with + { + UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; + + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits + size_t NUM_CIRCUITS = 4; + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + if (idx == 3) { // At idx = 3, we've tampered with the one of the folding proofs so create the recursive + // folding verifier will throw an error. + EXPECT_ANY_THROW(circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5)); + break; + } + auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); + ivc.accumulate(circuit); + + if (idx == 2) { + EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation + tamper_with_proof(ivc.verification_queue[1].proof); // tamper with second proof + } + } + } + + // The IVC fails if the 3rd/FINAL fold proof is tampered with + { + UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; + + MockCircuitProducer circuit_producer; + + // Construct and accumulate a set of mocked private function execution circuits + size_t NUM_CIRCUITS = 4; + for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { + auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); + ivc.accumulate(circuit); + } + + // Only a single proof should be present in the queue when verification of the IVC is performed + EXPECT_EQ(ivc.verification_queue.size(), 1); + tamper_with_proof(ivc.verification_queue[0].proof); // tamper with the final fold proof + + EXPECT_ANY_THROW(ivc.prove_and_verify()); + } + + EXPECT_TRUE(true); +}; + +/** + * @brief Prove and verify accumulation of an arbitrary set of circuits using precomputed verification keys + * + */ +TEST_F(UltraVanillaClientIVCTests, PrecomputedVerificationKeys) +{ + UltraVanillaClientIVC ivc; + + size_t NUM_CIRCUITS = 4; + + MockCircuitProducer circuit_producer; + + 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) { + auto circuit = circuit_producer.create_next_circuit(ivc); + ivc.accumulate(circuit, /*one_circuit=*/false, precomputed_vks[idx]); + } + + EXPECT_TRUE(ivc.prove_and_verify()); +}; From 0fb8614dd88ca2f2999238d9a083b8ec58545833 Mon Sep 17 00:00:00 2001 From: Cody Date: Wed, 18 Dec 2024 17:19:07 +0000 Subject: [PATCH 2/9] WIP setting up IVC class --- .../commitment_schemes/commitment_key.hpp | 2 + .../ultra_vanilla_client_ivc.cpp | 304 +----------------- .../ultra_vanilla_client_ivc.hpp | 164 ++-------- 3 files changed, 38 insertions(+), 432 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.hpp index dca262c5edc..1d107e82296 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.hpp @@ -55,6 +55,7 @@ template class CommitmentKey { scalar_multiplication::pippenger_runtime_state pippenger_runtime_state; std::shared_ptr> crs_factory; std::shared_ptr> srs; + size_t dyadic_size; CommitmentKey() = delete; @@ -69,6 +70,7 @@ template class CommitmentKey { : pippenger_runtime_state(get_num_needed_srs_points(num_points)) , crs_factory(srs::get_crs_factory()) , srs(crs_factory->get_prover_crs(get_num_needed_srs_points(num_points))) + , dyadic_size(get_num_needed_srs_points(num_points)) {} // Note: This constructor is to be used only by Plonk; For Honk the srs lives in the CommitmentKey diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp index e9511c96847..3e1cc88edb5 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp @@ -3,150 +3,6 @@ namespace bb { -/** - * @brief Instantiate a stdlib verification queue for use in the kernel completion logic - * @details Construct a stdlib proof/verification_key for each entry in the native verification queue. By default, both - * are constructed from their counterpart in the native queue. Alternatively, Stdlib verification keys can be provided - * directly as input to this method. (The later option is used, for example, when constructing recursive verifiers based - * on the verification key witnesses from an acir recursion constraint. This option is not provided for proofs since - * valid proof witnesses are in general not known at the time of acir constraint generation). - * - * @param circuit - */ -void UltraVanillaClientIVC::instantiate_stdlib_verification_queue( - ClientCircuit& circuit, const std::vector>& input_keys) -{ - bool vkeys_provided = !input_keys.empty(); - if (vkeys_provided && verification_queue.size() != input_keys.size()) { - info("Warning: Incorrect number of verification keys provided in stdlib verification queue instantiation."); - ASSERT(false); - } - - size_t key_idx = 0; - for (auto& [proof, vkey, type] : verification_queue) { - // Construct stdlib proof directly from the internal native queue data - auto stdlib_proof = bb::convert_native_proof_to_stdlib(&circuit, proof); - - // Use the provided stdlib vkey if present, otherwise construct one from the internal native queue - auto stdlib_vkey = - vkeys_provided ? input_keys[key_idx++] : std::make_shared(&circuit, vkey); - - stdlib_verification_queue.push_back({ stdlib_proof, stdlib_vkey, type }); - } - verification_queue.clear(); // the native data is not needed beyond this point -} - -/** - * @brief Populate the provided circuit with constraints for (1) recursive verification of the provided accumulation - * proof and (2) the associated databus commitment consistency checks. - * @details The recursive verifier will be either Oink or Protogalaxy depending on the specified proof type. In either - * case, the verifier accumulator is updated in place via the verification algorithm. Databus commitment consistency - * checks are performed on the witness commitments and public inputs extracted from the proof by the verifier. - * - * @param circuit The circuit to which the constraints are appended - * @param proof A stdlib proof to be recursively verified (either oink or PG) - * @param vkey The stdlib verfication key associated with the proof - * @param type The type of the proof (equivalently, the type of the verifier) - */ -void UltraVanillaClientIVC::perform_recursive_verification_and_databus_consistency_checks( - ClientCircuit& circuit, - const StdlibProof& proof, - const std::shared_ptr& vkey, - const QUEUE_TYPE type) -{ - // Store the decider vk for the incoming circuit; its data is used in the databus consistency checks below - std::shared_ptr decider_vk; - - switch (type) { - case QUEUE_TYPE::PG: { - // Construct stdlib verifier accumulator from the native counterpart computed on a previous round - auto stdlib_verifier_accum = std::make_shared(&circuit, verifier_accumulator); - - // Perform folding recursive verification to update the verifier accumulator - FoldingRecursiveVerifier verifier{ &circuit, stdlib_verifier_accum, { vkey } }; - auto verifier_accum = verifier.verify_folding_proof(proof); - - // Extract native verifier accumulator from the stdlib accum for use on the next round - verifier_accumulator = std::make_shared(verifier_accum->get_value()); - - decider_vk = verifier.keys_to_fold[1]; // decider vk for the incoming circuit - - break; - } - case QUEUE_TYPE::OINK: { - // Construct an incomplete stdlib verifier accumulator from the corresponding stdlib verification key - auto verifier_accum = std::make_shared(&circuit, vkey); - - // Perform oink recursive verification to complete the initial verifier accumulator - OinkRecursiveVerifier oink{ &circuit, verifier_accum }; - oink.verify_proof(proof); - verifier_accum->is_accumulator = true; // indicate to PG that it should not run oink - - // Extract native verifier accumulator from the stdlib accum for use on the next round - verifier_accumulator = std::make_shared(verifier_accum->get_value()); - // Initialize the gate challenges to zero for use in first round of folding - verifier_accumulator->gate_challenges = std::vector(CONST_PG_LOG_N, 0); - - decider_vk = verifier_accum; // decider vk for the incoming circuit - - break; - } - } - - // Set the return data commitment to be propagated on the public inputs of the present kernel and peform consistency - // checks between the calldata commitments and the return data commitments contained within the public inputs - bus_depot.set_return_data_to_be_propagated_and_perform_consistency_checks( - decider_vk->witness_commitments.return_data, - decider_vk->witness_commitments.calldata, - decider_vk->witness_commitments.secondary_calldata, - decider_vk->public_inputs, - decider_vk->verification_key->databus_propagation_data); -} - -/** - * @brief Perform recursive merge verification for each merge proof in the queue - * - * @param circuit - */ -void UltraVanillaClientIVC::process_recursive_merge_verification_queue(ClientCircuit& circuit) -{ - // Recusively verify all merge proofs in queue - for (auto& proof : merge_verification_queue) { - goblin.verify_merge(circuit, proof); - } - merge_verification_queue.clear(); -} - -/** - * @brief Append logic to complete a kernel circuit - * @details A kernel circuit may contain some combination of PG recursive verification, merge recursive - * verification, and databus commitment consistency checks. This method appends this logic to a provided kernel - * circuit. - * - * @param circuit - */ -void UltraVanillaClientIVC::complete_kernel_circuit_logic(ClientCircuit& circuit) -{ - circuit.databus_propagation_data.is_kernel = true; - - // Instantiate stdlib verifier inputs from their native counterparts - if (stdlib_verification_queue.empty()) { - instantiate_stdlib_verification_queue(circuit); - } - - // Peform recursive verification and databus consistency checks for each entry in the verification queue - for (auto& [proof, vkey, type] : stdlib_verification_queue) { - perform_recursive_verification_and_databus_consistency_checks(circuit, proof, vkey, type); - } - stdlib_verification_queue.clear(); - - // Propagate return data commitments via the public inputs for use in databus consistency checks - bus_depot.propagate_return_data_commitments(circuit); - - // Perform recursive merge verification for every merge proof in the queue - process_recursive_merge_verification_queue(circuit); -} - /** * @brief Execute prover work for accumulation * @details Construct an proving key for the provided circuit. If this is the first step in the IVC, simply initialize @@ -156,54 +12,28 @@ void UltraVanillaClientIVC::complete_kernel_circuit_logic(ClientCircuit& circuit * @param circuit * @param precomputed_vk */ -void UltraVanillaClientIVC::accumulate(ClientCircuit& circuit, - const bool _one_circuit, - const std::shared_ptr& precomputed_vk, - const bool mock_vk) +void UltraVanillaClientIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk) { - // Construct merge proof for the present circuit and add to merge verification queue - MergeProof merge_proof = goblin.prove_merge(circuit); - merge_verification_queue.emplace_back(merge_proof); - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1069): Do proper aggregation with merge recursive // verifier. circuit.add_pairing_point_accumulator(stdlib::recursion::init_default_agg_obj_indices(circuit)); // Construct the proving key for circuit - std::shared_ptr proving_key = std::make_shared(circuit, trace_settings); + std::shared_ptr proving_key = std::make_shared(circuit); // The commitment key is initialised with the number of points determined by the trace_settings' dyadic size. If a // circuit overflows past the dyadic size the commitment key will not have enough points so we need to increase it - if (proving_key->proving_key.circuit_size > trace_settings.dyadic_size()) { + if (proving_key->proving_key.circuit_size > bn254_commitment_key->dyadic_size) { bn254_commitment_key = std::make_shared>(proving_key->proving_key.circuit_size); - goblin.commitment_key = bn254_commitment_key; } proving_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 - honk_vk = precomputed_vk ? precomputed_vk : std::make_shared(proving_key->proving_key); - if (mock_vk) { - honk_vk->set_metadata(proving_key->proving_key); - vinfo("set honk vk metadata"); - } + vk = precomputed_vk ? precomputed_vk : std::make_shared(proving_key->proving_key); - if (_one_circuit) { - one_circuit = _one_circuit; - MegaProver prover{ proving_key }; - vinfo("computing mega proof..."); - mega_proof = prover.prove(); - vinfo("mega proof computed"); - - proving_key->is_accumulator = true; // indicate to PG that it should not run oink on this key - // Initialize the gate challenges to zero for use in first round of folding - proving_key->gate_challenges = std::vector(CONST_PG_LOG_N, 0); - - fold_output.accumulator = proving_key; - } else if (!initialized) { + if (!initialized) { // If this is the first circuit in the IVC, use oink to complete the decider proving key and generate an oink // proof MegaOinkProver oink_prover{ proving_key }; @@ -222,10 +52,10 @@ void UltraVanillaClientIVC::accumulate(ClientCircuit& circuit, initialized = true; } else { // Otherwise, fold the new key into the accumulator - vinfo("computing folding proof"); - FoldingProver folding_prover({ fold_output.accumulator, proving_key }, trace_usage_tracker); - fold_output = folding_prover.prove(); - vinfo("constructed folding proof"); + vinfo("computing proof"); + Prover folding_prover({ fold_output.accumulator, proving_key }, trace_usage_tracker); + prover_output = prover.prove(); + vinfo("constructed proof"); // Add fold proof and corresponding verification key to the verification queue verification_queue.push_back( @@ -233,72 +63,6 @@ void UltraVanillaClientIVC::accumulate(ClientCircuit& circuit, } } -/** - * @brief Construct the hiding circuit, which recursively verifies the last folding proof and decider proof, and - * then produce a proof of the circuit's correctness with MegaHonk. - * - * @details The aim of this intermediate stage is to reduce the cost of producing a zero-knowledge ClientIVCProof. - * @return HonkProof - a Mega proof - */ -HonkProof UltraVanillaClientIVC::construct_and_prove_hiding_circuit() -{ - trace_usage_tracker.print(); // print minimum structured sizes for each block - ASSERT(verification_queue.size() == 1); - ASSERT(merge_verification_queue.size() == 1); // ensure only a single merge proof remains in the queue - - FoldProof& fold_proof = verification_queue[0].proof; - HonkProof decider_proof = decider_prove(); - - fold_output.accumulator = nullptr; - - ClientCircuit builder{ goblin.op_queue }; - // The last circuit being folded is a kernel circuit whose public inputs need to be passed to the base rollup - // circuit. So, these have to be preserved as public inputs to the hiding circuit (and, subsequently, as public - // inputs to the tube circuit) which are intermediate stages. - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1048): link these properly, likely insecure - auto num_public_inputs = static_cast(static_cast(fold_proof[PUBLIC_INPUTS_SIZE_INDEX])); - num_public_inputs -= bb::PAIRING_POINT_ACCUMULATOR_SIZE; // exclude aggregation object - num_public_inputs -= bb::PROPAGATED_DATABUS_COMMITMENTS_SIZE; // exclude propagated databus commitments - for (size_t i = 0; i < num_public_inputs; i++) { - size_t offset = HONK_PROOF_PUBLIC_INPUT_OFFSET; - builder.add_public_variable(fold_proof[i + offset]); - } - - process_recursive_merge_verification_queue(builder); - - // Construct stdlib accumulator, decider vkey and folding proof - auto stdlib_verifier_accumulator = - std::make_shared(&builder, verifier_accumulator); - - auto stdlib_decider_vk = - std::make_shared(&builder, verification_queue[0].honk_verification_key); - - auto stdlib_proof = bb::convert_native_proof_to_stdlib(&builder, fold_proof); - - // Perform recursive folding verification of the last folding proof - FoldingRecursiveVerifier folding_verifier{ &builder, stdlib_verifier_accumulator, { stdlib_decider_vk } }; - auto recursive_verifier_accumulator = folding_verifier.verify_folding_proof(stdlib_proof); - verification_queue.clear(); - - // Perform recursive decider verification - DeciderRecursiveVerifier decider{ &builder, recursive_verifier_accumulator }; - decider.verify_proof(decider_proof); - - builder.add_pairing_point_accumulator(stdlib::recursion::init_default_agg_obj_indices(builder)); - - // Construct the last merge proof for the present circuit and add to merge verification queue - MergeProof merge_proof = goblin.prove_merge(builder); - merge_verification_queue.emplace_back(merge_proof); - - auto decider_pk = std::make_shared(builder, TraceSettings(), bn254_commitment_key); - honk_vk = std::make_shared(decider_pk->proving_key); - MegaProver prover(decider_pk); - - HonkProof proof = prover.construct_proof(); - - return proof; -} - /** * @brief Construct a proof for the IVC, which, if verified, fully establishes its correctness * @@ -307,12 +71,12 @@ HonkProof UltraVanillaClientIVC::construct_and_prove_hiding_circuit() UltraVanillaClientIVC::Proof UltraVanillaClientIVC::prove() { if (!one_circuit) { - mega_proof = construct_and_prove_hiding_circuit(); + previous_proof = construct_and_prove_hiding_circuit(); ASSERT(merge_verification_queue.size() == 1); // ensure only a single merge proof remains in the queue } MergeProof& merge_proof = merge_verification_queue[0]; - return { mega_proof, goblin.prove(merge_proof) }; + return { previous_proof, goblin.prove(merge_proof) }; }; bool UltraVanillaClientIVC::verify(const Proof& proof, const VerificationKey& vk) @@ -320,7 +84,7 @@ bool UltraVanillaClientIVC::verify(const Proof& proof, const VerificationKey& vk // Verify the hiding circuit proof MegaVerifier verifer{ vk.mega }; - bool mega_verified = verifer.verify_proof(proof.mega_proof); + bool mega_verified = verifer.verify_proof(proof.previous_proof); vinfo("Mega verified: ", mega_verified); // Goblin verification (final merge, eccvm, translator) GoblinVerifier goblin_verifier{ vk.eccvm, vk.translator }; @@ -342,20 +106,6 @@ bool UltraVanillaClientIVC::verify(const Proof& proof) return verify(proof, { honk_vk, eccvm_vk, translator_vk }); } -/** - * @brief Internal method for constructing a decider proof - * - * @return HonkProof - */ -HonkProof UltraVanillaClientIVC::decider_prove() const -{ - vinfo("prove decider..."); - fold_output.accumulator->proving_key.commitment_key = bn254_commitment_key; - MegaDeciderProver decider_prover(fold_output.accumulator); - vinfo("finished decider proving."); - return decider_prover.construct_proof(); -} - /** * @brief Construct and verify a proof for the IVC * @note Use of this method only makes sense when the prover and verifier are the same entity, e.g. in @@ -380,34 +130,4 @@ bool UltraVanillaClientIVC::prove_and_verify() return verified; } -/** - * @brief Given a set of circuits, compute the verification keys that will be required by the IVC scheme - * @details The verification keys computed here are in general not the same as the verification keys for the - * raw input circuits because recursive verifier circuits (merge and/or folding) may be appended to the incoming - * circuits as part accumulation. - * @note This method exists for convenience and is not not meant to be used in practice for IVC. Given a set of - * circuits, it could be run once and for all to compute then save the required VKs. It also provides a convenient - * (albeit innefficient) way of separating out the cost of computing VKs from a benchmark. - * - * @param circuits A copy of the circuits to be accumulated (passing by reference would alter the original circuits) - * @return std::vector> - */ -std::vector> UltraVanillaClientIVC::precompute_folding_verification_keys( - std::vector circuits) -{ - std::vector> vkeys; - - for (auto& circuit : circuits) { - accumulate(circuit); - vkeys.emplace_back(honk_vk); - } - - // Reset the scheme so it can be reused for actual accumulation, maintaining the trace structure setting as is - TraceSettings settings = trace_settings; - *this = UltraVanillaClientIVC(); - this->trace_settings = settings; - - return vkeys; -} - } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp index 3ef09507292..d0f554bc5e1 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp @@ -1,15 +1,9 @@ #pragma once -#include "barretenberg/goblin/goblin.hpp" -#include "barretenberg/goblin/mock_circuits.hpp" #include "barretenberg/plonk_honk_shared/execution_trace/execution_trace_usage_tracker.hpp" -#include "barretenberg/protogalaxy/protogalaxy_prover.hpp" -#include "barretenberg/protogalaxy/protogalaxy_verifier.hpp" -#include "barretenberg/stdlib/honk_verifier/decider_recursive_verifier.hpp" -#include "barretenberg/stdlib/primitives/databus/databus.hpp" -#include "barretenberg/ultra_honk/decider_keys.hpp" -#include "barretenberg/ultra_honk/decider_prover.hpp" -#include "barretenberg/ultra_honk/decider_verifier.hpp" +#include "barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.hpp" +#include "barretenberg/ultra_honk/ultra_prover.hpp" +#include "barretenberg/ultra_honk/ultra_verifier.hpp" #include namespace bb { @@ -26,172 +20,62 @@ namespace bb { class UltraVanillaClientIVC { public: - using Flavor = MegaFlavor; - using MegaVerificationKey = Flavor::VerificationKey; + using Flavor = UltraFlavor; using FF = Flavor::FF; - using FoldProof = std::vector; - using MergeProof = std::vector; - using DeciderProvingKey = DeciderProvingKey_; - using DeciderVerificationKey = DeciderVerificationKey_; - using ClientCircuit = MegaCircuitBuilder; // can only be Mega - using DeciderProver = DeciderProver_; - using DeciderVerifier = DeciderVerifier_; - using DeciderProvingKeys = DeciderProvingKeys_; - using FoldingProver = ProtogalaxyProver_; - using DeciderVerificationKeys = DeciderVerificationKeys_; - using FoldingVerifier = ProtogalaxyVerifier_; - using ECCVMVerificationKey = bb::ECCVMFlavor::VerificationKey; - using TranslatorVerificationKey = bb::TranslatorFlavor::VerificationKey; - using MegaProver = UltraProver_; - using MegaVerifier = UltraVerifier_; - - using RecursiveFlavor = MegaRecursiveFlavor_; - using RecursiveDeciderVerificationKeys = - bb::stdlib::recursion::honk::RecursiveDeciderVerificationKeys_; - using RecursiveDeciderVerificationKey = RecursiveDeciderVerificationKeys::DeciderVK; - using RecursiveVerificationKey = RecursiveFlavor::VerificationKey; - using FoldingRecursiveVerifier = - bb::stdlib::recursion::honk::ProtogalaxyRecursiveVerifier_; - using OinkRecursiveVerifier = stdlib::recursion::honk::OinkRecursiveVerifier_; - using DeciderRecursiveVerifier = stdlib::recursion::honk::DeciderRecursiveVerifier_; - - using DataBusDepot = stdlib::DataBusDepot; - - /** - * @brief A full proof for the IVC scheme containing a Mega proof showing correctness of the hiding circuit (which - * recursive verified the last folding and decider proof) and a Goblin proof (translator VM, ECCVM and last merge - * proof). - * - * @details This proof will be zero-knowledge. - */ - struct Proof { - HonkProof mega_proof; - GoblinProof goblin_proof; - - size_t size() const { return mega_proof.size() + goblin_proof.size(); } - - MSGPACK_FIELDS(mega_proof, goblin_proof); - }; + using ClientCircuit = UltraCircuitBuilder; + using PK = DeciderProvingKey_; - struct VerificationKey { - std::shared_ptr mega; - std::shared_ptr eccvm; - std::shared_ptr translator; - - MSGPACK_FIELDS(mega, eccvm, translator); - }; + using RecursiveFlavor = UltraRecursiveFlavor_; + using RecursiveVerificationKey = RecursiveFlavor::VerificationKey; - enum class QUEUE_TYPE { OINK, PG }; // for specifying type of proof in the verification queue + using Proof = HonkProof; + using VK = UltraFlavor::VerificationKey; // An entry in the native verification queue struct VerifierInputs { - std::vector proof; // oink or PG - std::shared_ptr honk_verification_key; - QUEUE_TYPE type; + HonkProof proof; + // std::shared_ptr honk_verification_key; + // QUEUE_TYPE type; }; using VerificationQueue = std::vector; // An entry in the stdlib verification queue struct StdlibVerifierInputs { - StdlibProof proof; // oink or PG - std::shared_ptr honk_verification_key; - QUEUE_TYPE type; + StdlibProof proof; + // std::shared_ptr honk_verification_key; + // QUEUE_TYPE type; }; using StdlibVerificationQueue = std::vector; - // Utility for tracking the max size of each block across the full IVC - ExecutionTraceUsageTracker trace_usage_tracker; - - private: - using ProverFoldOutput = FoldingResult; - public: - ProverFoldOutput fold_output; // prover accumulator and fold proof - HonkProof mega_proof; + // ProverOutput fold_output; // prover accumulator and fold proof + HonkProof previous_proof; - std::shared_ptr verifier_accumulator; // verifier accumulator - std::shared_ptr honk_vk; // honk vk to be completed and folded into the accumulator + stdlib::recursion::aggregation_state> verifier_accumulator; + std::shared_ptr vk; // Set of tuples {proof, verification_key, type} to be recursively verified VerificationQueue verification_queue; - // Set of tuples {stdlib_proof, stdlib_verification_key, type} corresponding to the native verification queue - StdlibVerificationQueue stdlib_verification_queue; - // Set of merge proofs to be recursively verified - std::vector merge_verification_queue; - - // Management of linking databus commitments between circuits in the IVC - DataBusDepot bus_depot; - - // Settings related to the use of fixed block sizes for each gate in the execution trace - TraceSettings trace_settings; std::shared_ptr bn254_commitment_key; - GoblinProver goblin; - // We dynamically detect whether the input stack consists of one circuit, in which case we do not construct the // hiding circuit and instead simply prove the single input circuit. bool one_circuit = false; - bool initialized = false; // Is the IVC accumulator initialized - - UltraVanillaClientIVC(TraceSettings trace_settings = {}) - : trace_usage_tracker(trace_settings) - , trace_settings(trace_settings) - , bn254_commitment_key(trace_settings.structure.has_value() - ? std::make_shared>(trace_settings.dyadic_size()) - : nullptr) - , goblin(bn254_commitment_key) + UltraVanillaClientIVC(const size_t dyadic_size) + : bn254_commitment_key(std::make_shared>(dyadic_size)) {} - void instantiate_stdlib_verification_queue( - ClientCircuit& circuit, const std::vector>& input_keys = {}); - - void perform_recursive_verification_and_databus_consistency_checks( - ClientCircuit& circuit, - const StdlibProof& proof, - const std::shared_ptr& vkey, - const QUEUE_TYPE type); - - void process_recursive_merge_verification_queue(ClientCircuit& circuit); - - // Complete the logic of a kernel circuit (e.g. PG/merge recursive verification, databus consistency checks) - void complete_kernel_circuit_logic(ClientCircuit& circuit); - - /** - * @brief Perform prover work for accumulation (e.g. PG folding, merge proving) - * - * @param circuit The incoming statement - * @param precomputed_vk The verification key of the incoming statement OR a mocked key whose metadata needs to be - * 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 shoudl have its metadata set. - */ - void accumulate(ClientCircuit& circuit, - const bool _one_circuit = false, - const std::shared_ptr& precomputed_vk = nullptr, - const bool mock_vk = false); + void accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk = nullptr); Proof prove(); - HonkProof construct_and_prove_hiding_circuit(); - - static bool verify(const Proof& proof, const VerificationKey& vk); + static bool verify(const Proof& proof, const VK& vk); bool verify(const Proof& proof); bool prove_and_verify(); - - HonkProof decider_prove() const; - - std::vector> precompute_folding_verification_keys( - std::vector circuits); - - VerificationKey get_vk() const - { - return { honk_vk, - std::make_shared(goblin.get_eccvm_proving_key()), - std::make_shared(goblin.get_translator_proving_key()) }; - } }; } // namespace bb \ No newline at end of file From 42735b68acc7e936860d5fe029e0d147d90d3b61 Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 19 Dec 2024 19:04:15 +0000 Subject: [PATCH 3/9] Basic and BasicFour --- .../ultra_recursive_verifier.test.cpp | 10 +- .../aggregation_state/aggregation_state.hpp | 1 + .../barretenberg/ultra_honk/ultra_prover.cpp | 8 + .../barretenberg/ultra_honk/ultra_prover.hpp | 2 + .../ultra_vanilla_client_ivc/CMakeLists.txt | 2 +- .../ultra_vanilla_client_ivc.cpp | 129 +++---- .../ultra_vanilla_client_ivc.hpp | 76 ++-- .../ultra_vanilla_client_ivc.test.cpp | 334 ++++++++---------- 8 files changed, 229 insertions(+), 333 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp index 86ab9028de1..03857184d42 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp @@ -38,6 +38,8 @@ template class RecursiveVerifierTest : public testing using RecursiveVerifier = UltraRecursiveVerifier_; using VerificationKey = typename RecursiveVerifier::VerificationKey; + using AggState = aggregation_state; + using VerifierOutput = bb::stdlib::recursion::honk::UltraRecursiveVerifierOutput; /** * @brief Create a non-trivial arbitrary inner circuit, the proof of which will be recursively verified * @@ -209,11 +211,9 @@ template class RecursiveVerifierTest : public testing OuterBuilder outer_circuit; RecursiveVerifier verifier{ &outer_circuit, verification_key }; - aggregation_state agg_obj = - init_default_aggregation_state(outer_circuit); - bb::stdlib::recursion::honk::UltraRecursiveVerifierOutput output = - verifier.verify_proof(inner_proof, agg_obj); - aggregation_state pairing_points = output.agg_obj; + AggState agg_obj = init_default_aggregation_state(outer_circuit); + VerifierOutput output = verifier.verify_proof(inner_proof, agg_obj); + AggState pairing_points = output.agg_obj; info("Recursive Verifier: num gates = ", outer_circuit.get_estimated_num_finalized_gates()); // Check for a failure flag in the recursive verifier circuit diff --git a/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp b/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp index 4eba0fa247f..18e94db14b0 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp @@ -137,6 +137,7 @@ template PairingPointAccumulatorIndices init_default_agg_obj_ // TODO(https://github.com/AztecProtocol/barretenberg/issues/911): These are pairing points extracted from a valid // proof. This is a workaround because we can't represent the point at infinity in biggroup yet. + // WORKTODO: Add assert that this is a valid initial starting point. PairingPointAccumulatorIndices agg_obj_indices = {}; fq x0("0x031e97a575e9d05a107acb64952ecab75c020998797da7842ab5d6d1986846cf"); fq y0("0x178cbf4206471d722669117f9758a4c410db10a01750aebb5666547acf8bd5a4"); diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp index c07c75b34a3..a211564ae5d 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp @@ -4,6 +4,14 @@ #include "barretenberg/ultra_honk/oink_prover.hpp" namespace bb { +template +UltraProver_::UltraProver_(const std::shared_ptr& proving_key, + const std::shared_ptr& commitment_key) + : proving_key(std::move(proving_key)) + , transcript(std::make_shared()) + , commitment_key(commitment_key) +{} + /** * @brief Create UltraProver_ from a decider proving key. * diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp index 82608299ed5..896e5fd96f4 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp @@ -37,6 +37,8 @@ template class UltraProver_ { std::shared_ptr commitment_key; + UltraProver_(const std::shared_ptr&, const std::shared_ptr&); + explicit UltraProver_(const std::shared_ptr&, const std::shared_ptr& transcript = std::make_shared()); diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt index 26ce7caca10..e8120167c08 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(ultra_vanilla_client_ivc stdlib_protogalaxy_verifier stdlib_goblin_verifier) \ No newline at end of file +barretenberg_module(ultra_vanilla_client_ivc stdlib_honk_verifier) \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp index 3e1cc88edb5..4e8d7f94e7b 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp @@ -4,106 +4,56 @@ namespace bb { /** - * @brief Execute prover work for accumulation - * @details Construct an proving key for the provided circuit. If this is the first step in the IVC, simply initialize - * the folding accumulator. Otherwise, execute the PG prover to fold the proving key into the accumulator and produce a - * folding proof. Also execute the merge protocol to produce a merge proof. + * @brief Execute one IVC step * * @param circuit * @param precomputed_vk */ -void UltraVanillaClientIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk) +void UltraVanillaClientIVC::accumulate(Circuit& circuit, const Proof& proof, const std::shared_ptr& vk) { - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1069): Do proper aggregation with merge recursive - // verifier. - circuit.add_pairing_point_accumulator(stdlib::recursion::init_default_agg_obj_indices(circuit)); - - // Construct the proving key for circuit - std::shared_ptr proving_key = std::make_shared(circuit); - - // The commitment key is initialised with the number of points determined by the trace_settings' dyadic size. If a - // circuit overflows past the dyadic size the commitment key will not have enough points so we need to increase it - if (proving_key->proving_key.circuit_size > bn254_commitment_key->dyadic_size) { - bn254_commitment_key = std::make_shared>(proving_key->proving_key.circuit_size); - } - proving_key->proving_key.commitment_key = bn254_commitment_key; - - vinfo("getting honk vk... precomputed?: ", precomputed_vk); - - // Set the verification key from precomputed if available, else compute it - vk = precomputed_vk ? precomputed_vk : std::make_shared(proving_key->proving_key); - - if (!initialized) { - // If this is the first circuit in the IVC, use oink to complete the decider proving key and generate an oink - // proof - MegaOinkProver oink_prover{ proving_key }; - vinfo("computing oink proof..."); - oink_prover.prove(); - vinfo("oink proof constructed"); - proving_key->is_accumulator = true; // indicate to PG that it should not run oink on this key - // Initialize the gate challenges to zero for use in first round of folding - proving_key->gate_challenges = std::vector(CONST_PG_LOG_N, 0); - - fold_output.accumulator = proving_key; // initialize the prover accum with the completed key - - // Add oink proof and corresponding verification key to the verification queue - verification_queue.push_back( - bb::UltraVanillaClientIVC::VerifierInputs{ oink_prover.transcript->proof_data, honk_vk, QUEUE_TYPE::OINK }); - - initialized = true; - } else { // Otherwise, fold the new key into the accumulator - vinfo("computing proof"); - Prover folding_prover({ fold_output.accumulator, proving_key }, trace_usage_tracker); - prover_output = prover.prove(); - vinfo("constructed proof"); - - // Add fold proof and corresponding verification key to the verification queue - verification_queue.push_back( - bb::UltraVanillaClientIVC::VerifierInputs{ fold_output.proof, honk_vk, QUEUE_TYPE::PG }); - } + RecursiveVerifier verifier{ &circuit, std::make_shared(&circuit, vk) }; + Accumulator agg_obj = stdlib::recursion::init_default_aggregation_state>(circuit); + accumulator = verifier.verify_proof(proof, agg_obj).agg_obj; } -/** - * @brief Construct a proof for the IVC, which, if verified, fully establishes its correctness - * - * @return Proof - */ -UltraVanillaClientIVC::Proof UltraVanillaClientIVC::prove() +HonkProof UltraVanillaClientIVC::prove(std::vector circuits, + std::vector>> vks) { - if (!one_circuit) { - previous_proof = construct_and_prove_hiding_circuit(); - ASSERT(merge_verification_queue.size() == 1); // ensure only a single merge proof remains in the queue + for (size_t step = 0; auto [circuit, vk] : zip_view(circuits, vks)) { + if (step == 0) { + accumulator_indices = stdlib::recursion::init_default_agg_obj_indices(circuit); + } else { + accumulate(circuit, previous_proof, previous_vk); + accumulator_indices = accumulator.get_witness_indices(); + } + + circuit.add_pairing_point_accumulator(accumulator_indices); + + auto proving_key = std::make_shared(circuit); + + if (step < circuits.size() - 1) { + UltraProver prover{ proving_key, commitment_key }; + previous_proof = prover.construct_proof(); + } else { + // WORKTODO: use ZK here + UltraProver prover{ proving_key, commitment_key }; + previous_proof = prover.construct_proof(); + } + + previous_vk = vk.has_value() ? *vk : std::make_shared(proving_key->proving_key); + step++; } - - MergeProof& merge_proof = merge_verification_queue[0]; - return { previous_proof, goblin.prove(merge_proof) }; + return previous_proof; }; -bool UltraVanillaClientIVC::verify(const Proof& proof, const VerificationKey& vk) +bool UltraVanillaClientIVC::verify(const Proof& proof, const std::shared_ptr& vk) { // Verify the hiding circuit proof - MegaVerifier verifer{ vk.mega }; - bool mega_verified = verifer.verify_proof(proof.previous_proof); - vinfo("Mega verified: ", mega_verified); - // Goblin verification (final merge, eccvm, translator) - GoblinVerifier goblin_verifier{ vk.eccvm, vk.translator }; - bool goblin_verified = goblin_verifier.verify(proof.goblin_proof); - vinfo("Goblin verified: ", goblin_verified); - return goblin_verified && mega_verified; -} - -/** - * @brief Verify a full proof of the IVC - * - * @param proof - * @return bool - */ -bool UltraVanillaClientIVC::verify(const Proof& proof) -{ - auto eccvm_vk = std::make_shared(goblin.get_eccvm_proving_key()); - auto translator_vk = std::make_shared(goblin.get_translator_proving_key()); - return verify(proof, { honk_vk, eccvm_vk, translator_vk }); + UltraVerifier verifer{ vk }; + const bool verified = verifer.verify_proof(proof); + vinfo("verified: ", verified); + return verified; } /** @@ -112,16 +62,17 @@ bool UltraVanillaClientIVC::verify(const Proof& proof) * development/testing. * */ -bool UltraVanillaClientIVC::prove_and_verify() +bool UltraVanillaClientIVC::prove_and_verify(std::vector circuits, + std::vector>> vks) { auto start = std::chrono::steady_clock::now(); - const auto proof = prove(); + prove(circuits, vks); auto end = std::chrono::steady_clock::now(); auto diff = std::chrono::duration_cast(end - start); vinfo("time to call UltraVanillaClientIVC::prove: ", diff.count(), " ms."); start = end; - const bool verified = verify(proof); + const bool verified = verify(previous_proof, previous_vk); end = std::chrono::steady_clock::now(); diff = std::chrono::duration_cast(end - start); diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp index d0f554bc5e1..19391a8433f 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp @@ -1,81 +1,47 @@ #pragma once #include "barretenberg/plonk_honk_shared/execution_trace/execution_trace_usage_tracker.hpp" +#include "barretenberg/plonk_honk_shared/types/aggregation_object_type.hpp" #include "barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.hpp" +#include "barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp" #include "barretenberg/ultra_honk/ultra_prover.hpp" #include "barretenberg/ultra_honk/ultra_verifier.hpp" #include - namespace bb { -/** - * @brief The IVC scheme used by the aztec client for private function execution - * @details Combines Protogalaxy with Goblin to accumulate one circuit at a time with efficient EC group - * operations. It is assumed that the circuits being accumulated correspond alternatingly to an app and a kernel, as is - * the case in Aztec. Two recursive folding verifiers are appended to each kernel (except the first one) to verify the - * folding of a previous kernel and an app/function circuit. Due to this structure it is enforced that the total number - * of circuits being accumulated is even. - * - */ class UltraVanillaClientIVC { public: using Flavor = UltraFlavor; using FF = Flavor::FF; - using ClientCircuit = UltraCircuitBuilder; + using Circuit = UltraCircuitBuilder; using PK = DeciderProvingKey_; - - using RecursiveFlavor = UltraRecursiveFlavor_; - using RecursiveVerificationKey = RecursiveFlavor::VerificationKey; - - using Proof = HonkProof; using VK = UltraFlavor::VerificationKey; + using Proof = HonkProof; - // An entry in the native verification queue - struct VerifierInputs { - HonkProof proof; - // std::shared_ptr honk_verification_key; - // QUEUE_TYPE type; - }; - using VerificationQueue = std::vector; - - // An entry in the stdlib verification queue - struct StdlibVerifierInputs { - StdlibProof proof; - // std::shared_ptr honk_verification_key; - // QUEUE_TYPE type; - }; - - using StdlibVerificationQueue = std::vector; + using RecursiveFlavor = UltraRecursiveFlavor_; + using RecursiveVerifier = stdlib::recursion::honk::UltraRecursiveVerifier_; + using RecursiveVK = RecursiveFlavor::VerificationKey; + using Curve = stdlib::bn254; + using Accumulator = stdlib::recursion::aggregation_state; public: - // ProverOutput fold_output; // prover accumulator and fold proof - HonkProof previous_proof; - - stdlib::recursion::aggregation_state> verifier_accumulator; - std::shared_ptr vk; - - // Set of tuples {proof, verification_key, type} to be recursively verified - VerificationQueue verification_queue; - - std::shared_ptr bn254_commitment_key; - - // We dynamically detect whether the input stack consists of one circuit, in which case we do not construct the - // hiding circuit and instead simply prove the single input circuit. - bool one_circuit = false; - - UltraVanillaClientIVC(const size_t dyadic_size) - : bn254_commitment_key(std::make_shared>(dyadic_size)) + std::shared_ptr> commitment_key; + Proof previous_proof; + std::shared_ptr previous_vk; + Accumulator accumulator; + PairingPointAccumulatorIndices accumulator_indices; + + UltraVanillaClientIVC(const size_t dyadic_size = 1 << 20) + : commitment_key(std::make_shared>(dyadic_size)) {} - void accumulate(ClientCircuit& circuit, const std::shared_ptr& precomputed_vk = nullptr); - - Proof prove(); + void accumulate(Circuit&, const Proof&, const std::shared_ptr&); - static bool verify(const Proof& proof, const VK& vk); + HonkProof prove(std::vector, std::vector>>); - bool verify(const Proof& proof); + static bool verify(const Proof&, const std::shared_ptr&); - bool prove_and_verify(); + bool prove_and_verify(std::vector, std::vector>>); }; } // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp index 12cc0aa89f0..094f409652d 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp @@ -17,97 +17,59 @@ class UltraVanillaClientIVCTests : public ::testing::Test { } using Flavor = UltraVanillaClientIVC::Flavor; + using Builder = UltraCircuitBuilder; using FF = typename Flavor::FF; using VerificationKey = Flavor::VerificationKey; - using Builder = UltraVanillaClientIVC::ClientCircuit; - using DeciderProvingKey = UltraVanillaClientIVC::DeciderProvingKey; - using DeciderVerificationKey = UltraVanillaClientIVC::DeciderVerificationKey; - using FoldProof = UltraVanillaClientIVC::FoldProof; - using DeciderProver = UltraVanillaClientIVC::DeciderProver; - using DeciderVerifier = UltraVanillaClientIVC::DeciderVerifier; - using DeciderProvingKeys = DeciderProvingKeys_; - using FoldingProver = ProtogalaxyProver_; - using DeciderVerificationKeys = DeciderVerificationKeys_; - using FoldingVerifier = ProtogalaxyVerifier_; - - /** - * @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). - * - */ - static Builder create_mock_circuit(UltraVanillaClientIVC& ivc, size_t log2_num_gates = 16) - { - Builder circuit{ ivc.goblin.op_queue }; - MockCircuits::construct_arithmetic_circuit(circuit, log2_num_gates); - - // TODO(https://github.com/AztecProtocol/barretenberg/issues/911): We require goblin ops to be added to the - // function circuit because we cannot support zero commtiments. While the builder handles this at - // finalisation stage via the add_gates_to_ensure_all_polys_are_non_zero function for other MegaHonk - // circuits (where we don't explicitly need to add goblin ops), in UltraVanillaClientIVC merge proving happens - // prior to folding where the absense of goblin ecc ops will result in zero commitments. - MockCircuits::construct_goblin_ecc_op_circuit(circuit); - return circuit; - } /** * @brief A test utility for generating alternating mock app and kernel circuits and precomputing verification keys * */ class MockCircuitProducer { - using ClientCircuit = UltraVanillaClientIVC::ClientCircuit; - - bool is_kernel = false; + using Circuit = UltraVanillaClientIVC::Circuit; public: - ClientCircuit create_next_circuit(UltraVanillaClientIVC& ivc, size_t log2_num_gates = 16) + Circuit create_next_circuit(size_t log2_num_gates = 16) { - ClientCircuit circuit{ ivc.goblin.op_queue }; - circuit = create_mock_circuit(ivc, log2_num_gates); // construct mock base logic - if (is_kernel) { - ivc.complete_kernel_circuit_logic(circuit); // complete with recursive verifiers etc - } - is_kernel = !is_kernel; // toggle is_kernel on/off alternatingly - + Circuit circuit; + MockCircuits::construct_arithmetic_circuit(circuit, log2_num_gates); return circuit; } - auto precompute_verification_keys(const size_t num_circuits, - TraceSettings trace_settings, - size_t log2_num_gates = 16) - { - UltraVanillaClientIVC ivc{ - trace_settings - }; // temporary IVC instance needed to produce the complete kernel circuits - - std::vector> vkeys; - - for (size_t idx = 0; idx < num_circuits; ++idx) { - ClientCircuit circuit = create_next_circuit(ivc, log2_num_gates); // create the next circuit - ivc.accumulate(circuit); // accumulate the circuit - vkeys.emplace_back(ivc.honk_vk); // save the VK for the circuit - } - is_kernel = false; - - return vkeys; - } + // auto precompute_verification_keys(const size_t num_circuits, + // TraceSettings trace_settings, + // size_t log2_num_gates = 16) + // { + // UltraVanillaClientIVC ivc{ + // trace_settings + // }; // temporary IVC instance needed to produce the complete kernel circuits + + // std::vector> vkeys; + + // for (size_t idx = 0; idx < num_circuits; ++idx) { + // Circuit circuit = create_next_circuit(ivc, log2_num_gates); // create the next circuit + // ivc.accumulate(circuit); // accumulate the circuit + // vkeys.emplace_back(ivc.honk_vk); // save the VK for the circuit + // } + // is_kernel = false; + + // return vkeys; + // } }; - /** - * @brief Tamper with a proof by finding the first non-zero value and incrementing it by 1 - * - */ - static void tamper_with_proof(FoldProof& proof) - { - for (auto& val : proof) { - if (val > 0) { - val += 1; - break; - } - } - } + // /** + // * @brief Tamper with a proof by finding the first non-zero value and incrementing it by 1 + // * + // */ + // static void tamper_with_proof(FoldProof& proof) + // { + // for (auto& val : proof) { + // if (val > 0) { + // val += 1; + // break; + // } + // } + // } }; /** @@ -118,132 +80,42 @@ class UltraVanillaClientIVCTests : public ::testing::Test { */ TEST_F(UltraVanillaClientIVCTests, Basic) { - UltraVanillaClientIVC ivc; + static constexpr size_t LOG_SIZE = 10; + UltraVanillaClientIVC ivc(1 << LOG_SIZE); MockCircuitProducer circuit_producer; - // Initialize the IVC with an arbitrary circuit - Builder circuit_0 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_0); + std::vector circuits{ circuit_producer.create_next_circuit(LOG_SIZE), + circuit_producer.create_next_circuit(LOG_SIZE) }; - // Create another circuit and accumulate - Builder circuit_1 = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit_1); + std::vector>> vks{ std::nullopt, std::nullopt }; - EXPECT_TRUE(ivc.prove_and_verify()); + EXPECT_TRUE(ivc.prove_and_verify(circuits, vks)); }; /** * @brief A simple test demonstrating IVC for four mock circuits, which is slightly more than minimal. - * @details When accumulating only four circuits, we execute all the functionality of a full UltraVanillaClientIVC run. + * @details When accumulating only four circuits, we execute all the functionality of a full UltraVanillaClientIVC + run. * */ TEST_F(UltraVanillaClientIVCTests, BasicFour) { - UltraVanillaClientIVC ivc; + static constexpr size_t LOG_SIZE = 10; + UltraVanillaClientIVC ivc(1 << LOG_SIZE); MockCircuitProducer circuit_producer; - for (size_t idx = 0; idx < 4; ++idx) { - Builder circuit = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit); - } - - EXPECT_TRUE(ivc.prove_and_verify()); -}; -/** - * @brief Check that the IVC fails if an intermediate fold proof is invalid - * @details When accumulating 4 circuits, there are 3 fold proofs to verify (the first two are recursively verfied and - * the 3rd is verified as part of the IVC proof). Check that if any of one of these proofs is invalid, the IVC will - * fail. - * - */ -TEST_F(UltraVanillaClientIVCTests, BadProofFailure) -{ - // Confirm that the IVC verifies if nothing is tampered with - { - UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; - - MockCircuitProducer circuit_producer; - - // Construct and accumulate a set of mocked private function execution circuits - size_t NUM_CIRCUITS = 4; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); - } - EXPECT_TRUE(ivc.prove_and_verify()); - } - - // The IVC throws an exception if the FIRST fold proof is tampered with - { - UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; - - MockCircuitProducer circuit_producer; - - // Construct and accumulate a set of mocked private function execution circuits - size_t NUM_CIRCUITS = 4; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - if (idx == 3) { // At idx = 3, we've tampered with the one of the folding proofs so create the recursive - // folding verifier will throw an error. - EXPECT_ANY_THROW(circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5)); - break; - } - auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); - - if (idx == 2) { - EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation - tamper_with_proof(ivc.verification_queue[0].proof); // tamper with first proof - } - } - } + std::vector circuits{ circuit_producer.create_next_circuit(LOG_SIZE), + circuit_producer.create_next_circuit(LOG_SIZE), + circuit_producer.create_next_circuit(LOG_SIZE), + circuit_producer.create_next_circuit(LOG_SIZE) }; - // The IVC fails if the SECOND fold proof is tampered with - { - UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; - - MockCircuitProducer circuit_producer; - - // Construct and accumulate a set of mocked private function execution circuits - size_t NUM_CIRCUITS = 4; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - if (idx == 3) { // At idx = 3, we've tampered with the one of the folding proofs so create the recursive - // folding verifier will throw an error. - EXPECT_ANY_THROW(circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5)); - break; - } - auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); - - if (idx == 2) { - EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation - tamper_with_proof(ivc.verification_queue[1].proof); // tamper with second proof - } - } - } - - // The IVC fails if the 3rd/FINAL fold proof is tampered with - { - UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; - - MockCircuitProducer circuit_producer; - - // Construct and accumulate a set of mocked private function execution circuits - size_t NUM_CIRCUITS = 4; - for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { - auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); - ivc.accumulate(circuit); - } - - // Only a single proof should be present in the queue when verification of the IVC is performed - EXPECT_EQ(ivc.verification_queue.size(), 1); - tamper_with_proof(ivc.verification_queue[0].proof); // tamper with the final fold proof - - EXPECT_ANY_THROW(ivc.prove_and_verify()); - } + std::vector>> vks{ + std::nullopt, std::nullopt, std::nullopt, std::nullopt + }; - EXPECT_TRUE(true); + EXPECT_TRUE(ivc.prove_and_verify(circuits, vks)); }; /** @@ -268,3 +140,99 @@ TEST_F(UltraVanillaClientIVCTests, PrecomputedVerificationKeys) EXPECT_TRUE(ivc.prove_and_verify()); }; + +// /** +// * @brief Check that the IVC fails if an intermediate fold proof is invalid +// * @details When accumulating 4 circuits, there are 3 fold proofs to verify (the first two are recursively verfied +// and +// * the 3rd is verified as part of the IVC proof). Check that if any of one of these proofs is invalid, the IVC will +// * fail. +// * +// */ +// TEST_F(UltraVanillaClientIVCTests, BadProofFailure) +// { +// // Confirm that the IVC verifies if nothing is tampered with +// { +// UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; + +// MockCircuitProducer circuit_producer; + +// // Construct and accumulate a set of mocked private function execution circuits +// size_t NUM_CIRCUITS = 4; +// for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { +// auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); +// ivc.accumulate(circuit); +// } +// EXPECT_TRUE(ivc.prove_and_verify()); +// } + +// // The IVC throws an exception if the FIRST fold proof is tampered with +// { +// UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; + +// MockCircuitProducer circuit_producer; + +// // Construct and accumulate a set of mocked private function execution circuits +// size_t NUM_CIRCUITS = 4; +// for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { +// if (idx == 3) { // At idx = 3, we've tampered with the one of the folding proofs so create the recursive +// // folding verifier will throw an error. +// EXPECT_ANY_THROW(circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5)); +// break; +// } +// auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); +// ivc.accumulate(circuit); + +// if (idx == 2) { +// EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation +// tamper_with_proof(ivc.verification_queue[0].proof); // tamper with first proof +// } +// } +// } + +// // The IVC fails if the SECOND fold proof is tampered with +// { +// UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; + +// MockCircuitProducer circuit_producer; + +// // Construct and accumulate a set of mocked private function execution circuits +// size_t NUM_CIRCUITS = 4; +// for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { +// if (idx == 3) { // At idx = 3, we've tampered with the one of the folding proofs so create the recursive +// // folding verifier will throw an error. +// EXPECT_ANY_THROW(circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5)); +// break; +// } +// auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); +// ivc.accumulate(circuit); + +// if (idx == 2) { +// EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation +// tamper_with_proof(ivc.verification_queue[1].proof); // tamper with second proof +// } +// } +// } + +// // The IVC fails if the 3rd/FINAL fold proof is tampered with +// { +// UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; + +// MockCircuitProducer circuit_producer; + +// // Construct and accumulate a set of mocked private function execution circuits +// size_t NUM_CIRCUITS = 4; +// for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { +// auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); +// ivc.accumulate(circuit); +// } + +// // Only a single proof should be present in the queue when verification of the IVC is performed +// EXPECT_EQ(ivc.verification_queue.size(), 1); +// tamper_with_proof(ivc.verification_queue[0].proof); // tamper with the final fold proof + +// EXPECT_ANY_THROW(ivc.prove_and_verify()); +// } + +// EXPECT_TRUE(true); +// }; From 542a0f2e22c264f758a82ddaf6e107b5178bc6bd Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 19 Dec 2024 21:14:39 +0000 Subject: [PATCH 4/9] Precomputation of VKs --- .../ultra_vanilla_client_ivc.cpp | 26 +++-- .../ultra_vanilla_client_ivc.hpp | 23 ++++- .../ultra_vanilla_client_ivc.test.cpp | 99 +++++++------------ 3 files changed, 76 insertions(+), 72 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp index 4e8d7f94e7b..373b9059bef 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp @@ -16,10 +16,10 @@ void UltraVanillaClientIVC::accumulate(Circuit& circuit, const Proof& proof, con accumulator = verifier.verify_proof(proof, agg_obj).agg_obj; } -HonkProof UltraVanillaClientIVC::prove(std::vector circuits, - std::vector>> vks) +HonkProof UltraVanillaClientIVC::prove(CircuitSource& source, const bool cache_vks) { - for (size_t step = 0; auto [circuit, vk] : zip_view(circuits, vks)) { + for (size_t step = 0; step < source.num_circuits(); step++) { + auto [circuit, vk] = source.next(); if (step == 0) { accumulator_indices = stdlib::recursion::init_default_agg_obj_indices(circuit); } else { @@ -31,7 +31,7 @@ HonkProof UltraVanillaClientIVC::prove(std::vector circuits, auto proving_key = std::make_shared(circuit); - if (step < circuits.size() - 1) { + if (step < source.num_circuits() - 1) { UltraProver prover{ proving_key, commitment_key }; previous_proof = prover.construct_proof(); } else { @@ -40,8 +40,10 @@ HonkProof UltraVanillaClientIVC::prove(std::vector circuits, previous_proof = prover.construct_proof(); } - previous_vk = vk.has_value() ? *vk : std::make_shared(proving_key->proving_key); - step++; + previous_vk = vk ? vk : std::make_shared(proving_key->proving_key); + if (cache_vks) { + vk_cache.push_back(previous_vk); + } } return previous_proof; }; @@ -62,11 +64,10 @@ bool UltraVanillaClientIVC::verify(const Proof& proof, const std::shared_ptr * development/testing. * */ -bool UltraVanillaClientIVC::prove_and_verify(std::vector circuits, - std::vector>> vks) +bool UltraVanillaClientIVC::prove_and_verify(CircuitSource& source, const bool cache_vks) { auto start = std::chrono::steady_clock::now(); - prove(circuits, vks); + prove(source, cache_vks); auto end = std::chrono::steady_clock::now(); auto diff = std::chrono::duration_cast(end - start); vinfo("time to call UltraVanillaClientIVC::prove: ", diff.count(), " ms."); @@ -81,4 +82,11 @@ bool UltraVanillaClientIVC::prove_and_verify(std::vector circuits, return verified; } +std::vector> UltraVanillaClientIVC::compute_vks( + CircuitSource& source) +{ + prove_and_verify(source, /*cache_vks=*/true); + return vk_cache; +}; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp index 19391a8433f..26a005dde29 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp @@ -9,6 +9,21 @@ #include namespace bb { +template + +class CircuitSource { + public: + struct Output { + Builder circuit; + std::shared_ptr vk; + }; + + virtual Output next(); + virtual size_t num_circuits() const; +}; + class UltraVanillaClientIVC { public: @@ -32,16 +47,20 @@ class UltraVanillaClientIVC { Accumulator accumulator; PairingPointAccumulatorIndices accumulator_indices; + std::vector> vk_cache; + UltraVanillaClientIVC(const size_t dyadic_size = 1 << 20) : commitment_key(std::make_shared>(dyadic_size)) {} void accumulate(Circuit&, const Proof&, const std::shared_ptr&); - HonkProof prove(std::vector, std::vector>>); + HonkProof prove(CircuitSource& source, const bool cache_vks = false); static bool verify(const Proof&, const std::shared_ptr&); - bool prove_and_verify(std::vector, std::vector>>); + bool prove_and_verify(CircuitSource& source, const bool cache_vks = false); + + std::vector> compute_vks(CircuitSource& source); }; } // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp index 094f409652d..958e9e5f50e 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp @@ -19,42 +19,44 @@ class UltraVanillaClientIVCTests : public ::testing::Test { using Flavor = UltraVanillaClientIVC::Flavor; using Builder = UltraCircuitBuilder; using FF = typename Flavor::FF; - using VerificationKey = Flavor::VerificationKey; + using VK = Flavor::VerificationKey; + using PK = DeciderProvingKey_; /** * @brief A test utility for generating alternating mock app and kernel circuits and precomputing verification keys * */ - class MockCircuitProducer { - using Circuit = UltraVanillaClientIVC::Circuit; + class MockCircuitSource : public CircuitSource { + std::vector _sizes; + std::vector> _vks; + uint32_t step{ 0 }; public: - Circuit create_next_circuit(size_t log2_num_gates = 16) + MockCircuitSource(const std::vector& sizes) + : _sizes(sizes) { - Circuit circuit; - MockCircuits::construct_arithmetic_circuit(circuit, log2_num_gates); - return circuit; + std::fill_n(std::back_inserter(_vks), sizes.size(), nullptr); } - // auto precompute_verification_keys(const size_t num_circuits, - // TraceSettings trace_settings, - // size_t log2_num_gates = 16) - // { - // UltraVanillaClientIVC ivc{ - // trace_settings - // }; // temporary IVC instance needed to produce the complete kernel circuits - - // std::vector> vkeys; - - // for (size_t idx = 0; idx < num_circuits; ++idx) { - // Circuit circuit = create_next_circuit(ivc, log2_num_gates); // create the next circuit - // ivc.accumulate(circuit); // accumulate the circuit - // vkeys.emplace_back(ivc.honk_vk); // save the VK for the circuit - // } - // is_kernel = false; - - // return vkeys; - // } + MockCircuitSource(const MockCircuitSource& source_without_sizes, std::vector> vks) + : _sizes(source_without_sizes._sizes) + , _vks(vks) + {} + + Output next() override + { + Builder circuit; + MockCircuits::construct_arithmetic_circuit(circuit, _sizes[step]); + const auto& vk = _vks[step]; + ++step; + return { circuit, vk }; + } + + size_t num_circuits() const override + { + ASSERT(_sizes.size() == _vks.size()); + return _sizes.size(); + } }; // /** @@ -82,15 +84,8 @@ TEST_F(UltraVanillaClientIVCTests, Basic) { static constexpr size_t LOG_SIZE = 10; UltraVanillaClientIVC ivc(1 << LOG_SIZE); - - MockCircuitProducer circuit_producer; - - std::vector circuits{ circuit_producer.create_next_circuit(LOG_SIZE), - circuit_producer.create_next_circuit(LOG_SIZE) }; - - std::vector>> vks{ std::nullopt, std::nullopt }; - - EXPECT_TRUE(ivc.prove_and_verify(circuits, vks)); + MockCircuitSource circuit_source{ { LOG_SIZE, LOG_SIZE } }; + EXPECT_TRUE(ivc.prove_and_verify(circuit_source)); }; /** @@ -103,19 +98,8 @@ TEST_F(UltraVanillaClientIVCTests, BasicFour) { static constexpr size_t LOG_SIZE = 10; UltraVanillaClientIVC ivc(1 << LOG_SIZE); - - MockCircuitProducer circuit_producer; - - std::vector circuits{ circuit_producer.create_next_circuit(LOG_SIZE), - circuit_producer.create_next_circuit(LOG_SIZE), - circuit_producer.create_next_circuit(LOG_SIZE), - circuit_producer.create_next_circuit(LOG_SIZE) }; - - std::vector>> vks{ - std::nullopt, std::nullopt, std::nullopt, std::nullopt - }; - - EXPECT_TRUE(ivc.prove_and_verify(circuits, vks)); + MockCircuitSource circuit_source{ { LOG_SIZE, LOG_SIZE, LOG_SIZE, LOG_SIZE } }; + EXPECT_TRUE(ivc.prove_and_verify(circuit_source)); }; /** @@ -124,21 +108,14 @@ TEST_F(UltraVanillaClientIVCTests, BasicFour) */ TEST_F(UltraVanillaClientIVCTests, PrecomputedVerificationKeys) { - UltraVanillaClientIVC ivc; - - size_t NUM_CIRCUITS = 4; - MockCircuitProducer circuit_producer; - - 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) { - auto circuit = circuit_producer.create_next_circuit(ivc); - ivc.accumulate(circuit, /*one_circuit=*/false, precomputed_vks[idx]); - } + static constexpr size_t LOG_SIZE = 10; - EXPECT_TRUE(ivc.prove_and_verify()); + UltraVanillaClientIVC ivc(1 << LOG_SIZE); + MockCircuitSource circuit_source_no_vks{ { LOG_SIZE, LOG_SIZE } }; + auto vks = ivc.compute_vks(circuit_source_no_vks); + MockCircuitSource circuit_source_with_vks{ circuit_source_no_vks, vks }; + EXPECT_TRUE(ivc.prove_and_verify(circuit_source_with_vks)); }; // /** From eb06a57ee03fa44853da4b255f8543d3c32177f0 Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 19 Dec 2024 21:17:31 +0000 Subject: [PATCH 5/9] Ref issue --- .../ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp index 373b9059bef..2005d8b3398 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp @@ -35,7 +35,7 @@ HonkProof UltraVanillaClientIVC::prove(CircuitSource& source, const bool UltraProver prover{ proving_key, commitment_key }; previous_proof = prover.construct_proof(); } else { - // WORKTODO: use ZK here + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1176) Use UltraZKProver when it exists UltraProver prover{ proving_key, commitment_key }; previous_proof = prover.construct_proof(); } From ba62f0b759e08219f94ea4f30faf5274a70e1947 Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 19 Dec 2024 21:48:47 +0000 Subject: [PATCH 6/9] Comments and cleanup --- .../ultra_vanilla_client_ivc.cpp | 7 - .../ultra_vanilla_client_ivc.hpp | 55 ++++++- .../ultra_vanilla_client_ivc.test.cpp | 134 +----------------- 3 files changed, 56 insertions(+), 140 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp index 2005d8b3398..4e3e0e54fcf 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp @@ -3,12 +3,6 @@ namespace bb { -/** - * @brief Execute one IVC step - * - * @param circuit - * @param precomputed_vk - */ void UltraVanillaClientIVC::accumulate(Circuit& circuit, const Proof& proof, const std::shared_ptr& vk) { RecursiveVerifier verifier{ &circuit, std::make_shared(&circuit, vk) }; @@ -51,7 +45,6 @@ HonkProof UltraVanillaClientIVC::prove(CircuitSource& source, const bool bool UltraVanillaClientIVC::verify(const Proof& proof, const std::shared_ptr& vk) { - // Verify the hiding circuit proof UltraVerifier verifer{ vk }; const bool verified = verifer.verify_proof(proof); vinfo("verified: ", verified); diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp index 26a005dde29..243164e807e 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp @@ -9,6 +9,17 @@ #include namespace bb { +/** + * @brief A function that produces a set of circuits and possibly their precomputed vks + * @details One has the option of not providing vks--just provide nullptr instead. + * This class is introduced as an experiment. We _could_ just use vectors of vks and shared_ptrs, but this limits + * applicability of the class because, in practice, we don't have sufficient memory to store all circuit builders at + * once. The idea is this class is applicable in both situations we care about testing via mocks (cf the test file for + * UltraVanillaClientIVC, which implements a source of mock circuits), and IVC of circuits written in Noir, where the + * source (not yet implemented) is ACIR and partial witnesses which are processed by our DSL code, expanding blackbox + * function calls. + * @todo Relocate this at the appropriate time, if it does become a standard interface. + */ template @@ -24,6 +35,13 @@ class CircuitSource { virtual size_t num_circuits() const; }; +/** + * @brief A class encapsulating multiple sequential steps of the IVC scheme that arises most naturally from recursive + * proof verification. + * + * @details "Vanilla" is in the colloquial sense of meaning "plain". "Client" refers to the fact that this is intended + * for executing proof construction in constrained environments. + */ class UltraVanillaClientIVC { public: @@ -40,27 +58,56 @@ class UltraVanillaClientIVC { using Curve = stdlib::bn254; using Accumulator = stdlib::recursion::aggregation_state; + /** + * @brief Append a recursive verifier and update the accumulator. + */ + void accumulate(Circuit&, const Proof&, const std::shared_ptr&); + public: std::shared_ptr> commitment_key; Proof previous_proof; std::shared_ptr previous_vk; Accumulator accumulator; PairingPointAccumulatorIndices accumulator_indices; - std::vector> vk_cache; UltraVanillaClientIVC(const size_t dyadic_size = 1 << 20) : commitment_key(std::make_shared>(dyadic_size)) {} - void accumulate(Circuit&, const Proof&, const std::shared_ptr&); - + /** + * @brief Iterate through all circuits and prove each, appending a recursive verifier of the previous proof after + * the first step. + * @param source A source of circuits, possibly accompanied by precomputed verification keys. + * @param cache_vks If true, case the verification key that is computed. + * @return HonkProof A proof of the final circuit which through recursive verification, demonstrates that all + * circuits were satisfied, or one of them was not satisfied, depending on whether it verifies or does not verify. + */ HonkProof prove(CircuitSource& source, const bool cache_vks = false); - static bool verify(const Proof&, const std::shared_ptr&); + /** + * @brief Verify an IVC proof. + * @details This is here for a consistent interface, but it's a mere convenience--the function just wraps UltraHonk + * verification. + * + * @param proof + * @param vk + * @return true All circuits provided have been satisfied. + * @return false Some circuit provided was not satisfied. + */ + static bool verify(const Proof& proof, const std::shared_ptr& vk); + /** + * @brief Prove and then verify the proof. This is used for testing. + */ bool prove_and_verify(CircuitSource& source, const bool cache_vks = false); + /** + * @brief Compute the verification key of each circuit provided by the source. + * @details This runs a full IVC prover. Our public interface provides a faster but more brittle method via dummy + * witnesses. This is a useful fallback that we might want for debugging. Currently it is used to test the prover + * flow that using precomputed verification keys. + */ std::vector> compute_vks(CircuitSource& source); }; } // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp index 958e9e5f50e..c350c4349b1 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp @@ -22,10 +22,6 @@ class UltraVanillaClientIVCTests : public ::testing::Test { using VK = Flavor::VerificationKey; using PK = DeciderProvingKey_; - /** - * @brief A test utility for generating alternating mock app and kernel circuits and precomputing verification keys - * - */ class MockCircuitSource : public CircuitSource { std::vector _sizes; std::vector> _vks; @@ -58,29 +54,9 @@ class UltraVanillaClientIVCTests : public ::testing::Test { return _sizes.size(); } }; - - // /** - // * @brief Tamper with a proof by finding the first non-zero value and incrementing it by 1 - // * - // */ - // static void tamper_with_proof(FoldProof& proof) - // { - // for (auto& val : proof) { - // if (val > 0) { - // val += 1; - // break; - // } - // } - // } }; -/** - * @brief A simple-as-possible test demonstrating IVC for two mock circuits - * @details When accumulating only two circuits, only a single round of folding is performed thus no recursive - * verfication occurs. - * - */ -TEST_F(UltraVanillaClientIVCTests, Basic) +TEST_F(UltraVanillaClientIVCTests, TwoCircuits) { static constexpr size_t LOG_SIZE = 10; UltraVanillaClientIVC ivc(1 << LOG_SIZE); @@ -88,23 +64,17 @@ TEST_F(UltraVanillaClientIVCTests, Basic) EXPECT_TRUE(ivc.prove_and_verify(circuit_source)); }; -/** - * @brief A simple test demonstrating IVC for four mock circuits, which is slightly more than minimal. - * @details When accumulating only four circuits, we execute all the functionality of a full UltraVanillaClientIVC - run. - * - */ -TEST_F(UltraVanillaClientIVCTests, BasicFour) +TEST_F(UltraVanillaClientIVCTests, ThreeCircuits) { static constexpr size_t LOG_SIZE = 10; UltraVanillaClientIVC ivc(1 << LOG_SIZE); - MockCircuitSource circuit_source{ { LOG_SIZE, LOG_SIZE, LOG_SIZE, LOG_SIZE } }; + MockCircuitSource circuit_source{ { LOG_SIZE, LOG_SIZE, LOG_SIZE } }; EXPECT_TRUE(ivc.prove_and_verify(circuit_source)); }; /** * @brief Prove and verify accumulation of an arbitrary set of circuits using precomputed verification keys - * + * @details The tests precomputed the keys via one pass of the ivc prover, then it usese then in a second pass. */ TEST_F(UltraVanillaClientIVCTests, PrecomputedVerificationKeys) { @@ -118,98 +88,4 @@ TEST_F(UltraVanillaClientIVCTests, PrecomputedVerificationKeys) EXPECT_TRUE(ivc.prove_and_verify(circuit_source_with_vks)); }; -// /** -// * @brief Check that the IVC fails if an intermediate fold proof is invalid -// * @details When accumulating 4 circuits, there are 3 fold proofs to verify (the first two are recursively verfied -// and -// * the 3rd is verified as part of the IVC proof). Check that if any of one of these proofs is invalid, the IVC will -// * fail. -// * -// */ -// TEST_F(UltraVanillaClientIVCTests, BadProofFailure) -// { -// // Confirm that the IVC verifies if nothing is tampered with -// { -// UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; - -// MockCircuitProducer circuit_producer; - -// // Construct and accumulate a set of mocked private function execution circuits -// size_t NUM_CIRCUITS = 4; -// for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { -// auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); -// ivc.accumulate(circuit); -// } -// EXPECT_TRUE(ivc.prove_and_verify()); -// } - -// // The IVC throws an exception if the FIRST fold proof is tampered with -// { -// UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; - -// MockCircuitProducer circuit_producer; - -// // Construct and accumulate a set of mocked private function execution circuits -// size_t NUM_CIRCUITS = 4; -// for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { -// if (idx == 3) { // At idx = 3, we've tampered with the one of the folding proofs so create the recursive -// // folding verifier will throw an error. -// EXPECT_ANY_THROW(circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5)); -// break; -// } -// auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); -// ivc.accumulate(circuit); - -// if (idx == 2) { -// EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation -// tamper_with_proof(ivc.verification_queue[0].proof); // tamper with first proof -// } -// } -// } - -// // The IVC fails if the SECOND fold proof is tampered with -// { -// UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; - -// MockCircuitProducer circuit_producer; - -// // Construct and accumulate a set of mocked private function execution circuits -// size_t NUM_CIRCUITS = 4; -// for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { -// if (idx == 3) { // At idx = 3, we've tampered with the one of the folding proofs so create the recursive -// // folding verifier will throw an error. -// EXPECT_ANY_THROW(circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5)); -// break; -// } -// auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); -// ivc.accumulate(circuit); - -// if (idx == 2) { -// EXPECT_EQ(ivc.verification_queue.size(), 2); // two proofs after 3 calls to accumulation -// tamper_with_proof(ivc.verification_queue[1].proof); // tamper with second proof -// } -// } -// } - -// // The IVC fails if the 3rd/FINAL fold proof is tampered with -// { -// UltraVanillaClientIVC ivc{ { SMALL_TEST_STRUCTURE } }; - -// MockCircuitProducer circuit_producer; - -// // Construct and accumulate a set of mocked private function execution circuits -// size_t NUM_CIRCUITS = 4; -// for (size_t idx = 0; idx < NUM_CIRCUITS; ++idx) { -// auto circuit = circuit_producer.create_next_circuit(ivc, /*log2_num_gates=*/5); -// ivc.accumulate(circuit); -// } - -// // Only a single proof should be present in the queue when verification of the IVC is performed -// EXPECT_EQ(ivc.verification_queue.size(), 1); -// tamper_with_proof(ivc.verification_queue[0].proof); // tamper with the final fold proof - -// EXPECT_ANY_THROW(ivc.prove_and_verify()); -// } - -// EXPECT_TRUE(true); -// }; +// TODO(https://github.com/AztecProtocol/barretenberg/issues/1177) Implement failure tests \ No newline at end of file From 63870a14023cbe2a97312212732285f29bd32db4 Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 19 Dec 2024 22:04:53 +0000 Subject: [PATCH 7/9] Check pairing points --- .../ultra_vanilla_client_ivc.cpp | 12 +++++++++--- .../ultra_vanilla_client_ivc.hpp | 7 ++++--- .../ultra_vanilla_client_ivc.test.cpp | 8 +++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp index 4e3e0e54fcf..77b22b521e2 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp @@ -22,6 +22,7 @@ HonkProof UltraVanillaClientIVC::prove(CircuitSource& source, const bool } circuit.add_pairing_point_accumulator(accumulator_indices); + accumulator_value = { accumulator.P0.get_value(), accumulator.P1.get_value() }; auto proving_key = std::make_shared(circuit); @@ -46,8 +47,13 @@ bool UltraVanillaClientIVC::verify(const Proof& proof, const std::shared_ptr { UltraVerifier verifer{ vk }; - const bool verified = verifer.verify_proof(proof); - vinfo("verified: ", verified); + bool verified = verifer.verify_proof(proof); + vinfo("proof verified: ", verified); + + using VerifierCommitmentKey = typename Flavor::VerifierCommitmentKey; + auto pcs_verification_key = std::make_shared(); + verified &= pcs_verification_key->pairing_check(accumulator_value[0], accumulator_value[1]); + vinfo("pairing verified: ", verified); return verified; } @@ -66,7 +72,7 @@ bool UltraVanillaClientIVC::prove_and_verify(CircuitSource& source, cons vinfo("time to call UltraVanillaClientIVC::prove: ", diff.count(), " ms."); start = end; - const bool verified = verify(previous_proof, previous_vk); + bool verified = verify(previous_proof, previous_vk); end = std::chrono::steady_clock::now(); diff = std::chrono::duration_cast(end - start); diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp index 243164e807e..b035ce672ce 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp @@ -68,6 +68,7 @@ class UltraVanillaClientIVC { Proof previous_proof; std::shared_ptr previous_vk; Accumulator accumulator; + std::array accumulator_value; PairingPointAccumulatorIndices accumulator_indices; std::vector> vk_cache; @@ -87,15 +88,15 @@ class UltraVanillaClientIVC { /** * @brief Verify an IVC proof. - * @details This is here for a consistent interface, but it's a mere convenience--the function just wraps UltraHonk - * verification. + * @details This verifies the final proof, including (natively) checking the pairing of the two points in the final + * accumulator. * * @param proof * @param vk * @return true All circuits provided have been satisfied. * @return false Some circuit provided was not satisfied. */ - static bool verify(const Proof& proof, const std::shared_ptr& vk); + bool verify(const Proof& proof, const std::shared_ptr& vk); /** * @brief Prove and then verify the proof. This is used for testing. diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp index c350c4349b1..653b7d8f641 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.test.cpp @@ -81,11 +81,13 @@ TEST_F(UltraVanillaClientIVCTests, PrecomputedVerificationKeys) static constexpr size_t LOG_SIZE = 10; - UltraVanillaClientIVC ivc(1 << LOG_SIZE); + UltraVanillaClientIVC ivc_1(1 << LOG_SIZE); MockCircuitSource circuit_source_no_vks{ { LOG_SIZE, LOG_SIZE } }; - auto vks = ivc.compute_vks(circuit_source_no_vks); + auto vks = ivc_1.compute_vks(circuit_source_no_vks); + + UltraVanillaClientIVC ivc_2(1 << LOG_SIZE); // need to refactor accumulator_value use to reuse ivc_1 MockCircuitSource circuit_source_with_vks{ circuit_source_no_vks, vks }; - EXPECT_TRUE(ivc.prove_and_verify(circuit_source_with_vks)); + EXPECT_TRUE(ivc_2.prove_and_verify(circuit_source_with_vks)); }; // TODO(https://github.com/AztecProtocol/barretenberg/issues/1177) Implement failure tests \ No newline at end of file From 4a96d55f419076c3d7370cfdaefd7559244796ff Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 19 Dec 2024 22:07:12 +0000 Subject: [PATCH 8/9] Remove TODO --- .../plonk_recursion/aggregation_state/aggregation_state.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp b/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp index 18e94db14b0..4eba0fa247f 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp @@ -137,7 +137,6 @@ template PairingPointAccumulatorIndices init_default_agg_obj_ // TODO(https://github.com/AztecProtocol/barretenberg/issues/911): These are pairing points extracted from a valid // proof. This is a workaround because we can't represent the point at infinity in biggroup yet. - // WORKTODO: Add assert that this is a valid initial starting point. PairingPointAccumulatorIndices agg_obj_indices = {}; fq x0("0x031e97a575e9d05a107acb64952ecab75c020998797da7842ab5d6d1986846cf"); fq y0("0x178cbf4206471d722669117f9758a4c410db10a01750aebb5666547acf8bd5a4"); From 7bf77d22d242ae83148b873c05cae63afac920e6 Mon Sep 17 00:00:00 2001 From: Cody Date: Thu, 2 Jan 2025 14:41:21 +0000 Subject: [PATCH 9/9] Remove unused mock databus producer --- .../mock_circuit_producer.hpp | 73 ------------------- 1 file changed, 73 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp index c1d6abaf6c9..75f451c13bc 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp @@ -9,77 +9,6 @@ using namespace bb; namespace { -/** - * @brief Test utility for coordinating passing of databus data between mocked private function execution circuits - * @details Facilitates testing of the databus consistency checks that establish the correct passing of databus data - * between circuits. Generates arbitrary return data for each app/kernel. Sets the kernel calldata and - * secondary_calldata based respectively on the previous kernel return data and app return data. - */ -class MockDatabusProducer { - private: - using ClientCircuit = UltraVanillaClientIVC::ClientCircuit; - using Flavor = MegaFlavor; - using FF = Flavor::FF; - using BusDataArray = std::vector; - - static constexpr size_t BUS_ARRAY_SIZE = 3; // arbitrary length of mock bus inputs - BusDataArray app_return_data; - BusDataArray kernel_return_data; - - FF dummy_return_val = 1; // use simple return val for easier test debugging - - BusDataArray generate_random_bus_array() - { - BusDataArray result; - for (size_t i = 0; i < BUS_ARRAY_SIZE; ++i) { - result.emplace_back(dummy_return_val); - } - dummy_return_val += 1; - return result; - } - - public: - /** - * @brief Update the app return data and populate it in the app circuit - */ - void populate_app_databus(ClientCircuit& circuit) - { - app_return_data = generate_random_bus_array(); - for (auto& val : app_return_data) { - circuit.add_public_return_data(circuit.add_variable(val)); - } - }; - - /** - * @brief Populate the calldata and secondary calldata in the kernel from respectively the previous kernel and app - * return data. Update and populate the return data for the present kernel. - */ - void populate_kernel_databus(ClientCircuit& circuit) - { - // Populate calldata from previous kernel return data (if it exists) - for (auto& val : kernel_return_data) { - circuit.add_public_calldata(circuit.add_variable(val)); - } - // Populate secondary_calldata from app return data (if it exists), then clear the app return data - for (auto& val : app_return_data) { - circuit.add_public_secondary_calldata(circuit.add_variable(val)); - } - app_return_data.clear(); - - // Mock the return data for the present kernel circuit - kernel_return_data = generate_random_bus_array(); - for (auto& val : kernel_return_data) { - circuit.add_public_return_data(circuit.add_variable(val)); - } - }; - - /** - * @brief Add an arbitrary value to the app return data. This leads to a descrepency between the values used by the - * app itself and the secondary_calldata values in the kernel that will be set based on these tampered values. - */ - void tamper_with_app_return_data() { app_return_data.emplace_back(17); } -}; - /** * @brief Manage the construction of mock app/kernel circuits for the private function execution setting * @details Per the medium complexity benchmark spec, the first app circuit is size 2^19. Subsequent app and kernel @@ -94,8 +23,6 @@ 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 public: