diff --git a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp index d18d18131e7..98c960ac125 100644 --- a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.cpp @@ -1,4 +1,5 @@ #include "barretenberg/aztec_ivc/aztec_ivc.hpp" +#include "barretenberg/ultra_honk/oink_prover.hpp" namespace bb { @@ -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(&circuit, verifier_accumulator); - auto stdlib_vkey = std::make_shared(&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(&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(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(&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(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(&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(verifier_accum->get_value()); + // Initialize the gate challenges to zero for use in first round of folding + verifier_accumulator->gate_challenges = + std::vector(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(); @@ -71,17 +102,26 @@ void AztecIVC::accumulate(ClientCircuit& circuit, const std::shared_ptr(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(instance_vk); + OinkProver 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(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) diff --git a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp index c203e6036b7..ead9390e54f 100644 --- a/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp +++ b/barretenberg/cpp/src/barretenberg/aztec_ivc/aztec_ivc.hpp @@ -42,12 +42,13 @@ class AztecIVC { using ECCVMVerificationKey = bb::ECCVMFlavor::VerificationKey; using TranslatorVerificationKey = bb::TranslatorFlavor::VerificationKey; - using GURecursiveFlavor = MegaRecursiveFlavor_; - using RecursiveVerifierInstances = bb::stdlib::recursion::honk::RecursiveVerifierInstances_; + using RecursiveFlavor = MegaRecursiveFlavor_; + using RecursiveVerifierInstances = bb::stdlib::recursion::honk::RecursiveVerifierInstances_; using RecursiveVerifierInstance = RecursiveVerifierInstances::Instance; - using RecursiveVerificationKey = RecursiveVerifierInstances::VerificationKey; + using RecursiveVerificationKey = RecursiveFlavor::VerificationKey; using FoldingRecursiveVerifier = bb::stdlib::recursion::honk::ProtoGalaxyRecursiveVerifier_; + using OinkRecursiveVerifier = stdlib::recursion::honk::OinkRecursiveVerifier_; using DataBusDepot = stdlib::DataBusDepot; @@ -62,9 +63,11 @@ class AztecIVC { MSGPACK_FIELDS(folding_proof, decider_proof, goblin_proof); }; - struct FoldingVerifierInputs { - FoldProof proof; + enum class QUEUE_TYPE { OINK, PG }; + struct RecursiveVerifierInputs { + std::vector proof; // oink or PG std::shared_ptr instance_vk; + QUEUE_TYPE type; }; // Utility for tracking the max size of each block across the full IVC @@ -82,7 +85,7 @@ class AztecIVC { std::shared_ptr instance_vk; // verification key for instance to be folded // Set of pairs of {fold_proof, verification_key} to be recursively verified - std::vector verification_queue; + std::vector verification_queue; // Set of merge proofs to be recursively verified std::vector merge_verification_queue; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/oink_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/oink_recursive_verifier.cpp index ab1b5dc32e1..2a8401d577e 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/oink_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/oink_recursive_verifier.cpp @@ -18,10 +18,21 @@ OinkRecursiveVerifier_::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 +OinkRecursiveVerifier_::OinkRecursiveVerifier_(Builder* builder, + const std::shared_ptr& instance, + std::string domain_separator) + : instance(instance) + , builder(builder) + , domain_separator(std::move(domain_separator)) +{} + +template void OinkRecursiveVerifier_::verify_proof(OinkProof& proof) +{ + transcript = std::make_shared(proof); + verify(); +} + template void OinkRecursiveVerifier_::verify() { using CommitmentLabels = typename Flavor::CommitmentLabels; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/oink_recursive_verifier.hpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/oink_recursive_verifier.hpp index ef6989e9814..4147e6908fa 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/oink_recursive_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/oink_recursive_verifier.hpp @@ -17,14 +17,46 @@ template class OinkRecursiveVerifier_ { using RelationSeparator = typename Flavor::RelationSeparator; using Transcript = bb::BaseTranscript>; using WitnessCommitments = typename Flavor::WitnessCommitments; + using OinkProof = std::vector; + /** + * @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, std::shared_ptr 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, + 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; Builder* builder; std::shared_ptr transcript; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp index c9b0fcb751d..ed204d91de8 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/databus/databus.hpp @@ -71,6 +71,7 @@ template class DataBusDepot { using RecursiveFlavor = MegaRecursiveFlavor_; using RecursiveVerifierInstances = bb::stdlib::recursion::honk::RecursiveVerifierInstances_; + 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; @@ -78,67 +79,50 @@ template class DataBusDepot { /** * @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(const WitnessCommitments& commitments, + const std::vector& public_inputs, + const 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); }; /** @@ -150,7 +134,7 @@ template class DataBusDepot { * @param commitment * @param is_kernel Indicates whether the return data being propagated is from a kernel or an app */ - void propagate_commitment_via_public_inputs(Commitment& commitment, bool is_kernel = false) + void propagate_commitment_via_public_inputs(const Commitment& commitment, bool is_kernel = false) { auto context = commitment.get_context(); @@ -177,11 +161,11 @@ template class DataBusDepot { * @param return_data_commitment_limbs_start_idx Start index for range where commitment limbs are stored * @return Commitment */ - Commitment reconstruct_commitment_from_public_inputs(const std::span public_inputs, - size_t& return_data_commitment_limbs_start_idx) + Commitment reconstruct_commitment_from_public_inputs(std::span public_inputs, + const size_t& return_data_commitment_limbs_start_idx) { // Extract from the public inputs the limbs needed reconstruct a commitment - std::span return_data_commitment_limbs{ + std::span return_data_commitment_limbs{ public_inputs.data() + return_data_commitment_limbs_start_idx, NUM_FR_LIMBS_PER_COMMITMENT }; return reconstruct_commitment_from_fr_limbs(return_data_commitment_limbs); @@ -194,10 +178,10 @@ template class DataBusDepot { * @param limbs * @return Commitment */ - Commitment reconstruct_commitment_from_fr_limbs(std::span limbs) + Commitment reconstruct_commitment_from_fr_limbs(std::span limbs) { - std::span x_limbs{ limbs.data(), NUM_FR_LIMBS_PER_FQ }; - std::span y_limbs{ limbs.data() + NUM_FR_LIMBS_PER_FQ, NUM_FR_LIMBS_PER_FQ }; + std::span x_limbs{ limbs.data(), NUM_FR_LIMBS_PER_FQ }; + std::span y_limbs{ limbs.data() + NUM_FR_LIMBS_PER_FQ, NUM_FR_LIMBS_PER_FQ }; const Fq x = reconstruct_fq_from_fr_limbs(x_limbs); const Fq y = reconstruct_fq_from_fr_limbs(y_limbs); @@ -210,7 +194,7 @@ template class DataBusDepot { * @param limbs * @return Fq */ - Fq reconstruct_fq_from_fr_limbs(std::span& limbs) + Fq reconstruct_fq_from_fr_limbs(std::span& limbs) { const Fr l0 = limbs[0]; const Fr l1 = limbs[1]; @@ -223,7 +207,7 @@ template class DataBusDepot { return Fq(l0, l1, l2, l3, /*can_overflow=*/false); } - void assert_equality_of_commitments(Commitment& P0, Commitment& P1) + void assert_equality_of_commitments(const Commitment& P0, const Commitment& P1) { if (P0.get_value() != P1.get_value()) { // debug print indicating consistency check failure info("DataBusDepot: Databus consistency check failed!"); @@ -238,7 +222,7 @@ template class DataBusDepot { * @param point A biggroup element * @return std::array */ - std::array get_witness_indices_for_commitment(Commitment& point) + std::array get_witness_indices_for_commitment(const Commitment& point) { return { point.x.binary_basis_limbs[0].element.normalize().witness_index, point.x.binary_basis_limbs[1].element.normalize().witness_index, diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp index 16c02cfd1cd..ff49fcfec28 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp @@ -50,7 +50,7 @@ template class OinkProver { using RelationSeparator = typename Flavor::RelationSeparator; OinkProver(std::shared_ptr instance, - const std::shared_ptr& transcript, + const std::shared_ptr& transcript = std::make_shared(), std::string domain_separator = "") : instance(instance) , transcript(transcript)