-
Notifications
You must be signed in to change notification settings - Fork 310
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
feat: Encapsulated UltraHonk Vanilla IVC #10900
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
4dedf77
cp and reduce a bit
codygunton 0fb8614
WIP setting up IVC class
codygunton 42735b6
Basic and BasicFour
codygunton 542a0f2
Precomputation of VKs
codygunton eb06a57
Ref issue
codygunton ba62f0b
Comments and cleanup
codygunton 63870a1
Check pairing points
codygunton 4a96d55
Remove TODO
codygunton 0d979cd
Merge remote-tracking branch 'origin/master' into cg/ivcu
codygunton 7bf77d2
Remove unused mock databus producer
codygunton File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/CMakeLists.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
barretenberg_module(ultra_vanilla_client_ivc stdlib_honk_verifier) |
90 changes: 90 additions & 0 deletions
90
barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/mock_circuit_producer.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
|
||
#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 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; | ||
|
||
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 |
91 changes: 91 additions & 0 deletions
91
barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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++) { | ||
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 |
114 changes: 114 additions & 0 deletions
114
barretenberg/cpp/src/barretenberg/ultra_vanilla_client_ivc/ultra_vanilla_client_ivc.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
#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 <algorithm> | ||
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 <typename Flavor, | ||
typename Builder = typename Flavor::CircuitBuilder, | ||
typename VK = typename Flavor::VerificationKey> | ||
|
||
class CircuitSource { | ||
public: | ||
struct Output { | ||
Builder circuit; | ||
std::shared_ptr<VK> vk; | ||
}; | ||
|
||
virtual Output next(); | ||
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: | ||
using Flavor = UltraFlavor; | ||
using FF = Flavor::FF; | ||
using Circuit = UltraCircuitBuilder; | ||
using PK = DeciderProvingKey_<Flavor>; | ||
using VK = UltraFlavor::VerificationKey; | ||
using Proof = HonkProof; | ||
|
||
using RecursiveFlavor = UltraRecursiveFlavor_<Circuit>; | ||
using RecursiveVerifier = stdlib::recursion::honk::UltraRecursiveVerifier_<RecursiveFlavor>; | ||
using RecursiveVK = RecursiveFlavor::VerificationKey; | ||
using Curve = stdlib::bn254<Circuit>; | ||
using Accumulator = stdlib::recursion::aggregation_state<Curve>; | ||
|
||
/** | ||
* @brief Append a recursive verifier and update the accumulator. | ||
*/ | ||
void accumulate(Circuit&, const Proof&, const std::shared_ptr<VK>&); | ||
|
||
public: | ||
std::shared_ptr<CommitmentKey<curve::BN254>> commitment_key; | ||
Proof previous_proof; | ||
std::shared_ptr<VK> previous_vk; | ||
Accumulator accumulator; | ||
std::array<curve::BN254::AffineElement, 2> accumulator_value; | ||
PairingPointAccumulatorIndices accumulator_indices; | ||
std::vector<std::shared_ptr<VK>> vk_cache; | ||
|
||
UltraVanillaClientIVC(const size_t dyadic_size = 1 << 20) | ||
: commitment_key(std::make_shared<CommitmentKey<curve::BN254>>(dyadic_size)) | ||
{} | ||
|
||
/** | ||
* @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<Flavor>& source, const bool cache_vks = false); | ||
|
||
/** | ||
* @brief Verify an IVC proof. | ||
* @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. | ||
*/ | ||
bool verify(const Proof& proof, const std::shared_ptr<VK>& vk); | ||
|
||
/** | ||
* @brief Prove and then verify the proof. This is used for testing. | ||
*/ | ||
bool prove_and_verify(CircuitSource<Flavor>& 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<std::shared_ptr<VK>> compute_vks(CircuitSource<Flavor>& source); | ||
}; | ||
} // namespace bb |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
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?
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.
Yeah I think this is an interface that also works for processing ACIR one bytecode/partial witness pair at a time (for memory efficiency).