-
Notifications
You must be signed in to change notification settings - Fork 614
feat: Encapsulated UltraHonk Vanilla IVC #10900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
4dedf77
0fb8614
42735b6
542a0f2
eb06a57
ba62f0b
63870a1
4a96d55
0d979cd
7bf77d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| barretenberg_module(ultra_vanilla_client_ivc stdlib_honk_verifier) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<FF>; | ||
|
|
||
| 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<std::shared_ptr<VerificationKey>> 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| #include "barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp" | ||
| #include "barretenberg/ultra_honk/oink_prover.hpp" | ||
|
|
||
| namespace bb { | ||
|
|
||
| void UltraVanillaClientIVC::accumulate(Circuit& circuit, const Proof& proof, const std::shared_ptr<VK>& vk) | ||
| { | ||
| RecursiveVerifier verifier{ &circuit, std::make_shared<RecursiveVK>(&circuit, vk) }; | ||
| Accumulator agg_obj = stdlib::recursion::init_default_aggregation_state<Circuit, stdlib::bn254<Circuit>>(circuit); | ||
| accumulator = verifier.verify_proof(proof, agg_obj).agg_obj; | ||
| } | ||
|
|
||
| HonkProof UltraVanillaClientIVC::prove(CircuitSource<Flavor>& source, const bool cache_vks) | ||
| { | ||
| for (size_t step = 0; step < source.num_circuits(); step++) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this CircuitSource interface meant only for testing or are you imagining that you'd have some version of the class that would be loaded up with acir bytecode in actual usage?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I think this is an interface that also works for processing ACIR one bytecode/partial witness pair at a time (for memory efficiency). |
||
| auto [circuit, vk] = source.next(); | ||
| 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); | ||
| accumulator_value = { accumulator.P0.get_value(), accumulator.P1.get_value() }; | ||
|
|
||
| auto proving_key = std::make_shared<PK>(circuit); | ||
|
|
||
| if (step < source.num_circuits() - 1) { | ||
| UltraProver prover{ proving_key, commitment_key }; | ||
| previous_proof = prover.construct_proof(); | ||
| } else { | ||
| // TODO(https://github.com/AztecProtocol/barretenberg/issues/1176) Use UltraZKProver when it exists | ||
| UltraProver prover{ proving_key, commitment_key }; | ||
| previous_proof = prover.construct_proof(); | ||
| } | ||
|
|
||
| previous_vk = vk ? vk : std::make_shared<VK>(proving_key->proving_key); | ||
| if (cache_vks) { | ||
| vk_cache.push_back(previous_vk); | ||
| } | ||
| } | ||
| return previous_proof; | ||
| }; | ||
|
|
||
| bool UltraVanillaClientIVC::verify(const Proof& proof, const std::shared_ptr<VK>& vk) | ||
| { | ||
|
|
||
| UltraVerifier verifer{ vk }; | ||
| bool verified = verifer.verify_proof(proof); | ||
| vinfo("proof verified: ", verified); | ||
|
|
||
| using VerifierCommitmentKey = typename Flavor::VerifierCommitmentKey; | ||
| auto pcs_verification_key = std::make_shared<VerifierCommitmentKey>(); | ||
| verified &= pcs_verification_key->pairing_check(accumulator_value[0], accumulator_value[1]); | ||
| vinfo("pairing verified: ", verified); | ||
| return verified; | ||
| } | ||
|
|
||
| /** | ||
| * @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(CircuitSource<Flavor>& source, const bool cache_vks) | ||
| { | ||
| auto start = std::chrono::steady_clock::now(); | ||
| prove(source, cache_vks); | ||
| auto end = std::chrono::steady_clock::now(); | ||
| auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); | ||
| vinfo("time to call UltraVanillaClientIVC::prove: ", diff.count(), " ms."); | ||
|
|
||
| start = end; | ||
| bool verified = verify(previous_proof, previous_vk); | ||
| end = std::chrono::steady_clock::now(); | ||
|
|
||
| diff = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); | ||
| vinfo("time to verify UltraVanillaClientIVC proof: ", diff.count(), " ms."); | ||
|
|
||
| return verified; | ||
| } | ||
|
|
||
| std::vector<std::shared_ptr<UltraFlavor::VerificationKey>> UltraVanillaClientIVC::compute_vks( | ||
| CircuitSource<Flavor>& source) | ||
| { | ||
| prove_and_verify(source, /*cache_vks=*/true); | ||
| return vk_cache; | ||
| }; | ||
|
|
||
| } // namespace bb | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just copied from its other location yeah? maybe you meant to do something with it but for now it should just be deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, deleted