Skip to content
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: Use oink in IVC #8161

Merged
merged 11 commits into from
Aug 26, 2024
78 changes: 59 additions & 19 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "barretenberg/aztec_ivc/aztec_ivc.hpp"
#include "barretenberg/ultra_honk/oink_prover.hpp"

namespace bb {

Expand All @@ -13,23 +14,53 @@ void AztecIVC::complete_kernel_circuit_logic(ClientCircuit& circuit)
{
circuit.databus_propagation_data.is_kernel = true;

// The folding verification queue should be either empty or contain two fold proofs
ASSERT(verification_queue.empty() || verification_queue.size() == 2);

for (auto& [proof, vkey] : verification_queue) {

// Construct stdlib accumulator, vkey and proof
auto stdlib_verifier_accum = std::make_shared<RecursiveVerifierInstance>(&circuit, verifier_accumulator);
auto stdlib_vkey = std::make_shared<RecursiveVerificationKey>(&circuit, vkey);
// Peform recursive verification and databus consistency checks for each entry in the verification queue
for (auto& [proof, vkey, type] : verification_queue) {
// Construct stdlib verification key and proof
auto stdlib_proof = bb::convert_proof_to_witness(&circuit, proof);
auto stdlib_vkey = std::make_shared<RecursiveVerificationKey>(&circuit, vkey);

// Perform folding recursive verification
FoldingRecursiveVerifier verifier{ &circuit, stdlib_verifier_accum, { stdlib_vkey } };
auto verifier_accum = verifier.verify_folding_proof(stdlib_proof);
verifier_accumulator = std::make_shared<VerifierInstance>(verifier_accum->get_value());

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier.instances);
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<RecursiveVerifierInstance>(&circuit, verifier_accumulator);

// Perform folding recursive verification to update the verifier accumulator
FoldingRecursiveVerifier verifier{ &circuit, stdlib_verifier_accum, { stdlib_vkey } };
auto verifier_accum = verifier.verify_folding_proof(stdlib_proof);

// Extract native verifier accumulator from the stdlib accum for use on the next round
verifier_accumulator = std::make_shared<VerifierInstance>(verifier_accum->get_value());

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier.instances[1]->witness_commitments,
verifier.instances[1]->public_inputs,
verifier.instances[1]->verification_key->databus_propagation_data);
break;
}
case QUEUE_TYPE::OINK: {
// Construct an incomplete stdlib verifier accumulator from the corresponding stdlib verification key
auto verifier_accum = std::make_shared<RecursiveVerifierInstance>(&circuit, stdlib_vkey);

// Perform oink recursive verification to complete the initial verifier accumulator
OinkRecursiveVerifier oink{ &circuit, verifier_accum };
oink.verify_proof(stdlib_proof);
verifier_accum->is_accumulator = true; // indicate to PG that it should not run oink on this instance

// Extract native verifier accumulator from the stdlib accum for use on the next round
verifier_accumulator = std::make_shared<VerifierInstance>(verifier_accum->get_value());
// Initialize the gate challenges to zero for use in first round of folding
verifier_accumulator->gate_challenges =
std::vector<FF>(verifier_accum->verification_key->log_circuit_size, 0);

// Perform databus commitment consistency checks and propagate return data commitments via public inputs
bus_depot.execute(verifier_accum->witness_commitments,
verifier_accum->public_inputs,
verifier_accum->verification_key->databus_propagation_data);

break;
}
}
}
verification_queue.clear();

Expand Down Expand Up @@ -71,17 +102,26 @@ void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr<Verifica
// Set the instance verification key from precomputed if available, else compute it
instance_vk = precomputed_vk ? precomputed_vk : std::make_shared<VerificationKey>(prover_instance->proving_key);

// If this is the first circuit simply initialize the prover and verifier accumulator instances
// If this is the first circuit in the IVC, use oink to compute the completed instance and generate an oink proof
if (!initialized) {
fold_output.accumulator = prover_instance;
verifier_accumulator = std::make_shared<VerifierInstance>(instance_vk);
OinkProver<Flavor> oink_prover{ prover_instance };
oink_prover.prove();
prover_instance->is_accumulator = true; // indicate to PG that it should not run oink on this instance
// Initialize the gate challenges to zero for use in first round of folding
prover_instance->gate_challenges = std::vector<FF>(prover_instance->proving_key.log_circuit_size, 0);

fold_output.accumulator = prover_instance; // initialize the prover accum with the completed instance

// Add oink proof and corresponding verification key to the verification queue
verification_queue.emplace_back(oink_prover.transcript->proof_data, instance_vk, QUEUE_TYPE::OINK);

initialized = true;
} else { // Otherwise, fold the new instance into the accumulator
FoldingProver folding_prover({ fold_output.accumulator, prover_instance });
fold_output = folding_prover.prove();

// Add fold proof and corresponding verification key to the verification queue
verification_queue.emplace_back(fold_output.proof, instance_vk);
verification_queue.emplace_back(fold_output.proof, instance_vk, QUEUE_TYPE::PG);
}

// Track the maximum size of each block for all circuits porcessed (for debugging purposes only)
Expand Down
9 changes: 6 additions & 3 deletions barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ class AztecIVC {
using ECCVMVerificationKey = bb::ECCVMFlavor::VerificationKey;
using TranslatorVerificationKey = bb::TranslatorFlavor::VerificationKey;

using GURecursiveFlavor = MegaRecursiveFlavor_<bb::MegaCircuitBuilder>;
using RecursiveVerifierInstances = bb::stdlib::recursion::honk::RecursiveVerifierInstances_<GURecursiveFlavor, 2>;
using RecursiveFlavor = MegaRecursiveFlavor_<bb::MegaCircuitBuilder>;
using RecursiveVerifierInstances = bb::stdlib::recursion::honk::RecursiveVerifierInstances_<RecursiveFlavor, 2>;
using RecursiveVerifierInstance = RecursiveVerifierInstances::Instance;
using RecursiveVerificationKey = RecursiveVerifierInstances::VerificationKey;
using RecursiveVerificationKey = RecursiveFlavor::VerificationKey;
using FoldingRecursiveVerifier =
bb::stdlib::recursion::honk::ProtoGalaxyRecursiveVerifier_<RecursiveVerifierInstances>;
using OinkRecursiveVerifier = stdlib::recursion::honk::OinkRecursiveVerifier_<RecursiveFlavor>;

using DataBusDepot = stdlib::DataBusDepot<ClientCircuit>;

Expand All @@ -62,9 +63,11 @@ class AztecIVC {
MSGPACK_FIELDS(folding_proof, decider_proof, goblin_proof);
};

enum class QUEUE_TYPE { OINK, PG };
struct FoldingVerifierInputs {
FoldProof proof;
std::shared_ptr<VerificationKey> instance_vk;
QUEUE_TYPE type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this struct and the proof type inside should be renamed now because it's not about folding in the Oink case, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why not handle the merge steps using this queue as well?

Copy link
Contributor Author

@ledwards2225 ledwards2225 Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated the naming. You're right that the merge stuff could be handled in the same way. I didn't do it because there are no verification keys for the merge protocol and also because I'm hoping that the merge recursive verifier goes away altogether. If it doesn't tho, you're right that it should probably be made to conform

};

// Utility for tracking the max size of each block across the full IVC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,21 @@ OinkRecursiveVerifier_<Flavor>::OinkRecursiveVerifier_(Builder* builder,
, domain_separator(std::move(domain_separator))
{}

/**
* @brief This function constructs a recursive verifier circuit for a native Ultra Honk proof of a given flavor.
* @return Output aggregation object
*/
template <typename Flavor>
OinkRecursiveVerifier_<Flavor>::OinkRecursiveVerifier_(Builder* builder,
const std::shared_ptr<Instance>& instance,
std::string domain_separator)
: instance(instance)
, builder(builder)
, domain_separator(std::move(domain_separator))
{}

template <typename Flavor> void OinkRecursiveVerifier_<Flavor>::verify_proof(OinkProof& proof)
{
transcript = std::make_shared<Transcript>(proof);
verify();
}

template <typename Flavor> void OinkRecursiveVerifier_<Flavor>::verify()
{
using CommitmentLabels = typename Flavor::CommitmentLabels;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,46 @@ template <typename Flavor> class OinkRecursiveVerifier_ {
using RelationSeparator = typename Flavor::RelationSeparator;
using Transcript = bb::BaseTranscript<bb::stdlib::recursion::honk::StdlibTranscriptParams<Builder>>;
using WitnessCommitments = typename Flavor::WitnessCommitments;
using OinkProof = std::vector<FF>;

/**
* @brief Constructs an Oink Recursive Verifier with a transcript that has been instantiated externally.
* @details Used when oink recursive verification is part of a larger protocol for which a transcript already
* exists, e.g. Honk recursive verification.
*
* @param builder
* @param instance Incomplete verifier instance to be completed during verification
* @param transcript Transcript instantiated with an Oink proof (or a proof that contains an Oink proof).
* @param domain_separator string used for differentiating instances in the transcript (PG only)
*/
explicit OinkRecursiveVerifier_(Builder* builder,
const std::shared_ptr<Instance>& instance,
std::shared_ptr<Transcript> transcript,
std::string domain_separator = "");

/**
* @brief Constructs an Oink Recursive Verifier
*
* @param builder
* @param instance Incomplete verifier instance to be completed during verification
* @param domain_separator string used for differentiating instances in the transcript (PG only)
*/
explicit OinkRecursiveVerifier_(Builder* builder,
const std::shared_ptr<Instance>& instance,
std::string domain_separator = "");

/**
* @brief Constructs an oink recursive verifier circuit for an oink proof assumed to be contained in the transcript.
*
*/
void verify();

/**
* @brief Constructs an oink recursive verifier circuit for a provided oink proof.
*
*/
void verify_proof(OinkProof& proof);

std::shared_ptr<Instance> instance;
Builder* builder;
std::shared_ptr<Transcript> transcript;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,74 +71,58 @@ template <class Builder> class DataBusDepot {

using RecursiveFlavor = MegaRecursiveFlavor_<Builder>;
using RecursiveVerifierInstances = bb::stdlib::recursion::honk::RecursiveVerifierInstances_<RecursiveFlavor, 2>;
using WitnessCommitments = RecursiveFlavor::WitnessCommitments;

static constexpr size_t NUM_FR_LIMBS_PER_FQ = Fq::NUM_LIMBS;
static constexpr size_t NUM_FR_LIMBS_PER_COMMITMENT = NUM_FR_LIMBS_PER_FQ * 2;

/**
* @brief Execute circuit logic to establish proper transfer of databus data between circuits
* @details The databus mechanism establishes the transfer of data between two circuits (i-1 and i) in a third
* circuit (i+1) via commitment equality checks of the form [R_{i-1}] = [C_i]. In practice, circuit (i+1) is given
* access to [R_{i-1}] via the public inputs of \pi_i, and it has access to [C_i] directly from \pi_i. The
* consistency checks in circuit (i+1) are thus of the form \pi_i.public_inputs.[R_{i-1}] = \pi_i.[C_i]. This method
* peforms the two primary operations required for these checks: (1) extract commitments [R] from proofs received as
* private witnesses and propagate them to the next circuit via adding them to the public inputs. (2) Assert
* equality of commitments.
* circuit (i+1) via commitment equality checks of the form [R_{i-1}] = [C_i], where R and C represent return data
* and calldata, respectively. In practice, circuit (i+1) is given access to [R_{i-1}] via the public inputs of
* \pi_i, and it has access to [C_i] directly from \pi_i. The consistency checks in circuit (i+1) are thus of the
* form \pi_i.public_inputs.[R_{i-1}] = \pi_i.[C_i]. This method peforms the two primary operations required for
* these checks: (1) extract commitments [R] from proofs received as private witnesses and propagate them to the
* next circuit via adding them to the public inputs. (2) Assert equality of commitments.
*
* In Aztec private function execution, this mechanism is used as follows. Kernel circuit K_{i+1} must in general
* perform two databus consistency checks: (1) that the return_data of app circuit A_{i} was calldata to K_{i}, and
* (2) that the return_data of K_{i-1} was calldata to K_{i}. (Note that kernel circuits have two databus calldata
* columns). The relevant databus column commitments are extracted from non-accumulator verifier instances (which
* contain all witness polynomial commitments extracted from a proof in oink).
* perform two databus consistency checks: (1) that the return_data of app circuit A_{i} was secondary calldata to
* K_{i}, and (2) that the return_data of K_{i-1} was calldata to K_{i}.
*
* @param instances Completed verifier instances corresponding to prover instances that have been folded
* @param commitments Witness polynomial commitments for an instance that has been accumulated
* @param public_inputs The public inputs of that instance
* @param propagation_data Data about the presence of databus commitments on the public inputs of the instance
*/
void execute(RecursiveVerifierInstances& instances)
void execute(WitnessCommitments& commitments,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will you please make this const correct if it's not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, thanks. consts added to basically everything

std::vector<Fr>& public_inputs,
DatabusPropagationData& propagation_data)
{
// Upon completion of folding recursive verfication, the verifier contains two completed verifier instances
// which store data from a fold proof. The first is the instance into which we're folding and the second
// corresponds to an instance being folded.
auto inst_1 = instances[0]; // instance into which we're folding (an accumulator, except on the initial round)
auto inst_2 = instances[1]; // instance that has been folded

// The first folding round is a special case in that it folds an instance into a non-accumulator instance. The
// fold proof thus contains two oink proofs. The first oink proof (stored in first instance) contains the return
// data R_0' from the first app, and its calldata counterpart C_0' in the kernel will be contained in the second
// oink proof (stored in second instance). In this special case, we can check directly that \pi_0.R_0' =
// \pi_0.C_0', without having had to propagate the return data commitment via the public inputs.
if (!inst_1->is_accumulator) {
// Assert equality of \pi_0.R_0' and \pi_0.C_0'
auto& app_return_data = inst_1->witness_commitments.return_data; // \pi_0.R_0'
auto& secondary_calldata = inst_2->witness_commitments.secondary_calldata; // \pi_0.C_0'
assert_equality_of_commitments(app_return_data, secondary_calldata); // assert equality R_0' == C_0'
}

// Define aliases for members in the second (non-accumulator) instance
bool is_kernel_instance = inst_2->verification_key->databus_propagation_data.is_kernel;
auto& propagation_data = inst_2->verification_key->databus_propagation_data;
auto& public_inputs = inst_2->public_inputs;
auto& commitments = inst_2->witness_commitments;
// Flag indicating whether the input data corresponds to a kernel instance (else, an app instance). This is
// used to indicate whether the return data commitment being propagated belongs to a kernel or an app so that it
// can be checked against the appropriate calldata commitment in a subsequent round.
bool is_kernel_data = propagation_data.is_kernel;

// Assert equality between return data commitments propagated via the public inputs and the corresponding
// calldata commitment
if (is_kernel_instance) { // only kernels can contain commitments propagated via public inputs
if (propagation_data.contains_app_return_data_commitment) {
// Assert equality between the app return data commitment and the kernel secondary calldata commitment
size_t start_idx = propagation_data.app_return_data_public_input_idx;
Commitment app_return_data = reconstruct_commitment_from_public_inputs(public_inputs, start_idx);
assert_equality_of_commitments(app_return_data, commitments.secondary_calldata);
}

if (propagation_data.contains_kernel_return_data_commitment) {
// Assert equality between the previous kernel return data commitment and the kernel calldata commitment
size_t start_idx = propagation_data.kernel_return_data_public_input_idx;
Commitment kernel_return_data = reconstruct_commitment_from_public_inputs(public_inputs, start_idx);
assert_equality_of_commitments(kernel_return_data, commitments.calldata);
}
if (propagation_data.contains_app_return_data_commitment) { // public inputs contain [R]_app
ASSERT(is_kernel_data); // Only kernels should contain databus commitments in their public inputs
size_t start_idx = propagation_data.app_return_data_public_input_idx;
Commitment app_return_data = reconstruct_commitment_from_public_inputs(public_inputs, start_idx);
// App return data should correspond to the secondary calldata of the subsequent kernel
assert_equality_of_commitments(app_return_data, commitments.secondary_calldata);
}

if (propagation_data.contains_kernel_return_data_commitment) { // pub inputs contain [R]_kernel
ASSERT(is_kernel_data); // Only kernels should contain databus commitments in their public inputs
size_t start_idx = propagation_data.kernel_return_data_public_input_idx;
Commitment kernel_return_data = reconstruct_commitment_from_public_inputs(public_inputs, start_idx);
// Previous kernel return data should correspond to the calldata of the subsequent kernel
assert_equality_of_commitments(kernel_return_data, commitments.calldata);
}

// Propagate the return data commitment via the public inputs mechanism
propagate_commitment_via_public_inputs(commitments.return_data, is_kernel_instance);
propagate_commitment_via_public_inputs(commitments.return_data, is_kernel_data);
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ template <IsUltraFlavor Flavor> class OinkProver {
using RelationSeparator = typename Flavor::RelationSeparator;

OinkProver(std::shared_ptr<Instance> instance,
const std::shared_ptr<typename Flavor::Transcript>& transcript,
const std::shared_ptr<typename Flavor::Transcript>& transcript = std::make_shared<Transcript>(),
std::string domain_separator = "")
: instance(instance)
, transcript(transcript)
Expand Down
Loading